blob: ba89b0c047a776006c170ee04cfa74c0ae39cf68 [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 <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
#include <setjmp.h>
#include <limits.h>
#include <util/log.h>
#include <util/sysfs.h>
#include <ccan/array_size/array_size.h>
#include <ndctl/libndctl.h>
#include <daxctl/libdaxctl.h>
#ifdef HAVE_NDCTL_H
#include <linux/ndctl.h>
#else
#include <ndctl.h>
#endif
#define fail() fprintf(stderr, "%s: failed at: %d\n", __func__, __LINE__)
struct check_cmd {
struct ndctl_cmd *cmd;
struct ndctl_test *test;
};
static sigjmp_buf sj_env;
static int sig_count;
static const char *NFIT_PROVIDER0 = "nfit_test.0";
static struct check_cmd *check_cmds;
static void sigbus_hdl(int sig, siginfo_t *siginfo, void *ptr)
{
fprintf(stderr, "** Received a SIGBUS **\n");
sig_count++;
siglongjmp(sj_env, 1);
}
static int check_ars_cap(struct ndctl_bus *bus, uint64_t start,
size_t size, struct check_cmd *check)
{
struct ndctl_cmd *cmd;
int rc;
if (check->cmd != NULL) {
fprintf(stderr, "%s: expected a NULL command, by default\n",
__func__);
return -EINVAL;
}
cmd = ndctl_bus_cmd_new_ars_cap(bus, start, size);
if (!cmd) {
fprintf(stderr, "%s: bus: %s failed to create cmd\n",
__func__, ndctl_bus_get_provider(bus));
return -ENOTTY;
}
rc = ndctl_cmd_submit(cmd);
if (rc) {
fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
__func__, ndctl_bus_get_provider(bus), rc);
ndctl_cmd_unref(cmd);
return rc;
}
if (ndctl_cmd_ars_cap_get_size(cmd) < sizeof(struct nd_cmd_ars_status)){
fprintf(stderr, "%s: bus: %s expected size >= %zd got: %d\n",
__func__, ndctl_bus_get_provider(bus),
sizeof(struct nd_cmd_ars_status),
ndctl_cmd_ars_cap_get_size(cmd));
ndctl_cmd_unref(cmd);
return -ENXIO;
}
check->cmd = cmd;
return 0;
}
static int check_ars_start(struct ndctl_bus *bus, struct check_cmd *check)
{
struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
struct ndctl_cmd *cmd;
int rc;
if (check->cmd != NULL) {
fprintf(stderr, "%s: expected a NULL command, by default\n",
__func__);
return -ENXIO;
}
cmd = ndctl_bus_cmd_new_ars_start(cmd_ars_cap, ND_ARS_PERSISTENT);
if (!cmd) {
fprintf(stderr, "%s: bus: %s failed to create cmd\n",
__func__, ndctl_bus_get_provider(bus));
return -ENOTTY;
}
rc = ndctl_cmd_submit(cmd);
if (rc) {
fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
__func__, ndctl_bus_get_provider(bus), rc);
ndctl_cmd_unref(cmd);
return rc;
}
check->cmd = cmd;
return 0;
}
static int check_ars_status(struct ndctl_bus *bus, struct check_cmd *check)
{
struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
struct ndctl_cmd *cmd;
unsigned long tmo = 5;
unsigned int i;
int rc;
if (check->cmd != NULL) {
fprintf(stderr, "%s: expected a NULL command, by default\n",
__func__);
return -ENXIO;
}
retry:
cmd = ndctl_bus_cmd_new_ars_status(cmd_ars_cap);
if (!cmd) {
fprintf(stderr, "%s: bus: %s failed to create cmd\n",
__func__, ndctl_bus_get_provider(bus));
return -ENOTTY;
}
rc = ndctl_cmd_submit(cmd);
if (rc) {
fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
__func__, ndctl_bus_get_provider(bus), rc);
ndctl_cmd_unref(cmd);
return rc;
}
if (!tmo) {
fprintf(stderr, "%s: bus: %s ars timeout\n", __func__,
ndctl_bus_get_provider(bus));
return -EIO;
}
if (ndctl_cmd_ars_in_progress(cmd)) {
tmo--;
sleep(1);
goto retry;
}
for (i = 0; i < ndctl_cmd_ars_num_records(cmd); i++) {
fprintf(stderr, "%s: record[%d].addr: 0x%llx\n", __func__, i,
ndctl_cmd_ars_get_record_addr(cmd, i));
fprintf(stderr, "%s: record[%d].length: 0x%llx\n", __func__, i,
ndctl_cmd_ars_get_record_len(cmd, i));
}
check->cmd = cmd;
return 0;
}
static int check_clear_error(struct ndctl_bus *bus, struct check_cmd *check)
{
struct ndctl_cmd *ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
struct ndctl_cmd *clear_err;
unsigned long long cleared;
struct ndctl_range range;
int rc;
if (check->cmd != NULL) {
fprintf(stderr, "%s: expected a NULL command, by default\n",
__func__);
return -ENXIO;
}
if (ndctl_cmd_ars_cap_get_range(ars_cap, &range)) {
fprintf(stderr, "failed to get ars_cap range\n");
return -ENXIO;
}
fprintf(stderr, "%s: clearing at %#llx for %llu bytes\n",
__func__, range.address, range.length);
clear_err = ndctl_bus_cmd_new_clear_error(range.address,
range.length, ars_cap);
if (!clear_err) {
fprintf(stderr, "%s: bus: %s failed to create cmd\n",
__func__, ndctl_bus_get_provider(bus));
return -ENOTTY;
}
rc = ndctl_cmd_submit(clear_err);
if (rc) {
fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
__func__, ndctl_bus_get_provider(bus), rc);
ndctl_cmd_unref(clear_err);
return rc;
}
cleared = ndctl_cmd_clear_error_get_cleared(clear_err);
if (cleared != range.length) {
fprintf(stderr, "%s: bus: %s expected to clear: %lld actual: %lld\
n",
__func__, ndctl_bus_get_provider(bus),
range.length, cleared);
return -ENXIO;
}
check->cmd = clear_err;
return 0;
}
static struct ndctl_dax * get_dax_region(struct ndctl_region *region)
{
struct ndctl_dax *dax;
ndctl_dax_foreach(region, dax)
if (ndctl_dax_is_enabled(dax) &&
ndctl_dax_is_configured(dax))
return dax;
return NULL;
}
static int test_daxdev_clear_error(const char *bus_name,
const char *region_name)
{
int rc = 0, i;
struct ndctl_ctx *ctx;
struct ndctl_bus *bus;
struct ndctl_region *region;
struct ndctl_dax *dax = NULL;
uint64_t base, start, offset, blocks, size;
struct check_cmd __bus_cmds[] = {
[ND_CMD_ARS_CAP] = {},
[ND_CMD_ARS_START] = {},
[ND_CMD_ARS_STATUS] = {},
[ND_CMD_CLEAR_ERROR] = {},
};
char path[256];
char buf[SYSFS_ATTR_SIZE];
struct log_ctx log_ctx;
log_init(&log_ctx, "test/init", "NDCTL_DAXDEV_TEST");
rc = ndctl_new(&ctx);
if (rc)
return rc;
bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0);
if (!bus) {
rc = -ENODEV;
goto cleanup;
}
ndctl_region_foreach(bus, region) {
if (strncmp(region_name,
ndctl_region_get_devname(region), 256) == 0) {
/* find the dax region */
dax = get_dax_region(region);
break;
}
}
if (!dax) {
rc = -ENODEV;
goto cleanup;
}
/* get badblocks */
if (snprintf(path, 256,
"/sys/devices/platform/%s/%s/%s/badblocks",
NFIT_PROVIDER0,
bus_name,
ndctl_region_get_devname(region)) >= 256) {
fprintf(stderr, "%s: buffer too small!\n",
ndctl_region_get_devname(region));
rc = -ENXIO;
goto cleanup;
}
if (__sysfs_read_attr(&log_ctx, path, buf) < 0) {
rc = -ENXIO;
goto cleanup;
}
/* retrieve badblocks from buf */
rc = sscanf(buf, "%lu %lu", &offset, &blocks);
if (rc == EOF) {
rc = -errno;
goto cleanup;
}
/* get resource base */
base = ndctl_region_get_resource(region);
if (base == ULLONG_MAX) {
rc = -ERANGE;
goto cleanup;
}
check_cmds = __bus_cmds;
start = base + offset * 512;
size = 512 * blocks;
rc = check_ars_cap(bus, start, size, &check_cmds[ND_CMD_ARS_CAP]);
if (rc < 0)
goto cleanup;
rc = check_ars_start(bus, &check_cmds[ND_CMD_ARS_START]);
if (rc < 0)
goto cleanup;
rc = check_ars_status(bus, &check_cmds[ND_CMD_ARS_STATUS]);
if (rc < 0)
goto cleanup;
rc = check_clear_error(bus, &check_cmds[ND_CMD_CLEAR_ERROR]);
if (rc < 0)
goto cleanup;
for (i = 1; i < (int)ARRAY_SIZE(__bus_cmds); i++) {
if (__bus_cmds[i].cmd) {
ndctl_cmd_unref(__bus_cmds[i].cmd);
__bus_cmds[i].cmd = NULL;
}
}
cleanup:
ndctl_unref(ctx);
return rc;
}
int main(int argc, char *argv[])
{
int rc;
struct sigaction act;
if (argc < 1 || argc > 4)
return -EINVAL;
memset(&act, 0, sizeof(act));
act.sa_sigaction = sigbus_hdl;
act.sa_flags = SA_SIGINFO;
if (sigaction(SIGBUS, &act, 0)) {
fail();
return 1;
}
rc = test_daxdev_clear_error(argv[1], argv[2]);
return rc;
}