From: Alexey K. <ale...@or...> - 2013-06-14 11:18:31
|
This test checks that from kernel 3.7 firmware can be loaded directly (by-pass udev) or as usual. The test consists of the two parts: userspace and kernelspace. Signed-off-by: Alexey Kodanev <ale...@or...> --- runtest/syscalls | 2 + testcases/kernel/Makefile | 1 + testcases/kernel/firmware/Makefile | 45 +++ .../kernel/firmware/fw_load_kernel/.gitignore | 1 + testcases/kernel/firmware/fw_load_kernel/Makefile | 37 +++ testcases/kernel/firmware/fw_load_kernel/README | 14 + testcases/kernel/firmware/fw_load_kernel/fw_load.c | 162 ++++++++++ testcases/kernel/firmware/fw_load_user/.gitignore | 1 + testcases/kernel/firmware/fw_load_user/Makefile | 20 ++ testcases/kernel/firmware/fw_load_user/README | 11 + testcases/kernel/firmware/fw_load_user/fw_load.c | 325 ++++++++++++++++++++ 11 files changed, 619 insertions(+), 0 deletions(-) create mode 100644 testcases/kernel/firmware/Makefile create mode 100644 testcases/kernel/firmware/fw_load_kernel/.gitignore create mode 100644 testcases/kernel/firmware/fw_load_kernel/Makefile create mode 100644 testcases/kernel/firmware/fw_load_kernel/README create mode 100644 testcases/kernel/firmware/fw_load_kernel/fw_load.c create mode 100644 testcases/kernel/firmware/fw_load_user/.gitignore create mode 100644 testcases/kernel/firmware/fw_load_user/Makefile create mode 100644 testcases/kernel/firmware/fw_load_user/README create mode 100644 testcases/kernel/firmware/fw_load_user/fw_load.c diff --git a/runtest/syscalls b/runtest/syscalls index e6ce29c..c11379c 100644 --- a/runtest/syscalls +++ b/runtest/syscalls @@ -314,6 +314,8 @@ ftruncate04_64 ftruncate04.sh 64 #futimesat test cases futimesat01 futimesat01 +fw_load fw_load + getcontext01 getcontext01 getcpu01 getcpu01 diff --git a/testcases/kernel/Makefile b/testcases/kernel/Makefile index 4b4800d..256a574 100644 --- a/testcases/kernel/Makefile +++ b/testcases/kernel/Makefile @@ -38,6 +38,7 @@ ifneq ($(UCLINUX),1) SUBDIRS += connectors \ containers \ controllers \ + firmware \ fs \ hotplug \ io \ diff --git a/testcases/kernel/firmware/Makefile b/testcases/kernel/firmware/Makefile new file mode 100644 index 0000000..f6db454 --- /dev/null +++ b/testcases/kernel/firmware/Makefile @@ -0,0 +1,45 @@ +# Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it would 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +top_srcdir ?= ../../.. + +include $(top_srcdir)/include/mk/env_pre.mk + +SUBDIRS = +PROCEED = 0 +REQ_VERSION_MAJOR = 3 +REQ_VERSION_MINOR = 7 + +ifeq ($(MAKECMDGOALS),clean) +proceed = 1 +endif + +ifeq ($(WITH_MODULES),yes) +proceed = $(shell expr $(LINUX_VERSION_MAJOR) '>' $(REQ_VERSION_MAJOR)) +ifeq ($(proceed),0) +proceed = $(shell expr $(LINUX_VERSION_MAJOR) '=' $(REQ_VERSION_MAJOR)) +ifeq ($(proceed),1) +proceed = $(shell expr $(LINUX_PATCHLEVEL) '>=' $(REQ_VERSION_MINOR)) +endif +endif +endif + +ifeq ($(proceed),1) +SUBDIRS += fw_load_kernel +SUBDIRS += fw_load_user +endif + +include $(top_srcdir)/include/mk/generic_trunk_target.mk diff --git a/testcases/kernel/firmware/fw_load_kernel/.gitignore b/testcases/kernel/firmware/fw_load_kernel/.gitignore new file mode 100644 index 0000000..fdb463d --- /dev/null +++ b/testcases/kernel/firmware/fw_load_kernel/.gitignore @@ -0,0 +1 @@ +/fw_load.ko diff --git a/testcases/kernel/firmware/fw_load_kernel/Makefile b/testcases/kernel/firmware/fw_load_kernel/Makefile new file mode 100644 index 0000000..e8d12be --- /dev/null +++ b/testcases/kernel/firmware/fw_load_kernel/Makefile @@ -0,0 +1,37 @@ +# Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it would 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +MODULE := fw_load + +ifneq ($(KERNELRELEASE),) + +obj-m := $(MODULE).o + +else + +top_srcdir ?= ../../../.. +include $(top_srcdir)/include/mk/env_pre.mk + +MAKE_TARGETS := $(MODULE).ko +$(MODULE).ko: + -$(MAKE) -C $(LINUX_DIR) M=$(abs_srcdir) + -mv $(MODULE).ko $(MODULE).ko~ + -$(MAKE) -C $(LINUX_DIR) M=$(abs_srcdir) clean + -mv $(MODULE).ko~ $(MODULE).ko + +include $(top_srcdir)/include/mk/generic_leaf_target.mk + +endif diff --git a/testcases/kernel/firmware/fw_load_kernel/README b/testcases/kernel/firmware/fw_load_kernel/README new file mode 100644 index 0000000..78343d3 --- /dev/null +++ b/testcases/kernel/firmware/fw_load_kernel/README @@ -0,0 +1,14 @@ +The aim of the test is to check that after kernel 3.7 firmware can be loaded +directly (by-pass udev) or as usual. The test consists of the two parts: + - userspace part + - kernelspace part + +This is a kernel module, which is a part of the device firmware loading test. +It allows to call request_firmware kernel function with specified parameters. +The parameters passed with the insmod command. They include template firmware +file name, number of firmware files to request, and expected data size in the +firmware files. In the end, the device will create sysfs file, that can be +read to get request firmware results. Also, some information regarding module +loading, can be obtained by looking at kernel log file. + +It is automatically used by user space part of the test. diff --git a/testcases/kernel/firmware/fw_load_kernel/fw_load.c b/testcases/kernel/firmware/fw_load_kernel/fw_load.c new file mode 100644 index 0000000..47bd78f --- /dev/null +++ b/testcases/kernel/firmware/fw_load_kernel/fw_load.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Alexey Kodanev <ale...@or...> + * + * This module is trying to load external test firmware files (load_tst_#.fw). + * In the end, it writes results to sys/devices/fw_load_tst/result file. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/string.h> +#include <linux/firmware.h> + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alexey Kodanev <ale...@or...>"); +MODULE_DESCRIPTION("This module is checking firmware loading process"); + +#define TCID "fw_load" + +/* number of firmware files to check in the test */ +static int fw_num = 8; +static char *fw_name = "load_tst.fw"; +static int fw_size = 0x1000; +static int max_name = 64; +static int fw; + +module_param(fw_num, int, 0444); +MODULE_PARM_DESC(fw_num, "Number of firmwares to check"); + +module_param(fw_name, charp, 0444); +MODULE_PARM_DESC(fw_name, "Template firmware file: n#_name"); + +module_param(fw_size, int, 0444); +MODULE_PARM_DESC(fw_size, "Firmware file size"); + +/* + * bit mask for each test-case, + * if test is passed, bit will be set to 1 + */ +static int test_result; + +/* read and print firmware data */ +static int fw_read(const u8 *data, size_t size); + +static void device_release(struct device *dev); + +static struct device tdev = { + .init_name = TCID, + .release = device_release, +}; + +static int try_request_fw(const char *name); + +/* print test result to sysfs file */ +static ssize_t sys_result(struct device *dev, + struct device_attribute *attr, char *buf); +static DEVICE_ATTR(result, S_IRUSR, sys_result, NULL); + + +static int test_init(void) +{ + int err; + + err = device_register(&tdev); + + if (err) { + pr_err(TCID ": Unable to register device\n"); + return err; + } + pr_info(TCID ": device registered\n"); + + for (fw = 0; fw < fw_num; ++fw) { + char name[max_name]; + snprintf(name, max_name, "n%d_%s", fw, fw_name); + err = try_request_fw(name); + test_result |= (err == 0) << fw; + } + + err = device_create_file(&tdev, &dev_attr_result); + if (err != 0) + pr_info(TCID ": Can't create sysfs file\n"); + + return err; +} + +static void test_exit(void) +{ + device_remove_file(&tdev, &dev_attr_result); + device_unregister(&tdev); + pr_info(TCID ": module exited\n"); +} + +static void device_release(struct device *dev) +{ + pr_info(TCID ": device released\n"); +} + +static ssize_t sys_result(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", test_result); +} + +static int fw_read(const u8 *data, size_t size) +{ + size_t i; + + pr_info(TCID ": Firmware has size '%d'\n", (unsigned int) size); + + if (size != fw_size) { + pr_err(TCID ": Expected firmware size '%d'\n", + (unsigned int) fw_size); + return -1; + } + + for (i = 0; i < size; ++i) { + if (data[i] != (u8)fw) { + pr_err(TCID ": Unexpected firmware data\n"); + return -1; + } + } + return 0; +} + +static int try_request_fw(const char *name) +{ + int err; + const struct firmware *fw_entry = NULL; + + err = request_firmware(&fw_entry, name, &tdev); + + if (!err) { + pr_info(TCID ": firmware '%s' requested\n", name); + err = fw_read(fw_entry->data, fw_entry->size); + } else + pr_err(TCID ": Can't request firmware '%s'\n", name); + + release_firmware(fw_entry); + + return err; +} + +module_init(test_init); +module_exit(test_exit); diff --git a/testcases/kernel/firmware/fw_load_user/.gitignore b/testcases/kernel/firmware/fw_load_user/.gitignore new file mode 100644 index 0000000..1d08149 --- /dev/null +++ b/testcases/kernel/firmware/fw_load_user/.gitignore @@ -0,0 +1 @@ +/fw_load diff --git a/testcases/kernel/firmware/fw_load_user/Makefile b/testcases/kernel/firmware/fw_load_user/Makefile new file mode 100644 index 0000000..effd5da --- /dev/null +++ b/testcases/kernel/firmware/fw_load_user/Makefile @@ -0,0 +1,20 @@ +# Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it would 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +top_srcdir ?= ../../../.. + +include $(top_srcdir)/include/mk/testcases.mk +include $(top_srcdir)/include/mk/generic_leaf_target.mk diff --git a/testcases/kernel/firmware/fw_load_user/README b/testcases/kernel/firmware/fw_load_user/README new file mode 100644 index 0000000..41c2bf9 --- /dev/null +++ b/testcases/kernel/firmware/fw_load_user/README @@ -0,0 +1,11 @@ +The aim of the test is to check that after kernel 3.7 firmware can be loaded +directly (by-pass udev) or as usual. The test consists of the two parts: + - userspace part + - kernelspace part + +This is the userspace part, its tasks are: + - create firmware files in the searched paths by udev and kernels + - replace udev's searched paths to tmp directory (firmware.sh) + - load the module and wait for results + - read device's result file and print test results + - clean up tmp directory and unload the module. diff --git a/testcases/kernel/firmware/fw_load_user/fw_load.c b/testcases/kernel/firmware/fw_load_user/fw_load.c new file mode 100644 index 0000000..900144b --- /dev/null +++ b/testcases/kernel/firmware/fw_load_user/fw_load.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Alexey Kodanev <ale...@or...> + * + * Test checks following preconditions: + * Linux kernels from version 3.7 are loading firmware files directly + * using hard coded paths. In case the firmware not found, loading will + * be proceeded as usual (udev). + */ + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/xattr.h> +#include <sys/utsname.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "test.h" +#include "usctest.h" +#include "safe_macros.h" + +/* number of test firmware files */ +#define FW_FILES 9 + +char *TCID = "fw_load"; +int TST_TOTAL = FW_FILES; + +#define MAX_CMD_LEN 256 +/* hard coded paths in the kernel */ +#define FW_PATHS_NUM 4 + +static int fw_size = 0x1000; + +static const char fw_name[] = "load_tst.fw"; +static const char module_name[] = "fw_load.ko"; +static const char udev_script[] = "/lib/udev/firmware.sh"; + +enum load_mode { + DIRECT_LOAD = 0, + UDEV_LOAD +}; + +struct fw_file_info { + char file[PATH_MAX]; + char dir[PATH_MAX]; + int mode; + int fake; + int remove_dir; + int remove_file; +}; + +static struct fw_file_info fw[FW_FILES]; +static int fw_num; + +/* related firmware paths which are searched by kernel and udev */ +static char fw_paths[FW_PATHS_NUM][PATH_MAX]; +static char tmp_dir[PATH_MAX]; +static char mod_path[PATH_MAX]; + +/* test options */ +static char *narg; +static int nflag; +static int skip_cleanup; +static int verbose; +static const option_t options[] = { + {"n:", &nflag, &narg}, + {"s", &skip_cleanup, NULL}, + {"v", &verbose, NULL}, + {NULL, NULL, NULL} +}; + +static void help(void); +static void setup(int argc, char *argv[]); +static void test_run(void); +static void cleanup(void); + +/* + * create firmware files in the fw_paths + * @path: start directory + * @mode: can be DIRECT_LOAD or UDEV_LOAD + */ +static void create_firmware(const char *path, int mode); +/* make a string from another by adding escape '\' before each '/' */ +static void reg_path(char *dst, const char *src); +/* replace a string with another string in a file */ +static void str_replace(const char *file, const char *old, const char *new); + +int main(int argc, char *argv[]) +{ + setup(argc, argv); + + test_run(); + + cleanup(); + + tst_exit(); +} + +static void help(void) +{ + printf(" -n x Write x bytes to firmware file, default is %d\n", + fw_size); + printf(" -s Skip cleanup\n"); + printf(" -v Verbose\n"); +} + +/* cleanup flags */ +static int fw_script_changed; +static int fw_rules_copied; +static int module_registered; + +void setup(int argc, char *argv[]) +{ + char *msg; + msg = parse_opts(argc, argv, options, help); + if (msg != NULL) + tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg); + + if (nflag) { + if (sscanf(narg, "%i", &fw_size) != 1) + tst_brkm(TBROK, NULL, "-n option arg is not a number"); + if (fw_size < 0) + tst_brkm(TBROK, NULL, "-n option arg is less than 0"); + } + + tst_require_root(NULL); + + if (tst_kvercmp(3, 7, 0) < 0) { + tst_brkm(TCONF, NULL, + "Test must be run with kernel 3.7 or newer"); + } + + if (access(module_name, F_OK) == -1) { + tst_brkm(TCONF, NULL, + "Test requires kernel module '%s'", module_name); + } + + tst_sig(FORK, DEF_HANDLER, cleanup); + + /* add firmware rule to udev */ + if (access("/etc/udev/rules.d/50-firmware.rules", 0) == -1) { + SAFE_CP(cleanup, "/lib/udev/rules.d/50-firmware.rules", + "/etc/udev/rules.d/"); + fw_rules_copied = 1; + + if (system("udevadm control --reload-rules") != 0) + tst_resm(TWARN, "Can't update udev rules"); + } + + /* get current Linux version and make firmware paths */ + struct utsname uts_name; + uname(&uts_name); + strcpy(fw_paths[0], "firmware"); + strcpy(fw_paths[1], "firmware/updates"); + snprintf(fw_paths[2], PATH_MAX, "firmware/%s", uts_name.release); + snprintf(fw_paths[3], PATH_MAX, "firmware/updates/%s", + uts_name.release); + + /* copy firmware to direct firmware search paths */ + create_firmware("/lib", DIRECT_LOAD); + + /* save module path to mod_path */ + SAFE_GETCWD(cleanup, mod_path, PATH_MAX); + int offset = strlen(mod_path); + snprintf(mod_path + offset, PATH_MAX - offset, "/%s", module_name); + + tst_tmpdir(); + + char *cwd = get_tst_tmpdir(); + snprintf(tmp_dir, PATH_MAX, cwd); + free(cwd); + + /* replace udev's firmware search path in firmware.sh */ + str_replace(udev_script, "/lib", tmp_dir); + fw_script_changed = 1; + + /* create firmware in the udev firmware search paths */ + create_firmware(tmp_dir, UDEV_LOAD); + + /* make non-existent firmware file */ + snprintf(fw[fw_num].file, PATH_MAX, "n%d_%s", fw_num, fw_name); + fw[fw_num].fake = 1; + fw[fw_num].mode = UDEV_LOAD; + ++fw_num; +} + +static void test_run() +{ + /* load test module */ + char cmd[MAX_CMD_LEN]; + snprintf(cmd, MAX_CMD_LEN, + "insmod %s fw_name=%s fw_num=%d fw_size=%d", + mod_path, fw_name, fw_num, fw_size); + if (system(cmd) != 0) + tst_brkm(TBROK, cleanup, "Failed to insert %s", module_name); + module_registered = 1; + + /* get module results */ + char dev_path[PATH_MAX]; + snprintf(dev_path, PATH_MAX, "/sys/devices/%s/result", TCID); + + int result = 0; + /* read result bit mask */ + SAFE_FILE_SCANF(cleanup, dev_path, "%d", &result); + + int i, fail; + for (i = 0; i < fw_num; ++i) { + fail = (result & (1 << i)) == 0 && !fw[i].fake; + + tst_resm((fail) ? TFAIL : TPASS, + "Expect: %s load firmware '...%s', %s used", + (fw[i].fake) ? "can't" : "can", + fw[i].file + strlen(fw[i].dir), + (fw[i].mode == UDEV_LOAD) ? "udev" : "kernel"); + } +} + +static void cleanup(void) +{ + if (skip_cleanup) + return; + + if (fw_rules_copied) + remove("/etc/udev/rules.d/50-firmware.rules"); + + if (fw_script_changed) + str_replace(udev_script, tmp_dir, "/lib"); + + int i; + for (i = fw_num - 1; i >= 0; --i) { + if (fw[i].remove_file && remove(fw[i].file) == -1) + tst_resm(TWARN, "Can't remove: %s", fw[i].file); + + if (fw[i].remove_dir && remove(fw[i].dir) == -1) + tst_resm(TWARN, "Can't remove %s", fw[i].dir); + } + + if (module_registered) { + char cmd[MAX_CMD_LEN]; + snprintf(cmd, MAX_CMD_LEN, "rmmod %s", mod_path); + if (system(cmd) != 0) + tst_brkm(TBROK, NULL, "Can't remove %s", module_name); + } + + tst_rmdir(); + TEST_CLEANUP; +} + +static void create_firmware(const char *path, int mode) +{ + int i; + for (i = 0; i < ARRAY_SIZE(fw_paths); ++i) { + struct fw_file_info *f = &fw[fw_num]; + f->mode = mode; + snprintf(f->dir, PATH_MAX, "%s/%s", path, fw_paths[i]); + if (access(f->dir, X_OK) == -1) { + /* create dir */ + SAFE_MKDIR(cleanup, f->dir, 0755); + f->remove_dir = mode == DIRECT_LOAD; + } + + /* create test firmware file */ + snprintf(f->file, PATH_MAX, "%s/n%d_%s", + f->dir, fw_num, fw_name); + + FILE *fd = fopen(f->file, "w"); + if (fd == NULL) + tst_brkm(TBROK, cleanup, "Failed to create firmware"); + int k; + for (k = 0; k < fw_size; ++k) + fputc(fw_num, fd); + fclose(fd); + + f->remove_file = mode == DIRECT_LOAD; + ++fw_num; + } +} + +static void reg_path(char *dst, const char *src) +{ + const char *p = src; + while ((p = strchr(src, '/')) != NULL) { + int len = p - src; + strncpy(dst, src, len); + dst += len; + strcpy(dst, "\\/"); + dst += 2; + src = p + 1; + } + strcpy(dst, src); +} + +static void str_replace(const char *file, const char *old, const char *new) +{ + char cmd[MAX_CMD_LEN], reg_old[PATH_MAX], reg_new[PATH_MAX]; + + reg_path(reg_old, old); + reg_path(reg_new, new); + + snprintf(cmd, MAX_CMD_LEN, "sed -i 's/%s/%s/g' %s", + reg_old, reg_new, file); + if (system(cmd) != 0) + tst_brkm(TBROK, cleanup, "Can't replace strings"); +} -- 1.7.1 |