blob: 3e3c1037e016a2e07649970b8ef6abe8b506dc04 [file] [log] [blame]
/*
* 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>
#ifdef HAVE_NDCTL_H
#include <linux/ndctl.h>
#else
#include <ndctl.h>
#endif
#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, 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);
}