| /* |
| * 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 <ctype.h> |
| #include <errno.h> |
| #include <syslog.h> |
| #include <libkmod.h> |
| #include <util/log.h> |
| #include <util/sysfs.h> |
| #include <linux/version.h> |
| |
| #include <ccan/array_size/array_size.h> |
| #include <ndctl/libndctl.h> |
| #include <builtin.h> |
| #include <ndctl.h> |
| #include <test.h> |
| |
| #define DIMM_PATH "/sys/devices/platform/nfit_test.0/nfit_test_dimm/test_dimm0" |
| |
| static int 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) { |
| if (!ndctl_dimm_read_labels(dimm)) |
| return -ENXIO; |
| ndctl_dimm_disable(dimm); |
| ndctl_dimm_init_labels(dimm, NDCTL_NS_VERSION_1_2); |
| ndctl_dimm_enable(dimm); |
| } |
| |
| /* set regions back to their default state */ |
| ndctl_region_foreach(bus, region) |
| ndctl_region_enable(region); |
| return 0; |
| } |
| |
| static int set_dimm_response(const char *dimm_path, int cmd, int error_code, |
| struct log_ctx *log_ctx) |
| { |
| char path[1024], buf[SYSFS_ATTR_SIZE]; |
| int rc; |
| |
| if (error_code) { |
| sprintf(path, "%s/fail_cmd", dimm_path); |
| sprintf(buf, "%#x\n", 1 << cmd); |
| rc = __sysfs_write_attr(log_ctx, path, buf); |
| if (rc) |
| goto out; |
| sprintf(path, "%s/fail_cmd_code", dimm_path); |
| sprintf(buf, "%d\n", error_code); |
| rc = __sysfs_write_attr(log_ctx, path, buf); |
| if (rc) |
| goto out; |
| } else { |
| sprintf(path, "%s/fail_cmd", dimm_path); |
| sprintf(buf, "0\n"); |
| rc = __sysfs_write_attr(log_ctx, path, buf); |
| if (rc) |
| goto out; |
| } |
| out: |
| if (rc < 0) |
| fprintf(stderr, "%s failed, cmd: %d code: %d\n", |
| __func__, cmd, error_code); |
| return 0; |
| } |
| |
| static int dimms_disable(struct ndctl_bus *bus) |
| { |
| struct ndctl_dimm *dimm; |
| |
| ndctl_dimm_foreach(bus, dimm) { |
| int rc = ndctl_dimm_disable(dimm); |
| |
| if (rc) { |
| fprintf(stderr, "dimm: %s failed to disable: %d\n", |
| ndctl_dimm_get_devname(dimm), rc); |
| return rc; |
| } |
| } |
| return 0; |
| } |
| |
| static int test_dimms_enable(struct ndctl_bus *bus, struct ndctl_dimm *victim, |
| bool expect) |
| { |
| struct ndctl_dimm *dimm; |
| |
| ndctl_dimm_foreach(bus, dimm) { |
| int rc = ndctl_dimm_enable(dimm); |
| |
| if (((expect != (rc == 0)) && (dimm == victim)) |
| || (rc && dimm != victim)) { |
| bool __expect = true; |
| |
| if (dimm == victim) |
| __expect = expect; |
| fprintf(stderr, "fail expected %s enable %s victim: %s rc: %d\n", |
| ndctl_dimm_get_devname(dimm), |
| __expect ? "success" : "failure", |
| ndctl_dimm_get_devname(victim), rc); |
| return -ENXIO; |
| } |
| } |
| return 0; |
| } |
| |
| static int test_regions_enable(struct ndctl_bus *bus, |
| struct ndctl_dimm *victim, struct ndctl_region *victim_region, |
| bool region_expect, int namespace_count) |
| { |
| struct ndctl_region *region; |
| |
| ndctl_region_foreach(bus, region) { |
| struct ndctl_namespace *ndns; |
| struct ndctl_dimm *dimm; |
| bool has_victim = false; |
| int rc, count = 0; |
| |
| ndctl_dimm_foreach_in_region(region, dimm) { |
| if (dimm == victim) { |
| has_victim = true; |
| break; |
| } |
| } |
| |
| rc = ndctl_region_enable(region); |
| fprintf(stderr, "region: %s enable: %d has_victim: %d\n", |
| ndctl_region_get_devname(region), rc, has_victim); |
| if (((region_expect != (rc == 0)) && has_victim) |
| || (rc && !has_victim)) { |
| bool __expect = true; |
| |
| if (has_victim) |
| __expect = region_expect; |
| fprintf(stderr, "%s: fail expected enable: %s with %s\n", |
| ndctl_region_get_devname(region), |
| __expect ? "success" : "failure", |
| ndctl_dimm_get_devname(victim)); |
| return -ENXIO; |
| } |
| if (region != victim_region) |
| continue; |
| ndctl_namespace_foreach(region, ndns) { |
| if (ndctl_namespace_is_enabled(ndns)) { |
| fprintf(stderr, "%s: enabled, expected disabled\n", |
| ndctl_namespace_get_devname(ndns)); |
| return -ENXIO; |
| } |
| fprintf(stderr, "%s: %s: size: %lld\n", __func__, |
| ndctl_namespace_get_devname(ndns), |
| ndctl_namespace_get_size(ndns)); |
| count++; |
| } |
| if (count != namespace_count) { |
| fprintf(stderr, "%s: fail expected %d namespaces got %d\n", |
| ndctl_region_get_devname(region), |
| namespace_count, count); |
| return -ENXIO; |
| } |
| } |
| return 0; |
| } |
| |
| static int do_test(struct ndctl_ctx *ctx, struct ndctl_test *test) |
| { |
| struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0"); |
| struct ndctl_region *region, *victim_region = NULL; |
| struct ndctl_dimm *dimm, *victim = NULL; |
| char path[1024], buf[SYSFS_ATTR_SIZE]; |
| struct log_ctx log_ctx; |
| unsigned int handle; |
| int rc, err = 0; |
| |
| if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) |
| return 77; |
| |
| if (!bus) |
| return -ENXIO; |
| |
| log_init(&log_ctx, "test/dsm-fail", "NDCTL_TEST"); |
| |
| if (reset_bus(bus)) { |
| fprintf(stderr, "failed to read labels\n"); |
| return -ENXIO; |
| } |
| |
| sprintf(path, "%s/handle", DIMM_PATH); |
| rc = __sysfs_read_attr(&log_ctx, path, buf); |
| if (rc) { |
| fprintf(stderr, "failed to retrieve test dimm handle\n"); |
| return -ENXIO; |
| } |
| |
| handle = strtoul(buf, NULL, 0); |
| |
| ndctl_dimm_foreach(bus, dimm) |
| if (ndctl_dimm_get_handle(dimm) == handle) { |
| victim = dimm; |
| break; |
| } |
| |
| if (!victim) { |
| fprintf(stderr, "failed to find victim dimm\n"); |
| return -ENXIO; |
| } |
| fprintf(stderr, "victim: %s\n", ndctl_dimm_get_devname(victim)); |
| |
| ndctl_region_foreach(bus, region) { |
| if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM) |
| continue; |
| ndctl_dimm_foreach_in_region(region, dimm) { |
| const char *argv[] = { |
| "__func__", "-v", "-r", |
| ndctl_region_get_devname(region), |
| "-s", "4M", "-m", "raw", |
| }; |
| struct ndctl_namespace *ndns; |
| int count, i; |
| |
| if (dimm != victim) |
| continue; |
| /* |
| * Validate that we only have the one seed |
| * namespace, and then create one so that we can |
| * verify namespace enumeration while locked. |
| */ |
| count = 0; |
| ndctl_namespace_foreach(region, ndns) |
| count++; |
| if (count != 1) { |
| fprintf(stderr, "%s: found %d namespaces expected 1\n", |
| ndctl_region_get_devname(region), |
| count); |
| rc = -ENXIO; |
| goto out; |
| } |
| if (ndctl_region_get_size(region) |
| != ndctl_region_get_available_size(region)) { |
| fprintf(stderr, "%s: expected empty region\n", |
| ndctl_region_get_devname(region)); |
| rc = -ENXIO; |
| goto out; |
| } |
| for (i = 0; i < 2; i++) { |
| builtin_xaction_namespace_reset(); |
| rc = cmd_create_namespace(ARRAY_SIZE(argv), argv, |
| ndctl_region_get_ctx(region)); |
| if (rc) { |
| fprintf(stderr, "%s: failed to create namespace\n", |
| ndctl_region_get_devname(region)); |
| rc = -ENXIO; |
| goto out; |
| } |
| } |
| victim_region = region; |
| } |
| if (victim_region) |
| break; |
| } |
| |
| /* disable all regions so that we can disable a dimm */ |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| |
| rc = dimms_disable(bus); |
| if (rc) |
| goto out; |
| |
| |
| rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_SIZE, -EACCES, |
| &log_ctx); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = test_dimms_enable(bus, victim, true); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = test_regions_enable(bus, victim, victim_region, true, 2); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_SIZE, 0, &log_ctx); |
| if (rc) |
| goto out; |
| |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = dimms_disable(bus); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| |
| rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_DATA, -EACCES, |
| &log_ctx); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = test_dimms_enable(bus, victim, false); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = test_regions_enable(bus, victim, victim_region, false, 0); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_DATA, 0, &log_ctx); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| rc = dimms_disable(bus); |
| if (rc) |
| goto out; |
| fprintf(stderr, "%s:%d\n", __func__, __LINE__); |
| |
| out: |
| err = rc; |
| sprintf(path, "%s/fail_cmd", DIMM_PATH); |
| sprintf(buf, "0\n"); |
| rc = __sysfs_write_attr(&log_ctx, path, buf); |
| if (rc) |
| fprintf(stderr, "%s: failed to clear fail_cmd mask\n", |
| ndctl_dimm_get_devname(victim)); |
| rc = ndctl_dimm_enable(victim); |
| if (rc) { |
| fprintf(stderr, "failed to enable victim: %s after clearing error\n", |
| ndctl_dimm_get_devname(victim)); |
| rc = -ENXIO; |
| } |
| reset_bus(bus); |
| |
| if (rc) |
| err = rc; |
| return err; |
| } |
| |
| int test_dsm_fail(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) |
| { |
| struct kmod_module *mod; |
| struct kmod_ctx *kmod_ctx; |
| int result = EXIT_FAILURE, err; |
| |
| 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_test(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_dsm_fail(LOG_DEBUG, test, ctx); |
| ndctl_unref(ctx); |
| return ndctl_test_result(test, rc); |
| } |