diff --git a/drivers/Kconfig b/drivers/Kconfig index 9f339bed684788494ff084545a2bd0e27ce5f432..f765c3a688b6b0a6396f7616eb14c89dfcc51c8f 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -242,4 +242,6 @@ source "drivers/roh/Kconfig" source "drivers/ub/Kconfig" +source "drivers/cpuinspect/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index c9b8abae034960ffdf129dc2ffb703b1ba3439a9..fbee4dff52e098ac07506a326b5fcf055e1db5fb 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_EISA) += eisa/ obj-$(CONFIG_PM_OPP) += opp/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ +obj-$(CONFIG_CPU_INSPECT) += cpuinspect/ obj-y += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-$(CONFIG_NEW_LEDS) += leds/ diff --git a/drivers/cpuinspect/Kconfig b/drivers/cpuinspect/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..c386913048712bccd2b82eb1baea8d52a9c29f04 --- /dev/null +++ b/drivers/cpuinspect/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "CPU Inspect" + +config CPU_INSPECT + tristate "CPU inspect support" + depends on SYSFS + default n + help + CPU-inspect is designed to provide a framework for early detection + of SDC by proactively executing CPU inspection test cases. It + includes modular inspector that can be swapped during runtime. + +if CPU_INSPECT + +config CPU_INSPECTOR_ATF + tristate "ATF CPU inspector" + depends on ARM64 + default n + help + This inspector implements the execution of inspection instructions + in BIOS. + +endif +endmenu diff --git a/drivers/cpuinspect/Makefile b/drivers/cpuinspect/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..51e89ce718226482174778be74f80799277322e2 --- /dev/null +++ b/drivers/cpuinspect/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for cpuidle. +# +ccflags-y = -DDEBUG +obj-$(CONFIG_CPU_INSPECT) += cpu_inspect.o +obj-$(CONFIG_CPU_INSPECTOR_ATF) += inspector-atf.o +cpu_inspect-y = cpuinspect.o inspector.o sysfs.o diff --git a/drivers/cpuinspect/cpuinspect.c b/drivers/cpuinspect/cpuinspect.c new file mode 100644 index 0000000000000000000000000000000000000000..db6f4e48cc275a9c53c398af36c78070beda2556 --- /dev/null +++ b/drivers/cpuinspect/cpuinspect.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cpuinspect.c - core cpuinspect infrastructure + * + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * + * Author: Yu Liao + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, 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 General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpuinspect.h" + +/* + * The core struct, store the most relevant data for cpuinspect. + */ +struct cpuinspect ci_core = { + .inspect_times = 1, + .cpu_utility = 100, +}; + +static struct task_struct *cpuinspect_threads[NR_CPUS]; +static atomic_t active_threads_num; +DECLARE_BITMAP(result, NR_CPUS); +DEFINE_MUTEX(cpuinspect_lock); + +/* inspection thread function */ +static int run_inspector(void *data) +{ + unsigned int cpu = (unsigned int)(unsigned long)data; + unsigned int inspect_times = 0, group, sleep_us, ret; + ktime_t start_time, duration; + + while (!kthread_should_stop()) { + if (inspect_times >= ci_core.inspect_times) + break; + + for (group = 0; group < curr_cpu_inspector->group_num; group++) { + start_time = ktime_get(); + ret = curr_cpu_inspector->start_inspect(group); + if (ret) { + set_bit(cpu, result); + cpuinspect_result_notify(); + } + + /* + * Sleep for a while if user set desired cpu utility. + */ + if (ci_core.cpu_utility < 100) { + duration = ktime_get() - start_time; + sleep_us = (duration * 100 / ci_core.cpu_utility - duration) / 1000; + /* + * Since usleep_range is built on top of hrtimers, + * and we don't want to introduce a large number of + * undesired interrupts, choose a range of 1000us + * to balance performance and latency. This can + * cause inspection threads cpu utility is lower + * than required cpu utility. + */ + usleep_range(sleep_us, sleep_us + 1000); + } + + } + inspect_times++; + } + + cpuinspect_threads[cpu] = NULL; + + /* + * When this condition is met, it indicate this is the final cpuinspect + * thread, mark inspect state as 0 and notify user that it has been + * completed. + */ + if (atomic_dec_and_test(&active_threads_num)) { + ci_core.inspect_on = 0; + cpuinspect_result_notify(); + } + + return 0; +} + +int start_inspect_threads(void) +{ + unsigned int cpu = 0; + + bitmap_zero(result, NR_CPUS); + + ci_core.inspect_on = 1; + for_each_cpu(cpu, &ci_core.inspect_cpumask) { + cpuinspect_threads[cpu] = kthread_create_on_node(run_inspector, + (unsigned long *)(unsigned long)cpu, + cpu_to_node(cpu), "cpuinspect/%u", cpu); + if (IS_ERR(cpuinspect_threads[cpu])) { + cpuinspect_threads[cpu] = NULL; + continue; + } + + kthread_bind(cpuinspect_threads[cpu], cpu); + wake_up_process(cpuinspect_threads[cpu]); + atomic_inc(&active_threads_num); + } + + /* + * If creating inspection threads for all CPUs in mask fails (or + * inspect_cpumask is empty), notify user, mark the inspection status + * as 0 and simply exit. + */ + if (unlikely(!atomic_read(&active_threads_num))) { + ci_core.inspect_on = 0; + cpuinspect_result_notify(); + } + + return 0; +} + +int stop_inspect_threads(void) +{ + unsigned int cpu = 0; + + /* All inspection threads has been stopped */ + if (atomic_read(&active_threads_num) == 0) + return 0; + + for_each_cpu(cpu, &ci_core.inspect_cpumask) { + if (cpuinspect_threads[cpu]) + kthread_stop(cpuinspect_threads[cpu]); + } + + return 0; +} + +/** + * cpuinspect_init - core initializer + */ +static int __init cpuinspect_init(void) +{ + cpumask_copy(&ci_core.inspect_cpumask, cpu_all_mask); + + return cpuinspect_add_interface(cpu_subsys.dev_root); +} + +static void __exit cpuinspect_exit(void) +{ + return cpuinspect_remove_interface(cpu_subsys.dev_root); +} + +module_init(cpuinspect_init); +module_exit(cpuinspect_exit); +module_param_string(inspector, param_inspector, CPUINSPECT_NAME_LEN, 0444); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpuinspect/cpuinspect.h b/drivers/cpuinspect/cpuinspect.h new file mode 100644 index 0000000000000000000000000000000000000000..d1c779e03df50f80482a3251105c8595c3880983 --- /dev/null +++ b/drivers/cpuinspect/cpuinspect.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * cpuinspect.h - The internal header file + */ + +#ifndef __DRIVER_CPUINSPECT_H +#define __DRIVER_CPUINSPECT_H + +#define CPUINSPECT_NAME_LEN 16 + +/* sysfs */ +int cpuinspect_add_interface(struct device *dev); +void cpuinspect_remove_interface(struct device *dev); +void cpuinspect_result_notify(void); + +/* inspect control */ +int start_inspect_threads(void); +int stop_inspect_threads(void); +int cpuinspect_is_running(void); + +/* switch inspector */ +int cpuinspect_switch_inspector(struct cpu_inspector *insp); + +/* for internal use only */ +extern DECLARE_BITMAP(result, NR_CPUS); +extern struct cpu_inspector *curr_cpu_inspector; +extern struct mutex cpuinspect_lock; +extern struct cpuinspect ci_core; +extern struct list_head cpu_inspectors; +extern char param_inspector[]; + +/** + * struct cpuinspect - the basic cpuinspect structure + * @cpu_utility: Maximum CPU utilization occupied by the inspection thread. + * @inspect_times: The number of times the inspection code will be executed. + * @inspect_cpumask: cpumask to indicate for which CPUs are involved in inspection. + * @inspect_on: Set if the inspection thread is running. + */ +struct cpuinspect { + unsigned int cpu_utility; + unsigned int inspect_times; + bool inspect_on; + cpumask_t inspect_cpumask; +}; + +#endif /* __DRIVER_CPUINSPECT_H */ diff --git a/drivers/cpuinspect/inspector-atf.c b/drivers/cpuinspect/inspector-atf.c new file mode 100644 index 0000000000000000000000000000000000000000..5b497aea0b70c128adb83733ba22eac5a5017532 --- /dev/null +++ b/drivers/cpuinspect/inspector-atf.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * inspector-atf: cpuinspect inspector for EFI-based systems + * + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * + * Author: Yu Liao + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, 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 General Public License for + * more details. + */ + +#include +#include +#include +#include + +#define PRIVATE_ARM_SMC_ID_STL_GET_MAX_GROUP 0x83000504 +#define PRIVATE_ARM_SMC_ID_STL_ONLINE_TEST 0x83000505 + +static struct cpu_inspector atf_inspector; + +static int atf_get_group_num(void) +{ + struct arm_smccc_res res; + + arm_smccc_smc(PRIVATE_ARM_SMC_ID_STL_GET_MAX_GROUP, 0, 0, 0, 0, + 0, 0, 0, &res); + + return res.a0; +} + +static int atf_run_chip_test(unsigned int group) +{ + struct arm_smccc_res res; + + arm_smccc_smc(PRIVATE_ARM_SMC_ID_STL_ONLINE_TEST, group, 0, 0, 0, + 0, 0, 0, &res); + + return res.a0; +} + +static struct cpu_inspector atf_inspector = { + .name = "atf", + .start_inspect = atf_run_chip_test, +}; + +/** + * init_atf_inspector - initializes the inspector + */ +static __init int init_atf_inspector(void) +{ + atf_inspector.group_num = atf_get_group_num(); + return cpuinspect_register_inspector(&atf_inspector); +} + +static __exit void exit_atf_inspector(void) +{ + cpuinspect_unregister_inspector(&atf_inspector); +} + +MODULE_LICENSE("GPL"); +module_init(init_atf_inspector); +module_exit(exit_atf_inspector); diff --git a/drivers/cpuinspect/inspector.c b/drivers/cpuinspect/inspector.c new file mode 100644 index 0000000000000000000000000000000000000000..6cb3cacbbbdb7208be5da6409244c8ecfe5de00d --- /dev/null +++ b/drivers/cpuinspect/inspector.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * inspector.c - inspector support + * + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * + * Author: Yu Liao + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, 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 General Public License for + * more details. + */ + +#define pr_fmt(fmt) "CPUINSPECT: " fmt + +#include +#include +#include +#include +#include + +#include "cpuinspect.h" + + +char param_inspector[CPUINSPECT_NAME_LEN]; + +LIST_HEAD(cpu_inspectors); +struct cpu_inspector *curr_cpu_inspector; +struct cpu_inspector *prev_cpu_inspector; + +/** + * cpuinspect_find_inspector - finds a inspector of the specified name + * @str: the name + */ +static struct cpu_inspector *cpuinspect_find_inspector(const char *str) +{ + struct cpu_inspector *insp; + + list_for_each_entry(insp, &cpu_inspectors, list) + if (!strncasecmp(str, insp->name, CPUINSPECT_NAME_LEN)) + return insp; + return NULL; +} + +/** + * cpuinspect_switch_inspector - changes the inspector + * @insp: the new target inspector + */ +int cpuinspect_switch_inspector(struct cpu_inspector *insp) +{ + if (!insp) + return -EINVAL; + + if (insp == curr_cpu_inspector) + return 0; + + curr_cpu_inspector = insp; + pr_info("using inspector %s, group_num: %u\n", insp->name, insp->group_num); + + return 0; +} + +/** + * cpuinspect_register_inspector - registers a inspector + * @insp: the inspector + */ +int cpuinspect_register_inspector(struct cpu_inspector *insp) +{ + int ret = -EEXIST; + + if (!insp) + return -EINVAL; + + mutex_lock(&cpuinspect_lock); + if (cpuinspect_find_inspector(insp->name) == NULL) { + ret = 0; + list_add_tail(&insp->list, &cpu_inspectors); + + /* + * We select the inspector if current inspector is NULL or it is + * one specificed by kernel parameter. + */ + if (!curr_cpu_inspector || + !strncasecmp(param_inspector, insp->name, CPUINSPECT_NAME_LEN)) + cpuinspect_switch_inspector(insp); + } + mutex_unlock(&cpuinspect_lock); + + return ret; +} +EXPORT_SYMBOL(cpuinspect_register_inspector); + +/** + * cpuinspect_unregister_inspector - unregisters a inspector + * @insp: the inspector + */ +int cpuinspect_unregister_inspector(struct cpu_inspector *insp) +{ + if (!insp) + return -EINVAL; + + mutex_lock(&cpuinspect_lock); + if (curr_cpu_inspector == insp) { + if (ci_core.inspect_on) { + mutex_unlock(&cpuinspect_lock); + return -EBUSY; + } + + curr_cpu_inspector = NULL; + } + + if (!list_empty(&insp->list)) + list_del(&insp->list); + + mutex_unlock(&cpuinspect_lock); + + return 0; +} +EXPORT_SYMBOL(cpuinspect_unregister_inspector); diff --git a/drivers/cpuinspect/sysfs.c b/drivers/cpuinspect/sysfs.c new file mode 100644 index 0000000000000000000000000000000000000000..6838d31d48bb60608313f9d239066faee8c9732b --- /dev/null +++ b/drivers/cpuinspect/sysfs.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * sysfs.c - sysfs support + * + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * + * Author: Yu Liao + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, 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 General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpuinspect.h" + +static ssize_t available_inspector_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct cpu_inspector *tmp; + ssize_t i = 0; + + list_for_each_entry(tmp, &cpu_inspectors, list) { + if (i >= (ssize_t) (PAGE_SIZE - (CPUINSPECT_NAME_LEN + 2))) + goto out; + + i += scnprintf(&buf[i], CPUINSPECT_NAME_LEN + 1, "%s ", tmp->name); + } + +out: + i += sprintf(&buf[i], "\n"); + return i; +} + +static ssize_t current_inspector_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + + if (curr_cpu_inspector) + ret = sprintf(buf, "%s\n", curr_cpu_inspector->name); + else + ret = sprintf(buf, "none\n"); + + return ret; +} + +static ssize_t current_inspector_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char insp_name[CPUINSPECT_NAME_LEN + 1]; + int ret; + struct cpu_inspector *insp; + + ret = sscanf(buf, "%" __stringify(CPUINSPECT_NAME_LEN) "s", insp_name); + if (ret != 1) + return -EINVAL; + + if (ci_core.inspect_on) + return -EBUSY; + + ret = -EINVAL; + list_for_each_entry(insp, &cpu_inspectors, list) { + if (!strncmp(insp->name, insp_name, CPUINSPECT_NAME_LEN)) { + ret = cpuinspect_switch_inspector(insp); + break; + } + } + + return ret ? ret : count; +} + +ssize_t patrol_complete_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", !ci_core.inspect_on); +} + +ssize_t cpu_utility_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", ci_core.cpu_utility); +} + +ssize_t cpu_utility_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned int cpu_util; + + if (kstrtouint(buf, 10, &cpu_util) || cpu_util < 1 || cpu_util > 100) + return -EINVAL; + + ci_core.cpu_utility = cpu_util; + + return size; +} + +ssize_t start_patrol_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + bool start_patrol; + + if (strtobool(buf, &start_patrol) < 0) + return -EINVAL; + + if (!mutex_trylock(&cpuinspect_lock)) + return -EBUSY; + + /* + * It is not allowed to start the inspection again during the + * inspection process. + */ + if (start_patrol == ci_core.inspect_on) { + mutex_unlock(&cpuinspect_lock); + return -EBUSY; + } + + if (start_patrol == 0) + stop_inspect_threads(); + else if (curr_cpu_inspector) + start_inspect_threads(); + + mutex_unlock(&cpuinspect_lock); + return size; +} + +static ssize_t result_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%*pbl\n", nr_cpu_ids, &result); +} + +static ssize_t cpumask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%*pbl\n", + cpumask_pr_args(&ci_core.inspect_cpumask)); +} + +static ssize_t cpumask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t err; + + /* + * It is not allowed to modify cpumask during the CPU + * inspection operation. + */ + if (ci_core.inspect_on == 1) + return -EBUSY; + + err = cpulist_parse(buf, &ci_core.inspect_cpumask); + if (err) + return err; + + return count; +} + +/* + * Tell userspace to handle result if one of the following conditions is met: + * - Found fault core + * - Inspection task completed + */ +void cpuinspect_result_notify(void) +{ + sysfs_notify(&cpu_subsys.dev_root->kobj, "cpuinspect", "result"); +} + +static DEVICE_ATTR_RO(result); +static DEVICE_ATTR_WO(start_patrol); +static DEVICE_ATTR_RO(patrol_complete); +static DEVICE_ATTR_RW(cpu_utility); +static DEVICE_ATTR_RW(cpumask); +static DEVICE_ULONG_ATTR(patrol_times, 0644, ci_core.inspect_times); + +/* show and switch inspector */ +static DEVICE_ATTR_RO(available_inspector); +static DEVICE_ATTR_RW(current_inspector); + + +static struct attribute *cpuinspect_attrs[] = { + &dev_attr_result.attr, + &dev_attr_start_patrol.attr, + &dev_attr_patrol_complete.attr, + &dev_attr_cpu_utility.attr, + &dev_attr_cpumask.attr, + &dev_attr_patrol_times.attr.attr, + &dev_attr_available_inspector.attr, + &dev_attr_current_inspector.attr, + NULL +}; + +static struct attribute_group cpuinspect_attr_group = { + .attrs = cpuinspect_attrs, + .name = "cpuinspect", +}; + +/** + * cpuinspect_add_interface - add CPU global sysfs attributes + * @dev: the target device + */ +int cpuinspect_add_interface(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &cpuinspect_attr_group); +} + +/** + * cpuinspect_remove_interface - remove CPU global sysfs attributes + * @dev: the target device + */ +void cpuinspect_remove_interface(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &cpuinspect_attr_group); +} diff --git a/include/linux/cpuinspect.h b/include/linux/cpuinspect.h new file mode 100644 index 0000000000000000000000000000000000000000..5bf1c57f73ca238e417fe2f63730794833e0cde8 --- /dev/null +++ b/include/linux/cpuinspect.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * cpuinspect.h - a generic framework for CPU online inspection + * + * Copyright (c) 2023 Yu Liao + */ + +#ifndef __LINUX_CPUINSPECT_H +#define __LINUX_CPUINSPECT_H + +#include +#include +#include + +#define CPUINSPECT_NAME_LEN 16 + +/** + * struct cpu_inspector - CPU Inspection driver. Inspection code may run in + * kernel, BIOS, trusted OS, etc., and contains many test cases. All test + * cases can be divided into multiple or one group, provied a function + * to start inspection for a specified group. + * + * @name: Pointer to inspector name + * @list: List head for registration (internal) + * @group_num: Number of inspection code groups + * @start_inspect: Function to start inspect process, passes group + * number as a argument + */ +struct cpu_inspector { + const char name[CPUINSPECT_NAME_LEN]; + struct list_head list; + unsigned int group_num; + + int (*start_inspect)(unsigned int group); +}; + +extern int cpuinspect_register_inspector(struct cpu_inspector *insp); +extern int cpuinspect_unregister_inspector(struct cpu_inspector *insp); + +#endif /* __LINUX_CPUINSPECT_H */