From f1d34fe4c239a25e4c7112c8c20687765f143086 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Tue, 11 Mar 2025 11:24:03 +0800 Subject: [PATCH 001/107] cpufreq: Fix re-boost issue after hotplugging a CPU [Upstream commit 1608f0230510489d74a2e24e47054233b7e4678a] It turns out that CPUX will stay on the base frequency after performing these operations: 1. boost all CPUs: echo 1 > /sys/devices/system/cpu/cpufreq/boost 2. offline one CPU: echo 0 > /sys/devices/system/cpu/cpuX/online 3. deboost all CPUs: echo 0 > /sys/devices/system/cpu/cpufreq/boost 4. online CPUX: echo 1 > /sys/devices/system/cpu/cpuX/online 5. boost all CPUs again: echo 1 > /sys/devices/system/cpu/cpufreq/boost This is because max_freq_req of the policy is not updated during the online process, and the value of max_freq_req before the last offline is retained. When the CPU is boosted again, freq_qos_update_request() will do nothing because the old value is the same as the new one. This causes the CPU to stay at the base frequency. Updating max_freq_req in cpufreq_online() will solve this problem. Signed-off-by: Lifeng Zheng Acked-by: Viresh Kumar Link: https://patch.msgid.link/20250117101457.1530653-2-zhenglifeng1@huawei.com [ rjw: Subject and changelog edits ] Signed-off-by: Rafael J. Wysocki Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 76b0b9e6309b9..7e1efc3940a3d 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1504,6 +1504,10 @@ static int cpufreq_online(unsigned int cpu) blocking_notifier_call_chain(&cpufreq_policy_notifier_list, CPUFREQ_CREATE_POLICY, policy); + } else { + ret = freq_qos_update_request(policy->max_freq_req, policy->max); + if (ret < 0) + goto out_destroy_policy; } if (cpufreq_driver->get && has_target()) { From c144c4b14ae3802ee2550930165cdbb653cff539 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Tue, 11 Mar 2025 11:24:04 +0800 Subject: [PATCH 002/107] cpufreq: Introduce a more generic way to set default per-policy boost flag [Upstream commit dd016f379ebc2d43a9405742d1a6066577509bd7] In cpufreq_online() of cpufreq.c, the per-policy boost flag is already set to mirror the cpufreq_driver boost during init but using freq_table to judge if the policy has boost frequency. There are two drawbacks to this approach: 1. It doesn't work for the cpufreq drivers that do not use a frequency table. For now, acpi-cpufreq and amd-pstate have to enable boost in policy initialization. And cppc_cpufreq never set policy to boost when going online no matter what the cpufreq_driver boost flag is. 2. If the CPU goes offline when cpufreq_driver boost is enabled and then goes online when cpufreq_driver boost is disabled, the per-policy boost flag will incorrectly remain true. Running set_boost at the end of the online process is a more generic way for all cpufreq drivers. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250117101457.1530653-3-zhenglifeng1@huawei.com Acked-by: Viresh Kumar [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 7e1efc3940a3d..2e4e03ec855cf 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1438,10 +1438,6 @@ static int cpufreq_online(unsigned int cpu) goto out_free_policy; } - /* Let the per-policy boost flag mirror the cpufreq_driver boost during init */ - if (cpufreq_boost_enabled() && policy_has_boost_freq(policy)) - policy->boost_enabled = true; - /* * The initialization has succeeded and the policy is online. * If there is a problem with its frequency table, take it @@ -1602,6 +1598,18 @@ static int cpufreq_online(unsigned int cpu) if (new_policy && cpufreq_thermal_control_enabled(cpufreq_driver)) policy->cdev = of_cpufreq_cooling_register(policy); + /* Let the per-policy boost flag mirror the cpufreq_driver boost during init */ + if (policy->boost_enabled != cpufreq_boost_enabled()) { + policy->boost_enabled = cpufreq_boost_enabled(); + ret = cpufreq_driver->set_boost(policy, policy->boost_enabled); + if (ret) { + /* If the set_boost fails, the online operation is not affected */ + pr_info("%s: CPU%d: Cannot %s BOOST\n", __func__, policy->cpu, + policy->boost_enabled ? "enable" : "disable"); + policy->boost_enabled = !policy->boost_enabled; + } + } + pr_debug("initialization complete\n"); return 0; From ad9f9fad56ce3dde03a9da1c20fc37315fb098a2 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Tue, 11 Mar 2025 11:24:05 +0800 Subject: [PATCH 003/107] cpufreq: CPPC: Fix wrong max_freq in policy initialization [Upstream commit 03d8b4e76266e11662c5e544854b737843173e2d] In policy initialization, policy->max and policy->cpuinfo.max_freq are always set to the value calculated from caps->nominal_perf. This will cause the frequency stay on base frequency even if the policy is already boosted when a CPU is going online. Fix this by using policy->boost_enabled to determine which value should be set. Signed-off-by: Lifeng Zheng Acked-by: Viresh Kumar Link: https://patch.msgid.link/20250117101457.1530653-4-zhenglifeng1@huawei.com [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 4d96eed64fe0c..ec786018adf78 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -645,7 +645,8 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) * Section 8.4.7.1.1.5 of ACPI 6.1 spec) */ policy->min = cppc_perf_to_khz(caps, caps->lowest_nonlinear_perf); - policy->max = cppc_perf_to_khz(caps, caps->nominal_perf); + policy->max = cppc_perf_to_khz(caps, policy->boost_enabled ? + caps->highest_perf : caps->nominal_perf); /* * Set cpuinfo.min_freq to Lowest to make the full range of performance @@ -653,7 +654,7 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) * nonlinear perf */ policy->cpuinfo.min_freq = cppc_perf_to_khz(caps, caps->lowest_perf); - policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, caps->nominal_perf); + policy->cpuinfo.max_freq = policy->max; policy->transition_delay_us = cppc_cpufreq_get_transition_delay_us(cpu); policy->shared_type = cpu_data->shared_type; From 12a30ec5a8a39684ae99efd0c3918750496ccaee Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Tue, 11 Mar 2025 11:24:06 +0800 Subject: [PATCH 004/107] cpufreq: ACPI: Remove set_boost in acpi_cpufreq_cpu_init() [Upstream commit 2b16c631832df6cf8782fb1fdc7df8a4f03f4f16] At the end of cpufreq_online() in cpufreq.c, set_boost is executed and the per-policy boost flag is set to mirror the cpufreq_driver boost, so it is not necessary to run set_boost in acpi_cpufreq_cpu_init(). Signed-off-by: Lifeng Zheng Acked-by: Viresh Kumar Link: https://patch.msgid.link/20250117101457.1530653-5-zhenglifeng1@huawei.com [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/acpi-cpufreq.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 66a2047dd8981..92268e285cfa1 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c @@ -970,11 +970,6 @@ static int acpi_cpufreq_cpu_init(struct cpufreq_policy *policy) if (perf->states[0].core_frequency * 1000 != freq_table[0].frequency) pr_warn(FW_WARN "P-state 0 is not max freq\n"); - if (acpi_cpufreq_driver.set_boost) { - set_boost(policy, acpi_cpufreq_driver.boost_enabled); - policy->boost_enabled = acpi_cpufreq_driver.boost_enabled; - } - return result; err_unreg: From 41213aa9057040f3a2f199f7e8c4acb41789e9f9 Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Tue, 11 Mar 2025 11:24:07 +0800 Subject: [PATCH 005/107] cpufreq: prevent NULL dereference in cpufreq_online() [Upstream commit 0813fd2e14ca6ecd4e6ba005a9766f08e26020d7] Ensure cpufreq_driver->set_boost is non-NULL before using it in cpufreq_online() to prevent a potential NULL pointer dereference. Reported-by: Gautam Menghani Closes: https://lore.kernel.org/all/c9e56c5f54cc33338762c94e9bed7b5a0d5de812.camel@linux.ibm.com/ Fixes: da59223d340c ("cpufreq: Introduce a more generic way to set default per-policy boost flag") Suggested-by: Viresh Kumar Signed-off-by: Aboorva Devarajan Link: https://patch.msgid.link/20250205181347.2079272-1-aboorvad@linux.ibm.com [ rjw: Minor edits in the subject and changelog ] Signed-off-by: Rafael J. Wysocki Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 2e4e03ec855cf..55608a0763d55 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1599,7 +1599,8 @@ static int cpufreq_online(unsigned int cpu) policy->cdev = of_cpufreq_cooling_register(policy); /* Let the per-policy boost flag mirror the cpufreq_driver boost during init */ - if (policy->boost_enabled != cpufreq_boost_enabled()) { + if (cpufreq_driver->set_boost && + policy->boost_enabled != cpufreq_boost_enabled()) { policy->boost_enabled = cpufreq_boost_enabled(); ret = cpufreq_driver->set_boost(policy, policy->boost_enabled); if (ret) { From 88a926a320320301520bbb9ab6bfc8cd42598f76 Mon Sep 17 00:00:00 2001 From: Jie Zhan Date: Fri, 13 Jun 2025 15:27:49 +0800 Subject: [PATCH 006/107] cpufreq: CPPC: Don't warn on failing to read perf counters on offline cpus commit d08e86c77069fbbdd7fdbdaa408c198223bc0900 openEuler Reading perf counters on offline cpus should be expected to fail, e.g. it returns -EFAULT as counters are shown to be 0. Remove the unnecessary warning print on this failure path. Fixes: 1eb5dde674f5 ("cpufreq: CPPC: Add support for frequency invariance") Signed-off-by: Jie Zhan Signed-off-by: Hongye Lin Signed-off-by: huwentao Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index ec786018adf78..4cfad9ca1eb36 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -173,16 +173,14 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) init_irq_work(&cppc_fi->irq_work, cppc_irq_work); ret = cppc_get_perf_ctrs(cpu, &cppc_fi->prev_perf_fb_ctrs); - if (ret) { - pr_warn("%s: failed to read perf counters for cpu:%d: %d\n", - __func__, cpu, ret); - + if (ret && cpu_online(cpu)) { /* * Don't abort if the CPU was offline while the driver * was getting registered. */ - if (cpu_online(cpu)) - return; + pr_debug("%s: failed to read perf counters for cpu:%d: %d\n", + __func__, cpu, ret); + return; } } From 2ddf94a487adc06b19a145f3051ad66af160985c Mon Sep 17 00:00:00 2001 From: Jie Zhan Date: Fri, 13 Jun 2025 15:27:50 +0800 Subject: [PATCH 007/107] cpufreq: CPPC: Fix error handling in cppc_scale_freq_workfn() commit 2eab37aa8a55cdb8b85a912a4b1156c39689c89d openEuler Perf counters could be 0 if the cpu is in a low-power idle state. Just try it again next time and update the frequency scale when the cpu is active and perf counters successfully return. Also, remove the FIE source on an actual failure. Fixes: 1eb5dde674f5 ("cpufreq: CPPC: Add support for frequency invariance") Signed-off-by: Jie Zhan Signed-off-by: Hongye Lin Signed-off-by: huwentao Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 4cfad9ca1eb36..a5b7a4c0fc6bd 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -107,12 +107,23 @@ static void cppc_scale_freq_workfn(struct kthread_work *work) struct cppc_cpudata *cpu_data; unsigned long local_freq_scale; u64 perf; + int ret; cppc_fi = container_of(work, struct cppc_freq_invariance, work); cpu_data = cppc_fi->cpu_data; - if (cppc_get_perf_ctrs(cppc_fi->cpu, &fb_ctrs)) { + ret = cppc_get_perf_ctrs(cppc_fi->cpu, &fb_ctrs); + /* + * Perf counters could be 0 if the cpu is in a low-power idle state. + * Just try it again next time. + */ + if (ret == -EFAULT) + return; + + if (ret) { pr_warn("%s: failed to read perf counters\n", __func__); + topology_clear_scale_freq_source(SCALE_FREQ_SOURCE_CPPC, + cpu_data->shared_cpu_map); return; } From 41729f92b8ae900e1d0bc2c787c80e97c4f6c69a Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Thu, 21 Aug 2025 15:15:57 +0800 Subject: [PATCH 008/107] cpufreq: cppc: Fix invalid return value in .get() callback [Upstream commit 2b8e6b58889c672e1ae3601d9b2b070be4dc2fbc] Returning a negative error code in a function with an unsigned return type is a pretty bad idea. It is probably worse when the justification for the change is "our static analisys tool found it". Fixes: cf7de25878a1 ("cppc_cpufreq: Fix possible null pointer dereference") Signed-off-by: Marc Zyngier Cc: "Rafael J. Wysocki" Cc: Viresh Kumar Reviewed-by: Lifeng Zheng Signed-off-by: Viresh Kumar Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index a5b7a4c0fc6bd..aa671abe6abdf 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -893,7 +893,7 @@ static unsigned int hisi_cppc_cpufreq_get_rate(unsigned int cpu) int ret; if (!policy) - return -ENODEV; + return 0; cpu_data = policy->driver_data; From dbdb317681194f1c1364e77cf553c91cfdf766c0 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:15:58 +0800 Subject: [PATCH 009/107] cpufreq: cppc: Fix invalid return value in hisi_cppc_cpufreq_get_rate() commit d7c560f56e528fbb009f5f2b70cc813aad66661d openEuler Returning a negative error code in a function with an unsigned return type is a pretty bad idea. Return 0 is enough when something wrong. Fixes: f84b9b25d045 ("cppc_cpufreq: Fix possible null pointer dereference") Signed-off-by: Lifeng Zheng Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index aa671abe6abdf..e266136e31b02 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -901,7 +901,7 @@ static unsigned int hisi_cppc_cpufreq_get_rate(unsigned int cpu) ret = cppc_get_desired_perf(cpu, &desired_perf); if (ret < 0) - return -EIO; + return 0; return cppc_perf_to_khz(&cpu_data->perf_caps, desired_perf); } From 3e99fd9ea03295a253150092a0b0ebf725750553 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:15:59 +0800 Subject: [PATCH 010/107] cpufreq: CPPC: Remove forward declaration of hisi_cppc_cpufreq_get_rate() commit 5c5c2aac07c1202962821d54c8bebd5c8418d22e openEuler After commit ae2df912d1a5 ("ACPI: CPPC: Disable FIE if registers in PCC regions"), the only place uses hisi_cppc_cpufreq_get_rate() is cppc_check_hisi_workaround(), which is after the implementation of hisi_cppc_cpufreq_get_rate(). A forward declaration of hisi_cppc_cpufreq_get_rate() is unnecessarily. Fixes: ae2df912d1a5 ("ACPI: CPPC: Disable FIE if registers in PCC regions") Signed-off-by: Lifeng Zheng Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index e266136e31b02..7083b5aaf99f3 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -78,7 +78,6 @@ struct cppc_freq_invariance { static DEFINE_PER_CPU(struct cppc_freq_invariance, cppc_freq_inv); static struct kthread_worker *kworker_fie; -static unsigned int hisi_cppc_cpufreq_get_rate(unsigned int cpu); static int cppc_perf_from_fbctrs(struct cppc_cpudata *cpu_data, struct cppc_perf_fb_ctrs *fb_ctrs_t0, struct cppc_perf_fb_ctrs *fb_ctrs_t1); From 32dc4a9f80bdcb59521e5e4ad80c87b99cb392e9 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:00 +0800 Subject: [PATCH 011/107] cpufreq: CPPC: Remove cpu_data_list [Upstream commit d80a75624051b817043431f847470fb4680f2582] After commit a28b2bfc099c ("cppc_cpufreq: replace per-cpu data array with a list"), cpu_data can be got from policy->driver_data, so cpu_data_list is not actually needed and can be removed. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250526113057.3086513-2-zhenglifeng1@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 25 ------------------------- include/acpi/cppc_acpi.h | 1 - 2 files changed, 26 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 7083b5aaf99f3..ab97ebc344051 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -26,14 +26,6 @@ #include -/* - * This list contains information parsed from per CPU ACPI _CPC and _PSD - * structures: e.g. the highest and lowest supported performance, capabilities, - * desired performance, level requested etc. Depending on the share_type, not - * all CPUs will have an entry in the list. - */ -static LIST_HEAD(cpu_data_list); - static bool boost_supported; struct cppc_workaround_oem_info { @@ -611,8 +603,6 @@ static struct cppc_cpudata *cppc_cpufreq_get_cpu_data(unsigned int cpu) goto free_mask; } - list_add(&cpu_data->node, &cpu_data_list); - return cpu_data; free_mask: @@ -627,7 +617,6 @@ static void cppc_cpufreq_put_cpu_data(struct cpufreq_policy *policy) { struct cppc_cpudata *cpu_data = policy->driver_data; - list_del(&cpu_data->node); free_cpumask_var(cpu_data->shared_cpu_map); kfree(cpu_data); policy->driver_data = NULL; @@ -947,24 +936,10 @@ static int __init cppc_cpufreq_init(void) return ret; } -static inline void free_cpu_data(void) -{ - struct cppc_cpudata *iter, *tmp; - - list_for_each_entry_safe(iter, tmp, &cpu_data_list, node) { - free_cpumask_var(iter->shared_cpu_map); - list_del(&iter->node); - kfree(iter); - } - -} - static void __exit cppc_cpufreq_exit(void) { cpufreq_unregister_driver(&cppc_cpufreq_driver); cppc_freq_invariance_exit(); - - free_cpu_data(); } module_exit(cppc_cpufreq_exit); diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index a451ca4c207bb..301e432fb0605 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -130,7 +130,6 @@ struct cppc_perf_fb_ctrs { /* Per CPU container for runtime CPPC management. */ struct cppc_cpudata { - struct list_head node; struct cppc_perf_caps perf_caps; struct cppc_perf_ctrls perf_ctrls; struct cppc_perf_fb_ctrs perf_fb_ctrs; From 266a317eec5314e4129e90094de5c67198b2b1ed Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:01 +0800 Subject: [PATCH 012/107] cpufreq: CPPC: Do not return a value from populate_efficiency_class() [Upstream commit 3d5978ea6cbc4df192d0ea1800ef5d55b28b965e] The return value of populate_efficiency_class() is never needed and the result of it doesn't affect the initialization of cppc_cpufreq. It makes more sense to change it into a void function. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250526113057.3086513-3-zhenglifeng1@huawei.com [ rjw: Subject and changelog edits ] Signed-off-by: Rafael J. Wysocki Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index ab97ebc344051..aeea18d3cb4a5 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -524,7 +524,7 @@ static int cppc_get_cpu_cost(struct device *cpu_dev, unsigned long KHz, return 0; } -static int populate_efficiency_class(void) +static void populate_efficiency_class(void) { struct acpi_madt_generic_interrupt *gicc; DECLARE_BITMAP(used_classes, 256) = {}; @@ -539,7 +539,7 @@ static int populate_efficiency_class(void) if (bitmap_weight(used_classes, 256) <= 1) { pr_debug("Efficiency classes are all equal (=%d). " "No EM registered", class); - return -EINVAL; + return; } /* @@ -556,8 +556,6 @@ static int populate_efficiency_class(void) index++; } cppc_cpufreq_driver.register_em = cppc_cpufreq_register_em; - - return 0; } static void cppc_cpufreq_register_em(struct cpufreq_policy *policy) @@ -573,9 +571,8 @@ static void cppc_cpufreq_register_em(struct cpufreq_policy *policy) } #else -static int populate_efficiency_class(void) +static void populate_efficiency_class(void) { - return 0; } #endif From 5547c88482a71b3c0aa393680d69f52c94093b82 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:02 +0800 Subject: [PATCH 013/107] cpufreq: CPPC: Remove forward declaration of cppc_cpufreq_register_em() [Upstream commit c83a92df2fc60bf0b3130cdf0bc2104d61750317] cppc_cpufreq_register_em() is only used in populate_efficiency_class(). A forward declaration of it is not necessary. Move cppc_cpufreq_register_em() in front of populate_efficiency_class() and remove the forward declaration of cppc_cpufreq_register_em(). No functional change. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250526113057.3086513-4-zhenglifeng1@huawei.com [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cppc_cpufreq.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index aeea18d3cb4a5..d58a4ae31b7c2 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -388,7 +388,6 @@ static unsigned int cppc_cpufreq_get_transition_delay_us(unsigned int cpu) #if defined(CONFIG_ARM64) && defined(CONFIG_ENERGY_MODEL) static DEFINE_PER_CPU(unsigned int, efficiency_class); -static void cppc_cpufreq_register_em(struct cpufreq_policy *policy); /* Create an artificial performance state every CPPC_EM_CAP_STEP capacity unit. */ #define CPPC_EM_CAP_STEP (20) @@ -524,6 +523,18 @@ static int cppc_get_cpu_cost(struct device *cpu_dev, unsigned long KHz, return 0; } +static void cppc_cpufreq_register_em(struct cpufreq_policy *policy) +{ + struct cppc_cpudata *cpu_data; + struct em_data_callback em_cb = + EM_ADV_DATA_CB(cppc_get_cpu_power, cppc_get_cpu_cost); + + cpu_data = policy->driver_data; + em_dev_register_perf_domain(get_cpu_device(policy->cpu), + get_perf_level_count(policy), &em_cb, + cpu_data->shared_cpu_map, 0); +} + static void populate_efficiency_class(void) { struct acpi_madt_generic_interrupt *gicc; @@ -558,18 +569,6 @@ static void populate_efficiency_class(void) cppc_cpufreq_driver.register_em = cppc_cpufreq_register_em; } -static void cppc_cpufreq_register_em(struct cpufreq_policy *policy) -{ - struct cppc_cpudata *cpu_data; - struct em_data_callback em_cb = - EM_ADV_DATA_CB(cppc_get_cpu_power, cppc_get_cpu_cost); - - cpu_data = policy->driver_data; - em_dev_register_perf_domain(get_cpu_device(policy->cpu), - get_perf_level_count(policy), &em_cb, - cpu_data->shared_cpu_map, 0); -} - #else static void populate_efficiency_class(void) { From 017ad0dcea058994332266fb9ffcba948faf8a33 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:03 +0800 Subject: [PATCH 014/107] cpufreq: Contain scaling_cur_freq.attr in cpufreq_attrs [Upstream commit 2e554cfa259fe07085a4fcff7d2ec4b7041bbd9c] After commit c034b02e213d ("cpufreq: expose scaling_cur_freq sysfs file for set_policy() drivers"), the file scaling_cur_freq is exposed to all drivers. No need to create this file separately. It's better to be contained in cpufreq_attrs. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250623133402.3120230-4-zhenglifeng1@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 55608a0763d55..63d88194870a6 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -991,6 +991,7 @@ static struct attribute *cpufreq_attrs[] = { &cpuinfo_min_freq.attr, &cpuinfo_max_freq.attr, &cpuinfo_transition_latency.attr, + &scaling_cur_freq.attr, &scaling_min_freq.attr, &scaling_max_freq.attr, &affected_cpus.attr, @@ -1100,10 +1101,6 @@ static int cpufreq_add_dev_interface(struct cpufreq_policy *policy) return ret; } - ret = sysfs_create_file(&policy->kobj, &scaling_cur_freq.attr); - if (ret) - return ret; - if (cpufreq_driver->bios_limit) { ret = sysfs_create_file(&policy->kobj, &bios_limit.attr); if (ret) From 86cd42a63f137dbdff7e79358c097c1a2e6f8ae5 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:04 +0800 Subject: [PATCH 015/107] cpufreq: Remove duplicate check in __cpufreq_offline() [Upstream commit 5d6ecaaa922611ec3ca067723ccefafb543010ee] The has_target() checks in __cpufreq_offline() are duplicate. Remove one of them and put the operations of exiting governor together with storing last governor's name. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250623133402.3120230-5-zhenglifeng1@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 63d88194870a6..301be4cb8fad8 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1683,14 +1683,13 @@ static void __cpufreq_offline(unsigned int cpu, struct cpufreq_policy *policy) return; } - if (has_target()) + if (has_target()) { strncpy(policy->last_governor, policy->governor->name, CPUFREQ_NAME_LEN); - else - policy->last_policy = policy->policy; - - if (has_target()) cpufreq_exit_governor(policy); + } else { + policy->last_policy = policy->policy; + } /* * Perform the ->offline() during light-weight tear-down, as From 32c40aee8f76cdb8b2ca5ee65f0bfd9c9223e1f4 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:05 +0800 Subject: [PATCH 016/107] cpufreq: Hold cpufreq_driver_lock when assigning cpufreq_driver->set_boost commit efc1ef3222b0c34a14395f84330fa890cfd4ec3f openEuler Hold the lock to avoid concurrency problems in cpufreq_enable_boost_support() when assigning cpufreq_driver->set_boost. Fixes: 7a6c79f2fe53 ("cpufreq: Simplify core code related to boost support") Signed-off-by: Lifeng Zheng Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 301be4cb8fad8..91170a1cf6b6c 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2863,13 +2863,17 @@ static void remove_boost_sysfs_file(void) int cpufreq_enable_boost_support(void) { + unsigned long flags; + if (!cpufreq_driver) return -EINVAL; if (cpufreq_boost_supported()) return 0; + write_lock_irqsave(&cpufreq_driver_lock, flags); cpufreq_driver->set_boost = cpufreq_boost_set_sw; + write_unlock_irqrestore(&cpufreq_driver_lock, flags); /* This will get removed on driver unregister */ return create_boost_sysfs_file(); From 048e4e678af3c7ee3fc8dd9a90b67a76592eeb73 Mon Sep 17 00:00:00 2001 From: Lifeng Zheng Date: Thu, 21 Aug 2025 15:16:08 +0800 Subject: [PATCH 017/107] cpufreq: Move the check of cpufreq_driver->get into cpufreq_verify_current_freq() [Upstream commit 908981d85f86c5e2b39dfe0b2267c6d44d9c48f7] Move the check of cpufreq_driver->get into cpufreq_verify_current_freq() in case of calling it without check. Signed-off-by: Lifeng Zheng Link: https://patch.msgid.link/20250709104145.2348017-4-zhenglifeng1@huawei.com Signed-off-by: Rafael J. Wysocki Signed-off-by: Hongye Lin Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/cpufreq/cpufreq.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 91170a1cf6b6c..3151929c62393 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1796,6 +1796,9 @@ static unsigned int cpufreq_verify_current_freq(struct cpufreq_policy *policy, b { unsigned int new_freq; + if (!cpufreq_driver->get) + return 0; + new_freq = cpufreq_driver->get(policy->cpu); if (!new_freq) return 0; @@ -1920,8 +1923,7 @@ unsigned int cpufreq_get(unsigned int cpu) if (policy) { down_read(&policy->rwsem); - if (cpufreq_driver->get) - ret_freq = __cpufreq_get(policy); + ret_freq = __cpufreq_get(policy); up_read(&policy->rwsem); cpufreq_cpu_put(policy); @@ -2487,8 +2489,7 @@ int cpufreq_start_governor(struct cpufreq_policy *policy) pr_debug("%s: for CPU %u\n", __func__, policy->cpu); - if (cpufreq_driver->get) - cpufreq_verify_current_freq(policy, false); + cpufreq_verify_current_freq(policy, false); if (policy->governor->start) { ret = policy->governor->start(policy); From 071d413b6f44e81b69ba704df9ccbbfd46edd93b Mon Sep 17 00:00:00 2001 From: hepeng Date: Tue, 3 Dec 2024 18:02:35 +0800 Subject: [PATCH 018/107] ACPI: CPPC: Add New ABIs for reading and writing three new CPPC registers commit b718dd523687c682c11f3aa590780c277a8a90d9 openEuler cppc_set_epp - write energy performance preference register cppc_get_auto_act_window - read autonomous activity window register cppc_set_auto_act_window - write autonomous activity window register cppc_get_auto_sel - read autonomous selection enable register Signed-off-by: hepeng Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/acpi/cppc_acpi.c | 151 +++++++++++++++++++++++++++++++++++++-- include/acpi/cppc_acpi.h | 21 ++++++ 2 files changed, 168 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 9a9c896bef6ea..ece9371ceaba5 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1213,6 +1213,11 @@ static int cppc_get_perf(int cpunum, enum cppc_regs reg_idx, u64 *perf) return ret; } + if (!CPC_SUPPORTED(reg)) { + pr_debug("CPC register %d is not supported\n", reg_idx); + return -EOPNOTSUPP; + } + cpc_read(cpunum, reg, perf); return 0; @@ -1538,6 +1543,139 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) } EXPORT_SYMBOL_GPL(cppc_set_epp_perf); +static int cppc_get_reg(int cpunum, enum cppc_regs reg_idx, u64 *val) +{ + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); + struct cppc_pcc_data *pcc_ss_data = NULL; + struct cpc_register_resource *reg; + int pcc_ss_id; + int ret = 0; + + if (!cpc_desc) { + pr_debug("No CPC descriptor for CPU:%d\n", cpunum); + return -ENODEV; + } + + reg = &cpc_desc->cpc_regs[reg_idx]; + + if (!CPC_SUPPORTED(reg)) { + pr_debug("CPC register (reg_idx=%u) is not supported\n", reg_idx); + return -EOPNOTSUPP; + } + + if (CPC_IN_PCC(reg)) { + pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); + + if (pcc_ss_id < 0) + return -ENODEV; + + pcc_ss_data = pcc_data[pcc_ss_id]; + + down_write(&pcc_ss_data->pcc_lock); + + if (send_pcc_cmd(pcc_ss_id, CMD_READ) >= 0) + cpc_read(cpunum, reg, val); + else + ret = -EIO; + + up_write(&pcc_ss_data->pcc_lock); + + return ret; + } + + cpc_read(cpunum, reg, val); + + return 0; +} + +static int cppc_set_reg(int cpu, enum cppc_regs reg_idx, u64 val) +{ + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + struct cppc_pcc_data *pcc_ss_data = NULL; + struct cpc_register_resource *reg; + int pcc_ss_id; + int ret; + + if (!cpc_desc) { + pr_debug("No CPC descriptor for CPU:%d\n", cpu); + return -ENODEV; + } + + reg = &cpc_desc->cpc_regs[reg_idx]; + + if (!CPC_SUPPORTED(reg)) { + pr_debug("CPC register (reg_idx=%u) is not supported\n", reg_idx); + return -EOPNOTSUPP; + } + + if (CPC_IN_PCC(reg)) { + pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); + + if (pcc_ss_id < 0) { + pr_debug("Invalid pcc_ss_id\n"); + return -ENODEV; + } + + ret = cpc_write(cpu, reg, val); + if (ret) + return ret; + + pcc_ss_data = pcc_data[pcc_ss_id]; + + down_write(&pcc_ss_data->pcc_lock); + /* after writing CPC, transfer the ownership of PCC to platform */ + ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); + up_write(&pcc_ss_data->pcc_lock); + return ret; + } + + return cpc_write(cpu, reg, val); +} + +/** + * cppc_set_epp() - Write the EPP register + * @cpu:CPU on which to write register. + * @epp_val:Value to write to the EPP register. + */ +int cppc_set_epp(int cpu, u64 epp_val) +{ + return cppc_set_reg(cpu, ENERGY_PERF, epp_val); +} +EXPORT_SYMBOL_GPL(cppc_set_epp); + +/** + * cppc_get_auto_act_window() - Read autonomous activity window register. + * @cpunum:CPU from which to read register. + * @auto_act_window:Return address. + */ +int cppc_get_auto_act_window(int cpunum, u64 *auto_act_window) +{ + return cppc_get_reg(cpunum, AUTO_ACT_WINDOW, auto_act_window); +} +EXPORT_SYMBOL_GPL(cppc_get_auto_act_window); + +/** + * cppc_set_auto_act_window() - Write autonomous activity window register. + * @cpu:CPU on which to write register. + * @auto_act_window:Value to write to the autonomous activity window register. + */ +int cppc_set_auto_act_window(int cpu, u64 auto_act_window) +{ + return cppc_set_reg(cpu, AUTO_ACT_WINDOW, auto_act_window); +} +EXPORT_SYMBOL_GPL(cppc_set_auto_act_window); + +/** + * cppc_get_auto_sel() - Read autonomous selection register. + * @cpunum:CPU from which to read register. + * @auto_act_window:Return address. + */ +int cppc_get_auto_sel(int cpunum, u64 *auto_sel) +{ + return cppc_get_reg(cpunum, AUTO_SEL_ENABLE, auto_sel); +} +EXPORT_SYMBOL_GPL(cppc_get_auto_sel); + /** * cppc_get_auto_sel_caps - Read autonomous selection register. * @cpunum : CPU from which to read register. @@ -1625,12 +1763,17 @@ int cppc_set_auto_sel(int cpu, bool enable) /* after writing CPC, transfer the ownership of PCC to platform */ ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); up_write(&pcc_ss_data->pcc_lock); - } else { - ret = -ENOTSUPP; - pr_debug("_CPC in PCC is not supported\n"); + + return ret; } - return ret; + if (!CPC_SUPPORTED(auto_sel_reg)) { + pr_debug("CPC register (reg_idx=%u) is not supported\n", + AUTO_SEL_ENABLE); + return -EOPNOTSUPP; + } + + return cpc_write(cpu, auto_sel_reg, enable); } EXPORT_SYMBOL_GPL(cppc_set_auto_sel); diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 301e432fb0605..7fbf9cae45c4a 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -160,6 +160,10 @@ extern int cppc_get_epp_perf(int cpunum, u64 *epp_perf); extern int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable); extern int cppc_get_auto_sel_caps(int cpunum, struct cppc_perf_caps *perf_caps); extern int cppc_set_auto_sel(int cpu, bool enable); +extern int cppc_set_epp(int cpu, u64 epp_val); +extern int cppc_get_auto_act_window(int cpunum, u64 *auto_act_window); +extern int cppc_set_auto_act_window(int cpu, u64 auto_act_window); +extern int cppc_get_auto_sel(int cpunum, u64 *auto_sel); #else /* !CONFIG_ACPI_CPPC_LIB */ static inline int cppc_get_desired_perf(int cpunum, u64 *desired_perf) { @@ -233,6 +237,23 @@ static inline int cppc_get_auto_sel_caps(int cpunum, struct cppc_perf_caps *perf { return -ENOTSUPP; } +static inline int cppc_set_epp(int cpu, u64 epp_val) +{ + return -EOPNOTSUPP; +} +static inline int cppc_get_auto_act_window(int cpunum, u64 *auto_act_window) +{ + return -EOPNOTSUPP; +} +static inline int cppc_set_auto_act_window(int cpu, u64 auto_act_window) +{ + return -EOPNOTSUPP; +} +static inline int cppc_get_auto_sel(int cpunum, u64 *auto_sel) +{ + return -EOPNOTSUPP; +} + #endif /* !CONFIG_ACPI_CPPC_LIB */ #endif /* _CPPC_ACPI_H*/ From cdb391be37d1bcf1ac247eac7f388b55b58ae3e5 Mon Sep 17 00:00:00 2001 From: hepeng Date: Tue, 3 Dec 2024 19:14:35 +0800 Subject: [PATCH 019/107] cpufreq: CPPC: Support for auto act window and energy perf in cppc_cpufreq commit c4ba198c5c002c06a6bd49c5a5520c01fef890b5 openEuler Add sysfs interfaces for CPPC auto act window and energy perf in the cppc_cpufreq driver. Signed-off-by: hepeng Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- .../ABI/testing/sysfs-devices-system-cpu | 39 +++++++ drivers/cpufreq/Kconfig.arm | 11 ++ drivers/cpufreq/cppc_cpufreq.c | 108 ++++++++++++++++++ 3 files changed, 158 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index cfa393c51f4d6..ea1d2dff61af8 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -268,6 +268,45 @@ Description: Discover CPUs in the same CPU frequency coordination domain This file is only present if the acpi-cpufreq or the cppc-cpufreq drivers are in use. +What: /sys/devices/system/cpu/cpuX/cpufreq/auto_act_window +Date: October 2024 +Contact: linux-pm@vger.kernel.org +Description: Autonomous activity window + + This file indicates a moving utilization sensitivity window to + the platform's autonomous selection policy. + + Read/write an integer represents autonomous activity window (in + microseconds) from/to this file. The max value to write is + 1270000000 but the max significand is 127. This means that if 128 + is written to this file, 127 will be stored. If the value is + greater than 130, only the first two digits will be saved as + significand. + + Writing a zero value to this file enable the platform to + determine an appropriate Activity Window depending on the workload. + + Writing to this file only has meaning when Autonomous Selection is + enabled. + + This file only presents if the cppc-cpufreq driver is in use. + +What: /sys/devices/system/cpu/cpuX/cpufreq/energy_perf +Date: October 2024 +Contact: linux-pm@vger.kernel.org +Description: Energy performance preference + + Read/write an 8-bit integer from/to this file. This file + represents a range of values from 0 (performance preference) to + 0xFF (energy efficiency preference) that influences the rate of + performance increase/decrease and the result of the hardware's + energy efficiency and performance optimization policies. + + Writing to this file only has meaning when Autonomous Selection is + enabled. + + This file only presents if the cppc-cpufreq driver is in use. + What: /sys/devices/system/cpu/cpu*/cache/index3/cache_disable_{0,1} Date: August 2008 diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index c5cecbd89ba9c..6265c91fbf6bf 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -19,6 +19,17 @@ config ACPI_CPPC_CPUFREQ If in doubt, say N. +config CPPC_CPUFREQ_SYSFS_INTERFACE + bool "Enable CPPC CPUFreq sysfs tuning interfaces" + depends on ACPI_CPPC_CPUFREQ && ARM64 + default n + help + This enables sysfs interfaces for CPPC CPUFreq driver including + auto_act_window and energy_perf. These interfaces allow users + to tune CPPC parameters at runtime. + + If unsure, say N. + config ACPI_CPPC_CPUFREQ_FIE bool "Frequency Invariance support for CPPC cpufreq driver" depends on ACPI_CPPC_CPUFREQ && GENERIC_ARCH_TOPOLOGY diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index d58a4ae31b7c2..214fd69a49435 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -843,10 +843,118 @@ static ssize_t show_freqdomain_cpus(struct cpufreq_policy *policy, char *buf) return cpufreq_show_cpus(cpu_data->shared_cpu_map, buf); } + +#ifdef CONFIG_CPPC_CPUFREQ_SYSFS_INTERFACE + +#define AUTO_ACT_WINDOW_SIG_BIT_SIZE (7) +#define AUTO_ACT_WINDOW_EXP_BIT_SIZE (3) +#define AUTO_ACT_WINDOW_MAX_SIG ((1 << AUTO_ACT_WINDOW_SIG_BIT_SIZE) - 1) +#define AUTO_ACT_WINDOW_MAX_EXP ((1 << AUTO_ACT_WINDOW_EXP_BIT_SIZE) - 1) +/* AUTO_ACT_WINDOW_MAX_SIG is 127, so 128 and 129 will decay to 127 when writing */ +#define AUTO_ACT_WINDOW_SIG_CARRY_THRESH 129 + +static ssize_t show_auto_act_window(struct cpufreq_policy *policy, char *buf) +{ + unsigned int exp; + u64 val, sig; + int ret; + + ret = cppc_get_auto_act_window(policy->cpu, &val); + + /* show "" when this register is not supported by cpc */ + if (ret == -EOPNOTSUPP) + return sysfs_emit(buf, "%s\n", ""); + + if (ret) + return ret; + + sig = val & AUTO_ACT_WINDOW_MAX_SIG; + exp = (val >> AUTO_ACT_WINDOW_SIG_BIT_SIZE) & AUTO_ACT_WINDOW_MAX_EXP; + + return sysfs_emit(buf, "%llu\n", sig * int_pow(10, exp)); +} + +static ssize_t store_auto_act_window(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned long usec; + int digits = 0; + int ret; + + ret = kstrtoul(buf, 0, &usec); + if (ret) + return ret; + + if (usec > AUTO_ACT_WINDOW_MAX_SIG * int_pow(10, AUTO_ACT_WINDOW_MAX_EXP)) + return -EINVAL; + + while (usec > AUTO_ACT_WINDOW_SIG_CARRY_THRESH) { + usec /= 10; + digits += 1; + } + + if (usec > AUTO_ACT_WINDOW_MAX_SIG) + usec = AUTO_ACT_WINDOW_MAX_SIG; + + ret = cppc_set_auto_act_window(policy->cpu, + (digits << AUTO_ACT_WINDOW_SIG_BIT_SIZE) + usec); + if (ret) + return ret; + + return count; +} + +static ssize_t show_energy_perf(struct cpufreq_policy *policy, char *buf) +{ + u64 val; + int ret; + + ret = cppc_get_epp_perf(policy->cpu, &val); + + /* show "" when this register is not supported by cpc */ + if (ret == -EOPNOTSUPP) + return sysfs_emit(buf, "%s\n", ""); + + if (ret) + return ret; + + return sysfs_emit(buf, "%lld\n", val); +} + +#define ENERGY_PERF_MAX (0xFF) + +static ssize_t store_energy_perf(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + unsigned long val; + int ret; + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + + if (val > ENERGY_PERF_MAX) + return -EINVAL; + + ret = cppc_set_epp(policy->cpu, val); + if (ret) + return ret; + + return count; +} + +cpufreq_freq_attr_rw(auto_act_window); +cpufreq_freq_attr_rw(energy_perf); +#endif // CONFIG_CPPC_CPUFREQ_SYSFS_INTERFACE + cpufreq_freq_attr_ro(freqdomain_cpus); static struct freq_attr *cppc_cpufreq_attr[] = { &freqdomain_cpus, +#ifdef CONFIG_CPPC_CPUFREQ_SYSFS_INTERFACE + &auto_act_window, + &energy_perf, +#endif // CONFIG_CPPC_CPUFREQ_SYSFS_INTERFACE NULL, }; From 417baf02070add67ef721c09003389bb9a767bcd Mon Sep 17 00:00:00 2001 From: hepeng Date: Wed, 4 Dec 2024 10:48:10 +0800 Subject: [PATCH 020/107] cpufreq: Add SEEP governor for hardware-managed P-states commit 4e05c2f4ecf5a9116751b941c885f6d516860529 openEuler Add a new CPUFreq governor 'seep' designed for platforms with hardware-managed P-states through CPPC (Collaborative Processor Performance Control). This governor enables the hardware's autonomous frequency selection capability, allowing the processor to manage its own frequency based on workload characteristics. The SEEP governor requires: - cppc_cpufreq driver - Platform support for CPPC features: * Autonomous selection (auto_sel) * Autonomous activity window (auto_act_window) * Energy Performance Preference (epp) Two per-policy sysfs interfaces are provided: - auto_act_window: Control the hardware's frequency scaling window - energy_perf: Bias between performance and energy efficiency Signed-off-by: hepeng Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- Documentation/admin-guide/pm/cpufreq.rst | 42 +++++++++ drivers/cpufreq/Kconfig | 20 +++++ drivers/cpufreq/Makefile | 1 + drivers/cpufreq/cpufreq_seep.c | 109 +++++++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 drivers/cpufreq/cpufreq_seep.c diff --git a/Documentation/admin-guide/pm/cpufreq.rst b/Documentation/admin-guide/pm/cpufreq.rst index 6adb7988e0eb6..dcaa8873154e9 100644 --- a/Documentation/admin-guide/pm/cpufreq.rst +++ b/Documentation/admin-guide/pm/cpufreq.rst @@ -590,6 +590,48 @@ This governor exposes the following tunables: It effectively causes the frequency to go down ``sampling_down_factor`` times slower than it ramps up. +``seep`` +------------ + +This governor is specifically designed for platforms with hardware-managed P-states +through CPPC (Collaborative Processor Performance Control). Unlike other governors +that implement their own frequency scaling algorithms, the ``seep`` governor +delegates the P-state selection to the hardware/firmware by enabling CPPC +autonomous mode. + +The governor requires the ``cppc_cpufreq`` driver and the platform must support +three key CPPC capabilities: + * Autonomous selection (auto_sel) + * Autonomous activity window (auto_act_window) + * Energy-Performance Preference (EPP) + +When this governor is attached to a policy, it enables the hardware autonomous +mode, allowing the processor's firmware to directly manage frequency scaling based +on workload characteristics. The actual P-state selection algorithms are +implemented in firmware/hardware rather than in the operating system. + +This governor exposes the following tunables (per-policy): + +``auto_act_window`` + The time window (in microseconds) used by the hardware/firmware to observe + and evaluate workload characteristics before making P-state decisions. + A smaller window makes the algorithm more responsive but potentially less + stable, while a larger window provides more stable but less responsive + frequency scaling. + +``energy_perf`` + A value between 0 and 255 that biases the hardware's frequency scaling + decisions between performance and energy efficiency. Lower values (closer + to 0) favor performance at the expense of power consumption, while higher + values (closer to 255) favor energy efficiency but may impact performance. + +The ``seep`` governor is particularly suitable for platforms where the hardware +has more accurate and fine-grained information about the system's power and +thermal constraints than the operating system. It can potentially provide better +energy efficiency than software-based governors while maintaining good +performance, as the hardware can make faster and more informed P-state +decisions. + Frequency Boost Support ======================= diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 793f279cf7667..16bc38371504f 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -203,6 +203,26 @@ config CPU_FREQ_GOV_SCHEDUTIL If in doubt, say N. +config CPU_FREQ_GOV_SEEP + tristate "'seep' cpufreq policy governor" + depends on ACPI_CPPC_CPUFREQ && ARM64 + select CPPC_CPUFREQ_SYSFS_INTERFACE + select CPU_FREQ_GOV_COMMON + help + 'seep' - This governor is designed for platforms with hardware-managed + P-states through CPPC (Collaborative Processor Performance Control). + It enables the hardware's autonomous frequency selection capability, + allowing the processor to manage its own frequency based on workload. + + This governor requires the cppc_cpufreq driver and platform support + for CPPC features including auto-selection, autonomous activity window, + and energy-performance preference. + + To compile this driver as a module, choose M here: the + module will be called cpufreq_seep. + + If in doubt, say N. + comment "CPU frequency scaling drivers" config CPUFREQ_DT diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 92461b5618f71..328631bcf3436 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_POWERSAVE) += cpufreq_powersave.o obj-$(CONFIG_CPU_FREQ_GOV_USERSPACE) += cpufreq_userspace.o obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o +obj-$(CONFIG_CPU_FREQ_GOV_SEEP) += cpufreq_seep.o obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o obj-$(CONFIG_CPU_FREQ_GOV_ATTR_SET) += cpufreq_governor_attr_set.o diff --git a/drivers/cpufreq/cpufreq_seep.c b/drivers/cpufreq/cpufreq_seep.c new file mode 100644 index 0000000000000..15a33ddecc430 --- /dev/null +++ b/drivers/cpufreq/cpufreq_seep.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/cpufreq/cpufreq_seep.c + * + * Copyright (C) 2024 heppen + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +/* + * Check if the platform supports required CPPC features for SEEP governor: + * - auto_sel: Hardware-managed P-state selection + * - auto_act_window: Autonomous activity window + * - epp: Energy Performance Preference + */ +static bool seep_supported(void) +{ + const char *driver; + u64 val; + int ret; + + driver = cpufreq_get_current_driver(); + if (!driver || strcmp(driver, "cppc_cpufreq")) + return false; + + ret = cppc_get_auto_sel(0, &val); + if (ret) + return false; + + ret = cppc_get_auto_act_window(0, &val); + if (ret) + return false; + + ret = cppc_get_epp_perf(0, &val); + if (ret) + return false; + + return true; +} + +/* + * Start the SEEP governor for the given policy. + * Enable hardware-managed P-state selection. + */ +static int cpufreq_gov_seep_start(struct cpufreq_policy *policy) +{ + int ret; + + /* Enable BIOS frequency Scaling */ + ret = cppc_set_auto_sel(policy->cpu, 1); + if (ret) + pr_err("Failed to enable auto_sel: %d\n", ret); + return ret; +} + +/* + * Stop the SEEP governor for the given policy. + * Disable hardware-managed P-state selection. + */ +static void cpufreq_gov_seep_stop(struct cpufreq_policy *policy) +{ + int ret; + + /* Disable BIOS frequency Scaling */ + ret = cppc_set_auto_sel(policy->cpu, 0); + if (ret) + pr_err("Failed to disable auto_sel: %d\n", ret); +} + +static struct cpufreq_governor cpufreq_gov_seep = { + .name = "seep", + .start = cpufreq_gov_seep_start, + .stop = cpufreq_gov_seep_stop, + .owner = THIS_MODULE, +}; + +static int __init cpufreq_gov_seep_init(void) +{ + if (!seep_supported()) { + pr_err("SEEP governor requires cppc_cpufreq driver and CPPC feature support\n"); + return -ENODEV; + } + + return cpufreq_register_governor(&cpufreq_gov_seep); +} + +static void __exit cpufreq_gov_seep_exit(void) +{ + cpufreq_unregister_governor(&cpufreq_gov_seep); +} + +#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_SEEP +struct cpufreq_governor *cpufreq_default_governor(void) +{ + return &cpufreq_gov_seep; +} +#endif + +module_init(cpufreq_gov_seep_init); +module_exit(cpufreq_gov_seep_exit); + +MODULE_AUTHOR("heppen "); +MODULE_DESCRIPTION("CPUfreq policy governor 'seep'"); +MODULE_LICENSE("GPL"); From ac244ee44410e569d2ceb866a8e068226178a5c0 Mon Sep 17 00:00:00 2001 From: Jay Fang Date: Tue, 30 Jul 2024 09:16:39 +0800 Subject: [PATCH 021/107] PCI/ASPM: Update ASPM sysfs on MFD function removal to avoid use-after-free commit 951c5e26f86a2a342cbd533f4ae4a1545d8ff57e openEuler From 'commit 456d8aa37d0f ("PCI/ASPM: Disable ASPM on MFD function removal to avoid use-after-free")' we know that PCIe spec r6.0, sec 7.5.3.7, recommends that software program the same ASPM Control(pcie_link_state) value in all functions of multi-function devices, and free the pcie_link_state when any child function is removed. However, ASPM Control sysfs is still visible to other children even if it has been removed by any child function, and careless use it will trigger use-after-free error, e.g.: # lspci -tv -[0000:16]---00.0-[17]--+-00.0 Device 19e5:0222 \-00.1 Device 19e5:0222 # echo 1 > /sys/bus/pci/devices/0000:17:00.0/remove //pcie_link_state will be released # echo 1 > /sys/bus/pci/devices/0000:17:00.1/link/l1_aspm //will trigger error Unable to handle kernel NULL pointer dereference at virtual address 0000000000000030 Call trace: aspm_attr_store_common.constprop.0+0x10c/0x154 l1_aspm_store+0x24/0x30 dev_attr_store+0x20/0x34 sysfs_kf_write+0x4c/0x5c We can solve this problem by updating the ASPM Control sysfs of all children immediately after ASPM Control have been freed. Extra note: The Linux community plans to optimize to the pcie_link_state management code.For details,see the following link: https://patchwork.kernel.org/project/linux-pci/patch/ 20240730011639.2590846-1-f.fangjian@huawei.com/ Fixes: 456d8aa37d0f ("PCI/ASPM: Disable ASPM on MFD function removal to avoid use-after-free") Signed-off-by: Jay Fang Signed-off-by: lujunhua Signed-off-by: Slim6882 Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/pci/pcie/aspm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 2ee25e50a292a..413103249a5e4 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -993,6 +993,8 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev) } out: + pcie_aspm_update_sysfs_visibility(parent); + mutex_unlock(&aspm_lock); up_read(&pci_bus_sem); } From 7dca29aa54297d3c7d60390fc4678b5fc103a122 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Sat, 15 Feb 2025 17:26:12 +0800 Subject: [PATCH 022/107] hwtracing: hisi_ptt: Initialize the filter sysfs attribute when allocation commit 049045f499e1cc07a132c3ef010f15abce995e03 openEuler The filter sysfs attribute is initialized when register to the sysfs. This is unnecessary and could be done when allocation without distinguish the filter type. After the changes above, we don't need a wrapper for initializing and registering the filter's sysfs attributes. Remove them and call the sysfs creating/removing functions in place. Fixes: 6373c463ac89 ("hwtracing: hisi_ptt: Export available filters through sysfs") Signed-off-by: Yicong Yang Signed-off-by: lujunhua Signed-off-by: Slim6882 Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwtracing/ptt/hisi_ptt.c | 92 ++++++++++---------------------- 1 file changed, 29 insertions(+), 63 deletions(-) diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index 24a1f7797aeb6..64c1bfcaa9689 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -365,6 +365,19 @@ static void hisi_ptt_del_free_filter(struct hisi_ptt *hisi_ptt, kfree(filter); } +static ssize_t hisi_ptt_filter_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hisi_ptt_filter_desc *filter; + unsigned long filter_val; + + filter = container_of(attr, struct hisi_ptt_filter_desc, attr); + filter_val = hisi_ptt_get_filter_val(filter->devid, filter->is_port) | + (filter->is_port ? HISI_PTT_PMU_FILTER_IS_PORT : 0); + + return sysfs_emit(buf, "0x%05lx\n", filter_val); +} + static struct hisi_ptt_filter_desc * hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) { @@ -393,6 +406,11 @@ hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) filter->is_port = is_port; filter->devid = devid; + sysfs_attr_init(&filter->attr.attr); + filter->attr.attr.name = filter->name; + filter->attr.attr.mode = 0400; /* DEVICE_ATTR_ADMIN_RO */ + filter->attr.show = hisi_ptt_filter_show; + if (filter->is_port) { list_add_tail(&filter->list, &hisi_ptt->port_filters); @@ -405,74 +423,18 @@ hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) return filter; } -static ssize_t hisi_ptt_filter_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct hisi_ptt_filter_desc *filter; - unsigned long filter_val; - - filter = container_of(attr, struct hisi_ptt_filter_desc, attr); - filter_val = hisi_ptt_get_filter_val(filter->devid, filter->is_port) | - (filter->is_port ? HISI_PTT_PMU_FILTER_IS_PORT : 0); - - return sysfs_emit(buf, "0x%05lx\n", filter_val); -} - -static int hisi_ptt_create_rp_filter_attr(struct hisi_ptt *hisi_ptt, - struct hisi_ptt_filter_desc *filter) -{ - struct kobject *kobj = &hisi_ptt->hisi_ptt_pmu.dev->kobj; - - sysfs_attr_init(&filter->attr.attr); - filter->attr.attr.name = filter->name; - filter->attr.attr.mode = 0400; /* DEVICE_ATTR_ADMIN_RO */ - filter->attr.show = hisi_ptt_filter_show; - - return sysfs_add_file_to_group(kobj, &filter->attr.attr, - HISI_PTT_RP_FILTERS_GRP_NAME); -} - -static void hisi_ptt_remove_rp_filter_attr(struct hisi_ptt *hisi_ptt, - struct hisi_ptt_filter_desc *filter) -{ - struct kobject *kobj = &hisi_ptt->hisi_ptt_pmu.dev->kobj; - - sysfs_remove_file_from_group(kobj, &filter->attr.attr, - HISI_PTT_RP_FILTERS_GRP_NAME); -} - -static int hisi_ptt_create_req_filter_attr(struct hisi_ptt *hisi_ptt, - struct hisi_ptt_filter_desc *filter) -{ - struct kobject *kobj = &hisi_ptt->hisi_ptt_pmu.dev->kobj; - - sysfs_attr_init(&filter->attr.attr); - filter->attr.attr.name = filter->name; - filter->attr.attr.mode = 0400; /* DEVICE_ATTR_ADMIN_RO */ - filter->attr.show = hisi_ptt_filter_show; - - return sysfs_add_file_to_group(kobj, &filter->attr.attr, - HISI_PTT_REQ_FILTERS_GRP_NAME); -} - -static void hisi_ptt_remove_req_filter_attr(struct hisi_ptt *hisi_ptt, - struct hisi_ptt_filter_desc *filter) -{ - struct kobject *kobj = &hisi_ptt->hisi_ptt_pmu.dev->kobj; - - sysfs_remove_file_from_group(kobj, &filter->attr.attr, - HISI_PTT_REQ_FILTERS_GRP_NAME); -} - static int hisi_ptt_create_filter_attr(struct hisi_ptt *hisi_ptt, struct hisi_ptt_filter_desc *filter) { + struct kobject *kobj = &hisi_ptt->hisi_ptt_pmu.dev->kobj; int ret; if (filter->is_port) - ret = hisi_ptt_create_rp_filter_attr(hisi_ptt, filter); + ret = sysfs_add_file_to_group(kobj, &filter->attr.attr, + HISI_PTT_RP_FILTERS_GRP_NAME); else - ret = hisi_ptt_create_req_filter_attr(hisi_ptt, filter); + ret = sysfs_add_file_to_group(kobj, &filter->attr.attr, + HISI_PTT_REQ_FILTERS_GRP_NAME); if (ret) pci_err(hisi_ptt->pdev, "failed to create sysfs attribute for filter %s\n", @@ -484,10 +446,14 @@ static int hisi_ptt_create_filter_attr(struct hisi_ptt *hisi_ptt, static void hisi_ptt_remove_filter_attr(struct hisi_ptt *hisi_ptt, struct hisi_ptt_filter_desc *filter) { + struct kobject *kobj = &hisi_ptt->hisi_ptt_pmu.dev->kobj; + if (filter->is_port) - hisi_ptt_remove_rp_filter_attr(hisi_ptt, filter); + sysfs_remove_file_from_group(kobj, &filter->attr.attr, + HISI_PTT_RP_FILTERS_GRP_NAME); else - hisi_ptt_remove_req_filter_attr(hisi_ptt, filter); + sysfs_remove_file_from_group(kobj, &filter->attr.attr, + HISI_PTT_REQ_FILTERS_GRP_NAME); } static void hisi_ptt_remove_all_filter_attributes(void *data) From 390f140d5b7323f79c72bf13c50de0d03c495881 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Mon, 24 Feb 2025 22:03:12 +0800 Subject: [PATCH 023/107] hwtracing: hisi_ptt: Check duplicate filters before allocation commit 8945c86d86ee01a8198a9c04cb8082d284622f50 openEuler Abnormally it's reported duplicated filters added in the filter list, though the filter we created should be unique maintained by the PCIe framework. Add a duplicate filters check before allocation to prevent this case. Fixes: 6373c463ac89 ("hwtracing: hisi_ptt: Export available filters through sysfs") Signed-off-by: Yicong Yang Signed-off-by: lujunhua Signed-off-by: Slim6882 Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwtracing/ptt/hisi_ptt.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index 64c1bfcaa9689..cea30c22ccb64 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -378,6 +378,32 @@ static ssize_t hisi_ptt_filter_show(struct device *dev, struct device_attribute return sysfs_emit(buf, "0x%05lx\n", filter_val); } +static int hisi_ptt_check_duplicated_filters(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) +{ + struct hisi_ptt_filter_desc *filter; + struct list_head *target_list; + u8 devfn = devid & 0xff; + + if (is_port) + target_list = &hisi_ptt->port_filters; + else + target_list = &hisi_ptt->req_filters; + + list_for_each_entry(filter, target_list, list) { + if (filter->devid == devid) { + pci_warn(hisi_ptt->pdev, + "ignore duplicated filter %04x:%02x:%02x.%d\n", + pci_domain_nr(hisi_ptt->pdev->bus), + PCI_BUS_NUM(devid), PCI_SLOT(devfn), + PCI_FUNC(devfn)); + + return -EEXIST; + } + } + + return 0; +} + static struct hisi_ptt_filter_desc * hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) { @@ -385,6 +411,9 @@ hisi_ptt_alloc_add_filter(struct hisi_ptt *hisi_ptt, u16 devid, bool is_port) u8 devfn = devid & 0xff; char *filter_name; + if (hisi_ptt_check_duplicated_filters(hisi_ptt, devid, is_port)) + return NULL; + filter_name = kasprintf(GFP_KERNEL, "%04x:%02x:%02x.%d", pci_domain_nr(hisi_ptt->pdev->bus), PCI_BUS_NUM(devid), PCI_SLOT(devfn), PCI_FUNC(devfn)); if (!filter_name) { From 0c214ca41ab0921da232eeafe5437e89daeef52c Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 10 Oct 2023 16:47:27 +0800 Subject: [PATCH 024/107] hwtracing: hisi_ptt: Disable interrupt after trace end [Upstream commit 46f69b197b6cd06c709581f7ad271bc02dbedb7a] On trace end we disable the hardware but leave the interrupt unmasked. Mask the interrupt to make the process reverse to the start. No actual issue since hardware should send no interrupt after disabled. Signed-off-by: Yicong Yang Acked-by: Jonathan Cameron Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20231010084731.30450-2-yangyicong@huawei.com Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwtracing/ptt/hisi_ptt.c | 4 ++++ drivers/hwtracing/ptt/hisi_ptt.h | 1 + 2 files changed, 5 insertions(+) diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index cea30c22ccb64..a63394e755b3a 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -183,6 +183,10 @@ static void hisi_ptt_wait_dma_reset_done(struct hisi_ptt *hisi_ptt) static void hisi_ptt_trace_end(struct hisi_ptt *hisi_ptt) { writel(0, hisi_ptt->iobase + HISI_PTT_TRACE_CTRL); + + /* Mask the interrupt on the end */ + writel(HISI_PTT_TRACE_INT_MASK_ALL, hisi_ptt->iobase + HISI_PTT_TRACE_INT_MASK); + hisi_ptt->trace_ctrl.started = false; } diff --git a/drivers/hwtracing/ptt/hisi_ptt.h b/drivers/hwtracing/ptt/hisi_ptt.h index e17f045d7e725..46030aa880811 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.h +++ b/drivers/hwtracing/ptt/hisi_ptt.h @@ -47,6 +47,7 @@ #define HISI_PTT_TRACE_INT_STAT 0x0890 #define HISI_PTT_TRACE_INT_STAT_MASK GENMASK(3, 0) #define HISI_PTT_TRACE_INT_MASK 0x0894 +#define HISI_PTT_TRACE_INT_MASK_ALL GENMASK(3, 0) #define HISI_PTT_TUNING_INT_STAT 0x0898 #define HISI_PTT_TUNING_INT_STAT_MASK BIT(0) #define HISI_PTT_TRACE_WR_STS 0x08a0 From 4c88cba731eec8669fc8be956783aabcd97501d6 Mon Sep 17 00:00:00 2001 From: Smita Koralahalli Date: Wed, 7 Feb 2024 18:18:54 +0000 Subject: [PATCH 025/107] PCI/DPC: Ignore Surprise Down error on hot removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Upstream commit 2ae8fbbe1cd42a6624d345151859982cba89bbd3] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to PCIe r6.0 sec 6.7.6 [1], async removal with DPC may result in surprise down error. This error is expected and is just a side-effect of async remove. Ignore surprise down error generated as a side-effect of async remove. Typically, this error is benign as the pciehp handler invoked by PDC or/and DLLSC alongside DPC, de-enumerates and brings down the device appropriately, but the error messages might confuse users. Get rid of these irritating log messages with a 1s delay while pciehp waits for DPC recovery. The implementation is as follows: On an async remove a DPC is triggered along with a Presence Detect State change and/or DLL State Change. Determine it's an async remove by checking for DPC Trigger Status in DPC Status Register and Surprise Down Error Status in AER Uncorrected Error Status to be non-zero. If true, treat the DPC event as a side-effect of async remove, clear the error status registers and continue with hot-plug tear down routines. If not, follow the existing routine to handle AER and DPC errors. Masking Surprise Down Errors was explored as an alternative approach, but discarded due to the odd behavior that masking only avoids the interrupt, but still records an error per PCIe r6.0, sec 6.2.3.2.2. That stale error would be reported the next time some error other than Surprise Down is handled. Dmesg before: pcieport 0000:00:01.4: DPC: containment event, status:0x1f01 source:0x0000 pcieport 0000:00:01.4: DPC: unmasked uncorrectable error detected pcieport 0000:00:01.4: PCIe Bus Error: severity=Uncorrected (Fatal), type=Transaction Layer, (Receiver ID) pcieport 0000:00:01.4: device [1022:14ab] error status/mask=00000020/04004000 pcieport 0000:00:01.4: [ 5] SDES (First) nvme nvme2: frozen state error detected, reset controller pcieport 0000:00:01.4: DPC: Data Link Layer Link Active not set in 1000 msec pcieport 0000:00:01.4: AER: subordinate device reset failed pcieport 0000:00:01.4: AER: device recovery failed pcieport 0000:00:01.4: pciehp: Slot(16): Link Down nvme2n1: detected capacity change from 1953525168 to 0 pci 0000:04:00.0: Removing from iommu group 49 Dmesg after: pcieport 0000:00:01.4: pciehp: Slot(16): Link Down nvme1n1: detected capacity change from 1953525168 to 0 pci 0000:04:00.0: Removing from iommu group 37 [1] PCI Express Base Specification Revision 6.0, Dec 16 2021. https://members.pcisig.com/wg/PCI-SIG/document/16609 Fixes: aea47413e7ce ("PCI/DPC: Expose dpc_process_error(), dpc_reset_link() for use by EDR") Link: https://lore.kernel.org/r/20240207181854.121335-1-Smita.KoralahalliChannabasappa@amd.com Signed-off-by: Smita Koralahalli Signed-off-by: Bjorn Helgaas Reviewed-by: Lukas Wunner Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Ilpo Järvinen Signed-off-by: xia-bing1 <2328702590@qq.com> Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/pci/pcie/dpc.c | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index 3c3ecb9cf57af..e1923b944a026 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -293,10 +293,70 @@ void dpc_process_error(struct pci_dev *pdev) } } +static void pci_clear_surpdn_errors(struct pci_dev *pdev) +{ + if (pdev->dpc_rp_extensions) + pci_write_config_dword(pdev, pdev->dpc_cap + + PCI_EXP_DPC_RP_PIO_STATUS, ~0); + + /* + * In practice, Surprise Down errors have been observed to also set + * error bits in the Status Register as well as the Fatal Error + * Detected bit in the Device Status Register. + */ + pci_write_config_word(pdev, PCI_STATUS, 0xffff); + + pcie_capability_write_word(pdev, PCI_EXP_DEVSTA, PCI_EXP_DEVSTA_FED); +} + +static void dpc_handle_surprise_removal(struct pci_dev *pdev) +{ + if (!pcie_wait_for_link(pdev, false)) { + pci_info(pdev, "Data Link Layer Link Active not cleared in 1000 msec\n"); + goto out; + } + + if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev)) + goto out; + + pci_aer_raw_clear_status(pdev); + pci_clear_surpdn_errors(pdev); + + pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_STATUS, + PCI_EXP_DPC_STATUS_TRIGGER); + +out: + clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags); + wake_up_all(&dpc_completed_waitqueue); +} + +static bool dpc_is_surprise_removal(struct pci_dev *pdev) +{ + u16 status; + + if (!pdev->is_hotplug_bridge) + return false; + + if (pci_read_config_word(pdev, pdev->aer_cap + PCI_ERR_UNCOR_STATUS, + &status)) + return false; + + return status & PCI_ERR_UNC_SURPDN; +} + static irqreturn_t dpc_handler(int irq, void *context) { struct pci_dev *pdev = context; + /* + * According to PCIe r6.0 sec 6.7.6, errors are an expected side effect + * of async removal and should be ignored by software. + */ + if (dpc_is_surprise_removal(pdev)) { + dpc_handle_surprise_removal(pdev); + return IRQ_HANDLED; + } + dpc_process_error(pdev); /* We configure DPC so it only triggers on ERR_FATAL */ From 69e2e6eae35ae54cf1946f03ea55ec165665da7e Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 10 Oct 2023 16:47:29 +0800 Subject: [PATCH 026/107] hwtracing: hisi_ptt: Optimize the trace data committing [Upstream commit dabf410d8764dbb24832d18bb825fe7ba5e75d30] In the current implementation, there're 4*4MiB trace buffer and hardware will fill the buffer one by one. The driver will get notified if one buffer is full and then copy data to the AUX buffer. If there's no enough room for the next trace buffer, we'll commit the AUX buffer to the perf core and try to apply a new one. In a typical configuration the AUX buffer will be 16MiB, so we'll commit the data after the whole AUX buffer is occupied. Then the driver cannot apply a new AUX buffer immediately until the committed data is consumed by userspace and then there's room in the AUX buffer again. This patch tries to optimize this by commit the data after one single trace buffer is filled. Since there's still room in the AUX buffer, driver can apply a new one without failure and don't need to wait for the userspace to consume the data. Signed-off-by: Yicong Yang Acked-by: Jonathan Cameron Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20231010084731.30450-4-yangyicong@huawei.com Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwtracing/ptt/hisi_ptt.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index a63394e755b3a..400468e961175 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -274,15 +274,14 @@ static int hisi_ptt_update_aux(struct hisi_ptt *hisi_ptt, int index, bool stop) buf->pos += size; /* - * Just commit the traced data if we're going to stop. Otherwise if the - * resident AUX buffer cannot contain the data of next trace buffer, - * apply a new one. + * Always commit the data to the AUX buffer in time to make sure + * userspace got enough time to consume the data. + * + * If we're not going to stop, apply a new one and check whether + * there's enough room for the next trace. */ - if (stop) { - perf_aux_output_end(handle, buf->pos); - } else if (buf->length - buf->pos < HISI_PTT_TRACE_BUF_SIZE) { - perf_aux_output_end(handle, buf->pos); - + perf_aux_output_end(handle, size); + if (!stop) { buf = perf_aux_output_begin(handle, event); if (!buf) return -EINVAL; From f7af86edb55d547274ea3a9f558b7758df3fbf33 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Sat, 30 Sep 2023 15:27:19 +0800 Subject: [PATCH 027/107] perf hisi-ptt: Fix memory leak in lseek failure handling [Upstream commit be7a4caa7c45bd4b0a39cdb260905b52a87c8688] In the previous code, there was a memory leak issue where the previously allocated memory was not freed upon a failed lseek operation. This patch addresses the problem by releasing the old memory before returning -errno in case of a lseek failure. This ensures that memory is properly managed and avoids potential memory leaks. Signed-off-by: Kuan-Wei Chiu Acked-by: Namhyung Kim Cc: yangyicong@hisilicon.com Cc: jonathan.cameron@huawei.com Link: https://lore.kernel.org/r/20230930072719.1267784-1-visitorckw@gmail.com Signed-off-by: Namhyung Kim Signed-off-by: huwentao Signed-off-by: WangYuli --- tools/perf/util/hisi-ptt.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/hisi-ptt.c b/tools/perf/util/hisi-ptt.c index 764d660d30e2f..52d0ce302ca04 100644 --- a/tools/perf/util/hisi-ptt.c +++ b/tools/perf/util/hisi-ptt.c @@ -108,8 +108,10 @@ static int hisi_ptt_process_auxtrace_event(struct perf_session *session, data_offset = 0; } else { data_offset = lseek(fd, 0, SEEK_CUR); - if (data_offset == -1) + if (data_offset == -1) { + free(data); return -errno; + } } err = readn(fd, data, size); From bb753e4eac2b006c25bb7704ad216a8c8d1a9cc0 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 14 Mar 2025 15:34:22 +0800 Subject: [PATCH 028/107] drm/hisilicon/hibmc: convert to struct drm_edid [Upstream commit ff2a391349afcf62e99897de9bfd854ecf9d21b8] Prefer the struct drm_edid based functions for reading the EDID and updating the connector. Reviewed-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/386e3a64efbdd61c3eaed3f49ea9c3ebd4fcd41d.1715691257.git.jani.nikula@intel.com Signed-off-by: Jani Nikula Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- .../gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c index 8c6d2ea2a4729..7a2e2588f64fd 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c @@ -23,14 +23,16 @@ static int hibmc_connector_get_modes(struct drm_connector *connector) { - int count; - void *edid; struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector); + const struct drm_edid *drm_edid; + int count; + + drm_edid = drm_edid_read_ddc(connector, &hibmc_connector->adapter); - edid = drm_get_edid(connector, &hibmc_connector->adapter); - if (edid) { - drm_connector_update_edid_property(connector, edid); - count = drm_add_edid_modes(connector, edid); + drm_edid_connector_update(connector, drm_edid); + + if (drm_edid) { + count = drm_edid_connector_add_modes(connector); if (count) goto out; } @@ -41,7 +43,8 @@ static int hibmc_connector_get_modes(struct drm_connector *connector) drm_set_preferred_mode(connector, 1024, 768); out: - kfree(edid); + drm_edid_free(drm_edid); + return count; } From 42e7f11ce0b83c395d57112b31fbd3c8822751ad Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Fri, 14 Mar 2025 15:34:23 +0800 Subject: [PATCH 029/107] drm/hisilicon/hibmc: add dp aux in hibmc drivers [Upstream commit 057e77972556aed4a0f1eed7eeb85024d0a22ba1] Add dp aux read/write functions. They are basic functions and will be used later. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tian Tao Link: https://patchwork.freedesktop.org/patch/msgid/20250103093824.1963816-2-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Makefile | 3 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c | 164 +++++++++++++++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h | 42 +++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 27 +++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile index d25c75e60d3d4..8770ec6dfffd1 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o +hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \ + dp/dp_aux.o obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c new file mode 100644 index 0000000000000..0a903cce1fa95 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2024 Hisilicon Limited. + +#include +#include +#include +#include +#include +#include "dp_comm.h" +#include "dp_reg.h" + +#define HIBMC_AUX_CMD_REQ_LEN GENMASK(7, 4) +#define HIBMC_AUX_CMD_ADDR GENMASK(27, 8) +#define HIBMC_AUX_CMD_I2C_ADDR_ONLY BIT(28) +#define HIBMC_BYTES_IN_U32 4 +#define HIBMC_AUX_I2C_WRITE_SUCCESS 0x1 +#define HIBMC_DP_MIN_PULSE_NUM 0x9 +#define BITS_IN_U8 8 + +static inline void hibmc_dp_aux_reset(struct hibmc_dp_dev *dp) +{ + hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_RST_CTRL, HIBMC_DP_CFG_AUX_RST_N, 0x0); + usleep_range(10, 15); + hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_RST_CTRL, HIBMC_DP_CFG_AUX_RST_N, 0x1); +} + +static void hibmc_dp_aux_read_data(struct hibmc_dp_dev *dp, u8 *buf, u8 size) +{ + u32 reg_num; + u32 value; + u32 num; + u8 i, j; + + reg_num = DIV_ROUND_UP(size, HIBMC_BYTES_IN_U32); + for (i = 0; i < reg_num; i++) { + /* number of bytes read from a single register */ + num = min(size - i * HIBMC_BYTES_IN_U32, HIBMC_BYTES_IN_U32); + value = readl(dp->base + HIBMC_DP_AUX_RD_DATA0 + i * HIBMC_BYTES_IN_U32); + /* convert the 32-bit value of the register to the buffer. */ + for (j = 0; j < num; j++) + buf[i * HIBMC_BYTES_IN_U32 + j] = value >> (j * BITS_IN_U8); + } +} + +static void hibmc_dp_aux_write_data(struct hibmc_dp_dev *dp, u8 *buf, u8 size) +{ + u32 reg_num; + u32 value; + u32 num; + u8 i, j; + + reg_num = DIV_ROUND_UP(size, HIBMC_BYTES_IN_U32); + for (i = 0; i < reg_num; i++) { + /* number of bytes written to a single register */ + num = min_t(u8, size - i * HIBMC_BYTES_IN_U32, HIBMC_BYTES_IN_U32); + value = 0; + /* obtain the 32-bit value written to a single register. */ + for (j = 0; j < num; j++) + value |= buf[i * HIBMC_BYTES_IN_U32 + j] << (j * BITS_IN_U8); + /* writing data to a single register */ + writel(value, dp->base + HIBMC_DP_AUX_WR_DATA0 + i * HIBMC_BYTES_IN_U32); + } +} + +static u32 hibmc_dp_aux_build_cmd(const struct drm_dp_aux_msg *msg) +{ + u32 aux_cmd = msg->request; + + if (msg->size) + aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_REQ_LEN, (msg->size - 1)); + else + aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_I2C_ADDR_ONLY, 1); + + aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_ADDR, msg->address); + + return aux_cmd; +} + +/* ret >= 0, ret is size; ret < 0, ret is err code */ +static int hibmc_dp_aux_parse_xfer(struct hibmc_dp_dev *dp, struct drm_dp_aux_msg *msg) +{ + u32 buf_data_cnt; + u32 aux_status; + + aux_status = readl(dp->base + HIBMC_DP_AUX_STATUS); + msg->reply = FIELD_GET(HIBMC_DP_CFG_AUX_STATUS, aux_status); + + if (aux_status & HIBMC_DP_CFG_AUX_TIMEOUT) + return -ETIMEDOUT; + + /* only address */ + if (!msg->size) + return 0; + + if (msg->reply != DP_AUX_NATIVE_REPLY_ACK) + return -EIO; + + buf_data_cnt = FIELD_GET(HIBMC_DP_CFG_AUX_READY_DATA_BYTE, aux_status); + + switch (msg->request) { + case DP_AUX_NATIVE_WRITE: + return msg->size; + case DP_AUX_I2C_WRITE | DP_AUX_I2C_MOT: + if (buf_data_cnt == HIBMC_AUX_I2C_WRITE_SUCCESS) + return msg->size; + else + return FIELD_GET(HIBMC_DP_CFG_AUX, aux_status); + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ | DP_AUX_I2C_MOT: + buf_data_cnt--; + if (buf_data_cnt != msg->size) { + /* only the successful part of data is read */ + return -EBUSY; + } + + /* all data is successfully read */ + hibmc_dp_aux_read_data(dp, msg->buffer, msg->size); + return msg->size; + default: + return -EINVAL; + } +} + +/* ret >= 0 ,ret is size; ret < 0, ret is err code */ +static ssize_t hibmc_dp_aux_xfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + struct hibmc_dp_dev *dp = container_of(aux, struct hibmc_dp_dev, aux); + u32 aux_cmd; + int ret; + u32 val; /* val will be assigned at the beginning of readl_poll_timeout function */ + + writel(0, dp->base + HIBMC_DP_AUX_WR_DATA0); + writel(0, dp->base + HIBMC_DP_AUX_WR_DATA1); + writel(0, dp->base + HIBMC_DP_AUX_WR_DATA2); + writel(0, dp->base + HIBMC_DP_AUX_WR_DATA3); + + hibmc_dp_aux_write_data(dp, msg->buffer, msg->size); + + aux_cmd = hibmc_dp_aux_build_cmd(msg); + writel(aux_cmd, dp->base + HIBMC_DP_AUX_CMD_ADDR); + + /* enable aux transfer */ + hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_REQ, 0x1); + ret = readl_poll_timeout(dp->base + HIBMC_DP_AUX_REQ, val, + !(val & HIBMC_DP_CFG_AUX_REQ), 50, 5000); + if (ret) { + hibmc_dp_aux_reset(dp); + return ret; + } + + return hibmc_dp_aux_parse_xfer(dp, msg); +} + +void hibmc_dp_aux_init(struct hibmc_dp_dev *dp) +{ + hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0); + hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1); + hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM, + HIBMC_DP_MIN_PULSE_NUM); + + dp->aux.transfer = hibmc_dp_aux_xfer; + dp->aux.is_remote = 0; + drm_dp_aux_init(&dp->aux); +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h new file mode 100644 index 0000000000000..7d3cd32393c04 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2024 Hisilicon Limited. */ + +#ifndef DP_COMM_H +#define DP_COMM_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct hibmc_dp_dev { + struct drm_dp_aux aux; + struct drm_device *dev; + void __iomem *base; + struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */ +}; + +#define dp_field_modify(reg_value, mask, val) \ + do { \ + (reg_value) &= ~(mask); \ + (reg_value) |= FIELD_PREP(mask, val); \ + } while (0) \ + +#define hibmc_dp_reg_write_field(dp, offset, mask, val) \ + do { \ + typeof(dp) _dp = dp; \ + typeof(_dp->base) addr = (_dp->base + (offset)); \ + mutex_lock(&_dp->lock); \ + u32 reg_value = readl(addr); \ + dp_field_modify(reg_value, mask, val); \ + writel(reg_value, addr); \ + mutex_unlock(&_dp->lock); \ + } while (0) + +void hibmc_dp_aux_init(struct hibmc_dp_dev *dp); + +#endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h new file mode 100644 index 0000000000000..f3e6781e111a1 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2024 Hisilicon Limited. */ + +#ifndef DP_REG_H +#define DP_REG_H + +#define HIBMC_DP_AUX_CMD_ADDR 0x50 +#define HIBMC_DP_AUX_WR_DATA0 0x54 +#define HIBMC_DP_AUX_WR_DATA1 0x58 +#define HIBMC_DP_AUX_WR_DATA2 0x5c +#define HIBMC_DP_AUX_WR_DATA3 0x60 +#define HIBMC_DP_AUX_RD_DATA0 0x64 +#define HIBMC_DP_AUX_REQ 0x74 +#define HIBMC_DP_AUX_STATUS 0x78 +#define HIBMC_DP_DPTX_RST_CTRL 0x700 + +#define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1) +#define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2) +#define HIBMC_DP_CFG_AUX_MIN_PULSE_NUM GENMASK(13, 9) +#define HIBMC_DP_CFG_AUX_REQ BIT(0) +#define HIBMC_DP_CFG_AUX_RST_N BIT(4) +#define HIBMC_DP_CFG_AUX_TIMEOUT BIT(0) +#define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12) +#define HIBMC_DP_CFG_AUX GENMASK(24, 17) +#define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4) + +#endif From be2fd6fa37fe91cd388491b12ca74308ecc70991 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Fri, 14 Mar 2025 15:34:24 +0800 Subject: [PATCH 030/107] drm/hisilicon/hibmc: add dp link moduel in hibmc drivers [Upstream commit 54063d86e0369f53f180137e5e889bc19cd9015b] Add link training process functions in this moduel. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tian Tao Link: https://patchwork.freedesktop.org/patch/msgid/20250103093824.1963816-3-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Makefile | 2 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h | 21 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c | 332 +++++++++++++++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 8 + 4 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile index 8770ec6dfffd1..94d77da88bbfc 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \ - dp/dp_aux.o + dp/dp_aux.o dp/dp_link.o obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h index 7d3cd32393c04..2c52a4476c4dd 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h @@ -13,11 +13,31 @@ #include #include +#define HIBMC_DP_LANE_NUM_MAX 2 + +struct hibmc_link_status { + bool clock_recovered; + bool channel_equalized; +}; + +struct hibmc_link_cap { + u8 link_rate; + u8 lanes; +}; + +struct hibmc_dp_link { + struct hibmc_link_status status; + u8 train_set[HIBMC_DP_LANE_NUM_MAX]; + struct hibmc_link_cap cap; +}; + struct hibmc_dp_dev { struct drm_dp_aux aux; struct drm_device *dev; void __iomem *base; struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */ + struct hibmc_dp_link link; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; }; #define dp_field_modify(reg_value, mask, val) \ @@ -38,5 +58,6 @@ struct hibmc_dp_dev { } while (0) void hibmc_dp_aux_init(struct hibmc_dp_dev *dp); +int hibmc_dp_link_training(struct hibmc_dp_dev *dp); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c new file mode 100644 index 0000000000000..f6355c16cc0ab --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2024 Hisilicon Limited. + +#include +#include +#include +#include "dp_comm.h" +#include "dp_reg.h" + +#define HIBMC_EQ_MAX_RETRY 5 + +static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp) +{ + u8 buf[2]; + int ret; + + /* DP 2 lane */ + hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_LANE_DATA_EN, + dp->link.cap.lanes == 0x2 ? 0x3 : 0x1); + hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_GCTL0, HIBMC_DP_CFG_PHY_LANE_NUM, + dp->link.cap.lanes == 0x2 ? 0x1 : 0); + + /* enhanced frame */ + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_FRAME_MODE, 0x1); + + /* set rate and lane count */ + buf[0] = dp->link.cap.link_rate; + buf[1] = DP_LANE_COUNT_ENHANCED_FRAME_EN | dp->link.cap.lanes; + ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + drm_dbg_dp(dp->dev, "dp aux write link rate and lanes failed, ret: %d\n", ret); + return ret >= 0 ? -EIO : ret; + } + + /* set 8b/10b and downspread */ + buf[0] = DP_SPREAD_AMP_0_5; + buf[1] = DP_SET_ANSI_8B10B; + ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + drm_dbg_dp(dp->dev, "dp aux write 8b/10b and downspread failed, ret: %d\n", ret); + return ret >= 0 ? -EIO : ret; + } + + ret = drm_dp_read_dpcd_caps(&dp->aux, dp->dpcd); + if (ret) + drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret); + + return ret; +} + +static int hibmc_dp_link_set_pattern(struct hibmc_dp_dev *dp, int pattern) +{ + int ret; + u8 val; + u8 buf; + + buf = (u8)pattern; + if (pattern != DP_TRAINING_PATTERN_DISABLE && pattern != DP_TRAINING_PATTERN_4) { + buf |= DP_LINK_SCRAMBLING_DISABLE; + hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_SCRAMBLE_EN, 0x1); + } else { + hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_SCRAMBLE_EN, 0); + } + + switch (pattern) { + case DP_TRAINING_PATTERN_DISABLE: + val = 0; + break; + case DP_TRAINING_PATTERN_1: + val = 1; + break; + case DP_TRAINING_PATTERN_2: + val = 2; + break; + case DP_TRAINING_PATTERN_3: + val = 3; + break; + case DP_TRAINING_PATTERN_4: + val = 4; + break; + default: + return -EINVAL; + } + + hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_PAT_SEL, val); + + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_PATTERN_SET, &buf, sizeof(buf)); + if (ret != sizeof(buf)) { + drm_dbg_dp(dp->dev, "dp aux write training pattern set failed\n"); + return ret >= 0 ? -EIO : ret; + } + + return 0; +} + +static int hibmc_dp_link_training_cr_pre(struct hibmc_dp_dev *dp) +{ + u8 *train_set = dp->link.train_set; + int ret; + u8 i; + + ret = hibmc_dp_link_training_configure(dp); + if (ret) + return ret; + + ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_1); + if (ret) + return ret; + + for (i = 0; i < dp->link.cap.lanes; i++) + train_set[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2; + + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, train_set, dp->link.cap.lanes); + if (ret != dp->link.cap.lanes) { + drm_dbg_dp(dp->dev, "dp aux write training lane set failed\n"); + return ret >= 0 ? -EIO : ret; + } + + return 0; +} + +static bool hibmc_dp_link_get_adjust_train(struct hibmc_dp_dev *dp, + u8 lane_status[DP_LINK_STATUS_SIZE]) +{ + u8 train_set[HIBMC_DP_LANE_NUM_MAX] = {0}; + u8 lane; + + for (lane = 0; lane < dp->link.cap.lanes; lane++) + train_set[lane] = drm_dp_get_adjust_request_voltage(lane_status, lane) | + drm_dp_get_adjust_request_pre_emphasis(lane_status, lane); + + if (memcmp(dp->link.train_set, train_set, HIBMC_DP_LANE_NUM_MAX)) { + memcpy(dp->link.train_set, train_set, HIBMC_DP_LANE_NUM_MAX); + return true; + } + + return false; +} + +static inline int hibmc_dp_link_reduce_rate(struct hibmc_dp_dev *dp) +{ + switch (dp->link.cap.link_rate) { + case DP_LINK_BW_2_7: + dp->link.cap.link_rate = DP_LINK_BW_1_62; + return 0; + case DP_LINK_BW_5_4: + dp->link.cap.link_rate = DP_LINK_BW_2_7; + return 0; + case DP_LINK_BW_8_1: + dp->link.cap.link_rate = DP_LINK_BW_5_4; + return 0; + default: + return -EINVAL; + } +} + +static inline int hibmc_dp_link_reduce_lane(struct hibmc_dp_dev *dp) +{ + switch (dp->link.cap.lanes) { + case 0x2: + dp->link.cap.lanes--; + break; + case 0x1: + drm_err(dp->dev, "dp link training reduce lane failed, already reach minimum\n"); + return -EIO; + default: + return -EINVAL; + } + + return 0; +} + +static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp) +{ + u8 lane_status[DP_LINK_STATUS_SIZE] = {0}; + bool level_changed; + u32 voltage_tries; + u32 cr_tries; + int ret; + + /* + * DP 1.4 spec define 10 for maxtries value, for pre DP 1.4 version set a limit of 80 + * (4 voltage levels x 4 preemphasis levels x 5 identical voltage retries) + */ + + voltage_tries = 1; + for (cr_tries = 0; cr_tries < 80; cr_tries++) { + drm_dp_link_train_clock_recovery_delay(&dp->aux, dp->dpcd); + + ret = drm_dp_dpcd_read_link_status(&dp->aux, lane_status); + if (ret != DP_LINK_STATUS_SIZE) { + drm_err(dp->dev, "Get lane status failed\n"); + return ret; + } + + if (drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) { + drm_dbg_dp(dp->dev, "dp link training cr done\n"); + dp->link.status.clock_recovered = true; + return 0; + } + + if (voltage_tries == 5) { + drm_dbg_dp(dp->dev, "same voltage tries 5 times\n"); + dp->link.status.clock_recovered = false; + return 0; + } + + level_changed = hibmc_dp_link_get_adjust_train(dp, lane_status); + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, + dp->link.cap.lanes); + if (ret != dp->link.cap.lanes) { + drm_dbg_dp(dp->dev, "Update link training failed\n"); + return ret >= 0 ? -EIO : ret; + } + + voltage_tries = level_changed ? 1 : voltage_tries + 1; + } + + drm_err(dp->dev, "dp link training clock recovery 80 times failed\n"); + dp->link.status.clock_recovered = false; + + return 0; +} + +static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp) +{ + u8 lane_status[DP_LINK_STATUS_SIZE] = {0}; + u8 eq_tries; + int ret; + + ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_2); + if (ret) + return ret; + + for (eq_tries = 0; eq_tries < HIBMC_EQ_MAX_RETRY; eq_tries++) { + drm_dp_link_train_channel_eq_delay(&dp->aux, dp->dpcd); + + ret = drm_dp_dpcd_read_link_status(&dp->aux, lane_status); + if (ret != DP_LINK_STATUS_SIZE) { + drm_err(dp->dev, "get lane status failed\n"); + break; + } + + if (!drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) { + drm_dbg_dp(dp->dev, "clock recovery check failed\n"); + drm_dbg_dp(dp->dev, "cannot continue channel equalization\n"); + dp->link.status.clock_recovered = false; + break; + } + + if (drm_dp_channel_eq_ok(lane_status, dp->link.cap.lanes)) { + dp->link.status.channel_equalized = true; + drm_dbg_dp(dp->dev, "dp link training eq done\n"); + break; + } + + hibmc_dp_link_get_adjust_train(dp, lane_status); + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + dp->link.train_set, dp->link.cap.lanes); + if (ret != dp->link.cap.lanes) { + drm_dbg_dp(dp->dev, "Update link training failed\n"); + ret = (ret >= 0) ? -EIO : ret; + break; + } + } + + if (eq_tries == HIBMC_EQ_MAX_RETRY) + drm_err(dp->dev, "channel equalization failed %u times\n", eq_tries); + + hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); + + return ret < 0 ? ret : 0; +} + +static int hibmc_dp_link_downgrade_training_cr(struct hibmc_dp_dev *dp) +{ + if (hibmc_dp_link_reduce_rate(dp)) + return hibmc_dp_link_reduce_lane(dp); + + return 0; +} + +static int hibmc_dp_link_downgrade_training_eq(struct hibmc_dp_dev *dp) +{ + if ((dp->link.status.clock_recovered && !dp->link.status.channel_equalized)) { + if (!hibmc_dp_link_reduce_lane(dp)) + return 0; + } + + return hibmc_dp_link_reduce_rate(dp); +} + +int hibmc_dp_link_training(struct hibmc_dp_dev *dp) +{ + struct hibmc_dp_link *link = &dp->link; + int ret; + + while (true) { + ret = hibmc_dp_link_training_cr_pre(dp); + if (ret) + goto err; + + ret = hibmc_dp_link_training_cr(dp); + if (ret) + goto err; + + if (!link->status.clock_recovered) { + ret = hibmc_dp_link_downgrade_training_cr(dp); + if (ret) + goto err; + continue; + } + + ret = hibmc_dp_link_training_channel_eq(dp); + if (ret) + goto err; + + if (!link->status.channel_equalized) { + ret = hibmc_dp_link_downgrade_training_eq(dp); + if (ret) + goto err; + continue; + } + + return 0; + } + +err: + hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); + + return ret; +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index f3e6781e111a1..0bd308eccdc50 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -12,16 +12,24 @@ #define HIBMC_DP_AUX_RD_DATA0 0x64 #define HIBMC_DP_AUX_REQ 0x74 #define HIBMC_DP_AUX_STATUS 0x78 +#define HIBMC_DP_PHYIF_CTRL0 0xa0 +#define HIBMC_DP_VIDEO_CTRL 0x100 #define HIBMC_DP_DPTX_RST_CTRL 0x700 +#define HIBMC_DP_DPTX_GCTL0 0x708 #define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1) #define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2) +#define HIBMC_DP_CFG_STREAM_FRAME_MODE BIT(6) #define HIBMC_DP_CFG_AUX_MIN_PULSE_NUM GENMASK(13, 9) +#define HIBMC_DP_CFG_LANE_DATA_EN GENMASK(11, 8) +#define HIBMC_DP_CFG_PHY_LANE_NUM GENMASK(2, 1) #define HIBMC_DP_CFG_AUX_REQ BIT(0) #define HIBMC_DP_CFG_AUX_RST_N BIT(4) #define HIBMC_DP_CFG_AUX_TIMEOUT BIT(0) #define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12) #define HIBMC_DP_CFG_AUX GENMASK(24, 17) #define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4) +#define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0) +#define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4) #endif From 213d5132b1ab213e355ea789a1499f6433a7926c Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Fri, 14 Mar 2025 15:34:25 +0800 Subject: [PATCH 031/107] drm/hisilicon/hibmc: add dp hw moduel in hibmc driver [Upstream commit 94ee73ee30208f3d92b2f2a4f7d3346ba56245bf] Build a dp level that hibmc driver can enable dp by calling their functions. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tian Tao Link: https://patchwork.freedesktop.org/patch/msgid/20250103093824.1963816-4-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Makefile | 2 +- .../gpu/drm/hisilicon/hibmc/dp/dp_config.h | 19 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 220 ++++++++++++++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 28 +++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 41 ++++ 5 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile index 94d77da88bbfc..214228052ccf4 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \ - dp/dp_aux.o dp/dp_link.o + dp/dp_aux.o dp/dp_link.o dp/dp_hw.o obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h new file mode 100644 index 0000000000000..74dd9956144e4 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2024 Hisilicon Limited. */ + +#ifndef DP_CONFIG_H +#define DP_CONFIG_H + +#define HIBMC_DP_BPP 24 +#define HIBMC_DP_SYMBOL_PER_FCLK 4 +#define HIBMC_DP_MSA1 0x20 +#define HIBMC_DP_MSA2 0x845c00 +#define HIBMC_DP_OFFSET 0x1e0000 +#define HIBMC_DP_HDCP 0x2 +#define HIBMC_DP_INT_RST 0xffff +#define HIBMC_DP_DPTX_RST 0x3ff +#define HIBMC_DP_CLK_EN 0x7 +#define HIBMC_DP_SYNC_EN_MASK 0x3 +#define HIBMC_DP_LINK_RATE_CAL 27 + +#endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c new file mode 100644 index 0000000000000..a8d543881c099 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2024 Hisilicon Limited. + +#include +#include +#include "dp_config.h" +#include "dp_comm.h" +#include "dp_reg.h" +#include "dp_hw.h" + +static void hibmc_dp_set_tu(struct hibmc_dp_dev *dp, struct drm_display_mode *mode) +{ + u32 tu_symbol_frac_size; + u32 tu_symbol_size; + u32 rate_ks; + u8 lane_num; + u32 value; + u32 bpp; + + lane_num = dp->link.cap.lanes; + if (lane_num == 0) { + drm_err(dp->dev, "set tu failed, lane num cannot be 0!\n"); + return; + } + + bpp = HIBMC_DP_BPP; + rate_ks = dp->link.cap.link_rate * HIBMC_DP_LINK_RATE_CAL; + value = (mode->clock * bpp * 5) / (61 * lane_num * rate_ks); + + if (value % 10 == 9) { /* 9 carry */ + tu_symbol_size = value / 10 + 1; + tu_symbol_frac_size = 0; + } else { + tu_symbol_size = value / 10; + tu_symbol_frac_size = value % 10 + 1; + } + + drm_dbg_dp(dp->dev, "tu value: %u.%u value: %u\n", + tu_symbol_size, tu_symbol_frac_size, value); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET, + HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE, tu_symbol_size); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET, + HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE, tu_symbol_frac_size); +} + +static void hibmc_dp_set_sst(struct hibmc_dp_dev *dp, struct drm_display_mode *mode) +{ + u32 hblank_size; + u32 htotal_size; + u32 htotal_int; + u32 hblank_int; + u32 fclk; /* flink_clock */ + + fclk = dp->link.cap.link_rate * HIBMC_DP_LINK_RATE_CAL; + + /* Considering the effect of spread spectrum, the value may be deviated. + * The coefficient (0.9947) is used to offset the deviation. + */ + htotal_int = mode->htotal * 9947 / 10000; + htotal_size = htotal_int * fclk / (HIBMC_DP_SYMBOL_PER_FCLK * (mode->clock / 1000)); + + hblank_int = mode->htotal - mode->hdisplay - mode->hdisplay * 53 / 10000; + hblank_size = hblank_int * fclk * 9947 / + (mode->clock * 10 * HIBMC_DP_SYMBOL_PER_FCLK); + + drm_dbg_dp(dp->dev, "h_active %u v_active %u htotal_size %u hblank_size %u", + mode->hdisplay, mode->vdisplay, htotal_size, hblank_size); + drm_dbg_dp(dp->dev, "flink_clock %u pixel_clock %d", fclk, mode->clock / 1000); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE, + HIBMC_DP_CFG_STREAM_HTOTAL_SIZE, htotal_size); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE, + HIBMC_DP_CFG_STREAM_HBLANK_SIZE, hblank_size); +} + +static void hibmc_dp_link_cfg(struct hibmc_dp_dev *dp, struct drm_display_mode *mode) +{ + u32 timing_delay; + u32 vblank; + u32 hstart; + u32 vstart; + + vblank = mode->vtotal - mode->vdisplay; + timing_delay = mode->htotal - mode->hsync_start; + hstart = mode->htotal - mode->hsync_start; + vstart = mode->vtotal - mode->vsync_start; + + hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG0, + HIBMC_DP_CFG_TIMING_GEN0_HBLANK, mode->htotal - mode->hdisplay); + hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG0, + HIBMC_DP_CFG_TIMING_GEN0_HACTIVE, mode->hdisplay); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG2, + HIBMC_DP_CFG_TIMING_GEN0_VBLANK, vblank); + hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG2, + HIBMC_DP_CFG_TIMING_GEN0_VACTIVE, mode->vdisplay); + hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG3, + HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH, + mode->vsync_start - mode->vdisplay); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG0, + HIBMC_DP_CFG_STREAM_HACTIVE, mode->hdisplay); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG0, + HIBMC_DP_CFG_STREAM_HBLANK, mode->htotal - mode->hdisplay); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG2, + HIBMC_DP_CFG_STREAM_HSYNC_WIDTH, + mode->hsync_end - mode->hsync_start); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG1, + HIBMC_DP_CFG_STREAM_VACTIVE, mode->vdisplay); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG1, + HIBMC_DP_CFG_STREAM_VBLANK, vblank); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG3, + HIBMC_DP_CFG_STREAM_VFRONT_PORCH, + mode->vsync_start - mode->vdisplay); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG3, + HIBMC_DP_CFG_STREAM_VSYNC_WIDTH, + mode->vsync_end - mode->vsync_start); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_MSA0, + HIBMC_DP_CFG_STREAM_VSTART, vstart); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_MSA0, + HIBMC_DP_CFG_STREAM_HSTART, hstart); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_VSYNC_POLARITY, + mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_HSYNC_POLARITY, + mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0); + + /* MSA mic 0 and 1 */ + writel(HIBMC_DP_MSA1, dp->base + HIBMC_DP_VIDEO_MSA1); + writel(HIBMC_DP_MSA2, dp->base + HIBMC_DP_VIDEO_MSA2); + + hibmc_dp_set_tu(dp, mode); + + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_RGB_ENABLE, 0x1); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_VIDEO_MAPPING, 0); + + /* divide 2: up even */ + if (timing_delay % 2) + timing_delay++; + + hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_MODEL_CTRL, + HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1, timing_delay); + + hibmc_dp_set_sst(dp, mode); +} + +int hibmc_dp_hw_init(struct hibmc_dp *dp) +{ + struct drm_device *drm_dev = dp->drm_dev; + struct hibmc_dp_dev *dp_dev; + + dp_dev = devm_kzalloc(drm_dev->dev, sizeof(struct hibmc_dp_dev), GFP_KERNEL); + if (!dp_dev) + return -ENOMEM; + + mutex_init(&dp_dev->lock); + + dp->dp_dev = dp_dev; + + dp_dev->dev = drm_dev; + dp_dev->base = dp->mmio + HIBMC_DP_OFFSET; + + hibmc_dp_aux_init(dp_dev); + + dp_dev->link.cap.lanes = 0x2; + dp_dev->link.cap.link_rate = DP_LINK_BW_2_7; + + /* hdcp data */ + writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); + /* int init */ + writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE); + writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS); + /* rst */ + writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); + /* clock enable */ + writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL); + + return 0; +} + +void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable) +{ + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + + if (enable) { + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0x1); + writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0x1); + writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); + } else { + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0); + writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0); + writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); + } + + msleep(50); +} + +int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode) +{ + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + int ret; + + if (!dp_dev->link.status.channel_equalized) { + ret = hibmc_dp_link_training(dp_dev); + if (ret) { + drm_err(dp->drm_dev, "dp link training failed, ret: %d\n", ret); + return ret; + } + } + + hibmc_dp_display_en(dp, false); + hibmc_dp_link_cfg(dp_dev, mode); + + return 0; +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h new file mode 100644 index 0000000000000..4dc13b3d9875f --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (c) 2024 Hisilicon Limited. */ + +#ifndef DP_KAPI_H +#define DP_KAPI_H + +#include +#include +#include +#include +#include +#include + +struct hibmc_dp_dev; + +struct hibmc_dp { + struct hibmc_dp_dev *dp_dev; + struct drm_device *drm_dev; + struct drm_encoder encoder; + struct drm_connector connector; + void __iomem *mmio; +}; + +int hibmc_dp_hw_init(struct hibmc_dp *dp); +int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode); +void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable); + +#endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 0bd308eccdc50..4a515c726d524 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -14,8 +14,26 @@ #define HIBMC_DP_AUX_STATUS 0x78 #define HIBMC_DP_PHYIF_CTRL0 0xa0 #define HIBMC_DP_VIDEO_CTRL 0x100 +#define HIBMC_DP_VIDEO_CONFIG0 0x104 +#define HIBMC_DP_VIDEO_CONFIG1 0x108 +#define HIBMC_DP_VIDEO_CONFIG2 0x10c +#define HIBMC_DP_VIDEO_CONFIG3 0x110 +#define HIBMC_DP_VIDEO_PACKET 0x114 +#define HIBMC_DP_VIDEO_MSA0 0x118 +#define HIBMC_DP_VIDEO_MSA1 0x11c +#define HIBMC_DP_VIDEO_MSA2 0x120 +#define HIBMC_DP_VIDEO_HORIZONTAL_SIZE 0X124 +#define HIBMC_DP_TIMING_GEN_CONFIG0 0x26c +#define HIBMC_DP_TIMING_GEN_CONFIG2 0x274 +#define HIBMC_DP_TIMING_GEN_CONFIG3 0x278 +#define HIBMC_DP_HDCP_CFG 0x600 #define HIBMC_DP_DPTX_RST_CTRL 0x700 +#define HIBMC_DP_DPTX_CLK_CTRL 0x704 #define HIBMC_DP_DPTX_GCTL0 0x708 +#define HIBMC_DP_INTR_ENABLE 0x720 +#define HIBMC_DP_INTR_ORIGINAL_STATUS 0x728 +#define HIBMC_DP_TIMING_MODEL_CTRL 0x884 +#define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0 #define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1) #define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2) @@ -31,5 +49,28 @@ #define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4) #define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0) #define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4) +#define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0) +#define HIBMC_DP_CFG_TIMING_GEN0_VACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_TIMING_GEN0_VBLANK GENMASK(15, 0) +#define HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HBLANK GENMASK(15, 0) +#define HIBMC_DP_CFG_STREAM_HSYNC_WIDTH GENMASK(15, 0) +#define HIBMC_DP_CFG_STREAM_VACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_VBLANK GENMASK(15, 0) +#define HIBMC_DP_CFG_STREAM_VFRONT_PORCH GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_VSYNC_WIDTH GENMASK(15, 0) +#define HIBMC_DP_CFG_STREAM_VSTART GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HSTART GENMASK(15, 0) +#define HIBMC_DP_CFG_STREAM_VSYNC_POLARITY BIT(8) +#define HIBMC_DP_CFG_STREAM_HSYNC_POLARITY BIT(7) +#define HIBMC_DP_CFG_STREAM_RGB_ENABLE BIT(1) +#define HIBMC_DP_CFG_STREAM_VIDEO_MAPPING GENMASK(5, 2) +#define HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1 GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE GENMASK(5, 0) +#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE GENMASK(9, 6) +#define HIBMC_DP_CFG_STREAM_HTOTAL_SIZE GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0) #endif From 9ea9629689608f028df633caa752bf76382437ae Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Fri, 14 Mar 2025 15:34:26 +0800 Subject: [PATCH 032/107] drm/hisilicon/hibmc: refactored struct hibmc_drm_private [Upstream commit 587013d72c1a217ced9f42a9a08c8013052cabfc] Refactored struct hibmc_drm_private to separate VGA module from generic struct. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tian Tao Link: https://patchwork.freedesktop.org/patch/msgid/20250103093824.1963816-5-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 16 +++---- .../gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c | 42 +++++++++---------- .../gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c | 20 ++++----- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index f957552c6c507..3ab894f59a739 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -21,9 +21,10 @@ #include #include -struct hibmc_connector { - struct drm_connector base; - +struct hibmc_vdac { + struct drm_device *dev; + struct drm_encoder encoder; + struct drm_connector connector; struct i2c_adapter adapter; struct i2c_algo_bit_data bit_data; }; @@ -36,13 +37,12 @@ struct hibmc_drm_private { struct drm_device dev; struct drm_plane primary_plane; struct drm_crtc crtc; - struct drm_encoder encoder; - struct hibmc_connector connector; + struct hibmc_vdac vdac; }; -static inline struct hibmc_connector *to_hibmc_connector(struct drm_connector *connector) +static inline struct hibmc_vdac *to_hibmc_vdac(struct drm_connector *connector) { - return container_of(connector, struct hibmc_connector, base); + return container_of(connector, struct hibmc_vdac, connector); } static inline struct hibmc_drm_private *to_hibmc_drm_private(struct drm_device *dev) @@ -59,6 +59,6 @@ int hibmc_de_init(struct hibmc_drm_private *priv); int hibmc_vdac_init(struct hibmc_drm_private *priv); int hibmc_mm_init(struct hibmc_drm_private *hibmc); -int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_connector *connector); +int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *connector); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c index 410bd019bb357..99b3b77b5445f 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c @@ -25,8 +25,8 @@ static void hibmc_set_i2c_signal(void *data, u32 mask, int value) { - struct hibmc_connector *hibmc_connector = data; - struct hibmc_drm_private *priv = to_hibmc_drm_private(hibmc_connector->base.dev); + struct hibmc_vdac *vdac = data; + struct hibmc_drm_private *priv = to_hibmc_drm_private(vdac->connector.dev); u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION); if (value) { @@ -45,8 +45,8 @@ static void hibmc_set_i2c_signal(void *data, u32 mask, int value) static int hibmc_get_i2c_signal(void *data, u32 mask) { - struct hibmc_connector *hibmc_connector = data; - struct hibmc_drm_private *priv = to_hibmc_drm_private(hibmc_connector->base.dev); + struct hibmc_vdac *vdac = data; + struct hibmc_drm_private *priv = to_hibmc_drm_private(vdac->connector.dev); u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION); if ((tmp_dir & mask) != mask) { @@ -77,23 +77,21 @@ static int hibmc_ddc_getscl(void *data) return hibmc_get_i2c_signal(data, I2C_SCL_MASK); } -int hibmc_ddc_create(struct drm_device *drm_dev, - struct hibmc_connector *connector) +int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *vdac) { - connector->adapter.owner = THIS_MODULE; - connector->adapter.class = I2C_CLASS_DDC; - snprintf(connector->adapter.name, I2C_NAME_SIZE, "HIS i2c bit bus"); - connector->adapter.dev.parent = drm_dev->dev; - i2c_set_adapdata(&connector->adapter, connector); - connector->adapter.algo_data = &connector->bit_data; - - connector->bit_data.udelay = 20; - connector->bit_data.timeout = usecs_to_jiffies(2000); - connector->bit_data.data = connector; - connector->bit_data.setsda = hibmc_ddc_setsda; - connector->bit_data.setscl = hibmc_ddc_setscl; - connector->bit_data.getsda = hibmc_ddc_getsda; - connector->bit_data.getscl = hibmc_ddc_getscl; - - return i2c_bit_add_bus(&connector->adapter); + vdac->adapter.owner = THIS_MODULE; + snprintf(vdac->adapter.name, I2C_NAME_SIZE, "HIS i2c bit bus"); + vdac->adapter.dev.parent = drm_dev->dev; + i2c_set_adapdata(&vdac->adapter, vdac); + vdac->adapter.algo_data = &vdac->bit_data; + + vdac->bit_data.udelay = 20; + vdac->bit_data.timeout = usecs_to_jiffies(2000); + vdac->bit_data.data = vdac; + vdac->bit_data.setsda = hibmc_ddc_setsda; + vdac->bit_data.setscl = hibmc_ddc_setscl; + vdac->bit_data.getsda = hibmc_ddc_getsda; + vdac->bit_data.getscl = hibmc_ddc_getscl; + + return i2c_bit_add_bus(&vdac->adapter); } diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c index 7a2e2588f64fd..db3d1591c56f8 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c @@ -23,11 +23,11 @@ static int hibmc_connector_get_modes(struct drm_connector *connector) { - struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector); + struct hibmc_vdac *vdac = to_hibmc_vdac(connector); const struct drm_edid *drm_edid; int count; - drm_edid = drm_edid_read_ddc(connector, &hibmc_connector->adapter); + drm_edid = drm_edid_read_ddc(connector, &vdac->adapter); drm_edid_connector_update(connector, drm_edid); @@ -50,9 +50,9 @@ static int hibmc_connector_get_modes(struct drm_connector *connector) static void hibmc_connector_destroy(struct drm_connector *connector) { - struct hibmc_connector *hibmc_connector = to_hibmc_connector(connector); + struct hibmc_vdac *vdac = to_hibmc_vdac(connector); - i2c_del_adapter(&hibmc_connector->adapter); + i2c_del_adapter(&vdac->adapter); drm_connector_cleanup(connector); } @@ -92,20 +92,20 @@ static const struct drm_encoder_helper_funcs hibmc_encoder_helper_funcs = { int hibmc_vdac_init(struct hibmc_drm_private *priv) { struct drm_device *dev = &priv->dev; - struct hibmc_connector *hibmc_connector = &priv->connector; - struct drm_encoder *encoder = &priv->encoder; + struct hibmc_vdac *vdac = &priv->vdac; + struct drm_encoder *encoder = &vdac->encoder; struct drm_crtc *crtc = &priv->crtc; - struct drm_connector *connector = &hibmc_connector->base; + struct drm_connector *connector = &vdac->connector; int ret; - ret = hibmc_ddc_create(dev, hibmc_connector); + ret = hibmc_ddc_create(dev, vdac); if (ret) { drm_err(dev, "failed to create ddc: %d\n", ret); return ret; } encoder->possible_crtcs = drm_crtc_mask(crtc); - ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DAC); + ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_DAC, NULL); if (ret) { drm_err(dev, "failed to init encoder: %d\n", ret); return ret; @@ -116,7 +116,7 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv) ret = drm_connector_init_with_ddc(dev, connector, &hibmc_connector_funcs, DRM_MODE_CONNECTOR_VGA, - &hibmc_connector->adapter); + &vdac->adapter); if (ret) { drm_err(dev, "failed to init connector: %d\n", ret); return ret; From 47f1b7e9544e3d48ec654231ffcbd9090dbe3fd7 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Fri, 14 Mar 2025 15:34:27 +0800 Subject: [PATCH 033/107] drm/hisilicon/hibmc: add dp module in hibmc [Upstream commit 0ab6ea261c1fe4edbf4d99dfe65d8ebaae905092] To support DP interface displaying in hibmc driver. Add a encoder and connector for DP modual. The HPD function and get_edid function will be add in next series, so temporarily using 1024x768 as default in hibmc_dp_connector_get_modes() Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Reviewed-by: Tian Tao Link: https://patchwork.freedesktop.org/patch/msgid/20250103093824.1963816-6-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Makefile | 2 +- .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 118 ++++++++++++++++++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 14 +++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 3 + 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile index 214228052ccf4..95a4ed599d980 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \ - dp/dp_aux.o dp/dp_link.o dp/dp_hw.o + dp/dp_aux.o dp/dp_link.o dp/dp_hw.o hibmc_drm_dp.o obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c new file mode 100644 index 0000000000000..603d6b198a540 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2024 Hisilicon Limited. + +#include + +#include +#include +#include +#include +#include +#include + +#include "hibmc_drm_drv.h" +#include "dp/dp_hw.h" + +static int hibmc_dp_connector_get_modes(struct drm_connector *connector) +{ + int count; + + count = drm_add_modes_noedid(connector, connector->dev->mode_config.max_width, + connector->dev->mode_config.max_height); + drm_set_preferred_mode(connector, 1024, 768); // temporary implementation + + return count; +} + +static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { + .get_modes = hibmc_dp_connector_get_modes, +}; + +static const struct drm_connector_funcs hibmc_dp_conn_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static inline int hibmc_dp_prepare(struct hibmc_dp *dp, struct drm_display_mode *mode) +{ + int ret; + + hibmc_dp_display_en(dp, false); + + ret = hibmc_dp_mode_set(dp, mode); + if (ret) + drm_err(dp->drm_dev, "hibmc dp mode set failed: %d\n", ret); + + return ret; +} + +static void hibmc_dp_encoder_enable(struct drm_encoder *drm_encoder, + struct drm_atomic_state *state) +{ + struct hibmc_dp *dp = container_of(drm_encoder, struct hibmc_dp, encoder); + struct drm_display_mode *mode = &drm_encoder->crtc->state->mode; + + if (hibmc_dp_prepare(dp, mode)) + return; + + hibmc_dp_display_en(dp, true); +} + +static void hibmc_dp_encoder_disable(struct drm_encoder *drm_encoder, + struct drm_atomic_state *state) +{ + struct hibmc_dp *dp = container_of(drm_encoder, struct hibmc_dp, encoder); + + hibmc_dp_display_en(dp, false); +} + +static const struct drm_encoder_helper_funcs hibmc_dp_encoder_helper_funcs = { + .atomic_enable = hibmc_dp_encoder_enable, + .atomic_disable = hibmc_dp_encoder_disable, +}; + +int hibmc_dp_init(struct hibmc_drm_private *priv) +{ + struct drm_device *dev = &priv->dev; + struct drm_crtc *crtc = &priv->crtc; + struct hibmc_dp *dp = &priv->dp; + struct drm_connector *connector = &dp->connector; + struct drm_encoder *encoder = &dp->encoder; + int ret; + + dp->mmio = priv->mmio; + dp->drm_dev = dev; + + ret = hibmc_dp_hw_init(&priv->dp); + if (ret) { + drm_err(dev, "hibmc dp hw init failed: %d\n", ret); + return ret; + } + + hibmc_dp_display_en(&priv->dp, false); + + encoder->possible_crtcs = drm_crtc_mask(crtc); + ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL); + if (ret) { + drm_err(dev, "init dp encoder failed: %d\n", ret); + return ret; + } + + drm_encoder_helper_add(encoder, &hibmc_dp_encoder_helper_funcs); + + ret = drm_connector_init(dev, connector, &hibmc_dp_conn_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + drm_err(dev, "init dp connector failed: %d\n", ret); + return ret; + } + + drm_connector_helper_add(connector, &hibmc_dp_conn_helper_funcs); + + drm_connector_attach_encoder(connector, encoder); + + return 0; +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 96f960bcfd82d..cb54751e26b90 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -27,6 +27,10 @@ #include "hibmc_drm_drv.h" #include "hibmc_drm_regs.h" +#define HIBMC_DP_HOST_SERDES_CTRL 0x1f001c +#define HIBMC_DP_HOST_SERDES_CTRL_VAL 0x8a00 +#define HIBMC_DP_HOST_SERDES_CTRL_MASK 0x7ffff + DEFINE_DRM_GEM_FOPS(hibmc_fops); static irqreturn_t hibmc_interrupt(int irq, void *arg) @@ -116,6 +120,14 @@ static int hibmc_kms_init(struct hibmc_drm_private *priv) return ret; } + /* if DP existed, init DP */ + if ((readl(priv->mmio + HIBMC_DP_HOST_SERDES_CTRL) & + HIBMC_DP_HOST_SERDES_CTRL_MASK) == HIBMC_DP_HOST_SERDES_CTRL_VAL) { + ret = hibmc_dp_init(priv); + if (ret) + drm_err(dev, "failed to init dp: %d\n", ret); + } + ret = hibmc_vdac_init(priv); if (ret) { drm_err(dev, "failed to init vdac: %d\n", ret); @@ -326,6 +338,8 @@ static int hibmc_pci_probe(struct pci_dev *pdev, goto err_return; } + pci_set_master(pdev); + ret = hibmc_load(dev); if (ret) { drm_err(dev, "failed to load hibmc: %d\n", ret); diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index 3ab894f59a739..431f3c6fd1d2a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -20,6 +20,7 @@ #include #include +#include "dp/dp_hw.h" struct hibmc_vdac { struct drm_device *dev; @@ -38,6 +39,7 @@ struct hibmc_drm_private { struct drm_plane primary_plane; struct drm_crtc crtc; struct hibmc_vdac vdac; + struct hibmc_dp dp; }; static inline struct hibmc_vdac *to_hibmc_vdac(struct drm_connector *connector) @@ -60,5 +62,6 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv); int hibmc_mm_init(struct hibmc_drm_private *hibmc); int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *connector); +int hibmc_dp_init(struct hibmc_drm_private *priv); #endif From cb5fa546310847f8bb273b8c44d9cd351ca887c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 16 Apr 2025 16:24:55 +0800 Subject: [PATCH 034/107] drm/sysfs: Register "ddc" symlink later MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Upstream commit 21b6c2812f3d9cbcfce9eca76632e2cc8fef214f] Currently drm_sysfs_connector_add() attempts to register the "ddc" symlink (based one connector->ddc) before the driver's .early_register() hook has been called. That is too early for i915 which only fully registers the aux ch and associated i2c bus from said hook (to prevent half initialized stuff getting exposed to userspace). This causes my attempt at using drm_connector_init_with_ddc() to fail, and the entire connector disappears from sysfs on account of sysfs_create_link() failing. To fix that split the sysfs symlink stuff into separate functions (drm_sysfs_connector_add_late() and drm_sysfs_connector_remove_early()) which are called on the opposite side of the .later_register() and .early_unregister() hooks. Cc: Andrzej Pietrasiewicz Cc: Daniel Vetter Cc: Andrzej Hajda Cc: Emil Velikov Cc: Sam Ravnborg Cc: Neil Armstrong Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20230829113920.13713-3-ville.syrjala@linux.intel.com Reviewed-by: Jani Nikula Acked-by: Thomas Zimmermann #irc Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/drm_connector.c | 9 +++++++++ drivers/gpu/drm/drm_internal.h | 2 ++ drivers/gpu/drm/drm_sysfs.c | 22 +++++++++++++++------- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 573086d88fd26..1c0635c1fc03f 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -632,6 +632,10 @@ int drm_connector_register(struct drm_connector *connector) goto err_debugfs; } + ret = drm_sysfs_connector_add_late(connector); + if (ret) + goto err_late_register; + drm_mode_object_register(connector->dev, &connector->base); connector->registration_state = DRM_CONNECTOR_REGISTERED; @@ -648,6 +652,9 @@ int drm_connector_register(struct drm_connector *connector) mutex_unlock(&connector_list_lock); goto unlock; +err_late_register: + if (connector->funcs->early_unregister) + connector->funcs->early_unregister(connector); err_debugfs: drm_debugfs_connector_remove(connector); drm_sysfs_connector_remove(connector); @@ -682,6 +689,8 @@ void drm_connector_unregister(struct drm_connector *connector) connector->privacy_screen, &connector->privacy_screen_notifier); + drm_sysfs_connector_remove_early(connector); + if (connector->funcs->early_unregister) connector->funcs->early_unregister(connector); diff --git a/drivers/gpu/drm/drm_internal.h b/drivers/gpu/drm/drm_internal.h index 8d433fe37e8fa..9edc2b87ea0d3 100644 --- a/drivers/gpu/drm/drm_internal.h +++ b/drivers/gpu/drm/drm_internal.h @@ -149,6 +149,8 @@ int drm_sysfs_init(void); void drm_sysfs_destroy(void); struct device *drm_sysfs_minor_alloc(struct drm_minor *minor); int drm_sysfs_connector_add(struct drm_connector *connector); +int drm_sysfs_connector_add_late(struct drm_connector *connector); +void drm_sysfs_connector_remove_early(struct drm_connector *connector); void drm_sysfs_connector_remove(struct drm_connector *connector); void drm_sysfs_lease_event(struct drm_device *dev); diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index b169b3e44a921..a953f69a34b6e 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -400,10 +400,6 @@ int drm_sysfs_connector_add(struct drm_connector *connector) drm_err(dev, "failed to add component to create link to typec connector\n"); } - if (connector->ddc) - return sysfs_create_link(&connector->kdev->kobj, - &connector->ddc->dev.kobj, "ddc"); - return 0; err_free: @@ -411,13 +407,25 @@ int drm_sysfs_connector_add(struct drm_connector *connector) return r; } -void drm_sysfs_connector_remove(struct drm_connector *connector) +int drm_sysfs_connector_add_late(struct drm_connector *connector) { - if (!connector->kdev) - return; + if (connector->ddc) + return sysfs_create_link(&connector->kdev->kobj, + &connector->ddc->dev.kobj, "ddc"); + + return 0; +} +void drm_sysfs_connector_remove_early(struct drm_connector *connector) +{ if (connector->ddc) sysfs_remove_link(&connector->kdev->kobj, "ddc"); +} + +void drm_sysfs_connector_remove(struct drm_connector *connector) +{ + if (!connector->kdev) + return; if (dev_fwnode(connector->kdev)) component_del(connector->kdev, &typec_connector_ops); From a34374b30f2e3e6db324ecb40b69371c95bd74aa Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 16 Apr 2025 16:24:56 +0800 Subject: [PATCH 035/107] drm/ast: Implement polling for VGA and SIL164 connectors [Upstream commit 225a8d0bd93eb87fe49947069075260031bad8af] Implement polling for VGA and SIL164 connectors. Set the flag DRM_CONNECTOR_POLL_DISCONNECT for each to detect the removal of the monitor cable. Implement struct drm_connector_helper_funcs.detect_ctx for each type of connector by testing for EDID data. The helper drm_connector_helper_detect_ctx() implements .detect_ctx() on top of the connector's DDC channel. The function can be used by other drivers as companion to drm_connector_helper_get_modes(). v6: - change helper name to drm_connector_helper_detec_from_ddc() (Maxime, Sui) v5: - share implementation in drm_connector_helper_detect_ctx() (Maxime) - test for DDC presence with drm_probe_ddc() (Maxime, Jani) Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Acked-by: Sui Jingfeng Acked-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20240325200855.21150-13-tzimmermann@suse.de Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/drm_probe_helper.c | 29 +++++++++++++++++++++++++++++ include/drm/drm_probe_helper.h | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index c90afb5d08987..03a3fb8034d85 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -1313,3 +1313,32 @@ int drm_connector_helper_tv_get_modes(struct drm_connector *connector) return i; } EXPORT_SYMBOL(drm_connector_helper_tv_get_modes); + +/** + * drm_connector_helper_detect_from_ddc - Read EDID and detect connector status. + * @connector: The connector + * @ctx: Acquire context + * @force: Perform screen-destructive operations, if necessary + * + * Detects the connector status by reading the EDID using drm_probe_ddc(), + * which requires connector->ddc to be set. Returns connector_status_connected + * on success or connector_status_disconnected on failure. + * + * Returns: + * The connector status as defined by enum drm_connector_status. + */ +int drm_connector_helper_detect_from_ddc(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct i2c_adapter *ddc = connector->ddc; + + if (!ddc) + return connector_status_unknown; + + if (drm_probe_ddc(ddc)) + return connector_status_connected; + + return connector_status_disconnected; +} +EXPORT_SYMBOL(drm_connector_helper_detect_from_ddc); diff --git a/include/drm/drm_probe_helper.h b/include/drm/drm_probe_helper.h index fad3c4003b2b5..4b7bc31a3686f 100644 --- a/include/drm/drm_probe_helper.h +++ b/include/drm/drm_probe_helper.h @@ -38,4 +38,8 @@ int drm_connector_helper_get_modes_fixed(struct drm_connector *connector, int drm_connector_helper_get_modes(struct drm_connector *connector); int drm_connector_helper_tv_get_modes(struct drm_connector *connector); +int drm_connector_helper_detect_from_ddc(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force); + #endif From fb3a00e197698a3a42353a0e5608a1ba65c80d97 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Wed, 16 Apr 2025 16:24:57 +0800 Subject: [PATCH 036/107] drm: Call drm_atomic_helper_shutdown() at shutdown time for misc drivers [Upstream commit ce3d99c8349584bc0fbe1e21918a3ea1155343aa] Based on grepping through the source code these drivers appear to be missing a call to drm_atomic_helper_shutdown() at system shutdown time. Among other things, this means that if a panel is in use that it won't be cleanly powered off at system shutdown time. The fact that we should call drm_atomic_helper_shutdown() in the case of OS shutdown/restart comes straight out of the kernel doc "driver instance overview" in drm_drv.c. All of the drivers in this patch were fairly straightforward to fix since they already had a call to drm_atomic_helper_shutdown() at remove/unbind time but were just lacking one at system shutdown. The only hitch is that some of these drivers use the component model to register/unregister their DRM devices. The shutdown callback is part of the original device. The typical solution here, based on how other DRM drivers do this, is to keep track of whether the device is bound based on drvdata. In most cases the drvdata is the drm_device, so we can just make sure it is NULL when the device is not bound. In some drivers, this required minor code changes. To make things simpler, drm_atomic_helper_shutdown() has been modified to consider a NULL drm_device as a noop in the patch ("drm/atomic-helper: drm_atomic_helper_shutdown(NULL) should be a noop"). Suggested-by: Maxime Ripard Reviewed-by: Tomi Valkeinen Tested-by: Tomi Valkeinen Acked-by: Maxime Ripard Tested-by: Jernej Skrabec Reviewed-by: Jernej Skrabec Reviewed-by: Sui Jingfeng Tested-by: Sui Jingfeng Signed-off-by: Douglas Anderson Link: https://patchwork.freedesktop.org/patch/msgid/20230901163944.RFT.2.I9115e5d094a43e687978b0699cc1fe9f2a3452ea@changeid Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index cb54751e26b90..8a73fcdfd0bc3 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -371,6 +371,11 @@ static void hibmc_pci_remove(struct pci_dev *pdev) hibmc_unload(dev); } +static void hibmc_pci_shutdown(struct pci_dev *pdev) +{ + drm_atomic_helper_shutdown(pci_get_drvdata(pdev)); +} + static const struct pci_device_id hibmc_pci_table[] = { { PCI_VDEVICE(HUAWEI, 0x1711) }, {0,} @@ -381,6 +386,7 @@ static struct pci_driver hibmc_pci_driver = { .id_table = hibmc_pci_table, .probe = hibmc_pci_probe, .remove = hibmc_pci_remove, + .shutdown = hibmc_pci_shutdown, .driver.pm = &hibmc_pm_ops, }; From ca89d3585718c0a58284c305eb3fdfbee2c6402c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 16 Apr 2025 16:24:58 +0800 Subject: [PATCH 037/107] drm/hisilicon/hibmc: select CONFIG_DRM_DISPLAY_DP_HELPER [Upstream commit 9ab127a18018fb06bd42a54ed38bb7b8c449d686] Without the DP helper code, the newly added displayport support causes a link failure: x86_64-linux-ld: drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.o: in function `hibmc_dp_aux_init': dp_aux.c:(.text+0x37e): undefined reference to `drm_dp_aux_init' x86_64-linux-ld: drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.o: in function `hibmc_dp_link_set_pattern': dp_link.c:(.text+0xae): undefined reference to `drm_dp_dpcd_write' x86_64-linux-ld: drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.o: in function `hibmc_dp_link_get_adjust_train': dp_link.c:(.text+0x121): undefined reference to `drm_dp_get_adjust_request_voltage' x86_64-linux-ld: dp_link.c:(.text+0x12e): undefined reference to `drm_dp_get_adjust_request_pre_emphasis' x86_64-linux-ld: drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.o: in function `hibmc_dp_link_training': dp_link.c:(.text+0x2b0): undefined reference to `drm_dp_dpcd_write' x86_64-linux-ld: dp_link.c:(.text+0x2e3): undefined reference to `drm_dp_dpcd_write' Add both DRM_DISPLAY_DP_HELPER and DRM_DISPLAY_HELPER, which is in turn required by the former. Fixes: 8d6349a4232a ("drm/hisilicon/hibmc: add dp module in hibmc") Signed-off-by: Arnd Bergmann Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20250127071059.617567-1-arnd@kernel.org Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/hisilicon/hibmc/Kconfig b/drivers/gpu/drm/hisilicon/hibmc/Kconfig index 126504318a4f9..8f1f666e30afb 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Kconfig +++ b/drivers/gpu/drm/hisilicon/hibmc/Kconfig @@ -3,6 +3,8 @@ config DRM_HISI_HIBMC tristate "DRM Support for Hisilicon Hibmc" depends on DRM && PCI && (ARM64 || COMPILE_TEST) depends on MMU + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_HELPER select DRM_KMS_HELPER select DRM_VRAM_HELPER select DRM_TTM From 97e99e71da9625401fe5e12015a15bdecdbf0ed2 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:24:59 +0800 Subject: [PATCH 038/107] drm/hisilicon/hibmc: Restructuring the header dp_reg.h [Upstream commit f9698f802e50fbe696b3ac6f82c0e966574a3edb] Move the macros below their corresponding registers to make them more obvious. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-2-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 98 +++++++++++++-------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 4a515c726d524..dc2bd3f80b70d 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -5,72 +5,94 @@ #define DP_REG_H #define HIBMC_DP_AUX_CMD_ADDR 0x50 + #define HIBMC_DP_AUX_WR_DATA0 0x54 #define HIBMC_DP_AUX_WR_DATA1 0x58 #define HIBMC_DP_AUX_WR_DATA2 0x5c #define HIBMC_DP_AUX_WR_DATA3 0x60 #define HIBMC_DP_AUX_RD_DATA0 0x64 + #define HIBMC_DP_AUX_REQ 0x74 +#define HIBMC_DP_CFG_AUX_REQ BIT(0) +#define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1) +#define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2) +#define HIBMC_DP_CFG_AUX_MIN_PULSE_NUM GENMASK(13, 9) + #define HIBMC_DP_AUX_STATUS 0x78 +#define HIBMC_DP_CFG_AUX_TIMEOUT BIT(0) +#define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4) +#define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12) +#define HIBMC_DP_CFG_AUX GENMASK(24, 17) + #define HIBMC_DP_PHYIF_CTRL0 0xa0 +#define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0) +#define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4) +#define HIBMC_DP_CFG_LANE_DATA_EN GENMASK(11, 8) + #define HIBMC_DP_VIDEO_CTRL 0x100 +#define HIBMC_DP_CFG_STREAM_RGB_ENABLE BIT(1) +#define HIBMC_DP_CFG_STREAM_VIDEO_MAPPING GENMASK(5, 2) +#define HIBMC_DP_CFG_STREAM_FRAME_MODE BIT(6) +#define HIBMC_DP_CFG_STREAM_HSYNC_POLARITY BIT(7) +#define HIBMC_DP_CFG_STREAM_VSYNC_POLARITY BIT(8) + #define HIBMC_DP_VIDEO_CONFIG0 0x104 +#define HIBMC_DP_CFG_STREAM_HACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HBLANK GENMASK(15, 0) + #define HIBMC_DP_VIDEO_CONFIG1 0x108 +#define HIBMC_DP_CFG_STREAM_VACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_VBLANK GENMASK(15, 0) + #define HIBMC_DP_VIDEO_CONFIG2 0x10c +#define HIBMC_DP_CFG_STREAM_HSYNC_WIDTH GENMASK(15, 0) + #define HIBMC_DP_VIDEO_CONFIG3 0x110 +#define HIBMC_DP_CFG_STREAM_VSYNC_WIDTH GENMASK(15, 0) +#define HIBMC_DP_CFG_STREAM_VFRONT_PORCH GENMASK(31, 16) + #define HIBMC_DP_VIDEO_PACKET 0x114 +#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE GENMASK(5, 0) +#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE GENMASK(9, 6) + #define HIBMC_DP_VIDEO_MSA0 0x118 +#define HIBMC_DP_CFG_STREAM_VSTART GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HSTART GENMASK(15, 0) + #define HIBMC_DP_VIDEO_MSA1 0x11c #define HIBMC_DP_VIDEO_MSA2 0x120 + #define HIBMC_DP_VIDEO_HORIZONTAL_SIZE 0X124 +#define HIBMC_DP_CFG_STREAM_HTOTAL_SIZE GENMASK(31, 16) +#define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0) + #define HIBMC_DP_TIMING_GEN_CONFIG0 0x26c +#define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0) + #define HIBMC_DP_TIMING_GEN_CONFIG2 0x274 +#define HIBMC_DP_CFG_TIMING_GEN0_VACTIVE GENMASK(31, 16) +#define HIBMC_DP_CFG_TIMING_GEN0_VBLANK GENMASK(15, 0) + #define HIBMC_DP_TIMING_GEN_CONFIG3 0x278 +#define HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH GENMASK(31, 16) + #define HIBMC_DP_HDCP_CFG 0x600 + #define HIBMC_DP_DPTX_RST_CTRL 0x700 +#define HIBMC_DP_CFG_AUX_RST_N BIT(4) + #define HIBMC_DP_DPTX_CLK_CTRL 0x704 + #define HIBMC_DP_DPTX_GCTL0 0x708 +#define HIBMC_DP_CFG_PHY_LANE_NUM GENMASK(2, 1) + #define HIBMC_DP_INTR_ENABLE 0x720 #define HIBMC_DP_INTR_ORIGINAL_STATUS 0x728 -#define HIBMC_DP_TIMING_MODEL_CTRL 0x884 -#define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0 -#define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1) -#define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2) -#define HIBMC_DP_CFG_STREAM_FRAME_MODE BIT(6) -#define HIBMC_DP_CFG_AUX_MIN_PULSE_NUM GENMASK(13, 9) -#define HIBMC_DP_CFG_LANE_DATA_EN GENMASK(11, 8) -#define HIBMC_DP_CFG_PHY_LANE_NUM GENMASK(2, 1) -#define HIBMC_DP_CFG_AUX_REQ BIT(0) -#define HIBMC_DP_CFG_AUX_RST_N BIT(4) -#define HIBMC_DP_CFG_AUX_TIMEOUT BIT(0) -#define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12) -#define HIBMC_DP_CFG_AUX GENMASK(24, 17) -#define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4) -#define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0) -#define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4) -#define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16) -#define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0) -#define HIBMC_DP_CFG_TIMING_GEN0_VACTIVE GENMASK(31, 16) -#define HIBMC_DP_CFG_TIMING_GEN0_VBLANK GENMASK(15, 0) -#define HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_HACTIVE GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_HBLANK GENMASK(15, 0) -#define HIBMC_DP_CFG_STREAM_HSYNC_WIDTH GENMASK(15, 0) -#define HIBMC_DP_CFG_STREAM_VACTIVE GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_VBLANK GENMASK(15, 0) -#define HIBMC_DP_CFG_STREAM_VFRONT_PORCH GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_VSYNC_WIDTH GENMASK(15, 0) -#define HIBMC_DP_CFG_STREAM_VSTART GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_HSTART GENMASK(15, 0) -#define HIBMC_DP_CFG_STREAM_VSYNC_POLARITY BIT(8) -#define HIBMC_DP_CFG_STREAM_HSYNC_POLARITY BIT(7) -#define HIBMC_DP_CFG_STREAM_RGB_ENABLE BIT(1) -#define HIBMC_DP_CFG_STREAM_VIDEO_MAPPING GENMASK(5, 2) +#define HIBMC_DP_TIMING_MODEL_CTRL 0x884 #define HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1 GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE GENMASK(5, 0) -#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE GENMASK(9, 6) -#define HIBMC_DP_CFG_STREAM_HTOTAL_SIZE GENMASK(31, 16) -#define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0) + +#define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0 #endif From 12e2629a8e9a8c300c4e2063c5fc77a765246a89 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:00 +0800 Subject: [PATCH 039/107] drm/hisilicon/hibmc: Add dp serdes cfg to adjust serdes rate, voltage and pre-emphasis [Upstream commit 9e736cd444f49efa2334e405f7a59773ea02155b] This dp controller need features of digital-to-analog conversion and high-speed transmission in chip by its extern serdes controller. Our serdes cfg is relatively simple, just need two register configurations. Don't need too much functions, like: power on/off, initialize, and some complex configurations, so I'm not going to use the phy framework. This serdes is inited and configured in dp initialization, and also integrating them into link training process. For rate changing, we can change from 1.62-8.2Gpbs by cfg reg. For voltage and pre-emphasis levels changing, we can cfg different serdes ffe value. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-3-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Makefile | 2 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h | 4 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 5 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 23 ++++++ .../gpu/drm/hisilicon/hibmc/dp/dp_serdes.c | 71 +++++++++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile index 95a4ed599d980..43de077d6769a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \ - dp/dp_aux.o dp/dp_link.o dp/dp_hw.o hibmc_drm_dp.o + dp/dp_aux.o dp/dp_link.o dp/dp_hw.o dp/dp_serdes.o hibmc_drm_dp.o obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h index 2c52a4476c4dd..e0c6a3b7463b8 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h @@ -38,6 +38,7 @@ struct hibmc_dp_dev { struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */ struct hibmc_dp_link link; u8 dpcd[DP_RECEIVER_CAP_SIZE]; + void __iomem *serdes_base; }; #define dp_field_modify(reg_value, mask, val) \ @@ -59,5 +60,8 @@ struct hibmc_dp_dev { void hibmc_dp_aux_init(struct hibmc_dp_dev *dp); int hibmc_dp_link_training(struct hibmc_dp_dev *dp); +int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp); +int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp); +int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX]); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index a8d543881c099..3612f3c5ab231 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -151,6 +151,7 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) { struct drm_device *drm_dev = dp->drm_dev; struct hibmc_dp_dev *dp_dev; + int ret; dp_dev = devm_kzalloc(drm_dev->dev, sizeof(struct hibmc_dp_dev), GFP_KERNEL); if (!dp_dev) @@ -165,6 +166,10 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) hibmc_dp_aux_init(dp_dev); + ret = hibmc_dp_serdes_init(dp_dev); + if (ret) + return ret; + dp_dev->link.cap.lanes = 0x2; dp_dev->link.cap.link_rate = DP_LINK_BW_2_7; diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index dc2bd3f80b70d..16ea589035980 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -95,4 +95,27 @@ #define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0 +/* dp serdes reg */ +#define HIBMC_DP_HOST_OFFSET 0x10000 +#define HIBMC_DP_LANE0_RATE_OFFSET 0x4 +#define HIBMC_DP_LANE1_RATE_OFFSET 0xc +#define HIBMC_DP_LANE_STATUS_OFFSET 0x10 +#define HIBMC_DP_PMA_LANE0_OFFSET 0x18 +#define HIBMC_DP_PMA_LANE1_OFFSET 0x1c +#define HIBMC_DP_PMA_TXDEEMPH GENMASK(18, 1) +#define DP_SERDES_DONE 0x3 + +/* dp serdes TX-Deempth Configuration */ +#define DP_SERDES_VOL0_PRE0 0x280 +#define DP_SERDES_VOL0_PRE1 0x2300 +#define DP_SERDES_VOL0_PRE2 0x53c0 +#define DP_SERDES_VOL0_PRE3 0x8400 +#define DP_SERDES_VOL1_PRE0 0x380 +#define DP_SERDES_VOL1_PRE1 0x3440 +#define DP_SERDES_VOL1_PRE2 0x6480 +#define DP_SERDES_VOL2_PRE0 0x4c1 +#define DP_SERDES_VOL2_PRE1 0x4500 +#define DP_SERDES_VOL3_PRE0 0x600 +#define DP_SERDES_BW_8_1 0x3 + #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c new file mode 100644 index 0000000000000..676059d4c1e60 --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Hisilicon Limited. + +#include +#include +#include +#include "dp_comm.h" +#include "dp_config.h" +#include "dp_reg.h" + +int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX]) +{ + static const u32 serdes_tx_cfg[4][4] = { {DP_SERDES_VOL0_PRE0, DP_SERDES_VOL0_PRE1, + DP_SERDES_VOL0_PRE2, DP_SERDES_VOL0_PRE3}, + {DP_SERDES_VOL1_PRE0, DP_SERDES_VOL1_PRE1, + DP_SERDES_VOL1_PRE2}, {DP_SERDES_VOL2_PRE0, + DP_SERDES_VOL2_PRE1}, {DP_SERDES_VOL3_PRE0}}; + int cfg[2]; + int i; + + for (i = 0; i < HIBMC_DP_LANE_NUM_MAX; i++) { + cfg[i] = serdes_tx_cfg[FIELD_GET(DP_TRAIN_VOLTAGE_SWING_MASK, train_set[i])] + [FIELD_GET(DP_TRAIN_PRE_EMPHASIS_MASK, train_set[i])]; + if (!cfg[i]) + return -EINVAL; + + /* lane1 offset is 4 */ + writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, cfg[i]), + dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET + i * 4); + } + + usleep_range(300, 500); + + if (readl(dp->serdes_base + HIBMC_DP_LANE_STATUS_OFFSET) != DP_SERDES_DONE) { + drm_dbg_dp(dp->dev, "dp serdes cfg failed\n"); + return -EAGAIN; + } + + return 0; +} + +int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp) +{ + writel(rate, dp->serdes_base + HIBMC_DP_LANE0_RATE_OFFSET); + writel(rate, dp->serdes_base + HIBMC_DP_LANE1_RATE_OFFSET); + + usleep_range(300, 500); + + if (readl(dp->serdes_base + HIBMC_DP_LANE_STATUS_OFFSET) != DP_SERDES_DONE) { + drm_dbg_dp(dp->dev, "dp serdes rate switching failed\n"); + return -EAGAIN; + } + + if (rate < DP_SERDES_BW_8_1) + drm_dbg_dp(dp->dev, "reducing serdes rate to :%d\n", + rate ? rate * HIBMC_DP_LINK_RATE_CAL * 10 : 162); + + return 0; +} + +int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp) +{ + dp->serdes_base = dp->base + HIBMC_DP_HOST_OFFSET; + + writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0), + dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET); + writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0), + dp->serdes_base + HIBMC_DP_PMA_LANE1_OFFSET); + + return hibmc_dp_serdes_rate_switch(DP_SERDES_BW_8_1, dp); +} From 073839c8f252dbb364c0bf6b2897fdc2a1588ac8 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:01 +0800 Subject: [PATCH 040/107] drm/hisilicon/hibmc: Add dp serdes cfg in dp process [Upstream commit 5f80fb4d6abd1f7f4007e4bf8dd75a8c71d2f724] Add dp serdes cfg in link training process, and related adapting and modificating. Change some init values about training, because we want completely to negotiation process, so we start with the maximum rate and the electrical characteristic level is 0. Because serdes default cfgs is changed and used in hibmc_kms_init(), we changed the if-statement to check whether the value is 0. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-4-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- .../gpu/drm/hisilicon/hibmc/dp/dp_config.h | 1 + drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 5 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c | 70 ++++++++++++++++--- drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 5 ++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 13 ++-- 5 files changed, 77 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h index 74dd9956144e4..c5feef8dc27d1 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h @@ -15,5 +15,6 @@ #define HIBMC_DP_CLK_EN 0x7 #define HIBMC_DP_SYNC_EN_MASK 0x3 #define HIBMC_DP_LINK_RATE_CAL 27 +#define HIBMC_DP_SYNC_DELAY(lanes) ((lanes) == 0x2 ? 86 : 46) #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index 3612f3c5ab231..dcb2ab5ea6bb8 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -72,6 +72,9 @@ static void hibmc_dp_set_sst(struct hibmc_dp_dev *dp, struct drm_display_mode *m HIBMC_DP_CFG_STREAM_HTOTAL_SIZE, htotal_size); hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE, HIBMC_DP_CFG_STREAM_HBLANK_SIZE, hblank_size); + hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET, + HIBMC_DP_CFG_STREAM_SYNC_CALIBRATION, + HIBMC_DP_SYNC_DELAY(dp->link.cap.lanes)); } static void hibmc_dp_link_cfg(struct hibmc_dp_dev *dp, struct drm_display_mode *mode) @@ -171,7 +174,7 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) return ret; dp_dev->link.cap.lanes = 0x2; - dp_dev->link.cap.link_rate = DP_LINK_BW_2_7; + dp_dev->link.cap.link_rate = DP_LINK_BW_8_1; /* hdcp data */ writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c index f6355c16cc0ab..b1fcfaa8354f6 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c @@ -9,6 +9,22 @@ #define HIBMC_EQ_MAX_RETRY 5 +static inline int hibmc_dp_get_serdes_rate_cfg(struct hibmc_dp_dev *dp) +{ + switch (dp->link.cap.link_rate) { + case DP_LINK_BW_1_62: + return DP_SERDES_BW_1_62; + case DP_LINK_BW_2_7: + return DP_SERDES_BW_2_7; + case DP_LINK_BW_5_4: + return DP_SERDES_BW_5_4; + case DP_LINK_BW_8_1: + return DP_SERDES_BW_8_1; + default: + return -EINVAL; + } +} + static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp) { u8 buf[2]; @@ -41,11 +57,7 @@ static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp) return ret >= 0 ? -EIO : ret; } - ret = drm_dp_read_dpcd_caps(&dp->aux, dp->dpcd); - if (ret) - drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret); - - return ret; + return 0; } static int hibmc_dp_link_set_pattern(struct hibmc_dp_dev *dp, int pattern) @@ -108,7 +120,11 @@ static int hibmc_dp_link_training_cr_pre(struct hibmc_dp_dev *dp) return ret; for (i = 0; i < dp->link.cap.lanes; i++) - train_set[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2; + train_set[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0; + + ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set); + if (ret) + return ret; ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, train_set, dp->link.cap.lanes); if (ret != dp->link.cap.lanes) { @@ -137,21 +153,29 @@ static bool hibmc_dp_link_get_adjust_train(struct hibmc_dp_dev *dp, return false; } -static inline int hibmc_dp_link_reduce_rate(struct hibmc_dp_dev *dp) +static int hibmc_dp_link_reduce_rate(struct hibmc_dp_dev *dp) { + int ret; + switch (dp->link.cap.link_rate) { case DP_LINK_BW_2_7: dp->link.cap.link_rate = DP_LINK_BW_1_62; - return 0; + break; case DP_LINK_BW_5_4: dp->link.cap.link_rate = DP_LINK_BW_2_7; - return 0; + break; case DP_LINK_BW_8_1: dp->link.cap.link_rate = DP_LINK_BW_5_4; - return 0; + break; default: return -EINVAL; } + + ret = hibmc_dp_get_serdes_rate_cfg(dp); + if (ret < 0) + return ret; + + return hibmc_dp_serdes_rate_switch(ret, dp); } static inline int hibmc_dp_link_reduce_lane(struct hibmc_dp_dev *dp) @@ -159,6 +183,7 @@ static inline int hibmc_dp_link_reduce_lane(struct hibmc_dp_dev *dp) switch (dp->link.cap.lanes) { case 0x2: dp->link.cap.lanes--; + drm_dbg_dp(dp->dev, "dp link training reduce to 1 lane\n"); break; case 0x1: drm_err(dp->dev, "dp link training reduce lane failed, already reach minimum\n"); @@ -206,6 +231,11 @@ static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp) } level_changed = hibmc_dp_link_get_adjust_train(dp, lane_status); + + ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set); + if (ret) + return ret; + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, dp->link.cap.lanes); if (ret != dp->link.cap.lanes) { @@ -255,6 +285,11 @@ static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp) } hibmc_dp_link_get_adjust_train(dp, lane_status); + + ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set); + if (ret) + return ret; + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, dp->link.cap.lanes); if (ret != dp->link.cap.lanes) { @@ -295,6 +330,21 @@ int hibmc_dp_link_training(struct hibmc_dp_dev *dp) struct hibmc_dp_link *link = &dp->link; int ret; + ret = drm_dp_read_dpcd_caps(&dp->aux, dp->dpcd); + if (ret) + drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret); + + dp->link.cap.link_rate = dp->dpcd[DP_MAX_LINK_RATE]; + dp->link.cap.lanes = 0x2; + + ret = hibmc_dp_get_serdes_rate_cfg(dp); + if (ret < 0) + return ret; + + ret = hibmc_dp_serdes_rate_switch(ret, dp); + if (ret) + return ret; + while (true) { ret = hibmc_dp_link_training_cr_pre(dp); if (ret) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 16ea589035980..6eb76decc636e 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -54,6 +54,7 @@ #define HIBMC_DP_VIDEO_PACKET 0x114 #define HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE GENMASK(5, 0) #define HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE GENMASK(9, 6) +#define HIBMC_DP_CFG_STREAM_SYNC_CALIBRATION GENMASK(31, 20) #define HIBMC_DP_VIDEO_MSA0 0x118 #define HIBMC_DP_CFG_STREAM_VSTART GENMASK(31, 16) @@ -102,6 +103,7 @@ #define HIBMC_DP_LANE_STATUS_OFFSET 0x10 #define HIBMC_DP_PMA_LANE0_OFFSET 0x18 #define HIBMC_DP_PMA_LANE1_OFFSET 0x1c +#define HIBMC_DP_HOST_SERDES_CTRL 0x1f001c #define HIBMC_DP_PMA_TXDEEMPH GENMASK(18, 1) #define DP_SERDES_DONE 0x3 @@ -117,5 +119,8 @@ #define DP_SERDES_VOL2_PRE1 0x4500 #define DP_SERDES_VOL3_PRE0 0x600 #define DP_SERDES_BW_8_1 0x3 +#define DP_SERDES_BW_5_4 0x2 +#define DP_SERDES_BW_2_7 0x1 +#define DP_SERDES_BW_1_62 0x0 #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 8a73fcdfd0bc3..514a573bb2972 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -27,9 +27,7 @@ #include "hibmc_drm_drv.h" #include "hibmc_drm_regs.h" -#define HIBMC_DP_HOST_SERDES_CTRL 0x1f001c -#define HIBMC_DP_HOST_SERDES_CTRL_VAL 0x8a00 -#define HIBMC_DP_HOST_SERDES_CTRL_MASK 0x7ffff +#include "dp/dp_reg.h" DEFINE_DRM_GEM_FOPS(hibmc_fops); @@ -120,9 +118,12 @@ static int hibmc_kms_init(struct hibmc_drm_private *priv) return ret; } - /* if DP existed, init DP */ - if ((readl(priv->mmio + HIBMC_DP_HOST_SERDES_CTRL) & - HIBMC_DP_HOST_SERDES_CTRL_MASK) == HIBMC_DP_HOST_SERDES_CTRL_VAL) { + /* + * If the serdes reg is readable and is not equal to 0, + * DP block exists and initializes it. + */ + ret = readl(priv->mmio + HIBMC_DP_HOST_SERDES_CTRL); + if (ret) { ret = hibmc_dp_init(priv); if (ret) drm_err(dev, "failed to init dp: %d\n", ret); From 34022884e0cdb6fab7a0a2374dd3db73d0d3e587 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:02 +0800 Subject: [PATCH 041/107] drm/hisilicon/hibmc: Refactor the member of drm_aux in struct hibmc_dp [Upstream commit 1e7f35512e77dd7276e91ade4e03807f88b97eb3] Because the drm_aux of struct hibmc_dp_dev's member is not easy to get in hibmc_drm_dp.c, move the drm_aux to struct hibmc_dp. Then there are some adaptations and modifications to make this patch compile. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-5-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c | 13 +++++++----- drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h | 6 ++++-- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 2 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 2 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c | 22 ++++++++++---------- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c index 0a903cce1fa95..ded9e7ce887a1 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c @@ -8,6 +8,7 @@ #include #include "dp_comm.h" #include "dp_reg.h" +#include "dp_hw.h" #define HIBMC_AUX_CMD_REQ_LEN GENMASK(7, 4) #define HIBMC_AUX_CMD_ADDR GENMASK(27, 8) @@ -124,7 +125,8 @@ static int hibmc_dp_aux_parse_xfer(struct hibmc_dp_dev *dp, struct drm_dp_aux_ms /* ret >= 0 ,ret is size; ret < 0, ret is err code */ static ssize_t hibmc_dp_aux_xfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { - struct hibmc_dp_dev *dp = container_of(aux, struct hibmc_dp_dev, aux); + struct hibmc_dp *dp_priv = container_of(aux, struct hibmc_dp, aux); + struct hibmc_dp_dev *dp = dp_priv->dp_dev; u32 aux_cmd; int ret; u32 val; /* val will be assigned at the beginning of readl_poll_timeout function */ @@ -151,14 +153,15 @@ static ssize_t hibmc_dp_aux_xfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg * return hibmc_dp_aux_parse_xfer(dp, msg); } -void hibmc_dp_aux_init(struct hibmc_dp_dev *dp) +void hibmc_dp_aux_init(struct hibmc_dp *dp) { - hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0); - hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1); - hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM, + hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0); + hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1); + hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM, HIBMC_DP_MIN_PULSE_NUM); dp->aux.transfer = hibmc_dp_aux_xfer; dp->aux.is_remote = 0; drm_dp_aux_init(&dp->aux); + dp->dp_dev->aux = &dp->aux; } diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h index e0c6a3b7463b8..4add05c7f161a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h @@ -13,6 +13,8 @@ #include #include +#include "dp_hw.h" + #define HIBMC_DP_LANE_NUM_MAX 2 struct hibmc_link_status { @@ -32,7 +34,7 @@ struct hibmc_dp_link { }; struct hibmc_dp_dev { - struct drm_dp_aux aux; + struct drm_dp_aux *aux; struct drm_device *dev; void __iomem *base; struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */ @@ -58,7 +60,7 @@ struct hibmc_dp_dev { mutex_unlock(&_dp->lock); \ } while (0) -void hibmc_dp_aux_init(struct hibmc_dp_dev *dp); +void hibmc_dp_aux_init(struct hibmc_dp *dp); int hibmc_dp_link_training(struct hibmc_dp_dev *dp); int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp); int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp); diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index dcb2ab5ea6bb8..aa9354a996c9a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -167,7 +167,7 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) dp_dev->dev = drm_dev; dp_dev->base = dp->mmio + HIBMC_DP_OFFSET; - hibmc_dp_aux_init(dp_dev); + hibmc_dp_aux_init(dp); ret = hibmc_dp_serdes_init(dp_dev); if (ret) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 4dc13b3d9875f..53b6d0beecea0 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -10,6 +10,7 @@ #include #include #include +#include struct hibmc_dp_dev; @@ -19,6 +20,7 @@ struct hibmc_dp { struct drm_encoder encoder; struct drm_connector connector; void __iomem *mmio; + struct drm_dp_aux aux; }; int hibmc_dp_hw_init(struct hibmc_dp *dp); diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c index b1fcfaa8354f6..21cad69c37575 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c @@ -42,7 +42,7 @@ static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp) /* set rate and lane count */ buf[0] = dp->link.cap.link_rate; buf[1] = DP_LANE_COUNT_ENHANCED_FRAME_EN | dp->link.cap.lanes; - ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); + ret = drm_dp_dpcd_write(dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); if (ret != sizeof(buf)) { drm_dbg_dp(dp->dev, "dp aux write link rate and lanes failed, ret: %d\n", ret); return ret >= 0 ? -EIO : ret; @@ -51,7 +51,7 @@ static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp) /* set 8b/10b and downspread */ buf[0] = DP_SPREAD_AMP_0_5; buf[1] = DP_SET_ANSI_8B10B; - ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); + ret = drm_dp_dpcd_write(dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); if (ret != sizeof(buf)) { drm_dbg_dp(dp->dev, "dp aux write 8b/10b and downspread failed, ret: %d\n", ret); return ret >= 0 ? -EIO : ret; @@ -96,7 +96,7 @@ static int hibmc_dp_link_set_pattern(struct hibmc_dp_dev *dp, int pattern) hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_PAT_SEL, val); - ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_PATTERN_SET, &buf, sizeof(buf)); + ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_PATTERN_SET, &buf, sizeof(buf)); if (ret != sizeof(buf)) { drm_dbg_dp(dp->dev, "dp aux write training pattern set failed\n"); return ret >= 0 ? -EIO : ret; @@ -126,7 +126,7 @@ static int hibmc_dp_link_training_cr_pre(struct hibmc_dp_dev *dp) if (ret) return ret; - ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, train_set, dp->link.cap.lanes); + ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET, train_set, dp->link.cap.lanes); if (ret != dp->link.cap.lanes) { drm_dbg_dp(dp->dev, "dp aux write training lane set failed\n"); return ret >= 0 ? -EIO : ret; @@ -210,9 +210,9 @@ static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp) voltage_tries = 1; for (cr_tries = 0; cr_tries < 80; cr_tries++) { - drm_dp_link_train_clock_recovery_delay(&dp->aux, dp->dpcd); + drm_dp_link_train_clock_recovery_delay(dp->aux, dp->dpcd); - ret = drm_dp_dpcd_read_link_status(&dp->aux, lane_status); + ret = drm_dp_dpcd_read_link_status(dp->aux, lane_status); if (ret != DP_LINK_STATUS_SIZE) { drm_err(dp->dev, "Get lane status failed\n"); return ret; @@ -236,7 +236,7 @@ static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp) if (ret) return ret; - ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, + ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, dp->link.cap.lanes); if (ret != dp->link.cap.lanes) { drm_dbg_dp(dp->dev, "Update link training failed\n"); @@ -263,9 +263,9 @@ static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp) return ret; for (eq_tries = 0; eq_tries < HIBMC_EQ_MAX_RETRY; eq_tries++) { - drm_dp_link_train_channel_eq_delay(&dp->aux, dp->dpcd); + drm_dp_link_train_channel_eq_delay(dp->aux, dp->dpcd); - ret = drm_dp_dpcd_read_link_status(&dp->aux, lane_status); + ret = drm_dp_dpcd_read_link_status(dp->aux, lane_status); if (ret != DP_LINK_STATUS_SIZE) { drm_err(dp->dev, "get lane status failed\n"); break; @@ -290,7 +290,7 @@ static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp) if (ret) return ret; - ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, dp->link.cap.lanes); if (ret != dp->link.cap.lanes) { drm_dbg_dp(dp->dev, "Update link training failed\n"); @@ -330,7 +330,7 @@ int hibmc_dp_link_training(struct hibmc_dp_dev *dp) struct hibmc_dp_link *link = &dp->link; int ret; - ret = drm_dp_read_dpcd_caps(&dp->aux, dp->dpcd); + ret = drm_dp_read_dpcd_caps(dp->aux, dp->dpcd); if (ret) drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret); From 9c7f66a1835229ecc7153085479c9f36acd1402b Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:03 +0800 Subject: [PATCH 042/107] drm/hisilicon/hibmc: Getting connector info and EDID by using AUX channel [Upstream commit bd1c935811ae6bd112321c50ed83444eca4facc8] Add registering drm_aux and use it to get connector edid with drm functions. Add ddc channel in connector initialization to put drm_aux in drm_connector. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-6-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c | 3 +- .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 31 ++++++++++++++++--- .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 5 +++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c index ded9e7ce887a1..8732cd1d8cb6c 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_aux.c @@ -161,7 +161,8 @@ void hibmc_dp_aux_init(struct hibmc_dp *dp) HIBMC_DP_MIN_PULSE_NUM); dp->aux.transfer = hibmc_dp_aux_xfer; - dp->aux.is_remote = 0; + dp->aux.name = "HIBMC DRM dp aux"; + dp->aux.drm_dev = dp->drm_dev; drm_dp_aux_init(&dp->aux); dp->dp_dev->aux = &dp->aux; } diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index 603d6b198a540..8e5ee816dfbd9 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -15,11 +15,16 @@ static int hibmc_dp_connector_get_modes(struct drm_connector *connector) { + const struct drm_edid *drm_edid; int count; - count = drm_add_modes_noedid(connector, connector->dev->mode_config.max_width, - connector->dev->mode_config.max_height); - drm_set_preferred_mode(connector, 1024, 768); // temporary implementation + drm_edid = drm_edid_read(connector); + + drm_edid_connector_update(connector, drm_edid); + + count = drm_edid_connector_add_modes(connector); + + drm_edid_free(drm_edid); return count; } @@ -28,12 +33,28 @@ static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { .get_modes = hibmc_dp_connector_get_modes, }; +static int hibmc_dp_late_register(struct drm_connector *connector) +{ + struct hibmc_dp *dp = to_hibmc_dp(connector); + + return drm_dp_aux_register(&dp->aux); +} + +static void hibmc_dp_early_unregister(struct drm_connector *connector) +{ + struct hibmc_dp *dp = to_hibmc_dp(connector); + + drm_dp_aux_unregister(&dp->aux); +} + static const struct drm_connector_funcs hibmc_dp_conn_funcs = { .reset = drm_atomic_helper_connector_reset, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = drm_connector_cleanup, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .late_register = hibmc_dp_late_register, + .early_unregister = hibmc_dp_early_unregister, }; static inline int hibmc_dp_prepare(struct hibmc_dp *dp, struct drm_display_mode *mode) @@ -103,8 +124,8 @@ int hibmc_dp_init(struct hibmc_drm_private *priv) drm_encoder_helper_add(encoder, &hibmc_dp_encoder_helper_funcs); - ret = drm_connector_init(dev, connector, &hibmc_dp_conn_funcs, - DRM_MODE_CONNECTOR_DisplayPort); + ret = drm_connector_init_with_ddc(dev, connector, &hibmc_dp_conn_funcs, + DRM_MODE_CONNECTOR_DisplayPort, &dp->aux.ddc); if (ret) { drm_err(dev, "init dp connector failed: %d\n", ret); return ret; diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index 431f3c6fd1d2a..7e1cd107a4c5a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -47,6 +47,11 @@ static inline struct hibmc_vdac *to_hibmc_vdac(struct drm_connector *connector) return container_of(connector, struct hibmc_vdac, connector); } +static inline struct hibmc_dp *to_hibmc_dp(struct drm_connector *connector) +{ + return container_of(connector, struct hibmc_dp, connector); +} + static inline struct hibmc_drm_private *to_hibmc_drm_private(struct drm_device *dev) { return container_of(dev, struct hibmc_drm_private, dev); From f32fe46bd91d9d7ea9015b7857f1b3ffaf9ea825 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:04 +0800 Subject: [PATCH 043/107] drm/hisilicon/hibmc: Add colorbar-cfg feature and its debugfs file [Upstream commit 2f6182616cfdb154e2ecfe9554bb814b8a6378e9] DP controller can support generating a color bar signal over the DisplayPort interface. This can be useful to check for possible DDR or GPU problems, as the signal generator resides completely in the DP block. Add debugfs file that controls colorbar generator. echo: config the color bar register to display cat: print the color bar configuration Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-7-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/Makefile | 3 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 43 ++++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 29 +++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 3 + .../drm/hisilicon/hibmc/hibmc_drm_debugfs.c | 104 ++++++++++++++++++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 1 + .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 2 + 7 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c diff --git a/drivers/gpu/drm/hisilicon/hibmc/Makefile b/drivers/gpu/drm/hisilicon/hibmc/Makefile index 43de077d6769a..1f65c683282f9 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/Makefile +++ b/drivers/gpu/drm/hisilicon/hibmc/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \ - dp/dp_aux.o dp/dp_link.o dp/dp_hw.o dp/dp_serdes.o hibmc_drm_dp.o + dp/dp_aux.o dp/dp_link.o dp/dp_hw.o dp/dp_serdes.o hibmc_drm_dp.o \ + hibmc_drm_debugfs.o obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index aa9354a996c9a..ce7cb07815b22 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -226,3 +226,46 @@ int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode) return 0; } + +static const struct hibmc_dp_color_raw g_rgb_raw[] = { + {CBAR_COLOR_BAR, 0x000, 0x000, 0x000}, + {CBAR_WHITE, 0xfff, 0xfff, 0xfff}, + {CBAR_RED, 0xfff, 0x000, 0x000}, + {CBAR_ORANGE, 0xfff, 0x800, 0x000}, + {CBAR_YELLOW, 0xfff, 0xfff, 0x000}, + {CBAR_GREEN, 0x000, 0xfff, 0x000}, + {CBAR_CYAN, 0x000, 0x800, 0x800}, + {CBAR_BLUE, 0x000, 0x000, 0xfff}, + {CBAR_PURPLE, 0x800, 0x000, 0x800}, + {CBAR_BLACK, 0x000, 0x000, 0x000}, +}; + +void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg) +{ + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + struct hibmc_dp_color_raw raw_data; + + if (cfg->enable) { + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(9), + cfg->self_timing); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, GENMASK(8, 1), + cfg->dynamic_rate); + if (cfg->pattern == CBAR_COLOR_BAR) { + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(10), 0); + } else { + raw_data = g_rgb_raw[cfg->pattern]; + drm_dbg_dp(dp->drm_dev, "r:%x g:%x b:%x\n", raw_data.r_value, + raw_data.g_value, raw_data.b_value); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(10), 1); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, GENMASK(23, 12), + raw_data.r_value); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, GENMASK(23, 12), + raw_data.g_value); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, GENMASK(11, 0), + raw_data.b_value); + } + } + + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(0), cfg->enable); + writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 53b6d0beecea0..83a53dae80120 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -14,6 +14,33 @@ struct hibmc_dp_dev; +enum hibmc_dp_cbar_pattern { + CBAR_COLOR_BAR, + CBAR_WHITE, + CBAR_RED, + CBAR_ORANGE, + CBAR_YELLOW, + CBAR_GREEN, + CBAR_CYAN, + CBAR_BLUE, + CBAR_PURPLE, + CBAR_BLACK, +}; + +struct hibmc_dp_color_raw { + enum hibmc_dp_cbar_pattern pattern; + u32 r_value; + u32 g_value; + u32 b_value; +}; + +struct hibmc_dp_cbar_cfg { + u8 enable; + u8 self_timing; + u8 dynamic_rate; /* 0:static, 1-255(frame):dynamic */ + enum hibmc_dp_cbar_pattern pattern; +}; + struct hibmc_dp { struct hibmc_dp_dev *dp_dev; struct drm_device *drm_dev; @@ -21,10 +48,12 @@ struct hibmc_dp { struct drm_connector connector; void __iomem *mmio; struct drm_dp_aux aux; + struct hibmc_dp_cbar_cfg cfg; }; int hibmc_dp_hw_init(struct hibmc_dp *dp); int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode); void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable); +void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 6eb76decc636e..5614b727a710e 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -67,6 +67,9 @@ #define HIBMC_DP_CFG_STREAM_HTOTAL_SIZE GENMASK(31, 16) #define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0) +#define HIBMC_DP_COLOR_BAR_CTRL 0x260 +#define HIBMC_DP_COLOR_BAR_CTRL1 0x264 + #define HIBMC_DP_TIMING_GEN_CONFIG0 0x26c #define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16) #define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c new file mode 100644 index 0000000000000..f585387c3a49f --- /dev/null +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2024 Hisilicon Limited. + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hibmc_drm_drv.h" + +#define MAX_BUF_SIZE 12 + +static ssize_t hibmc_control_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct hibmc_drm_private *priv = file_inode(file)->i_private; + struct hibmc_dp_cbar_cfg *cfg = &priv->dp.cfg; + int ret, idx; + u8 buf[MAX_BUF_SIZE]; + + if (count >= MAX_BUF_SIZE) + return -EINVAL; + + if (copy_from_user(buf, user_buf, count)) + return -EFAULT; + + buf[count] = '\0'; + + /* Only 4 parameters is allowed, the ranger are as follow: + * [0] enable/disable colorbar feature + 0: enable colorbar, 1: disable colorbar + * [1] the timing source of colorbar displaying + 0: timing follows XDP, 1: internal self timing + * [2] the movment of colorbar displaying + 0: static colorbar image, + * 1~255: right shifting a type of color per (1~255)frames + * [3] the color type of colorbar displaying + 0~9: color bar, white, red, orange, + * yellow, green, cyan, bule, pupper, black + */ + if (sscanf(buf, "%hhu %hhu %hhu %u", &cfg->enable, &cfg->self_timing, + &cfg->dynamic_rate, &cfg->pattern) != 4) { + return -EINVAL; + } + + if (cfg->pattern > 9 || cfg->enable > 1 || cfg->self_timing > 1) + return -EINVAL; + + ret = drm_dev_enter(&priv->dev, &idx); + if (!ret) + return -ENODEV; + + hibmc_dp_set_cbar(&priv->dp, cfg); + + drm_dev_exit(idx); + + return count; +} + +static int hibmc_dp_dbgfs_show(struct seq_file *m, void *arg) +{ + struct hibmc_drm_private *priv = m->private; + struct hibmc_dp_cbar_cfg *cfg = &priv->dp.cfg; + int idx; + + if (!drm_dev_enter(&priv->dev, &idx)) + return -ENODEV; + + seq_printf(m, "hibmc dp colorbar cfg: %u %u %u %u\n", cfg->enable, cfg->self_timing, + cfg->dynamic_rate, cfg->pattern); + + drm_dev_exit(idx); + + return 0; +} + +static int hibmc_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, hibmc_dp_dbgfs_show, inode->i_private); +} + +static const struct file_operations hibmc_dbg_fops = { + .owner = THIS_MODULE, + .write = hibmc_control_write, + .read = seq_read, + .open = hibmc_open, + .llseek = seq_lseek, + .release = single_release, +}; + +void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root) +{ + struct drm_device *dev = connector->dev; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + + /* create the file in drm directory, so we don't need to remove manually */ + debugfs_create_file("colorbar-cfg", 0200, + root, priv, &hibmc_dbg_fops); +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index 8e5ee816dfbd9..a86ecce4b3c14 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -55,6 +55,7 @@ static const struct drm_connector_funcs hibmc_dp_conn_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .late_register = hibmc_dp_late_register, .early_unregister = hibmc_dp_early_unregister, + .debugfs_init = hibmc_debugfs_init, }; static inline int hibmc_dp_prepare(struct hibmc_dp *dp, struct drm_display_mode *mode) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index 7e1cd107a4c5a..d29354afdbe03 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -69,4 +69,6 @@ int hibmc_mm_init(struct hibmc_drm_private *hibmc); int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *connector); int hibmc_dp_init(struct hibmc_drm_private *priv); +void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root); + #endif From fe5fde66420b2d82e9d7ec386d6f36f9ff17feab Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:05 +0800 Subject: [PATCH 044/107] drm/hisilicon/hibmc: Enable this hot plug detect of irq feature [Upstream commit 3c7623fb5bb6c319531b941b15b7bfc12455d3d3] Add HPD interrupt enable functions in drm framework, and also add detect_ctx functions. Because of the debouncing when HPD pulled out, add 200 ms delay in detect. Add link reset process to reset link status when a new connector pulgged in. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-8-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- .../gpu/drm/hisilicon/hibmc/dp/dp_config.h | 1 + drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 36 ++++++++++++++++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 5 +++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 42 +++++++++++++++++++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 2 + 5 files changed, 86 insertions(+) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h index c5feef8dc27d1..08f9e1caf7fcb 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_config.h @@ -16,5 +16,6 @@ #define HIBMC_DP_SYNC_EN_MASK 0x3 #define HIBMC_DP_LINK_RATE_CAL 27 #define HIBMC_DP_SYNC_DELAY(lanes) ((lanes) == 0x2 ? 86 : 46) +#define HIBMC_DP_INT_ENABLE 0xc #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index ce7cb07815b22..8f0daec7d1749 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -189,6 +189,36 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) return 0; } +void hibmc_dp_enable_int(struct hibmc_dp *dp) +{ + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + + writel(HIBMC_DP_INT_ENABLE, dp_dev->base + HIBMC_DP_INTR_ENABLE); +} + +void hibmc_dp_disable_int(struct hibmc_dp *dp) +{ + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + + writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE); + writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS); +} + +void hibmc_dp_hpd_cfg(struct hibmc_dp *dp) +{ + struct hibmc_dp_dev *dp_dev = dp->dp_dev; + + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1); + hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM, 0x9); + writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); + writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE); + writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS); + writel(HIBMC_DP_INT_ENABLE, dp_dev->base + HIBMC_DP_INTR_ENABLE); + writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); + writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL); +} + void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable) { struct hibmc_dp_dev *dp_dev = dp->dp_dev; @@ -227,6 +257,12 @@ int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode) return 0; } +void hibmc_dp_reset_link(struct hibmc_dp *dp) +{ + dp->dp_dev->link.status.clock_recovered = false; + dp->dp_dev->link.status.channel_equalized = false; +} + static const struct hibmc_dp_color_raw g_rgb_raw[] = { {CBAR_COLOR_BAR, 0x000, 0x000, 0x000}, {CBAR_WHITE, 0xfff, 0xfff, 0xfff}, diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 83a53dae80120..665f5b166dfb5 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -49,11 +49,16 @@ struct hibmc_dp { void __iomem *mmio; struct drm_dp_aux aux; struct hibmc_dp_cbar_cfg cfg; + u32 irq_status; }; int hibmc_dp_hw_init(struct hibmc_dp *dp); int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode); void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable); void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg); +void hibmc_dp_reset_link(struct hibmc_dp *dp); +void hibmc_dp_hpd_cfg(struct hibmc_dp *dp); +void hibmc_dp_enable_int(struct hibmc_dp *dp); +void hibmc_dp_disable_int(struct hibmc_dp *dp); #endif diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index a86ecce4b3c14..d06832e62e966 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -13,6 +13,8 @@ #include "hibmc_drm_drv.h" #include "dp/dp_hw.h" +#define DP_MASKED_SINK_HPD_PLUG_INT BIT(2) + static int hibmc_dp_connector_get_modes(struct drm_connector *connector) { const struct drm_edid *drm_edid; @@ -29,14 +31,25 @@ static int hibmc_dp_connector_get_modes(struct drm_connector *connector) return count; } +static int hibmc_dp_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, bool force) +{ + mdelay(200); + + return drm_connector_helper_detect_from_ddc(connector, ctx, force); +} + static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { .get_modes = hibmc_dp_connector_get_modes, + .detect_ctx = hibmc_dp_detect, }; static int hibmc_dp_late_register(struct drm_connector *connector) { struct hibmc_dp *dp = to_hibmc_dp(connector); + hibmc_dp_enable_int(dp); + return drm_dp_aux_register(&dp->aux); } @@ -45,6 +58,8 @@ static void hibmc_dp_early_unregister(struct drm_connector *connector) struct hibmc_dp *dp = to_hibmc_dp(connector); drm_dp_aux_unregister(&dp->aux); + + hibmc_dp_disable_int(dp); } static const struct drm_connector_funcs hibmc_dp_conn_funcs = { @@ -96,6 +111,31 @@ static const struct drm_encoder_helper_funcs hibmc_dp_encoder_helper_funcs = { .atomic_disable = hibmc_dp_encoder_disable, }; +irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) +{ + struct drm_device *dev = (struct drm_device *)arg; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + int idx; + + if (!drm_dev_enter(dev, &idx)) + return -ENODEV; + + if (priv->dp.irq_status & DP_MASKED_SINK_HPD_PLUG_INT) { + drm_dbg_dp(&priv->dev, "HPD IN isr occur!\n"); + hibmc_dp_hpd_cfg(&priv->dp); + } else { + drm_dbg_dp(&priv->dev, "HPD OUT isr occur!\n"); + hibmc_dp_reset_link(&priv->dp); + } + + if (dev->registered) + drm_connector_helper_hpd_irq_event(&priv->dp.connector); + + drm_dev_exit(idx); + + return IRQ_HANDLED; +} + int hibmc_dp_init(struct hibmc_drm_private *priv) { struct drm_device *dev = &priv->dev; @@ -136,5 +176,7 @@ int hibmc_dp_init(struct hibmc_drm_private *priv) drm_connector_attach_encoder(connector, encoder); + connector->polled = DRM_CONNECTOR_POLL_HPD; + return 0; } diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index d29354afdbe03..228afd599baf6 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -71,4 +71,6 @@ int hibmc_dp_init(struct hibmc_drm_private *priv); void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root); +irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg); + #endif From d65fb717b72c6aac8963b2235ea46d71924204d2 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:06 +0800 Subject: [PATCH 045/107] drm/hisilicon/hibmc: Add MSI irq getting and requesting for HPD [Upstream commit b11bc1ae46587f3563c47078e605184f18e7fa57] To realize HPD feature, request irq for HPD , add its handler function. We use pci_alloc_irq_vectors() to get our msi irq, because we have two interrupts now. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-9-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 3 + .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 74 +++++++++++++++---- .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 3 + 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 5614b727a710e..394b1e933c3ae 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -99,6 +99,9 @@ #define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0 +#define HIBMC_DP_INTSTAT 0x1e0724 +#define HIBMC_DP_INTCLR 0x1e0728 + /* dp serdes reg */ #define HIBMC_DP_HOST_OFFSET 0x10000 #define HIBMC_DP_LANE0_RATE_OFFSET 0x4 diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 514a573bb2972..c472a134b9482 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -31,6 +31,8 @@ DEFINE_DRM_GEM_FOPS(hibmc_fops); +static const char *g_irqs_names_map[HIBMC_MAX_VECTORS] = { "vblank", "hpd" }; + static irqreturn_t hibmc_interrupt(int irq, void *arg) { struct drm_device *dev = (struct drm_device *)arg; @@ -48,6 +50,22 @@ static irqreturn_t hibmc_interrupt(int irq, void *arg) return IRQ_HANDLED; } +static irqreturn_t hibmc_dp_interrupt(int irq, void *arg) +{ + struct drm_device *dev = (struct drm_device *)arg; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + u32 status; + + status = readl(priv->mmio + HIBMC_DP_INTSTAT); + if (status) { + priv->dp.irq_status = status; + writel(status, priv->mmio + HIBMC_DP_INTCLR); + return IRQ_WAKE_THREAD; + } + + return IRQ_HANDLED; +} + static int hibmc_dumb_create(struct drm_file *file, struct drm_device *dev, struct drm_mode_create_dumb *args) { @@ -250,15 +268,48 @@ static int hibmc_hw_init(struct hibmc_drm_private *priv) return 0; } -static int hibmc_unload(struct drm_device *dev) +static void hibmc_unload(struct drm_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); - drm_atomic_helper_shutdown(dev); +} - free_irq(pdev->irq, dev); +static int hibmc_msi_init(struct drm_device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev->dev); + char name[32] = {0}; + int valid_irq_num; + int irq; + int ret; - pci_disable_msi(to_pci_dev(dev->dev)); + ret = pci_alloc_irq_vectors(pdev, HIBMC_MIN_VECTORS, + HIBMC_MAX_VECTORS, PCI_IRQ_MSI); + if (ret < 0) { + drm_err(dev, "enabling MSI failed: %d\n", ret); + return ret; + } + + valid_irq_num = ret; + + for (int i = 0; i < valid_irq_num; i++) { + snprintf(name, ARRAY_SIZE(name) - 1, "%s-%s-%s", + dev->driver->name, pci_name(pdev), g_irqs_names_map[i]); + + irq = pci_irq_vector(pdev, i); + + if (i) + /* PCI devices require shared interrupts. */ + ret = devm_request_threaded_irq(&pdev->dev, irq, + hibmc_dp_interrupt, + hibmc_dp_hpd_isr, + IRQF_SHARED, name, dev); + else + ret = devm_request_irq(&pdev->dev, irq, hibmc_interrupt, + IRQF_SHARED, name, dev); + if (ret) { + drm_err(dev, "install irq failed: %d\n", ret); + return ret; + } + } return 0; } @@ -290,15 +341,10 @@ static int hibmc_load(struct drm_device *dev) goto err; } - ret = pci_enable_msi(pdev); + ret = hibmc_msi_init(dev); if (ret) { - drm_warn(dev, "enabling MSI failed: %d\n", ret); - } else { - /* PCI devices require shared interrupts. */ - ret = request_irq(pdev->irq, hibmc_interrupt, IRQF_SHARED, - dev->driver->name, dev); - if (ret) - drm_warn(dev, "install irq failed: %d\n", ret); + drm_err(dev, "hibmc msi init failed, ret:%d\n", ret); + goto err; } /* reset all the states of crtc/plane/encoder/connector */ @@ -374,7 +420,7 @@ static void hibmc_pci_remove(struct pci_dev *pdev) static void hibmc_pci_shutdown(struct pci_dev *pdev) { - drm_atomic_helper_shutdown(pci_get_drvdata(pdev)); + hibmc_pci_remove(pdev); } static const struct pci_device_id hibmc_pci_table[] = { diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index 228afd599baf6..3f907329797cc 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -22,6 +22,9 @@ #include #include "dp/dp_hw.h" +#define HIBMC_MIN_VECTORS 1 +#define HIBMC_MAX_VECTORS 2 + struct hibmc_vdac { struct drm_device *dev; struct drm_encoder encoder; From bebb511901581561b95938ed4680f1de84eb35e0 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Wed, 16 Apr 2025 16:25:07 +0800 Subject: [PATCH 046/107] drm/hisilicon/hibmc: Add vga connector detect functions [Upstream commit 4c962bc929f1734d209a0862359e25fef8f56fa0] Because the connected VGA connector would make driver can't get the userspace call, adding detect_ctx in vga connector to make HPD active userspace. Signed-off-by: Baihan Li Signed-off-by: Yongbang Shi Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20250331074212.3370287-10-shiyongbang@huawei.com Signed-off-by: Dmitry Baryshkov Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c index db3d1591c56f8..945438a014847 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c @@ -59,6 +59,7 @@ static void hibmc_connector_destroy(struct drm_connector *connector) static const struct drm_connector_helper_funcs hibmc_connector_helper_funcs = { .get_modes = hibmc_connector_get_modes, + .detect_ctx = drm_connector_helper_detect_from_ddc, }; static const struct drm_connector_funcs hibmc_connector_funcs = { @@ -126,5 +127,7 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv) drm_connector_attach_encoder(connector, encoder); + connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + return 0; } From 725ecf567a74fd37270881e12fe44dff3139aecd Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Thu, 17 Apr 2025 15:52:13 +0800 Subject: [PATCH 047/107] drm/hisilicon/hibmc: fix HPD interrupts triggering the wrong behavior commit ecdb52f7e59d6147d2cfa26b780d3720975c350f openEuler The HPD out signal will be very fast sometimes that drm detect the connector is still connectted by aux channel in dp's detect_ctx(). In this case, driver will ignore this out signal, which will be a problem. Using hibmc_dp_detect() to detect the connector by a hpd_status flag, which will change to 1/0 when get an in/out IRQ. Fixes: afdde4e4c1f9 ("drm/hisilicon/hibmc: Enable this hot plug detect of irq feature") Signed-off-by: Baihan Li Signed-off-by: chenyi Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 1 + .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 34 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 665f5b166dfb5..68867475508ce 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -50,6 +50,7 @@ struct hibmc_dp { struct drm_dp_aux aux; struct hibmc_dp_cbar_cfg cfg; u32 irq_status; + int hpd_status; }; int hibmc_dp_hw_init(struct hibmc_dp *dp); diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index d06832e62e966..41a5d3a9625c6 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -13,7 +13,8 @@ #include "hibmc_drm_drv.h" #include "dp/dp_hw.h" -#define DP_MASKED_SINK_HPD_PLUG_INT BIT(2) +#define HIBMC_DP_MASKED_SINK_HPD_PLUG_INT BIT(2) +#define HIBMC_DP_MASKED_SINK_HPD_UNPLUG_INT BIT(3) static int hibmc_dp_connector_get_modes(struct drm_connector *connector) { @@ -34,9 +35,12 @@ static int hibmc_dp_connector_get_modes(struct drm_connector *connector) static int hibmc_dp_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force) { - mdelay(200); + struct hibmc_dp *dp = to_hibmc_dp(connector); - return drm_connector_helper_detect_from_ddc(connector, ctx, force); + if (dp->hpd_status) + return connector_status_connected; + else + return connector_status_disconnected; } static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { @@ -115,22 +119,30 @@ irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) { struct drm_device *dev = (struct drm_device *)arg; struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + struct hibmc_dp *dp = &priv->dp; int idx; if (!drm_dev_enter(dev, &idx)) return -ENODEV; - if (priv->dp.irq_status & DP_MASKED_SINK_HPD_PLUG_INT) { - drm_dbg_dp(&priv->dev, "HPD IN isr occur!\n"); - hibmc_dp_hpd_cfg(&priv->dp); + if (dp->hpd_status) { /* only check unplug int when the last status is HPD in */ + if ((dp->irq_status & HIBMC_DP_MASKED_SINK_HPD_UNPLUG_INT)) { + drm_dbg_dp(dev, "HPD OUT isr occur!\n"); + hibmc_dp_reset_link(dp); + dp->hpd_status = 0; + if (dev->registered) + drm_connector_helper_hpd_irq_event(&dp->connector); + } } else { - drm_dbg_dp(&priv->dev, "HPD OUT isr occur!\n"); - hibmc_dp_reset_link(&priv->dp); + if (dp->irq_status & HIBMC_DP_MASKED_SINK_HPD_PLUG_INT) { + drm_dbg_dp(&priv->dev, "HPD IN isr occur!\n"); + hibmc_dp_hpd_cfg(dp); + dp->hpd_status = 1; + if (dev->registered) + drm_connector_helper_hpd_irq_event(&dp->connector); + } } - if (dev->registered) - drm_connector_helper_hpd_irq_event(&priv->dp.connector); - drm_dev_exit(idx); return IRQ_HANDLED; From a7043969bdde19f18d9727bc8012e437ea855518 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Thu, 17 Apr 2025 15:59:55 +0800 Subject: [PATCH 048/107] drm/hisilicon/hibmc: fix irq_request()'s irq name variable is local commit 1d8f35456b3a037a5f6452cbd0d723a1ca5aab71 openEuler fix irq_request()'s irq name variable is local. Fixes: 9fb9e467cce3 ("drm/hisilicon/hibmc: Add MSI irq getting and requesting for HPD") Signed-off-by: Baihan Li Signed-off-by: chenyi Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index c472a134b9482..9c963e083ea24 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -31,7 +31,7 @@ DEFINE_DRM_GEM_FOPS(hibmc_fops); -static const char *g_irqs_names_map[HIBMC_MAX_VECTORS] = { "vblank", "hpd" }; +static const char *g_irqs_names_map[HIBMC_MAX_VECTORS] = { "hibmc-vblank", "hibmc-hpd" }; static irqreturn_t hibmc_interrupt(int irq, void *arg) { @@ -276,7 +276,6 @@ static void hibmc_unload(struct drm_device *dev) static int hibmc_msi_init(struct drm_device *dev) { struct pci_dev *pdev = to_pci_dev(dev->dev); - char name[32] = {0}; int valid_irq_num; int irq; int ret; @@ -291,9 +290,6 @@ static int hibmc_msi_init(struct drm_device *dev) valid_irq_num = ret; for (int i = 0; i < valid_irq_num; i++) { - snprintf(name, ARRAY_SIZE(name) - 1, "%s-%s-%s", - dev->driver->name, pci_name(pdev), g_irqs_names_map[i]); - irq = pci_irq_vector(pdev, i); if (i) @@ -301,10 +297,10 @@ static int hibmc_msi_init(struct drm_device *dev) ret = devm_request_threaded_irq(&pdev->dev, irq, hibmc_dp_interrupt, hibmc_dp_hpd_isr, - IRQF_SHARED, name, dev); + IRQF_SHARED, g_irqs_names_map[i], dev); else ret = devm_request_irq(&pdev->dev, irq, hibmc_interrupt, - IRQF_SHARED, name, dev); + IRQF_SHARED, g_irqs_names_map[i], dev); if (ret) { drm_err(dev, "install irq failed: %d\n", ret); return ret; From c364203b43f069c494972f2a0cff71ad112af722 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Tue, 27 May 2025 21:39:00 +0800 Subject: [PATCH 049/107] drm/hisilicon/hibmc:fix KVM is not displaying when GRUB VGA is on commit d6c0faed6fc0c1bbab2f4145c03990238997a0e1 openEuler Add vdac detect_ctx(), and make vdac is always connected when DP is disconnected. Add force() to eliminate the influence of VGA's GRUB on DP when HPD occurring. Fixes: c8370691a41c ("drm/hisilicon/hibmc: Add vga connector detect functions") Signed-off-by: Baihan Li Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- .../gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c index 945438a014847..5bcaa02638920 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c @@ -56,18 +56,44 @@ static void hibmc_connector_destroy(struct drm_connector *connector) drm_connector_cleanup(connector); } +static int hibmc_vdac_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct hibmc_drm_private *priv = to_hibmc_drm_private(connector->dev); + struct hibmc_dp *dp = &priv->dp; + + if (dp->hpd_status) + return connector_status_disconnected; + + return connector_status_connected; +} + static const struct drm_connector_helper_funcs hibmc_connector_helper_funcs = { .get_modes = hibmc_connector_get_modes, - .detect_ctx = drm_connector_helper_detect_from_ddc, + .detect_ctx = hibmc_vdac_detect, }; +static void hibmc_vdac_force(struct drm_connector *connector) +{ + struct hibmc_drm_private *priv = to_hibmc_drm_private(connector->dev); + struct hibmc_dp *dp = &priv->dp; + + if (dp->hpd_status) { + connector->status = connector_status_disconnected; + return; + } + + connector->status = connector_status_connected; +} + static const struct drm_connector_funcs hibmc_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .destroy = hibmc_connector_destroy, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .force = hibmc_vdac_force, }; static void hibmc_encoder_mode_set(struct drm_encoder *encoder, From 2f6c5807f33cb42b8046420e378e7ec3bc064753 Mon Sep 17 00:00:00 2001 From: Baihan Li Date: Tue, 27 May 2025 21:39:56 +0800 Subject: [PATCH 050/107] drm/hisilicon/hibmc: hibmc-drm bugfix for DP commit 2a581acede344f1a7dc93c0ae6b155678045ec2b openEuler Fix DP is not displaying in some rare scenarios. Fixes: 7d33df2be7ca ("drm/hisilicon/hibmc: Restructuring the header dp_reg.h") Signed-off-by: Baihan Li Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h | 5 +- drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c | 83 +++++++++++++------ drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h | 9 ++ drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c | 67 +++++++++++---- drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h | 10 ++- .../gpu/drm/hisilicon/hibmc/dp/dp_serdes.c | 12 --- .../drm/hisilicon/hibmc/hibmc_drm_debugfs.c | 49 ++++++++++- .../gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c | 51 ++++++++++++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 19 +++-- .../gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h | 1 + .../gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c | 5 ++ .../gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c | 13 ++- 12 files changed, 256 insertions(+), 68 deletions(-) diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h index 4add05c7f161a..683b41ebf7ec9 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h @@ -15,6 +15,7 @@ #include "dp_hw.h" +#define HIBMC_DP_LINK_RATE_CAL 27 #define HIBMC_DP_LANE_NUM_MAX 2 struct hibmc_link_status { @@ -25,6 +26,9 @@ struct hibmc_link_status { struct hibmc_link_cap { u8 link_rate; u8 lanes; + int rx_dpcd_revision; + bool is_tps3; + bool is_tps4; }; struct hibmc_dp_link { @@ -62,7 +66,6 @@ struct hibmc_dp_dev { void hibmc_dp_aux_init(struct hibmc_dp *dp); int hibmc_dp_link_training(struct hibmc_dp_dev *dp); -int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp); int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp); int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX]); diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c index 8f0daec7d1749..9e8f95d957f20 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c @@ -154,7 +154,6 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) { struct drm_device *drm_dev = dp->drm_dev; struct hibmc_dp_dev *dp_dev; - int ret; dp_dev = devm_kzalloc(drm_dev->dev, sizeof(struct hibmc_dp_dev), GFP_KERNEL); if (!dp_dev) @@ -166,23 +165,22 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp) dp_dev->dev = drm_dev; dp_dev->base = dp->mmio + HIBMC_DP_OFFSET; + dp_dev->serdes_base = dp_dev->base + HIBMC_DP_HOST_OFFSET; hibmc_dp_aux_init(dp); - ret = hibmc_dp_serdes_init(dp_dev); - if (ret) - return ret; - dp_dev->link.cap.lanes = 0x2; dp_dev->link.cap.link_rate = DP_LINK_BW_8_1; - /* hdcp data */ - writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); /* int init */ writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE); writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS); /* rst */ + writel(0, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); + usleep_range(30, 50); writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL); + /* hdcp data */ + writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG); /* clock enable */ writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL); @@ -224,14 +222,17 @@ void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable) struct hibmc_dp_dev *dp_dev = dp->dp_dev; if (enable) { - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0x1); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, + HIBMC_DP_CFG_MST_ENABLE, 0x1); writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0x1); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, + HIBMC_DP_CFG_TIMING_GEN_ENABLE, 0x1); writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); } else { - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, + HIBMC_DP_CFG_TIMING_GEN_ENABLE, 0); writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_MST_ENABLE, 0); writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); } @@ -257,10 +258,36 @@ int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode) return 0; } +u8 hibmc_dp_get_link_rate(struct hibmc_dp *dp) +{ + if (!dp->dp_dev) + return 0; + + return dp->dp_dev->link.cap.link_rate; +} + +u8 hibmc_dp_get_lanes(struct hibmc_dp *dp) +{ + if (!dp->dp_dev) + return 0; + + return dp->dp_dev->link.cap.lanes; +} + +int hibmc_dp_get_dpcd(struct hibmc_dp *dp) +{ + if (!dp->dp_dev) + return 0; + + return dp->dp_dev->link.cap.rx_dpcd_revision; +} + void hibmc_dp_reset_link(struct hibmc_dp *dp) { - dp->dp_dev->link.status.clock_recovered = false; - dp->dp_dev->link.status.channel_equalized = false; + if (dp->dp_dev) { + dp->dp_dev->link.status.clock_recovered = false; + dp->dp_dev->link.status.channel_equalized = false; + } } static const struct hibmc_dp_color_raw g_rgb_raw[] = { @@ -282,26 +309,30 @@ void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg) struct hibmc_dp_color_raw raw_data; if (cfg->enable) { - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(9), - cfg->self_timing); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, GENMASK(8, 1), - cfg->dynamic_rate); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, + HIBMC_DP_COLOR_BAR_TIMING_SEL_M, cfg->self_timing); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, + HIBMC_DP_COLOR_BAR_CTRL_M, cfg->dynamic_rate); if (cfg->pattern == CBAR_COLOR_BAR) { - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(10), 0); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, + HIBMC_DP_COLOR_BAR_PATTERN_SEL_M, 0); } else { raw_data = g_rgb_raw[cfg->pattern]; drm_dbg_dp(dp->drm_dev, "r:%x g:%x b:%x\n", raw_data.r_value, raw_data.g_value, raw_data.b_value); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(10), 1); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, GENMASK(23, 12), - raw_data.r_value); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, GENMASK(23, 12), - raw_data.g_value); - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, GENMASK(11, 0), - raw_data.b_value); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, + HIBMC_DP_COLOR_BAR_PATTERN_SEL_M, 1); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, + HIBMC_DP_COLOR_BAR_DATA_R_M, raw_data.r_value); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, + HIBMC_DP_COLOR_BAR_DATA_G_M, raw_data.g_value); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, + HIBMC_DP_COLOR_BAR_DATA_B_M, raw_data.b_value); } } - hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(0), cfg->enable); + hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, + HIBMC_DP_COLOR_BAR_ENABLE_M, cfg->enable); writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL); } + diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h index 68867475508ce..6f6b6cee22589 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.h @@ -12,6 +12,10 @@ #include #include +// 27 * 10000000 * 80% = 216000000 +#define DP_MODE_VALI_CAL 216000000 +#define BPP_24 24 + struct hibmc_dp_dev; enum hibmc_dp_cbar_pattern { @@ -51,11 +55,16 @@ struct hibmc_dp { struct hibmc_dp_cbar_cfg cfg; u32 irq_status; int hpd_status; + bool is_connected; }; int hibmc_dp_hw_init(struct hibmc_dp *dp); int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode); void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable); +struct edid *hibmc_dp_get_edid(struct hibmc_dp *dp); +int hibmc_dp_get_dpcd(struct hibmc_dp *dp); +u8 hibmc_dp_get_link_rate(struct hibmc_dp *dp); +u8 hibmc_dp_get_lanes(struct hibmc_dp *dp); void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg); void hibmc_dp_reset_link(struct hibmc_dp *dp); void hibmc_dp_hpd_cfg(struct hibmc_dp *dp); diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c index 21cad69c37575..14ef709f7214e 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_link.c @@ -39,6 +39,14 @@ static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp) /* enhanced frame */ hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_FRAME_MODE, 0x1); + ret = hibmc_dp_get_serdes_rate_cfg(dp); + if (ret < 0) + return ret; + + ret = hibmc_dp_serdes_rate_switch(ret, dp); + if (ret) + return ret; + /* set rate and lane count */ buf[0] = dp->link.cap.link_rate; buf[1] = DP_LANE_COUNT_ENHANCED_FRAME_EN | dp->link.cap.lanes; @@ -201,21 +209,23 @@ static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp) bool level_changed; u32 voltage_tries; u32 cr_tries; + u32 max_cr; int ret; /* * DP 1.4 spec define 10 for maxtries value, for pre DP 1.4 version set a limit of 80 * (4 voltage levels x 4 preemphasis levels x 5 identical voltage retries) */ + max_cr = dp->link.cap.rx_dpcd_revision >= DP_DPCD_REV_14 ? 10 : 80; voltage_tries = 1; - for (cr_tries = 0; cr_tries < 80; cr_tries++) { + for (cr_tries = 0; cr_tries < max_cr; cr_tries++) { drm_dp_link_train_clock_recovery_delay(dp->aux, dp->dpcd); ret = drm_dp_dpcd_read_link_status(dp->aux, lane_status); if (ret != DP_LINK_STATUS_SIZE) { - drm_err(dp->dev, "Get lane status failed\n"); - return ret; + drm_err(dp->dev, "Get lane status failed, ret: %d\n", ret); + return ret >= 0 ? -EIO : ret; } if (drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) { @@ -246,7 +256,7 @@ static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp) voltage_tries = level_changed ? 1 : voltage_tries + 1; } - drm_err(dp->dev, "dp link training clock recovery 80 times failed\n"); + drm_err(dp->dev, "dp link training clock recovery %u timers failed\n", max_cr); dp->link.status.clock_recovered = false; return 0; @@ -256,9 +266,17 @@ static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp) { u8 lane_status[DP_LINK_STATUS_SIZE] = {0}; u8 eq_tries; + int tps; int ret; - ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_2); + if (dp->link.cap.is_tps4) + tps = DP_TRAINING_PATTERN_4; + else if (dp->link.cap.is_tps3) + tps = DP_TRAINING_PATTERN_3; + else + tps = DP_TRAINING_PATTERN_2; + + ret = hibmc_dp_link_set_pattern(dp, tps); if (ret) return ret; @@ -288,7 +306,7 @@ static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp) ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set); if (ret) - return ret; + break; ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set, dp->link.cap.lanes); @@ -317,7 +335,17 @@ static int hibmc_dp_link_downgrade_training_cr(struct hibmc_dp_dev *dp) static int hibmc_dp_link_downgrade_training_eq(struct hibmc_dp_dev *dp) { - if ((dp->link.status.clock_recovered && !dp->link.status.channel_equalized)) { + u8 status[DP_LINK_STATUS_SIZE] = {0}; + int ret; + + ret = drm_dp_dpcd_read_link_status(dp->aux, status); + if (ret != DP_LINK_STATUS_SIZE) { + drm_err(dp->dev, "get lane status failed\n"); + return ret >= 0 ? -EIO : ret; + } + + if ((dp->link.status.clock_recovered && !dp->link.status.channel_equalized) || + (status[0] != 0 && !dp->link.status.clock_recovered)) { // at least one cr_done if (!hibmc_dp_link_reduce_lane(dp)) return 0; } @@ -325,6 +353,20 @@ static int hibmc_dp_link_downgrade_training_eq(struct hibmc_dp_dev *dp) return hibmc_dp_link_reduce_rate(dp); } +static void hibmc_dp_update_caps(struct hibmc_dp_dev *dp) +{ + dp->link.cap.rx_dpcd_revision = dp->dpcd[DP_DPCD_REV]; + + dp->link.cap.is_tps3 = (dp->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_13) && + (dp->dpcd[DP_MAX_LANE_COUNT] & DP_TPS3_SUPPORTED); + dp->link.cap.is_tps4 = (dp->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) && + (dp->dpcd[DP_MAX_DOWNSPREAD] & DP_TPS4_SUPPORTED); + dp->link.cap.link_rate = dp->dpcd[DP_MAX_LINK_RATE]; + dp->link.cap.lanes = dp->dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK; + if (dp->link.cap.lanes > HIBMC_DP_LANE_NUM_MAX) + dp->link.cap.lanes = HIBMC_DP_LANE_NUM_MAX; +} + int hibmc_dp_link_training(struct hibmc_dp_dev *dp) { struct hibmc_dp_link *link = &dp->link; @@ -334,16 +376,7 @@ int hibmc_dp_link_training(struct hibmc_dp_dev *dp) if (ret) drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret); - dp->link.cap.link_rate = dp->dpcd[DP_MAX_LINK_RATE]; - dp->link.cap.lanes = 0x2; - - ret = hibmc_dp_get_serdes_rate_cfg(dp); - if (ret < 0) - return ret; - - ret = hibmc_dp_serdes_rate_switch(ret, dp); - if (ret) - return ret; + hibmc_dp_update_caps(dp); while (true) { ret = hibmc_dp_link_training_cr_pre(dp); diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h index 394b1e933c3ae..156e5d63c47f0 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h @@ -68,8 +68,14 @@ #define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0) #define HIBMC_DP_COLOR_BAR_CTRL 0x260 +#define HIBMC_DP_COLOR_BAR_ENABLE_M BIT(0) +#define HIBMC_DP_COLOR_BAR_CTRL_M GENMASK(8, 1) +#define HIBMC_DP_COLOR_BAR_TIMING_SEL_M BIT(9) +#define HIBMC_DP_COLOR_BAR_PATTERN_SEL_M BIT(10) +#define HIBMC_DP_COLOR_BAR_DATA_R_M GENMASK(23, 12) #define HIBMC_DP_COLOR_BAR_CTRL1 0x264 - +#define HIBMC_DP_COLOR_BAR_DATA_B_M GENMASK(11, 0) +#define HIBMC_DP_COLOR_BAR_DATA_G_M GENMASK(23, 12) #define HIBMC_DP_TIMING_GEN_CONFIG0 0x26c #define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16) #define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0) @@ -90,6 +96,8 @@ #define HIBMC_DP_DPTX_GCTL0 0x708 #define HIBMC_DP_CFG_PHY_LANE_NUM GENMASK(2, 1) +#define HIBMC_DP_CFG_TIMING_GEN_ENABLE BIT(10) +#define HIBMC_DP_CFG_MST_ENABLE BIT(0) #define HIBMC_DP_INTR_ENABLE 0x720 #define HIBMC_DP_INTR_ORIGINAL_STATUS 0x728 diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c index 676059d4c1e60..8191233aa965a 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c +++ b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c @@ -57,15 +57,3 @@ int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp) return 0; } - -int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp) -{ - dp->serdes_base = dp->base + HIBMC_DP_HOST_OFFSET; - - writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0), - dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET); - writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0), - dp->serdes_base + HIBMC_DP_PMA_LANE1_OFFSET); - - return hibmc_dp_serdes_rate_switch(DP_SERDES_BW_8_1, dp); -} diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c index f585387c3a49f..840ef38c8c220 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_debugfs.c @@ -15,6 +15,44 @@ #define MAX_BUF_SIZE 12 +static int hibmc_dp_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = m->private; + struct drm_device *dev = node->minor->dev; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + int idx, rate; + + if (!drm_dev_enter(dev, &idx)) + return -ENODEV; + + switch (hibmc_dp_get_link_rate(&priv->dp)) { + case 0x1e: + rate = 810; // 8.1Gbps + break; + case 0x14: + rate = 540; // 5.4Gbps + break; + case 0xA: + rate = 270; // 2.7Gbps + break; + case 0x6: + rate = 162; // 1.62Gbps + break; + default: + rate = 0; + } + + seq_printf(m, "enable lanes: %u\n", hibmc_dp_get_lanes(&priv->dp)); + seq_printf(m, "link rate: %d\n", rate); + seq_printf(m, "vfresh: %d\n", drm_mode_vrefresh(&priv->crtc.mode)); + seq_printf(m, "dpcd version: 0x%x\n", hibmc_dp_get_dpcd(&priv->dp)); + seq_printf(m, "hpd status: %d\n", priv->dp.hpd_status); + + drm_dev_exit(idx); + + return 0; +} + static ssize_t hibmc_control_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { @@ -93,12 +131,19 @@ static const struct file_operations hibmc_dbg_fops = { .release = single_release, }; +static struct drm_info_list hibmc_debugfs_list[] = { + { "hibmc-dp", hibmc_dp_show }, +}; + void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root) { struct drm_device *dev = connector->dev; struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + struct drm_minor *minor = dev->primary; /* create the file in drm directory, so we don't need to remove manually */ - debugfs_create_file("colorbar-cfg", 0200, - root, priv, &hibmc_dbg_fops); + debugfs_create_file("colorbar-cfg", 0200, root, priv, &hibmc_dbg_fops); + + drm_debugfs_create_files(hibmc_debugfs_list, ARRAY_SIZE(hibmc_debugfs_list), + minor->debugfs_root, minor); } diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c index 41a5d3a9625c6..4b07e53dcada8 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_dp.c @@ -11,6 +11,7 @@ #include #include "hibmc_drm_drv.h" +#include "hibmc_drm_regs.h" #include "dp/dp_hw.h" #define HIBMC_DP_MASKED_SINK_HPD_PLUG_INT BIT(2) @@ -18,6 +19,7 @@ static int hibmc_dp_connector_get_modes(struct drm_connector *connector) { + struct hibmc_dp *dp = to_hibmc_dp(connector); const struct drm_edid *drm_edid; int count; @@ -26,12 +28,39 @@ static int hibmc_dp_connector_get_modes(struct drm_connector *connector) drm_edid_connector_update(connector, drm_edid); count = drm_edid_connector_add_modes(connector); + if (count) + dp->is_connected = true; + else + dp->is_connected = false; drm_edid_free(drm_edid); return count; } +static int hibmc_dp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + struct drm_modeset_acquire_ctx *ctx, + enum drm_mode_status *status) +{ + struct hibmc_dp *dp = to_hibmc_dp(connector); + u64 cur_val, max_val; + + if (!dp->is_connected) { + *status = MODE_OK; + return 0; + } + + cur_val = (u64)mode->htotal * mode->vtotal * drm_mode_vrefresh(mode) * BPP_24; + max_val = (u64)hibmc_dp_get_link_rate(dp) * DP_MODE_VALI_CAL * hibmc_dp_get_lanes(dp); + if (cur_val > max_val) + *status = MODE_CLOCK_HIGH; + else + *status = MODE_OK; + + return 0; +} + static int hibmc_dp_detect(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, bool force) { @@ -45,6 +74,7 @@ static int hibmc_dp_detect(struct drm_connector *connector, static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = { .get_modes = hibmc_dp_connector_get_modes, + .mode_valid_ctx = hibmc_dp_mode_valid, .detect_ctx = hibmc_dp_detect, }; @@ -110,9 +140,26 @@ static void hibmc_dp_encoder_disable(struct drm_encoder *drm_encoder, hibmc_dp_display_en(dp, false); } +static void hibmc_dp_encoder_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + u32 reg; + struct drm_device *dev = encoder->dev; + struct hibmc_drm_private *priv = to_hibmc_drm_private(dev); + + reg = readl(priv->mmio + HIBMC_DISPLAY_CONTROL_HISILE); + reg |= HIBMC_DISPLAY_CONTROL_FPVDDEN(1); + reg |= HIBMC_DISPLAY_CONTROL_PANELDATE(1); + reg |= HIBMC_DISPLAY_CONTROL_FPEN(1); + reg |= HIBMC_DISPLAY_CONTROL_VBIASEN(1); + writel(reg, priv->mmio + HIBMC_DISPLAY_CONTROL_HISILE); +} + static const struct drm_encoder_helper_funcs hibmc_dp_encoder_helper_funcs = { .atomic_enable = hibmc_dp_encoder_enable, .atomic_disable = hibmc_dp_encoder_disable, + .atomic_mode_set = hibmc_dp_encoder_mode_set, }; irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) @@ -132,6 +179,8 @@ irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) dp->hpd_status = 0; if (dev->registered) drm_connector_helper_hpd_irq_event(&dp->connector); + } else { + drm_dbg_dp(dev, "HPD OUT occur but err!\n"); } } else { if (dp->irq_status & HIBMC_DP_MASKED_SINK_HPD_PLUG_INT) { @@ -140,6 +189,8 @@ irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg) dp->hpd_status = 1; if (dev->registered) drm_connector_helper_hpd_irq_event(&dp->connector); + } else { + drm_dbg_dp(dev, "HPD IN occur but err!\n"); } } diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 9c963e083ea24..5059cbab354e4 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -133,7 +133,7 @@ static int hibmc_kms_init(struct hibmc_drm_private *priv) ret = hibmc_de_init(priv); if (ret) { drm_err(dev, "failed to init de: %d\n", ret); - return ret; + goto err; } /* @@ -143,17 +143,24 @@ static int hibmc_kms_init(struct hibmc_drm_private *priv) ret = readl(priv->mmio + HIBMC_DP_HOST_SERDES_CTRL); if (ret) { ret = hibmc_dp_init(priv); - if (ret) + if (ret) { drm_err(dev, "failed to init dp: %d\n", ret); + goto err; + } } ret = hibmc_vdac_init(priv); if (ret) { drm_err(dev, "failed to init vdac: %d\n", ret); - return ret; + goto err; } return 0; + +err: + drm_atomic_helper_shutdown(dev); + + return ret; } /* @@ -328,8 +335,10 @@ static int hibmc_load(struct drm_device *dev) } ret = hibmc_kms_init(priv); - if (ret) - goto err; + if (ret) { + drm_err(dev, "hibmc kms init failed, ret:%d\n", ret); + return ret; + } ret = drm_vblank_init(dev, dev->mode_config.num_crtc); if (ret) { diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h index 3f907329797cc..74fd4bde02133 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.h @@ -70,6 +70,7 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv); int hibmc_mm_init(struct hibmc_drm_private *hibmc); int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *connector); +void hibmc_ddc_del(struct hibmc_vdac *vdac); int hibmc_dp_init(struct hibmc_drm_private *priv); void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root); diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c index 99b3b77b5445f..44860011855eb 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_i2c.c @@ -95,3 +95,8 @@ int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *vdac) return i2c_bit_add_bus(&vdac->adapter); } + +void hibmc_ddc_del(struct hibmc_vdac *vdac) +{ + i2c_del_adapter(&vdac->adapter); +} diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c index 5bcaa02638920..b1be55a56b2e8 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_vdac.c @@ -48,7 +48,7 @@ static int hibmc_connector_get_modes(struct drm_connector *connector) return count; } -static void hibmc_connector_destroy(struct drm_connector *connector) +static void hibmc_vdac_connector_destroy(struct drm_connector *connector) { struct hibmc_vdac *vdac = to_hibmc_vdac(connector); @@ -89,7 +89,7 @@ static void hibmc_vdac_force(struct drm_connector *connector) static const struct drm_connector_funcs hibmc_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = hibmc_connector_destroy, + .destroy = hibmc_vdac_connector_destroy, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -135,7 +135,7 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv) ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_DAC, NULL); if (ret) { drm_err(dev, "failed to init encoder: %d\n", ret); - return ret; + goto err; } drm_encoder_helper_add(encoder, &hibmc_encoder_helper_funcs); @@ -146,7 +146,7 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv) &vdac->adapter); if (ret) { drm_err(dev, "failed to init connector: %d\n", ret); - return ret; + goto err; } drm_connector_helper_add(connector, &hibmc_connector_helper_funcs); @@ -156,4 +156,9 @@ int hibmc_vdac_init(struct hibmc_drm_private *priv) connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; return 0; + +err: + hibmc_ddc_del(vdac); + + return ret; } From 2d3bdfd9b7bd79b2288af7fb4f86f688e7246d72 Mon Sep 17 00:00:00 2001 From: Yuchen Tang Date: Thu, 18 Jan 2024 14:40:29 +0800 Subject: [PATCH 051/107] mm: Export symbol walk_page_range commit a42298d4bc031d07a7b06adb02d73c1ac18ea208 openEuler Export symbol walk_page_range, so that etmem_scan can exploit this for page table walk. In etmem scan module, it will scan vma segments passed by user space, and perform page table walk to check the Access bit of each page, before reporting the scanning results to user space. Signed-off-by: Yuchen Tang Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- mm/pagewalk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/pagewalk.c b/mm/pagewalk.c index b7d7e4fcfad7a..144c9fed0fc94 100644 --- a/mm/pagewalk.c +++ b/mm/pagewalk.c @@ -525,6 +525,7 @@ int walk_page_range(struct mm_struct *mm, unsigned long start, } while (start = next, start < end); return err; } +EXPORT_SYMBOL_GPL(walk_page_range); /** * walk_page_range_novma - walk a range of pagetables not backed by a vma From 2e413de477665fe15f19c41c2f917b5713c059c6 Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Thu, 17 Apr 2025 21:49:29 +0800 Subject: [PATCH 052/107] soc cache: Add framework driver for HiSilicon SoC cache commit e6ecc3b028b8c3151c7ca3f4bd53e6385db72f93 openEuler HiSilicon SoC cache is comprised of multiple hardware devices, a driver in this patch is used to provide common utilities for other drivers to avoid redundancy. The driver will create a file of `/dev/hisi_soc_cache_mgmt`, mmap operations to it will be taken as applying cache lock for a memory region, ioctl operations will be taken as cache maintenance. The address is aligned up to return to user and the arena's starting address is stored for future release. Unused pages produced during above process are released to allow minimum arena. Co-developed-by: Yicong Yang Signed-off-by: Yicong Yang Co-developed-by: Yushan Wang Signed-off-by: Yushan Wang Signed-off-by: Jie Wang Signed-off-by: huwentao Signed-off-by: WangYuli --- drivers/soc/hisilicon/Kconfig | 11 + drivers/soc/hisilicon/Makefile | 1 + .../soc/hisilicon/hisi_soc_cache_framework.c | 378 ++++++++++++++++++ .../soc/hisilicon/hisi_soc_cache_framework.h | 77 ++++ .../uapi/misc/hisi_soc_cache/hisi_soc_cache.h | 35 ++ 5 files changed, 502 insertions(+) create mode 100644 drivers/soc/hisilicon/hisi_soc_cache_framework.c create mode 100644 drivers/soc/hisilicon/hisi_soc_cache_framework.h create mode 100644 include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h diff --git a/drivers/soc/hisilicon/Kconfig b/drivers/soc/hisilicon/Kconfig index 0ab688af308fe..a85dbe8ac60c1 100644 --- a/drivers/soc/hisilicon/Kconfig +++ b/drivers/soc/hisilicon/Kconfig @@ -18,4 +18,15 @@ config KUNPENG_HCCS Say M here if you want to include support for querying the health status and port information of HCCS on Kunpeng SoC. +config HISI_SOC_CACHE + tristate "HiSilicon Cache driver for Kunpeng SoC" + depends on ARCH_HISI + help + This driver provides the basic utilities for drivers of + different part of Kunpeng SoC cache, including L3 cache and + Hydra Home Agent etc. + + If either HiSilicon L3 cache driver or HiSilicon Hydra Home + Agent driver is needed, say yes. + endmenu diff --git a/drivers/soc/hisilicon/Makefile b/drivers/soc/hisilicon/Makefile index 226e747e70d67..dbc673b71d4d0 100644 --- a/drivers/soc/hisilicon/Makefile +++ b/drivers/soc/hisilicon/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_KUNPENG_HCCS) += kunpeng_hccs.o +obj-$(CONFIG_HISI_SOC_CACHE) += hisi_soc_cache_framework.o diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.c b/drivers/soc/hisilicon/hisi_soc_cache_framework.c new file mode 100644 index 0000000000000..1782e6a44668d --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Framework for HiSilicon SoC cache, manages HiSilicon SoC cache drivers. + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Jie Wang + * Author: Yicong Yang + * Author: Yushan Wang + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hisi_soc_cache_framework.h" + +struct hisi_soc_comp_inst { + struct list_head node; + struct hisi_soc_comp *comp; +}; + +struct hisi_soc_comp_list { + struct list_head node; + /* protects list of HiSilicon SoC cache components */ + spinlock_t lock; + u32 inst_num; +}; + +static struct hisi_soc_comp_list soc_cache_devs[SOC_COMP_TYPE_MAX]; + +int hisi_soc_cache_maintain(phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + struct hisi_soc_comp_inst *inst; + struct list_head *head; + int ret = -EOPNOTSUPP; + + if (mnt_type >= HISI_CACHE_MAINT_MAX) + return -EINVAL; + + guard(spinlock)(&soc_cache_devs[HISI_SOC_HHA].lock); + + head = &soc_cache_devs[HISI_SOC_HHA].node; + list_for_each_entry(inst, head, node) { + ret = inst->comp->ops->do_maintain(inst->comp, addr, size, + mnt_type); + if (ret) + return ret; + } + + list_for_each_entry(inst, head, node) { + ret = inst->comp->ops->poll_maintain_done(inst->comp, addr, + size, mnt_type); + if (ret) + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(hisi_soc_cache_maintain); + +static int hisi_soc_cache_maint_pte_entry(pte_t *pte, unsigned long addr, + unsigned long next, struct mm_walk *walk) +{ +#ifdef HISI_SOC_CACHE_LLT + unsigned int mnt_type = *((unsigned int *)walk->priv); +#else + unsigned int mnt_type = *((unsigned int *)walk->private); +#endif + size_t size = next - addr; + phys_addr_t paddr; + + if (!pte_present(ptep_get(pte))) + return -EINVAL; + + paddr = PFN_PHYS(pte_pfn(*pte)) + offset_in_page(addr); + + return hisi_soc_cache_maintain(paddr, size, mnt_type); +} + +static const struct mm_walk_ops hisi_soc_cache_maint_walk = { + .pte_entry = hisi_soc_cache_maint_pte_entry, + .walk_lock = PGWALK_RDLOCK, +}; + +static int hisi_soc_cache_inst_check(const struct hisi_soc_comp *comp, + enum hisi_soc_comp_type comp_type) +{ + /* Different types of component could have different ops. */ + switch (comp_type) { + case HISI_SOC_HHA: + if (!comp->ops->do_maintain || !comp->ops->poll_maintain_done) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int hisi_soc_cache_inst_add(struct hisi_soc_comp *comp, + enum hisi_soc_comp_type comp_type) +{ + struct hisi_soc_comp_inst *comp_inst; + int ret; + + ret = hisi_soc_cache_inst_check(comp, comp_type); + if (ret) + return ret; + + comp_inst = kzalloc(sizeof(*comp_inst), GFP_KERNEL); + if (!comp_inst) + return -ENOMEM; + + comp_inst->comp = comp; + + scoped_guard(spinlock, &soc_cache_devs[comp_type].lock) { + list_add_tail(&comp_inst->node, + &soc_cache_devs[comp_type].node); + soc_cache_devs[comp_type].inst_num++; + } + + return 0; +} + +/* + * When @comp is NULL, it means to delete all instances of @comp_type. + */ +static void hisi_soc_cache_inst_del(struct hisi_soc_comp *comp, + enum hisi_soc_comp_type comp_type) +{ + struct hisi_soc_comp_inst *inst, *tmp; + + guard(spinlock)(&soc_cache_devs[comp_type].lock); + list_for_each_entry_safe(inst, tmp, &soc_cache_devs[comp_type].node, + node) { + if (comp && comp != inst->comp) + continue; + + if (soc_cache_devs[comp_type].inst_num > 0) + soc_cache_devs[comp_type].inst_num--; + + list_del(&inst->node); + kfree(inst); + + /* Stop the loop if we have already deleted @comp. */ + if (comp) + break; + } +} + +int hisi_soc_comp_inst_add(struct hisi_soc_comp *comp) +{ + int ret, i = 0; + + if (!comp || !comp->ops || comp->comp_type == 0) + return -EINVAL; + + for_each_set_bit_from(i, &comp->comp_type, SOC_COMP_TYPE_MAX) { + ret = hisi_soc_cache_inst_add(comp, i); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hisi_soc_comp_inst_add); + +int hisi_soc_comp_inst_del(struct hisi_soc_comp *comp) +{ + int i; + + if (!comp) + return -EINVAL; + + for_each_set_bit(i, &comp->comp_type, SOC_COMP_TYPE_MAX) + hisi_soc_cache_inst_del(comp, i); + + return 0; +} +EXPORT_SYMBOL_GPL(hisi_soc_comp_inst_del); + +static int __hisi_soc_cache_maintain(unsigned long __user vaddr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + unsigned long start = untagged_addr(vaddr); + struct vm_area_struct *vma; + int ret = 0; + + mmap_read_lock_killable(current->mm); + + vma = vma_lookup(current->mm, vaddr); + if (!vma || vaddr + size > vma->vm_end || !size) { + ret = -EINVAL; + goto out; + } + + /* User should have the write permission of target memory */ + if (!(vma->vm_flags & VM_WRITE)) { + ret = -EINVAL; + goto out; + } + + ret = walk_page_range(current->mm, start, start + size, + &hisi_soc_cache_maint_walk, &mnt_type); + +out: + mmap_read_unlock(current->mm); + return ret; +} + +static long hisi_soc_cache_mgmt_ioctl(struct file *file, u32 cmd, unsigned long arg) +{ + struct hisi_soc_cache_ioctl_param *param = + kzalloc(sizeof(struct hisi_soc_cache_ioctl_param), GFP_KERNEL); + long ret; + + if (!param) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(param, (void __user *)arg, sizeof(*param))) { + ret = -EFAULT; + goto out; + } + + switch (cmd) { + case HISI_CACHE_MAINTAIN: + ret = __hisi_soc_cache_maintain(param->addr, param->size, + param->op_type); + break; + default: + ret = -EINVAL; + break; + } +out: + kfree(param); + return ret; +} + +static const struct file_operations soc_cache_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = hisi_soc_cache_mgmt_ioctl, +}; + +static struct miscdevice soc_cache_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "hisi_soc_cache_mgmt", + .fops = &soc_cache_dev_fops, + .mode = 0600, +}; + +static void hisi_soc_cache_inst_uninit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(soc_cache_devs); ++i) + hisi_soc_cache_inst_del(NULL, i); +} + +static void hisi_soc_cache_framework_data_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(soc_cache_devs); ++i) { + spin_lock_init(&soc_cache_devs[i].lock); + INIT_LIST_HEAD(&soc_cache_devs[i].node); + } +} + +static const char *const hisi_soc_cache_item_str[SOC_COMP_TYPE_MAX] = { + "hha" +}; + +/* + * Print cache instance number debug information for debug FS. + */ +static ssize_t hisi_soc_cache_dbg_get_inst_num(struct file *file, + char __user *buff, + size_t cnt, + loff_t *ppos) +{ +#define HISI_SOC_CACHE_DBGFS_REG_LEN 100 + char *read_buff; + int len, i, pos = 0; + int ret = 0; + + if (!access_ok(buff, cnt)) + return -EFAULT; + if (*ppos < 0) + return -EINVAL; + if (cnt == 0) + return 0; + + read_buff = kzalloc(HISI_SOC_CACHE_DBGFS_REG_LEN, GFP_KERNEL); + if (!read_buff) + return -ENOMEM; + + len = HISI_SOC_CACHE_DBGFS_REG_LEN; + + for (i = 0; i < ARRAY_SIZE(soc_cache_devs); i++) { + guard(spinlock)(&soc_cache_devs[i].lock); + pos += scnprintf(read_buff + pos, len - pos, + "%s inst num: %u\n", + hisi_soc_cache_item_str[i], + soc_cache_devs[i].inst_num); + } + + ret = simple_read_from_buffer(buff, cnt, ppos, read_buff, + strlen(read_buff)); + kfree(read_buff); + return ret; +} + +static struct dentry *hisi_cache_dbgfs_root; +static const struct file_operations hisi_cache_dbgfs_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = hisi_soc_cache_dbg_get_inst_num, +}; + +static void hisi_soc_cache_dbgfs_init(void) +{ + hisi_cache_dbgfs_root = debugfs_create_dir("hisi_soc_cache_frm", NULL); + debugfs_create_file("instance", 0400, hisi_cache_dbgfs_root, NULL, + &hisi_cache_dbgfs_ops); +} + +static void hisi_soc_cache_dbgfs_uninit(void) +{ + debugfs_remove_recursive(hisi_cache_dbgfs_root); + hisi_cache_dbgfs_root = NULL; +} + +static int __init hisi_soc_cache_framework_init(void) +{ + int ret; + + hisi_soc_cache_framework_data_init(); + + ret = misc_register(&soc_cache_miscdev); + if (ret) { + hisi_soc_cache_inst_uninit(); + return ret; + } + + hisi_soc_cache_dbgfs_init(); + + return 0; +} +module_init(hisi_soc_cache_framework_init); + +static void __exit hisi_soc_cache_framework_exit(void) +{ + hisi_soc_cache_dbgfs_uninit(); + misc_deregister(&soc_cache_miscdev); + hisi_soc_cache_inst_uninit(); +} +module_exit(hisi_soc_cache_framework_exit); + +MODULE_DESCRIPTION("HiSilicon SoC Cache Framework Driver"); +MODULE_AUTHOR("Jie Wang "); +MODULE_AUTHOR("Yushan Wang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.h b/drivers/soc/hisilicon/hisi_soc_cache_framework.h new file mode 100644 index 0000000000000..9b3de4e501612 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file of framework for HiSilicon SoC cache. + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Jie Wang + * Author: Yicong Yang + * Author: Yushan Wang + */ + +#ifndef HISI_CACHE_FRAMEWORK_H +#define HISI_CACHE_FRAMEWORK_H + +#include +#include + +#include + +enum hisi_soc_comp_type { + HISI_SOC_HHA, + SOC_COMP_TYPE_MAX +}; + +struct hisi_soc_comp; + +/** + * struct hisi_soc_comp_ops - Callbacks for SoC cache drivers to handle + * operation requests. + * @maintain_enable: perform certain cache maintain operation on HHA. + * @poll_maintain_done: check if the HHA maintain operation has succeeded. + * + * Operations are decoupled into two phases so that framework does not have + * to wait for one operation to finish before calling the next when multiple + * hardwares onboard. + * + * Implementers must implement the functions in pairs. Implementation should + * return -EBUSY when: + * - insufficient resources are available to perform the operation. + * - previously raised operation is not finished. + * - new operations (do_lock(), do_unlock() etc.) to the same address + * before corresponding done functions being called. + */ +struct hisi_soc_comp_ops { + int (*do_maintain)(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type); + int (*poll_maintain_done)(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type); +}; + +/** + * struct hisi_soc_comp - Struct of HiSilicon SoC cache components. + * @ops: possible operations a component may perform. + * @affinity_mask: cpus that associate with this component. + * @comp_type: bitmap declaring the type of the component. + * + * A component may have multiple types (e.g. a piece of multi-function device). + * If so, set the bit of @comp_type according to its supporting type in struct + * hisi_soc_comp_type. + */ +struct hisi_soc_comp { + struct hisi_soc_comp_ops *ops; + cpumask_t affinity_mask; + /* + * Setting bit x to 1 means this instance supports feature of x-th + * entry in enum hisi_soc_comp_type. + */ + unsigned long comp_type; +}; + +int hisi_soc_comp_inst_add(struct hisi_soc_comp *comp); +int hisi_soc_comp_inst_del(struct hisi_soc_comp *comp); +int hisi_soc_cache_maintain(phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type); + +#endif diff --git a/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h b/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h new file mode 100644 index 0000000000000..5441f6f75b819 --- /dev/null +++ b/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* Copyright (c) 2024-2024 HiSilicon Limited. */ +#ifndef _UAPI_HISI_SOC_CACHE_H +#define _UAPI_HISI_SOC_CACHE_H + +#include + +/* HISI_CACHE_MAINTAIN: cache maintain operation for HiSilicon SoC */ +#define HISI_CACHE_MAINTAIN _IOW('C', 1, unsigned long) + +/* + * Further information of these operations can be found at: + * https://developer.arm.com/documentation/ihi0050/latest/ + */ +enum hisi_soc_cache_maint_type { + HISI_CACHE_MAINT_CLEANSHARED, + HISI_CACHE_MAINT_CLEANINVALID, + HISI_CACHE_MAINT_MAKEINVALID, + + HISI_CACHE_MAINT_MAX +}; + +/** + * struct hisi_soc_cache_ioctl_param - User data for hisi cache operates. + * @op_type: cache maintain type + * @addr: cache maintain address + * @size: cache maintain size + */ +struct hisi_soc_cache_ioctl_param { + enum hisi_soc_cache_maint_type op_type; + unsigned long addr; + unsigned long size; +}; + +#endif From 315105c1b647cf1da5e7638e19a04a17964426fb Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Thu, 17 Apr 2025 21:49:30 +0800 Subject: [PATCH 053/107] soc cache: Support cache maintenance for HiSilicon SoC Hydra Home Agent commit 84d2eab3f180350469bf01b351bba6610c22c932 openEuler Hydra Home Agent is a device used for maintain cache coherency, add support of cache maintenance operations for it. Same as HiSilicon L3 cache and L3 cache PMU, memory resource of HHA conflicts with that of HHA PMU. Same workaround is implemented here to workaround resource conflict check. Signed-off-by: Yicong Yang Co-developed-by: Yushan Wang Signed-off-by: Yushan Wang Signed-off-by: huwentao Signed-off-by: WangYuli --- drivers/soc/hisilicon/Kconfig | 11 ++ drivers/soc/hisilicon/Makefile | 1 + drivers/soc/hisilicon/hisi_soc_hha.c | 189 +++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 drivers/soc/hisilicon/hisi_soc_hha.c diff --git a/drivers/soc/hisilicon/Kconfig b/drivers/soc/hisilicon/Kconfig index a85dbe8ac60c1..49ccc25a0d13d 100644 --- a/drivers/soc/hisilicon/Kconfig +++ b/drivers/soc/hisilicon/Kconfig @@ -29,4 +29,15 @@ config HISI_SOC_CACHE If either HiSilicon L3 cache driver or HiSilicon Hydra Home Agent driver is needed, say yes. +config HISI_SOC_HHA + tristate "HiSilicon Hydra Home Agent (HHA) device driver" + depends on ARM64 && ACPI || COMPILE_TEST + depends on HISI_SOC_CACHE + help + The Hydra Home Agent (HHA) is responsible of cache coherency + on SoC. This drivers provides cache maintenance functions of HHA. + + This driver can be built as a module. If so, the module will be + called hisi_soc_hha. + endmenu diff --git a/drivers/soc/hisilicon/Makefile b/drivers/soc/hisilicon/Makefile index dbc673b71d4d0..f852d2ca80067 100644 --- a/drivers/soc/hisilicon/Makefile +++ b/drivers/soc/hisilicon/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_KUNPENG_HCCS) += kunpeng_hccs.o obj-$(CONFIG_HISI_SOC_CACHE) += hisi_soc_cache_framework.o +obj-$(CONFIG_HISI_SOC_HHA) += hisi_soc_hha.o diff --git a/drivers/soc/hisilicon/hisi_soc_hha.c b/drivers/soc/hisilicon/hisi_soc_hha.c new file mode 100644 index 0000000000000..2b6ded47d4fe0 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_soc_hha.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for HiSilicon Hydra Home Agent (HHA). + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Yicong Yang + * Yushan Wang + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hisi_soc_cache_framework.h" + +#define HISI_HHA_CTRL 0x5004 +#define HISI_HHA_CTRL_EN BIT(0) +#define HISI_HHA_CTRL_RANGE BIT(1) +#define HISI_HHA_CTRL_TYPE GENMASK(3, 2) +#define HISI_HHA_START_L 0x5008 +#define HISI_HHA_START_H 0x500c +#define HISI_HHA_LEN_L 0x5010 +#define HISI_HHA_LEN_H 0x5014 + +/* The maintain operation performs in a 128 Byte granularity */ +#define HISI_HHA_MAINT_ALIGN 128 + +#define HISI_HHA_POLL_GAP_US 10 + +struct hisi_soc_hha { + struct hisi_soc_comp comp; + /* Locks HHA instance to forbid overlapping access. */ + spinlock_t lock; + struct device *dev; + void __iomem *base; +}; + +static bool hisi_hha_cache_maintain_wait_finished(struct hisi_soc_hha *soc_hha) +{ + u32 val; + + return !readl_poll_timeout_atomic(soc_hha->base + HISI_HHA_CTRL, val, + !(val & HISI_HHA_CTRL_EN), + HISI_HHA_POLL_GAP_US, + jiffies_to_usecs(HZ)); +} + +static int hisi_hha_cache_do_maintain(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + struct hisi_soc_hha *soc_hha = container_of(comp, struct hisi_soc_hha, + comp); + int ret = 0; + u32 reg; + + if (!size) + return -EINVAL; + + if (mnt_type < 0) + return -EOPNOTSUPP; + + /* + * Hardware will search for addresses ranging [addr, addr + size -1], + * last byte included, and perform maintain in 128 byte granule + * on those which contain the addresses. + */ + size -= 1; + + guard(spinlock)(&soc_hha->lock); + + if (!hisi_hha_cache_maintain_wait_finished(soc_hha)) + return -EBUSY; + + writel(lower_32_bits(addr), soc_hha->base + HISI_HHA_START_L); + writel(upper_32_bits(addr), soc_hha->base + HISI_HHA_START_H); + writel(lower_32_bits(size), soc_hha->base + HISI_HHA_LEN_L); + writel(upper_32_bits(size), soc_hha->base + HISI_HHA_LEN_H); + + reg = FIELD_PREP(HISI_HHA_CTRL_TYPE, mnt_type); + reg |= HISI_HHA_CTRL_RANGE | HISI_HHA_CTRL_EN; + writel(reg, soc_hha->base + HISI_HHA_CTRL); + + return ret; +} + +static int hisi_hha_cache_poll_maintain_done(struct hisi_soc_comp *comp, + phys_addr_t addr, size_t size, + enum hisi_soc_cache_maint_type mnt_type) +{ + struct hisi_soc_hha *soc_hha = container_of(comp, struct hisi_soc_hha, + comp); + + guard(spinlock)(&soc_hha->lock); + + if (!hisi_hha_cache_maintain_wait_finished(soc_hha)) + return -ETIMEDOUT; + + return 0; +} + +static struct hisi_soc_comp_ops hisi_soc_hha_comp_ops = { + .do_maintain = hisi_hha_cache_do_maintain, + .poll_maintain_done = hisi_hha_cache_poll_maintain_done, +}; + +static void hisi_hha_comp_inst_del(void *priv) +{ + struct hisi_soc_hha *soc_hha = priv; + + hisi_soc_comp_inst_del(&soc_hha->comp); +} + +static int hisi_soc_hha_probe(struct platform_device *pdev) +{ + struct hisi_soc_hha *soc_hha; + struct resource *mem; + int ret; + + soc_hha = devm_kzalloc(&pdev->dev, sizeof(*soc_hha), GFP_KERNEL); + if (!soc_hha) + return -ENOMEM; + + platform_set_drvdata(pdev, soc_hha); + soc_hha->dev = &pdev->dev; + + spin_lock_init(&soc_hha->lock); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -ENODEV; + + /* + * HHA cache driver share the same register region with HHA uncore PMU + * driver in hardware's perspective, none of them should reserve the + * resource to itself only. Here exclusive access verification is + * avoided by calling devm_ioremap instead of devm_ioremap_resource to + * allow both drivers to exist at the same time. + */ + soc_hha->base = devm_ioremap(&pdev->dev, mem->start, + resource_size(mem)); + if (IS_ERR_OR_NULL(soc_hha->base)) { + return dev_err_probe(&pdev->dev, PTR_ERR(soc_hha->base), + "failed to remap io memory"); + } + + soc_hha->comp.ops = &hisi_soc_hha_comp_ops; + soc_hha->comp.comp_type = BIT(HISI_SOC_HHA); + cpumask_copy(&soc_hha->comp.affinity_mask, cpu_possible_mask); + + ret = hisi_soc_comp_inst_add(&soc_hha->comp); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register maintain inst"); + + return devm_add_action_or_reset(&pdev->dev, hisi_hha_comp_inst_del, + soc_hha); +} + +static const struct acpi_device_id hisi_soc_hha_ids[] = { + { "HISI0511", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hisi_soc_hha_ids); + +static struct platform_driver hisi_soc_hha_driver = { + .driver = { + .name = "hisi_soc_hha", + .acpi_match_table = hisi_soc_hha_ids, + }, + .probe = hisi_soc_hha_probe, +}; + +module_platform_driver(hisi_soc_hha_driver); + +MODULE_DESCRIPTION("Hisilicon Hydra Home Agent driver supporting cache maintenance"); +MODULE_AUTHOR("Yicong Yang "); +MODULE_AUTHOR("Yushan Wang "); +MODULE_LICENSE("GPL"); From 456e95b7f9008446ca6041876a2003c42733f3f8 Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Tue, 26 Aug 2025 10:02:24 +0800 Subject: [PATCH 054/107] soc cache: Enforce maintain type check commit ea6777a90d70f56dbf730fafbe936dbc0ee784ad openEuler Make Invalid may cause data loss, which is too dangereous to be exposed to userspace, so invalidate it then. Fixes: b6bd7d35a480 ("soc cache: Add framework driver for HiSilicon SoC cache") Signed-off-by: Yushan Wang Signed-off-by: Hongye Lin Signed-off-by: WangYuli --- drivers/soc/hisilicon/hisi_soc_cache_framework.c | 4 ++++ drivers/soc/hisilicon/hisi_soc_hha.c | 7 ++++++- include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.c b/drivers/soc/hisilicon/hisi_soc_cache_framework.c index 1782e6a44668d..6e3b128df4e73 100644 --- a/drivers/soc/hisilicon/hisi_soc_cache_framework.c +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.c @@ -200,6 +200,10 @@ static int __hisi_soc_cache_maintain(unsigned long __user vaddr, size_t size, struct vm_area_struct *vma; int ret = 0; + /* MakeInvalid is not allowed for calls from userspace. */ + if (mnt_type >= HISI_CACHE_MAINT_MAKEINVALID) + return -EINVAL; + mmap_read_lock_killable(current->mm); vma = vma_lookup(current->mm, vaddr); diff --git a/drivers/soc/hisilicon/hisi_soc_hha.c b/drivers/soc/hisilicon/hisi_soc_hha.c index 2b6ded47d4fe0..22a1ec8b8fc94 100644 --- a/drivers/soc/hisilicon/hisi_soc_hha.c +++ b/drivers/soc/hisilicon/hisi_soc_hha.c @@ -61,13 +61,18 @@ static int hisi_hha_cache_do_maintain(struct hisi_soc_comp *comp, { struct hisi_soc_hha *soc_hha = container_of(comp, struct hisi_soc_hha, comp); + phys_addr_t top; int ret = 0; u32 reg; if (!size) return -EINVAL; - if (mnt_type < 0) + addr = ALIGN_DOWN(addr, HISI_HHA_MAINT_ALIGN); + top = ALIGN(addr + size, HISI_HHA_MAINT_ALIGN); + size = top - addr; + + if (mnt_type < 0 || mnt_type >= HISI_CACHE_MAINT_MAX) return -EOPNOTSUPP; /* diff --git a/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h b/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h index 5441f6f75b819..8b190941c8050 100644 --- a/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h +++ b/include/uapi/misc/hisi_soc_cache/hisi_soc_cache.h @@ -15,7 +15,9 @@ enum hisi_soc_cache_maint_type { HISI_CACHE_MAINT_CLEANSHARED, HISI_CACHE_MAINT_CLEANINVALID, +#ifdef __KERNEL__ HISI_CACHE_MAINT_MAKEINVALID, +#endif HISI_CACHE_MAINT_MAX }; From bb832d01cbafe7fc5a0bb997ac54a74b285bee29 Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Tue, 26 Aug 2025 10:02:25 +0800 Subject: [PATCH 055/107] soc cache: Fix incorrect size validation commit 54706fe41c59efd23de41e30a46b7b55722c2aa3 openEuler Check the size, which is controlled by user, by adding it to vm_start and compare with end could be problematic when malicious user passes an arbitrarily big number as size which causes overflow and thus bypass the size check. Fix this by using the size as is to compare with vma range. Fixes: b6bd7d35a480 ("soc cache: Add framework driver for HiSilicon SoC cache") Signed-off-by: Yushan Wang Signed-off-by: Hongye Lin Signed-off-by: WangYuli --- drivers/soc/hisilicon/hisi_soc_cache_framework.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.c b/drivers/soc/hisilicon/hisi_soc_cache_framework.c index 6e3b128df4e73..53e9a774c528b 100644 --- a/drivers/soc/hisilicon/hisi_soc_cache_framework.c +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.c @@ -204,10 +204,14 @@ static int __hisi_soc_cache_maintain(unsigned long __user vaddr, size_t size, if (mnt_type >= HISI_CACHE_MAINT_MAKEINVALID) return -EINVAL; - mmap_read_lock_killable(current->mm); + /* Prevent overflow of vaddr + size. */ + if (!size || vaddr + size < vaddr) + return -EINVAL; + mmap_read_lock_killable(current->mm); vma = vma_lookup(current->mm, vaddr); - if (!vma || vaddr + size > vma->vm_end || !size) { + + if (!range_in_vma(vma, vaddr, vaddr + size)) { ret = -EINVAL; goto out; } From f76dc25ac29836a5211a3f4c6521cd524f45f510 Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Tue, 26 Aug 2025 10:02:26 +0800 Subject: [PATCH 056/107] soc cache: Fix incorrect error path of ioctl commit 10cbc1bc29bcc0f7b69c7d978eef9fdde4f6eb17 openEuler Though passing null pointer to kfree() could be harmless, the error path per malloc failure could be confusing when jumping to kfree() with validated null pointer. Fix this by early return instead of jumping to error handling label. Fixes: b6bd7d35a480 ("soc cache: Add framework driver for HiSilicon SoC cache") Signed-off-by: Yushan Wang Signed-off-by: Hongye Lin Signed-off-by: WangYuli --- drivers/soc/hisilicon/hisi_soc_cache_framework.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/soc/hisilicon/hisi_soc_cache_framework.c b/drivers/soc/hisilicon/hisi_soc_cache_framework.c index 53e9a774c528b..e74037e1ef909 100644 --- a/drivers/soc/hisilicon/hisi_soc_cache_framework.c +++ b/drivers/soc/hisilicon/hisi_soc_cache_framework.c @@ -236,10 +236,8 @@ static long hisi_soc_cache_mgmt_ioctl(struct file *file, u32 cmd, unsigned long kzalloc(sizeof(struct hisi_soc_cache_ioctl_param), GFP_KERNEL); long ret; - if (!param) { - ret = -ENOMEM; - goto out; - } + if (!param) + return -ENOMEM; if (copy_from_user(param, (void __user *)arg, sizeof(*param))) { ret = -EFAULT; From c2151a55a01b2e1d9aa255b2496f21205866dec1 Mon Sep 17 00:00:00 2001 From: Devyn Liu Date: Mon, 21 Jul 2025 19:26:22 +0800 Subject: [PATCH 057/107] spi: hisi-kunpeng: Fixed the wrong debugfs node name in hisi_spi debugfs initialization commit 9c04e8a224613f439d43cda9afe487e6a0702040 openEuler in hisi_spi_debugfs_init, spi controller pointer is calculated by container_of macro, and the member is hs->dev. But the host pointer cannot be calculated offset directly by this, because hs->dev points to the device in platform device(pdev->dev), and the host->dev points to the pdev->dev.parent. In this patch, this issues is fixed by getting the controller data from pdev->dev.driver_data directly, driver_data points to the spi controller data in the probe stage. Fixes: 9f5890466e93 ("spi: hisi-kunpeng: switch to use modern name") Signed-off-by: lujunhua Signed-off-by: Devyn Liu Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/spi/spi-hisi-kunpeng.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-hisi-kunpeng.c b/drivers/spi/spi-hisi-kunpeng.c index 16054695bdb04..241630589c622 100644 --- a/drivers/spi/spi-hisi-kunpeng.c +++ b/drivers/spi/spi-hisi-kunpeng.c @@ -164,7 +164,7 @@ static int hisi_spi_debugfs_init(struct hisi_spi *hs) struct spi_controller *host; - host = container_of(hs->dev, struct spi_controller, dev); + host = hs->dev->driver_data; snprintf(name, 32, "hisi_spi%d", host->bus_num); hs->debugfs = debugfs_create_dir(name, NULL); if (IS_ERR(hs->debugfs)) From 877ec42b17bce674d6a3c4841bd695ece631e732 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Wed, 27 Sep 2023 17:26:12 +0100 Subject: [PATCH 058/107] hwmon: (xgene) Migrate to use generic PCC shmem related macros [Upstream commit 2cf39b806be74661cc347e99796c97d9f54b7c42] Use the newly defined common and generic PCC shared memory region related macros in this driver to replace the locally defined ones. Cc: Jean Delvare Acked-by: Guenter Roeck Link: https://lore.kernel.org/r/20230927-pcc_defines-v2-3-0b8ffeaef2e5@arm.com Signed-off-by: Sudeep Holla Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/xgene-hwmon.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 6768dbf390390..a3ca1d5ff3217 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -57,12 +57,6 @@ (MSG_TYPE_SET(MSG_TYPE_PWRMGMT) | \ MSG_SUBTYPE_SET(hndl) | TPC_CMD_SET(cmd) | type) -/* PCC defines */ -#define PCC_SIGNATURE_MASK 0x50424300 -#define PCCC_GENERATE_DB_INT BIT(15) -#define PCCS_CMD_COMPLETE BIT(0) -#define PCCS_SCI_DOORBEL BIT(1) -#define PCCS_PLATFORM_NOTIFICATION BIT(3) /* * Arbitrary retries in case the remote processor is slow to respond * to PCC commands @@ -142,15 +136,15 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) /* Write signature for subspace */ WRITE_ONCE(generic_comm_base->signature, - cpu_to_le32(PCC_SIGNATURE_MASK | ctx->mbox_idx)); + cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); /* Write to the shared command region */ WRITE_ONCE(generic_comm_base->command, - cpu_to_le16(MSG_TYPE(msg[0]) | PCCC_GENERATE_DB_INT)); + cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); /* Flip CMD COMPLETE bit */ val = le16_to_cpu(READ_ONCE(generic_comm_base->status)); - val &= ~PCCS_CMD_COMPLETE; + val &= ~PCC_STATUS_CMD_COMPLETE; WRITE_ONCE(generic_comm_base->status, cpu_to_le16(val)); /* Copy the message to the PCC comm space */ @@ -544,7 +538,7 @@ static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) msg = generic_comm_base + 1; /* Check if platform sends interrupt */ if (!xgene_word_tst_and_clr(&generic_comm_base->status, - PCCS_SCI_DOORBEL)) + PCC_STATUS_SCI_DOORBELL)) return; /* @@ -566,7 +560,7 @@ static void xgene_hwmon_pcc_rx_cb(struct mbox_client *cl, void *msg) TPC_CMD(((u32 *)msg)[0]) == TPC_ALARM))) { /* Check if platform completes command */ if (xgene_word_tst_and_clr(&generic_comm_base->status, - PCCS_CMD_COMPLETE)) { + PCC_STATUS_CMD_COMPLETE)) { ctx->sync_msg.msg = ((u32 *)msg)[0]; ctx->sync_msg.param1 = ((u32 *)msg)[1]; ctx->sync_msg.param2 = ((u32 *)msg)[2]; From c0fd6bd2119437da38742f90d73f1e7ab3774104 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Wed, 27 Sep 2023 17:26:13 +0100 Subject: [PATCH 059/107] soc: kunpeng_hccs: Migrate to use generic PCC shmem related macros [Upstream commit a46e42c097982e258f89c64c5f52f30994bcfeda] Use the newly defined common and generic PCC shared memory region related macros in this driver to replace the locally defined ones. Cc: Huisong Li Reviewed-by: Huisong Li Link: https://lore.kernel.org/r/20230927-pcc_defines-v2-4-0b8ffeaef2e5@arm.com Signed-off-by: Sudeep Holla Signed-off-by: zhaolichang <943677312@qq.com> Signed-off-by: WangYuli --- drivers/soc/hisilicon/kunpeng_hccs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/soc/hisilicon/kunpeng_hccs.c b/drivers/soc/hisilicon/kunpeng_hccs.c index f3810d9d1caa7..27a96cafd1ea8 100644 --- a/drivers/soc/hisilicon/kunpeng_hccs.c +++ b/drivers/soc/hisilicon/kunpeng_hccs.c @@ -31,10 +31,6 @@ #include "kunpeng_hccs.h" -/* PCC defines */ -#define HCCS_PCC_SIGNATURE_MASK 0x50434300 -#define HCCS_PCC_STATUS_CMD_COMPLETE BIT(0) - /* * Arbitrary retries in case the remote processor is slow to respond * to PCC commands @@ -187,7 +183,7 @@ static int hccs_check_chan_cmd_complete(struct hccs_dev *hdev) * deadline_us(timeout_us) until PCC command complete bit is set(cond) */ ret = readw_poll_timeout(&comm_base->status, status, - status & HCCS_PCC_STATUS_CMD_COMPLETE, + status & PCC_STATUS_CMD_COMPLETE, HCCS_POLL_STATUS_TIME_INTERVAL_US, cl_info->deadline_us); if (unlikely(ret)) @@ -208,7 +204,7 @@ static int hccs_pcc_cmd_send(struct hccs_dev *hdev, u8 cmd, int ret; /* Write signature for this subspace */ - tmp.signature = HCCS_PCC_SIGNATURE_MASK | hdev->chan_id; + tmp.signature = PCC_SIGNATURE | hdev->chan_id; /* Write to the shared command region */ tmp.command = cmd; /* Clear cmd complete bit */ From 421b7cd9131a14a1760600ad84174adb8d7bcaad Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 1 Apr 2025 17:08:49 +0800 Subject: [PATCH 060/107] hwmon: (acpi_power_meter) Fix fail to load module on platform without _PMD method [Upstream commit fabb1f813ec05975fd3428e72a62ef9f855fd3b4] According to the ACPI specification, the _PMD method is optional. The acpi_power_meter driver shouldn't fail to load if the platform has no _PMD method. Signed-off-by: Huisong Li Message-ID: <20241112021228.22914-1-lihuisong@huawei.com> [groeck: Reworded commit description] Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index b772c076a5aed..262e503ff17f9 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -680,8 +680,9 @@ static int setup_attrs(struct acpi_power_meter_resource *resource) { int res = 0; + /* _PMD method is optional. */ res = read_domain_devices(resource); - if (res) + if (res != -ENODEV) return res; if (resource->caps.flags & POWER_METER_CAN_MEASURE) { From d5993d3e014902d2d7bf816f605d1a461381a5f9 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 1 Apr 2025 17:08:50 +0800 Subject: [PATCH 061/107] hwmon: (acpi_power_meter) Fix uninitialized variables [Upstream commit 7532e68f5d8f05353765d7585a791a14985d68b7] The 'power1_alarm' attribute uses the 'power' and 'cap' in the acpi_power_meter_resource structure. Currently, these two fields are just updated when user query 'power' and 'cap' attribute. If user directly query the 'power1_alarm' attribute without queryng above two attributes, driver will use uninitialized variables to judge. So this patch adds the setting of alarm state and update 'cap' in the notification callback and update 'power' and 'cap' if needed to show the real alarm state. Signed-off-by: Huisong Li Link: https://lore.kernel.org/r/20250109081708.27366-2-lihuisong@huawei.com Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 262e503ff17f9..9b54f0b87066e 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -84,6 +84,7 @@ struct acpi_power_meter_resource { u64 power; u64 cap; u64 avg_interval; + bool power_alarm; int sensors_valid; unsigned long sensors_last_updated; struct sensor_device_attribute sensors[NUM_SENSORS]; @@ -396,6 +397,9 @@ static ssize_t show_val(struct device *dev, struct acpi_device *acpi_dev = to_acpi_device(dev); struct acpi_power_meter_resource *resource = acpi_dev->driver_data; u64 val = 0; + int ret; + + guard(mutex)(&resource->lock); switch (attr->index) { case 0: @@ -423,10 +427,17 @@ static ssize_t show_val(struct device *dev, val = 0; break; case 6: - if (resource->power > resource->cap) - val = 1; - else - val = 0; + ret = update_meter(resource); + if (ret) + return ret; + /* need to update cap if not to support the notification. */ + if (!(resource->caps.flags & POWER_METER_CAN_NOTIFY)) { + ret = update_cap(resource); + if (ret) + return ret; + } + val = resource->power_alarm || resource->power > resource->cap; + resource->power_alarm = resource->power > resource->cap; break; case 7: case 8: @@ -848,12 +859,20 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); break; case METER_NOTIFY_CAP: + mutex_lock(&resource->lock); + res = update_cap(resource); + if (res) + dev_err_once(&device->dev, "update cap failed when capping value is changed.\n"); + mutex_unlock(&resource->lock); sysfs_notify(&device->dev.kobj, NULL, POWER_CAP_NAME); break; case METER_NOTIFY_INTERVAL: sysfs_notify(&device->dev.kobj, NULL, POWER_AVG_INTERVAL_NAME); break; case METER_NOTIFY_CAPPING: + mutex_lock(&resource->lock); + resource->power_alarm = true; + mutex_unlock(&resource->lock); sysfs_notify(&device->dev.kobj, NULL, POWER_ALARM_NAME); dev_info(&device->dev, "Capping in progress.\n"); break; From a337eec9c3027fc4e1733964bdca050809d593b9 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 1 Apr 2025 17:08:51 +0800 Subject: [PATCH 062/107] hwmon: (acpi_power_meter) Fix update the power trip points on failure [Upstream commit 02f1a5911550bd6b39526d18282b10d441e04ed1] The power trip points maintained in local should not be updated when '_PTP' method fails to evaluate. Signed-off-by: Huisong Li Link: https://lore.kernel.org/r/20250109081708.27366-3-lihuisong@huawei.com Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 9b54f0b87066e..8381f4405978b 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -293,8 +293,8 @@ static ssize_t set_trip(struct device *dev, struct device_attribute *devattr, struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct acpi_device *acpi_dev = to_acpi_device(dev); struct acpi_power_meter_resource *resource = acpi_dev->driver_data; + unsigned long temp, trip_bk; int res; - unsigned long temp; res = kstrtoul(buf, 10, &temp); if (res) @@ -302,13 +302,15 @@ static ssize_t set_trip(struct device *dev, struct device_attribute *devattr, temp = DIV_ROUND_CLOSEST(temp, 1000); - mutex_lock(&resource->lock); + guard(mutex)(&resource->lock); + + trip_bk = resource->trip[attr->index - 7]; resource->trip[attr->index - 7] = temp; res = set_acpi_trip(resource); - mutex_unlock(&resource->lock); - - if (res) + if (res) { + resource->trip[attr->index - 7] = trip_bk; return res; + } return count; } From e35c3f0ab4b101c2573a61ce05c635043f15bb31 Mon Sep 17 00:00:00 2001 From: Kazuhiro Abe Date: Tue, 1 Apr 2025 17:08:52 +0800 Subject: [PATCH 063/107] hwmon: (acpi_power_meter) Fix a check for the return value of read_domain_devices(). [Upstream commit 8d6bf2e1055fa2cca4bf233f46d4d1e2086cc5ff] After commit fabb1f813ec0 ("hwmon: (acpi_power_meter) Fix fail to load module on platform without _PMD method"), the acpi_power_meter driver fails to load if the platform has _PMD method. To address this, add a check for successful read_domain_devices(). Tested on Nvidia Grace machine. Fixes: fabb1f813ec0 ("hwmon: (acpi_power_meter) Fix fail to load module on platform without _PMD method") Signed-off-by: Kazuhiro Abe Link: https://lore.kernel.org/r/20250115073532.3211000-1-fj1078ii@aa.jp.fujitsu.com [groeck: Dropped unnecessary () from expression] Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 8381f4405978b..5bac41ef41fca 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -695,7 +695,7 @@ static int setup_attrs(struct acpi_power_meter_resource *resource) /* _PMD method is optional. */ res = read_domain_devices(resource); - if (res != -ENODEV) + if (res && res != -ENODEV) return res; if (resource->caps.flags & POWER_METER_CAN_MEASURE) { From aeb014abe82436e82b1acd054dd004da73268a6f Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 1 Apr 2025 17:08:53 +0800 Subject: [PATCH 064/107] hwmon: (acpi_power_meter) Fix the fake power alarm reporting [Upstream commit 0ea627381eb527a0ebd262c690c3992085b87ff4] We encountered a problem that a fake power alarm is reported to user on the platform unsupported notifications at the second step below: 1> Query 'power1_alarm' attribute when the power capping occurs. 2> Query 'power1_alarm' attribute when the power capping is over and the current average power is less then power cap value. The root cause is that the resource->power_alarm is set to true at the first step. And power meter use this old value to show the power alarm state instead of the current the comparison value. Signed-off-by: Huisong Li Link: https://lore.kernel.org/r/20250220030832.2976-1-lihuisong@huawei.com Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 5bac41ef41fca..f951bbc05a2fb 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -437,9 +437,13 @@ static ssize_t show_val(struct device *dev, ret = update_cap(resource); if (ret) return ret; + resource->power_alarm = resource->power > resource->cap; + val = resource->power_alarm; + } else { + val = resource->power_alarm || + resource->power > resource->cap; + resource->power_alarm = resource->power > resource->cap; } - val = resource->power_alarm || resource->power > resource->cap; - resource->power_alarm = resource->power > resource->cap; break; case 7: case 8: From b2b8261df75aaa21292085265d330f2a1b5e24f6 Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Tue, 15 Apr 2025 14:53:49 +0800 Subject: [PATCH 065/107] ACPI: IPMI: Add helper to wait for when SMI is selected [Upstream commit 670e98a34a9e44cd384bafbda681c8c8e072b714] On Dell servers, many APCI methods of acpi_power_meter module evaluate variables inside IPMI region, so the region handler needs to be installed. In addition to that, the handler needs to be fully functional, and that depends on SMI being selected. So add a helper to let acpi_power_meter know when the handler is installed and ready to be used. Signed-off-by: Kai-Heng Feng Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/20240320084317.366853-1-kai.heng.feng@canonical.com Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/acpi/acpi_ipmi.c | 23 ++++++++++++++++++++++- include/acpi/acpi_bus.h | 5 +++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_ipmi.c b/drivers/acpi/acpi_ipmi.c index 0555f68c2dfd6..5fba4dab5d088 100644 --- a/drivers/acpi/acpi_ipmi.c +++ b/drivers/acpi/acpi_ipmi.c @@ -22,6 +22,8 @@ MODULE_LICENSE("GPL"); /* the IPMI timeout is 5s */ #define IPMI_TIMEOUT (5000) #define ACPI_IPMI_MAX_MSG_LENGTH 64 +/* 2s should be suffient for SMI being selected */ +#define ACPI_IPMI_SMI_SELECTION_TIMEOUT (2 * HZ) struct acpi_ipmi_device { /* the device list attached to driver_data.ipmi_devices */ @@ -54,6 +56,7 @@ struct ipmi_driver_data { * to this selected global IPMI system interface. */ struct acpi_ipmi_device *selected_smi; + struct completion smi_selection_done; }; struct acpi_ipmi_msg { @@ -463,8 +466,10 @@ static void ipmi_register_bmc(int iface, struct device *dev) if (temp->handle == handle) goto err_lock; } - if (!driver_data.selected_smi) + if (!driver_data.selected_smi) { driver_data.selected_smi = ipmi_device; + complete(&driver_data.smi_selection_done); + } list_add_tail(&ipmi_device->head, &driver_data.ipmi_devices); mutex_unlock(&driver_data.ipmi_lock); @@ -578,6 +583,20 @@ acpi_ipmi_space_handler(u32 function, acpi_physical_address address, return status; } +int acpi_wait_for_acpi_ipmi(void) +{ + long ret; + + ret = wait_for_completion_interruptible_timeout(&driver_data.smi_selection_done, + ACPI_IPMI_SMI_SELECTION_TIMEOUT); + + if (ret <= 0) + return -ETIMEDOUT; + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_wait_for_acpi_ipmi); + static int __init acpi_ipmi_init(void) { int result; @@ -586,6 +605,8 @@ static int __init acpi_ipmi_init(void) if (acpi_disabled) return 0; + init_completion(&driver_data.smi_selection_done); + status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT, ACPI_ADR_SPACE_IPMI, &acpi_ipmi_space_handler, diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 330d14ad73e17..37721850e9348 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -824,11 +824,16 @@ static inline void acpi_put_acpi_dev(struct acpi_device *adev) { acpi_dev_put(adev); } + +int acpi_wait_for_acpi_ipmi(void); + #else /* CONFIG_ACPI */ static inline int register_acpi_bus_type(void *bus) { return 0; } static inline int unregister_acpi_bus_type(void *bus) { return 0; } +static inline int acpi_wait_for_acpi_ipmi(void) { return 0; } + #endif /* CONFIG_ACPI */ #endif /*__ACPI_BUS_H__*/ From 7fe3c879368e51206b4fcd3a6021630b25ddc830 Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Tue, 15 Apr 2025 14:53:50 +0800 Subject: [PATCH 066/107] hwmon: (acpi_power_meter) Ensure IPMI space handler is ready on Dell systems [Upstream commit 71113b9be4259d4bd396f22034653a9035ba07fb] The following error can be observed at boot: [ 3.717920] ACPI Error: No handler for Region [SYSI] (00000000ab9e62c5) [IPMI] (20230628/evregion-130) [ 3.717928] ACPI Error: Region IPMI (ID=7) has no handler (20230628/exfldio-261) [ 3.717936] No Local Variables are initialized for Method [_GHL] [ 3.717938] No Arguments are initialized for method [_GHL] [ 3.717940] ACPI Error: Aborting method \_SB.PMI0._GHL due to previous error (AE_NOT_EXIST) (20230628/psparse-529) [ 3.717949] ACPI Error: Aborting method \_SB.PMI0._PMC due to previous error (AE_NOT_EXIST) (20230628/psparse-529) [ 3.717957] ACPI: \_SB_.PMI0: _PMC evaluation failed: AE_NOT_EXIST On Dell systems several methods of acpi_power_meter access variables in IPMI region [0], so wait until IPMI space handler is installed by acpi_ipmi and also wait until SMI is selected to make the space handler fully functional. Since the dependency is inside BIOS's ASL code and it's not discoverable, so use this fixup is a hack to workaround BIOS issue. [0] https://www.dell.com/support/manuals/en-us/redhat-enterprise-linux-v8.0/rhel8_rn_pub/advanced-configuration-and-power-interface-acpi-error-messages-displayed-in-dmesg?guid=guid-0d5ae482-1977-42cf-b417-3ed5c3f5ee62 Signed-off-by: Kai-Heng Feng Link: https://lore.kernel.org/r/20240320084317.366853-2-kai.heng.feng@canonical.com [groeck: Simplified added code] Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index f951bbc05a2fb..687141f088bce 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -910,6 +910,22 @@ static int acpi_power_meter_add(struct acpi_device *device) strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); device->driver_data = resource; +#if IS_REACHABLE(CONFIG_ACPI_IPMI) + /* + * On Dell systems several methods of acpi_power_meter access + * variables in IPMI region, so wait until IPMI space handler is + * installed by acpi_ipmi and also wait until SMI is selected to make + * the space handler fully functional. + */ + if (dmi_match(DMI_SYS_VENDOR, "Dell Inc.")) { + struct acpi_device *ipi_device = acpi_dev_get_first_match_dev("IPI0001", NULL, -1); + + if (ipi_device && acpi_wait_for_acpi_ipmi()) + dev_warn(&device->dev, "Waiting for ACPI IPMI timeout"); + acpi_dev_put(ipi_device); + } +#endif + res = read_capabilities(resource); if (res) goto exit_free; From 5ca5da5bb5ace905b44208d834ec18d88fc4a98c Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 15 Apr 2025 14:53:51 +0800 Subject: [PATCH 067/107] hwmon: Fix the missing of 'average' word in hwmon_power_attr_templates [Upstream commit 232427772fc123942d110c18269cfbdf40edae18] The string "power%d_interval_max" and "power%d_interval_min" in the hwmon_power_attr_templates[] are corresponding to the sysfs interface name of hwmon_power_average_interval_max and hwmon_power_average_interval_min. But the 'average' word is missing in two strings. Fortunately, there is no driver to use it yet. Signed-off-by: Huisong Li Link: https://lore.kernel.org/r/20250304074640.2770353-1-lihuisong@huawei.com Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/hwmon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 3a01c76a0c71c..a8b81a667bb53 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -538,8 +538,8 @@ static const char * const hwmon_power_attr_templates[] = { [hwmon_power_enable] = "power%d_enable", [hwmon_power_average] = "power%d_average", [hwmon_power_average_interval] = "power%d_average_interval", - [hwmon_power_average_interval_max] = "power%d_interval_max", - [hwmon_power_average_interval_min] = "power%d_interval_min", + [hwmon_power_average_interval_max] = "power%d_average_interval_max", + [hwmon_power_average_interval_min] = "power%d_average_interval_min", [hwmon_power_average_highest] = "power%d_average_highest", [hwmon_power_average_lowest] = "power%d_average_lowest", [hwmon_power_average_max] = "power%d_average_max", From 1c1889a14d1adfc9786308d4d935dbf1956df28c Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 15 Apr 2025 14:53:52 +0800 Subject: [PATCH 068/107] hwmon: (acpi_power_meter) Replace the deprecated hwmon_device_register [Upstream commit 16746ce8adfe04f9ff8df75c1133286ba93c0e17] When load this mode, we can see the following log: "power_meter ACPI000D:00: hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info()." So replace hwmon_device_register with hwmon_device_register_with_info. These attributes, 'power_accuracy', 'power_cap_hyst', 'power_average_min' and 'power_average_max', should have been placed in hwmon_chip_info as power data type. But these attributes are displayed as string format on the following case: a) power1_accuracy --> display like '90.0%' b) power1_cap_hyst --> display 'unknown' when its value is 0xFFFFFFFF c) power1_average_min/max --> display 'unknown' when its value is negative. To avoid any changes in the display of these sysfs interfaces, we can't modifiy the type of these attributes in hwmon core and have to put them to extra_groups. Please note that the path of these sysfs interfaces are modified accordingly if use hwmon_device_register_with_info(): old: all sysfs interfaces are under acpi device, namely, /sys/class/hwmon/hwmonX/device/ now: all sysfs interfaces are under hwmon device, namely, /sys/class/hwmon/hwmonX/ The new ABI does not guarantee that the underlying path remains the same. But we have to accept this change so as to replace the deprecated API. Fortunately, some userspace application, like libsensors, would scan the two path and handles this automatically. So we can accept this change so as to drop the deprecated message. Signed-off-by: Huisong Li Link: https://lore.kernel.org/r/20250319020638.59925-1-lihuisong@huawei.com [groeck: Fixed some multi-line alignment issues; reverted to 32-bit arithmetic in power1_accuracy_show() fixed bad return code from power_meter_is_visible()] Signed-off-by: Guenter Roeck Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/hwmon/acpi_power_meter.c | 860 +++++++++++++++---------------- 1 file changed, 425 insertions(+), 435 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 687141f088bce..e0dafcea99e46 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -87,25 +87,14 @@ struct acpi_power_meter_resource { bool power_alarm; int sensors_valid; unsigned long sensors_last_updated; - struct sensor_device_attribute sensors[NUM_SENSORS]; - int num_sensors; +#define POWER_METER_TRIP_AVERAGE_MIN_IDX 0 +#define POWER_METER_TRIP_AVERAGE_MAX_IDX 1 s64 trip[2]; int num_domain_devices; struct acpi_device **domain_devices; struct kobject *holders_dir; }; -struct sensor_template { - char *label; - ssize_t (*show)(struct device *dev, - struct device_attribute *devattr, - char *buf); - ssize_t (*set)(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count); - int index; -}; - /* Averaging interval */ static int update_avg_interval(struct acpi_power_meter_resource *resource) { @@ -124,62 +113,6 @@ static int update_avg_interval(struct acpi_power_meter_resource *resource) return 0; } -static ssize_t show_avg_interval(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - - mutex_lock(&resource->lock); - update_avg_interval(resource); - mutex_unlock(&resource->lock); - - return sprintf(buf, "%llu\n", resource->avg_interval); -} - -static ssize_t set_avg_interval(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list args = { 1, &arg0 }; - int res; - unsigned long temp; - unsigned long long data; - acpi_status status; - - res = kstrtoul(buf, 10, &temp); - if (res) - return res; - - if (temp > resource->caps.max_avg_interval || - temp < resource->caps.min_avg_interval) - return -EINVAL; - arg0.integer.value = temp; - - mutex_lock(&resource->lock); - status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI", - &args, &data); - if (ACPI_SUCCESS(status)) - resource->avg_interval = temp; - mutex_unlock(&resource->lock); - - if (ACPI_FAILURE(status)) { - acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PAI", - status); - return -EINVAL; - } - - /* _PAI returns 0 on success, nonzero otherwise */ - if (data) - return -EINVAL; - - return count; -} - /* Cap functions */ static int update_cap(struct acpi_power_meter_resource *resource) { @@ -198,61 +131,6 @@ static int update_cap(struct acpi_power_meter_resource *resource) return 0; } -static ssize_t show_cap(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - - mutex_lock(&resource->lock); - update_cap(resource); - mutex_unlock(&resource->lock); - - return sprintf(buf, "%llu\n", resource->cap * 1000); -} - -static ssize_t set_cap(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - union acpi_object arg0 = { ACPI_TYPE_INTEGER }; - struct acpi_object_list args = { 1, &arg0 }; - int res; - unsigned long temp; - unsigned long long data; - acpi_status status; - - res = kstrtoul(buf, 10, &temp); - if (res) - return res; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - if (temp > resource->caps.max_cap || temp < resource->caps.min_cap) - return -EINVAL; - arg0.integer.value = temp; - - mutex_lock(&resource->lock); - status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL", - &args, &data); - if (ACPI_SUCCESS(status)) - resource->cap = temp; - mutex_unlock(&resource->lock); - - if (ACPI_FAILURE(status)) { - acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_SHL", - status); - return -EINVAL; - } - - /* _SHL returns 0 on success, nonzero otherwise */ - if (data) - return -EINVAL; - - return count; -} - /* Power meter trip points */ static int set_acpi_trip(struct acpi_power_meter_resource *resource) { @@ -287,34 +165,6 @@ static int set_acpi_trip(struct acpi_power_meter_resource *resource) return 0; } -static ssize_t set_trip(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - unsigned long temp, trip_bk; - int res; - - res = kstrtoul(buf, 10, &temp); - if (res) - return res; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - - guard(mutex)(&resource->lock); - - trip_bk = resource->trip[attr->index - 7]; - resource->trip[attr->index - 7] = temp; - res = set_acpi_trip(resource); - if (res) { - resource->trip[attr->index - 7] = trip_bk; - return res; - } - - return count; -} - /* Power meter */ static int update_meter(struct acpi_power_meter_resource *resource) { @@ -341,206 +191,6 @@ static int update_meter(struct acpi_power_meter_resource *resource) return 0; } -static ssize_t show_power(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - - mutex_lock(&resource->lock); - update_meter(resource); - mutex_unlock(&resource->lock); - - if (resource->power == UNKNOWN_POWER) - return -ENODATA; - - return sprintf(buf, "%llu\n", resource->power * 1000); -} - -/* Miscellaneous */ -static ssize_t show_str(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - acpi_string val; - int ret; - - mutex_lock(&resource->lock); - switch (attr->index) { - case 0: - val = resource->model_number; - break; - case 1: - val = resource->serial_number; - break; - case 2: - val = resource->oem_info; - break; - default: - WARN(1, "Implementation error: unexpected attribute index %d\n", - attr->index); - val = ""; - break; - } - ret = sprintf(buf, "%s\n", val); - mutex_unlock(&resource->lock); - return ret; -} - -static ssize_t show_val(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - u64 val = 0; - int ret; - - guard(mutex)(&resource->lock); - - switch (attr->index) { - case 0: - val = resource->caps.min_avg_interval; - break; - case 1: - val = resource->caps.max_avg_interval; - break; - case 2: - val = resource->caps.min_cap * 1000; - break; - case 3: - val = resource->caps.max_cap * 1000; - break; - case 4: - if (resource->caps.hysteresis == UNKNOWN_HYSTERESIS) - return sprintf(buf, "unknown\n"); - - val = resource->caps.hysteresis * 1000; - break; - case 5: - if (resource->caps.flags & POWER_METER_IS_BATTERY) - val = 1; - else - val = 0; - break; - case 6: - ret = update_meter(resource); - if (ret) - return ret; - /* need to update cap if not to support the notification. */ - if (!(resource->caps.flags & POWER_METER_CAN_NOTIFY)) { - ret = update_cap(resource); - if (ret) - return ret; - resource->power_alarm = resource->power > resource->cap; - val = resource->power_alarm; - } else { - val = resource->power_alarm || - resource->power > resource->cap; - resource->power_alarm = resource->power > resource->cap; - } - break; - case 7: - case 8: - if (resource->trip[attr->index - 7] < 0) - return sprintf(buf, "unknown\n"); - - val = resource->trip[attr->index - 7] * 1000; - break; - default: - WARN(1, "Implementation error: unexpected attribute index %d\n", - attr->index); - break; - } - - return sprintf(buf, "%llu\n", val); -} - -static ssize_t show_accuracy(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_power_meter_resource *resource = acpi_dev->driver_data; - unsigned int acc = resource->caps.accuracy; - - return sprintf(buf, "%u.%u%%\n", acc / 1000, acc % 1000); -} - -static ssize_t show_name(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME); -} - -#define RO_SENSOR_TEMPLATE(_label, _show, _index) \ - { \ - .label = _label, \ - .show = _show, \ - .index = _index, \ - } - -#define RW_SENSOR_TEMPLATE(_label, _show, _set, _index) \ - { \ - .label = _label, \ - .show = _show, \ - .set = _set, \ - .index = _index, \ - } - -/* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */ -static struct sensor_template meter_attrs[] = { - RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0), - RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0), - RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0), - RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), - RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), - RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, - set_avg_interval, 0), - {}, -}; - -static struct sensor_template misc_cap_attrs[] = { - RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2), - RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3), - RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4), - RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6), - {}, -}; - -static struct sensor_template ro_cap_attrs[] = { - RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0), - {}, -}; - -static struct sensor_template rw_cap_attrs[] = { - RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0), - {}, -}; - -static struct sensor_template trip_attrs[] = { - RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7), - RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8), - {}, -}; - -static struct sensor_template misc_attrs[] = { - RO_SENSOR_TEMPLATE("name", show_name, 0), - RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0), - RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2), - RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1), - {}, -}; - -#undef RO_SENSOR_TEMPLATE -#undef RW_SENSOR_TEMPLATE - /* Read power domain data */ static void remove_domain_devices(struct acpi_power_meter_resource *resource) { @@ -642,109 +292,434 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) return res; } -/* Registration and deregistration */ -static int register_attrs(struct acpi_power_meter_resource *resource, - struct sensor_template *attrs) +static int set_trip(struct acpi_power_meter_resource *resource, u16 trip_idx, + unsigned long trip) { - struct device *dev = &resource->acpi_dev->dev; - struct sensor_device_attribute *sensors = - &resource->sensors[resource->num_sensors]; - int res = 0; + unsigned long trip_bk; + int ret; - while (attrs->label) { - sensors->dev_attr.attr.name = attrs->label; - sensors->dev_attr.attr.mode = 0444; - sensors->dev_attr.show = attrs->show; - sensors->index = attrs->index; + trip = DIV_ROUND_CLOSEST(trip, 1000); + trip_bk = resource->trip[trip_idx]; - if (attrs->set) { - sensors->dev_attr.attr.mode |= 0200; - sensors->dev_attr.store = attrs->set; - } + resource->trip[trip_idx] = trip; + ret = set_acpi_trip(resource); + if (ret) { + dev_err(&resource->acpi_dev->dev, "set %s failed.\n", + (trip_idx == POWER_METER_TRIP_AVERAGE_MIN_IDX) ? + "power1_average_min" : "power1_average_max"); + resource->trip[trip_idx] = trip_bk; + } - sysfs_attr_init(&sensors->dev_attr.attr); - res = device_create_file(dev, &sensors->dev_attr); - if (res) { - sensors->dev_attr.attr.name = NULL; - goto error; - } - sensors++; - resource->num_sensors++; - attrs++; + return ret; +} + +static int set_cap(struct acpi_power_meter_resource *resource, + unsigned long cap) +{ + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + unsigned long long data; + acpi_status status; + + cap = DIV_ROUND_CLOSEST(cap, 1000); + if (cap > resource->caps.max_cap || cap < resource->caps.min_cap) + return -EINVAL; + + arg0.integer.value = cap; + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_SHL", + &args, &data); + if (ACPI_FAILURE(status)) { + acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_SHL", + status); + return -EINVAL; } + resource->cap = cap; -error: - return res; + /* _SHL returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; + + return 0; } -static void remove_attrs(struct acpi_power_meter_resource *resource) +static int set_avg_interval(struct acpi_power_meter_resource *resource, + unsigned long val) { - int i; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + unsigned long long data; + acpi_status status; - for (i = 0; i < resource->num_sensors; i++) { - if (!resource->sensors[i].dev_attr.attr.name) - continue; - device_remove_file(&resource->acpi_dev->dev, - &resource->sensors[i].dev_attr); + if (val > resource->caps.max_avg_interval || + val < resource->caps.min_avg_interval) + return -EINVAL; + + arg0.integer.value = val; + status = acpi_evaluate_integer(resource->acpi_dev->handle, "_PAI", + &args, &data); + if (ACPI_FAILURE(status)) { + acpi_evaluation_failure_warn(resource->acpi_dev->handle, "_PAI", + status); + return -EINVAL; } + resource->avg_interval = val; - remove_domain_devices(resource); + /* _PAI returns 0 on success, nonzero otherwise */ + if (data) + return -EINVAL; - resource->num_sensors = 0; + return 0; } -static int setup_attrs(struct acpi_power_meter_resource *resource) +static int get_power_alarm_state(struct acpi_power_meter_resource *resource, + long *val) { - int res = 0; + int ret; - /* _PMD method is optional. */ - res = read_domain_devices(resource); - if (res && res != -ENODEV) - return res; + ret = update_meter(resource); + if (ret) + return ret; - if (resource->caps.flags & POWER_METER_CAN_MEASURE) { - res = register_attrs(resource, meter_attrs); - if (res) - goto error; + /* need to update cap if not to support the notification. */ + if (!(resource->caps.flags & POWER_METER_CAN_NOTIFY)) { + ret = update_cap(resource); + if (ret) + return ret; + resource->power_alarm = resource->power > resource->cap; + *val = resource->power_alarm; + } else { + *val = resource->power_alarm || resource->power > resource->cap; + resource->power_alarm = resource->power > resource->cap; } - if (resource->caps.flags & POWER_METER_CAN_CAP) { - if (!can_cap_in_hardware()) { - dev_warn(&resource->acpi_dev->dev, - "Ignoring unsafe software power cap!\n"); - goto skip_unsafe_cap; + return 0; +} + +static umode_t power_meter_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct acpi_power_meter_resource *res = data; + + if (type != hwmon_power) + return 0; + + switch (attr) { + case hwmon_power_average: + case hwmon_power_average_interval_min: + case hwmon_power_average_interval_max: + if (res->caps.flags & POWER_METER_CAN_MEASURE) + return 0444; + break; + case hwmon_power_average_interval: + if (res->caps.flags & POWER_METER_CAN_MEASURE) + return 0644; + break; + case hwmon_power_cap_min: + case hwmon_power_cap_max: + case hwmon_power_alarm: + if (res->caps.flags & POWER_METER_CAN_CAP && can_cap_in_hardware()) + return 0444; + break; + case hwmon_power_cap: + if (res->caps.flags & POWER_METER_CAN_CAP && can_cap_in_hardware()) { + if (res->caps.configurable_cap) + return 0644; + else + return 0444; } + break; + default: + break; + } + + return 0; +} - if (resource->caps.configurable_cap) - res = register_attrs(resource, rw_cap_attrs); - else - res = register_attrs(resource, ro_cap_attrs); +static int power_meter_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + int ret = 0; - if (res) - goto error; + if (type != hwmon_power) + return -EINVAL; - res = register_attrs(resource, misc_cap_attrs); - if (res) - goto error; + guard(mutex)(&res->lock); + + switch (attr) { + case hwmon_power_average: + ret = update_meter(res); + if (ret) + return ret; + if (res->power == UNKNOWN_POWER) + return -ENODATA; + *val = res->power * 1000; + break; + case hwmon_power_average_interval_min: + *val = res->caps.min_avg_interval; + break; + case hwmon_power_average_interval_max: + *val = res->caps.max_avg_interval; + break; + case hwmon_power_average_interval: + ret = update_avg_interval(res); + if (ret) + return ret; + *val = (res)->avg_interval; + break; + case hwmon_power_cap_min: + *val = res->caps.min_cap * 1000; + break; + case hwmon_power_cap_max: + *val = res->caps.max_cap * 1000; + break; + case hwmon_power_alarm: + ret = get_power_alarm_state(res, val); + if (ret) + return ret; + break; + case hwmon_power_cap: + ret = update_cap(res); + if (ret) + return ret; + *val = res->cap * 1000; + break; + default: + break; } -skip_unsafe_cap: - if (resource->caps.flags & POWER_METER_CAN_TRIP) { - res = register_attrs(resource, trip_attrs); - if (res) - goto error; + return 0; +} + +static int power_meter_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + int ret; + + if (type != hwmon_power) + return -EINVAL; + + guard(mutex)(&res->lock); + switch (attr) { + case hwmon_power_cap: + ret = set_cap(res, val); + break; + case hwmon_power_average_interval: + ret = set_avg_interval(res, val); + break; + default: + ret = -EOPNOTSUPP; } - res = register_attrs(resource, misc_attrs); - if (res) - goto error; + return ret; +} - return res; -error: - remove_attrs(resource); - return res; +static const struct hwmon_channel_info * const power_meter_info[] = { + HWMON_CHANNEL_INFO(power, HWMON_P_AVERAGE | + HWMON_P_AVERAGE_INTERVAL | HWMON_P_AVERAGE_INTERVAL_MIN | + HWMON_P_AVERAGE_INTERVAL_MAX | HWMON_P_CAP | HWMON_P_CAP_MIN | + HWMON_P_CAP_MAX | HWMON_P_ALARM), + NULL +}; + +static const struct hwmon_ops power_meter_ops = { + .is_visible = power_meter_is_visible, + .read = power_meter_read, + .write = power_meter_write, +}; + +static const struct hwmon_chip_info power_meter_chip_info = { + .ops = &power_meter_ops, + .info = power_meter_info, +}; + +static ssize_t power1_average_max_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + unsigned long trip; + int ret; + + ret = kstrtoul(buf, 10, &trip); + if (ret) + return ret; + + mutex_lock(&res->lock); + ret = set_trip(res, POWER_METER_TRIP_AVERAGE_MAX_IDX, trip); + mutex_unlock(&res->lock); + + return ret == 0 ? count : ret; +} + +static ssize_t power1_average_min_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + unsigned long trip; + int ret; + + ret = kstrtoul(buf, 10, &trip); + if (ret) + return ret; + + mutex_lock(&res->lock); + ret = set_trip(res, POWER_METER_TRIP_AVERAGE_MIN_IDX, trip); + mutex_unlock(&res->lock); + + return ret == 0 ? count : ret; +} + +static ssize_t power1_average_min_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + if (res->trip[POWER_METER_TRIP_AVERAGE_MIN_IDX] < 0) + return sysfs_emit(buf, "unknown\n"); + + return sysfs_emit(buf, "%lld\n", + res->trip[POWER_METER_TRIP_AVERAGE_MIN_IDX] * 1000); +} + +static ssize_t power1_average_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + if (res->trip[POWER_METER_TRIP_AVERAGE_MAX_IDX] < 0) + return sysfs_emit(buf, "unknown\n"); + + return sysfs_emit(buf, "%lld\n", + res->trip[POWER_METER_TRIP_AVERAGE_MAX_IDX] * 1000); +} + +static ssize_t power1_cap_hyst_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + if (res->caps.hysteresis == UNKNOWN_HYSTERESIS) + return sysfs_emit(buf, "unknown\n"); + + return sysfs_emit(buf, "%llu\n", res->caps.hysteresis * 1000); +} + +static ssize_t power1_accuracy_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + unsigned int acc = res->caps.accuracy; + + return sysfs_emit(buf, "%u.%u%%\n", acc / 1000, acc % 1000); +} + +static ssize_t power1_is_battery_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", + res->caps.flags & POWER_METER_IS_BATTERY ? 1 : 0); +} + +static ssize_t power1_model_number_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", res->model_number); +} + +static ssize_t power1_oem_info_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", res->oem_info); +} + +static ssize_t power1_serial_number_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", res->serial_number); } +/* depend on POWER_METER_CAN_TRIP */ +static DEVICE_ATTR_RW(power1_average_max); +static DEVICE_ATTR_RW(power1_average_min); + +/* depend on POWER_METER_CAN_CAP */ +static DEVICE_ATTR_RO(power1_cap_hyst); + +/* depend on POWER_METER_CAN_MEASURE */ +static DEVICE_ATTR_RO(power1_accuracy); +static DEVICE_ATTR_RO(power1_is_battery); + +static DEVICE_ATTR_RO(power1_model_number); +static DEVICE_ATTR_RO(power1_oem_info); +static DEVICE_ATTR_RO(power1_serial_number); + +static umode_t power_extra_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct acpi_power_meter_resource *res = dev_get_drvdata(dev); + + if (attr == &dev_attr_power1_is_battery.attr || + attr == &dev_attr_power1_accuracy.attr) { + if ((res->caps.flags & POWER_METER_CAN_MEASURE) == 0) + return 0; + } + + if (attr == &dev_attr_power1_cap_hyst.attr) { + if ((res->caps.flags & POWER_METER_CAN_CAP) == 0) { + return 0; + } else if (!can_cap_in_hardware()) { + dev_warn(&res->acpi_dev->dev, + "Ignoring unsafe software power cap!\n"); + return 0; + } + } + + if (attr == &dev_attr_power1_average_max.attr || + attr == &dev_attr_power1_average_min.attr) { + if ((res->caps.flags & POWER_METER_CAN_TRIP) == 0) + return 0; + } + + return attr->mode; +} + +static struct attribute *power_extra_attrs[] = { + &dev_attr_power1_average_max.attr, + &dev_attr_power1_average_min.attr, + &dev_attr_power1_cap_hyst.attr, + &dev_attr_power1_accuracy.attr, + &dev_attr_power1_is_battery.attr, + &dev_attr_power1_model_number.attr, + &dev_attr_power1_oem_info.attr, + &dev_attr_power1_serial_number.attr, + NULL +}; + +static const struct attribute_group power_extra_group = { + .attrs = power_extra_attrs, + .is_visible = power_extra_is_visible, +}; + +__ATTRIBUTE_GROUPS(power_extra); + static void free_capabilities(struct acpi_power_meter_resource *resource) { acpi_string *str; @@ -853,13 +828,23 @@ static void acpi_power_meter_notify(struct acpi_device *device, u32 event) case METER_NOTIFY_CONFIG: mutex_lock(&resource->lock); free_capabilities(resource); + remove_domain_devices(resource); + hwmon_device_unregister(resource->hwmon_dev); res = read_capabilities(resource); - mutex_unlock(&resource->lock); if (res) - break; - - remove_attrs(resource); - setup_attrs(resource); + dev_err_once(&device->dev, "read capabilities failed.\n"); + res = read_domain_devices(resource); + if (res && res != -ENODEV) + dev_err_once(&device->dev, "read domain devices failed.\n"); + resource->hwmon_dev = + hwmon_device_register_with_info(&device->dev, + ACPI_POWER_METER_NAME, + resource, + &power_meter_chip_info, + power_extra_groups); + if (IS_ERR(resource->hwmon_dev)) + dev_err_once(&device->dev, "register hwmon device failed.\n"); + mutex_unlock(&resource->lock); break; case METER_NOTIFY_TRIP: sysfs_notify(&device->dev.kobj, NULL, POWER_AVERAGE_NAME); @@ -921,7 +906,7 @@ static int acpi_power_meter_add(struct acpi_device *device) struct acpi_device *ipi_device = acpi_dev_get_first_match_dev("IPI0001", NULL, -1); if (ipi_device && acpi_wait_for_acpi_ipmi()) - dev_warn(&device->dev, "Waiting for ACPI IPMI timeout"); + dev_warn(&device->dev, "Waiting for ACPI IPMI timeout"); acpi_dev_put(ipi_device); } #endif @@ -933,11 +918,16 @@ static int acpi_power_meter_add(struct acpi_device *device) resource->trip[0] = -1; resource->trip[1] = -1; - res = setup_attrs(resource); - if (res) + /* _PMD method is optional. */ + res = read_domain_devices(resource); + if (res && res != -ENODEV) goto exit_free_capability; - resource->hwmon_dev = hwmon_device_register(&device->dev); + resource->hwmon_dev = + hwmon_device_register_with_info(&device->dev, + ACPI_POWER_METER_NAME, resource, + &power_meter_chip_info, + power_extra_groups); if (IS_ERR(resource->hwmon_dev)) { res = PTR_ERR(resource->hwmon_dev); goto exit_remove; @@ -947,7 +937,7 @@ static int acpi_power_meter_add(struct acpi_device *device) goto exit; exit_remove: - remove_attrs(resource); + remove_domain_devices(resource); exit_free_capability: free_capabilities(resource); exit_free: @@ -966,7 +956,7 @@ static void acpi_power_meter_remove(struct acpi_device *device) resource = acpi_driver_data(device); hwmon_device_unregister(resource->hwmon_dev); - remove_attrs(resource); + remove_domain_devices(resource); free_capabilities(resource); kfree(resource); From 8e2c9ab808047ea03bb1b1f48a05653bda77589b Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Thu, 10 Apr 2025 16:25:50 +0800 Subject: [PATCH 069/107] perf stat: Enable iostat mode for HiSilicon PCIe PMU commit d0171cb565db3146ec37035ef16626a3d97be1f7 openEuler Some HiSilicon platforms provide PCIe PMU devices for monitoring the bandwidth and latency of PCIe traffic. With the support of PCIe PMU we can enable the perf iostat mode. The HiSilicon PCIe PMU can support measuring the bandwidth of certain TLP types and of certian root port. Totally 6 metrics are provided in the unit of MB: - Inbound MWR: The memory write TLPs from the devices downstream the root port - Inbound MRD: The memory read TLPs from the devices downstream the root port - Inbound CPL: The completion TLPs from the devices downstream the root port - Outbound MWR: The memory write TLPs from the CPU to the downstream devices - Outbound MRD: The memory read TLPs from the CPU to the downstream devices - Outbound CPL: The completions TLPs from the CPU to the downstream devices Since the PMU measure the throughput with unit of DWords. So we need the calculate the actually bandwidth like: Count * 4B / 1024 / 1024 / Time_elapsed (MB) Some of the display of the `perf iostat` will be like: [root@localhost tmp]# ./perf iostat list hisi_pcie8_core2<0000:aa:00.0> hisi_pcie0_core2<0000:40:00.0> hisi_pcie10_core2<0000:d5:00.0> hisi_pcie8_core1<0000:95:04.0> hisi_pcie8_core1<0000:95:00.0> hisi_pcie2_core2<0000:5f:00.0> hisi_pcie0_core1<0000:16:00.0> hisi_pcie0_core1<0000:16:04.0> [root@localhost tmp]# ./perf iostat --timeout 10000 Performance counter stats for 'system wide': port Inbound MWR(MB) Inbound MRD(MB) Inbound CPL(MB) Outbound MWR(MB) Outbound MRD(MB) Outbound CPL(MB) 0000:aa:00.0 0 0 0 0 0 0 0000:40:00.0 0 0 0 0 0 0 0000:d5:00.0 0 0 0 0 0 0 0000:95:04.0 9 81 0 1 0 2497 0000:95:00.0 0 0 0 0 0 0 0000:5f:00.0 0 0 0 0 0 0 0000:16:00.0 0 0 0 0 0 0 0000:16:04.0 5658 2 0 2 0 17 10.006795510 seconds time elapsed [root@localhost tmp]# ./perf iostat 0000:95:04.0 -- iperf -c 192.100.100.1 -t 30 ------------------------------------------------------------ Client connecting to 192.100.100.1, TCP port 5001 TCP window size: 1.79 MByte (default) ------------------------------------------------------------ [ 3] local 192.100.100.2 port 57130 connected with 192.100.100.1 port 5001 [ ID] Interval Transfer Bandwidth [ 3] 0.0-30.0 sec 32.9 GBytes 9.41 Gbits/sec Performance counter stats for 'system wide': port Inbound MWR(MB) Inbound MRD(MB) Inbound CPL(MB) Outbound MWR(MB) Outbound MRD(MB) Outbound CPL(MB) 0000:95:04.0 4 41 0 1 0 1259 30.060558310 seconds time elapsed 0.060853000 seconds user 3.758800000 seconds sys More information of the HiSilicon PCIe PMU can be found at Documentation/admin-guide/perf/hisi-pcie-pmu.rst. Signed-off-by: Yicong Yang Signed-off-by: Yushan Wang Signed-off-by: Qizhi Zhang Signed-off-by: Slim6882 Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- tools/perf/arch/arm64/util/Build | 1 + tools/perf/arch/arm64/util/hisi-iostat.c | 431 +++++++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 tools/perf/arch/arm64/util/hisi-iostat.c diff --git a/tools/perf/arch/arm64/util/Build b/tools/perf/arch/arm64/util/Build index 78ef7115be3d9..4e8dabf98b29c 100644 --- a/tools/perf/arch/arm64/util/Build +++ b/tools/perf/arch/arm64/util/Build @@ -3,6 +3,7 @@ perf-y += machine.o perf-y += perf_regs.o perf-y += tsc.o perf-y += pmu.o +perf-y += hisi-iostat.o perf-$(CONFIG_LIBTRACEEVENT) += kvm-stat.o perf-$(CONFIG_DWARF) += dwarf-regs.o perf-$(CONFIG_LOCAL_LIBUNWIND) += unwind-libunwind.o diff --git a/tools/perf/arch/arm64/util/hisi-iostat.c b/tools/perf/arch/arm64/util/hisi-iostat.c new file mode 100644 index 0000000000000..96684cc004648 --- /dev/null +++ b/tools/perf/arch/arm64/util/hisi-iostat.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * perf iostat support for HiSilicon PCIe PMU. + * Partly derived from tools/perf/arch/x86/util/iostat.c. + * + * Copyright (c) 2024 HiSilicon Technologies Co., Ltd. + * Author: Yicong Yang + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util/counts.h" +#include "util/cpumap.h" +#include "util/debug.h" +#include "util/iostat.h" +#include "util/pmu.h" + +#define PCI_DEFAULT_DOMAIN 0 +#define PCI_DEVICE_NAME_PATTERN "%04x:%02hhx:%02hhx.%hhu" +#define PCI_ROOT_BUS_DEVICES_PATH "bus/pci/devices" + +static const char *hisi_iostat_metrics[] = { + "Inbound MWR(MB)", + "Inbound MRD(MB)", + "Inbound CPL(MB)", + "Outbound MWR(MB)", + "Outbound MRD(MB)", + "Outbound CPL(MB)", +}; + +static const char *hisi_iostat_cmd_template[] = { + /* Inbound Memory Write */ + "hisi_pcie%hu_core%hu/event=0x0104,port=0x%hx/", + /* Inbound Memory Read */ + "hisi_pcie%hu_core%hu/event=0x0804,port=0x%hx/", + /* Inbound Memory Completion */ + "hisi_pcie%hu_core%hu/event=0x2004,port=0x%hx/", + /* Outbound Memory Write */ + "hisi_pcie%hu_core%hu/event=0x0105,port=0x%hx/", + /* Outbound Memory Read */ + "hisi_pcie%hu_core%hu/event=0x0405,port=0x%hx/", + /* Outbound Memory Completion */ + "hisi_pcie%hu_core%hu/event=0x1005,port=0x%hx/", +}; + +struct hisi_pcie_root_port { + struct list_head list; + /* Is this Root Port selected for monitoring */ + bool selected; + /* IDs to locate the PMU */ + u16 sicl_id; + u16 core_id; + /* Filter mask for this Root Port */ + u16 mask; + /* PCIe Root Port's ::. */ + u32 domain; + u8 bus; + u8 dev; + u8 fn; +}; + +LIST_HEAD(hisi_pcie_root_ports_list); +static int hisi_pcie_root_ports_num; + +static void hisi_pcie_init_root_port_mask(struct hisi_pcie_root_port *rp) +{ + rp->mask = BIT(rp->dev << 1); +} + +/* + * Select specific Root Port to monitor. Return 0 if successfully find the + * Root Port, Otherwise -EINVAL. + */ +static int hisi_pcie_root_ports_select_one(u32 domain, u8 bus, u8 dev, u8 fn) +{ + struct hisi_pcie_root_port *rp; + + list_for_each_entry(rp, &hisi_pcie_root_ports_list, list) + if (domain == rp->domain && bus == rp->bus && + dev == rp->dev && fn == rp->fn) { + rp->selected = true; + return 0; + } + + return -EINVAL; +} + +static void hisi_pcie_root_ports_select_all(void) +{ + struct hisi_pcie_root_port *rp; + + list_for_each_entry(rp, &hisi_pcie_root_ports_list, list) + rp->selected = true; +} + +static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus) +{ + const char *sysfs = sysfs__mountpoint(); + struct hisi_pcie_root_port *rp; + struct dirent *dent; + char path[PATH_MAX]; + u8 bus, dev, fn; + u32 domain; + DIR *dir; + int ret; + + snprintf(path, PATH_MAX, "%s/%s", sysfs, PCI_ROOT_BUS_DEVICES_PATH); + dir = opendir(path); + if (!dir) + return; + + /* Scan the PCI root bus to find the match root port on @target_bus */ + while ((dent = readdir(dir))) { + ret = sscanf(dent->d_name, PCI_DEVICE_NAME_PATTERN, + &domain, &bus, &dev, &fn); + if (ret != 4 || bus != target_bus) + continue; + + rp = zalloc(sizeof(*rp)); + if (!rp) + continue; + + rp->selected = false; + rp->sicl_id = sicl_id; + rp->core_id = core_id; + rp->domain = domain; + rp->bus = bus; + rp->dev = dev; + rp->fn = fn; + + hisi_pcie_init_root_port_mask(rp); + + list_add(&rp->list, &hisi_pcie_root_ports_list); + hisi_pcie_root_ports_num++; + + pr_debug3("Found root port %s\n", dent->d_name); + } + + closedir(dir); +} + +/* Scan the PMUs and build the mapping of the Root Ports to the PMU */ +static int hisi_pcie_root_ports_init(void) +{ + char event_source[PATH_MAX], bus_path[PATH_MAX]; + unsigned long long bus; + u16 sicl_id, core_id; + struct dirent *dent; + DIR *dir; + + perf_pmu__event_source_devices_scnprintf(event_source, sizeof(event_source)); + dir = opendir(event_source); + if (!dir) + return -ENOENT; + + while ((dent = readdir(dir))) { + /* + * This HiSilicon PCIe PMU will be named as: + * hisi_pcie_core + */ + if ((sscanf(dent->d_name, "hisi_pcie%hu_core%hu", &sicl_id, &core_id)) != 2) + continue; + + /* + * Driver will export the root port it can monitor through + * the "bus" sysfs attribute. + */ + scnprintf(bus_path, sizeof(bus_path), "%s/hisi_pcie%hu_core%hu/bus", + event_source, sicl_id, core_id); + + /* + * Per PCIe spec the bus should be 8bit, use unsigned long long + * for the convience of the library function. + */ + if (filename__read_ull(bus_path, &bus)) + continue; + + pr_debug3("Found pmu %s bus 0x%llx\n", dent->d_name, bus); + + hisi_pcie_root_ports_add(sicl_id, core_id, (u8)bus); + } + + closedir(dir); + return hisi_pcie_root_ports_num > 0 ? 0 : -ENOENT; +} + +static void hisi_pcie_root_ports_free(void) +{ + struct hisi_pcie_root_port *rp, *tmp; + + if (hisi_pcie_root_ports_num == 0) + return; + + list_for_each_entry_safe(rp, tmp, &hisi_pcie_root_ports_list, list) { + list_del(&rp->list); + zfree(&rp); + hisi_pcie_root_ports_num--; + } +} + +static int hisi_iostat_add_events(struct evlist *evl) +{ + struct hisi_pcie_root_port *rp; + struct evsel *evsel; + unsigned int i, j; + char *iostat_cmd; + int pos = 0; + int ret; + + if (!hisi_pcie_root_ports_num) + return -ENOENT; + + iostat_cmd = zalloc(PATH_MAX); + if (!iostat_cmd) + return -ENOMEM; + + list_for_each_entry(rp, &hisi_pcie_root_ports_list, list) { + if (!rp->selected) + continue; + + iostat_cmd[pos++] = '{'; + for (j = 0; j < ARRAY_SIZE(hisi_iostat_cmd_template); j++) { + pos += snprintf(iostat_cmd + pos, ARG_MAX - pos - 1, + hisi_iostat_cmd_template[j], + rp->sicl_id, rp->core_id, rp->mask); + + if (j == ARRAY_SIZE(hisi_iostat_cmd_template) - 1) + iostat_cmd[pos++] = '}'; + else + iostat_cmd[pos++] = ','; + } + + ret = parse_event(evl, iostat_cmd); + if (ret) + break; + + i = 0; + evlist__for_each_entry_reverse(evl, evsel) { + if (i == ARRAY_SIZE(hisi_iostat_cmd_template)) + break; + + evsel->priv = rp; + i++; + } + + memset(iostat_cmd, 0, PATH_MAX); + pos = 0; + } + + zfree(&iostat_cmd); + return ret; +} + +int iostat_prepare(struct evlist *evlist, + struct perf_stat_config *config) +{ + if (evlist->core.nr_entries > 0) { + pr_warning("The -e and -M options are not supported." + "All chosen events/metrics will be dropped\n"); + evlist__delete(evlist); + evlist = evlist__new(); + if (!evlist) + return -ENOMEM; + } + + config->metric_only = true; + config->aggr_mode = AGGR_GLOBAL; + + return hisi_iostat_add_events(evlist); +} + +static int hisi_pcie_root_ports_list_filter(const char *str) +{ + char *tok, *tmp, *copy = NULL; + u8 bus, dev, fn; + u32 domain; + int ret; + + copy = strdup(str); + if (!copy) + return -ENOMEM; + + for (tok = strtok_r(copy, ",", &tmp); tok; tok = strtok_r(NULL, ",", &tmp)) { + ret = sscanf(tok, PCI_DEVICE_NAME_PATTERN, &domain, &bus, &dev, &fn); + if (ret != 4) { + ret = -EINVAL; + break; + } + + ret = hisi_pcie_root_ports_select_one(domain, bus, dev, fn); + if (ret) + break; + } + + zfree(©); + return ret; +} + +int iostat_parse(const struct option *opt, const char *str, int unset __maybe_unused) +{ + struct perf_stat_config *config = (struct perf_stat_config *)opt->data; + int ret; + + ret = hisi_pcie_root_ports_init(); + if (!ret) { + config->iostat_run = true; + + if (!str) { + iostat_mode = IOSTAT_RUN; + hisi_pcie_root_ports_select_all(); + } else if (!strcmp(str, "list")) { + iostat_mode = IOSTAT_LIST; + hisi_pcie_root_ports_select_all(); + } else { + iostat_mode = IOSTAT_RUN; + ret = hisi_pcie_root_ports_list_filter(str); + } + } + + return ret; +} + +static void hisi_pcie_root_port_show(FILE *output, + const struct hisi_pcie_root_port * const rp) +{ + if (output && rp) + fprintf(output, "hisi_pcie%hu_core%hu<" PCI_DEVICE_NAME_PATTERN ">\n", + rp->sicl_id, rp->core_id, rp->domain, rp->bus, rp->dev, rp->fn); +} + +void iostat_list(struct evlist *evlist __maybe_unused, struct perf_stat_config *config) +{ + struct hisi_pcie_root_port *rp = NULL; + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + if (rp != evsel->priv) { + hisi_pcie_root_port_show(config->output, evsel->priv); + rp = evsel->priv; + } + } +} + +void iostat_release(struct evlist *evlist) +{ + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) + evsel->priv = NULL; + + hisi_pcie_root_ports_free(); +} + +void iostat_print_header_prefix(struct perf_stat_config *config) +{ + if (config->csv_output) + fputs("port,", config->output); + else if (config->interval) + fprintf(config->output, "# time port "); + else + fprintf(config->output, " port "); +} + +void iostat_print_metric(struct perf_stat_config *config, struct evsel *evsel, + struct perf_stat_output_ctx *out) +{ + const char *iostat_metric = hisi_iostat_metrics[evsel->core.idx % ARRAY_SIZE(hisi_iostat_metrics)]; + struct perf_counts_values *count; + double iostat_value; + + /* We're using AGGR_GLOBAL so there's only one aggr counts aggr[0]. */ + count = &evsel->stats->aggr[0].counts; + + /* The counts has been scaled, we can use it directly. */ + iostat_value = (double)count->val; + + /* + * Display two digits after decimal point for better accuracy if the + * value is non-zero. + */ + out->print_metric(config, out->ctx, NULL, + iostat_value > 0 ? "%8.2f" : "%8.0f", + iostat_metric, iostat_value / (256 * 1024)); +} + +void iostat_prefix(struct evlist *evlist, struct perf_stat_config *config, + char *prefix, struct timespec *ts) +{ + struct hisi_pcie_root_port *rp = evlist->selected->priv; + + if (rp) { + if (ts) + sprintf(prefix, "%6lu.%09lu%s" PCI_DEVICE_NAME_PATTERN "%s", + ts->tv_sec, ts->tv_nsec, config->csv_sep, + rp->domain, rp->bus, rp->dev, rp->fn, + config->csv_sep); + else + sprintf(prefix, PCI_DEVICE_NAME_PATTERN "%s", + rp->domain, rp->bus, rp->dev, rp->fn, + config->csv_sep); + } +} + +void iostat_print_counters(struct evlist *evlist, struct perf_stat_config *config, + struct timespec *ts, char *prefix, + iostat_print_counter_t print_cnt_cb, void *arg) +{ + struct evsel *counter = evlist__first(evlist); + void *perf_device; + + evlist__set_selected(evlist, counter); + iostat_prefix(evlist, config, prefix, ts); + fprintf(config->output, "%s", prefix); + evlist__for_each_entry(evlist, counter) { + perf_device = evlist->selected->priv; + if (perf_device && perf_device != counter->priv) { + evlist__set_selected(evlist, counter); + iostat_prefix(evlist, config, prefix, ts); + fprintf(config->output, "\n%s", prefix); + } + print_cnt_cb(config, counter, arg); + } + fputc('\n', config->output); +} From bfb8b9b0012e665dd8953226bc1a4608657f6b7e Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Mon, 16 Jun 2025 18:26:44 +0800 Subject: [PATCH 070/107] perf iostat: hisi: Fix port range retrival commit 313828a0bc4dff454d20721ff3a1b99ab7ac3c9b openEuler Root ports monitored by different the PCIe PMUs may locates on the same root bus. It's not correct to match all the Root Ports on the same bus for one PCIe PMU. Fix this. Fixes: 26b19f9990c0 ("perf stat: Enable iostat mode for HiSilicon PCIe PMU") Signed-off-by: Yicong Yang Signed-off-by: Qizhi Zhang Signed-off-by: huwentao Signed-off-by: WangYuli --- tools/perf/arch/arm64/util/hisi-iostat.c | 32 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tools/perf/arch/arm64/util/hisi-iostat.c b/tools/perf/arch/arm64/util/hisi-iostat.c index 96684cc004648..fc794ee7d8d46 100644 --- a/tools/perf/arch/arm64/util/hisi-iostat.c +++ b/tools/perf/arch/arm64/util/hisi-iostat.c @@ -22,6 +22,10 @@ #include "util/iostat.h" #include "util/pmu.h" +/* From include/uapi/linux/pci.h */ +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) + #define PCI_DEFAULT_DOMAIN 0 #define PCI_DEVICE_NAME_PATTERN "%04x:%02hhx:%02hhx.%hhu" #define PCI_ROOT_BUS_DEVICES_PATH "bus/pci/devices" @@ -69,9 +73,9 @@ struct hisi_pcie_root_port { LIST_HEAD(hisi_pcie_root_ports_list); static int hisi_pcie_root_ports_num; -static void hisi_pcie_init_root_port_mask(struct hisi_pcie_root_port *rp) +static void hisi_pcie_init_root_port_mask(struct hisi_pcie_root_port *rp, u16 devbase) { - rp->mask = BIT(rp->dev << 1); + rp->mask = BIT((rp->dev - devbase) << 1); } /* @@ -100,7 +104,8 @@ static void hisi_pcie_root_ports_select_all(void) rp->selected = true; } -static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus) +static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus, + u16 bdf_min, u16 bdf_max) { const char *sysfs = sysfs__mountpoint(); struct hisi_pcie_root_port *rp; @@ -109,6 +114,7 @@ static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus) u8 bus, dev, fn; u32 domain; DIR *dir; + u16 bdf; int ret; snprintf(path, PATH_MAX, "%s/%s", sysfs, PCI_ROOT_BUS_DEVICES_PATH); @@ -123,6 +129,10 @@ static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus) if (ret != 4 || bus != target_bus) continue; + bdf = (bus << 8) | PCI_DEVFN(dev, fn); + if (bdf < bdf_min || bdf > bdf_max) + continue; + rp = zalloc(sizeof(*rp)); if (!rp) continue; @@ -135,7 +145,7 @@ static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus) rp->dev = dev; rp->fn = fn; - hisi_pcie_init_root_port_mask(rp); + hisi_pcie_init_root_port_mask(rp, PCI_SLOT(bdf_min)); list_add(&rp->list, &hisi_pcie_root_ports_list); hisi_pcie_root_ports_num++; @@ -150,7 +160,7 @@ static void hisi_pcie_root_ports_add(u16 sicl_id, u16 core_id, u8 target_bus) static int hisi_pcie_root_ports_init(void) { char event_source[PATH_MAX], bus_path[PATH_MAX]; - unsigned long long bus; + unsigned long long bus, bdf_max, bdf_min; u16 sicl_id, core_id; struct dirent *dent; DIR *dir; @@ -182,9 +192,19 @@ static int hisi_pcie_root_ports_init(void) if (filename__read_ull(bus_path, &bus)) continue; + scnprintf(bus_path, sizeof(bus_path), "%s/hisi_pcie%hu_core%hu/bdf_max", + event_source, sicl_id, core_id); + if (filename__read_xll(bus_path, &bdf_max)) + bdf_max = -1; + + scnprintf(bus_path, sizeof(bus_path), "%s/hisi_pcie%hu_core%hu/bdf_min", + event_source, sicl_id, core_id); + if (filename__read_xll(bus_path, &bdf_min)) + bdf_min = 0; + pr_debug3("Found pmu %s bus 0x%llx\n", dent->d_name, bus); - hisi_pcie_root_ports_add(sicl_id, core_id, (u8)bus); + hisi_pcie_root_ports_add(sicl_id, core_id, (u8)bus, (u16)bdf_min, (u16)bdf_max); } closedir(dir); From 023fa2c3497d446f0430cd9717ba6f901954d546 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:21 +0800 Subject: [PATCH 071/107] firmware: arm_sdei: add interrupt binding api commit 394a8507f8556ca7f430007e3f20d15e19f7bdbc openEuler This patch add a interrupt binding api function which returns the binded event number. Signed-off-by: Xiongfeng Wang Reviewed-by: Kefeng Wang Signed-off-by: Yang Yingliang Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/firmware/arm_sdei.c | 10 ++++++++++ include/linux/arm_sdei.h | 1 + 2 files changed, 11 insertions(+) diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c index 71e2a9a89f6ad..5fb4c67366a37 100644 --- a/drivers/firmware/arm_sdei.c +++ b/drivers/firmware/arm_sdei.c @@ -188,6 +188,16 @@ int sdei_api_event_context(u32 query, u64 *result) } NOKPROBE_SYMBOL(sdei_api_event_context); +int sdei_api_event_interrupt_bind(int hwirq) +{ + u64 event_number; + + invoke_sdei_fn(SDEI_1_0_FN_SDEI_INTERRUPT_BIND, hwirq, 0, 0, 0, 0, + &event_number); + + return (int)event_number; +} + static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) { return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, diff --git a/include/linux/arm_sdei.h b/include/linux/arm_sdei.h index f652a5028b590..bfa3af932b970 100644 --- a/include/linux/arm_sdei.h +++ b/include/linux/arm_sdei.h @@ -36,6 +36,7 @@ int sdei_event_unregister(u32 event_num); int sdei_event_enable(u32 event_num); int sdei_event_disable(u32 event_num); +int sdei_api_event_interrupt_bind(int hwirq); /* GHES register/unregister helpers */ int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, From dc6f01fb037a3ec6bd357e90c6b85b48a5c41dbc Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:22 +0800 Subject: [PATCH 072/107] firmware: arm_sdei: make 'sdei_api_event_disable/enable' public commit 2b61c8803b5ae428fa7408808c999d2a6e3f5c24 openEuler NMI Watchdog need to enable the event for each core individually. But the existing public api 'sdei_event_enable' enable events for all cores when the event type is private. Signed-off-by: Xiongfeng Wang Reviewed-by: Kefeng Wang Reviewed-by: Hanjun Guo Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/firmware/arm_sdei.c | 4 ++-- include/linux/arm_sdei.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c index 5fb4c67366a37..05441c3aab703 100644 --- a/drivers/firmware/arm_sdei.c +++ b/drivers/firmware/arm_sdei.c @@ -389,7 +389,7 @@ static int sdei_platform_reset(void) return err; } -static int sdei_api_event_enable(u32 event_num) +int sdei_api_event_enable(u32 event_num) { return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0, 0, NULL); @@ -436,7 +436,7 @@ int sdei_event_enable(u32 event_num) return err; } -static int sdei_api_event_disable(u32 event_num) +int sdei_api_event_disable(u32 event_num) { return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0, 0, 0, NULL); diff --git a/include/linux/arm_sdei.h b/include/linux/arm_sdei.h index bfa3af932b970..880abe829dfb8 100644 --- a/include/linux/arm_sdei.h +++ b/include/linux/arm_sdei.h @@ -37,6 +37,8 @@ int sdei_event_unregister(u32 event_num); int sdei_event_enable(u32 event_num); int sdei_event_disable(u32 event_num); int sdei_api_event_interrupt_bind(int hwirq); +int sdei_api_event_disable(u32 event_num); +int sdei_api_event_enable(u32 event_num); /* GHES register/unregister helpers */ int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, From be0afe5fae4c2d89b85c8a59dcc6c5dc11176a02 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:23 +0800 Subject: [PATCH 073/107] lockup_detector: init lockup detector after all the init_calls commit 078428464b38d44898d9aa09dd6b66ebc681ae36 openEuler We call 'sdei_init' as 'subsys_initcall_sync'. lockup detector need to be initialised after sdei_init. The influence of this patch is that we can not detect the hard lockup in init_calls. Signed-off-by: Xiongfeng Wang Reviewed-by: Kefeng Wang Reviewed-by: Hanjun Guo Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- init/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/init/main.c b/init/main.c index c98ca410a37b0..243da5471259a 100644 --- a/init/main.c +++ b/init/main.c @@ -1553,7 +1553,6 @@ static noinline void __init kernel_init_freeable(void) rcu_init_tasks_generic(); do_pre_smp_initcalls(); - lockup_detector_init(); smp_init(); sched_init_smp(); @@ -1564,6 +1563,8 @@ static noinline void __init kernel_init_freeable(void) do_basic_setup(); + lockup_detector_init(); + kunit_run_all_tests(); wait_for_initramfs(); From 43cde10c4a3a3f208f191a58179e370d4d9f0e42 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:24 +0800 Subject: [PATCH 074/107] watchdog: add nmi_watchdog support for arm64 based on SDEI commit 06f3c8d593243b14ddfd020fb835d8d1f196cccb openEuler Add nmi_watchdog support for arm64 based on SDEI. Signed-off-by: Xiongfeng Wang Reviewed-by: Kefeng Wang Reviewed-by: Hanjun Guo Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/Makefile | 1 + arch/arm64/kernel/watchdog_sdei.c | 112 ++++++++++++++++++++++++++++++ lib/Kconfig.debug | 9 +++ 3 files changed, 122 insertions(+) create mode 100644 arch/arm64/kernel/watchdog_sdei.c diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index 4c66d803c054d..4eb8d39fe1ab3 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -69,6 +69,7 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o obj-$(CONFIG_CRASH_DUMP) += crash_dump.o obj-$(CONFIG_CRASH_CORE) += crash_core.o obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +obj-$(CONFIG_SDEI_WATCHDOG) += watchdog_sdei.o obj-$(CONFIG_ARM64_PTR_AUTH) += pointer_auth.o obj-$(CONFIG_ARM64_MTE) += mte.o obj-y += vdso-wrap.o diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c new file mode 100644 index 0000000000000..8f9eb838b9698 --- /dev/null +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Detect hard lockups on a system + * + * Note: Most of this code is borrowed heavily from the perf hardlockup + * detector, so thanks to Don for the initial implementation. + */ + +#define pr_fmt(fmt) "SDEI NMI watchdog: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* We use the secure physical timer as SDEI NMI watchdog timer */ +#define SDEI_NMI_WATCHDOG_HWIRQ 29 + +static int sdei_watchdog_event_num; +static bool disable_sdei_nmi_watchdog; +static bool sdei_watchdog_registered; + +void watchdog_hardlockup_enable(unsigned int cpu) +{ + int ret; + + if (!sdei_watchdog_registered) + return; + + /* Skip the first hardlockup check incase BIOS didn't init the + * secure timer correctly */ + watchdog_hardlockup_touch_cpu(cpu); + ret = sdei_api_event_enable(sdei_watchdog_event_num); + if (ret) { + pr_err("Enable NMI Watchdog failed on cpu%d\n", + smp_processor_id()); + } +} + +void watchdog_hardlockup_disable(unsigned int cpu) +{ + int ret; + + if (!sdei_watchdog_registered) + return; + + ret = sdei_api_event_disable(sdei_watchdog_event_num); + if (ret) + pr_err("Disable NMI Watchdog failed on cpu%d\n", + smp_processor_id()); +} + +static int sdei_watchdog_callback(u32 event, + struct pt_regs *regs, void *arg) +{ + watchdog_hardlockup_check(smp_processor_id(), regs); + + return 0; +} + +static void sdei_nmi_watchdog_bind(void *data) +{ + int ret; + + ret = sdei_api_event_interrupt_bind(SDEI_NMI_WATCHDOG_HWIRQ); + if (ret < 0) + pr_err("SDEI bind failed on cpu%d, return %d\n", + smp_processor_id(), ret); +} + +static int __init disable_sdei_nmi_watchdog_setup(char *str) +{ + disable_sdei_nmi_watchdog = true; + return 1; +} +__setup("disable_sdei_nmi_watchdog", disable_sdei_nmi_watchdog_setup); + +int __init watchdog_hardlockup_probe(void) +{ + int ret; + + if (disable_sdei_nmi_watchdog) + return -EINVAL; + + if (!is_hyp_mode_available()) { + pr_err("Disable SDEI NMI Watchdog in VM\n"); + return -EINVAL; + } + + sdei_watchdog_event_num = sdei_api_event_interrupt_bind(SDEI_NMI_WATCHDOG_HWIRQ); + if (sdei_watchdog_event_num < 0) { + pr_err("Bind interrupt failed. Firmware may not support SDEI !\n"); + return sdei_watchdog_event_num; + } + + on_each_cpu(sdei_nmi_watchdog_bind, NULL, true); + + ret = sdei_event_register(sdei_watchdog_event_num, + sdei_watchdog_callback, NULL); + if (ret) { + pr_err("SDEI Watchdog register callback failed\n"); + return ret; + } + + sdei_watchdog_registered = true; + pr_info("SDEI Watchdog registered successfully\n"); + + return 0; +} diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 8e552c89de21d..5c4172e220522 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1046,6 +1046,12 @@ config HAVE_HARDLOCKUP_DETECTOR_BUDDY depends on SMP default y +config SDEI_WATCHDOG + bool "SDEI NMI Watchdog support" + depends on ARM_SDE_INTERFACE + depends on HARDLOCKUP_DETECTOR + select HARDLOCKUP_DETECTOR_COUNTS_HRTIMER + # # Global switch whether to build a hardlockup detector at all. It is available # only when the architecture supports at least one implementation. There are @@ -1062,6 +1068,7 @@ config HARDLOCKUP_DETECTOR depends on HAVE_HARDLOCKUP_DETECTOR_PERF || HAVE_HARDLOCKUP_DETECTOR_BUDDY || HAVE_HARDLOCKUP_DETECTOR_ARCH imply HARDLOCKUP_DETECTOR_PERF imply HARDLOCKUP_DETECTOR_BUDDY + imply SDEI_WATCHDOG imply HARDLOCKUP_DETECTOR_ARCH select LOCKUP_DETECTOR @@ -1098,6 +1105,7 @@ config HARDLOCKUP_DETECTOR_PERF depends on HARDLOCKUP_DETECTOR depends on HAVE_HARDLOCKUP_DETECTOR_PERF && !HARDLOCKUP_DETECTOR_PREFER_BUDDY depends on !HAVE_HARDLOCKUP_DETECTOR_ARCH + depends on !SDEI_WATCHDOG select HARDLOCKUP_DETECTOR_COUNTS_HRTIMER config HARDLOCKUP_DETECTOR_BUDDY @@ -1106,6 +1114,7 @@ config HARDLOCKUP_DETECTOR_BUDDY depends on HAVE_HARDLOCKUP_DETECTOR_BUDDY depends on !HAVE_HARDLOCKUP_DETECTOR_PERF || HARDLOCKUP_DETECTOR_PREFER_BUDDY depends on !HAVE_HARDLOCKUP_DETECTOR_ARCH + depends on !SDEI_WATCHDOG select HARDLOCKUP_DETECTOR_COUNTS_HRTIMER config HARDLOCKUP_DETECTOR_ARCH From 78cf6339fafa037b25e2000aa92ba8bddb2b148c Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:25 +0800 Subject: [PATCH 075/107] sdei_watchdog: clear EOI of the secure timer before kdump commit 490288e8343f9914f850f13a00ff98f8b878fdcd openEuler When we panic in hardlockup, the secure timer interrupt remains activate because firmware clear eoi after dispatch is completed. This will cause arm_arch_timer interrupt failed to trigger in the second kernel. This patch add a new SMC helper to clear eoi of a certain interrupt and clear eoi of the secure timer before booting the second kernel. Signed-off-by: Xiongfeng Wang Reviewed-by: Hanjun Guo Signed-off-by: Zheng Zengkai Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/machine_kexec.c | 10 ++++++++++ arch/arm64/kernel/watchdog_sdei.c | 6 ++++++ drivers/firmware/arm_sdei.c | 6 ++++++ include/linux/arm_sdei.h | 1 + include/linux/nmi.h | 6 ++++++ include/uapi/linux/arm_sdei.h | 1 + 6 files changed, 30 insertions(+) diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c index 078910db77a41..cfa6b0dafc88b 100644 --- a/arch/arm64/kernel/machine_kexec.c +++ b/arch/arm64/kernel/machine_kexec.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -262,6 +263,15 @@ void machine_crash_shutdown(struct pt_regs *regs) /* shutdown non-crashing cpus */ crash_smp_send_stop(); + /* + * when we panic in hardlockup detected by sdei_watchdog, the secure + * timer interrupt remains activate here because firmware clear eoi + * after dispatch is completed. This will cause arm_arch_timer + * interrupt failed to trigger in the second kernel. So we clear eoi + * of the secure timer before booting the second kernel. + */ + sdei_watchdog_clear_eoi(); + /* for crashing cpu */ crash_save_cpu(regs, smp_processor_id()); machine_kexec_mask_interrupts(); diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 8f9eb838b9698..7ebf6b5ab237c 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -78,6 +78,12 @@ static int __init disable_sdei_nmi_watchdog_setup(char *str) } __setup("disable_sdei_nmi_watchdog", disable_sdei_nmi_watchdog_setup); +void sdei_watchdog_clear_eoi(void) +{ + if (sdei_watchdog_registered) + sdei_api_clear_eoi(SDEI_NMI_WATCHDOG_HWIRQ); +} + int __init watchdog_hardlockup_probe(void) { int ret; diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c index 05441c3aab703..43fd48b5f8695 100644 --- a/drivers/firmware/arm_sdei.c +++ b/drivers/firmware/arm_sdei.c @@ -198,6 +198,12 @@ int sdei_api_event_interrupt_bind(int hwirq) return (int)event_number; } +int sdei_api_clear_eoi(int hwirq) +{ + return invoke_sdei_fn(SDEI_1_0_FN_SDEI_CLEAR_EOI, hwirq, 0, 0, 0, 0, + NULL); +} + static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) { return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, diff --git a/include/linux/arm_sdei.h b/include/linux/arm_sdei.h index 880abe829dfb8..6a30a540500aa 100644 --- a/include/linux/arm_sdei.h +++ b/include/linux/arm_sdei.h @@ -39,6 +39,7 @@ int sdei_event_disable(u32 event_num); int sdei_api_event_interrupt_bind(int hwirq); int sdei_api_event_disable(u32 event_num); int sdei_api_event_enable(u32 event_num); +int sdei_api_clear_eoi(int hwirq); /* GHES register/unregister helpers */ int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, diff --git a/include/linux/nmi.h b/include/linux/nmi.h index e92e378df000f..404c78e04a05f 100644 --- a/include/linux/nmi.h +++ b/include/linux/nmi.h @@ -235,4 +235,10 @@ static inline void nmi_backtrace_stall_snap(const struct cpumask *btp) {} static inline void nmi_backtrace_stall_check(const struct cpumask *btp) {} #endif +#ifdef CONFIG_SDEI_WATCHDOG +void sdei_watchdog_clear_eoi(void); +#else +static inline void sdei_watchdog_clear_eoi(void) { } +#endif + #endif diff --git a/include/uapi/linux/arm_sdei.h b/include/uapi/linux/arm_sdei.h index af0630ba5437d..1187b1b49c87d 100644 --- a/include/uapi/linux/arm_sdei.h +++ b/include/uapi/linux/arm_sdei.h @@ -24,6 +24,7 @@ #define SDEI_1_0_FN_SDEI_INTERRUPT_RELEASE SDEI_1_0_FN(0x0E) #define SDEI_1_0_FN_SDEI_PRIVATE_RESET SDEI_1_0_FN(0x11) #define SDEI_1_0_FN_SDEI_SHARED_RESET SDEI_1_0_FN(0x12) +#define SDEI_1_0_FN_SDEI_CLEAR_EOI SDEI_1_0_FN(0x18) #define SDEI_VERSION_MAJOR_SHIFT 48 #define SDEI_VERSION_MAJOR_MASK 0x7fff From 35e236fd60378f02ec4c75549ac02c5ebce11a33 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:26 +0800 Subject: [PATCH 076/107] sdei_watchdog: set secure timer period base on 'watchdog_thresh' commit 7fc503364616a91334aa623a97949e2573b87d5d openEuler The period of the secure timer is set to 3s by BIOS. That means the secure timer interrupt will trigger every 3 seconds. To further decrease the NMI watchdog's effect on performance, this patch set the period of the secure timer base on 'watchdog_thresh'. This variable is initiallized to 10s. We can also set the period at runtime by modifying '/proc/sys/kernel/watchdog_thresh' Signed-off-by: Xiongfeng Wang Reviewed-by: Hanjun Guo Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 13 +++++++++++++ drivers/firmware/arm_sdei.c | 6 ++++++ include/linux/arm_sdei.h | 1 + include/uapi/linux/arm_sdei.h | 1 + 4 files changed, 21 insertions(+) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 7ebf6b5ab237c..758e20eadc318 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -33,6 +33,8 @@ void watchdog_hardlockup_enable(unsigned int cpu) /* Skip the first hardlockup check incase BIOS didn't init the * secure timer correctly */ watchdog_hardlockup_touch_cpu(cpu); + sdei_api_set_secure_timer_period(watchdog_thresh); + ret = sdei_api_event_enable(sdei_watchdog_event_num); if (ret) { pr_err("Enable NMI Watchdog failed on cpu%d\n", @@ -102,6 +104,17 @@ int __init watchdog_hardlockup_probe(void) return sdei_watchdog_event_num; } + /* + * After we introduced 'sdei_api_set_secure_timer_period', we disselect + * 'CONFIG_HARDLOCKUP_CHECK_TIMESTAMP'. So we need to make sure that + * firmware can set the period of the secure timer and the timer + * interrupt doesn't trigger too soon. + */ + if (sdei_api_set_secure_timer_period(watchdog_thresh)) { + pr_err("Firmware doesn't support setting the secure timer period, please update your BIOS !\n"); + return -EINVAL; + } + on_each_cpu(sdei_nmi_watchdog_bind, NULL, true); ret = sdei_event_register(sdei_watchdog_event_num, diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c index 43fd48b5f8695..5df85f08be071 100644 --- a/drivers/firmware/arm_sdei.c +++ b/drivers/firmware/arm_sdei.c @@ -204,6 +204,12 @@ int sdei_api_clear_eoi(int hwirq) NULL); } +int sdei_api_set_secure_timer_period(int sec) +{ + return invoke_sdei_fn(SDEI_1_0_FN_SET_SECURE_TIMER_PERIOD, sec, 0, 0, 0, + 0, NULL); +} + static int sdei_api_event_get_info(u32 event, u32 info, u64 *result) { return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0, diff --git a/include/linux/arm_sdei.h b/include/linux/arm_sdei.h index 6a30a540500aa..bb8a436b8a9c9 100644 --- a/include/linux/arm_sdei.h +++ b/include/linux/arm_sdei.h @@ -40,6 +40,7 @@ int sdei_api_event_interrupt_bind(int hwirq); int sdei_api_event_disable(u32 event_num); int sdei_api_event_enable(u32 event_num); int sdei_api_clear_eoi(int hwirq); +int sdei_api_set_secure_timer_period(int sec); /* GHES register/unregister helpers */ int sdei_register_ghes(struct ghes *ghes, sdei_event_callback *normal_cb, diff --git a/include/uapi/linux/arm_sdei.h b/include/uapi/linux/arm_sdei.h index 1187b1b49c87d..a5375679dd503 100644 --- a/include/uapi/linux/arm_sdei.h +++ b/include/uapi/linux/arm_sdei.h @@ -25,6 +25,7 @@ #define SDEI_1_0_FN_SDEI_PRIVATE_RESET SDEI_1_0_FN(0x11) #define SDEI_1_0_FN_SDEI_SHARED_RESET SDEI_1_0_FN(0x12) #define SDEI_1_0_FN_SDEI_CLEAR_EOI SDEI_1_0_FN(0x18) +#define SDEI_1_0_FN_SET_SECURE_TIMER_PERIOD SDEI_1_0_FN(0x19) #define SDEI_VERSION_MAJOR_SHIFT 48 #define SDEI_VERSION_MAJOR_MASK 0x7fff From 8368f4688578910b60cf9283b91091d62ac55605 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:27 +0800 Subject: [PATCH 077/107] sdei_watchdog: avoid possible false hardlockup commit cac525db2b93ba824cd861b66ac7a374c3ebb069 openEuler Firmware may not trigger SDEI event as required frequency. SDEI event may be triggered too soon, which cause false hardlockup in kernel. Check the time stamp in sdei_watchdog_callbak and skip the hardlockup check if it is invoked too soon. Signed-off-by: Xiongfeng Wang Reviewed-by: Hanjun Guo Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 758e20eadc318..4a143a598eef3 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -22,6 +22,7 @@ static int sdei_watchdog_event_num; static bool disable_sdei_nmi_watchdog; static bool sdei_watchdog_registered; +static DEFINE_PER_CPU(ktime_t, last_check_time); void watchdog_hardlockup_enable(unsigned int cpu) { @@ -34,6 +35,7 @@ void watchdog_hardlockup_enable(unsigned int cpu) * secure timer correctly */ watchdog_hardlockup_touch_cpu(cpu); sdei_api_set_secure_timer_period(watchdog_thresh); + __this_cpu_write(last_check_time, ktime_get_mono_fast_ns()); ret = sdei_api_event_enable(sdei_watchdog_event_num); if (ret) { @@ -58,6 +60,22 @@ void watchdog_hardlockup_disable(unsigned int cpu) static int sdei_watchdog_callback(u32 event, struct pt_regs *regs, void *arg) { + ktime_t delta, now = ktime_get_mono_fast_ns(); + + delta = now - __this_cpu_read(last_check_time); + __this_cpu_write(last_check_time, now); + + /* + * Set delta to 4/5 of the actual watchdog threshold period so the + * hrtimer is guaranteed to fire at least once within the real + * watchdog threshold. + */ + if (delta < watchdog_thresh * (u64)NSEC_PER_SEC * 4 / 5) { + pr_err(FW_BUG "SDEI Watchdog event triggered too soon, " + "time to last check:%lld ns\n", delta); + return 0; + } + watchdog_hardlockup_check(smp_processor_id(), regs); return 0; From 6401d93821dafe53450d7107f3d0607c252e075d Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:28 +0800 Subject: [PATCH 078/107] init: only move down lockup_detector_init() when sdei_watchdog is enabled commit 1509d06c9c41985ee6b7dd6acbb08d9ee5dcf2b3 openEuler When I enable CONFIG_DEBUG_PREEMPT and CONFIG_PREEMPT on X86, I got the following Call Trace: [ 3.341853] BUG: using smp_processor_id() in preemptible [00000000] code: swapper/0/1 [ 3.344392] caller is debug_smp_processor_id+0x17/0x20 [ 3.344395] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.10.0+ #398 [ 3.344397] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.10.2-0-g5f4c7b1-prebuilt.qemu-project.org 04/01/2014 [ 3.344399] Call Trace: [ 3.344410] dump_stack+0x60/0x76 [ 3.344412] check_preemption_disabled+0xba/0xc0 [ 3.344415] debug_smp_processor_id+0x17/0x20 [ 3.344422] hardlockup_detector_event_create+0xf/0x60 [ 3.344427] hardlockup_detector_perf_init+0xf/0x41 [ 3.344430] watchdog_nmi_probe+0xe/0x10 [ 3.344432] lockup_detector_init+0x22/0x5b [ 3.344437] kernel_init_freeable+0x20c/0x245 [ 3.344439] ? rest_init+0xd0/0xd0 [ 3.344441] kernel_init+0xe/0x110 [ 3.344446] ret_from_fork+0x22/0x30 It is because sched_init_smp() set 'current->nr_cpus_allowed' to possible cpu number, and check_preemption_disabled() failed. This issue is introduced by commit bf4c2c61cc74 ("lockup_detector: init lockup detector after all the init_calls"), which moves down lockup_detector_init() after do_basic_setup(). Fix it by moving lockup_detector_init() to its origin place when sdei_watchdog is disabled. There is no problem when sdei_watchdog is enabled because watchdog_nmi_probe() is overridden in 'arch/arm64/kernel/watchdog_sdei.c' in this case. Fixes: 8747ebe027bd ("lockup_detector: init lockup detector after all the init_calls") Signed-off-by: Xiongfeng Wang Reviewed-by: Wei Li Signed-off-by: Chen Jun Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 2 +- include/linux/nmi.h | 2 ++ init/main.c | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 4a143a598eef3..155f36e24699b 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -20,7 +20,7 @@ #define SDEI_NMI_WATCHDOG_HWIRQ 29 static int sdei_watchdog_event_num; -static bool disable_sdei_nmi_watchdog; +bool disable_sdei_nmi_watchdog; static bool sdei_watchdog_registered; static DEFINE_PER_CPU(ktime_t, last_check_time); diff --git a/include/linux/nmi.h b/include/linux/nmi.h index 404c78e04a05f..7bd446acad24a 100644 --- a/include/linux/nmi.h +++ b/include/linux/nmi.h @@ -237,8 +237,10 @@ static inline void nmi_backtrace_stall_check(const struct cpumask *btp) {} #ifdef CONFIG_SDEI_WATCHDOG void sdei_watchdog_clear_eoi(void); +extern bool disable_sdei_nmi_watchdog; #else static inline void sdei_watchdog_clear_eoi(void) { } +#define disable_sdei_nmi_watchdog 1 #endif #endif diff --git a/init/main.c b/init/main.c index 243da5471259a..89263a4723cd1 100644 --- a/init/main.c +++ b/init/main.c @@ -1553,6 +1553,8 @@ static noinline void __init kernel_init_freeable(void) rcu_init_tasks_generic(); do_pre_smp_initcalls(); + if (disable_sdei_nmi_watchdog) + lockup_detector_init(); smp_init(); sched_init_smp(); @@ -1563,7 +1565,9 @@ static noinline void __init kernel_init_freeable(void) do_basic_setup(); - lockup_detector_init(); + /* sdei_watchdog needs to be initialized after sdei_init */ + if (!disable_sdei_nmi_watchdog) + lockup_detector_init(); kunit_run_all_tests(); From 7a8526433d5ab517eda2ac321afa6a9fc016c5ca Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:29 +0800 Subject: [PATCH 079/107] kprobes/arm64: Blacklist sdei watchdog callback functions commit 9c2c933d3e9ac36f9f77391fe63ffbb145a566d6 openEuler Functions called in sdei_handler are not allowed to be kprobed, so marked them as NOKPROBE_SYMBOL. There are so many functions in 'watchdog_check_timestamp()'. Luckily, we don't need 'CONFIG_HARDLOCKUP_CHECK_TIMESTAMP' now. So just make CONFIG_SDEI_WATCHDOG depends on !CONFIG_HARDLOCKUP_CHECK_TIMESTAMP in case someone add 'CONFIG_HARDLOCKUP_CHECK_TIMESTAMP' in the future. Signed-off-by: Xiongfeng Wang Reviewed-by: Yang Yingliang Reviewed-by: Hanjun Guo Signed-off-by: Xiongfeng Wang Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 2 ++ kernel/watchdog.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 155f36e24699b..6f43496de56ef 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -14,6 +14,7 @@ #include #include #include +#include #include /* We use the secure physical timer as SDEI NMI watchdog timer */ @@ -80,6 +81,7 @@ static int sdei_watchdog_callback(u32 event, return 0; } +NOKPROBE_SYMBOL(sdei_watchdog_callback); static void sdei_nmi_watchdog_bind(void *data) { diff --git a/kernel/watchdog.c b/kernel/watchdog.c index 7abd69a776b20..f9bed8c77610c 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -19,6 +19,9 @@ #include #include #include + +#include + #include #include #include @@ -128,6 +131,7 @@ static bool is_hardlockup(unsigned int cpu) return false; } +NOKPROBE_SYMBOL(is_hardlockup); static void watchdog_hardlockup_kick(void) { @@ -185,6 +189,7 @@ void watchdog_hardlockup_check(unsigned int cpu, struct pt_regs *regs) per_cpu(watchdog_hardlockup_warned, cpu) = false; } } +NOKPROBE_SYMBOL(watchdog_hardlockup_check); #else /* CONFIG_HARDLOCKUP_DETECTOR_COUNTS_HRTIMER */ From 578806cebd15ca2d63f6a02ffa466ffd89981f92 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:31 +0800 Subject: [PATCH 080/107] stop_machine: mask sdei before running the callback commit dab3bdb9cab08acf5763f5ddedc75eead8daf70b openEuler Kprobes use 'stop_machine' to modify code which could be ran in the sdei_handler at the same time. This patch mask sdei before running the stop_machine callback to avoid this race condition. Signed-off-by: Xiongfeng Wang Reviewed-by: Yang Yingliang Signed-off-by: Wei Li Reviewed-by: Cheng Jian Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- kernel/stop_machine.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c index cedb17ba158a9..9466d61d21c98 100644 --- a/kernel/stop_machine.c +++ b/kernel/stop_machine.c @@ -23,6 +23,10 @@ #include #include +#ifdef CONFIG_ARM64 +#include +#endif + /* * Structure to determine completion condition and record errors. May * be shared by works on different cpus. @@ -234,6 +238,9 @@ static int multi_cpu_stop(void *data) case MULTI_STOP_DISABLE_IRQ: local_irq_disable(); hard_irq_disable(); +#ifdef CONFIG_ARM64 + sdei_mask_local_cpu(); +#endif break; case MULTI_STOP_RUN: if (is_active) @@ -254,6 +261,9 @@ static int multi_cpu_stop(void *data) rcu_momentary_dyntick_idle(); } while (curstate != MULTI_STOP_EXIT); +#ifdef CONFIG_ARM64 + sdei_unmask_local_cpu(); +#endif local_irq_restore(flags); return err; } From 9e11a4e1bd528f28c149a16e89eafd59c95b0759 Mon Sep 17 00:00:00 2001 From: Xiongfeng Wang Date: Tue, 5 Dec 2023 10:17:32 +0800 Subject: [PATCH 081/107] arm64: kexec: only clear EOI for SDEI in NMI context commit 2dfa9f6ad9405cdafabc27c26e2cf56b5fca3442 openEuler We need to clear EOI for the secure timer only when we panic from sdei_handler. If we clear EOI for the secure timer in normal panic routiue, it has no bad effect on Hi1620, but it may cause undefine behavior on Hi1616. So add a check for NMI context before we clear EOI for the secure timer. Fixes: dc0ef6dbb57d ("sdei_watchdog: clear EOI of the secure timer before kdump") Signed-off-by: Xiongfeng Wang Reviewed-by: Wei Li Reviewed-by: Xie XiuQi Signed-off-by: huwentao Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/machine_kexec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c index cfa6b0dafc88b..40607a4fe3a57 100644 --- a/arch/arm64/kernel/machine_kexec.c +++ b/arch/arm64/kernel/machine_kexec.c @@ -270,7 +270,8 @@ void machine_crash_shutdown(struct pt_regs *regs) * interrupt failed to trigger in the second kernel. So we clear eoi * of the secure timer before booting the second kernel. */ - sdei_watchdog_clear_eoi(); + if (in_nmi()) + sdei_watchdog_clear_eoi(); /* for crashing cpu */ crash_save_cpu(regs, smp_processor_id()); From 5e818172a0e76e180c7f587b0fafe99f3dbe1ce7 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Thu, 1 Feb 2024 15:51:37 +0800 Subject: [PATCH 082/107] watchdog: Support watchdog_sdei coexist with existing watchdogs commit f61b11535a0bcb5c0a90f626a757cb710d71409c openEuler Currently we cannot use watchdog_{perf, buddy} if CONFIG_SDEI_WATCHDOG=y. Not all the platforms has watchdog_sdei so this patch tries to make watchdog_sdei coexist with other watchdogs. Only one watchdog will finally works. By default watchdog_sdei will be used. If boot with "disable_sdei_nmi_watchdog", other watchdogs will be used if probed. Signed-off-by: Yicong Yang Signed-off-by: Jie Liu Signed-off-by: huwentao Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 6 +++--- include/linux/nmi.h | 6 ++++++ kernel/watchdog.c | 16 ++++++++++++---- lib/Kconfig.debug | 2 -- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 6f43496de56ef..c7b12806364e0 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -25,7 +25,7 @@ bool disable_sdei_nmi_watchdog; static bool sdei_watchdog_registered; static DEFINE_PER_CPU(ktime_t, last_check_time); -void watchdog_hardlockup_enable(unsigned int cpu) +void sdei_watchdog_hardlockup_enable(unsigned int cpu) { int ret; @@ -45,7 +45,7 @@ void watchdog_hardlockup_enable(unsigned int cpu) } } -void watchdog_hardlockup_disable(unsigned int cpu) +void sdei_watchdog_hardlockup_disable(unsigned int cpu) { int ret; @@ -106,7 +106,7 @@ void sdei_watchdog_clear_eoi(void) sdei_api_clear_eoi(SDEI_NMI_WATCHDOG_HWIRQ); } -int __init watchdog_hardlockup_probe(void) +int __init sdei_watchdog_hardlockup_probe(void) { int ret; diff --git a/include/linux/nmi.h b/include/linux/nmi.h index 7bd446acad24a..43dd3a79fdf21 100644 --- a/include/linux/nmi.h +++ b/include/linux/nmi.h @@ -236,10 +236,16 @@ static inline void nmi_backtrace_stall_check(const struct cpumask *btp) {} #endif #ifdef CONFIG_SDEI_WATCHDOG +void sdei_watchdog_hardlockup_enable(unsigned int cpu); +void sdei_watchdog_hardlockup_disable(unsigned int cpu); void sdei_watchdog_clear_eoi(void); +int sdei_watchdog_hardlockup_probe(void); extern bool disable_sdei_nmi_watchdog; #else +static inline void sdei_watchdog_hardlockup_enable(unsigned int cpu) { } +static inline void sdei_watchdog_hardlockup_disable(unsigned int cpu) { } static inline void sdei_watchdog_clear_eoi(void) { } +static inline int sdei_watchdog_hardlockup_probe(void) { return -ENODEV; } #define disable_sdei_nmi_watchdog 1 #endif diff --git a/kernel/watchdog.c b/kernel/watchdog.c index f9bed8c77610c..a1021235ddea1 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -565,8 +565,12 @@ static void watchdog_enable(unsigned int cpu) /* Initialize timestamp */ update_touch_ts(); /* Enable the hardlockup detector */ - if (watchdog_enabled & WATCHDOG_HARDLOCKUP_ENABLED) - watchdog_hardlockup_enable(cpu); + if (watchdog_enabled & WATCHDOG_HARDLOCKUP_ENABLED) { + if (disable_sdei_nmi_watchdog) + watchdog_hardlockup_enable(cpu); + else + sdei_watchdog_hardlockup_enable(cpu); + } } static void watchdog_disable(unsigned int cpu) @@ -580,7 +584,10 @@ static void watchdog_disable(unsigned int cpu) * delay between disabling the timer and disabling the hardlockup * detector causes a false positive. */ - watchdog_hardlockup_disable(cpu); + if (disable_sdei_nmi_watchdog) + watchdog_hardlockup_disable(cpu); + else + sdei_watchdog_hardlockup_disable(cpu); hrtimer_cancel(hrtimer); wait_for_completion(this_cpu_ptr(&softlockup_completion)); } @@ -1037,7 +1044,8 @@ void __init lockup_detector_init(void) cpumask_copy(&watchdog_cpumask, housekeeping_cpumask(HK_TYPE_TIMER)); - if (!watchdog_hardlockup_probe()) + if ((!disable_sdei_nmi_watchdog && !sdei_watchdog_hardlockup_probe()) || + !watchdog_hardlockup_probe()) watchdog_hardlockup_available = true; else allow_lockup_detector_init_retry = true; diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 5c4172e220522..935b56bf4ea35 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1105,7 +1105,6 @@ config HARDLOCKUP_DETECTOR_PERF depends on HARDLOCKUP_DETECTOR depends on HAVE_HARDLOCKUP_DETECTOR_PERF && !HARDLOCKUP_DETECTOR_PREFER_BUDDY depends on !HAVE_HARDLOCKUP_DETECTOR_ARCH - depends on !SDEI_WATCHDOG select HARDLOCKUP_DETECTOR_COUNTS_HRTIMER config HARDLOCKUP_DETECTOR_BUDDY @@ -1114,7 +1113,6 @@ config HARDLOCKUP_DETECTOR_BUDDY depends on HAVE_HARDLOCKUP_DETECTOR_BUDDY depends on !HAVE_HARDLOCKUP_DETECTOR_PERF || HARDLOCKUP_DETECTOR_PREFER_BUDDY depends on !HAVE_HARDLOCKUP_DETECTOR_ARCH - depends on !SDEI_WATCHDOG select HARDLOCKUP_DETECTOR_COUNTS_HRTIMER config HARDLOCKUP_DETECTOR_ARCH From f5ee0369409bb3d01023ee54267e31b8def54b1c Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Thu, 28 Mar 2024 11:00:55 +0800 Subject: [PATCH 083/107] watchdog: Fix call trace when failed to initialize sdei commit 20c2368e0195aae332176096336105c470d48f15 openEuler 1509d06c9c41 ("init: only move down lockup_detector_init() when sdei_watchdog is enabled") In the above commit, sdei_watchdog needs to move down lockup_detector_init (), while nmi_watchdog does not. So when sdei_watchdog fails to be initialized, nmi_watchdog should not be initialized. [ 0.706631][ T1] SDEI NMI watchdog: Disable SDEI NMI Watchdog in VM [ 0.707405][ T1] ------------[ cut here ]------------ [ 0.708020][ T1] WARNING: CPU: 0 PID: 1 at kernel/watchdog_perf.c:117 hardlockup_detector_event_create+0x24/0x108 [ 0.709230][ T1] Modules linked in: [ 0.709665][ T1] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 6.6.0 #1 [ 0.710700][ T1] Hardware name: QEMU KVM Virtual Machine, BIOS 0.0.0 02/06/2015 [ 0.711625][ T1] pstate: 00400005 (nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 0.712547][ T1] pc : hardlockup_detector_event_create+0x24/0x108 [ 0.713316][ T1] lr : watchdog_hardlockup_probe+0x28/0xa8 [ 0.714010][ T1] sp : ffff8000831cbdc0 [ 0.714501][ T1] pmr_save: 000000e0 [ 0.714957][ T1] x29: ffff8000831cbdc0 x28: 0000000000000000 x27: 0000000000000000 [ 0.715899][ T1] x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000 [ 0.716839][ T1] x23: 0000000000000000 x22: 0000000000000000 x21: ffff80008218fab0 [ 0.717775][ T1] x20: ffff8000821af000 x19: ffff0000c0261900 x18: 0000000000000020 [ 0.718713][ T1] x17: 00000000cb551c45 x16: ffff800082625e48 x15: ffffffffffffffff [ 0.719663][ T1] x14: 0000000000000000 x13: 205d315420202020 x12: 5b5d313336363037 [ 0.720607][ T1] x11: 00000000ffff7fff x10: 00000000ffff7fff x9 : ffff800081b5f630 [ 0.721590][ T1] x8 : 00000000000bffe8 x7 : c0000000ffff7fff x6 : 000000000005fff4 [ 0.722528][ T1] x5 : 00000000002bffa8 x4 : 0000000000000000 x3 : 0000000000000000 [ 0.723482][ T1] x2 : 0000000000000000 x1 : 0000000000000140 x0 : ffff0000c02c0000 [ 0.724426][ T1] Call trace: [ 0.724808][ T1] hardlockup_detector_event_create+0x24/0x108 [ 0.725535][ T1] watchdog_hardlockup_probe+0x28/0xa8 [ 0.726174][ T1] lockup_detector_init+0x110/0x158 [ 0.726776][ T1] kernel_init_freeable+0x208/0x288 [ 0.727387][ T1] kernel_init+0x2c/0x200 [ 0.727902][ T1] ret_from_fork+0x10/0x20 [ 0.728420][ T1] ---[ end trace 0000000000000000 ]--- Fixes: 7cc5cc30a7d1 ("watchdog: Support watchdog_sdei coexist with existing watchdogs") Signed-off-by: Yicong Yang Signed-off-by: Jie Liu Signed-off-by: huwentao Signed-off-by: WangYuli --- kernel/watchdog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/watchdog.c b/kernel/watchdog.c index a1021235ddea1..e7c17ed11aca3 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -1045,7 +1045,7 @@ void __init lockup_detector_init(void) housekeeping_cpumask(HK_TYPE_TIMER)); if ((!disable_sdei_nmi_watchdog && !sdei_watchdog_hardlockup_probe()) || - !watchdog_hardlockup_probe()) + (disable_sdei_nmi_watchdog && !watchdog_hardlockup_probe())) watchdog_hardlockup_available = true; else allow_lockup_detector_init_retry = true; From 1ed7c96db8dcdd0b987022d2a0a885d4b72532bf Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 13 May 2025 19:20:00 +0800 Subject: [PATCH 084/107] watchdog/perf: Provide function for adjusting the event period [Upstream commit 60bc47b5a0b164082d448815d7db3066266aa3ed] Architecture's using perf events for hard lockup detection needs to convert the watchdog_thresh to the event's period, some architecture for example arm64 perform this conversion using the CPU's maximum frequency which will be acquired by cpufreq. However by the time the lockup detector's initialized the cpufreq driver may not be initialized, thus launch a watchdog with inaccurate period. Provide a function hardlockup_detector_perf_adjust_period() to allowing adjust the event period. Then architecture can update with more accurate period if cpufreq is initialized. Fixes: 94946f9eaac1 ("arm64: add hw_nmi_get_sample_period for preparation of lockup detector") Signed-off-by: Yicong Yang Signed-off-by: Hongye Lin Signed-off-by: huwentao Signed-off-by: WangYuli --- include/linux/nmi.h | 2 ++ kernel/watchdog_perf.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/linux/nmi.h b/include/linux/nmi.h index 43dd3a79fdf21..95805d31d28da 100644 --- a/include/linux/nmi.h +++ b/include/linux/nmi.h @@ -105,10 +105,12 @@ void watchdog_hardlockup_check(unsigned int cpu, struct pt_regs *regs); extern void hardlockup_detector_perf_stop(void); extern void hardlockup_detector_perf_restart(void); extern void hardlockup_detector_perf_cleanup(void); +extern void hardlockup_detector_perf_adjust_period(int cpu, u64 period); #else static inline void hardlockup_detector_perf_stop(void) { } static inline void hardlockup_detector_perf_restart(void) { } static inline void hardlockup_detector_perf_cleanup(void) { } +static inline void hardlockup_detector_perf_adjust_period(int cpu, u64 period) { } #endif void watchdog_hardlockup_stop(void); diff --git a/kernel/watchdog_perf.c b/kernel/watchdog_perf.c index 0052afe18b7fc..989d30f80951e 100644 --- a/kernel/watchdog_perf.c +++ b/kernel/watchdog_perf.c @@ -198,6 +198,29 @@ void hardlockup_detector_perf_cleanup(void) cpumask_clear(&dead_events_mask); } +/** + * hardlockup_detector_perf_adjust_period - Adjust the event period due + * to cpu frequency change + * @cpu: The CPU whose event period will be adjusted + * @period: The target period to be set + */ +void hardlockup_detector_perf_adjust_period(int cpu, u64 period) +{ + struct perf_event *event = per_cpu(watchdog_ev, cpu); + + if (!(watchdog_enabled & WATCHDOG_HARDLOCKUP_ENABLED)) + return; + + if (!event) + return; + + if (event->attr.sample_period == period) + return; + + if (perf_event_period(event, period)) + pr_err("failed to change period to %llu\n", period); +} + /** * hardlockup_detector_perf_stop - Globally stop watchdog events * From f2cdb1bbc092a89ddd8f52d3a2a2731a269ccac7 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 13 May 2025 19:20:01 +0800 Subject: [PATCH 085/107] arm64/watchdog_hld: Add a cpufreq notifier for update watchdog thresh [Upstream commit 7a884442aeb694c28ac90a1fb023856039ef57ee] arm64 depends on the cpufreq driver to gain the maximum cpu frequency to convert the watchdog_thresh to perf event period. cpufreq drivers like cppc_cpufreq will be initialized lately after the initializing of the hard lockup detector so just use a safe cpufreq which will be inaccurency. Use a cpufreq notifier to adjust the event's period to a more accurate one. Fixes: 94946f9eaac1 ("arm64: add hw_nmi_get_sample_period for preparation of lockup detector") Signed-off-by: Yicong Yang Signed-off-by: Hongye Lin Signed-off-by: huwentao Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_hld.c | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/arch/arm64/kernel/watchdog_hld.c b/arch/arm64/kernel/watchdog_hld.c index dcd25322127c3..e55548cb26df0 100644 --- a/arch/arm64/kernel/watchdog_hld.c +++ b/arch/arm64/kernel/watchdog_hld.c @@ -34,3 +34,61 @@ bool __init arch_perf_nmi_is_available(void) */ return arm_pmu_irq_is_nmi(); } + +static int watchdog_perf_update_period(void *data) +{ + int cpu = raw_smp_processor_id(); + u64 max_cpu_freq, new_period; + + max_cpu_freq = cpufreq_get_hw_max_freq(cpu) * 1000UL; + if (!max_cpu_freq) + return 0; + + new_period = watchdog_thresh * max_cpu_freq; + hardlockup_detector_perf_adjust_period(cpu, new_period); + + return 0; +} + +static int watchdog_freq_notifier_callback(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_policy *policy = data; + int cpu; + + if (val != CPUFREQ_CREATE_POLICY) + return NOTIFY_DONE; + + /* + * Let each online CPU related to the policy update the period by their + * own. This will serialize with the framework on start/stop the lockup + * detector (softlockup_{start,stop}_all) and avoid potential race + * condition. Otherwise we may have below theoretical race condition: + * (core 0/1 share the same policy) + * [core 0] [core 1] + * hardlockup_detector_event_create() + * hw_nmi_get_sample_period() + * (cpufreq registered, notifier callback invoked) + * watchdog_freq_notifier_callback() + * watchdog_perf_update_period() + * (since core 1's event's not yet created, + * the period is not set) + * perf_event_create_kernel_counter() + * (event's period is SAFE_MAX_CPU_FREQ) + */ + for_each_cpu(cpu, policy->cpus) + smp_call_on_cpu(cpu, watchdog_perf_update_period, NULL, false); + + return NOTIFY_DONE; +} + +static struct notifier_block watchdog_freq_notifier = { + .notifier_call = watchdog_freq_notifier_callback, +}; + +static int __init init_watchdog_freq_notifier(void) +{ + return cpufreq_register_notifier(&watchdog_freq_notifier, + CPUFREQ_POLICY_NOTIFIER); +} +core_initcall(init_watchdog_freq_notifier); From c41af4e059863dba7710da2b3ae4a498fe9b87da Mon Sep 17 00:00:00 2001 From: Bowen You Date: Wed, 28 May 2025 02:00:49 +0000 Subject: [PATCH 086/107] arm64/watchdog: fix watchdog failure in low power scenarios commit c8f96adca7fadbacc9b1af75528964da9d7bb166 openEuler During testing, it was found that in low-power scenarios, the CPU core frequently powers on and off, which causes the SDEI watchdog to fail. This is because the secure timer in the BIOS does not save and restore the state during the CPU core power-on and power-off process. The OS needs to add enable/disable operations in the suspend/resume process. Fixes: e29d570c5413 ("watchdog: add nmi_watchdog support for arm64 based on SDEI") Signed-off-by: Bowen You Signed-off-by: huwentao Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index c7b12806364e0..5ea5e4b9d7616 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -16,6 +16,7 @@ #include #include #include +#include /* We use the secure physical timer as SDEI NMI watchdog timer */ #define SDEI_NMI_WATCHDOG_HWIRQ 29 @@ -106,6 +107,34 @@ void sdei_watchdog_clear_eoi(void) sdei_api_clear_eoi(SDEI_NMI_WATCHDOG_HWIRQ); } +static int sdei_watchdog_pm_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + int rv; + + WARN_ON_ONCE(preemptible()); + + switch (action) { + case CPU_PM_ENTER: + rv = sdei_api_event_disable(sdei_watchdog_event_num); + break; + case CPU_PM_EXIT: + rv = sdei_api_event_enable(sdei_watchdog_event_num); + break; + default: + return NOTIFY_DONE; + } + + if (rv) + return notifier_from_errno(rv); + + return NOTIFY_OK; +} + +static struct notifier_block sdei_watchdog_pm_nb = { + .notifier_call = sdei_watchdog_pm_notifier, +}; + int __init sdei_watchdog_hardlockup_probe(void) { int ret; @@ -144,6 +173,11 @@ int __init sdei_watchdog_hardlockup_probe(void) return ret; } + ret = cpu_pm_register_notifier(&sdei_watchdog_pm_nb); + if (ret) { + pr_warn("Failed to register sdei PM notifier...\n"); + return ret; + } sdei_watchdog_registered = true; pr_info("SDEI Watchdog registered successfully\n"); From 1af02823461468d3202abb7ca686561ff3290d45 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Tue, 5 Aug 2025 15:24:10 +0800 Subject: [PATCH 087/107] sdei_watchdog: use lockup_detector_retry_init() to init sdei watchdog commit f022c4cac9c1013fa8820b0af01aaa98e38053f1 openEuler sdei watchdog needs to be initialized after sdei_init, so commit 81e349d81958 ("init: only move down lockup_detector_init() when sdei_watchdog is enabled") move down the lockup_detector_init(). Now Commit 930d8f8dbab9 ("watchdog/perf: adapt the watchdog_perf interface for async model") provide an API lockup_detector_retry_init() for anyone who needs to delayed init lockup detector, so use this API to delay init sdei watchdog. Signed-off-by: Yang Yingliang Signed-off-by: zhangguangzhi <908293048@qq.com> Signed-off-by: WangYuli --- arch/arm64/kernel/watchdog_sdei.c | 10 ++++++++++ drivers/perf/arm_pmuv3.c | 2 +- init/main.c | 7 +------ kernel/watchdog.c | 10 +++++++--- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/arch/arm64/kernel/watchdog_sdei.c b/arch/arm64/kernel/watchdog_sdei.c index 5ea5e4b9d7616..4116386632123 100644 --- a/arch/arm64/kernel/watchdog_sdei.c +++ b/arch/arm64/kernel/watchdog_sdei.c @@ -183,3 +183,13 @@ int __init sdei_watchdog_hardlockup_probe(void) return 0; } + +static int __init sdei_watchdog_hardlockup_init(void) +{ + /* sdei_watchdog needs to be initialized after sdei_init */ + if (!disable_sdei_nmi_watchdog) + lockup_detector_retry_init(); + + return 0; +} +device_initcall(sdei_watchdog_hardlockup_init) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 0858e6096453e..8249f1f0b4b59 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -1364,7 +1364,7 @@ static int __init armv8_pmu_driver_init(void) else ret = arm_pmu_acpi_probe(armv8_pmuv3_pmu_init); - if (!ret) + if (!ret && disable_sdei_nmi_watchdog) lockup_detector_retry_init(); return ret; diff --git a/init/main.c b/init/main.c index 89263a4723cd1..c98ca410a37b0 100644 --- a/init/main.c +++ b/init/main.c @@ -1553,8 +1553,7 @@ static noinline void __init kernel_init_freeable(void) rcu_init_tasks_generic(); do_pre_smp_initcalls(); - if (disable_sdei_nmi_watchdog) - lockup_detector_init(); + lockup_detector_init(); smp_init(); sched_init_smp(); @@ -1565,10 +1564,6 @@ static noinline void __init kernel_init_freeable(void) do_basic_setup(); - /* sdei_watchdog needs to be initialized after sdei_init */ - if (!disable_sdei_nmi_watchdog) - lockup_detector_init(); - kunit_run_all_tests(); wait_for_initramfs(); diff --git a/kernel/watchdog.c b/kernel/watchdog.c index e7c17ed11aca3..714a553ca9eed 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -988,7 +988,12 @@ static void __init lockup_detector_delay_init(struct work_struct *work) { int ret; - ret = watchdog_hardlockup_probe(); + if (disable_sdei_nmi_watchdog) { + ret = watchdog_hardlockup_probe(); + } else { + ret = sdei_watchdog_hardlockup_probe(); + } + if (ret) { pr_info("Delayed init of the lockup detector failed: %d\n", ret); pr_info("Hard watchdog permanently disabled\n"); @@ -1044,8 +1049,7 @@ void __init lockup_detector_init(void) cpumask_copy(&watchdog_cpumask, housekeeping_cpumask(HK_TYPE_TIMER)); - if ((!disable_sdei_nmi_watchdog && !sdei_watchdog_hardlockup_probe()) || - (disable_sdei_nmi_watchdog && !watchdog_hardlockup_probe())) + if (disable_sdei_nmi_watchdog && !watchdog_hardlockup_probe()) watchdog_hardlockup_available = true; else allow_lockup_detector_init_retry = true; From be3dcf77f4f88845b08aa020864f53ec5f10b1f6 Mon Sep 17 00:00:00 2001 From: Qinxin Xia Date: Thu, 28 Aug 2025 17:05:20 +0800 Subject: [PATCH 088/107] gic: increase the arch_timer priority to avoid hardlockup commit f0f9be237c2266d30fa01c294577438a2e2ee749 openEuler The current interrupt policy of the Hip12 preferentially schedules LPI and SGI interrupts with the same priority. As a result, the arch_timer may not be executed when frequent LPI/SGI interrupts are generated, and the watchdog is starved. Signed-off-by: Qinxin Xia Signed-off-by: Hongye Lin Signed-off-by: WangYuli --- arch/arm64/Kconfig | 11 +++++++ drivers/clocksource/arm_arch_timer.c | 44 ++++++++++++++++++++++++++++ drivers/irqchip/irq-gic-v3.c | 2 +- include/linux/irqchip/arm-gic-v3.h | 3 ++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index bf8dab5e31c45..793a90194413d 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1217,6 +1217,17 @@ config HISILICON_ERRATUM_161600802 If unsure, say Y. +config HISILICON_ERRATUM_165010801 + bool "Hisilicon erratum 165010801" + depends on ARCH_HISI + default y + help + On HIP12, when GIC receives multiple interrupts of the same priority and + different types, the interrupts are selected in the following sequence: + SPI > LPI > SGI > PPI. This scheduling rule may cause PPI starvation. + To prevent starvation from triggering system watchdog hardlockup, the + interrupt priority is explicitly increased in the arch_timer driver. + config QCOM_FALKOR_ERRATUM_1003 bool "Falkor E1003: Incorrect translation due to ASID change" default y diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c index 071b04f1ee730..c545a2fb3fbe0 100644 --- a/drivers/clocksource/arm_arch_timer.c +++ b/drivers/clocksource/arm_arch_timer.c @@ -29,6 +29,11 @@ #include #include +#ifdef CONFIG_HISILICON_ERRATUM_165010801 +#include +#include +#endif + #include #include @@ -92,6 +97,9 @@ static enum vdso_clock_mode vdso_default = VDSO_CLOCKMODE_ARCHTIMER; #else static enum vdso_clock_mode vdso_default = VDSO_CLOCKMODE_NONE; #endif /* CONFIG_GENERIC_GETTIMEOFDAY */ +#ifdef CONFIG_HISILICON_ERRATUM_165010801 +static bool prio_setup; +#endif static cpumask_t evtstrm_available = CPU_MASK_NONE; static bool evtstrm_enable __ro_after_init = IS_ENABLED(CONFIG_ARM_ARCH_TIMER_EVTSTREAM); @@ -355,6 +363,17 @@ static struct ate_acpi_oem_info hisi_161010101_oem_info[] = { }; #endif +#ifdef CONFIG_HISILICON_ERRATUM_165010801 +static struct ate_acpi_oem_info hisi_165010801_oem_info[] = { + { + .oem_id = "HISI ", + .oem_table_id = "HIP12 ", + .oem_revision = 0, + }, + { /* Sentinel indicating the end of the OEM array */ }, +}; +#endif + #ifdef CONFIG_ARM64_ERRATUM_858921 static u64 notrace arm64_858921_read_cntpct_el0(void) { @@ -485,6 +504,13 @@ static const struct arch_timer_erratum_workaround ool_workarounds[] = { .set_next_event_virt = erratum_set_next_event_virt, }, #endif +#ifdef CONFIG_HISILICON_ERRATUM_165010801 + { + .match_type = ate_match_acpi_oem_info, + .id = hisi_165010801_oem_info, + .desc = "HiSilicon erratum 165010801", + }, +#endif #ifdef CONFIG_ARM64_ERRATUM_858921 { .match_type = ate_match_local_cap_id, @@ -592,6 +618,12 @@ void arch_timer_enable_workaround(const struct arch_timer_erratum_workaround *wa if (wa->read_cntvct_el0 || wa->read_cntpct_el0) atomic_set(&timer_unstable_counter_workaround_in_use, 1); +#ifdef CONFIG_HISILICON_ERRATUM_165010801 + if (!strncmp(wa->desc, "HiSilicon erratum 165010801", + strlen("HiSilicon erratum 165010801"))) + prio_setup = true; +#endif + /* * Don't use the vdso fastpath if errata require using the * out-of-line counter accessor. We may change our mind pretty @@ -1007,6 +1039,18 @@ static int arch_timer_starting_cpu(unsigned int cpu) __arch_timer_setup(ARCH_TIMER_TYPE_CP15, clk); +#ifdef CONFIG_HISILICON_ERRATUM_165010801 + if (prio_setup && is_kernel_in_hyp_mode()) { + struct irq_data *d = irq_get_irq_data(arch_timer_ppi[arch_timer_uses_ppi]); + + if (!d) + pr_warn("WARNING: Invalid arch_timer ppi irq: %d!\n", + arch_timer_ppi[arch_timer_uses_ppi]); + else + gic_irq_set_prio(d, GICD_INT_DEF_PRI & (GICD_INT_DEF_PRI - 1)); + } +#endif + flags = check_ppi_trigger(arch_timer_ppi[arch_timer_uses_ppi]); enable_percpu_irq(arch_timer_ppi[arch_timer_uses_ppi], flags); diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 6c7943c516eb0..49db1a0057716 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -504,7 +504,7 @@ static int gic_irq_get_irqchip_state(struct irq_data *d, return 0; } -static void gic_irq_set_prio(struct irq_data *d, u8 prio) +void gic_irq_set_prio(struct irq_data *d, u8 prio) { void __iomem *base = gic_dist_base(d); u32 offset, index; diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 728691365464c..002b16d034d6b 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -656,6 +656,9 @@ static inline bool gic_enable_sre(void) return !!(val & ICC_SRE_EL1_SRE); } +#ifdef CONFIG_HISILICON_ERRATUM_165010801 +extern void gic_irq_set_prio(struct irq_data *d, u8 prio); +#endif #endif #endif From 8ae22ae2e80baa57718d923eb05eb77b1ff86d97 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 21 Nov 2023 17:25:59 +0800 Subject: [PATCH 089/107] arch_topology: Support basic SMT control for the driver commit 6dd0f06a404f481ea58fb04b3006287dc3c5aea3 openEuler The core CPU control framework supports runtime SMT control which is not yet supported by arch_topology driver and thus arch_topology based architectures. This patch implements it in the following aspects: - implement topology_is_primary_thread() to indicate the primary thread, required by the framework - architecture code can get/set the SMT thread number by topology_smt_{get, set}_num_threads() - update the SMT thread number for the framework after the topology enumerated on arm64, which is also required by the framework For disabling SMT we'll offline all the secondary threads and only leave the primary thread. Since we don't have restriction for primary thread selection, the first thread is chosen as the primary thread in this implementation. This patch only implements the basic support for SMT control, which needs to collabrate with ACPI/OF based topology building to fully enable the feature. The SMT control will be enabled unless the correct SMT thread number is set and HOTPLUG_SMT kconfig is selected. Signed-off-by: Yicong Yang Signed-off-by: Jie Liu Signed-off-by: huwentao Signed-off-by: WangYuli --- drivers/base/arch_topology.c | 38 +++++++++++++++++++++++++++++++++++ include/linux/arch_topology.h | 14 +++++++++++++ 2 files changed, 52 insertions(+) diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c index 3a14ed36eb92d..d3fcdd51deed3 100644 --- a/drivers/base/arch_topology.c +++ b/drivers/base/arch_topology.c @@ -750,6 +750,36 @@ const struct cpumask *cpu_clustergroup_mask(int cpu) return &cpu_topology[cpu].cluster_sibling; } +#ifdef CONFIG_HOTPLUG_SMT + +/* Maximum threads number per-Core */ +static unsigned int topology_smt_num_threads = 1; + +void __init topology_smt_set_num_threads(unsigned int num_threads) +{ + topology_smt_num_threads = num_threads; +} + +unsigned int __init topology_smt_get_num_threads(void) +{ + return topology_smt_num_threads; +} + +/* + * On SMT Hotplug the primary thread of the SMT won't be disabled. For x86 they + * seem to have a primary thread for special purpose. For other arthitectures + * like arm64 there's no such restriction for a primary thread, so make the + * first thread in the SMT as the primary thread. + */ +bool topology_is_primary_thread(unsigned int cpu) +{ + if (cpu == cpumask_first(topology_sibling_cpumask(cpu))) + return true; + + return false; +} +#endif + void update_siblings_masks(unsigned int cpuid) { struct cpu_topology *cpu_topo, *cpuid_topo = &cpu_topology[cpuid]; @@ -862,6 +892,14 @@ void __init init_cpu_topology(void) reset_cpu_topology(); } + /* + * By this stage we get to know whether we support SMT or not, update + * the information for the core. We don't support + * CONFIG_SMT_NUM_THREADS_DYNAMIC so make the max_threads == num_threads. + */ + cpu_smt_set_num_threads(topology_smt_get_num_threads(), + topology_smt_get_num_threads()); + for_each_possible_cpu(cpu) { ret = fetch_cache_info(cpu); if (!ret) diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h index a63d61ca55afc..da0fb19c21b1e 100644 --- a/include/linux/arch_topology.h +++ b/include/linux/arch_topology.h @@ -100,6 +100,20 @@ void remove_cpu_topology(unsigned int cpuid); void reset_cpu_topology(void); int parse_acpi_topology(void); void freq_inv_set_max_ratio(int cpu, u64 max_rate); + +#ifdef CONFIG_HOTPLUG_SMT +bool topology_is_primary_thread(unsigned int cpu); +void topology_smt_set_num_threads(unsigned int num_threads); +unsigned int topology_smt_get_num_threads(void); +#else +static inline bool topology_is_primary_thread(unsigned int cpu) { return false; } +static inline void topology_smt_set_num_threads(unsigned int num_threads) { } +static inline unsigned int topology_smt_get_num_threads(void) +{ + return 1; +} +#endif + #endif #endif /* _LINUX_ARCH_TOPOLOGY_H_ */ From 6328ae9c196993b3824ffb824c11d2657f3459bc Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 21 Nov 2023 17:26:00 +0800 Subject: [PATCH 090/107] arch_topology: Support SMT control for OF based system [Upstream commit 5deb9c789ae468a71a7c11c92d21769f7cbf68fa] On building the topology from the devicetree, we've already gotten the SMT thread number of each core. Update the largest SMT thread number to enable the SMT control. Signed-off-by: Yicong Yang Signed-off-by: Jie Liu Signed-off-by: huwentao Signed-off-by: WangYuli --- drivers/base/arch_topology.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c index d3fcdd51deed3..471c7f60b8227 100644 --- a/drivers/base/arch_topology.c +++ b/drivers/base/arch_topology.c @@ -547,6 +547,13 @@ static int __init parse_core(struct device_node *core, int package_id, i++; } while (t); + /* + * We've already gotten threads number in this core, update the SMT + * threads number when necessary. + */ + if (i > topology_smt_get_num_threads()) + topology_smt_set_num_threads(i); + cpu = get_cpu_for_node(core); if (cpu >= 0) { if (!leaf) { From 82e5242770c4a87ea133f50fd1e576880535c8f3 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 21 Nov 2023 17:26:01 +0800 Subject: [PATCH 091/107] arm64: topology: Support SMT control on ACPI based system [Upstream commit e6b18ebfaf6360a357455507ae3e965989461c71] For ACPI we'll build the topology from PPTT and we cannot directly get the SMT number of each core. Instead using a temporary xarray to record the SMT number of each core when building the topology and we can know the largest SMT number in the system. Then we can notify the arch_topology for supporting SMT control. Signed-off-by: Yicong Yang Signed-off-by: Jie Liu Signed-off-by: huwentao Signed-off-by: WangYuli --- arch/arm64/kernel/topology.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 1a2c72f3e7f80..dc92ac21f08b1 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -43,11 +44,16 @@ static bool __init acpi_cpu_is_threaded(int cpu) */ int __init parse_acpi_topology(void) { + int thread_num, max_smt_thread_num = 1; + struct xarray core_threads; int cpu, topology_id; + void *entry; if (acpi_disabled) return 0; + xa_init(&core_threads); + for_each_possible_cpu(cpu) { topology_id = find_acpi_cpu_topology(cpu, 0); if (topology_id < 0) @@ -57,6 +63,20 @@ int __init parse_acpi_topology(void) cpu_topology[cpu].thread_id = topology_id; topology_id = find_acpi_cpu_topology(cpu, 1); cpu_topology[cpu].core_id = topology_id; + + entry = xa_load(&core_threads, topology_id); + if (!entry) { + xa_store(&core_threads, topology_id, + xa_mk_value(1), GFP_KERNEL); + } else { + thread_num = xa_to_value(entry); + thread_num++; + xa_store(&core_threads, topology_id, + xa_mk_value(thread_num), GFP_KERNEL); + + if (thread_num > max_smt_thread_num) + max_smt_thread_num = thread_num; + } } else { cpu_topology[cpu].thread_id = -1; cpu_topology[cpu].core_id = topology_id; @@ -67,6 +87,9 @@ int __init parse_acpi_topology(void) cpu_topology[cpu].package_id = topology_id; } + topology_smt_set_num_threads(max_smt_thread_num); + + xa_destroy(&core_threads); return 0; } #endif From e632e74e6ee329e15c80ed686181cf7f63ef5507 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 21 Nov 2023 17:26:02 +0800 Subject: [PATCH 092/107] arm64: Kconfig: Enable HOTPLUG_SMT [Upstream commit eed4583bcf9a60f8d6dd3a3c7c94dea28134b1eb] Enable HOTPLUG_SMT for SMT control. Signed-off-by: Yicong Yang Signed-off-by: Jie Liu Signed-off-by: huwentao Signed-off-by: WangYuli --- arch/arm64/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 793a90194413d..502ffb294d914 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -235,6 +235,7 @@ config ARM64 select HAVE_KRETPROBES select HAVE_GENERIC_VDSO select HOTPLUG_CORE_SYNC_DEAD if HOTPLUG_CPU + select HOTPLUG_SMT if (SMP && HOTPLUG_CPU) select IRQ_DOMAIN select IRQ_FORCED_THREADING select KASAN_VMALLOC if KASAN From 992fc3a82d750ab131f2569bc9c40bad1b2e3496 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 19 Sep 2024 14:25:06 +0800 Subject: [PATCH 093/107] drivers: perf: arm_pmuv3: Read PMMIR_EL1 unconditionally [Upstream commit 58f8fc57b1d314b5402c374a8c454ac7c870574c] Currently the PMUv3 driver only reads PMMIR_EL1 if the PMU implements FEAT_PMUv3p4 and the STALL_SLOT event, but the check for STALL_SLOT event isn't necessary and can be removed. The check for STALL_SLOT event was introduced with the read of PMMIR_EL1 in commit f5be3a61fdb5dd11 ("arm64: perf: Add support caps under sysfs") When this logic was written, the ARM ARM said: | If STALL_SLOT is not implemented, it is IMPLEMENTATION DEFINED whether | the PMMIR System registers are implemented. ... and thus the driver had to check for STALL_SLOT event to verify that PMMIR_EL1 was implemented and accesses to PMMIR_EL1 would not be UNDEFINED. Subsequently, the architecture was retrospectively tightened to require that any FEAT_PMUv3p4 implementation implements PMMIR_EL1. Since the G.b release of the ARM ARM, the wording regarding STALL_SLOT event has been removed, and the description of PMMIR_EL1 says: | This register is present only when FEAT_PMUv3p4 is implemented. Drop the unnecessary check for STALL_SLOT event when reading PMMIR_EL1. Cc: Will Deacon Cc: Mark Rutland Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Link: https://lore.kernel.org/r/20231013024354.1289070-1-anshuman.khandual@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 8249f1f0b4b59..c4bfcc8be3d49 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -1130,7 +1130,7 @@ static void __armv8pmu_probe_pmu(void *info) pmceid, ARMV8_PMUV3_MAX_COMMON_EVENTS); /* store PMMIR register for sysfs */ - if (is_pmuv3p4(pmuver) && (pmceid_raw[1] & BIT(31))) + if (is_pmuv3p4(pmuver)) cpu_pmu->reg_pmmir = read_pmmir(); else cpu_pmu->reg_pmmir = 0; From d02ad126027d6de61a8e12160cdb682260ddf5a8 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 19 Sep 2024 14:25:07 +0800 Subject: [PATCH 094/107] drivers: perf: arm_pmuv3: Drop some unused arguments from armv8_pmu_init() [Upstream commit 3b9a22d345ff89232227b2449a311bf3f910f5f2] All the PMU init functions want the default sysfs attribute groups, and so these all call armv8_pmu_init_nogroups() helper, with none of them calling armv8_pmu_init() directly. When we introduced armv8_pmu_init_nogroups() in the commit e424b1798526 ("arm64: perf: Refactor PMU init callbacks") ... we thought that we might need custom attribute groups in future, but as we evidently haven't, we can remove the option. This patch folds armv8_pmu_init_nogroups() into armv8_pmu_init(), removing the ability to use custom attribute groups and simplifying the code. CC: James Clark Cc: Robin Murphy Cc: Will Deacon Cc: Mark Rutland Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Acked-by: Mark Rutland Signed-off-by: Anshuman Khandual Link: https://lore.kernel.org/r/20231016025436.1368945-1-anshuman.khandual@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 44 +++++++++++----------------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index c4bfcc8be3d49..07d759e891d9e 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -1191,10 +1191,7 @@ static void armv8_pmu_register_sysctl_table(void) } static int armv8_pmu_init(struct arm_pmu *cpu_pmu, char *name, - int (*map_event)(struct perf_event *event), - const struct attribute_group *events, - const struct attribute_group *format, - const struct attribute_group *caps) + int (*map_event)(struct perf_event *event)) { int ret = armv8pmu_probe_pmu(cpu_pmu); if (ret) @@ -1216,27 +1213,17 @@ static int armv8_pmu_init(struct arm_pmu *cpu_pmu, char *name, cpu_pmu->name = name; cpu_pmu->map_event = map_event; - cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS] = events ? - events : &armv8_pmuv3_events_attr_group; - cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_FORMATS] = format ? - format : &armv8_pmuv3_format_attr_group; - cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_CAPS] = caps ? - caps : &armv8_pmuv3_caps_attr_group; - + cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS] = &armv8_pmuv3_events_attr_group; + cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_FORMATS] = &armv8_pmuv3_format_attr_group; + cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_CAPS] = &armv8_pmuv3_caps_attr_group; armv8_pmu_register_sysctl_table(); return 0; } -static int armv8_pmu_init_nogroups(struct arm_pmu *cpu_pmu, char *name, - int (*map_event)(struct perf_event *event)) -{ - return armv8_pmu_init(cpu_pmu, name, map_event, NULL, NULL, NULL); -} - #define PMUV3_INIT_SIMPLE(name) \ static int name##_pmu_init(struct arm_pmu *cpu_pmu) \ { \ - return armv8_pmu_init_nogroups(cpu_pmu, #name, armv8_pmuv3_map_event);\ + return armv8_pmu_init(cpu_pmu, #name, armv8_pmuv3_map_event); \ } PMUV3_INIT_SIMPLE(armv8_pmuv3) @@ -1267,44 +1254,37 @@ PMUV3_INIT_SIMPLE(armv8_nvidia_denver) static int armv8_a35_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a35", - armv8_a53_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_cortex_a35", armv8_a53_map_event); } static int armv8_a53_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a53", - armv8_a53_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_cortex_a53", armv8_a53_map_event); } static int armv8_a57_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a57", - armv8_a57_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_cortex_a57", armv8_a57_map_event); } static int armv8_a72_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a72", - armv8_a57_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_cortex_a72", armv8_a57_map_event); } static int armv8_a73_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cortex_a73", - armv8_a73_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_cortex_a73", armv8_a73_map_event); } static int armv8_thunder_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_cavium_thunder", - armv8_thunder_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_cavium_thunder", armv8_thunder_map_event); } static int armv8_vulcan_pmu_init(struct arm_pmu *cpu_pmu) { - return armv8_pmu_init_nogroups(cpu_pmu, "armv8_brcm_vulcan", - armv8_vulcan_map_event); + return armv8_pmu_init(cpu_pmu, "armv8_brcm_vulcan", armv8_vulcan_map_event); } static const struct of_device_id armv8_pmu_of_device_ids[] = { From 66157cd0f7d5bbce6f8b82fe8dd96f524981e75e Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 19 Sep 2024 14:25:08 +0800 Subject: [PATCH 095/107] KVM: arm64: Make PMEVTYPER_EL0.NSH RES0 if EL2 isn't advertised [Upstream commit bc512d6a9b92390760f0e8c895501c5016539929] The NSH bit, which filters event counting at EL2, is required by the architecture if an implementation has EL2. Even though KVM doesn't support nested virt yet, it makes no effort to hide the existence of EL2 from the ID registers. Userspace can, however, change the value of PFR0 to hide EL2. Align KVM's sysreg emulation with the architecture and make NSH RES0 if EL2 isn't advertised. Keep in mind the bit is ignored when constructing the backing perf event. While at it, build the event type mask using explicit field definitions instead of relying on ARMV8_PMU_EVTYPE_MASK. KVM probably should've been doing this in the first place, as it avoids changes to the aforementioned mask affecting sysreg emulation. Reviewed-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20231019185618.3442949-2-oliver.upton@linux.dev Signed-off-by: Oliver Upton Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kvm/pmu-emul.c | 21 ++++++++++++++------- arch/arm64/kvm/sys_regs.c | 8 ++++++-- include/kvm/arm_pmu.h | 5 +++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c index 3867d6d1f5d1b..da2a0c47915aa 100644 --- a/arch/arm64/kvm/pmu-emul.c +++ b/arch/arm64/kvm/pmu-emul.c @@ -60,6 +60,18 @@ static u32 kvm_pmu_event_mask(struct kvm *kvm) return __kvm_pmu_event_mask(pmuver); } +u64 kvm_pmu_evtyper_mask(struct kvm *kvm) +{ + u64 mask = ARMV8_PMU_EXCLUDE_EL1 | ARMV8_PMU_EXCLUDE_EL0 | + kvm_pmu_event_mask(kvm); + u64 pfr0 = IDREG(kvm, SYS_ID_AA64PFR0_EL1); + + if (SYS_FIELD_GET(ID_AA64PFR0_EL1, EL2, pfr0)) + mask |= ARMV8_PMU_INCLUDE_EL2; + + return mask; +} + /** * kvm_pmc_is_64bit - determine if counter is 64bit * @pmc: counter context @@ -656,18 +668,13 @@ void kvm_pmu_set_counter_event_type(struct kvm_vcpu *vcpu, u64 data, u64 select_idx) { struct kvm_pmc *pmc = kvm_vcpu_idx_to_pmc(vcpu, select_idx); - u64 reg, mask; + u64 reg; if (!kvm_vcpu_has_pmu(vcpu)) return; - mask = ARMV8_PMU_EVTYPE_MASK; - mask &= ~ARMV8_PMU_EVTYPE_EVENT; - mask |= kvm_pmu_event_mask(vcpu->kvm); - reg = counter_index_to_evtreg(pmc->idx); - - __vcpu_sys_reg(vcpu, reg) = data & mask; + __vcpu_sys_reg(vcpu, reg) = data & kvm_pmu_evtyper_mask(vcpu->kvm); kvm_pmu_create_perf_event(pmc); } diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index bfcf36c758c22..475eab52b4788 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -752,8 +752,12 @@ static u64 reset_pmevcntr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r) static u64 reset_pmevtyper(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r) { + /* This thing will UNDEF, who cares about the reset value? */ + if (!kvm_vcpu_has_pmu(vcpu)) + return 0; + reset_unknown(vcpu, r); - __vcpu_sys_reg(vcpu, r->reg) &= ARMV8_PMU_EVTYPE_MASK; + __vcpu_sys_reg(vcpu, r->reg) &= kvm_pmu_evtyper_mask(vcpu->kvm); return __vcpu_sys_reg(vcpu, r->reg); } @@ -994,7 +998,7 @@ static bool access_pmu_evtyper(struct kvm_vcpu *vcpu, struct sys_reg_params *p, kvm_pmu_set_counter_event_type(vcpu, p->regval, idx); kvm_vcpu_pmu_restore_guest(vcpu); } else { - p->regval = __vcpu_sys_reg(vcpu, reg) & ARMV8_PMU_EVTYPE_MASK; + p->regval = __vcpu_sys_reg(vcpu, reg); } return true; diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h index c4aabbf002f7c..10ec84a45df01 100644 --- a/include/kvm/arm_pmu.h +++ b/include/kvm/arm_pmu.h @@ -101,6 +101,7 @@ void kvm_vcpu_pmu_resync_el0(void); }) u8 kvm_arm_pmu_get_pmuver_limit(void); +u64 kvm_pmu_evtyper_mask(struct kvm *kvm); #else struct kvm_pmu { @@ -172,6 +173,10 @@ static inline u8 kvm_arm_pmu_get_pmuver_limit(void) { return 0; } +static inline u64 kvm_pmu_evtyper_mask(struct kvm *kvm) +{ + return 0; +} static inline void kvm_vcpu_pmu_resync_el0(void) {} #endif From a442e38700e398c52ace77b762df9bd592064e73 Mon Sep 17 00:00:00 2001 From: Oliver Upton Date: Thu, 19 Sep 2024 14:25:09 +0800 Subject: [PATCH 096/107] KVM: arm64: Add PMU event filter bits required if EL3 is implemented [Upstream commit ae8d3522e5b7208d238c893a00957864536983dc] Suzuki noticed that KVM's PMU emulation is oblivious to the NSU and NSK event filter bits. On systems that have EL3 these bits modify the filter behavior in non-secure EL0 and EL1, respectively. Even though the kernel doesn't use these bits, it is entirely possible some other guest OS does. Additionally, it would appear that these and the M bit are required by the architecture if EL3 is implemented. Allow the EL3 event filter bits to be set if EL3 is advertised in the guest's ID register. Implement the behavior of NSU and NSK according to the pseudocode, and entirely ignore the M bit for perf event creation. Reported-by: Suzuki K Poulose Reviewed-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20231019185618.3442949-3-oliver.upton@linux.dev Signed-off-by: Oliver Upton Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm64/kvm/pmu-emul.c | 15 +++++++++++++-- include/linux/perf/arm_pmuv3.h | 9 ++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c index da2a0c47915aa..165b3915b0148 100644 --- a/arch/arm64/kvm/pmu-emul.c +++ b/arch/arm64/kvm/pmu-emul.c @@ -69,6 +69,11 @@ u64 kvm_pmu_evtyper_mask(struct kvm *kvm) if (SYS_FIELD_GET(ID_AA64PFR0_EL1, EL2, pfr0)) mask |= ARMV8_PMU_INCLUDE_EL2; + if (SYS_FIELD_GET(ID_AA64PFR0_EL1, EL3, pfr0)) + mask |= ARMV8_PMU_EXCLUDE_NS_EL0 | + ARMV8_PMU_EXCLUDE_NS_EL1 | + ARMV8_PMU_EXCLUDE_EL3; + return mask; } @@ -595,6 +600,7 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc) struct perf_event *event; struct perf_event_attr attr; u64 eventsel, reg, data; + bool p, u, nsk, nsu; reg = counter_index_to_evtreg(pmc->idx); data = __vcpu_sys_reg(vcpu, reg); @@ -621,13 +627,18 @@ static void kvm_pmu_create_perf_event(struct kvm_pmc *pmc) !test_bit(eventsel, vcpu->kvm->arch.pmu_filter)) return; + p = data & ARMV8_PMU_EXCLUDE_EL1; + u = data & ARMV8_PMU_EXCLUDE_EL0; + nsk = data & ARMV8_PMU_EXCLUDE_NS_EL1; + nsu = data & ARMV8_PMU_EXCLUDE_NS_EL0; + memset(&attr, 0, sizeof(struct perf_event_attr)); attr.type = arm_pmu->pmu.type; attr.size = sizeof(attr); attr.pinned = 1; attr.disabled = !kvm_pmu_counter_is_enabled(pmc); - attr.exclude_user = data & ARMV8_PMU_EXCLUDE_EL0 ? 1 : 0; - attr.exclude_kernel = data & ARMV8_PMU_EXCLUDE_EL1 ? 1 : 0; + attr.exclude_user = (u != nsu); + attr.exclude_kernel = (p != nsk); attr.exclude_hv = 1; /* Don't count EL2 events */ attr.exclude_host = 1; /* Don't count host events */ attr.config = eventsel; diff --git a/include/linux/perf/arm_pmuv3.h b/include/linux/perf/arm_pmuv3.h index e3899bd77f5cc..9c226adf938a2 100644 --- a/include/linux/perf/arm_pmuv3.h +++ b/include/linux/perf/arm_pmuv3.h @@ -234,9 +234,12 @@ /* * Event filters for PMUv3 */ -#define ARMV8_PMU_EXCLUDE_EL1 (1U << 31) -#define ARMV8_PMU_EXCLUDE_EL0 (1U << 30) -#define ARMV8_PMU_INCLUDE_EL2 (1U << 27) +#define ARMV8_PMU_EXCLUDE_EL1 (1U << 31) +#define ARMV8_PMU_EXCLUDE_EL0 (1U << 30) +#define ARMV8_PMU_EXCLUDE_NS_EL1 (1U << 29) +#define ARMV8_PMU_EXCLUDE_NS_EL0 (1U << 28) +#define ARMV8_PMU_INCLUDE_EL2 (1U << 27) +#define ARMV8_PMU_EXCLUDE_EL3 (1U << 26) /* * PMUSERENR: user enable reg From 93a7043dc1d791c78fcc87dd5045adaccc2d64f9 Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 19 Sep 2024 14:25:11 +0800 Subject: [PATCH 097/107] drivers: perf: arm_pmuv3: Add new macro PMUV3_INIT_MAP_EVENT() [Upstream commit 877806b9b41ea371defaaf58d815320f1a76384f] This further compacts all remaining PMU init procedures requiring specific map_event functions via a new macro PMUV3_INIT_MAP_EVENT(). While here, it also changes generated init function names to match to those generated via the other macro PMUV3_INIT_SIMPLE(). This does not cause functional change. Cc: Will Deacon Cc: Mark Rutland Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Reviewed-by: James Clark Signed-off-by: Anshuman Khandual Link: https://lore.kernel.org/r/20231114061656.337231-1-anshuman.khandual@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 61 +++++++++++++--------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 07d759e891d9e..dc440e600ccc5 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -1226,6 +1226,12 @@ static int name##_pmu_init(struct arm_pmu *cpu_pmu) \ return armv8_pmu_init(cpu_pmu, #name, armv8_pmuv3_map_event); \ } +#define PMUV3_INIT_MAP_EVENT(name, map_event) \ +static int name##_pmu_init(struct arm_pmu *cpu_pmu) \ +{ \ + return armv8_pmu_init(cpu_pmu, #name, map_event); \ +} + PMUV3_INIT_SIMPLE(armv8_pmuv3) PMUV3_INIT_SIMPLE(armv8_cortex_a34) @@ -1252,51 +1258,24 @@ PMUV3_INIT_SIMPLE(armv8_neoverse_v1) PMUV3_INIT_SIMPLE(armv8_nvidia_carmel) PMUV3_INIT_SIMPLE(armv8_nvidia_denver) -static int armv8_a35_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_cortex_a35", armv8_a53_map_event); -} - -static int armv8_a53_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_cortex_a53", armv8_a53_map_event); -} - -static int armv8_a57_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_cortex_a57", armv8_a57_map_event); -} - -static int armv8_a72_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_cortex_a72", armv8_a57_map_event); -} - -static int armv8_a73_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_cortex_a73", armv8_a73_map_event); -} - -static int armv8_thunder_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_cavium_thunder", armv8_thunder_map_event); -} - -static int armv8_vulcan_pmu_init(struct arm_pmu *cpu_pmu) -{ - return armv8_pmu_init(cpu_pmu, "armv8_brcm_vulcan", armv8_vulcan_map_event); -} +PMUV3_INIT_MAP_EVENT(armv8_cortex_a35, armv8_a53_map_event) +PMUV3_INIT_MAP_EVENT(armv8_cortex_a53, armv8_a53_map_event) +PMUV3_INIT_MAP_EVENT(armv8_cortex_a57, armv8_a57_map_event) +PMUV3_INIT_MAP_EVENT(armv8_cortex_a72, armv8_a57_map_event) +PMUV3_INIT_MAP_EVENT(armv8_cortex_a73, armv8_a73_map_event) +PMUV3_INIT_MAP_EVENT(armv8_cavium_thunder, armv8_thunder_map_event) +PMUV3_INIT_MAP_EVENT(armv8_brcm_vulcan, armv8_vulcan_map_event) static const struct of_device_id armv8_pmu_of_device_ids[] = { {.compatible = "arm,armv8-pmuv3", .data = armv8_pmuv3_pmu_init}, {.compatible = "arm,cortex-a34-pmu", .data = armv8_cortex_a34_pmu_init}, - {.compatible = "arm,cortex-a35-pmu", .data = armv8_a35_pmu_init}, - {.compatible = "arm,cortex-a53-pmu", .data = armv8_a53_pmu_init}, + {.compatible = "arm,cortex-a35-pmu", .data = armv8_cortex_a35_pmu_init}, + {.compatible = "arm,cortex-a53-pmu", .data = armv8_cortex_a53_pmu_init}, {.compatible = "arm,cortex-a55-pmu", .data = armv8_cortex_a55_pmu_init}, - {.compatible = "arm,cortex-a57-pmu", .data = armv8_a57_pmu_init}, + {.compatible = "arm,cortex-a57-pmu", .data = armv8_cortex_a57_pmu_init}, {.compatible = "arm,cortex-a65-pmu", .data = armv8_cortex_a65_pmu_init}, - {.compatible = "arm,cortex-a72-pmu", .data = armv8_a72_pmu_init}, - {.compatible = "arm,cortex-a73-pmu", .data = armv8_a73_pmu_init}, + {.compatible = "arm,cortex-a72-pmu", .data = armv8_cortex_a72_pmu_init}, + {.compatible = "arm,cortex-a73-pmu", .data = armv8_cortex_a73_pmu_init}, {.compatible = "arm,cortex-a75-pmu", .data = armv8_cortex_a75_pmu_init}, {.compatible = "arm,cortex-a76-pmu", .data = armv8_cortex_a76_pmu_init}, {.compatible = "arm,cortex-a77-pmu", .data = armv8_cortex_a77_pmu_init}, @@ -1314,8 +1293,8 @@ static const struct of_device_id armv8_pmu_of_device_ids[] = { {.compatible = "arm,neoverse-n1-pmu", .data = armv8_neoverse_n1_pmu_init}, {.compatible = "arm,neoverse-n2-pmu", .data = armv9_neoverse_n2_pmu_init}, {.compatible = "arm,neoverse-v1-pmu", .data = armv8_neoverse_v1_pmu_init}, - {.compatible = "cavium,thunder-pmu", .data = armv8_thunder_pmu_init}, - {.compatible = "brcm,vulcan-pmu", .data = armv8_vulcan_pmu_init}, + {.compatible = "cavium,thunder-pmu", .data = armv8_cavium_thunder_pmu_init}, + {.compatible = "brcm,vulcan-pmu", .data = armv8_brcm_vulcan_pmu_init}, {.compatible = "nvidia,carmel-pmu", .data = armv8_nvidia_carmel_pmu_init}, {.compatible = "nvidia,denver-pmu", .data = armv8_nvidia_denver_pmu_init}, {}, From 735a385c0d81ecbb4866c6f5f43427e55f71eaa8 Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:12 +0800 Subject: [PATCH 098/107] arm: perf: Remove inlines from arm_pmuv3.c [Upstream commit 9343c790e6de7edd2bab17572832f4f21c748dc2] These are all static and in one compilation unit so the inline has no effect on the binary. Except if FTRACE is enabled, then 3 functions which were already not inlined now get the nops added which allows them to be traced. Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-2-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index dc440e600ccc5..d2c676035d14a 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -304,12 +304,12 @@ PMU_FORMAT_ATTR(rdpmc, "config1:1"); static int sysctl_perf_user_access __read_mostly; -static inline bool armv8pmu_event_is_64bit(struct perf_event *event) +static bool armv8pmu_event_is_64bit(struct perf_event *event) { return event->attr.config1 & 0x1; } -static inline bool armv8pmu_event_want_user_access(struct perf_event *event) +static bool armv8pmu_event_want_user_access(struct perf_event *event) { return event->attr.config1 & 0x2; } @@ -401,7 +401,7 @@ static bool armv8pmu_has_long_event(struct arm_pmu *cpu_pmu) return (IS_ENABLED(CONFIG_ARM64) && is_pmuv3p5(cpu_pmu->pmuver)); } -static inline bool armv8pmu_event_has_user_read(struct perf_event *event) +static bool armv8pmu_event_has_user_read(struct perf_event *event) { return event->hw.flags & PERF_EVENT_FLAG_USER_READ_CNT; } @@ -411,7 +411,7 @@ static inline bool armv8pmu_event_has_user_read(struct perf_event *event) * except when we have allocated the 64bit cycle counter (for CPU * cycles event) or when user space counter access is enabled. */ -static inline bool armv8pmu_event_is_chained(struct perf_event *event) +static bool armv8pmu_event_is_chained(struct perf_event *event) { int idx = event->hw.idx; struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu); @@ -432,36 +432,36 @@ static inline bool armv8pmu_event_is_chained(struct perf_event *event) #define ARMV8_IDX_TO_COUNTER(x) \ (((x) - ARMV8_IDX_COUNTER0) & ARMV8_PMU_COUNTER_MASK) -static inline u64 armv8pmu_pmcr_read(void) +static u64 armv8pmu_pmcr_read(void) { return read_pmcr(); } -static inline void armv8pmu_pmcr_write(u64 val) +static void armv8pmu_pmcr_write(u64 val) { val &= ARMV8_PMU_PMCR_MASK; isb(); write_pmcr(val); } -static inline int armv8pmu_has_overflowed(u32 pmovsr) +static int armv8pmu_has_overflowed(u32 pmovsr) { return pmovsr & ARMV8_PMU_OVERFLOWED_MASK; } -static inline int armv8pmu_counter_has_overflowed(u32 pmnc, int idx) +static int armv8pmu_counter_has_overflowed(u32 pmnc, int idx) { return pmnc & BIT(ARMV8_IDX_TO_COUNTER(idx)); } -static inline u64 armv8pmu_read_evcntr(int idx) +static u64 armv8pmu_read_evcntr(int idx) { u32 counter = ARMV8_IDX_TO_COUNTER(idx); return read_pmevcntrn(counter); } -static inline u64 armv8pmu_read_hw_counter(struct perf_event *event) +static u64 armv8pmu_read_hw_counter(struct perf_event *event) { int idx = event->hw.idx; u64 val = armv8pmu_read_evcntr(idx); @@ -523,14 +523,14 @@ static u64 armv8pmu_read_counter(struct perf_event *event) return armv8pmu_unbias_long_counter(event, value); } -static inline void armv8pmu_write_evcntr(int idx, u64 value) +static void armv8pmu_write_evcntr(int idx, u64 value) { u32 counter = ARMV8_IDX_TO_COUNTER(idx); write_pmevcntrn(counter, value); } -static inline void armv8pmu_write_hw_counter(struct perf_event *event, +static void armv8pmu_write_hw_counter(struct perf_event *event, u64 value) { int idx = event->hw.idx; @@ -556,7 +556,7 @@ static void armv8pmu_write_counter(struct perf_event *event, u64 value) armv8pmu_write_hw_counter(event, value); } -static inline void armv8pmu_write_evtype(int idx, u32 val) +static void armv8pmu_write_evtype(int idx, u32 val) { u32 counter = ARMV8_IDX_TO_COUNTER(idx); @@ -564,7 +564,7 @@ static inline void armv8pmu_write_evtype(int idx, u32 val) write_pmevtypern(counter, val); } -static inline void armv8pmu_write_event_type(struct perf_event *event) +static void armv8pmu_write_event_type(struct perf_event *event) { struct hw_perf_event *hwc = &event->hw; int idx = hwc->idx; @@ -598,7 +598,7 @@ static u32 armv8pmu_event_cnten_mask(struct perf_event *event) return mask; } -static inline void armv8pmu_enable_counter(u32 mask) +static void armv8pmu_enable_counter(u32 mask) { /* * Make sure event configuration register writes are visible before we @@ -608,7 +608,7 @@ static inline void armv8pmu_enable_counter(u32 mask) write_pmcntenset(mask); } -static inline void armv8pmu_enable_event_counter(struct perf_event *event) +static void armv8pmu_enable_event_counter(struct perf_event *event) { struct perf_event_attr *attr = &event->attr; u32 mask = armv8pmu_event_cnten_mask(event); @@ -620,7 +620,7 @@ static inline void armv8pmu_enable_event_counter(struct perf_event *event) armv8pmu_enable_counter(mask); } -static inline void armv8pmu_disable_counter(u32 mask) +static void armv8pmu_disable_counter(u32 mask) { write_pmcntenclr(mask); /* @@ -630,7 +630,7 @@ static inline void armv8pmu_disable_counter(u32 mask) isb(); } -static inline void armv8pmu_disable_event_counter(struct perf_event *event) +static void armv8pmu_disable_event_counter(struct perf_event *event) { struct perf_event_attr *attr = &event->attr; u32 mask = armv8pmu_event_cnten_mask(event); @@ -642,18 +642,18 @@ static inline void armv8pmu_disable_event_counter(struct perf_event *event) armv8pmu_disable_counter(mask); } -static inline void armv8pmu_enable_intens(u32 mask) +static void armv8pmu_enable_intens(u32 mask) { write_pmintenset(mask); } -static inline void armv8pmu_enable_event_irq(struct perf_event *event) +static void armv8pmu_enable_event_irq(struct perf_event *event) { u32 counter = ARMV8_IDX_TO_COUNTER(event->hw.idx); armv8pmu_enable_intens(BIT(counter)); } -static inline void armv8pmu_disable_intens(u32 mask) +static void armv8pmu_disable_intens(u32 mask) { write_pmintenclr(mask); isb(); @@ -662,13 +662,13 @@ static inline void armv8pmu_disable_intens(u32 mask) isb(); } -static inline void armv8pmu_disable_event_irq(struct perf_event *event) +static void armv8pmu_disable_event_irq(struct perf_event *event) { u32 counter = ARMV8_IDX_TO_COUNTER(event->hw.idx); armv8pmu_disable_intens(BIT(counter)); } -static inline u32 armv8pmu_getreset_flags(void) +static u32 armv8pmu_getreset_flags(void) { u32 value; From c9c2bd72bc482c8900827e14ca2192e0612fb5b8 Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:14 +0800 Subject: [PATCH 099/107] arm: perf: Use GENMASK for PMMIR fields [Upstream commit 2f6a00f30600417ee2737f2b1229c75663f1e3c9] This is so that FIELD_GET and FIELD_PREP can be used and that the fields are in a consistent format to arm64/tools/sysreg Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-4-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 8 +++----- include/linux/perf/arm_pmuv3.h | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index d2c676035d14a..5cdabac74c2ba 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -331,7 +331,7 @@ static ssize_t slots_show(struct device *dev, struct device_attribute *attr, { struct pmu *pmu = dev_get_drvdata(dev); struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); - u32 slots = cpu_pmu->reg_pmmir & ARMV8_PMU_SLOTS_MASK; + u32 slots = FIELD_GET(ARMV8_PMU_SLOTS, cpu_pmu->reg_pmmir); return sysfs_emit(page, "0x%08x\n", slots); } @@ -343,8 +343,7 @@ static ssize_t bus_slots_show(struct device *dev, struct device_attribute *attr, { struct pmu *pmu = dev_get_drvdata(dev); struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); - u32 bus_slots = (cpu_pmu->reg_pmmir >> ARMV8_PMU_BUS_SLOTS_SHIFT) - & ARMV8_PMU_BUS_SLOTS_MASK; + u32 bus_slots = FIELD_GET(ARMV8_PMU_BUS_SLOTS, cpu_pmu->reg_pmmir); return sysfs_emit(page, "0x%08x\n", bus_slots); } @@ -356,8 +355,7 @@ static ssize_t bus_width_show(struct device *dev, struct device_attribute *attr, { struct pmu *pmu = dev_get_drvdata(dev); struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); - u32 bus_width = (cpu_pmu->reg_pmmir >> ARMV8_PMU_BUS_WIDTH_SHIFT) - & ARMV8_PMU_BUS_WIDTH_MASK; + u32 bus_width = FIELD_GET(ARMV8_PMU_BUS_WIDTH, cpu_pmu->reg_pmmir); u32 val = 0; /* Encoded as Log2(number of bytes), plus one */ diff --git a/include/linux/perf/arm_pmuv3.h b/include/linux/perf/arm_pmuv3.h index 9c226adf938a2..a7fb84d9bccec 100644 --- a/include/linux/perf/arm_pmuv3.h +++ b/include/linux/perf/arm_pmuv3.h @@ -251,12 +251,9 @@ #define ARMV8_PMU_USERENR_ER (1 << 3) /* Event counter can be read at EL0 */ /* PMMIR_EL1.SLOTS mask */ -#define ARMV8_PMU_SLOTS_MASK 0xff - -#define ARMV8_PMU_BUS_SLOTS_SHIFT 8 -#define ARMV8_PMU_BUS_SLOTS_MASK 0xff -#define ARMV8_PMU_BUS_WIDTH_SHIFT 16 -#define ARMV8_PMU_BUS_WIDTH_MASK 0xf +#define ARMV8_PMU_SLOTS GENMASK(7, 0) +#define ARMV8_PMU_BUS_SLOTS GENMASK(15, 8) +#define ARMV8_PMU_BUS_WIDTH GENMASK(19, 16) /* * This code is really good From d5fb27150360177da1de6c8ba752bfa34f4c662e Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:15 +0800 Subject: [PATCH 100/107] arm: perf: Convert remaining fields to use GENMASK [Upstream commit d30f09b6d7de5d159dbb537f9d67dceb67409420] Convert the remaining fields to use either GENMASK or be built from other fields. These all already started at bit 0 so don't need a code change for the lack of _SHIFT. Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-5-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 2 +- include/linux/perf/arm_pmuv3.h | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 5cdabac74c2ba..9806d0eaac3a1 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -674,7 +674,7 @@ static u32 armv8pmu_getreset_flags(void) value = read_pmovsclr(); /* Write to clear flags */ - value &= ARMV8_PMU_OVSR_MASK; + value &= ARMV8_PMU_OVERFLOWED_MASK; write_pmovsclr(value); return value; diff --git a/include/linux/perf/arm_pmuv3.h b/include/linux/perf/arm_pmuv3.h index a7fb84d9bccec..99f0019565f49 100644 --- a/include/linux/perf/arm_pmuv3.h +++ b/include/linux/perf/arm_pmuv3.h @@ -217,19 +217,25 @@ #define ARMV8_PMU_PMCR_LP (1 << 7) /* Long event counter enable */ #define ARMV8_PMU_PMCR_N_SHIFT 11 /* Number of counters supported */ #define ARMV8_PMU_PMCR_N_MASK 0x1f -#define ARMV8_PMU_PMCR_MASK 0xff /* Mask for writable bits */ +/* Mask for writable bits */ +#define ARMV8_PMU_PMCR_MASK (ARMV8_PMU_PMCR_E | ARMV8_PMU_PMCR_P | \ + ARMV8_PMU_PMCR_C | ARMV8_PMU_PMCR_D | \ + ARMV8_PMU_PMCR_X | ARMV8_PMU_PMCR_DP | \ + ARMV8_PMU_PMCR_LC | ARMV8_PMU_PMCR_LP) /* * PMOVSR: counters overflow flag status reg */ -#define ARMV8_PMU_OVSR_MASK 0xffffffff /* Mask for writable bits */ -#define ARMV8_PMU_OVERFLOWED_MASK ARMV8_PMU_OVSR_MASK +#define ARMV8_PMU_OVSR_P GENMASK(30, 0) +#define ARMV8_PMU_OVSR_C BIT(31) +/* Mask for writable bits is both P and C fields */ +#define ARMV8_PMU_OVERFLOWED_MASK (ARMV8_PMU_OVSR_P | ARMV8_PMU_OVSR_C) /* * PMXEVTYPER: Event selection reg */ #define ARMV8_PMU_EVTYPE_MASK 0xc800ffff /* Mask for writable bits */ -#define ARMV8_PMU_EVTYPE_EVENT 0xffff /* Mask for EVENT bits */ +#define ARMV8_PMU_EVTYPE_EVENT GENMASK(15, 0) /* Mask for EVENT bits */ /* * Event filters for PMUv3 @@ -244,11 +250,13 @@ /* * PMUSERENR: user enable reg */ -#define ARMV8_PMU_USERENR_MASK 0xf /* Mask for writable bits */ #define ARMV8_PMU_USERENR_EN (1 << 0) /* PMU regs can be accessed at EL0 */ #define ARMV8_PMU_USERENR_SW (1 << 1) /* PMSWINC can be written at EL0 */ #define ARMV8_PMU_USERENR_CR (1 << 2) /* Cycle counter can be read at EL0 */ #define ARMV8_PMU_USERENR_ER (1 << 3) /* Event counter can be read at EL0 */ +/* Mask for writable bits */ +#define ARMV8_PMU_USERENR_MASK (ARMV8_PMU_USERENR_EN | ARMV8_PMU_USERENR_SW | \ + ARMV8_PMU_USERENR_CR | ARMV8_PMU_USERENR_ER) /* PMMIR_EL1.SLOTS mask */ #define ARMV8_PMU_SLOTS GENMASK(7, 0) From 84157103da0e43b40acf63d1eb909d6315054f64 Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:16 +0800 Subject: [PATCH 101/107] arm64: perf: Include threshold control fields in PMEVTYPER mask [Upstream commit 3115ee021bfb04efde2e96507bfcc1330261a6a1] FEAT_PMUv3_TH (Armv8.8) adds two new fields to PMEVTYPER, so include them in the mask. These aren't writable on 32 bit kernels as they are in the high part of the register, so only include them for arm64. It would be difficult to do this statically in the asm header files for each platform without resulting in circular includes or #ifdefs inline in the code. For that reason the ARMV8_PMU_EVTYPE_MASK definition has been removed and the mask is constructed programmatically. Reviewed-by: Suzuki K Poulose Reviewed-by: Anshuman Khandual Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-6-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 9 ++++++++- include/linux/perf/arm_pmuv3.h | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 9806d0eaac3a1..80e3e46d1c379 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -557,8 +557,15 @@ static void armv8pmu_write_counter(struct perf_event *event, u64 value) static void armv8pmu_write_evtype(int idx, u32 val) { u32 counter = ARMV8_IDX_TO_COUNTER(idx); + unsigned long mask = ARMV8_PMU_EVTYPE_EVENT | + ARMV8_PMU_INCLUDE_EL2 | + ARMV8_PMU_EXCLUDE_EL0 | + ARMV8_PMU_EXCLUDE_EL1; - val &= ARMV8_PMU_EVTYPE_MASK; + if (IS_ENABLED(CONFIG_ARM64)) + mask |= ARMV8_PMU_EVTYPE_TC | ARMV8_PMU_EVTYPE_TH; + + val &= mask; write_pmevtypern(counter, val); } diff --git a/include/linux/perf/arm_pmuv3.h b/include/linux/perf/arm_pmuv3.h index 99f0019565f49..d8397b6206b5a 100644 --- a/include/linux/perf/arm_pmuv3.h +++ b/include/linux/perf/arm_pmuv3.h @@ -234,8 +234,9 @@ /* * PMXEVTYPER: Event selection reg */ -#define ARMV8_PMU_EVTYPE_MASK 0xc800ffff /* Mask for writable bits */ #define ARMV8_PMU_EVTYPE_EVENT GENMASK(15, 0) /* Mask for EVENT bits */ +#define ARMV8_PMU_EVTYPE_TH GENMASK(43, 32) +#define ARMV8_PMU_EVTYPE_TC GENMASK(63, 61) /* * Event filters for PMUv3 From 65d1e0f635ba624f05150150758c83a49b37c68b Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:17 +0800 Subject: [PATCH 102/107] arm: pmu: Share user ABI format mechanism with SPE [Upstream commit f6da86969a3c284466ab6080764b2ed91689f262] This mechanism makes it much easier to define and read new attributes so move it to the arm_pmu.h header so that it can be shared. At the same time update the existing format attributes to use it. GENMASK has to be changed to GENMASK_ULL because the config fields are 64 bits even on arm32 where this will also be used now. Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-7-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 21 ++++++++++++++++----- drivers/perf/arm_spe_pmu.c | 22 ---------------------- include/linux/perf/arm_pmu.h | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 80e3e46d1c379..ac61540554209 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -298,20 +298,31 @@ static const struct attribute_group armv8_pmuv3_events_attr_group = { .is_visible = armv8pmu_event_attr_is_visible, }; -PMU_FORMAT_ATTR(event, "config:0-15"); -PMU_FORMAT_ATTR(long, "config1:0"); -PMU_FORMAT_ATTR(rdpmc, "config1:1"); +/* User ABI */ +#define ATTR_CFG_FLD_event_CFG config +#define ATTR_CFG_FLD_event_LO 0 +#define ATTR_CFG_FLD_event_HI 15 +#define ATTR_CFG_FLD_long_CFG config1 +#define ATTR_CFG_FLD_long_LO 0 +#define ATTR_CFG_FLD_long_HI 0 +#define ATTR_CFG_FLD_rdpmc_CFG config1 +#define ATTR_CFG_FLD_rdpmc_LO 1 +#define ATTR_CFG_FLD_rdpmc_HI 1 + +GEN_PMU_FORMAT_ATTR(event); +GEN_PMU_FORMAT_ATTR(long); +GEN_PMU_FORMAT_ATTR(rdpmc); static int sysctl_perf_user_access __read_mostly; static bool armv8pmu_event_is_64bit(struct perf_event *event) { - return event->attr.config1 & 0x1; + return ATTR_CFG_GET_FLD(&event->attr, long); } static bool armv8pmu_event_want_user_access(struct perf_event *event) { - return event->attr.config1 & 0x2; + return ATTR_CFG_GET_FLD(&event->attr, rdpmc); } static struct attribute *armv8_pmuv3_format_attrs[] = { diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c index affa78376b6a8..8da8a640f7479 100644 --- a/drivers/perf/arm_spe_pmu.c +++ b/drivers/perf/arm_spe_pmu.c @@ -207,28 +207,6 @@ static const struct attribute_group arm_spe_pmu_cap_group = { #define ATTR_CFG_FLD_inv_event_filter_LO 0 #define ATTR_CFG_FLD_inv_event_filter_HI 63 -/* Why does everything I do descend into this? */ -#define __GEN_PMU_FORMAT_ATTR(cfg, lo, hi) \ - (lo) == (hi) ? #cfg ":" #lo "\n" : #cfg ":" #lo "-" #hi - -#define _GEN_PMU_FORMAT_ATTR(cfg, lo, hi) \ - __GEN_PMU_FORMAT_ATTR(cfg, lo, hi) - -#define GEN_PMU_FORMAT_ATTR(name) \ - PMU_FORMAT_ATTR(name, \ - _GEN_PMU_FORMAT_ATTR(ATTR_CFG_FLD_##name##_CFG, \ - ATTR_CFG_FLD_##name##_LO, \ - ATTR_CFG_FLD_##name##_HI)) - -#define _ATTR_CFG_GET_FLD(attr, cfg, lo, hi) \ - ((((attr)->cfg) >> lo) & GENMASK(hi - lo, 0)) - -#define ATTR_CFG_GET_FLD(attr, name) \ - _ATTR_CFG_GET_FLD(attr, \ - ATTR_CFG_FLD_##name##_CFG, \ - ATTR_CFG_FLD_##name##_LO, \ - ATTR_CFG_FLD_##name##_HI) - GEN_PMU_FORMAT_ATTR(ts_enable); GEN_PMU_FORMAT_ATTR(pa_enable); GEN_PMU_FORMAT_ATTR(pct_enable); diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h index 143fbc10ecfe0..337f01674b38b 100644 --- a/include/linux/perf/arm_pmu.h +++ b/include/linux/perf/arm_pmu.h @@ -189,4 +189,26 @@ void armpmu_free_irq(int irq, int cpu); #define ARMV8_SPE_PDEV_NAME "arm,spe-v1" #define ARMV8_TRBE_PDEV_NAME "arm,trbe" +/* Why does everything I do descend into this? */ +#define __GEN_PMU_FORMAT_ATTR(cfg, lo, hi) \ + (lo) == (hi) ? #cfg ":" #lo "\n" : #cfg ":" #lo "-" #hi + +#define _GEN_PMU_FORMAT_ATTR(cfg, lo, hi) \ + __GEN_PMU_FORMAT_ATTR(cfg, lo, hi) + +#define GEN_PMU_FORMAT_ATTR(name) \ + PMU_FORMAT_ATTR(name, \ + _GEN_PMU_FORMAT_ATTR(ATTR_CFG_FLD_##name##_CFG, \ + ATTR_CFG_FLD_##name##_LO, \ + ATTR_CFG_FLD_##name##_HI)) + +#define _ATTR_CFG_GET_FLD(attr, cfg, lo, hi) \ + ((((attr)->cfg) >> lo) & GENMASK_ULL(hi - lo, 0)) + +#define ATTR_CFG_GET_FLD(attr, name) \ + _ATTR_CFG_GET_FLD(attr, \ + ATTR_CFG_FLD_##name##_CFG, \ + ATTR_CFG_FLD_##name##_LO, \ + ATTR_CFG_FLD_##name##_HI) + #endif /* __ARM_PMU_H__ */ From 2c000797c0c0b899f423961c75d2311f8909e1e5 Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:19 +0800 Subject: [PATCH 103/107] arm: pmu: Move error message and -EOPNOTSUPP to individual PMUs [Upstream commit 186c91aaf54989a9c74869dcc6ba031313d8e2b8] -EPERM or -EINVAL always get converted to -EOPNOTSUPP, so replace them. This will allow __hw_perf_event_init() to return a different code or not print that particular message for a different error in the next commit. Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-10-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- arch/arm/kernel/perf_event_v7.c | 6 ++++-- drivers/perf/apple_m1_cpu_pmu.c | 6 ++++-- drivers/perf/arm_pmu.c | 11 +++++------ drivers/perf/arm_pmuv3.c | 6 ++++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/arch/arm/kernel/perf_event_v7.c b/arch/arm/kernel/perf_event_v7.c index eb2190477da10..4e115076d3235 100644 --- a/arch/arm/kernel/perf_event_v7.c +++ b/arch/arm/kernel/perf_event_v7.c @@ -1072,8 +1072,10 @@ static int armv7pmu_set_event_filter(struct hw_perf_event *event, { unsigned long config_base = 0; - if (attr->exclude_idle) - return -EPERM; + if (attr->exclude_idle) { + pr_debug("ARM performance counters do not support mode exclusion\n"); + return -EOPNOTSUPP; + } if (attr->exclude_user) config_base |= ARMV7_EXCLUDE_USER; if (attr->exclude_kernel) diff --git a/drivers/perf/apple_m1_cpu_pmu.c b/drivers/perf/apple_m1_cpu_pmu.c index cd2de44b61b91..f322e5ca1114b 100644 --- a/drivers/perf/apple_m1_cpu_pmu.c +++ b/drivers/perf/apple_m1_cpu_pmu.c @@ -524,8 +524,10 @@ static int m1_pmu_set_event_filter(struct hw_perf_event *event, { unsigned long config_base = 0; - if (!attr->exclude_guest) - return -EINVAL; + if (!attr->exclude_guest) { + pr_debug("ARM performance counters do not support mode exclusion\n"); + return -EOPNOTSUPP; + } if (!attr->exclude_kernel) config_base |= M1_PMU_CFG_COUNT_KERNEL; if (!attr->exclude_user) diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c index b3bb4d62f13dc..30d67e670603b 100644 --- a/drivers/perf/arm_pmu.c +++ b/drivers/perf/arm_pmu.c @@ -443,7 +443,7 @@ __hw_perf_event_init(struct perf_event *event) { struct arm_pmu *armpmu = to_arm_pmu(event->pmu); struct hw_perf_event *hwc = &event->hw; - int mapping; + int mapping, ret; hwc->flags = 0; mapping = armpmu->map_event(event); @@ -468,11 +468,10 @@ __hw_perf_event_init(struct perf_event *event) /* * Check whether we need to exclude the counter from certain modes. */ - if (armpmu->set_event_filter && - armpmu->set_event_filter(hwc, &event->attr)) { - pr_debug("ARM performance counters do not support " - "mode exclusion\n"); - return -EOPNOTSUPP; + if (armpmu->set_event_filter) { + ret = armpmu->set_event_filter(hwc, &event->attr); + if (ret) + return ret; } /* diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index ac61540554209..f74183eb009ca 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -935,8 +935,10 @@ static int armv8pmu_set_event_filter(struct hw_perf_event *event, { unsigned long config_base = 0; - if (attr->exclude_idle) - return -EPERM; + if (attr->exclude_idle) { + pr_debug("ARM performance counters do not support mode exclusion\n"); + return -EOPNOTSUPP; + } /* * If we're running in hyp mode, then we *are* the hypervisor. From 7c48737b3db7313e49820beab89c21ce9678cdc9 Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:20 +0800 Subject: [PATCH 104/107] arm64: perf: Add support for event counting threshold [Upstream commit 816c26754447e8b28d6c604e1f5b1d205b2586ee] FEAT_PMUv3_TH (Armv8.8) permits a PMU counter to increment only on events whose count meets a specified threshold condition. For example if PMEVTYPERn.TC (Threshold Control) is set to 0b101 (Greater than or equal, count), and the threshold is set to 2, then the PMU counter will now only increment by 1 when an event would have previously incremented the PMU counter by 2 or more on a single processor cycle. Three new Perf event config fields, 'threshold', 'threshold_compare' and 'threshold_count' have been added to control the feature. threshold_compare maps to the upper two bits of PMEVTYPERn.TC and threshold_count maps to the first bit of TC. These separate attributes have been picked rather than enumerating all the possible combinations of the TC field as in the Arm ARM. The attributes would be used on a Perf command line like this: $ perf stat -e stall_slot/threshold=2,threshold_compare=2/ A new capability for reading out the maximum supported threshold value has also been added: $ cat /sys/bus/event_source/devices/armv8_pmuv3/caps/threshold_max 0x000000ff If a threshold higher than threshold_max is provided, then an error is generated. If FEAT_PMUv3_TH isn't implemented or a 32 bit kernel is running, then threshold_max reads zero, and attempting to set a threshold value will also result in an error. The threshold is per PMU counter, and there are potentially different threshold_max values per PMU type on heterogeneous systems. Bits higher than 32 now need to be written into PMEVTYPER, so armv8pmu_write_evtype() has to be updated to take an unsigned long value rather than u32 which gives the correct behavior on both aarch32 and 64. Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-11-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 79 +++++++++++++++++++++++++++++++++- include/linux/perf/arm_pmuv3.h | 1 + 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index f74183eb009ca..79a7b7f3b6f31 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -308,10 +308,22 @@ static const struct attribute_group armv8_pmuv3_events_attr_group = { #define ATTR_CFG_FLD_rdpmc_CFG config1 #define ATTR_CFG_FLD_rdpmc_LO 1 #define ATTR_CFG_FLD_rdpmc_HI 1 +#define ATTR_CFG_FLD_threshold_count_CFG config1 /* PMEVTYPER.TC[0] */ +#define ATTR_CFG_FLD_threshold_count_LO 2 +#define ATTR_CFG_FLD_threshold_count_HI 2 +#define ATTR_CFG_FLD_threshold_compare_CFG config1 /* PMEVTYPER.TC[2:1] */ +#define ATTR_CFG_FLD_threshold_compare_LO 3 +#define ATTR_CFG_FLD_threshold_compare_HI 4 +#define ATTR_CFG_FLD_threshold_CFG config1 /* PMEVTYPER.TH */ +#define ATTR_CFG_FLD_threshold_LO 5 +#define ATTR_CFG_FLD_threshold_HI 16 GEN_PMU_FORMAT_ATTR(event); GEN_PMU_FORMAT_ATTR(long); GEN_PMU_FORMAT_ATTR(rdpmc); +GEN_PMU_FORMAT_ATTR(threshold_count); +GEN_PMU_FORMAT_ATTR(threshold_compare); +GEN_PMU_FORMAT_ATTR(threshold); static int sysctl_perf_user_access __read_mostly; @@ -325,10 +337,27 @@ static bool armv8pmu_event_want_user_access(struct perf_event *event) return ATTR_CFG_GET_FLD(&event->attr, rdpmc); } +static u8 armv8pmu_event_threshold_control(struct perf_event_attr *attr) +{ + u8 th_compare = ATTR_CFG_GET_FLD(attr, threshold_compare); + u8 th_count = ATTR_CFG_GET_FLD(attr, threshold_count); + + /* + * The count bit is always the bottom bit of the full control field, and + * the comparison is the upper two bits, but it's not explicitly + * labelled in the Arm ARM. For the Perf interface we split it into two + * fields, so reconstruct it here. + */ + return (th_compare << 1) | th_count; +} + static struct attribute *armv8_pmuv3_format_attrs[] = { &format_attr_event.attr, &format_attr_long.attr, &format_attr_rdpmc.attr, + &format_attr_threshold.attr, + &format_attr_threshold_compare.attr, + &format_attr_threshold_count.attr, NULL, }; @@ -378,10 +407,38 @@ static ssize_t bus_width_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RO(bus_width); +static u32 threshold_max(struct arm_pmu *cpu_pmu) +{ + /* + * PMMIR.THWIDTH is readable and non-zero on aarch32, but it would be + * impossible to write the threshold in the upper 32 bits of PMEVTYPER. + */ + if (IS_ENABLED(CONFIG_ARM)) + return 0; + + /* + * The largest value that can be written to PMEVTYPER_EL0.TH is + * (2 ^ PMMIR.THWIDTH) - 1. + */ + return (1 << FIELD_GET(ARMV8_PMU_THWIDTH, cpu_pmu->reg_pmmir)) - 1; +} + +static ssize_t threshold_max_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu); + + return sysfs_emit(page, "0x%08x\n", threshold_max(cpu_pmu)); +} + +static DEVICE_ATTR_RO(threshold_max); + static struct attribute *armv8_pmuv3_caps_attrs[] = { &dev_attr_slots.attr, &dev_attr_bus_slots.attr, &dev_attr_bus_width.attr, + &dev_attr_threshold_max.attr, NULL, }; @@ -565,7 +622,7 @@ static void armv8pmu_write_counter(struct perf_event *event, u64 value) armv8pmu_write_hw_counter(event, value); } -static void armv8pmu_write_evtype(int idx, u32 val) +static void armv8pmu_write_evtype(int idx, unsigned long val) { u32 counter = ARMV8_IDX_TO_COUNTER(idx); unsigned long mask = ARMV8_PMU_EVTYPE_EVENT | @@ -934,6 +991,10 @@ static int armv8pmu_set_event_filter(struct hw_perf_event *event, struct perf_event_attr *attr) { unsigned long config_base = 0; + struct perf_event *perf_event = container_of(attr, struct perf_event, + attr); + struct arm_pmu *cpu_pmu = to_arm_pmu(perf_event->pmu); + u32 th; if (attr->exclude_idle) { pr_debug("ARM performance counters do not support mode exclusion\n"); @@ -967,6 +1028,22 @@ static int armv8pmu_set_event_filter(struct hw_perf_event *event, if (attr->exclude_user) config_base |= ARMV8_PMU_EXCLUDE_EL0; + /* + * If FEAT_PMUv3_TH isn't implemented, then THWIDTH (threshold_max) will + * be 0 and will also trigger this check, preventing it from being used. + */ + th = ATTR_CFG_GET_FLD(attr, threshold); + if (th > threshold_max(cpu_pmu)) { + pr_debug("PMU event threshold exceeds max value\n"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_ARM64) && th) { + config_base |= FIELD_PREP(ARMV8_PMU_EVTYPE_TH, th); + config_base |= FIELD_PREP(ARMV8_PMU_EVTYPE_TC, + armv8pmu_event_threshold_control(attr)); + } + /* * Install the filter into config_base as this is used to * construct the event type. diff --git a/include/linux/perf/arm_pmuv3.h b/include/linux/perf/arm_pmuv3.h index d8397b6206b5a..d79f96e0340f4 100644 --- a/include/linux/perf/arm_pmuv3.h +++ b/include/linux/perf/arm_pmuv3.h @@ -263,6 +263,7 @@ #define ARMV8_PMU_SLOTS GENMASK(7, 0) #define ARMV8_PMU_BUS_SLOTS GENMASK(15, 8) #define ARMV8_PMU_BUS_WIDTH GENMASK(19, 16) +#define ARMV8_PMU_THWIDTH GENMASK(23, 20) /* * This code is really good From 4af061be0b58368036c56d411a6e8acee3d1e40f Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:21 +0800 Subject: [PATCH 105/107] Documentation: arm64: Document the PMU event counting threshold feature [Upstream commit bd690638e2c27dcea1d56376aa4bf3995d82ccfc] Add documentation for the new Perf event open parameters and the threshold_max capability file. Reviewed-by: Anshuman Khandual Reviewed-by: Suzuki K Poulose Acked-by: Namhyung Kim Signed-off-by: James Clark Link: https://lore.kernel.org/r/20231211161331.1277825-12-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- Documentation/arch/arm64/perf.rst | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Documentation/arch/arm64/perf.rst b/Documentation/arch/arm64/perf.rst index 1f87b57c23324..997fd716b82f7 100644 --- a/Documentation/arch/arm64/perf.rst +++ b/Documentation/arch/arm64/perf.rst @@ -164,3 +164,75 @@ and should be used to mask the upper bits as needed. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/arch/arm64/tests/user-events.c .. _tools/lib/perf/tests/test-evsel.c: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/perf/tests/test-evsel.c + +Event Counting Threshold +========================================== + +Overview +-------- + +FEAT_PMUv3_TH (Armv8.8) permits a PMU counter to increment only on +events whose count meets a specified threshold condition. For example if +threshold_compare is set to 2 ('Greater than or equal'), and the +threshold is set to 2, then the PMU counter will now only increment by +when an event would have previously incremented the PMU counter by 2 or +more on a single processor cycle. + +To increment by 1 after passing the threshold condition instead of the +number of events on that cycle, add the 'threshold_count' option to the +commandline. + +How-to +------ + +These are the parameters for controlling the feature: + +.. list-table:: + :header-rows: 1 + + * - Parameter + - Description + * - threshold + - Value to threshold the event by. A value of 0 means that + thresholding is disabled and the other parameters have no effect. + * - threshold_compare + - | Comparison function to use, with the following values supported: + | + | 0: Not-equal + | 1: Equals + | 2: Greater-than-or-equal + | 3: Less-than + * - threshold_count + - If this is set, count by 1 after passing the threshold condition + instead of the value of the event on this cycle. + +The threshold, threshold_compare and threshold_count values can be +provided per event, for example: + +.. code-block:: sh + + perf stat -e stall_slot/threshold=2,threshold_compare=2/ \ + -e dtlb_walk/threshold=10,threshold_compare=3,threshold_count/ + +In this example the stall_slot event will count by 2 or more on every +cycle where 2 or more stalls happen. And dtlb_walk will count by 1 on +every cycle where the number of dtlb walks were less than 10. + +The maximum supported threshold value can be read from the caps of each +PMU, for example: + +.. code-block:: sh + + cat /sys/bus/event_source/devices/armv8_pmuv3/caps/threshold_max + + 0x000000ff + +If a value higher than this is given, then opening the event will result +in an error. The highest possible maximum is 4095, as the config field +for threshold is limited to 12 bits, and the Perf tool will refuse to +parse higher values. + +If the PMU doesn't support FEAT_PMUv3_TH, then threshold_max will read +0, and attempting to set a threshold value will also result in an error. +threshold_max will also read as 0 on aarch32 guests, even if the host +is running on hardware with the feature. From d47b6abb17f12f7b6ff584816af476b895186d61 Mon Sep 17 00:00:00 2001 From: James Clark Date: Thu, 19 Sep 2024 14:25:23 +0800 Subject: [PATCH 106/107] arm: perf: Fix ARCH=arm build with GCC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Upstream commit bb339db4d363c84e0a8d70827df591397ccd7312] LLVM ignores everything inside the if statement and doesn't generate errors, but GCC doesn't ignore it, resulting in the following error: drivers/perf/arm_pmuv3.c: In function ‘armv8pmu_write_evtype’: include/linux/bits.h:34:29: error: left shift count >= width of type [-Werror=shift-count-overflow] 34 | (((~UL(0)) - (UL(1) << (l)) + 1) & \ Fix it by using GENMASK_ULL which doesn't overflow on arm32 (even though the value is never used there). Fixes: 3115ee021bfb ("arm64: perf: Include threshold control fields in PMEVTYPER mask") Reported-by: Uwe Kleine-König Closes: https://lore.kernel.org/linux-arm-kernel/20231215120817.h2f3akgv72zhrtqo@pengutronix.de/ Signed-off-by: James Clark Acked-by: Mark Rutland Reviewed-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20231215175648.3397170-2-james.clark@arm.com Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- include/linux/perf/arm_pmuv3.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/perf/arm_pmuv3.h b/include/linux/perf/arm_pmuv3.h index d79f96e0340f4..e251da1603590 100644 --- a/include/linux/perf/arm_pmuv3.h +++ b/include/linux/perf/arm_pmuv3.h @@ -235,8 +235,8 @@ * PMXEVTYPER: Event selection reg */ #define ARMV8_PMU_EVTYPE_EVENT GENMASK(15, 0) /* Mask for EVENT bits */ -#define ARMV8_PMU_EVTYPE_TH GENMASK(43, 32) -#define ARMV8_PMU_EVTYPE_TC GENMASK(63, 61) +#define ARMV8_PMU_EVTYPE_TH GENMASK_ULL(43, 32) /* arm64 only */ +#define ARMV8_PMU_EVTYPE_TC GENMASK_ULL(63, 61) /* arm64 only */ /* * Event filters for PMUv3 From 74a2990d7127c5bde60163b4ae7d015ec86b8a01 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 19 Sep 2024 14:25:24 +0800 Subject: [PATCH 107/107] perf: arm_pmuv3: Avoid assigning fixed cycle counter with threshold [Upstream commit 81e15ca3e523a508d62806fe681c1d289361ca16] If the user has requested a counting threshold for the CPU cycles event, then the fixed cycle counter can't be assigned as it lacks threshold support. Currently, the thresholds will work or not randomly depending on which counter the event is assigned. While using thresholds for CPU cycles doesn't make much sense, it can be useful for testing purposes. Fixes: 816c26754447 ("arm64: perf: Add support for event counting threshold") Signed-off-by: Rob Herring (Arm) Acked-by: Mark Rutland Link: https://lore.kernel.org/r/20240626-arm-pmu-3-9-icntr-v2-1-c9784b4f4065@kernel.org Signed-off-by: Will Deacon Signed-off-by: yeyiyang <850219375@qq.com> Signed-off-by: WangYuli --- drivers/perf/arm_pmuv3.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/perf/arm_pmuv3.c b/drivers/perf/arm_pmuv3.c index 79a7b7f3b6f31..cede85983f028 100644 --- a/drivers/perf/arm_pmuv3.c +++ b/drivers/perf/arm_pmuv3.c @@ -337,6 +337,11 @@ static bool armv8pmu_event_want_user_access(struct perf_event *event) return ATTR_CFG_GET_FLD(&event->attr, rdpmc); } +static u32 armv8pmu_event_get_threshold(struct perf_event_attr *attr) +{ + return ATTR_CFG_GET_FLD(attr, threshold); +} + static u8 armv8pmu_event_threshold_control(struct perf_event_attr *attr) { u8 th_compare = ATTR_CFG_GET_FLD(attr, threshold_compare); @@ -940,7 +945,8 @@ static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc, unsigned long evtype = hwc->config_base & ARMV8_PMU_EVTYPE_EVENT; /* Always prefer to place a cycle counter into the cycle counter. */ - if (evtype == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) { + if ((evtype == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) && + !armv8pmu_event_get_threshold(&event->attr)) { if (!test_and_set_bit(ARMV8_IDX_CYCLE_COUNTER, cpuc->used_mask)) return ARMV8_IDX_CYCLE_COUNTER; else if (armv8pmu_event_is_64bit(event) && @@ -1032,7 +1038,7 @@ static int armv8pmu_set_event_filter(struct hw_perf_event *event, * If FEAT_PMUv3_TH isn't implemented, then THWIDTH (threshold_max) will * be 0 and will also trigger this check, preventing it from being used. */ - th = ATTR_CFG_GET_FLD(attr, threshold); + th = armv8pmu_event_get_threshold(attr); if (th > threshold_max(cpu_pmu)) { pr_debug("PMU event threshold exceeds max value\n"); return -EINVAL;