| /* |
| * Copyright (c) 2014-2016, Intel Corporation. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU Lesser General Public License, |
| * version 2.1, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
| * more details. |
| */ |
| #include <stdio.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #include <syslog.h> |
| #include <libkmod.h> |
| #include <sys/wait.h> |
| #include <uuid/uuid.h> |
| #include <sys/types.h> |
| #include <sys/ioctl.h> |
| #include <sys/select.h> |
| #include <linux/version.h> |
| |
| #include <util/kernel.h> |
| #include <ndctl/libndctl.h> |
| #include <daxctl/libdaxctl.h> |
| #ifdef HAVE_NDCTL_H |
| #include <linux/ndctl.h> |
| #else |
| #include <ndctl.h> |
| #endif |
| #include <test.h> |
| |
| #define BLKROGET _IO(0x12,94) /* get read-only status (0 = read_write) */ |
| #define BLKROSET _IO(0x12,93) /* set device read-only (0 = read-write) */ |
| |
| /* |
| * Kernel provider "nfit_test.0" produces an NFIT with the following attributes: |
| * |
| * (a) (b) DIMM BLK-REGION |
| * +-------------------+--------+--------+--------+ |
| * +------+ | pm0.0 | blk2.0 | pm1.0 | blk2.1 | 0 region2 |
| * | imc0 +--+- - - region0- - - +--------+ +--------+ |
| * +--+---+ | pm0.0 | blk3.0 | pm1.0 | blk3.1 | 1 region3 |
| * | +-------------------+--------v v--------+ |
| * +--+---+ | | |
| * | cpu0 | region1 |
| * +--+---+ | | |
| * | +----------------------------^ ^--------+ |
| * +--+---+ | blk4.0 | pm1.0 | blk4.0 | 2 region4 |
| * | imc1 +--+----------------------------| +--------+ |
| * +------+ | blk5.0 | pm1.0 | blk5.0 | 3 region5 |
| * +----------------------------+--------+--------+ |
| * |
| * *) In this layout we have four dimms and two memory controllers in one |
| * socket. Each unique interface ("blk" or "pmem") to DPA space |
| * is identified by a region device with a dynamically assigned id. |
| * |
| * *) The first portion of dimm0 and dimm1 are interleaved as REGION0. |
| * A single "pmem" namespace is created in the REGION0-"spa"-range |
| * that spans dimm0 and dimm1 with a user-specified name of "pm0.0". |
| * Some of that interleaved "spa" range is reclaimed as "bdw" |
| * accessed space starting at offset (a) into each dimm. In that |
| * reclaimed space we create two "bdw" "namespaces" from REGION2 and |
| * REGION3 where "blk2.0" and "blk3.0" are just human readable names |
| * that could be set to any user-desired name in the label. |
| * |
| * *) In the last portion of dimm0 and dimm1 we have an interleaved |
| * "spa" range, REGION1, that spans those two dimms as well as dimm2 |
| * and dimm3. Some of REGION1 allocated to a "pmem" namespace named |
| * "pm1.0" the rest is reclaimed in 4 "bdw" namespaces (for each |
| * dimm in the interleave set), "blk2.1", "blk3.1", "blk4.0", and |
| * "blk5.0". |
| * |
| * *) The portion of dimm2 and dimm3 that do not participate in the |
| * REGION1 interleaved "spa" range (i.e. the DPA address below |
| * offset (b) are also included in the "blk4.0" and "blk5.0" |
| * namespaces. Note, that this example shows that "bdw" namespaces |
| * don't need to be contiguous in DPA-space. |
| * |
| * Kernel provider "nfit_test.1" produces an NFIT with the following attributes: |
| * |
| * region2 |
| * +---------------------+ |
| * |---------------------| |
| * || pm2.0 || |
| * |---------------------| |
| * +---------------------+ |
| * |
| * *) Describes a simple system-physical-address range with a non-aliasing backing |
| * dimm. |
| */ |
| |
| static const char *NFIT_PROVIDER0 = "nfit_test.0"; |
| static const char *NFIT_PROVIDER1 = "nfit_test.1"; |
| #define SZ_4K 0x00001000 |
| #define SZ_128K 0x00020000 |
| #define SZ_7M 0x00700000 |
| #define SZ_2M 0x00200000 |
| #define SZ_8M 0x00800000 |
| #define SZ_11M 0x00b00000 |
| #define SZ_12M 0x00c00000 |
| #define SZ_16M 0x01000000 |
| #define SZ_18M 0x01200000 |
| #define SZ_20M 0x01400000 |
| #define SZ_27M 0x01b00000 |
| #define SZ_28M 0x01c00000 |
| #define SZ_32M 0x02000000 |
| #define SZ_64M 0x04000000 |
| #define SZ_1G 0x40000000 |
| |
| struct dimm { |
| unsigned int handle; |
| unsigned int phys_id; |
| unsigned int subsystem_vendor; |
| unsigned short manufacturing_date; |
| unsigned char manufacturing_location; |
| union { |
| unsigned long flags; |
| struct { |
| unsigned int f_arm:1; |
| unsigned int f_save:1; |
| unsigned int f_flush:1; |
| unsigned int f_smart:1; |
| unsigned int f_restore:1; |
| }; |
| }; |
| int formats; |
| int format[2]; |
| }; |
| |
| #define DIMM_HANDLE(n, s, i, c, d) \ |
| (((n & 0xfff) << 16) | ((s & 0xf) << 12) | ((i & 0xf) << 8) \ |
| | ((c & 0xf) << 4) | (d & 0xf)) |
| static struct dimm dimms0[] = { |
| { DIMM_HANDLE(0, 0, 0, 0, 0), 0, 0, 2016, 10, { 0 }, 2, { 0x201, 0x301, }, }, |
| { DIMM_HANDLE(0, 0, 0, 0, 1), 1, 0, 2016, 10, { 0 }, 2, { 0x201, 0x301, }, }, |
| { DIMM_HANDLE(0, 0, 1, 0, 0), 2, 0, 2016, 10, { 0 }, 2, { 0x201, 0x301, }, }, |
| { DIMM_HANDLE(0, 0, 1, 0, 1), 3, 0, 2016, 10, { 0 }, 2, { 0x201, 0x301, }, }, |
| }; |
| |
| static struct dimm dimms1[] = { |
| { |
| DIMM_HANDLE(0, 0, 0, 0, 0), 0, 0, 2016, 10, { |
| .f_arm = 1, |
| .f_save = 1, |
| .f_flush = 1, |
| .f_smart = 1, |
| .f_restore = 1, |
| }, |
| 1, { 0x101, }, |
| }, |
| }; |
| |
| static struct btt { |
| int enabled; |
| uuid_t uuid; |
| int num_sector_sizes; |
| unsigned int sector_sizes[7]; |
| } default_btt = { |
| 0, { 0, }, 7, { 512, 520, 528, 4096, 4104, 4160, 4224, }, |
| }; |
| |
| struct pfn { |
| int enabled; |
| uuid_t uuid; |
| enum ndctl_pfn_loc locs[2]; |
| unsigned long aligns[4]; |
| }; |
| |
| struct dax { |
| int enabled; |
| uuid_t uuid; |
| enum ndctl_pfn_loc locs[2]; |
| unsigned long aligns[4]; |
| }; |
| |
| static struct pfn_default { |
| int enabled; |
| uuid_t uuid; |
| enum ndctl_pfn_loc loc; |
| unsigned long align; |
| } default_pfn = { |
| .enabled = 0, |
| .uuid = { 0, }, |
| .loc = NDCTL_PFN_LOC_NONE, |
| .align = SZ_2M, |
| }; |
| |
| struct region { |
| union { |
| unsigned int range_index; |
| unsigned int handle; |
| }; |
| unsigned int interleave_ways; |
| int enabled; |
| char *type; |
| unsigned long long available_size; |
| unsigned long long size; |
| struct set { |
| int active; |
| } iset; |
| struct btt *btts[2]; |
| struct pfn_default *pfns[2]; |
| struct namespace *namespaces[4]; |
| }; |
| |
| static struct btt btt_settings = { |
| .enabled = 1, |
| .uuid = { 0, 1, 2, 3, 4, 5, 6, 7, |
| 8, 9, 10, 11, 12, 13, 14, 15 |
| }, |
| .num_sector_sizes = 7, |
| .sector_sizes = { 512, 520, 528, 4096, 4104, 4160, 4224, }, |
| }; |
| |
| static struct pfn pfn_settings = { |
| .enabled = 1, |
| .uuid = { 1, 2, 3, 4, 5, 6, 7, 0, |
| 8, 9, 10, 11, 12, 13, 14, 15 |
| }, |
| .locs = { NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM }, |
| }; |
| |
| static struct dax dax_settings = { |
| .enabled = 1, |
| .uuid = { 1, 2, 3, 4, 5, 6, 7, 0, |
| 8, 9, 10, 11, 12, 13, 14, 15 |
| }, |
| .locs = { NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM }, |
| }; |
| |
| struct namespace { |
| unsigned int id; |
| char *type; |
| struct btt *btt_settings; |
| struct pfn *pfn_settings; |
| struct dax *dax_settings; |
| unsigned long long size; |
| uuid_t uuid; |
| int do_configure; |
| int check_alt_name; |
| int ro; |
| int num_sector_sizes; |
| unsigned long *sector_sizes; |
| }; |
| |
| static uuid_t null_uuid; |
| static unsigned long blk_sector_sizes[] = { 512, 520, 528, 4096, 4104, 4160, 4224, }; |
| static unsigned long pmem_sector_sizes[] = { 512, 4096 }; |
| static unsigned long io_sector_sizes[] = { 0 }; |
| |
| static struct namespace namespace0_pmem0 = { |
| 0, "namespace_pmem", &btt_settings, &pfn_settings, &dax_settings, SZ_18M, |
| { 1, 1, 1, 1, |
| 1, 1, 1, 1, |
| 1, 1, 1, 1, |
| 1, 1, 1, 1, }, 1, 1, 0, |
| ARRAY_SIZE(pmem_sector_sizes), pmem_sector_sizes, |
| }; |
| |
| static struct namespace namespace1_pmem0 = { |
| 0, "namespace_pmem", &btt_settings, &pfn_settings, &dax_settings, SZ_20M, |
| { 2, 2, 2, 2, |
| 2, 2, 2, 2, |
| 2, 2, 2, 2, |
| 2, 2, 2, 2, }, 1, 1, 0, |
| ARRAY_SIZE(pmem_sector_sizes), pmem_sector_sizes, |
| }; |
| |
| static struct namespace namespace2_blk0 = { |
| 0, "namespace_blk", NULL, NULL, NULL, SZ_7M, |
| { 3, 3, 3, 3, |
| 3, 3, 3, 3, |
| 3, 3, 3, 3, |
| 3, 3, 3, 3, }, 1, 1, 0, |
| ARRAY_SIZE(blk_sector_sizes), blk_sector_sizes, |
| }; |
| |
| static struct namespace namespace2_blk1 = { |
| 1, "namespace_blk", NULL, NULL, NULL, SZ_11M, |
| { 4, 4, 4, 4, |
| 4, 4, 4, 4, |
| 4, 4, 4, 4, |
| 4, 4, 4, 4, }, 1, 1, 0, |
| ARRAY_SIZE(blk_sector_sizes), blk_sector_sizes, |
| }; |
| |
| static struct namespace namespace3_blk0 = { |
| 0, "namespace_blk", NULL, NULL, NULL, SZ_7M, |
| { 5, 5, 5, 5, |
| 5, 5, 5, 5, |
| 5, 5, 5, 5, |
| 5, 5, 5, 5, }, 1, 1, 0, |
| ARRAY_SIZE(blk_sector_sizes), blk_sector_sizes, |
| }; |
| |
| static struct namespace namespace3_blk1 = { |
| 1, "namespace_blk", NULL, NULL, NULL, SZ_11M, |
| { 6, 6, 6, 6, |
| 6, 6, 6, 6, |
| 6, 6, 6, 6, |
| 6, 6, 6, 6, }, 1, 1, 0, |
| ARRAY_SIZE(blk_sector_sizes), blk_sector_sizes, |
| }; |
| |
| static struct namespace namespace4_blk0 = { |
| 0, "namespace_blk", &btt_settings, NULL, NULL, SZ_27M, |
| { 7, 7, 7, 7, |
| 7, 7, 7, 7, |
| 7, 7, 7, 7, |
| 7, 7, 7, 7, }, 1, 1, 0, |
| ARRAY_SIZE(blk_sector_sizes), blk_sector_sizes, |
| }; |
| |
| static struct namespace namespace5_blk0 = { |
| 0, "namespace_blk", &btt_settings, NULL, NULL, SZ_27M, |
| { 8, 8, 8, 8, |
| 8, 8, 8, 8, |
| 8, 8, 8, 8, |
| 8, 8, 8, 8, }, 1, 1, 0, |
| ARRAY_SIZE(blk_sector_sizes), blk_sector_sizes, |
| }; |
| |
| static struct region regions0[] = { |
| { { 1 }, 2, 1, "pmem", SZ_32M, SZ_32M, { 1 }, |
| .namespaces = { |
| [0] = &namespace0_pmem0, |
| }, |
| .btts = { |
| [0] = &default_btt, |
| }, |
| .pfns = { |
| [0] = &default_pfn, |
| }, |
| }, |
| { { 2 }, 4, 1, "pmem", SZ_64M, SZ_64M, { 1 }, |
| .namespaces = { |
| [0] = &namespace1_pmem0, |
| }, |
| .btts = { |
| [0] = &default_btt, |
| }, |
| .pfns = { |
| [0] = &default_pfn, |
| }, |
| }, |
| { { DIMM_HANDLE(0, 0, 0, 0, 0) }, 1, 1, "blk", SZ_18M, SZ_32M, |
| .namespaces = { |
| [0] = &namespace2_blk0, |
| [1] = &namespace2_blk1, |
| }, |
| .btts = { |
| [0] = &default_btt, |
| }, |
| }, |
| { { DIMM_HANDLE(0, 0, 0, 0, 1) }, 1, 1, "blk", SZ_18M, SZ_32M, |
| .namespaces = { |
| [0] = &namespace3_blk0, |
| [1] = &namespace3_blk1, |
| }, |
| .btts = { |
| [0] = &default_btt, |
| }, |
| }, |
| { { DIMM_HANDLE(0, 0, 1, 0, 0) }, 1, 1, "blk", SZ_27M, SZ_32M, |
| .namespaces = { |
| [0] = &namespace4_blk0, |
| }, |
| .btts = { |
| [0] = &default_btt, |
| }, |
| }, |
| { { DIMM_HANDLE(0, 0, 1, 0, 1) }, 1, 1, "blk", SZ_27M, SZ_32M, |
| .namespaces = { |
| [0] = &namespace5_blk0, |
| }, |
| .btts = { |
| [0] = &default_btt, |
| }, |
| }, |
| }; |
| |
| static struct namespace namespace1 = { |
| 0, "namespace_io", &btt_settings, &pfn_settings, &dax_settings, SZ_32M, |
| { 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, }, -1, 0, 1, ARRAY_SIZE(io_sector_sizes), io_sector_sizes, |
| }; |
| |
| static struct region regions1[] = { |
| { { 1 }, 1, 1, "pmem", 0, SZ_32M, |
| .namespaces = { |
| [0] = &namespace1, |
| }, |
| }, |
| }; |
| |
| static unsigned long dimm_commands0 = 1UL << ND_CMD_GET_CONFIG_SIZE |
| | 1UL << ND_CMD_GET_CONFIG_DATA |
| | 1UL << ND_CMD_SET_CONFIG_DATA | 1UL << ND_CMD_SMART |
| | 1UL << ND_CMD_SMART_THRESHOLD; |
| |
| #ifdef HAVE_NDCTL_CLEAR_ERROR |
| #define CLEAR_ERROR_CMDS (1UL << ND_CMD_CLEAR_ERROR) |
| #else |
| #define CLEAR_ERROR_CMDS 0 |
| #endif |
| |
| #ifdef HAVE_NDCTL_ARS |
| #define ARS_CMDS (1UL << ND_CMD_ARS_CAP | 1UL << ND_CMD_ARS_START \ |
| | 1UL << ND_CMD_ARS_STATUS) |
| #else |
| #define ARS_CMDS 0 |
| #endif |
| |
| static unsigned long bus_commands0 = CLEAR_ERROR_CMDS | ARS_CMDS; |
| |
| static struct ndctl_dimm *get_dimm_by_handle(struct ndctl_bus *bus, unsigned int handle) |
| { |
| struct ndctl_dimm *dimm; |
| |
| ndctl_dimm_foreach(bus, dimm) |
| if (ndctl_dimm_get_handle(dimm) == handle) |
| return dimm; |
| |
| return NULL; |
| } |
| |
| static struct ndctl_btt *get_idle_btt(struct ndctl_region *region) |
| { |
| struct ndctl_btt *btt; |
| |
| ndctl_btt_foreach(region, btt) |
| if (!ndctl_btt_is_enabled(btt) && !ndctl_btt_is_configured(btt)) |
| return btt; |
| |
| return NULL; |
| } |
| |
| static struct ndctl_pfn *get_idle_pfn(struct ndctl_region *region) |
| { |
| struct ndctl_pfn *pfn; |
| |
| ndctl_pfn_foreach(region, pfn) |
| if (!ndctl_pfn_is_enabled(pfn) && !ndctl_pfn_is_configured(pfn)) |
| return pfn; |
| |
| return NULL; |
| } |
| |
| static struct ndctl_dax *get_idle_dax(struct ndctl_region *region) |
| { |
| struct ndctl_dax *dax; |
| |
| ndctl_dax_foreach(region, dax) |
| if (!ndctl_dax_is_enabled(dax) && !ndctl_dax_is_configured(dax)) |
| return dax; |
| |
| return NULL; |
| } |
| |
| static struct ndctl_namespace *get_namespace_by_id(struct ndctl_region *region, |
| struct namespace *namespace) |
| { |
| struct ndctl_namespace *ndns; |
| |
| if (memcmp(namespace->uuid, null_uuid, sizeof(uuid_t)) != 0) |
| ndctl_namespace_foreach(region, ndns) { |
| uuid_t ndns_uuid; |
| int cmp; |
| |
| ndctl_namespace_get_uuid(ndns, ndns_uuid); |
| cmp = memcmp(ndns_uuid, namespace->uuid, sizeof(uuid_t)); |
| if (cmp == 0) |
| return ndns; |
| } |
| |
| /* fall back to nominal id if uuid is not configured yet */ |
| ndctl_namespace_foreach(region, ndns) |
| if (ndctl_namespace_get_id(ndns) == namespace->id) |
| return ndns; |
| |
| return NULL; |
| } |
| |
| static struct ndctl_region *get_pmem_region_by_range_index(struct ndctl_bus *bus, |
| unsigned int range_index) |
| { |
| struct ndctl_region *region; |
| |
| ndctl_region_foreach(bus, region) { |
| if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM) |
| continue; |
| if (ndctl_region_get_range_index(region) == range_index) |
| return region; |
| } |
| return NULL; |
| } |
| |
| static struct ndctl_region *get_blk_region_by_dimm_handle(struct ndctl_bus *bus, |
| unsigned int handle) |
| { |
| struct ndctl_region *region; |
| |
| ndctl_region_foreach(bus, region) { |
| struct ndctl_mapping *map; |
| |
| if (ndctl_region_get_type(region) != ND_DEVICE_REGION_BLK) |
| continue; |
| ndctl_mapping_foreach(region, map) { |
| struct ndctl_dimm *dimm = ndctl_mapping_get_dimm(map); |
| |
| if (ndctl_dimm_get_handle(dimm) == handle) |
| return region; |
| } |
| } |
| return NULL; |
| } |
| |
| enum ns_mode { |
| BTT, PFN, DAX, |
| }; |
| static int check_namespaces(struct ndctl_region *region, |
| struct namespace **namespaces, enum ns_mode mode); |
| static int check_btts(struct ndctl_region *region, struct btt **btts); |
| |
| static int check_regions(struct ndctl_bus *bus, struct region *regions, int n, |
| enum ns_mode mode) |
| { |
| struct ndctl_region *region; |
| int i, rc = 0; |
| |
| for (i = 0; i < n; i++) { |
| struct ndctl_interleave_set *iset; |
| char devname[50]; |
| |
| if (strcmp(regions[i].type, "pmem") == 0) |
| region = get_pmem_region_by_range_index(bus, regions[i].range_index); |
| else |
| region = get_blk_region_by_dimm_handle(bus, regions[i].handle); |
| |
| if (!region) { |
| fprintf(stderr, "failed to find region type: %s ident: %x\n", |
| regions[i].type, regions[i].handle); |
| return -ENXIO; |
| } |
| |
| snprintf(devname, sizeof(devname), "region%d", |
| ndctl_region_get_id(region)); |
| if (strcmp(ndctl_region_get_type_name(region), regions[i].type) != 0) { |
| fprintf(stderr, "%s: expected type: %s got: %s\n", |
| devname, regions[i].type, |
| ndctl_region_get_type_name(region)); |
| return -ENXIO; |
| } |
| if (ndctl_region_get_interleave_ways(region) != regions[i].interleave_ways) { |
| fprintf(stderr, "%s: expected interleave_ways: %d got: %d\n", |
| devname, regions[i].interleave_ways, |
| ndctl_region_get_interleave_ways(region)); |
| return -ENXIO; |
| } |
| if (regions[i].enabled && !ndctl_region_is_enabled(region)) { |
| fprintf(stderr, "%s: expected enabled by default\n", |
| devname); |
| return -ENXIO; |
| } |
| |
| if (regions[i].available_size != ndctl_region_get_available_size(region)) { |
| fprintf(stderr, "%s: expected available_size: %#llx got: %#llx\n", |
| devname, regions[i].available_size, |
| ndctl_region_get_available_size(region)); |
| return -ENXIO; |
| } |
| |
| if (regions[i].size != ndctl_region_get_size(region)) { |
| fprintf(stderr, "%s: expected size: %#llx got: %#llx\n", |
| devname, regions[i].size, |
| ndctl_region_get_size(region)); |
| return -ENXIO; |
| } |
| |
| iset = ndctl_region_get_interleave_set(region); |
| if (regions[i].iset.active |
| && !(iset && ndctl_interleave_set_is_active(iset) > 0)) { |
| fprintf(stderr, "%s: expected interleave set active by default\n", |
| devname); |
| return -ENXIO; |
| } else if (regions[i].iset.active == 0 && iset) { |
| fprintf(stderr, "%s: expected no interleave set\n", |
| devname); |
| return -ENXIO; |
| } |
| |
| if (ndctl_region_disable_invalidate(region) < 0) { |
| fprintf(stderr, "%s: failed to disable\n", devname); |
| return -ENXIO; |
| } |
| if (regions[i].enabled && ndctl_region_enable(region) < 0) { |
| fprintf(stderr, "%s: failed to enable\n", devname); |
| return -ENXIO; |
| } |
| |
| rc = check_btts(region, regions[i].btts); |
| if (rc) |
| return rc; |
| |
| if (regions[i].namespaces[0]) |
| rc = check_namespaces(region, regions[i].namespaces, |
| mode); |
| if (rc) |
| break; |
| } |
| |
| if (rc == 0) |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| return rc; |
| } |
| |
| static int validate_dax(struct ndctl_dax *dax) |
| { |
| /* TODO: make nfit_test namespaces dax capable */ |
| struct ndctl_namespace *ndns = ndctl_dax_get_namespace(dax); |
| const char *devname = ndctl_namespace_get_devname(ndns); |
| struct ndctl_region *region = ndctl_dax_get_region(dax); |
| struct ndctl_ctx *ctx = ndctl_dax_get_ctx(dax); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct daxctl_region *dax_region = NULL, *found; |
| int rc = -ENXIO, fd, count, dax_expect; |
| struct daxctl_dev *dax_dev, *seed; |
| struct daxctl_ctx *dax_ctx; |
| uuid_t uuid, region_uuid; |
| char devpath[50]; |
| |
| dax_region = ndctl_dax_get_daxctl_region(dax); |
| if (!dax_region) { |
| fprintf(stderr, "%s: failed to retrieve daxctl_region\n", |
| devname); |
| return -ENXIO; |
| } |
| |
| dax_ctx = ndctl_get_daxctl_ctx(ctx); |
| count = 0; |
| daxctl_region_foreach(dax_ctx, found) |
| if (found == dax_region) |
| count++; |
| if (count != 1) { |
| fprintf(stderr, "%s: failed to iterate to single region instance\n", |
| devname); |
| return -ENXIO; |
| } |
| |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0))) { |
| if (daxctl_region_get_size(dax_region) |
| != ndctl_dax_get_size(dax)) { |
| fprintf(stderr, "%s: expect size: %llu != %llu\n", |
| devname, ndctl_dax_get_size(dax), |
| daxctl_region_get_size(dax_region)); |
| return -ENXIO; |
| } |
| |
| if (daxctl_region_get_align(dax_region) |
| != ndctl_dax_get_align(dax)) { |
| fprintf(stderr, "%s: expect align: %lu != %lu\n", |
| devname, ndctl_dax_get_align(dax), |
| daxctl_region_get_align(dax_region)); |
| return -ENXIO; |
| } |
| } |
| |
| rc = -ENXIO; |
| ndctl_dax_get_uuid(dax, uuid); |
| daxctl_region_get_uuid(dax_region, region_uuid); |
| if (uuid_compare(uuid, region_uuid) != 0) { |
| char expect[40], actual[40]; |
| |
| uuid_unparse(region_uuid, actual); |
| uuid_unparse(uuid, expect); |
| fprintf(stderr, "%s: expected uuid: %s got: %s\n", |
| devname, expect, actual); |
| goto out; |
| } |
| |
| if ((int) ndctl_region_get_id(region) |
| != daxctl_region_get_id(dax_region)) { |
| fprintf(stderr, "%s: expected region id: %d got: %d\n", |
| devname, ndctl_region_get_id(region), |
| daxctl_region_get_id(dax_region)); |
| goto out; |
| } |
| |
| dax_dev = daxctl_dev_get_first(dax_region); |
| if (!dax_dev) { |
| fprintf(stderr, "%s: failed to find daxctl_dev\n", |
| devname); |
| goto out; |
| } |
| |
| seed = daxctl_region_get_dev_seed(dax_region); |
| if (dax_dev != seed && daxctl_dev_get_size(dax_dev) <= 0) { |
| fprintf(stderr, "%s: expected non-zero sized dax device\n", |
| devname); |
| goto out; |
| } |
| |
| sprintf(devpath, "/dev/%s", daxctl_dev_get_devname(dax_dev)); |
| fd = open(devpath, O_RDWR); |
| if (fd < 0) { |
| fprintf(stderr, "%s: failed to open %s\n", devname, devpath); |
| goto out; |
| } |
| close(fd); |
| |
| count = 0; |
| daxctl_dev_foreach(dax_region, dax_dev) |
| count++; |
| dax_expect = seed ? 2 : 1; |
| if (count != dax_expect) { |
| fprintf(stderr, "%s: expected %d dax device%s, got %d\n", |
| devname, dax_expect, dax_expect == 1 ? "" : "s", |
| count); |
| rc = -ENXIO; |
| goto out; |
| } |
| |
| rc = 0; |
| |
| out: |
| daxctl_region_unref(dax_region); |
| return rc; |
| } |
| |
| static int __check_dax_create(struct ndctl_region *region, |
| struct ndctl_namespace *ndns, struct namespace *namespace, |
| enum ndctl_pfn_loc loc, uuid_t uuid) |
| { |
| struct ndctl_dax *dax_seed = ndctl_region_get_dax_seed(region); |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| enum ndctl_namespace_mode mode; |
| struct ndctl_dax *dax; |
| const char *devname; |
| ssize_t rc; |
| |
| dax = get_idle_dax(region); |
| if (!dax) |
| return -ENXIO; |
| |
| devname = ndctl_dax_get_devname(dax); |
| ndctl_dax_set_uuid(dax, uuid); |
| ndctl_dax_set_location(dax, loc); |
| /* |
| * nfit_test uses vmalloc()'d resources so the only feasible |
| * alignment is PAGE_SIZE |
| */ |
| ndctl_dax_set_align(dax, SZ_4K); |
| |
| rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_DAX); |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) { |
| fprintf(stderr, "%s: failed to enforce dax mode\n", devname); |
| return rc; |
| } |
| ndctl_dax_set_namespace(dax, ndns); |
| rc = ndctl_dax_enable(dax); |
| if (rc) { |
| fprintf(stderr, "%s: failed to enable dax\n", devname); |
| return rc; |
| } |
| |
| mode = ndctl_namespace_get_mode(ndns); |
| if (mode >= 0 && mode != NDCTL_NS_MODE_DAX) |
| fprintf(stderr, "%s: expected dax mode got: %d\n", |
| devname, mode); |
| |
| if (namespace->ro == (rc == 0)) { |
| fprintf(stderr, "%s: expected dax enable %s, %s read-%s\n", |
| devname, |
| namespace->ro ? "failure" : "success", |
| ndctl_region_get_devname(region), |
| namespace->ro ? "only" : "write"); |
| return -ENXIO; |
| } |
| |
| if (dax_seed == ndctl_region_get_dax_seed(region) |
| && dax == dax_seed) { |
| fprintf(stderr, "%s: failed to advance dax seed\n", |
| ndctl_region_get_devname(region)); |
| return -ENXIO; |
| } |
| |
| if (namespace->ro) { |
| ndctl_region_set_ro(region, 0); |
| rc = ndctl_dax_enable(dax); |
| fprintf(stderr, "%s: failed to enable after setting rw\n", |
| devname); |
| ndctl_region_set_ro(region, 1); |
| return -ENXIO; |
| } |
| |
| rc = validate_dax(dax); |
| if (rc) { |
| fprintf(stderr, "%s: %s validate_dax failed\n", __func__, |
| devname); |
| return rc; |
| } |
| |
| if (namespace->ro) |
| ndctl_region_set_ro(region, 1); |
| |
| rc = ndctl_dax_delete(dax); |
| if (rc) |
| fprintf(stderr, "%s: failed to delete dax (%zd)\n", devname, rc); |
| return rc; |
| } |
| |
| static int check_dax_create(struct ndctl_region *region, |
| struct ndctl_namespace *ndns, struct namespace *namespace) |
| { |
| struct dax *dax_s = namespace->dax_settings; |
| void *buf = NULL; |
| unsigned int i; |
| int rc = 0; |
| |
| if (!dax_s) |
| return 0; |
| |
| for (i = 0; i < ARRAY_SIZE(dax_s->locs); i++) { |
| /* |
| * The kernel enforces invalidating the previous info |
| * block when the current uuid is does not validate with |
| * the contents of the info block. |
| */ |
| dax_s->uuid[0]++; |
| rc = __check_dax_create(region, ndns, namespace, |
| dax_s->locs[i], dax_s->uuid); |
| if (rc) |
| break; |
| } |
| free(buf); |
| return rc; |
| } |
| |
| static int __check_pfn_create(struct ndctl_region *region, |
| struct ndctl_namespace *ndns, struct namespace *namespace, |
| void *buf, enum ndctl_pfn_loc loc, uuid_t uuid) |
| { |
| struct ndctl_pfn *pfn_seed = ndctl_region_get_pfn_seed(region); |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| enum ndctl_namespace_mode mode; |
| struct ndctl_pfn *pfn; |
| const char *devname; |
| int fd, retry = 10; |
| char bdevpath[50]; |
| ssize_t rc; |
| |
| pfn = get_idle_pfn(region); |
| if (!pfn) |
| return -ENXIO; |
| |
| devname = ndctl_pfn_get_devname(pfn); |
| ndctl_pfn_set_uuid(pfn, uuid); |
| ndctl_pfn_set_location(pfn, loc); |
| /* |
| * nfit_test uses vmalloc()'d resources so the only feasible |
| * alignment is PAGE_SIZE |
| */ |
| ndctl_pfn_set_align(pfn, SZ_4K); |
| rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_MEMORY); |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) { |
| fprintf(stderr, "%s: failed to enforce pfn mode\n", devname); |
| return rc; |
| } |
| ndctl_pfn_set_namespace(pfn, ndns); |
| rc = ndctl_pfn_enable(pfn); |
| if (rc) { |
| fprintf(stderr, "%s: failed to enable pfn\n", devname); |
| return rc; |
| } |
| |
| mode = ndctl_namespace_get_mode(ndns); |
| if (mode >= 0 && mode != NDCTL_NS_MODE_MEMORY) |
| fprintf(stderr, "%s: expected memory mode got: %d\n", |
| devname, mode); |
| |
| if (namespace->ro == (rc == 0)) { |
| fprintf(stderr, "%s: expected pfn enable %s, %s read-%s\n", |
| devname, |
| namespace->ro ? "failure" : "success", |
| ndctl_region_get_devname(region), |
| namespace->ro ? "only" : "write"); |
| return -ENXIO; |
| } |
| |
| if (pfn_seed == ndctl_region_get_pfn_seed(region) |
| && pfn == pfn_seed) { |
| fprintf(stderr, "%s: failed to advance pfn seed\n", |
| ndctl_region_get_devname(region)); |
| return -ENXIO; |
| } |
| |
| if (namespace->ro) { |
| ndctl_region_set_ro(region, 0); |
| rc = ndctl_pfn_enable(pfn); |
| fprintf(stderr, "%s: failed to enable after setting rw\n", |
| devname); |
| ndctl_region_set_ro(region, 1); |
| return -ENXIO; |
| } |
| |
| sprintf(bdevpath, "/dev/%s", ndctl_pfn_get_block_device(pfn)); |
| rc = -ENXIO; |
| fd = open(bdevpath, O_RDWR|O_DIRECT); |
| if (fd < 0) |
| fprintf(stderr, "%s: failed to open %s\n", |
| devname, bdevpath); |
| |
| while (fd >= 0) { |
| rc = pread(fd, buf, 4096, 0); |
| if (rc < 4096) { |
| /* TODO: track down how this happens! */ |
| if (errno == ENOENT && retry--) { |
| usleep(5000); |
| continue; |
| } |
| fprintf(stderr, "%s: failed to read %s: %d %zd (%s)\n", |
| devname, bdevpath, -errno, rc, |
| strerror(errno)); |
| rc = -ENXIO; |
| break; |
| } |
| if (write(fd, buf, 4096) < 4096) { |
| fprintf(stderr, "%s: failed to write %s\n", |
| devname, bdevpath); |
| rc = -ENXIO; |
| break; |
| } |
| rc = 0; |
| break; |
| } |
| if (namespace->ro) |
| ndctl_region_set_ro(region, 1); |
| if (fd >= 0) |
| close(fd); |
| |
| if (rc) |
| return rc; |
| |
| rc = ndctl_pfn_delete(pfn); |
| if (rc) |
| fprintf(stderr, "%s: failed to delete pfn (%zd)\n", devname, rc); |
| return rc; |
| } |
| |
| static int check_pfn_create(struct ndctl_region *region, |
| struct ndctl_namespace *ndns, struct namespace *namespace) |
| { |
| struct pfn *pfn_s = namespace->pfn_settings; |
| void *buf = NULL; |
| unsigned int i; |
| int rc = 0; |
| |
| if (!pfn_s) |
| return 0; |
| |
| if (posix_memalign(&buf, 4096, 4096) != 0) |
| return -ENXIO; |
| |
| for (i = 0; i < ARRAY_SIZE(pfn_s->locs); i++) { |
| /* |
| * The kernel enforces invalidating the previous info |
| * block when the current uuid is does not validate with |
| * the contents of the info block. |
| */ |
| pfn_s->uuid[0]++; |
| rc = __check_pfn_create(region, ndns, namespace, buf, |
| pfn_s->locs[i], pfn_s->uuid); |
| if (rc) |
| break; |
| } |
| free(buf); |
| return rc; |
| } |
| |
| static int check_btt_size(struct ndctl_btt *btt) |
| { |
| struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct ndctl_namespace *ndns = ndctl_btt_get_namespace(btt); |
| unsigned long long ns_size = ndctl_namespace_get_size(ndns); |
| unsigned long sect_size = ndctl_btt_get_sector_size(btt); |
| unsigned long long actual, expect; |
| int size_select, sect_select; |
| unsigned long long expect_table[][2] = { |
| [0] = { |
| [0] = 0x11b5400, |
| [1] = 0x8daa000, |
| }, |
| [1] = { |
| [0] = 0x13b1400, |
| [1] = 0x9d8a000, |
| }, |
| [2] = { |
| [0] = 0x1aa3600, |
| [1] = 0xd51b000, |
| }, |
| }; |
| |
| if (sect_size >= SZ_4K) |
| sect_select = 1; |
| else if (sect_size >= 512) |
| sect_select = 0; |
| else { |
| fprintf(stderr, "%s: %s unexpected sector size: %lx\n", |
| __func__, ndctl_btt_get_devname(btt), |
| sect_size); |
| return -ENXIO; |
| } |
| |
| switch (ns_size) { |
| case SZ_18M: |
| size_select = 0; |
| break; |
| case SZ_20M: |
| size_select = 1; |
| break; |
| case SZ_27M: |
| size_select = 2; |
| break; |
| default: |
| fprintf(stderr, "%s: %s unexpected namespace size: %llx\n", |
| __func__, ndctl_namespace_get_devname(ndns), |
| ns_size); |
| return -ENXIO; |
| } |
| |
| /* prior to 4.8 btt devices did not have a size attribute */ |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) |
| return 0; |
| |
| expect = expect_table[size_select][sect_select]; |
| actual = ndctl_btt_get_size(btt); |
| if (expect != actual) { |
| fprintf(stderr, "%s: namespace: %s unexpected size: %llx (expected: %llx)\n", |
| ndctl_btt_get_devname(btt), |
| ndctl_namespace_get_devname(ndns), actual, expect); |
| return -ENXIO; |
| } |
| |
| return 0; |
| } |
| |
| static int check_btt_create(struct ndctl_region *region, struct ndctl_namespace *ndns, |
| struct namespace *namespace) |
| { |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct btt *btt_s = namespace->btt_settings; |
| int i, fd, retry = 10; |
| struct ndctl_btt *btt; |
| const char *devname; |
| char bdevpath[50]; |
| void *buf = NULL; |
| ssize_t rc = 0; |
| |
| if (!namespace->btt_settings) |
| return 0; |
| |
| if (posix_memalign(&buf, 4096, 4096) != 0) |
| return -ENXIO; |
| |
| for (i = 0; i < btt_s->num_sector_sizes; i++) { |
| struct ndctl_namespace *ns_seed = ndctl_region_get_namespace_seed(region); |
| struct ndctl_btt *btt_seed = ndctl_region_get_btt_seed(region); |
| enum ndctl_namespace_mode mode; |
| |
| btt = get_idle_btt(region); |
| if (!btt) |
| goto err; |
| |
| devname = ndctl_btt_get_devname(btt); |
| ndctl_btt_set_uuid(btt, btt_s->uuid); |
| ndctl_btt_set_sector_size(btt, btt_s->sector_sizes[i]); |
| rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_SAFE); |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) { |
| fprintf(stderr, "%s: failed to enforce btt mode\n", devname); |
| goto err; |
| } |
| |
| ndctl_btt_set_namespace(btt, ndns); |
| rc = ndctl_btt_enable(btt); |
| if (namespace->ro == (rc == 0)) { |
| fprintf(stderr, "%s: expected btt enable %s, %s read-%s\n", |
| devname, |
| namespace->ro ? "failure" : "success", |
| ndctl_region_get_devname(region), |
| namespace->ro ? "only" : "write"); |
| goto err; |
| } |
| |
| /* prior to v4.5 the mode attribute did not exist */ |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 5, 0))) { |
| mode = ndctl_namespace_get_mode(ndns); |
| if (mode >= 0 && mode != NDCTL_NS_MODE_SAFE) |
| fprintf(stderr, "%s: expected safe mode got: %d\n", |
| devname, mode); |
| } |
| |
| /* prior to v4.13 the expected sizes were different due to BTT1.1 */ |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))) { |
| rc = check_btt_size(btt); |
| if (rc) |
| goto err; |
| } |
| |
| if (btt_seed == ndctl_region_get_btt_seed(region) |
| && btt == btt_seed) { |
| fprintf(stderr, "%s: failed to advance btt seed\n", |
| ndctl_region_get_devname(region)); |
| goto err; |
| } |
| |
| /* check new seed creation for BLK regions */ |
| if (ndctl_region_get_type(region) == ND_DEVICE_REGION_BLK) { |
| if (ns_seed == ndctl_region_get_namespace_seed(region) |
| && ndns == ns_seed) { |
| fprintf(stderr, "%s: failed to advance namespace seed\n", |
| ndctl_region_get_devname(region)); |
| goto err; |
| } |
| } |
| |
| if (namespace->ro) { |
| ndctl_region_set_ro(region, 0); |
| rc = ndctl_btt_enable(btt); |
| fprintf(stderr, "%s: failed to enable after setting rw\n", |
| devname); |
| ndctl_region_set_ro(region, 1); |
| goto err; |
| } |
| |
| sprintf(bdevpath, "/dev/%s", ndctl_btt_get_block_device(btt)); |
| rc = -ENXIO; |
| fd = open(bdevpath, O_RDWR|O_DIRECT); |
| if (fd < 0) |
| fprintf(stderr, "%s: failed to open %s\n", |
| devname, bdevpath); |
| |
| while (fd >= 0) { |
| rc = pread(fd, buf, 4096, 0); |
| if (rc < 4096) { |
| /* TODO: track down how this happens! */ |
| if (errno == ENOENT && retry--) { |
| usleep(5000); |
| continue; |
| } |
| fprintf(stderr, "%s: failed to read %s: %d %zd (%s)\n", |
| devname, bdevpath, -errno, rc, |
| strerror(errno)); |
| rc = -ENXIO; |
| break; |
| } |
| if (write(fd, buf, 4096) < 4096) { |
| fprintf(stderr, "%s: failed to write %s\n", |
| devname, bdevpath); |
| rc = -ENXIO; |
| break; |
| } |
| rc = 0; |
| break; |
| } |
| if (namespace->ro) |
| ndctl_region_set_ro(region, 1); |
| if (fd >= 0) |
| close(fd); |
| |
| if (rc) |
| break; |
| |
| rc = ndctl_btt_delete(btt); |
| if (rc) |
| fprintf(stderr, "%s: failed to delete btt (%zd)\n", devname, rc); |
| } |
| free(buf); |
| return rc; |
| err: |
| free(buf); |
| return -ENXIO; |
| } |
| |
| static int configure_namespace(struct ndctl_region *region, |
| struct ndctl_namespace *ndns, struct namespace *namespace, |
| unsigned long lbasize, enum ns_mode mode) |
| { |
| char devname[50]; |
| int rc; |
| |
| if (namespace->do_configure <= 0) |
| return 0; |
| |
| snprintf(devname, sizeof(devname), "namespace%d.%d", |
| ndctl_region_get_id(region), namespace->id); |
| |
| if (!ndctl_namespace_is_configured(ndns)) { |
| rc = ndctl_namespace_set_uuid(ndns, namespace->uuid); |
| if (rc) |
| fprintf(stderr, "%s: set_uuid failed: %d\n", devname, rc); |
| rc = ndctl_namespace_set_alt_name(ndns, devname); |
| if (rc) |
| fprintf(stderr, "%s: set_alt_name failed: %d\n", devname, rc); |
| rc = ndctl_namespace_set_size(ndns, namespace->size); |
| if (rc) |
| fprintf(stderr, "%s: set_size failed: %d\n", devname, rc); |
| } |
| |
| if (lbasize) { |
| rc = ndctl_namespace_set_sector_size(ndns, lbasize); |
| if (rc) |
| fprintf(stderr, "%s: set_sector_size (%lu) failed: %d\n", |
| devname, lbasize, rc); |
| } |
| |
| rc = ndctl_namespace_is_configured(ndns); |
| if (rc < 1) |
| fprintf(stderr, "%s: is_configured: %d\n", devname, rc); |
| |
| if (mode == BTT) { |
| rc = check_btt_create(region, ndns, namespace); |
| if (rc < 0) { |
| fprintf(stderr, "%s: failed to create btt\n", devname); |
| return rc; |
| } |
| } |
| |
| if (mode == PFN) { |
| rc = check_pfn_create(region, ndns, namespace); |
| if (rc < 0) { |
| fprintf(stderr, "%s: failed to create pfn\n", devname); |
| return rc; |
| } |
| } |
| |
| if (mode == DAX) { |
| rc = check_dax_create(region, ndns, namespace); |
| if (rc < 0) { |
| fprintf(stderr, "%s: failed to create dax\n", devname); |
| return rc; |
| } |
| } |
| |
| rc = ndctl_namespace_enable(ndns); |
| if (rc < 0) |
| fprintf(stderr, "%s: enable: %d\n", devname, rc); |
| |
| return rc; |
| } |
| |
| static int check_pfn_autodetect(struct ndctl_bus *bus, |
| struct ndctl_namespace *ndns, void *buf, |
| struct namespace *namespace) |
| { |
| struct ndctl_region *region = ndctl_namespace_get_region(ndns); |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| const char *devname = ndctl_namespace_get_devname(ndns); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct pfn *auto_pfn = namespace->pfn_settings; |
| struct ndctl_pfn *pfn, *found = NULL; |
| enum ndctl_namespace_mode mode; |
| ssize_t rc = -ENXIO; |
| char bdev[50]; |
| int fd, ro; |
| |
| ndctl_pfn_foreach(region, pfn) { |
| struct ndctl_namespace *pfn_ndns; |
| uuid_t uu; |
| |
| ndctl_pfn_get_uuid(pfn, uu); |
| if (uuid_compare(uu, auto_pfn->uuid) != 0) |
| continue; |
| if (!ndctl_pfn_is_enabled(pfn)) |
| continue; |
| pfn_ndns = ndctl_pfn_get_namespace(pfn); |
| if (strcmp(ndctl_namespace_get_devname(pfn_ndns), devname) != 0) |
| continue; |
| fprintf(stderr, "%s: pfn_ndns: %p ndns: %p\n", __func__, |
| pfn_ndns, ndns); |
| found = pfn; |
| break; |
| } |
| |
| if (!found) |
| return -ENXIO; |
| |
| mode = ndctl_namespace_get_enforce_mode(ndns); |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) |
| && mode != NDCTL_NS_MODE_MEMORY) { |
| fprintf(stderr, "%s expected enforce_mode pfn\n", devname); |
| return -ENXIO; |
| } |
| |
| sprintf(bdev, "/dev/%s", ndctl_pfn_get_block_device(pfn)); |
| fd = open(bdev, O_RDONLY); |
| if (fd < 0) |
| return -ENXIO; |
| rc = ioctl(fd, BLKROGET, &ro); |
| if (rc < 0) { |
| fprintf(stderr, "%s: failed to open %s\n", __func__, bdev); |
| rc = -ENXIO; |
| goto out; |
| } |
| close(fd); |
| fd = -1; |
| |
| rc = -ENXIO; |
| if (ro != namespace->ro) { |
| fprintf(stderr, "%s: read-%s expected read-%s by default\n", |
| bdev, ro ? "only" : "write", |
| namespace->ro ? "only" : "write"); |
| goto out; |
| } |
| |
| /* destroy pfn device */ |
| ndctl_pfn_delete(found); |
| |
| /* clear read-write, and enable raw mode */ |
| ndctl_region_set_ro(region, 0); |
| ndctl_namespace_set_raw_mode(ndns, 1); |
| ndctl_namespace_enable(ndns); |
| |
| /* destroy pfn metadata */ |
| sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); |
| fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL); |
| if (fd < 0) { |
| fprintf(stderr, "%s: failed to open %s to destroy pfn\n", |
| devname, bdev); |
| goto out; |
| } |
| |
| memset(buf, 0, 4096); |
| rc = pwrite(fd, buf, 4096, 4096); |
| if (rc < 4096) { |
| rc = -ENXIO; |
| fprintf(stderr, "%s: failed to overwrite pfn on %s\n", |
| devname, bdev); |
| } |
| out: |
| ndctl_region_set_ro(region, namespace->ro); |
| ndctl_namespace_set_raw_mode(ndns, 0); |
| if (fd >= 0) |
| close(fd); |
| |
| return rc; |
| } |
| |
| static int check_dax_autodetect(struct ndctl_bus *bus, |
| struct ndctl_namespace *ndns, void *buf, |
| struct namespace *namespace) |
| { |
| struct ndctl_region *region = ndctl_namespace_get_region(ndns); |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| const char *devname = ndctl_namespace_get_devname(ndns); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct dax *auto_dax = namespace->dax_settings; |
| struct ndctl_dax *dax, *found = NULL; |
| enum ndctl_namespace_mode mode; |
| ssize_t rc = -ENXIO; |
| char bdev[50]; |
| int fd; |
| |
| ndctl_dax_foreach(region, dax) { |
| struct ndctl_namespace *dax_ndns; |
| uuid_t uu; |
| |
| ndctl_dax_get_uuid(dax, uu); |
| if (uuid_compare(uu, auto_dax->uuid) != 0) |
| continue; |
| if (!ndctl_dax_is_enabled(dax)) |
| continue; |
| dax_ndns = ndctl_dax_get_namespace(dax); |
| if (strcmp(ndctl_namespace_get_devname(dax_ndns), devname) != 0) |
| continue; |
| fprintf(stderr, "%s: dax_ndns: %p ndns: %p\n", __func__, |
| dax_ndns, ndns); |
| found = dax; |
| break; |
| } |
| |
| if (!found) |
| return -ENXIO; |
| |
| mode = ndctl_namespace_get_enforce_mode(ndns); |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) |
| && mode != NDCTL_NS_MODE_DAX) { |
| fprintf(stderr, "%s expected enforce_mode dax\n", devname); |
| return -ENXIO; |
| } |
| |
| rc = validate_dax(dax); |
| if (rc) { |
| fprintf(stderr, "%s: %s validate_dax failed\n", __func__, |
| devname); |
| return rc; |
| } |
| |
| rc = -ENXIO; |
| |
| /* destroy dax device */ |
| ndctl_dax_delete(found); |
| |
| /* clear read-write, and enable raw mode */ |
| ndctl_region_set_ro(region, 0); |
| ndctl_namespace_set_raw_mode(ndns, 1); |
| ndctl_namespace_enable(ndns); |
| |
| /* destroy dax metadata */ |
| sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); |
| fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL); |
| if (fd < 0) { |
| fprintf(stderr, "%s: failed to open %s to destroy dax\n", |
| devname, bdev); |
| goto out; |
| } |
| |
| memset(buf, 0, 4096); |
| rc = pwrite(fd, buf, 4096, 4096); |
| if (rc < 4096) { |
| rc = -ENXIO; |
| fprintf(stderr, "%s: failed to overwrite dax on %s\n", |
| devname, bdev); |
| } |
| out: |
| ndctl_region_set_ro(region, namespace->ro); |
| ndctl_namespace_set_raw_mode(ndns, 0); |
| if (fd >= 0) |
| close(fd); |
| |
| return rc; |
| } |
| |
| static int check_btt_autodetect(struct ndctl_bus *bus, |
| struct ndctl_namespace *ndns, void *buf, |
| struct namespace *namespace) |
| { |
| struct ndctl_region *region = ndctl_namespace_get_region(ndns); |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| const char *devname = ndctl_namespace_get_devname(ndns); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct btt *auto_btt = namespace->btt_settings; |
| struct ndctl_btt *btt, *found = NULL; |
| enum ndctl_namespace_mode mode; |
| ssize_t rc = -ENXIO; |
| char bdev[50]; |
| int fd, ro; |
| |
| ndctl_btt_foreach(region, btt) { |
| struct ndctl_namespace *btt_ndns; |
| uuid_t uu; |
| |
| ndctl_btt_get_uuid(btt, uu); |
| if (uuid_compare(uu, auto_btt->uuid) != 0) |
| continue; |
| if (!ndctl_btt_is_enabled(btt)) |
| continue; |
| btt_ndns = ndctl_btt_get_namespace(btt); |
| if (strcmp(ndctl_namespace_get_devname(btt_ndns), devname) != 0) |
| continue; |
| fprintf(stderr, "%s: btt_ndns: %p ndns: %p\n", __func__, |
| btt_ndns, ndns); |
| found = btt; |
| break; |
| } |
| |
| if (!found) |
| return -ENXIO; |
| |
| mode = ndctl_namespace_get_enforce_mode(ndns); |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) |
| && mode != NDCTL_NS_MODE_SAFE) { |
| fprintf(stderr, "%s expected enforce_mode btt\n", devname); |
| return -ENXIO; |
| } |
| |
| sprintf(bdev, "/dev/%s", ndctl_btt_get_block_device(btt)); |
| fd = open(bdev, O_RDONLY); |
| if (fd < 0) |
| return -ENXIO; |
| rc = ioctl(fd, BLKROGET, &ro); |
| if (rc < 0) { |
| fprintf(stderr, "%s: failed to open %s\n", __func__, bdev); |
| rc = -ENXIO; |
| goto out; |
| } |
| close(fd); |
| fd = -1; |
| |
| rc = -ENXIO; |
| if (ro != namespace->ro) { |
| fprintf(stderr, "%s: read-%s expected read-%s by default\n", |
| bdev, ro ? "only" : "write", |
| namespace->ro ? "only" : "write"); |
| goto out; |
| } |
| |
| /* destroy btt device */ |
| ndctl_btt_delete(found); |
| |
| /* clear read-write, and enable raw mode */ |
| ndctl_region_set_ro(region, 0); |
| ndctl_namespace_set_raw_mode(ndns, 1); |
| ndctl_namespace_enable(ndns); |
| |
| /* destroy btt metadata */ |
| sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); |
| fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL); |
| if (fd < 0) { |
| fprintf(stderr, "%s: failed to open %s to destroy btt\n", |
| devname, bdev); |
| goto out; |
| } |
| |
| memset(buf, 0, 4096); |
| /* Delete both the first and second 4K pages */ |
| rc = pwrite(fd, buf, 4096, 4096); |
| if (rc < 4096) { |
| rc = -ENXIO; |
| fprintf(stderr, "%s: failed to overwrite btt on %s\n", |
| devname, bdev); |
| goto out; |
| } |
| rc = pwrite(fd, buf, 4096, 0); |
| if (rc < 4096) { |
| rc = -ENXIO; |
| fprintf(stderr, "%s: failed to overwrite btt on %s\n", |
| devname, bdev); |
| } |
| out: |
| ndctl_region_set_ro(region, namespace->ro); |
| ndctl_namespace_set_raw_mode(ndns, 0); |
| if (fd >= 0) |
| close(fd); |
| |
| return rc; |
| } |
| |
| static int validate_bdev(const char *devname, struct ndctl_btt *btt, |
| struct ndctl_pfn *pfn, struct ndctl_namespace *ndns, |
| struct namespace *namespace, void *buf) |
| { |
| char bdevpath[50]; |
| int fd, rc, ro; |
| |
| if (btt) |
| sprintf(bdevpath, "/dev/%s", |
| ndctl_btt_get_block_device(btt)); |
| else if (pfn) |
| sprintf(bdevpath, "/dev/%s", |
| ndctl_pfn_get_block_device(pfn)); |
| else |
| sprintf(bdevpath, "/dev/%s", |
| ndctl_namespace_get_block_device(ndns)); |
| |
| fd = open(bdevpath, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "%s: failed to open(%s, O_RDONLY)\n", |
| devname, bdevpath); |
| return -ENXIO; |
| } |
| |
| rc = ioctl(fd, BLKROGET, &ro); |
| if (rc < 0) { |
| fprintf(stderr, "%s: BLKROGET failed\n", |
| devname); |
| rc = -errno; |
| goto out; |
| } |
| |
| if (namespace->ro != ro) { |
| fprintf(stderr, "%s: read-%s expected: read-%s\n", |
| devname, ro ? "only" : "write", |
| namespace->ro ? "only" : "write"); |
| rc = -ENXIO; |
| goto out; |
| } |
| |
| ro = 0; |
| rc = ioctl(fd, BLKROSET, &ro); |
| if (rc < 0) { |
| fprintf(stderr, "%s: BLKROSET failed\n", |
| devname); |
| rc = -errno; |
| goto out; |
| } |
| |
| close(fd); |
| fd = open(bdevpath, O_RDWR|O_DIRECT); |
| if (fd < 0) { |
| fprintf(stderr, "%s: failed to open(%s, O_RDWR|O_DIRECT)\n", |
| devname, bdevpath); |
| return -ENXIO; |
| } |
| if (read(fd, buf, 4096) < 4096) { |
| fprintf(stderr, "%s: failed to read %s\n", |
| devname, bdevpath); |
| rc = -ENXIO; |
| goto out; |
| } |
| if (write(fd, buf, 4096) < 4096) { |
| fprintf(stderr, "%s: failed to write %s\n", |
| devname, bdevpath); |
| rc = -ENXIO; |
| goto out; |
| } |
| rc = 0; |
| out: |
| close(fd); |
| return rc; |
| } |
| |
| static int check_namespaces(struct ndctl_region *region, |
| struct namespace **namespaces, enum ns_mode mode) |
| { |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| struct ndctl_test *test = ndctl_get_private_data(ctx); |
| struct ndctl_bus *bus = ndctl_region_get_bus(region); |
| struct ndctl_namespace **ndns_save; |
| struct namespace *namespace; |
| int i, j, rc, retry_cnt = 1; |
| void *buf = NULL, *__ndns_save; |
| char devname[50]; |
| |
| if (posix_memalign(&buf, 4096, 4096) != 0) |
| return -ENOMEM; |
| |
| for (i = 0; (namespace = namespaces[i]); i++) |
| if (namespace->do_configure >= 0) |
| namespace->do_configure = 1; |
| |
| retry: |
| ndns_save = NULL; |
| for (i = 0; (namespace = namespaces[i]); i++) { |
| uuid_t uu; |
| struct ndctl_namespace *ndns; |
| unsigned long _sizes[] = { 0 }, *sector_sizes = _sizes; |
| int num_sector_sizes = (int) ARRAY_SIZE(_sizes); |
| |
| snprintf(devname, sizeof(devname), "namespace%d.%d", |
| ndctl_region_get_id(region), namespace->id); |
| ndns = get_namespace_by_id(region, namespace); |
| if (!ndns) { |
| fprintf(stderr, "%s: failed to find namespace\n", |
| devname); |
| break; |
| } |
| |
| if (ndctl_region_get_type(region) == ND_DEVICE_REGION_PMEM |
| && !ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))) |
| /* pass, no sector_size support for pmem prior to 4.13 */; |
| else { |
| num_sector_sizes = namespace->num_sector_sizes; |
| sector_sizes = namespace->sector_sizes; |
| } |
| |
| for (j = 0; j < num_sector_sizes; j++) { |
| struct btt *btt_s = NULL; |
| struct pfn *pfn_s = NULL; |
| struct dax *dax_s = NULL; |
| struct ndctl_btt *btt = NULL; |
| struct ndctl_pfn *pfn = NULL; |
| struct ndctl_dax *dax = NULL; |
| |
| rc = configure_namespace(region, ndns, namespace, |
| sector_sizes[j], mode); |
| if (rc < 0) { |
| fprintf(stderr, "%s: failed to configure namespace\n", |
| devname); |
| break; |
| } |
| |
| if (strcmp(ndctl_namespace_get_type_name(ndns), |
| namespace->type) != 0) { |
| fprintf(stderr, "%s: expected type: %s got: %s\n", |
| devname, |
| ndctl_namespace_get_type_name(ndns), |
| namespace->type); |
| rc = -ENXIO; |
| break; |
| } |
| |
| /* |
| * On the second time through this loop we skip |
| * establishing btt|pfn since |
| * check_{btt|pfn}_autodetect() destroyed the |
| * inital instance. |
| */ |
| if (mode == BTT) { |
| btt_s = namespace->do_configure > 0 |
| ? namespace->btt_settings : NULL; |
| btt = ndctl_namespace_get_btt(ndns); |
| if (!!btt_s != !!btt) { |
| fprintf(stderr, "%s expected btt %s by default\n", |
| devname, namespace->btt_settings |
| ? "enabled" : "disabled"); |
| rc = -ENXIO; |
| break; |
| } |
| } |
| |
| if (mode == PFN) { |
| pfn_s = namespace->do_configure > 0 |
| ? namespace->pfn_settings : NULL; |
| pfn = ndctl_namespace_get_pfn(ndns); |
| if (!!pfn_s != !!pfn) { |
| fprintf(stderr, "%s expected pfn %s by default\n", |
| devname, namespace->pfn_settings |
| ? "enabled" : "disabled"); |
| rc = -ENXIO; |
| break; |
| } |
| } |
| |
| if (mode == DAX) { |
| dax_s = namespace->do_configure > 0 |
| ? namespace->dax_settings : NULL; |
| dax = ndctl_namespace_get_dax(ndns); |
| if (!!dax_s != !!dax) { |
| fprintf(stderr, "%s expected dax %s by default\n", |
| devname, namespace->dax_settings |
| ? "enabled" : "disabled"); |
| rc = -ENXIO; |
| break; |
| } |
| } |
| |
| if (!btt_s && !pfn_s && !dax_s |
| && !ndctl_namespace_is_enabled(ndns)) { |
| fprintf(stderr, "%s: expected enabled by default\n", |
| devname); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (namespace->size != ndctl_namespace_get_size(ndns)) { |
| fprintf(stderr, "%s: expected size: %#llx got: %#llx\n", |
| devname, namespace->size, |
| ndctl_namespace_get_size(ndns)); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (sector_sizes[j] && sector_sizes[j] |
| != ndctl_namespace_get_sector_size(ndns)) { |
| fprintf(stderr, "%s: expected lbasize: %#lx got: %#x\n", |
| devname, sector_sizes[j], |
| ndctl_namespace_get_sector_size(ndns)); |
| rc = -ENXIO; |
| break; |
| } |
| |
| ndctl_namespace_get_uuid(ndns, uu); |
| if (uuid_compare(uu, namespace->uuid) != 0) { |
| char expect[40], actual[40]; |
| |
| uuid_unparse(uu, actual); |
| uuid_unparse(namespace->uuid, expect); |
| fprintf(stderr, "%s: expected uuid: %s got: %s\n", |
| devname, expect, actual); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (namespace->check_alt_name |
| && strcmp(ndctl_namespace_get_alt_name(ndns), |
| devname) != 0) { |
| fprintf(stderr, "%s: expected alt_name: %s got: %s\n", |
| devname, devname, |
| ndctl_namespace_get_alt_name(ndns)); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (dax) |
| rc = validate_dax(dax); |
| else |
| rc = validate_bdev(devname, btt, pfn, ndns, |
| namespace, buf); |
| if (rc) { |
| fprintf(stderr, "%s: %s validate_%s failed\n", |
| __func__, devname, dax ? "dax" : "bdev"); |
| break; |
| } |
| |
| if (ndctl_namespace_disable_invalidate(ndns) < 0) { |
| fprintf(stderr, "%s: failed to disable\n", devname); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (ndctl_namespace_enable(ndns) < 0) { |
| fprintf(stderr, "%s: failed to enable\n", devname); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (btt_s && check_btt_autodetect(bus, ndns, buf, |
| namespace) < 0) { |
| fprintf(stderr, "%s, failed btt autodetect\n", devname); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (pfn_s && check_pfn_autodetect(bus, ndns, buf, |
| namespace) < 0) { |
| fprintf(stderr, "%s, failed pfn autodetect\n", devname); |
| rc = -ENXIO; |
| break; |
| } |
| |
| if (dax_s && check_dax_autodetect(bus, ndns, buf, |
| namespace) < 0) { |
| fprintf(stderr, "%s, failed dax autodetect\n", devname); |
| rc = -ENXIO; |
| break; |
| } |
| |
| /* |
| * if the namespace is being tested with a btt, there is no |
| * point testing different sector sizes for the namespace itself |
| */ |
| if (btt_s || pfn_s || dax_s) |
| break; |
| |
| /* |
| * If this is the last sector size being tested, don't disable |
| * the namespace |
| */ |
| if (j == num_sector_sizes - 1) |
| break; |
| |
| /* |
| * If we're in the second time through this, don't loop for |
| * different sector sizes as ->do_configure is disabled |
| */ |
| if (!retry_cnt) |
| break; |
| |
| if (ndctl_namespace_disable_invalidate(ndns) < 0) { |
| fprintf(stderr, "%s: failed to disable\n", devname); |
| break; |
| } |
| } |
| namespace->do_configure = 0; |
| |
| __ndns_save = realloc(ndns_save, |
| sizeof(struct ndctl_namespace *) * (i + 1)); |
| if (!__ndns_save) { |
| fprintf(stderr, "%s: %s() -ENOMEM\n", |
| devname, __func__); |
| rc = -ENOMEM; |
| break; |
| } else { |
| ndns_save = __ndns_save; |
| ndns_save[i] = ndns; |
| } |
| |
| if (rc) |
| break; |
| } |
| |
| if (namespace || ndctl_region_disable_preserve(region) != 0) { |
| rc = -ENXIO; |
| if (!namespace) |
| fprintf(stderr, "failed to disable region%d\n", |
| ndctl_region_get_id(region)); |
| goto out; |
| } |
| |
| /* |
| * On the second time through configure_namespace() is skipped |
| * to test assembling namespace(s) from an existing label set |
| */ |
| if (retry_cnt--) { |
| ndctl_region_enable(region); |
| free(ndns_save); |
| goto retry; |
| } |
| |
| rc = 0; |
| for (i--; i >= 0; i--) { |
| struct ndctl_namespace *ndns = ndns_save[i]; |
| |
| snprintf(devname, sizeof(devname), "namespace%d.%d", |
| ndctl_region_get_id(region), |
| ndctl_namespace_get_id(ndns)); |
| if (ndctl_namespace_is_valid(ndns)) { |
| fprintf(stderr, "%s: failed to invalidate\n", devname); |
| rc = -ENXIO; |
| break; |
| } |
| } |
| ndctl_region_cleanup(region); |
| out: |
| free(ndns_save); |
| free(buf); |
| |
| return rc; |
| } |
| |
| static int check_btt_supported_sectors(struct ndctl_btt *btt, struct btt *expect_btt) |
| { |
| int s, t; |
| char devname[50]; |
| |
| snprintf(devname, sizeof(devname), "btt%d", ndctl_btt_get_id(btt)); |
| for (s = 0; s < expect_btt->num_sector_sizes; s++) { |
| for (t = 0; t < expect_btt->num_sector_sizes; t++) { |
| if (ndctl_btt_get_supported_sector_size(btt, t) |
| == expect_btt->sector_sizes[s]) |
| break; |
| } |
| if (t >= expect_btt->num_sector_sizes) { |
| fprintf(stderr, "%s: expected sector_size: %d to be supported\n", |
| devname, expect_btt->sector_sizes[s]); |
| return -ENXIO; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int check_btts(struct ndctl_region *region, struct btt **btts) |
| { |
| struct btt *btt_s; |
| int i; |
| |
| for (i = 0; (btt_s = btts[i]); i++) { |
| struct ndctl_btt *btt; |
| char devname[50]; |
| uuid_t btt_uuid; |
| int rc; |
| |
| btt = get_idle_btt(region); |
| if (!btt) { |
| fprintf(stderr, "failed to find idle btt\n"); |
| return -ENXIO; |
| } |
| snprintf(devname, sizeof(devname), "btt%d", |
| ndctl_btt_get_id(btt)); |
| ndctl_btt_get_uuid(btt, btt_uuid); |
| if (uuid_compare(btt_uuid, btt_s->uuid) != 0) { |
| char expect[40], actual[40]; |
| |
| uuid_unparse(btt_uuid, actual); |
| uuid_unparse(btt_s->uuid, expect); |
| fprintf(stderr, "%s: expected uuid: %s got: %s\n", |
| devname, expect, actual); |
| return -ENXIO; |
| } |
| if (ndctl_btt_get_num_sector_sizes(btt) != btt_s->num_sector_sizes) { |
| fprintf(stderr, "%s: expected num_sector_sizes: %d got: %d\n", |
| devname, btt_s->num_sector_sizes, |
| ndctl_btt_get_num_sector_sizes(btt)); |
| } |
| rc = check_btt_supported_sectors(btt, btt_s); |
| if (rc) |
| return rc; |
| if (btt_s->enabled && ndctl_btt_is_enabled(btt)) { |
| fprintf(stderr, "%s: expected disabled by default\n", |
| devname); |
| return -ENXIO; |
| } |
| } |
| |
| return 0; |
| } |
| |
| struct check_cmd { |
| int (*check_fn)(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check); |
| struct ndctl_cmd *cmd; |
| struct ndctl_test *test; |
| }; |
| |
| static struct check_cmd *check_cmds; |
| |
| static int check_get_config_size(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| struct ndctl_cmd *cmd; |
| int rc; |
| |
| if (check->cmd != NULL) { |
| fprintf(stderr, "%s: dimm: %#x expected a NULL command, by default\n", |
| __func__, ndctl_dimm_get_handle(dimm)); |
| return -ENXIO; |
| } |
| |
| cmd = ndctl_dimm_cmd_new_cfg_size(dimm); |
| if (!cmd) { |
| fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", |
| __func__, ndctl_dimm_get_handle(dimm)); |
| return -ENOTTY; |
| } |
| |
| rc = ndctl_cmd_submit(cmd); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| if (ndctl_cmd_cfg_size_get_size(cmd) != SZ_128K) { |
| fprintf(stderr, "%s: dimm: %#x expect size: %d got: %d\n", |
| __func__, ndctl_dimm_get_handle(dimm), SZ_128K, |
| ndctl_cmd_cfg_size_get_size(cmd)); |
| ndctl_cmd_unref(cmd); |
| return -ENXIO; |
| } |
| |
| check->cmd = cmd; |
| return 0; |
| } |
| |
| static int check_get_config_data(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| struct ndctl_cmd *cmd_size = check_cmds[ND_CMD_GET_CONFIG_SIZE].cmd; |
| struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_cfg_read(cmd_size); |
| static char buf[SZ_128K]; |
| ssize_t rc; |
| |
| if (!cmd) { |
| fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", |
| __func__, ndctl_dimm_get_handle(dimm)); |
| return -ENOTTY; |
| } |
| |
| rc = ndctl_cmd_submit(cmd); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| rc = ndctl_cmd_cfg_read_get_data(cmd, buf, SZ_128K, 0); |
| if (rc != SZ_128K) { |
| fprintf(stderr, "%s: dimm: %#x expected read %d bytes, got: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), SZ_128K, rc); |
| ndctl_cmd_unref(cmd); |
| return -ENXIO; |
| } |
| |
| check->cmd = cmd; |
| return 0; |
| } |
| |
| static int check_set_config_data(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| struct ndctl_cmd *cmd_read = check_cmds[ND_CMD_GET_CONFIG_DATA].cmd; |
| struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_cfg_write(cmd_read); |
| char buf[20], result[sizeof(buf)]; |
| size_t rc; |
| |
| if (!cmd) { |
| fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", |
| __func__, ndctl_dimm_get_handle(dimm)); |
| return -ENOTTY; |
| } |
| |
| memset(buf, 0, sizeof(buf)); |
| ndctl_cmd_cfg_write_set_data(cmd, buf, sizeof(buf), 0); |
| rc = ndctl_cmd_submit(cmd); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| rc = ndctl_cmd_submit(cmd_read); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit read1: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| ndctl_cmd_cfg_read_get_data(cmd_read, result, sizeof(result), 0); |
| if (memcmp(result, buf, sizeof(result)) != 0) { |
| fprintf(stderr, "%s: dimm: %#x read1 data miscompare: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return -ENXIO; |
| } |
| |
| sprintf(buf, "dimm-%#x", ndctl_dimm_get_handle(dimm)); |
| ndctl_cmd_cfg_write_set_data(cmd, buf, sizeof(buf), 0); |
| rc = ndctl_cmd_submit(cmd); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| rc = ndctl_cmd_submit(cmd_read); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit read2: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| ndctl_cmd_cfg_read_get_data(cmd_read, result, sizeof(result), 0); |
| if (memcmp(result, buf, sizeof(result)) != 0) { |
| fprintf(stderr, "%s: dimm: %#x read2 data miscompare: %zd\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| check->cmd = cmd; |
| return 0; |
| } |
| |
| #ifdef HAVE_NDCTL_SMART |
| #define __check_smart(dimm, cmd, field) ({ \ |
| if (ndctl_cmd_smart_get_##field(cmd) != smart_data.field) { \ |
| fprintf(stderr, "%s dimm: %#x expected field %#x got: %#x\n", \ |
| __func__, ndctl_dimm_get_handle(dimm), \ |
| smart_data.field, \ |
| ndctl_cmd_smart_get_##field(cmd)); \ |
| ndctl_cmd_unref(cmd); \ |
| return -ENXIO; \ |
| } \ |
| }) |
| |
| static int check_smart(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| static const struct nd_smart_payload smart_data = { |
| .flags = ND_SMART_HEALTH_VALID | ND_SMART_TEMP_VALID |
| | ND_SMART_SPARES_VALID | ND_SMART_ALARM_VALID |
| | ND_SMART_USED_VALID | ND_SMART_SHUTDOWN_VALID, |
| .health = ND_SMART_NON_CRITICAL_HEALTH, |
| .temperature = 23 * 16, |
| .spares = 75, |
| .alarm_flags = ND_SMART_SPARE_TRIP | ND_SMART_TEMP_TRIP, |
| .life_used = 5, |
| .shutdown_state = 0, |
| .vendor_size = 0, |
| }; |
| struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_smart(dimm); |
| int rc; |
| |
| if (!cmd) { |
| fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", |
| __func__, ndctl_dimm_get_handle(dimm)); |
| return -ENXIO; |
| } |
| |
| rc = ndctl_cmd_submit(cmd); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| __check_smart(dimm, cmd, flags); |
| __check_smart(dimm, cmd, health); |
| __check_smart(dimm, cmd, temperature); |
| __check_smart(dimm, cmd, spares); |
| __check_smart(dimm, cmd, alarm_flags); |
| __check_smart(dimm, cmd, life_used); |
| __check_smart(dimm, cmd, shutdown_state); |
| __check_smart(dimm, cmd, vendor_size); |
| |
| ndctl_cmd_unref(cmd); |
| return 0; |
| } |
| |
| #define __check_smart_threshold(dimm, cmd, field) ({ \ |
| if (ndctl_cmd_smart_threshold_get_##field(cmd) != smart_t_data.field) { \ |
| fprintf(stderr, "%s dimm: %#x expected field %#x got: %#x\n", \ |
| __func__, ndctl_dimm_get_handle(dimm), \ |
| smart_t_data.field, \ |
| ndctl_cmd_smart_threshold_get_##field(cmd)); \ |
| ndctl_cmd_unref(cmd); \ |
| return -ENXIO; \ |
| } \ |
| }) |
| |
| static int check_smart_threshold(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| static const struct nd_smart_threshold_payload smart_t_data = { |
| .alarm_control = ND_SMART_SPARE_TRIP | ND_SMART_TEMP_TRIP, |
| .temperature = 40 * 16, |
| .spares = 5, |
| }; |
| struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_smart_threshold(dimm); |
| struct timeval tm; |
| fd_set fds; |
| int rc, fd; |
| |
| if (!cmd) { |
| fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", |
| __func__, ndctl_dimm_get_handle(dimm)); |
| return -ENXIO; |
| } |
| |
| fd = ndctl_dimm_get_health_eventfd(dimm); |
| FD_ZERO(&fds); |
| tm.tv_sec = 0; |
| tm.tv_usec = 500; |
| rc = select(fd + 1, NULL, NULL, &fds, &tm); |
| if (rc) { |
| fprintf(stderr, "%s: expected health event timeout\n", |
| ndctl_dimm_get_devname(dimm)); |
| return -ENXIO; |
| } |
| |
| /* |
| * Starting with v4.9 smart threshold requests trigger the file |
| * descriptor returned by ndctl_dimm_get_health_eventfd(). |
| */ |
| if (ndctl_test_attempt(check->test, KERNEL_VERSION(4, 9, 0))) { |
| int pid = fork(); |
| |
| if (pid == 0) { |
| FD_ZERO(&fds); |
| FD_SET(fd, &fds); |
| tm.tv_sec = 1; |
| tm.tv_usec = 0; |
| rc = select(fd + 1, NULL, NULL, &fds, &tm); |
| if (rc != 1 || !FD_ISSET(fd, &fds)) |
| exit(EXIT_FAILURE); |
| exit(EXIT_SUCCESS); |
| } |
| } |
| |
| rc = ndctl_cmd_submit(cmd); |
| if (rc) { |
| fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", |
| __func__, ndctl_dimm_get_handle(dimm), rc); |
| ndctl_cmd_unref(cmd); |
| return rc; |
| } |
| |
| if (ndctl_test_attempt(check->test, KERNEL_VERSION(4, 9, 0))) { |
| wait(&rc); |
| if (WEXITSTATUS(rc) == EXIT_FAILURE) { |
| fprintf(stderr, "%s: expect health event trigger\n", |
| ndctl_dimm_get_devname(dimm)); |
| return -ENXIO; |
| } |
| } |
| |
| __check_smart_threshold(dimm, cmd, alarm_control); |
| __check_smart_threshold(dimm, cmd, temperature); |
| __check_smart_threshold(dimm, cmd, spares); |
| |
| ndctl_cmd_unref(cmd); |
| return 0; |
| } |
| #else |
| static int check_smart(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| fprintf(stderr, "%s: HAVE_NDCTL_SMART disabled, skipping\n", __func__); |
| return 0; |
| } |
| |
| static int check_smart_threshold(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| struct check_cmd *check) |
| { |
| fprintf(stderr, "%s: HAVE_NDCTL_SMART disabled, skipping\n", __func__); |
| return 0; |
| } |
| #endif |
| |
| static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm, |
| unsigned long bus_commands, unsigned long dimm_commands, |
| struct ndctl_test *test) |
| { |
| /* |
| * For now, by coincidence, these are indexed in test execution |
| * order such that check_get_config_data can assume that |
| * check_get_config_size has updated |
| * check_cmd[ND_CMD_GET_CONFIG_SIZE].cmd and |
| * check_set_config_data can assume that both |
| * check_get_config_size and check_get_config_data have run |
| */ |
| struct check_cmd __check_dimm_cmds[] = { |
| [ND_CMD_GET_CONFIG_SIZE] = { check_get_config_size }, |
| [ND_CMD_GET_CONFIG_DATA] = { check_get_config_data }, |
| [ND_CMD_SET_CONFIG_DATA] = { check_set_config_data }, |
| [ND_CMD_SMART] = { check_smart }, |
| [ND_CMD_SMART_THRESHOLD] = { |
| .check_fn = check_smart_threshold, |
| .test = test, |
| }, |
| }; |
| |
| unsigned int i, rc = 0; |
| |
| /* |
| * The kernel did not start emulating v1.2 namespace spec smart data |
| * until 4.9. |
| */ |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) |
| dimm_commands &= ~((1 << ND_CMD_SMART) |
| | (1 << ND_CMD_SMART_THRESHOLD)); |
| |
| /* Check DIMM commands */ |
| check_cmds = __check_dimm_cmds; |
| for (i = 0; i < BITS_PER_LONG; i++) { |
| struct check_cmd *check = &check_cmds[i]; |
| |
| if ((dimm_commands & (1UL << i)) == 0) |
| continue; |
| if (!ndctl_dimm_is_cmd_supported(dimm, i)) { |
| fprintf(stderr, "%s: bus: %s dimm%d expected cmd: %s supported\n", |
| __func__, |
| ndctl_bus_get_provider(bus), |
| ndctl_dimm_get_id(dimm), |
| ndctl_dimm_get_cmd_name(dimm, i)); |
| return -ENXIO; |
| } |
| |
| if (!check->check_fn) |
| continue; |
| rc = check->check_fn(bus, dimm, check); |
| if (rc) |
| break; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(__check_dimm_cmds); i++) { |
| if (__check_dimm_cmds[i].cmd) |
| ndctl_cmd_unref(__check_dimm_cmds[i].cmd); |
| __check_dimm_cmds[i].cmd = NULL; |
| } |
| if (rc) |
| goto out; |
| |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 6, 0))) |
| goto out; |
| |
| out: |
| return rc; |
| } |
| |
| static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n, |
| unsigned long bus_commands, unsigned long dimm_commands, |
| struct ndctl_test *test) |
| { |
| int i, j, rc; |
| |
| for (i = 0; i < n; i++) { |
| struct ndctl_dimm *dimm = get_dimm_by_handle(bus, dimms[i].handle); |
| |
| if (!dimm) { |
| fprintf(stderr, "failed to find dimm: %d\n", dimms[i].phys_id); |
| return -ENXIO; |
| } |
| |
| if (ndctl_dimm_get_phys_id(dimm) != dimms[i].phys_id) { |
| fprintf(stderr, "dimm%d expected phys_id: %d got: %d\n", |
| i, dimms[i].phys_id, |
| ndctl_dimm_get_phys_id(dimm)); |
| return -ENXIO; |
| } |
| |
| if (ndctl_dimm_has_errors(dimm) != !!dimms[i].flags) { |
| fprintf(stderr, "bus: %s dimm%d %s expected%s errors\n", |
| ndctl_bus_get_provider(bus), i, |
| ndctl_dimm_get_devname(dimm), |
| dimms[i].flags ? "" : " no"); |
| return -ENXIO; |
| } |
| |
| if (ndctl_dimm_failed_save(dimm) != dimms[i].f_save |
| || ndctl_dimm_failed_arm(dimm) != dimms[i].f_arm |
| || ndctl_dimm_failed_restore(dimm) != dimms[i].f_restore |
| || ndctl_dimm_smart_pending(dimm) != dimms[i].f_smart |
| || ndctl_dimm_failed_flush(dimm) != dimms[i].f_flush) { |
| fprintf(stderr, "expected: %s%s%s%s%sgot: %s%s%s%s%s\n", |
| dimms[i].f_save ? "save_fail " : "", |
| dimms[i].f_arm ? "not_armed " : "", |
| dimms[i].f_restore ? "restore_fail " : "", |
| dimms[i].f_smart ? "smart_event " : "", |
| dimms[i].f_flush ? "flush_fail " : "", |
| ndctl_dimm_failed_save(dimm) ? "save_fail " : "", |
| ndctl_dimm_failed_arm(dimm) ? "not_armed " : "", |
| ndctl_dimm_failed_restore(dimm) ? "restore_fail " : "", |
| ndctl_dimm_smart_pending(dimm) ? "smart_event " : "", |
| ndctl_dimm_failed_flush(dimm) ? "flush_fail " : ""); |
| return -ENXIO; |
| } |
| |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) { |
| if (ndctl_dimm_get_formats(dimm) != dimms[i].formats) { |
| fprintf(stderr, "dimm%d expected formats: %d got: %d\n", |
| i, dimms[i].formats, |
| ndctl_dimm_get_formats(dimm)); |
| return -ENXIO; |
| } |
| for (j = 0; j < dimms[i].formats; j++) { |
| if (ndctl_dimm_get_formatN(dimm, j) != dimms[i].format[j]) { |
| fprintf(stderr, |
| "dimm%d expected format[%d]: %d got: %d\n", |
| i, j, dimms[i].format[j], |
| ndctl_dimm_get_formatN(dimm, j)); |
| return -ENXIO; |
| } |
| } |
| } |
| |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) { |
| if (ndctl_dimm_get_subsystem_vendor(dimm) |
| != dimms[i].subsystem_vendor) { |
| fprintf(stderr, |
| "dimm%d expected subsystem vendor: %d got: %d\n", |
| i, dimms[i].subsystem_vendor, |
| ndctl_dimm_get_subsystem_vendor(dimm)); |
| return -ENXIO; |
| } |
| } |
| |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) { |
| if (ndctl_dimm_get_manufacturing_date(dimm) |
| != dimms[i].manufacturing_date) { |
| fprintf(stderr, |
| "dimm%d expected manufacturing date: %d got: %d\n", |
| i, dimms[i].manufacturing_date, |
| ndctl_dimm_get_manufacturing_date(dimm)); |
| return -ENXIO; |
| } |
| } |
| |
| rc = check_commands(bus, dimm, bus_commands, dimm_commands, test); |
| if (rc) |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static void reset_bus(struct ndctl_bus *bus) |
| { |
| struct ndctl_region *region; |
| struct ndctl_dimm *dimm; |
| |
| /* disable all regions so that set_config_data commands are permitted */ |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| ndctl_dimm_foreach(bus, dimm) |
| ndctl_dimm_zero_labels(dimm); |
| |
| /* set regions back to their default state */ |
| ndctl_region_foreach(bus, region) |
| ndctl_region_enable(region); |
| } |
| |
| static int do_test0(struct ndctl_ctx *ctx, struct ndctl_test *test) |
| { |
| struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0); |
| struct ndctl_region *region; |
| struct ndctl_dimm *dimm; |
| int rc; |
| |
| if (!bus) |
| return -ENXIO; |
| |
| ndctl_bus_wait_probe(bus); |
| |
| /* disable all regions so that set_config_data commands are permitted */ |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| rc = check_dimms(bus, dimms0, ARRAY_SIZE(dimms0), bus_commands0, |
| dimm_commands0, test); |
| if (rc) |
| return rc; |
| |
| ndctl_dimm_foreach(bus, dimm) { |
| rc = ndctl_dimm_zero_labels(dimm); |
| if (rc < 0) { |
| fprintf(stderr, "failed to zero %s\n", |
| ndctl_dimm_get_devname(dimm)); |
| return rc; |
| } |
| } |
| |
| /* set regions back to their default state */ |
| ndctl_region_foreach(bus, region) |
| ndctl_region_enable(region); |
| |
| /* pfn and dax tests require vmalloc-enabled nfit_test */ |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) { |
| rc = check_regions(bus, regions0, ARRAY_SIZE(regions0), DAX); |
| if (rc) |
| return rc; |
| reset_bus(bus); |
| } |
| |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) { |
| rc = check_regions(bus, regions0, ARRAY_SIZE(regions0), PFN); |
| if (rc) |
| return rc; |
| reset_bus(bus); |
| } |
| |
| return check_regions(bus, regions0, ARRAY_SIZE(regions0), BTT); |
| } |
| |
| static int do_test1(struct ndctl_ctx *ctx, struct ndctl_test *test) |
| { |
| struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER1); |
| int rc; |
| |
| if (!bus) |
| return -ENXIO; |
| |
| ndctl_bus_wait_probe(bus); |
| |
| /* |
| * Starting with v4.10 the dimm on nfit_test.1 gets a unique |
| * handle. |
| */ |
| if (ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0))) |
| dimms1[0].handle = DIMM_HANDLE(1, 0, 0, 0, 0); |
| |
| rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0, 0, test); |
| if (rc) |
| return rc; |
| |
| return check_regions(bus, regions1, ARRAY_SIZE(regions1), BTT); |
| } |
| |
| typedef int (*do_test_fn)(struct ndctl_ctx *ctx, struct ndctl_test *test); |
| static do_test_fn do_test[] = { |
| do_test0, |
| do_test1, |
| }; |
| |
| int test_libndctl(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) |
| { |
| unsigned int i; |
| struct kmod_module *mod; |
| struct kmod_ctx *kmod_ctx; |
| struct daxctl_ctx *daxctl_ctx; |
| int err, result = EXIT_FAILURE; |
| |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0))) |
| return 77; |
| |
| ndctl_set_log_priority(ctx, loglevel); |
| daxctl_ctx = ndctl_get_daxctl_ctx(ctx); |
| daxctl_set_log_priority(daxctl_ctx, loglevel); |
| ndctl_set_private_data(ctx, test); |
| |
| err = nfit_test_init(&kmod_ctx, &mod, loglevel, test); |
| if (err < 0) { |
| ndctl_test_skip(test); |
| fprintf(stderr, "nfit_test unavailable skipping tests\n"); |
| return 77; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(do_test); i++) { |
| err = do_test[i](ctx, test); |
| if (err < 0) { |
| fprintf(stderr, "ndctl-test%d failed: %d\n", i, err); |
| break; |
| } |
| } |
| |
| if (i >= ARRAY_SIZE(do_test)) |
| result = EXIT_SUCCESS; |
| kmod_module_remove_module(mod, 0); |
| kmod_unref(kmod_ctx); |
| return result; |
| } |
| |
| int __attribute__((weak)) main(int argc, char *argv[]) |
| { |
| struct ndctl_test *test = ndctl_test_new(0); |
| struct ndctl_ctx *ctx; |
| int rc; |
| |
| if (!test) { |
| fprintf(stderr, "failed to initialize test\n"); |
| return EXIT_FAILURE; |
| } |
| |
| rc = ndctl_new(&ctx); |
| if (rc) |
| return ndctl_test_result(test, rc); |
| rc = test_libndctl(LOG_DEBUG, test, ctx); |
| ndctl_unref(ctx); |
| return ndctl_test_result(test, rc); |
| } |