| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * User interface for Resource Allocation in Resource Director Technology(RDT) | |
| * | |
| * Copyright (C) 2016 Intel Corporation | |
| * | |
| * Author: Fenghua Yu <fenghua.yu@intel.com> | |
| * | |
| * More information about RDT be found in the Intel (R) x86 Architecture | |
| * Software Developer Manual. | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/cpu.h> | |
| #include <linux/debugfs.h> | |
| #include <linux/fs.h> | |
| #include <linux/fs_parser.h> | |
| #include <linux/sysfs.h> | |
| #include <linux/kernfs.h> | |
| #include <linux/resctrl.h> | |
| #include <linux/seq_buf.h> | |
| #include <linux/seq_file.h> | |
| #include <linux/sched/signal.h> | |
| #include <linux/sched/task.h> | |
| #include <linux/slab.h> | |
| #include <linux/task_work.h> | |
| #include <linux/user_namespace.h> | |
| #include <uapi/linux/magic.h> | |
| #include <asm/msr.h> | |
| #include "internal.h" | |
| DEFINE_STATIC_KEY_FALSE(rdt_enable_key); | |
| DEFINE_STATIC_KEY_FALSE(rdt_mon_enable_key); | |
| DEFINE_STATIC_KEY_FALSE(rdt_alloc_enable_key); | |
| /* | |
| * This is safe against resctrl_arch_sched_in() called from __switch_to() | |
| * because __switch_to() is executed with interrupts disabled. A local call | |
| * from update_closid_rmid() is protected against __switch_to() because | |
| * preemption is disabled. | |
| */ | |
| void resctrl_arch_sync_cpu_closid_rmid(void *info) | |
| { | |
| struct resctrl_cpu_defaults *r = info; | |
| if (r) { | |
| this_cpu_write(pqr_state.default_closid, r->closid); | |
| this_cpu_write(pqr_state.default_rmid, r->rmid); | |
| } | |
| /* | |
| * We cannot unconditionally write the MSR because the current | |
| * executing task might have its own closid selected. Just reuse | |
| * the context switch code. | |
| */ | |
| resctrl_arch_sched_in(current); | |
| } | |
| #define INVALID_CONFIG_INDEX UINT_MAX | |
| /** | |
| * mon_event_config_index_get - get the hardware index for the | |
| * configurable event | |
| * @evtid: event id. | |
| * | |
| * Return: 0 for evtid == QOS_L3_MBM_TOTAL_EVENT_ID | |
| * 1 for evtid == QOS_L3_MBM_LOCAL_EVENT_ID | |
| * INVALID_CONFIG_INDEX for invalid evtid | |
| */ | |
| static inline unsigned int mon_event_config_index_get(u32 evtid) | |
| { | |
| switch (evtid) { | |
| case QOS_L3_MBM_TOTAL_EVENT_ID: | |
| return 0; | |
| case QOS_L3_MBM_LOCAL_EVENT_ID: | |
| return 1; | |
| default: | |
| /* Should never reach here */ | |
| return INVALID_CONFIG_INDEX; | |
| } | |
| } | |
| void resctrl_arch_mon_event_config_read(void *_config_info) | |
| { | |
| struct resctrl_mon_config_info *config_info = _config_info; | |
| unsigned int index; | |
| u64 msrval; | |
| index = mon_event_config_index_get(config_info->evtid); | |
| if (index == INVALID_CONFIG_INDEX) { | |
| pr_warn_once("Invalid event id %d\n", config_info->evtid); | |
| return; | |
| } | |
| rdmsrq(MSR_IA32_EVT_CFG_BASE + index, msrval); | |
| /* Report only the valid event configuration bits */ | |
| config_info->mon_config = msrval & MAX_EVT_CONFIG_BITS; | |
| } | |
| void resctrl_arch_mon_event_config_write(void *_config_info) | |
| { | |
| struct resctrl_mon_config_info *config_info = _config_info; | |
| unsigned int index; | |
| index = mon_event_config_index_get(config_info->evtid); | |
| if (index == INVALID_CONFIG_INDEX) { | |
| pr_warn_once("Invalid event id %d\n", config_info->evtid); | |
| return; | |
| } | |
| wrmsrq(MSR_IA32_EVT_CFG_BASE + index, config_info->mon_config); | |
| } | |
| static void l3_qos_cfg_update(void *arg) | |
| { | |
| bool *enable = arg; | |
| wrmsrq(MSR_IA32_L3_QOS_CFG, *enable ? L3_QOS_CDP_ENABLE : 0ULL); | |
| } | |
| static void l2_qos_cfg_update(void *arg) | |
| { | |
| bool *enable = arg; | |
| wrmsrq(MSR_IA32_L2_QOS_CFG, *enable ? L2_QOS_CDP_ENABLE : 0ULL); | |
| } | |
| static int set_cache_qos_cfg(int level, bool enable) | |
| { | |
| void (*update)(void *arg); | |
| struct rdt_ctrl_domain *d; | |
| struct rdt_resource *r_l; | |
| cpumask_var_t cpu_mask; | |
| int cpu; | |
| /* Walking r->domains, ensure it can't race with cpuhp */ | |
| lockdep_assert_cpus_held(); | |
| if (level == RDT_RESOURCE_L3) | |
| update = l3_qos_cfg_update; | |
| else if (level == RDT_RESOURCE_L2) | |
| update = l2_qos_cfg_update; | |
| else | |
| return -EINVAL; | |
| if (!zalloc_cpumask_var(&cpu_mask, GFP_KERNEL)) | |
| return -ENOMEM; | |
| r_l = &rdt_resources_all[level].r_resctrl; | |
| list_for_each_entry_rcu(d, &r_l->ctrl_domains, hdr.list, lockdep_is_cpus_held()) { | |
| if (r_l->cache.arch_has_per_cpu_cfg) | |
| /* Pick all the CPUs in the domain instance */ | |
| for_each_cpu(cpu, &d->hdr.cpu_mask) | |
| cpumask_set_cpu(cpu, cpu_mask); | |
| else | |
| /* Pick one CPU from each domain instance to update MSR */ | |
| cpumask_set_cpu(cpumask_any(&d->hdr.cpu_mask), cpu_mask); | |
| } | |
| /* Update QOS_CFG MSR on all the CPUs in cpu_mask */ | |
| on_each_cpu_mask(cpu_mask, update, &enable, 1); | |
| free_cpumask_var(cpu_mask); | |
| return 0; | |
| } | |
| /* Restore the qos cfg state when a domain comes online */ | |
| void rdt_domain_reconfigure_cdp(struct rdt_resource *r) | |
| { | |
| struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r); | |
| if (!r->cdp_capable) | |
| return; | |
| if (r->rid == RDT_RESOURCE_L2) | |
| l2_qos_cfg_update(&hw_res->cdp_enabled); | |
| if (r->rid == RDT_RESOURCE_L3) | |
| l3_qos_cfg_update(&hw_res->cdp_enabled); | |
| } | |
| static int cdp_enable(int level) | |
| { | |
| struct rdt_resource *r_l = &rdt_resources_all[level].r_resctrl; | |
| int ret; | |
| if (!r_l->alloc_capable) | |
| return -EINVAL; | |
| ret = set_cache_qos_cfg(level, true); | |
| if (!ret) | |
| rdt_resources_all[level].cdp_enabled = true; | |
| return ret; | |
| } | |
| static void cdp_disable(int level) | |
| { | |
| struct rdt_hw_resource *r_hw = &rdt_resources_all[level]; | |
| if (r_hw->cdp_enabled) { | |
| set_cache_qos_cfg(level, false); | |
| r_hw->cdp_enabled = false; | |
| } | |
| } | |
| int resctrl_arch_set_cdp_enabled(enum resctrl_res_level l, bool enable) | |
| { | |
| struct rdt_hw_resource *hw_res = &rdt_resources_all[l]; | |
| if (!hw_res->r_resctrl.cdp_capable) | |
| return -EINVAL; | |
| if (enable) | |
| return cdp_enable(l); | |
| cdp_disable(l); | |
| return 0; | |
| } | |
| bool resctrl_arch_get_cdp_enabled(enum resctrl_res_level l) | |
| { | |
| return rdt_resources_all[l].cdp_enabled; | |
| } | |
| void resctrl_arch_reset_all_ctrls(struct rdt_resource *r) | |
| { | |
| struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r); | |
| struct rdt_hw_ctrl_domain *hw_dom; | |
| struct msr_param msr_param; | |
| struct rdt_ctrl_domain *d; | |
| int i; | |
| /* Walking r->domains, ensure it can't race with cpuhp */ | |
| lockdep_assert_cpus_held(); | |
| msr_param.res = r; | |
| msr_param.low = 0; | |
| msr_param.high = hw_res->num_closid; | |
| /* | |
| * Disable resource control for this resource by setting all | |
| * CBMs in all ctrl_domains to the maximum mask value. Pick one CPU | |
| * from each domain to update the MSRs below. | |
| */ | |
| list_for_each_entry_rcu(d, &r->ctrl_domains, hdr.list, lockdep_is_cpus_held()) { | |
| hw_dom = resctrl_to_arch_ctrl_dom(d); | |
| for (i = 0; i < hw_res->num_closid; i++) | |
| hw_dom->ctrl_val[i] = resctrl_get_default_ctrl(r); | |
| msr_param.dom = d; | |
| smp_call_function_any(&d->hdr.cpu_mask, rdt_ctrl_update, &msr_param, 1); | |
| } | |
| return; | |
| } |