mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-28 22:54:05 +08:00
40548c6b6c
Pull x86 pti updates from Thomas Gleixner: "This contains: - a PTI bugfix to avoid setting reserved CR3 bits when PCID is disabled. This seems to cause issues on a virtual machine at least and is incorrect according to the AMD manual. - a PTI bugfix which disables the perf BTS facility if PTI is enabled. The BTS AUX buffer is not globally visible and causes the CPU to fault when the mapping disappears on switching CR3 to user space. A full fix which restores BTS on PTI is non trivial and will be worked on. - PTI bugfixes for EFI and trusted boot which make sure that the user space visible page table entries have the NX bit cleared - removal of dead code in the PTI pagetable setup functions - add PTI documentation - add a selftest for vsyscall to verify that the kernel actually implements what it advertises. - a sysfs interface to expose vulnerability and mitigation information so there is a coherent way for users to retrieve the status. - the initial spectre_v2 mitigations, aka retpoline: + The necessary ASM thunk and compiler support + The ASM variants of retpoline and the conversion of affected ASM code + Make LFENCE serializing on AMD so it can be used as speculation trap + The RSB fill after vmexit - initial objtool support for retpoline As I said in the status mail this is the most of the set of patches which should go into 4.15 except two straight forward patches still on hold: - the retpoline add on of LFENCE which waits for ACKs - the RSB fill after context switch Both should be ready to go early next week and with that we'll have covered the major holes of spectre_v2 and go back to normality" * 'x86-pti-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (28 commits) x86,perf: Disable intel_bts when PTI security/Kconfig: Correct the Documentation reference for PTI x86/pti: Fix !PCID and sanitize defines selftests/x86: Add test_vsyscall x86/retpoline: Fill return stack buffer on vmexit x86/retpoline/irq32: Convert assembler indirect jumps x86/retpoline/checksum32: Convert assembler indirect jumps x86/retpoline/xen: Convert Xen hypercall indirect jumps x86/retpoline/hyperv: Convert assembler indirect jumps x86/retpoline/ftrace: Convert ftrace assembler indirect jumps x86/retpoline/entry: Convert entry assembler indirect jumps x86/retpoline/crypto: Convert crypto assembler indirect jumps x86/spectre: Add boot time option to select Spectre v2 mitigation x86/retpoline: Add initial retpoline support objtool: Allow alternatives to be ignored objtool: Detect jumps to retpoline thunks x86/pti: Make unpoison of pgd for trusted boot work for real x86/alternatives: Fix optimize_nops() checking sysfs/cpu: Fix typos in vulnerability documentation x86/cpu/AMD: Use LFENCE_RDTSC in preference to MFENCE_RDTSC ...
569 lines
14 KiB
C
569 lines
14 KiB
C
/*
|
|
* CPU subsystem support
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/topology.h>
|
|
#include <linux/device.h>
|
|
#include <linux/node.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/of.h>
|
|
#include <linux/cpufeature.h>
|
|
#include <linux/tick.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/sched/isolation.h>
|
|
|
|
#include "base.h"
|
|
|
|
static DEFINE_PER_CPU(struct device *, cpu_sys_devices);
|
|
|
|
static int cpu_subsys_match(struct device *dev, struct device_driver *drv)
|
|
{
|
|
/* ACPI style match is the only one that may succeed. */
|
|
if (acpi_driver_match_device(dev, drv))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
static void change_cpu_under_node(struct cpu *cpu,
|
|
unsigned int from_nid, unsigned int to_nid)
|
|
{
|
|
int cpuid = cpu->dev.id;
|
|
unregister_cpu_under_node(cpuid, from_nid);
|
|
register_cpu_under_node(cpuid, to_nid);
|
|
cpu->node_id = to_nid;
|
|
}
|
|
|
|
static int cpu_subsys_online(struct device *dev)
|
|
{
|
|
struct cpu *cpu = container_of(dev, struct cpu, dev);
|
|
int cpuid = dev->id;
|
|
int from_nid, to_nid;
|
|
int ret;
|
|
|
|
from_nid = cpu_to_node(cpuid);
|
|
if (from_nid == NUMA_NO_NODE)
|
|
return -ENODEV;
|
|
|
|
ret = cpu_up(cpuid);
|
|
/*
|
|
* When hot adding memory to memoryless node and enabling a cpu
|
|
* on the node, node number of the cpu may internally change.
|
|
*/
|
|
to_nid = cpu_to_node(cpuid);
|
|
if (from_nid != to_nid)
|
|
change_cpu_under_node(cpu, from_nid, to_nid);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cpu_subsys_offline(struct device *dev)
|
|
{
|
|
return cpu_down(dev->id);
|
|
}
|
|
|
|
void unregister_cpu(struct cpu *cpu)
|
|
{
|
|
int logical_cpu = cpu->dev.id;
|
|
|
|
unregister_cpu_under_node(logical_cpu, cpu_to_node(logical_cpu));
|
|
|
|
device_unregister(&cpu->dev);
|
|
per_cpu(cpu_sys_devices, logical_cpu) = NULL;
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_ARCH_CPU_PROBE_RELEASE
|
|
static ssize_t cpu_probe_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
ssize_t cnt;
|
|
int ret;
|
|
|
|
ret = lock_device_hotplug_sysfs();
|
|
if (ret)
|
|
return ret;
|
|
|
|
cnt = arch_cpu_probe(buf, count);
|
|
|
|
unlock_device_hotplug();
|
|
return cnt;
|
|
}
|
|
|
|
static ssize_t cpu_release_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
ssize_t cnt;
|
|
int ret;
|
|
|
|
ret = lock_device_hotplug_sysfs();
|
|
if (ret)
|
|
return ret;
|
|
|
|
cnt = arch_cpu_release(buf, count);
|
|
|
|
unlock_device_hotplug();
|
|
return cnt;
|
|
}
|
|
|
|
static DEVICE_ATTR(probe, S_IWUSR, NULL, cpu_probe_store);
|
|
static DEVICE_ATTR(release, S_IWUSR, NULL, cpu_release_store);
|
|
#endif /* CONFIG_ARCH_CPU_PROBE_RELEASE */
|
|
#endif /* CONFIG_HOTPLUG_CPU */
|
|
|
|
struct bus_type cpu_subsys = {
|
|
.name = "cpu",
|
|
.dev_name = "cpu",
|
|
.match = cpu_subsys_match,
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
.online = cpu_subsys_online,
|
|
.offline = cpu_subsys_offline,
|
|
#endif
|
|
};
|
|
EXPORT_SYMBOL_GPL(cpu_subsys);
|
|
|
|
#ifdef CONFIG_KEXEC
|
|
#include <linux/kexec.h>
|
|
|
|
static ssize_t show_crash_notes(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cpu *cpu = container_of(dev, struct cpu, dev);
|
|
ssize_t rc;
|
|
unsigned long long addr;
|
|
int cpunum;
|
|
|
|
cpunum = cpu->dev.id;
|
|
|
|
/*
|
|
* Might be reading other cpu's data based on which cpu read thread
|
|
* has been scheduled. But cpu data (memory) is allocated once during
|
|
* boot up and this data does not change there after. Hence this
|
|
* operation should be safe. No locking required.
|
|
*/
|
|
addr = per_cpu_ptr_to_phys(per_cpu_ptr(crash_notes, cpunum));
|
|
rc = sprintf(buf, "%Lx\n", addr);
|
|
return rc;
|
|
}
|
|
static DEVICE_ATTR(crash_notes, 0400, show_crash_notes, NULL);
|
|
|
|
static ssize_t show_crash_notes_size(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
ssize_t rc;
|
|
|
|
rc = sprintf(buf, "%zu\n", sizeof(note_buf_t));
|
|
return rc;
|
|
}
|
|
static DEVICE_ATTR(crash_notes_size, 0400, show_crash_notes_size, NULL);
|
|
|
|
static struct attribute *crash_note_cpu_attrs[] = {
|
|
&dev_attr_crash_notes.attr,
|
|
&dev_attr_crash_notes_size.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group crash_note_cpu_attr_group = {
|
|
.attrs = crash_note_cpu_attrs,
|
|
};
|
|
#endif
|
|
|
|
static const struct attribute_group *common_cpu_attr_groups[] = {
|
|
#ifdef CONFIG_KEXEC
|
|
&crash_note_cpu_attr_group,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group *hotplugable_cpu_attr_groups[] = {
|
|
#ifdef CONFIG_KEXEC
|
|
&crash_note_cpu_attr_group,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Print cpu online, possible, present, and system maps
|
|
*/
|
|
|
|
struct cpu_attr {
|
|
struct device_attribute attr;
|
|
const struct cpumask *const map;
|
|
};
|
|
|
|
static ssize_t show_cpus_attr(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cpu_attr *ca = container_of(attr, struct cpu_attr, attr);
|
|
|
|
return cpumap_print_to_pagebuf(true, buf, ca->map);
|
|
}
|
|
|
|
#define _CPU_ATTR(name, map) \
|
|
{ __ATTR(name, 0444, show_cpus_attr, NULL), map }
|
|
|
|
/* Keep in sync with cpu_subsys_attrs */
|
|
static struct cpu_attr cpu_attrs[] = {
|
|
_CPU_ATTR(online, &__cpu_online_mask),
|
|
_CPU_ATTR(possible, &__cpu_possible_mask),
|
|
_CPU_ATTR(present, &__cpu_present_mask),
|
|
};
|
|
|
|
/*
|
|
* Print values for NR_CPUS and offlined cpus
|
|
*/
|
|
static ssize_t print_cpus_kernel_max(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int n = snprintf(buf, PAGE_SIZE-2, "%d\n", NR_CPUS - 1);
|
|
return n;
|
|
}
|
|
static DEVICE_ATTR(kernel_max, 0444, print_cpus_kernel_max, NULL);
|
|
|
|
/* arch-optional setting to enable display of offline cpus >= nr_cpu_ids */
|
|
unsigned int total_cpus;
|
|
|
|
static ssize_t print_cpus_offline(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int n = 0, len = PAGE_SIZE-2;
|
|
cpumask_var_t offline;
|
|
|
|
/* display offline cpus < nr_cpu_ids */
|
|
if (!alloc_cpumask_var(&offline, GFP_KERNEL))
|
|
return -ENOMEM;
|
|
cpumask_andnot(offline, cpu_possible_mask, cpu_online_mask);
|
|
n = scnprintf(buf, len, "%*pbl", cpumask_pr_args(offline));
|
|
free_cpumask_var(offline);
|
|
|
|
/* display offline cpus >= nr_cpu_ids */
|
|
if (total_cpus && nr_cpu_ids < total_cpus) {
|
|
if (n && n < len)
|
|
buf[n++] = ',';
|
|
|
|
if (nr_cpu_ids == total_cpus-1)
|
|
n += snprintf(&buf[n], len - n, "%u", nr_cpu_ids);
|
|
else
|
|
n += snprintf(&buf[n], len - n, "%u-%d",
|
|
nr_cpu_ids, total_cpus-1);
|
|
}
|
|
|
|
n += snprintf(&buf[n], len - n, "\n");
|
|
return n;
|
|
}
|
|
static DEVICE_ATTR(offline, 0444, print_cpus_offline, NULL);
|
|
|
|
static ssize_t print_cpus_isolated(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int n = 0, len = PAGE_SIZE-2;
|
|
cpumask_var_t isolated;
|
|
|
|
if (!alloc_cpumask_var(&isolated, GFP_KERNEL))
|
|
return -ENOMEM;
|
|
|
|
cpumask_andnot(isolated, cpu_possible_mask,
|
|
housekeeping_cpumask(HK_FLAG_DOMAIN));
|
|
n = scnprintf(buf, len, "%*pbl\n", cpumask_pr_args(isolated));
|
|
|
|
free_cpumask_var(isolated);
|
|
|
|
return n;
|
|
}
|
|
static DEVICE_ATTR(isolated, 0444, print_cpus_isolated, NULL);
|
|
|
|
#ifdef CONFIG_NO_HZ_FULL
|
|
static ssize_t print_cpus_nohz_full(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int n = 0, len = PAGE_SIZE-2;
|
|
|
|
n = scnprintf(buf, len, "%*pbl\n", cpumask_pr_args(tick_nohz_full_mask));
|
|
|
|
return n;
|
|
}
|
|
static DEVICE_ATTR(nohz_full, 0444, print_cpus_nohz_full, NULL);
|
|
#endif
|
|
|
|
static void cpu_device_release(struct device *dev)
|
|
{
|
|
/*
|
|
* This is an empty function to prevent the driver core from spitting a
|
|
* warning at us. Yes, I know this is directly opposite of what the
|
|
* documentation for the driver core and kobjects say, and the author
|
|
* of this code has already been publically ridiculed for doing
|
|
* something as foolish as this. However, at this point in time, it is
|
|
* the only way to handle the issue of statically allocated cpu
|
|
* devices. The different architectures will have their cpu device
|
|
* code reworked to properly handle this in the near future, so this
|
|
* function will then be changed to correctly free up the memory held
|
|
* by the cpu device.
|
|
*
|
|
* Never copy this way of doing things, or you too will be made fun of
|
|
* on the linux-kernel list, you have been warned.
|
|
*/
|
|
}
|
|
|
|
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
|
|
static ssize_t print_cpu_modalias(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
ssize_t n;
|
|
u32 i;
|
|
|
|
n = sprintf(buf, "cpu:type:" CPU_FEATURE_TYPEFMT ":feature:",
|
|
CPU_FEATURE_TYPEVAL);
|
|
|
|
for (i = 0; i < MAX_CPU_FEATURES; i++)
|
|
if (cpu_have_feature(i)) {
|
|
if (PAGE_SIZE < n + sizeof(",XXXX\n")) {
|
|
WARN(1, "CPU features overflow page\n");
|
|
break;
|
|
}
|
|
n += sprintf(&buf[n], ",%04X", i);
|
|
}
|
|
buf[n++] = '\n';
|
|
return n;
|
|
}
|
|
|
|
static int cpu_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
{
|
|
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (buf) {
|
|
print_cpu_modalias(NULL, NULL, buf);
|
|
add_uevent_var(env, "MODALIAS=%s", buf);
|
|
kfree(buf);
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* register_cpu - Setup a sysfs device for a CPU.
|
|
* @cpu - cpu->hotpluggable field set to 1 will generate a control file in
|
|
* sysfs for this CPU.
|
|
* @num - CPU number to use when creating the device.
|
|
*
|
|
* Initialize and register the CPU device.
|
|
*/
|
|
int register_cpu(struct cpu *cpu, int num)
|
|
{
|
|
int error;
|
|
|
|
cpu->node_id = cpu_to_node(num);
|
|
memset(&cpu->dev, 0x00, sizeof(struct device));
|
|
cpu->dev.id = num;
|
|
cpu->dev.bus = &cpu_subsys;
|
|
cpu->dev.release = cpu_device_release;
|
|
cpu->dev.offline_disabled = !cpu->hotpluggable;
|
|
cpu->dev.offline = !cpu_online(num);
|
|
cpu->dev.of_node = of_get_cpu_node(num, NULL);
|
|
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
|
|
cpu->dev.bus->uevent = cpu_uevent;
|
|
#endif
|
|
cpu->dev.groups = common_cpu_attr_groups;
|
|
if (cpu->hotpluggable)
|
|
cpu->dev.groups = hotplugable_cpu_attr_groups;
|
|
error = device_register(&cpu->dev);
|
|
if (error)
|
|
return error;
|
|
|
|
per_cpu(cpu_sys_devices, num) = &cpu->dev;
|
|
register_cpu_under_node(num, cpu_to_node(num));
|
|
dev_pm_qos_expose_latency_limit(&cpu->dev,
|
|
PM_QOS_RESUME_LATENCY_NO_CONSTRAINT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct device *get_cpu_device(unsigned cpu)
|
|
{
|
|
if (cpu < nr_cpu_ids && cpu_possible(cpu))
|
|
return per_cpu(cpu_sys_devices, cpu);
|
|
else
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(get_cpu_device);
|
|
|
|
static void device_create_release(struct device *dev)
|
|
{
|
|
kfree(dev);
|
|
}
|
|
|
|
static struct device *
|
|
__cpu_device_create(struct device *parent, void *drvdata,
|
|
const struct attribute_group **groups,
|
|
const char *fmt, va_list args)
|
|
{
|
|
struct device *dev = NULL;
|
|
int retval = -ENODEV;
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (!dev) {
|
|
retval = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
device_initialize(dev);
|
|
dev->parent = parent;
|
|
dev->groups = groups;
|
|
dev->release = device_create_release;
|
|
dev_set_drvdata(dev, drvdata);
|
|
|
|
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
|
|
if (retval)
|
|
goto error;
|
|
|
|
retval = device_add(dev);
|
|
if (retval)
|
|
goto error;
|
|
|
|
return dev;
|
|
|
|
error:
|
|
put_device(dev);
|
|
return ERR_PTR(retval);
|
|
}
|
|
|
|
struct device *cpu_device_create(struct device *parent, void *drvdata,
|
|
const struct attribute_group **groups,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list vargs;
|
|
struct device *dev;
|
|
|
|
va_start(vargs, fmt);
|
|
dev = __cpu_device_create(parent, drvdata, groups, fmt, vargs);
|
|
va_end(vargs);
|
|
return dev;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpu_device_create);
|
|
|
|
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
|
|
static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL);
|
|
#endif
|
|
|
|
static struct attribute *cpu_root_attrs[] = {
|
|
#ifdef CONFIG_ARCH_CPU_PROBE_RELEASE
|
|
&dev_attr_probe.attr,
|
|
&dev_attr_release.attr,
|
|
#endif
|
|
&cpu_attrs[0].attr.attr,
|
|
&cpu_attrs[1].attr.attr,
|
|
&cpu_attrs[2].attr.attr,
|
|
&dev_attr_kernel_max.attr,
|
|
&dev_attr_offline.attr,
|
|
&dev_attr_isolated.attr,
|
|
#ifdef CONFIG_NO_HZ_FULL
|
|
&dev_attr_nohz_full.attr,
|
|
#endif
|
|
#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
|
|
&dev_attr_modalias.attr,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group cpu_root_attr_group = {
|
|
.attrs = cpu_root_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *cpu_root_attr_groups[] = {
|
|
&cpu_root_attr_group,
|
|
NULL,
|
|
};
|
|
|
|
bool cpu_is_hotpluggable(unsigned cpu)
|
|
{
|
|
struct device *dev = get_cpu_device(cpu);
|
|
return dev && container_of(dev, struct cpu, dev)->hotpluggable;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpu_is_hotpluggable);
|
|
|
|
#ifdef CONFIG_GENERIC_CPU_DEVICES
|
|
static DEFINE_PER_CPU(struct cpu, cpu_devices);
|
|
#endif
|
|
|
|
static void __init cpu_dev_register_generic(void)
|
|
{
|
|
#ifdef CONFIG_GENERIC_CPU_DEVICES
|
|
int i;
|
|
|
|
for_each_possible_cpu(i) {
|
|
if (register_cpu(&per_cpu(cpu_devices, i), i))
|
|
panic("Failed to register CPU device");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_GENERIC_CPU_VULNERABILITIES
|
|
|
|
ssize_t __weak cpu_show_meltdown(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "Not affected\n");
|
|
}
|
|
|
|
ssize_t __weak cpu_show_spectre_v1(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "Not affected\n");
|
|
}
|
|
|
|
ssize_t __weak cpu_show_spectre_v2(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "Not affected\n");
|
|
}
|
|
|
|
static DEVICE_ATTR(meltdown, 0444, cpu_show_meltdown, NULL);
|
|
static DEVICE_ATTR(spectre_v1, 0444, cpu_show_spectre_v1, NULL);
|
|
static DEVICE_ATTR(spectre_v2, 0444, cpu_show_spectre_v2, NULL);
|
|
|
|
static struct attribute *cpu_root_vulnerabilities_attrs[] = {
|
|
&dev_attr_meltdown.attr,
|
|
&dev_attr_spectre_v1.attr,
|
|
&dev_attr_spectre_v2.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group cpu_root_vulnerabilities_group = {
|
|
.name = "vulnerabilities",
|
|
.attrs = cpu_root_vulnerabilities_attrs,
|
|
};
|
|
|
|
static void __init cpu_register_vulnerabilities(void)
|
|
{
|
|
if (sysfs_create_group(&cpu_subsys.dev_root->kobj,
|
|
&cpu_root_vulnerabilities_group))
|
|
pr_err("Unable to register CPU vulnerabilities\n");
|
|
}
|
|
|
|
#else
|
|
static inline void cpu_register_vulnerabilities(void) { }
|
|
#endif
|
|
|
|
void __init cpu_dev_init(void)
|
|
{
|
|
if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
|
|
panic("Failed to register CPU subsystem");
|
|
|
|
cpu_dev_register_generic();
|
|
cpu_register_vulnerabilities();
|
|
}
|