blob: 22ec94451146ddf40b9f37da7e5f9402b3cbefb5 [file] [log] [blame]
/*
* 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>
#ifdef HAVE_NDCTL_H
#include <linux/ndctl.h>
#else
#include <ndctl.h>
#endif
#include <test.h>
#define DIMM_PATH "/sys/devices/platform/nfit_test.0/nfit_test_dimm/test_dimm0"
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_test(struct ndctl_ctx *ctx, struct ndctl_test *test)
{
struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0");
struct ndctl_dimm *dimm, *victim = NULL;
char path[1024], buf[SYSFS_ATTR_SIZE];
struct ndctl_region *region;
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");
ndctl_bus_wait_probe(bus);
/* disable all regions so that we can disable a dimm */
ndctl_region_foreach(bus, region)
ndctl_region_disable_invalidate(region);
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;
if (ndctl_dimm_disable(dimm)) {
fprintf(stderr, "failed to disable: %s\n",
ndctl_dimm_get_devname(dimm));
return -ENXIO;
}
}
if (!victim) {
fprintf(stderr, "failed to find victim dimm\n");
return -ENXIO;
}
fprintf(stderr, "victim: %s\n", ndctl_dimm_get_devname(victim));
sprintf(path, "%s/fail_cmd", DIMM_PATH);
sprintf(buf, "%#x\n", 1 << ND_CMD_GET_CONFIG_SIZE);
rc = __sysfs_write_attr(&log_ctx, path, buf);
if (rc) {
fprintf(stderr, "failed to set fail cmd mask\n");
return -ENXIO;
}
ndctl_dimm_foreach(bus, dimm) {
rc = ndctl_dimm_enable(dimm);
fprintf(stderr, "dimm: %s enable: %d\n",
ndctl_dimm_get_devname(dimm), rc);
if ((rc == 0) == (dimm == victim)) {
fprintf(stderr, "fail expected %s enable %s victim: %s\n",
ndctl_dimm_get_devname(dimm),
(dimm == victim) ? "failure" : "success",
ndctl_dimm_get_devname(victim));
err = -ENXIO;
goto out;
}
}
ndctl_region_foreach(bus, region) {
bool has_victim = false;
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 ((rc == 0) == has_victim) {
fprintf(stderr, "fail expected %s enable %s with %s disabled\n",
ndctl_region_get_devname(region),
has_victim ? "failure" : "success",
ndctl_dimm_get_devname(victim));
err = -ENXIO;
goto out;
}
}
out:
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));
err = -ENXIO;
}
rc = ndctl_dimm_enable(victim);
if (rc) {
fprintf(stderr, "failed to enable victim: %s after clearing error\n",
ndctl_dimm_get_devname(victim));
err = -ENXIO;
}
reset_bus(bus);
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, 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);
}