| /* |
| * Copyright(c) 2015-2017 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| */ |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <syslog.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <sys/time.h> |
| #include <libkmod.h> |
| #include <uuid/uuid.h> |
| #include <sys/types.h> |
| #include <util/size.h> |
| #include <linux/falloc.h> |
| #include <linux/version.h> |
| #include <ndctl/libndctl.h> |
| #include <ccan/array_size/array_size.h> |
| |
| #include <ndctl.h> |
| #include <builtin.h> |
| #include <test.h> |
| |
| #define NUM_NAMESPACES 4 |
| #define SZ_NAMESPACE SZ_16M |
| |
| static int setup_namespace(struct ndctl_region *region) |
| { |
| struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); |
| const char *argv[] = { |
| "__func__", "-v", "-m", "raw", "-s", "16M", "-r", "", |
| }; |
| int argc = ARRAY_SIZE(argv); |
| |
| argv[argc - 1] = ndctl_region_get_devname(region); |
| builtin_xaction_namespace_reset(); |
| return cmd_create_namespace(argc, argv, ctx); |
| } |
| |
| static void destroy_namespace(struct ndctl_namespace *ndns) |
| { |
| struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); |
| const char *argv[] = { |
| "__func__", "-v", "-f", "", |
| }; |
| int argc = ARRAY_SIZE(argv); |
| |
| argv[argc - 1] = ndctl_namespace_get_devname(ndns); |
| builtin_xaction_namespace_reset(); |
| cmd_destroy_namespace(argc, argv, ctx); |
| } |
| |
| /* Check that the namespace device is gone (if it wasn't the seed) */ |
| static int check_deleted(struct ndctl_region *region, const char *devname, |
| struct ndctl_test *test) |
| { |
| struct ndctl_namespace *ndns; |
| |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0))) |
| return 0; |
| |
| ndctl_namespace_foreach(region, ndns) { |
| if (strcmp(devname, ndctl_namespace_get_devname(ndns))) |
| continue; |
| if (ndns == ndctl_region_get_namespace_seed(region)) |
| continue; |
| fprintf(stderr, "multi-pmem: expected %s to be deleted\n", |
| devname); |
| return -ENXIO; |
| } |
| |
| return 0; |
| } |
| |
| static int do_multi_pmem(struct ndctl_ctx *ctx, struct ndctl_test *test) |
| { |
| int i; |
| char devname[100]; |
| struct ndctl_bus *bus; |
| uuid_t uuid[NUM_NAMESPACES]; |
| struct ndctl_namespace *ndns; |
| struct ndctl_dimm *dimm_target, *dimm; |
| struct ndctl_region *region, *target = NULL; |
| struct ndctl_namespace *namespaces[NUM_NAMESPACES]; |
| unsigned long long blk_avail, blk_avail_orig, expect; |
| |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) { |
| ndctl_test_skip(test); |
| return 77; |
| } |
| |
| bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0"); |
| if (!bus) |
| return -ENXIO; |
| |
| /* 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) { |
| int 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 and find our target |
| * region. |
| */ |
| ndctl_region_foreach(bus, region) { |
| ndctl_region_enable(region); |
| if (ndctl_region_get_available_size(region) |
| == SZ_NAMESPACE * NUM_NAMESPACES) |
| target = region; |
| } |
| |
| if (!target) { |
| fprintf(stderr, "multi-pmem: failed to find target region\n"); |
| return -ENXIO; |
| } |
| region = target; |
| |
| for (i = 0; i < (int) ARRAY_SIZE(uuid); i++) { |
| if (setup_namespace(region) != 0) { |
| fprintf(stderr, "multi-pmem: failed to setup namespace: %d\n", i); |
| return -ENXIO; |
| } |
| sprintf(devname, "namespace%d.%d", |
| ndctl_region_get_id(region), i); |
| ndctl_namespace_foreach(region, ndns) |
| if (strcmp(ndctl_namespace_get_devname(ndns), devname) == 0 |
| && ndctl_namespace_is_enabled(ndns)) |
| break; |
| if (!ndns) { |
| fprintf(stderr, "multi-pmem: failed to find namespace: %s\n", |
| devname); |
| return -ENXIO; |
| } |
| ndctl_namespace_get_uuid(ndns, uuid[i]); |
| } |
| |
| /* bounce the region and verify everything came back as expected */ |
| ndctl_region_disable_invalidate(region); |
| ndctl_region_enable(region); |
| |
| for (i = 0; i < (int) ARRAY_SIZE(uuid); i++) { |
| char uuid_str1[40], uuid_str2[40]; |
| uuid_t uuid_check; |
| |
| ndctl_namespace_foreach(region, ndns) |
| sprintf(devname, "namespace%d.%d", |
| ndctl_region_get_id(region), i); |
| ndctl_namespace_foreach(region, ndns) |
| if (strcmp(ndctl_namespace_get_devname(ndns), devname) == 0 |
| && ndctl_namespace_is_enabled(ndns)) |
| break; |
| if (!ndns) { |
| fprintf(stderr, "multi-pmem: failed to restore namespace: %s\n", |
| devname); |
| return -ENXIO; |
| } |
| |
| ndctl_namespace_get_uuid(ndns, uuid_check); |
| uuid_unparse(uuid_check, uuid_str2); |
| uuid_unparse(uuid[i], uuid_str1); |
| if (uuid_compare(uuid_check, uuid[i]) != 0) { |
| fprintf(stderr, "multi-pmem: expected uuid[%d]: %s, got %s\n", |
| i, uuid_str1, uuid_str2); |
| return -ENXIO; |
| } |
| namespaces[i] = ndns; |
| } |
| |
| /* |
| * Check that aliased blk capacity does not increase until the |
| * highest dpa pmem-namespace is deleted. |
| */ |
| dimm_target = ndctl_region_get_first_dimm(region); |
| if (!dimm_target) { |
| fprintf(stderr, "multi-pmem: failed to retrieve dimm from %s\n", |
| ndctl_region_get_devname(region)); |
| return -ENXIO; |
| } |
| |
| dimm = NULL; |
| ndctl_region_foreach(bus, region) { |
| if (ndctl_region_get_type(region) != ND_DEVICE_REGION_BLK) |
| continue; |
| ndctl_dimm_foreach_in_region(region, dimm) |
| if (dimm == dimm_target) |
| break; |
| if (dimm) |
| break; |
| } |
| |
| blk_avail_orig = ndctl_region_get_available_size(region); |
| for (i = 1; i < NUM_NAMESPACES - 1; i++) { |
| ndns = namespaces[i]; |
| sprintf(devname, "%s", ndctl_namespace_get_devname(ndns)); |
| destroy_namespace(ndns); |
| blk_avail = ndctl_region_get_available_size(region); |
| if (blk_avail != blk_avail_orig) { |
| fprintf(stderr, "multi-pmem: destroy %s %llx avail, expect %llx\n", |
| devname, blk_avail, blk_avail_orig); |
| return -ENXIO; |
| } |
| |
| if (check_deleted(target, devname, test) != 0) |
| return -ENXIO; |
| } |
| |
| ndns = namespaces[NUM_NAMESPACES - 1]; |
| sprintf(devname, "%s", ndctl_namespace_get_devname(ndns)); |
| destroy_namespace(ndns); |
| blk_avail = ndctl_region_get_available_size(region); |
| expect = (SZ_NAMESPACE / ndctl_region_get_interleave_ways(target)) |
| * (NUM_NAMESPACES - 1) + blk_avail_orig; |
| if (blk_avail != expect) { |
| fprintf(stderr, "multi-pmem: destroy %s %llx avail, expect %llx\n", |
| devname, blk_avail, expect); |
| return -ENXIO; |
| } |
| |
| if (check_deleted(target, devname, test) != 0) |
| return -ENXIO; |
| |
| ndctl_bus_foreach(ctx, bus) { |
| if (strncmp(ndctl_bus_get_provider(bus), "nfit_test", 9) != 0) |
| continue; |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| } |
| |
| return 0; |
| } |
| |
| int test_multi_pmem(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) |
| { |
| struct kmod_module *mod; |
| struct kmod_ctx *kmod_ctx; |
| int err, result = EXIT_FAILURE; |
| |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0))) |
| return 77; |
| |
| ndctl_set_log_priority(ctx, loglevel); |
| |
| err = nfit_test_init(&kmod_ctx, &mod, NULL, loglevel, test); |
| if (err < 0) { |
| result = 77; |
| ndctl_test_skip(test); |
| fprintf(stderr, "%s unavailable skipping tests\n", |
| "nfit_test"); |
| return result; |
| } |
| |
| result = do_multi_pmem(ctx, test); |
| |
| 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_multi_pmem(LOG_DEBUG, test, ctx); |
| ndctl_unref(ctx); |
| return ndctl_test_result(test, rc); |
| } |