| /* |
| * 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 <linux/version.h> |
| #include <sys/utsname.h> |
| #include <libkmod.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <test.h> |
| |
| #include <util/log.h> |
| #include <util/sysfs.h> |
| #include <ndctl/libndctl.h> |
| #include <ccan/array_size/array_size.h> |
| |
| #define KVER_STRLEN 20 |
| |
| struct ndctl_test { |
| unsigned int kver; |
| int attempt; |
| int skip; |
| }; |
| |
| static unsigned int get_system_kver(void) |
| { |
| const char *kver = getenv("KVER"); |
| struct utsname utsname; |
| int a, b, c; |
| |
| if (!kver) { |
| uname(&utsname); |
| kver = utsname.release; |
| } |
| |
| if (sscanf(kver, "%d.%d.%d", &a, &b, &c) != 3) |
| return LINUX_VERSION_CODE; |
| |
| return KERNEL_VERSION(a,b,c); |
| } |
| |
| struct ndctl_test *ndctl_test_new(unsigned int kver) |
| { |
| struct ndctl_test *test = calloc(1, sizeof(*test)); |
| |
| if (!test) |
| return NULL; |
| |
| if (!kver) |
| test->kver = get_system_kver(); |
| else |
| test->kver = kver; |
| |
| return test; |
| } |
| |
| int ndctl_test_result(struct ndctl_test *test, int rc) |
| { |
| if (ndctl_test_get_skipped(test)) |
| fprintf(stderr, "attempted: %d skipped: %d\n", |
| ndctl_test_get_attempted(test), |
| ndctl_test_get_skipped(test)); |
| if (rc && rc != 77) |
| return rc; |
| if (ndctl_test_get_skipped(test) >= ndctl_test_get_attempted(test)) |
| return 77; |
| /* return success if no failures and at least one test not skipped */ |
| return 0; |
| } |
| |
| static char *kver_str(char *buf, unsigned int kver) |
| { |
| snprintf(buf, KVER_STRLEN, "%d.%d.%d", (kver >> 16) & 0xffff, |
| (kver >> 8) & 0xff, kver & 0xff); |
| return buf; |
| } |
| |
| int __ndctl_test_attempt(struct ndctl_test *test, unsigned int kver, |
| const char *caller, int line) |
| { |
| char requires[KVER_STRLEN], current[KVER_STRLEN]; |
| |
| test->attempt++; |
| if (kver <= test->kver) |
| return 1; |
| fprintf(stderr, "%s: skip %s:%d requires: %s current: %s\n", |
| __func__, caller, line, kver_str(requires, kver), |
| kver_str(current, test->kver)); |
| test->skip++; |
| return 0; |
| } |
| |
| void __ndctl_test_skip(struct ndctl_test *test, const char *caller, int line) |
| { |
| test->skip++; |
| test->attempt = test->skip; |
| fprintf(stderr, "%s: explicit skip %s:%d\n", __func__, caller, line); |
| } |
| |
| int ndctl_test_get_attempted(struct ndctl_test *test) |
| { |
| return test->attempt; |
| } |
| |
| int ndctl_test_get_skipped(struct ndctl_test *test) |
| { |
| return test->skip; |
| } |
| |
| int nfit_test_init(struct kmod_ctx **ctx, struct kmod_module **mod, |
| struct ndctl_ctx *nd_ctx, int log_level, |
| struct ndctl_test *test) |
| { |
| int rc; |
| unsigned int i; |
| const char *name; |
| struct ndctl_bus *bus; |
| struct log_ctx log_ctx; |
| const char *list[] = { |
| "nfit", |
| "device_dax", |
| "dax_pmem", |
| "libnvdimm", |
| "nd_blk", |
| "nd_btt", |
| "nd_e820", |
| "nd_pmem", |
| }; |
| |
| log_init(&log_ctx, "test/init", "NDCTL_TEST"); |
| log_ctx.log_priority = log_level; |
| |
| *ctx = kmod_new(NULL, NULL); |
| if (!*ctx) |
| return -ENXIO; |
| kmod_set_log_priority(*ctx, log_level); |
| |
| /* |
| * Check that all nfit, libnvdimm, and device-dax modules are |
| * the mocked versions. If they are loaded, check that they have |
| * the "out-of-tree" kernel taint, otherwise check that they |
| * come from the "/lib/modules/<KVER>/extra" directory. |
| */ |
| for (i = 0; i < ARRAY_SIZE(list); i++) { |
| char attr[SYSFS_ATTR_SIZE]; |
| const char *path; |
| char buf[100]; |
| int state; |
| |
| name = list[i]; |
| |
| /* |
| * Don't check for device-dax modules on kernels older |
| * than 4.7. |
| */ |
| if (strstr(name, "dax") |
| && !ndctl_test_attempt(test, |
| KERNEL_VERSION(4, 7, 0))) |
| continue; |
| retry: |
| rc = kmod_module_new_from_name(*ctx, name, mod); |
| if (rc) { |
| log_err(&log_ctx, "%s.ko: missing\n", name); |
| break; |
| } |
| |
| path = kmod_module_get_path(*mod); |
| if (!path) { |
| log_err(&log_ctx, "%s.ko: failed to get path\n", name); |
| break; |
| } |
| |
| if (!strstr(path, "/extra/")) { |
| log_err(&log_ctx, "%s.ko: appears to be production version: %s\n", |
| name, path); |
| break; |
| } |
| |
| state = kmod_module_get_initstate(*mod); |
| if (state == KMOD_MODULE_LIVE) { |
| sprintf(buf, "/sys/module/%s/taint", name); |
| rc = __sysfs_read_attr(&log_ctx, buf, attr); |
| if (rc < 0) { |
| log_err(&log_ctx, "%s.ko: failed to read %s\n", |
| name, buf); |
| break; |
| } |
| |
| if (!strchr(attr, 'O')) { |
| log_err(&log_ctx, "%s.ko: expected taint: O got: %s\n", |
| name, attr); |
| break; |
| } |
| } else if (state == KMOD_MODULE_BUILTIN) { |
| log_err(&log_ctx, "%s: must be built as a module\n", name); |
| break; |
| } |
| } |
| |
| if (i < ARRAY_SIZE(list)) { |
| /* device-dax changed module names in 4.12 */ |
| if (strcmp(name, "device_dax") == 0) { |
| name = "dax"; |
| goto retry; |
| } |
| kmod_unref(*ctx); |
| return -ENXIO; |
| } |
| |
| rc = kmod_module_new_from_name(*ctx, "nfit_test", mod); |
| if (rc < 0) { |
| kmod_unref(*ctx); |
| return rc; |
| } |
| |
| if (nd_ctx) { |
| /* caller wants a full nfit_test reset */ |
| ndctl_bus_foreach(nd_ctx, bus) { |
| struct ndctl_region *region; |
| |
| if (strncmp(ndctl_bus_get_provider(bus), |
| "nfit_test", 9) != 0) |
| continue; |
| ndctl_region_foreach(bus, region) |
| ndctl_region_disable_invalidate(region); |
| } |
| |
| rc = kmod_module_remove_module(*mod, 0); |
| if (rc < 0 && rc != -ENOENT) { |
| kmod_unref(*ctx); |
| return rc; |
| } |
| ndctl_invalidate(nd_ctx); |
| } |
| |
| rc = kmod_module_probe_insert_module(*mod, KMOD_PROBE_APPLY_BLACKLIST, |
| NULL, NULL, NULL, NULL); |
| if (rc) |
| kmod_unref(*ctx); |
| return rc; |
| } |