| /* |
| * 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 <uuid/uuid.h> |
| |
| #include <test.h> |
| #include <linux/version.h> |
| #include <ndctl/libndctl.h> |
| #include <ccan/array_size/array_size.h> |
| |
| #ifdef HAVE_NDCTL_H |
| #include <linux/ndctl.h> |
| #else |
| #include <ndctl.h> |
| #endif |
| |
| static const char *NFIT_PROVIDER0 = "nfit_test.0"; |
| static const char *NFIT_PROVIDER1 = "nfit_test.1"; |
| #define SZ_4K 0x1000UL |
| #define NUM_NAMESPACES 4 |
| |
| struct test_dpa_namespace { |
| struct ndctl_namespace *ndns; |
| unsigned long long size; |
| uuid_t uuid; |
| } namespaces[NUM_NAMESPACES]; |
| |
| static int do_test(struct ndctl_ctx *ctx, struct ndctl_test *test) |
| { |
| unsigned int default_available_slots, available_slots, i; |
| struct ndctl_region *region, *blk_region = NULL; |
| struct ndctl_namespace *ndns; |
| struct ndctl_dimm *dimm; |
| unsigned long size; |
| struct ndctl_bus *bus; |
| char uuid_str[40]; |
| int round; |
| int rc; |
| |
| /* disable nfit_test.1, not used in this test */ |
| bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER1); |
| if (!bus) |
| return -ENXIO; |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| /* init nfit_test.0 */ |
| bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0); |
| if (!bus) |
| return -ENXIO; |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| 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; |
| } |
| } |
| |
| /* |
| * Find a guineapig BLK region, we know that the dimm with |
| * handle==0 from nfit_test.0 always allocates from highest DPA |
| * to lowest with no excursions into BLK only ranges. |
| */ |
| ndctl_region_foreach(bus, region) { |
| if (ndctl_region_get_type(region) != ND_DEVICE_REGION_BLK) |
| continue; |
| dimm = ndctl_region_get_first_dimm(region); |
| if (!dimm) |
| continue; |
| if (ndctl_dimm_get_handle(dimm) == 0) { |
| blk_region = region; |
| break; |
| } |
| } |
| if (!blk_region || ndctl_region_enable(blk_region) < 0) { |
| fprintf(stderr, "failed to find a usable BLK region\n"); |
| return -ENXIO; |
| } |
| region = blk_region; |
| |
| if (ndctl_region_get_available_size(region) / ND_MIN_NAMESPACE_SIZE |
| < NUM_NAMESPACES) { |
| fprintf(stderr, "%s insufficient available_size\n", |
| ndctl_region_get_devname(region)); |
| return -ENXIO; |
| } |
| |
| default_available_slots = ndctl_dimm_get_available_labels(dimm); |
| |
| /* grow namespaces */ |
| for (i = 0; i < ARRAY_SIZE(namespaces); i++) { |
| uuid_t uuid; |
| |
| ndns = ndctl_region_get_namespace_seed(region); |
| if (!ndns) { |
| fprintf(stderr, "%s: failed to get seed: %d\n", |
| ndctl_region_get_devname(region), i); |
| return -ENXIO; |
| } |
| uuid_generate_random(uuid); |
| ndctl_namespace_set_uuid(ndns, uuid); |
| ndctl_namespace_set_sector_size(ndns, 512); |
| ndctl_namespace_set_size(ndns, ND_MIN_NAMESPACE_SIZE); |
| rc = ndctl_namespace_enable(ndns); |
| if (rc) { |
| fprintf(stderr, "failed to enable %s: %d\n", |
| ndctl_namespace_get_devname(ndns), rc); |
| return rc; |
| } |
| ndctl_namespace_disable_invalidate(ndns); |
| rc = ndctl_namespace_set_size(ndns, SZ_4K); |
| if (rc) { |
| fprintf(stderr, "failed to init %s to size: %ld\n", |
| ndctl_namespace_get_devname(ndns), |
| SZ_4K); |
| return rc; |
| } |
| namespaces[i].ndns = ndns; |
| ndctl_namespace_get_uuid(ndns, namespaces[i].uuid); |
| } |
| |
| available_slots = ndctl_dimm_get_available_labels(dimm); |
| if (available_slots != default_available_slots |
| - ARRAY_SIZE(namespaces)) { |
| fprintf(stderr, "expected %ld slots available\n", |
| default_available_slots |
| - ARRAY_SIZE(namespaces)); |
| return -ENOSPC; |
| } |
| |
| /* exhaust label space, by round-robin allocating 4K */ |
| round = 1; |
| for (i = 0; i < available_slots; i++) { |
| ndns = namespaces[i % ARRAY_SIZE(namespaces)].ndns; |
| if (i % ARRAY_SIZE(namespaces) == 0) |
| round++; |
| size = SZ_4K * round; |
| rc = ndctl_namespace_set_size(ndns, size); |
| if (rc) { |
| fprintf(stderr, "%s: set_size: %lx failed: %d\n", |
| ndctl_namespace_get_devname(ndns), size, rc); |
| return rc; |
| } |
| } |
| |
| /* |
| * The last namespace we updated should still be modifiable via |
| * the kernel's reserve label |
| */ |
| i--; |
| round++; |
| ndns = namespaces[i % ARRAY_SIZE(namespaces)].ndns; |
| size = SZ_4K * round; |
| rc = ndctl_namespace_set_size(ndns, size); |
| if (rc) { |
| fprintf(stderr, "%s failed to update while labels full\n", |
| ndctl_namespace_get_devname(ndns)); |
| return rc; |
| } |
| |
| round--; |
| size = SZ_4K * round; |
| rc = ndctl_namespace_set_size(ndns, size); |
| if (rc) { |
| fprintf(stderr, "%s failed to reduce size while labels full\n", |
| ndctl_namespace_get_devname(ndns)); |
| return rc; |
| } |
| |
| /* do the allocations survive a region cycle? */ |
| for (i = 0; i < ARRAY_SIZE(namespaces); i++) { |
| ndns = namespaces[i].ndns; |
| namespaces[i].size = ndctl_namespace_get_size(ndns); |
| namespaces[i].ndns = NULL; |
| } |
| |
| ndctl_region_disable_invalidate(region); |
| rc = ndctl_region_enable(region); |
| if (rc) { |
| fprintf(stderr, "failed to re-enable %s: %d\n", |
| ndctl_region_get_devname(region), rc); |
| return rc; |
| } |
| |
| ndctl_namespace_foreach(region, ndns) { |
| uuid_t uuid; |
| |
| ndctl_namespace_get_uuid(ndns, uuid); |
| for (i = 0; i < ARRAY_SIZE(namespaces); i++) { |
| if (uuid_compare(uuid, namespaces[i].uuid) == 0) { |
| namespaces[i].ndns = ndns; |
| break; |
| } |
| } |
| } |
| |
| /* validate that they all came back */ |
| for (i = 0; i < ARRAY_SIZE(namespaces); i++) { |
| ndns = namespaces[i].ndns; |
| size = ndns ? ndctl_namespace_get_size(ndns) : 0; |
| |
| if (ndns && size == namespaces[i].size) |
| continue; |
| uuid_unparse(namespaces[i].uuid, uuid_str); |
| fprintf(stderr, "failed to recover %s\n", uuid_str); |
| return -ENODEV; |
| } |
| |
| /* test deletion and merging */ |
| ndns = namespaces[0].ndns; |
| for (i = 1; i < ARRAY_SIZE(namespaces); i++) { |
| struct ndctl_namespace *victim = namespaces[i].ndns; |
| |
| uuid_unparse(namespaces[i].uuid, uuid_str); |
| size = ndctl_namespace_get_size(victim); |
| rc = ndctl_namespace_delete(victim); |
| if (rc) { |
| fprintf(stderr, "failed to delete %s\n", uuid_str); |
| return rc; |
| } |
| size += ndctl_namespace_get_size(ndns); |
| rc = ndctl_namespace_set_size(ndns, size); |
| if (rc) { |
| fprintf(stderr, "failed to merge %s\n", uuid_str); |
| return rc; |
| } |
| } |
| |
| /* there can be only one */ |
| i = 0; |
| ndctl_namespace_foreach(region, ndns) { |
| unsigned long long sz = ndctl_namespace_get_size(ndns); |
| |
| if (sz) { |
| i++; |
| if (sz == size) |
| continue; |
| fprintf(stderr, "%s size: %llx expected %lx\n", |
| ndctl_namespace_get_devname(ndns), |
| sz, size); |
| return -ENXIO; |
| } |
| } |
| if (i != 1) { |
| fprintf(stderr, "failed to delete namespaces\n"); |
| return -ENXIO; |
| } |
| |
| available_slots = ndctl_dimm_get_available_labels(dimm); |
| if (available_slots != default_available_slots - 1) { |
| fprintf(stderr, "mishandled slot count\n"); |
| return -ENXIO; |
| } |
| |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| return 0; |
| } |
| |
| int test_dpa_alloc(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, loglevel, test); |
| if (err < 0) { |
| ndctl_test_skip(test); |
| fprintf(stderr, "nfit_test unavailable skipping tests\n"); |
| return 77; |
| } |
| |
| err = do_test(ctx, test); |
| if (err == 0) |
| 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_dpa_alloc(LOG_DEBUG, test, ctx); |
| ndctl_unref(ctx); |
| return ndctl_test_result(test, rc); |
| } |