From 844d8e4c7f9a3eeb681493f12c55de0392510fe3 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Wed, 28 May 2025 07:47:22 -0300 Subject: [PATCH 01/63] platform/x86: alienware-wmi-wmax: Add appropriate labels to fans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add known fan type IDs and match them to an appropriate label in awcc_hwmon_read_string(). Additionally, add the AWCC_TEMP_SENSOR_FRONT type, which was inferred from it's related fan type in supported systems. Signed-off-by: Kurt Borja Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20250528-awcc-labels-v1-1-6aa39d8e4c3d@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/dell/alienware-wmi-wmax.c | 100 +++++++++++------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index c42f9228b0b2..b25eb3225d8e 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -273,9 +273,29 @@ enum AWCC_SPECIAL_THERMAL_CODES { enum AWCC_TEMP_SENSOR_TYPES { AWCC_TEMP_SENSOR_CPU = 0x01, + AWCC_TEMP_SENSOR_FRONT = 0x03, AWCC_TEMP_SENSOR_GPU = 0x06, }; +enum AWCC_FAN_TYPES { + AWCC_FAN_CPU_1 = 0x32, + AWCC_FAN_GPU_1 = 0x33, + AWCC_FAN_PCI = 0x34, + AWCC_FAN_MID = 0x35, + AWCC_FAN_TOP_1 = 0x36, + AWCC_FAN_SIDE = 0x37, + AWCC_FAN_U2_1 = 0x38, + AWCC_FAN_U2_2 = 0x39, + AWCC_FAN_FRONT_1 = 0x3A, + AWCC_FAN_CPU_2 = 0x3B, + AWCC_FAN_GPU_2 = 0x3C, + AWCC_FAN_TOP_2 = 0x3D, + AWCC_FAN_TOP_3 = 0x3E, + AWCC_FAN_FRONT_2 = 0x3F, + AWCC_FAN_BOTTOM_1 = 0x40, + AWCC_FAN_BOTTOM_2 = 0x41, +}; + enum awcc_thermal_profile { AWCC_PROFILE_USTT_BALANCED, AWCC_PROFILE_USTT_BALANCED_PERFORMANCE, @@ -314,7 +334,6 @@ struct wmax_u32_args { struct awcc_fan_data { unsigned long auto_channels_temp; - const char *label; u32 min_rpm; u32 max_rpm; u8 suspend_cache; @@ -896,6 +915,9 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty case AWCC_TEMP_SENSOR_CPU: *str = "CPU"; break; + case AWCC_TEMP_SENSOR_FRONT: + *str = "Front"; + break; case AWCC_TEMP_SENSOR_GPU: *str = "GPU"; break; @@ -906,7 +928,46 @@ static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types ty break; case hwmon_fan: - *str = priv->fan_data[channel]->label; + switch (priv->fan_data[channel]->id) { + case AWCC_FAN_CPU_1: + case AWCC_FAN_CPU_2: + *str = "CPU Fan"; + break; + case AWCC_FAN_GPU_1: + case AWCC_FAN_GPU_2: + *str = "GPU Fan"; + break; + case AWCC_FAN_PCI: + *str = "PCI Fan"; + break; + case AWCC_FAN_MID: + *str = "Mid Fan"; + break; + case AWCC_FAN_TOP_1: + case AWCC_FAN_TOP_2: + case AWCC_FAN_TOP_3: + *str = "Top Fan"; + break; + case AWCC_FAN_SIDE: + *str = "Side Fan"; + break; + case AWCC_FAN_U2_1: + case AWCC_FAN_U2_2: + *str = "U.2 Fan"; + break; + case AWCC_FAN_FRONT_1: + case AWCC_FAN_FRONT_2: + *str = "Front Fan"; + break; + case AWCC_FAN_BOTTOM_1: + case AWCC_FAN_BOTTOM_2: + *str = "Bottom Fan"; + break; + default: + *str = "Unknown Fan"; + break; + } + break; default: return -EOPNOTSUPP; @@ -1051,40 +1112,6 @@ static int awcc_hwmon_temps_init(struct wmi_device *wdev) return 0; } -static char *awcc_get_fan_label(unsigned long *fan_temps) -{ - unsigned int temp_count = bitmap_weight(fan_temps, AWCC_ID_BITMAP_SIZE); - char *label; - u8 temp_id; - - switch (temp_count) { - case 0: - label = "Independent Fan"; - break; - case 1: - temp_id = find_first_bit(fan_temps, AWCC_ID_BITMAP_SIZE); - - switch (temp_id) { - case AWCC_TEMP_SENSOR_CPU: - label = "Processor Fan"; - break; - case AWCC_TEMP_SENSOR_GPU: - label = "Video Fan"; - break; - default: - label = "Unknown Fan"; - break; - } - - break; - default: - label = "Shared Fan"; - break; - } - - return label; -} - static int awcc_hwmon_fans_init(struct wmi_device *wdev) { struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); @@ -1138,7 +1165,6 @@ static int awcc_hwmon_fans_init(struct wmi_device *wdev) fan_data->id = id; fan_data->min_rpm = min_rpm; fan_data->max_rpm = max_rpm; - fan_data->label = awcc_get_fan_label(fan_temps); bitmap_gather(gather, fan_temps, priv->temp_sensors, AWCC_ID_BITMAP_SIZE); bitmap_copy(&fan_data->auto_channels_temp, gather, BITS_PER_LONG); priv->fan_data[i] = fan_data; From e7c1a9e8d33ceb44ef088de7a9112a1db94d13a4 Mon Sep 17 00:00:00 2001 From: Suma Hegde Date: Tue, 3 Jun 2025 05:58:07 +0000 Subject: [PATCH 02/63] platform/x86/amd/hsmp: Use IS_ENABLED() instead of IS_REACHABLE() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IS_REACHABLE() was required when CONFIG_HWMON was set to m and HSMP to y. However, commit 69157b00b526 ("platform/x86/amd/hsmp: fix building with CONFIG_HWMON=m") added a HWMON dependency for HSMP in Kconfig. With this change, using IS_ENABLED() is sufficient. Add the missing header file as well. Signed-off-by: Suma Hegde Link: https://lore.kernel.org/r/20250603055807.2503028-1-suma.hegde@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/hsmp.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 36b5ceea9ac0..0509a442eaae 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); -#if IS_REACHABLE(CONFIG_HWMON) +#if IS_ENABLED(CONFIG_HWMON) int hsmp_create_sensor(struct device *dev, u16 sock_ind); #else static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } From 3dd1e9c2a279cbc9c6a7f1e7961df08c76ecb464 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sun, 8 Jun 2025 02:25:10 +0100 Subject: [PATCH 03/63] platform/x86: intel_telemetry: Remove unused telemetry_*_events() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The functions: - telemetry_add_events() - telemetry_update_events() - telemetry_reset_events() - telemetry_get_eventconfig() were all added by the commit 378f956e3f93 ("platform/x86: Add Intel Telemetry Core Driver") in 2016 but have remained unused. They're each a tiny wrapper that is the only caller through a similarly named function pointer, and for each function pointer there's a 'def' empty implementation and a plt implementation. Remove all of those components for each function. Signed-off-by: Dr. David Alan Gilbert Link: https://lore.kernel.org/r/20250608012512.377134-2-linux@treblig.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- arch/x86/include/asm/intel_telemetry.h | 24 ---- drivers/platform/x86/intel/telemetry/core.c | 101 ---------------- drivers/platform/x86/intel/telemetry/pltdrv.c | 114 ------------------ 3 files changed, 239 deletions(-) diff --git a/arch/x86/include/asm/intel_telemetry.h b/arch/x86/include/asm/intel_telemetry.h index 43b7657febca..3d7e0b922341 100644 --- a/arch/x86/include/asm/intel_telemetry.h +++ b/arch/x86/include/asm/intel_telemetry.h @@ -62,13 +62,6 @@ struct telemetry_core_ops { int (*get_sampling_period)(u8 *pss_min_period, u8 *pss_max_period, u8 *ioss_min_period, u8 *ioss_max_period); - int (*get_eventconfig)(struct telemetry_evtconfig *pss_evtconfig, - struct telemetry_evtconfig *ioss_evtconfig, - int pss_len, int ioss_len); - - int (*update_events)(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig); - int (*set_sampling_period)(u8 pss_period, u8 ioss_period); int (*get_trace_verbosity)(enum telemetry_unit telem_unit, @@ -84,11 +77,6 @@ struct telemetry_core_ops { int (*read_eventlog)(enum telemetry_unit telem_unit, struct telemetry_evtlog *evtlog, int len, int log_all_evts); - - int (*add_events)(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap); - - int (*reset_events)(void); }; int telemetry_set_pltdata(const struct telemetry_core_ops *ops, @@ -101,18 +89,6 @@ struct telemetry_plt_config *telemetry_get_pltdata(void); int telemetry_get_evtname(enum telemetry_unit telem_unit, const char **name, int len); -int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig); - -int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap); - -int telemetry_reset_events(void); - -int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_config, - struct telemetry_evtconfig *ioss_config, - int pss_len, int ioss_len); - int telemetry_read_events(enum telemetry_unit telem_unit, struct telemetry_evtlog *evtlog, int len); diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c index e4be40f73eeb..229e59c64af7 100644 --- a/drivers/platform/x86/intel/telemetry/core.c +++ b/drivers/platform/x86/intel/telemetry/core.c @@ -21,12 +21,6 @@ struct telemetry_core_config { static struct telemetry_core_config telm_core_conf; -static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig) -{ - return 0; -} - static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period) { return 0; @@ -40,14 +34,6 @@ static int telemetry_def_get_sampling_period(u8 *pss_min_period, return 0; } -static int telemetry_def_get_eventconfig( - struct telemetry_evtconfig *pss_evtconfig, - struct telemetry_evtconfig *ioss_evtconfig, - int pss_len, int ioss_len) -{ - return 0; -} - static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit, u32 *verbosity) { @@ -75,51 +61,15 @@ static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit, return 0; } -static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap) -{ - return 0; -} - -static int telemetry_def_reset_events(void) -{ - return 0; -} - static const struct telemetry_core_ops telm_defpltops = { .set_sampling_period = telemetry_def_set_sampling_period, .get_sampling_period = telemetry_def_get_sampling_period, .get_trace_verbosity = telemetry_def_get_trace_verbosity, .set_trace_verbosity = telemetry_def_set_trace_verbosity, .raw_read_eventlog = telemetry_def_raw_read_eventlog, - .get_eventconfig = telemetry_def_get_eventconfig, .read_eventlog = telemetry_def_read_eventlog, - .update_events = telemetry_def_update_events, - .reset_events = telemetry_def_reset_events, - .add_events = telemetry_def_add_events, }; -/** - * telemetry_update_events() - Update telemetry Configuration - * @pss_evtconfig: PSS related config. No change if num_evts = 0. - * @ioss_evtconfig: IOSS related config. No change if num_evts = 0. - * - * This API updates the IOSS & PSS Telemetry configuration. Old config - * is overwritten. Call telemetry_reset_events when logging is over - * All sample period values should be in the form of: - * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) - * - * Return: 0 success, < 0 for failure - */ -int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig) -{ - return telm_core_conf.telem_ops->update_events(pss_evtconfig, - ioss_evtconfig); -} -EXPORT_SYMBOL_GPL(telemetry_update_events); - - /** * telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period * @pss_period: placeholder for PSS Period to be set. @@ -162,57 +112,6 @@ int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period, EXPORT_SYMBOL_GPL(telemetry_get_sampling_period); -/** - * telemetry_reset_events() - Restore the IOSS & PSS configuration to default - * - * Return: 0 success, < 0 for failure - */ -int telemetry_reset_events(void) -{ - return telm_core_conf.telem_ops->reset_events(); -} -EXPORT_SYMBOL_GPL(telemetry_reset_events); - -/** - * telemetry_get_eventconfig() - Returns the pss and ioss events enabled - * @pss_evtconfig: Pointer to PSS related configuration. - * @ioss_evtconfig: Pointer to IOSS related configuration. - * @pss_len: Number of u32 elements allocated for pss_evtconfig array - * @ioss_len: Number of u32 elements allocated for ioss_evtconfig array - * - * Return: 0 success, < 0 for failure - */ -int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig, - struct telemetry_evtconfig *ioss_evtconfig, - int pss_len, int ioss_len) -{ - return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig, - ioss_evtconfig, - pss_len, ioss_len); -} -EXPORT_SYMBOL_GPL(telemetry_get_eventconfig); - -/** - * telemetry_add_events() - Add IOSS & PSS configuration to existing settings. - * @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0. - * @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0. - * @pss_evtmap: Array of PSS Event-IDs to Enable - * @ioss_evtmap: Array of PSS Event-IDs to Enable - * - * Events are appended to Old Configuration. In case of total events > 28, it - * returns error. Call telemetry_reset_events to reset after eventlog done - * - * Return: 0 success, < 0 for failure - */ -int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap) -{ - return telm_core_conf.telem_ops->add_events(num_pss_evts, - num_ioss_evts, pss_evtmap, - ioss_evtmap); -} -EXPORT_SYMBOL_GPL(telemetry_add_events); - /** * telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id * @telem_unit: Specify whether IOSS or PSS Read diff --git a/drivers/platform/x86/intel/telemetry/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c index 9a499efa1e4d..60d3783de7ef 100644 --- a/drivers/platform/x86/intel/telemetry/pltdrv.c +++ b/drivers/platform/x86/intel/telemetry/pltdrv.c @@ -639,32 +639,6 @@ static int telemetry_setup(struct platform_device *pdev) return 0; } -static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig, - struct telemetry_evtconfig ioss_evtconfig) -{ - int ret; - - if ((pss_evtconfig.num_evts > 0) && - (TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) { - pr_err("PSS Sampling Period Out of Range\n"); - return -EINVAL; - } - - if ((ioss_evtconfig.num_evts > 0) && - (TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) { - pr_err("IOSS Sampling Period Out of Range\n"); - return -EINVAL; - } - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_UPDATE); - if (ret) - pr_err("TELEMETRY Config Failed\n"); - - return ret; -} - - static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period) { u32 telem_ctrl = 0; @@ -780,90 +754,6 @@ static int telemetry_plt_get_sampling_period(u8 *pss_min_period, } -static int telemetry_plt_reset_events(void) -{ - struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; - int ret; - - pss_evtconfig.evtmap = NULL; - pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; - pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; - - ioss_evtconfig.evtmap = NULL; - ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS; - ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD; - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_RESET); - if (ret) - pr_err("TELEMETRY Reset Failed\n"); - - return ret; -} - - -static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config, - struct telemetry_evtconfig *ioss_config, - int pss_len, int ioss_len) -{ - u32 *pss_evtmap, *ioss_evtmap; - u32 index; - - pss_evtmap = pss_config->evtmap; - ioss_evtmap = ioss_config->evtmap; - - mutex_lock(&(telm_conf->telem_lock)); - pss_config->num_evts = telm_conf->pss_config.ssram_evts_used; - ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used; - - pss_config->period = telm_conf->pss_config.curr_period; - ioss_config->period = telm_conf->ioss_config.curr_period; - - if ((pss_len < telm_conf->pss_config.ssram_evts_used) || - (ioss_len < telm_conf->ioss_config.ssram_evts_used)) { - mutex_unlock(&(telm_conf->telem_lock)); - return -EINVAL; - } - - for (index = 0; index < telm_conf->pss_config.ssram_evts_used; - index++) { - pss_evtmap[index] = - telm_conf->pss_config.telem_evts[index].evt_id; - } - - for (index = 0; index < telm_conf->ioss_config.ssram_evts_used; - index++) { - ioss_evtmap[index] = - telm_conf->ioss_config.telem_evts[index].evt_id; - } - - mutex_unlock(&(telm_conf->telem_lock)); - return 0; -} - - -static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts, - u32 *pss_evtmap, u32 *ioss_evtmap) -{ - struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig; - int ret; - - pss_evtconfig.evtmap = pss_evtmap; - pss_evtconfig.num_evts = num_pss_evts; - pss_evtconfig.period = telm_conf->pss_config.curr_period; - - ioss_evtconfig.evtmap = ioss_evtmap; - ioss_evtconfig.num_evts = num_ioss_evts; - ioss_evtconfig.period = telm_conf->ioss_config.curr_period; - - ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig, - TELEM_ADD); - if (ret) - pr_err("TELEMETRY ADD Failed\n"); - - return ret; -} - static int telem_evtlog_read(enum telemetry_unit telem_unit, struct telem_ssram_region *ssram_region, u8 len) { @@ -1096,11 +986,7 @@ static const struct telemetry_core_ops telm_pltops = { .set_sampling_period = telemetry_plt_set_sampling_period, .get_sampling_period = telemetry_plt_get_sampling_period, .raw_read_eventlog = telemetry_plt_raw_read_eventlog, - .get_eventconfig = telemetry_plt_get_eventconfig, - .update_events = telemetry_plt_update_events, .read_eventlog = telemetry_plt_read_eventlog, - .reset_events = telemetry_plt_reset_events, - .add_events = telemetry_plt_add_events, }; static int telemetry_pltdrv_probe(struct platform_device *pdev) From b35b9fb28c85ccee43c34768c5e2d9a5c510e660 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sun, 8 Jun 2025 02:25:11 +0100 Subject: [PATCH 04/63] platform/x86: intel_telemetry: Remove unused telemetry_[gs]et_sampling_period() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The functions: - telemetry_get_sampling_period() - telemetry_set_sampling_period() were both added by the commit 378f956e3f93 ("platform/x86: Add Intel Telemetry Core Driver") in 2016 but have remained unused. They're each a tiny wrapper that is the only caller through a similarly named function pointer, and for each function pointer there's a 'def' empty implementation and a plt implementation. Remove all of those components for each function. Signed-off-by: Dr. David Alan Gilbert Link: https://lore.kernel.org/r/20250608012512.377134-3-linux@treblig.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- arch/x86/include/asm/intel_telemetry.h | 10 -- drivers/platform/x86/intel/telemetry/core.c | 57 --------- drivers/platform/x86/intel/telemetry/pltdrv.c | 117 ------------------ 3 files changed, 184 deletions(-) diff --git a/arch/x86/include/asm/intel_telemetry.h b/arch/x86/include/asm/intel_telemetry.h index 3d7e0b922341..e7fb005ac8d8 100644 --- a/arch/x86/include/asm/intel_telemetry.h +++ b/arch/x86/include/asm/intel_telemetry.h @@ -59,11 +59,6 @@ struct telemetry_plt_config { }; struct telemetry_core_ops { - int (*get_sampling_period)(u8 *pss_min_period, u8 *pss_max_period, - u8 *ioss_min_period, u8 *ioss_max_period); - - int (*set_sampling_period)(u8 pss_period, u8 ioss_period); - int (*get_trace_verbosity)(enum telemetry_unit telem_unit, u32 *verbosity); @@ -101,11 +96,6 @@ int telemetry_read_eventlog(enum telemetry_unit telem_unit, int telemetry_raw_read_eventlog(enum telemetry_unit telem_unit, struct telemetry_evtlog *evtlog, int len); -int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period, - u8 *ioss_min_period, u8 *ioss_max_period); - -int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period); - int telemetry_set_trace_verbosity(enum telemetry_unit telem_unit, u32 verbosity); diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c index 229e59c64af7..fe9e8580a8f5 100644 --- a/drivers/platform/x86/intel/telemetry/core.c +++ b/drivers/platform/x86/intel/telemetry/core.c @@ -21,19 +21,6 @@ struct telemetry_core_config { static struct telemetry_core_config telm_core_conf; -static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period) -{ - return 0; -} - -static int telemetry_def_get_sampling_period(u8 *pss_min_period, - u8 *pss_max_period, - u8 *ioss_min_period, - u8 *ioss_max_period) -{ - return 0; -} - static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit, u32 *verbosity) { @@ -62,56 +49,12 @@ static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit, } static const struct telemetry_core_ops telm_defpltops = { - .set_sampling_period = telemetry_def_set_sampling_period, - .get_sampling_period = telemetry_def_get_sampling_period, .get_trace_verbosity = telemetry_def_get_trace_verbosity, .set_trace_verbosity = telemetry_def_set_trace_verbosity, .raw_read_eventlog = telemetry_def_raw_read_eventlog, .read_eventlog = telemetry_def_read_eventlog, }; -/** - * telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period - * @pss_period: placeholder for PSS Period to be set. - * Set to 0 if not required to be updated - * @ioss_period: placeholder for IOSS Period to be set - * Set to 0 if not required to be updated - * - * All values should be in the form of: - * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) - * - * Return: 0 success, < 0 for failure - */ -int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period) -{ - return telm_core_conf.telem_ops->set_sampling_period(pss_period, - ioss_period); -} -EXPORT_SYMBOL_GPL(telemetry_set_sampling_period); - -/** - * telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period - * @pss_min_period: placeholder for PSS Min Period supported - * @pss_max_period: placeholder for PSS Max Period supported - * @ioss_min_period: placeholder for IOSS Min Period supported - * @ioss_max_period: placeholder for IOSS Max Period supported - * - * All values should be in the form of: - * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent) - * - * Return: 0 success, < 0 for failure - */ -int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period, - u8 *ioss_min_period, u8 *ioss_max_period) -{ - return telm_core_conf.telem_ops->get_sampling_period(pss_min_period, - pss_max_period, - ioss_min_period, - ioss_max_period); -} -EXPORT_SYMBOL_GPL(telemetry_get_sampling_period); - - /** * telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id * @telem_unit: Specify whether IOSS or PSS Read diff --git a/drivers/platform/x86/intel/telemetry/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c index 60d3783de7ef..f23c170a55dc 100644 --- a/drivers/platform/x86/intel/telemetry/pltdrv.c +++ b/drivers/platform/x86/intel/telemetry/pltdrv.c @@ -639,121 +639,6 @@ static int telemetry_setup(struct platform_device *pdev) return 0; } -static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period) -{ - u32 telem_ctrl = 0; - int ret = 0; - - mutex_lock(&(telm_conf->telem_lock)); - if (ioss_period) { - struct intel_scu_ipc_dev *scu = telm_conf->scu; - - if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) { - pr_err("IOSS Sampling Period Out of Range\n"); - ret = -EINVAL; - goto out; - } - - /* Get telemetry EVENT CTL */ - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_READ, NULL, 0, - &telem_ctrl, sizeof(telem_ctrl)); - if (ret) { - pr_err("IOSS TELEM_CTRL Read Failed\n"); - goto out; - } - - /* Disable Telemetry */ - TELEM_DISABLE(telem_ctrl); - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), - NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n"); - goto out; - } - - /* Enable Periodic Telemetry Events and enable SRAM trace */ - TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); - TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); - TELEM_ENABLE_PERIODIC(telem_ctrl); - telem_ctrl |= ioss_period; - - ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM, - IOSS_TELEM_EVENT_CTL_WRITE, - &telem_ctrl, sizeof(telem_ctrl), - NULL, 0); - if (ret) { - pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n"); - goto out; - } - telm_conf->ioss_config.curr_period = ioss_period; - } - - if (pss_period) { - if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) { - pr_err("PSS Sampling Period Out of Range\n"); - ret = -EINVAL; - goto out; - } - - /* Get telemetry EVENT CTL */ - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL, - 0, 0, NULL, &telem_ctrl); - if (ret) { - pr_err("PSS TELEM_CTRL Read Failed\n"); - goto out; - } - - /* Disable Telemetry */ - TELEM_DISABLE(telem_ctrl); - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Disable Write Failed\n"); - goto out; - } - - /* Enable Periodic Telemetry Events and enable SRAM trace */ - TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl); - TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl); - TELEM_ENABLE_PERIODIC(telem_ctrl); - telem_ctrl |= pss_period; - - ret = intel_punit_ipc_command( - IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL, - 0, 0, &telem_ctrl, NULL); - if (ret) { - pr_err("PSS TELEM_CTRL Event Enable Write Failed\n"); - goto out; - } - telm_conf->pss_config.curr_period = pss_period; - } - -out: - mutex_unlock(&(telm_conf->telem_lock)); - return ret; -} - - -static int telemetry_plt_get_sampling_period(u8 *pss_min_period, - u8 *pss_max_period, - u8 *ioss_min_period, - u8 *ioss_max_period) -{ - *pss_min_period = telm_conf->pss_config.min_period; - *pss_max_period = telm_conf->pss_config.max_period; - *ioss_min_period = telm_conf->ioss_config.min_period; - *ioss_max_period = telm_conf->ioss_config.max_period; - - return 0; -} - - static int telem_evtlog_read(enum telemetry_unit telem_unit, struct telem_ssram_region *ssram_region, u8 len) { @@ -983,8 +868,6 @@ static int telemetry_plt_set_trace_verbosity(enum telemetry_unit telem_unit, static const struct telemetry_core_ops telm_pltops = { .get_trace_verbosity = telemetry_plt_get_trace_verbosity, .set_trace_verbosity = telemetry_plt_set_trace_verbosity, - .set_sampling_period = telemetry_plt_set_sampling_period, - .get_sampling_period = telemetry_plt_get_sampling_period, .raw_read_eventlog = telemetry_plt_raw_read_eventlog, .read_eventlog = telemetry_plt_read_eventlog, }; From 097cd6d6c90cc90410ab03b287166838ea71d41e Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sun, 8 Jun 2025 02:25:12 +0100 Subject: [PATCH 05/63] platform/x86: intel_telemetry: Remove unused telemetry_raw_read_events() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit telemetry_raw_read_events() was added by the commit 378f956e3f93 ("platform/x86: Add Intel Telemetry Core Driver") in 2016 but has remained unused. Remove it. Signed-off-by: Dr. David Alan Gilbert Link: https://lore.kernel.org/r/20250608012512.377134-4-linux@treblig.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- arch/x86/include/asm/intel_telemetry.h | 3 --- drivers/platform/x86/intel/telemetry/core.c | 19 ------------------- 2 files changed, 22 deletions(-) diff --git a/arch/x86/include/asm/intel_telemetry.h b/arch/x86/include/asm/intel_telemetry.h index e7fb005ac8d8..944637a4e6de 100644 --- a/arch/x86/include/asm/intel_telemetry.h +++ b/arch/x86/include/asm/intel_telemetry.h @@ -87,9 +87,6 @@ int telemetry_get_evtname(enum telemetry_unit telem_unit, int telemetry_read_events(enum telemetry_unit telem_unit, struct telemetry_evtlog *evtlog, int len); -int telemetry_raw_read_events(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len); - int telemetry_read_eventlog(enum telemetry_unit telem_unit, struct telemetry_evtlog *evtlog, int len); diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c index fe9e8580a8f5..f312864b8d07 100644 --- a/drivers/platform/x86/intel/telemetry/core.c +++ b/drivers/platform/x86/intel/telemetry/core.c @@ -72,25 +72,6 @@ int telemetry_read_events(enum telemetry_unit telem_unit, } EXPORT_SYMBOL_GPL(telemetry_read_events); -/** - * telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id - * @telem_unit: Specify whether IOSS or PSS Read - * @evtlog: Array of telemetry_evtlog structs to fill data - * evtlog.telem_evt_id specifies the ids to read - * @len: Length of array of evtlog - * - * The caller must take care of locking in this case. - * - * Return: number of eventlogs read for success, < 0 for failure - */ -int telemetry_raw_read_events(enum telemetry_unit telem_unit, - struct telemetry_evtlog *evtlog, int len) -{ - return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog, - len, 0); -} -EXPORT_SYMBOL_GPL(telemetry_raw_read_events); - /** * telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS * @telem_unit: Specify whether IOSS or PSS Read From fe6859aa646b9463379985165f602a44cf25c8ad Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Tue, 10 Jun 2025 15:28:24 -0400 Subject: [PATCH 06/63] platform/x86: thinklmi: improved DMI handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix issues reported by kernel test robot. - Require DMI for think-lmi. - Check return from getting serial string Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202506062319.F0IpDxF6-lkp@intel.com/ Signed-off-by: Mark Pearson Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250610192830.1731454-1-mpearson-lenovo@squebb.ca Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/think-lmi.c | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index e5cbd58a99f3..9f39547d98f6 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -659,6 +659,7 @@ config THINKPAD_ACPI_HOTKEY_POLL config THINKPAD_LMI tristate "Lenovo WMI-based systems management driver" depends on ACPI_WMI + depends on DMI select FW_ATTR_CLASS help This driver allows changing BIOS settings on Lenovo machines whose diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 00b1e7c79a3d..02ede1ec99e9 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -772,6 +772,7 @@ static ssize_t certificate_store(struct kobject *kobj, struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); enum cert_install_mode install_mode = TLMI_CERT_INSTALL; char *auth_str, *new_cert; + const char *serial; char *signature; char *guid; int ret; @@ -789,9 +790,10 @@ static ssize_t certificate_store(struct kobject *kobj, return -EACCES; /* Format: 'serial#, signature' */ - auth_str = cert_command(setting, - dmi_get_system_info(DMI_PRODUCT_SERIAL), - setting->signature); + serial = dmi_get_system_info(DMI_PRODUCT_SERIAL); + if (!serial) + return -ENODEV; + auth_str = cert_command(setting, serial, setting->signature); if (!auth_str) return -ENOMEM; From 651b57dd40871d4d0d61fb291e7f26e2b8bd69b1 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Tue, 10 Jun 2025 15:28:25 -0400 Subject: [PATCH 07/63] platform/x86: Move Lenovo files into lenovo subdir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create lenovo subdirectory for holding Lenovo specific drivers. Signed-off-by: Mark Pearson Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250610192830.1731454-2-mpearson-lenovo@squebb.ca [ij: put depends on DMI back, fix trailing empty lines.] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 16 +- drivers/platform/x86/Kconfig | 230 +---------------- drivers/platform/x86/Makefile | 13 +- drivers/platform/x86/lenovo/Kconfig | 233 ++++++++++++++++++ drivers/platform/x86/lenovo/Makefile | 23 ++ .../x86/{ => lenovo}/ideapad-laptop.c | 0 .../x86/{ => lenovo}/ideapad-laptop.h | 0 drivers/platform/x86/{ => lenovo}/think-lmi.c | 2 +- drivers/platform/x86/{ => lenovo}/think-lmi.h | 0 .../platform/x86/{ => lenovo}/thinkpad_acpi.c | 2 +- .../wmi-camera.c} | 0 .../wmi-hotkey-utilities.c} | 0 .../x86/{lenovo-ymc.c => lenovo/ymc.c} | 0 .../yoga-tab2-pro-1380-fastcharger.c} | 2 +- .../{lenovo-yogabook.c => lenovo/yogabook.c} | 0 15 files changed, 275 insertions(+), 246 deletions(-) create mode 100644 drivers/platform/x86/lenovo/Kconfig create mode 100644 drivers/platform/x86/lenovo/Makefile rename drivers/platform/x86/{ => lenovo}/ideapad-laptop.c (100%) rename drivers/platform/x86/{ => lenovo}/ideapad-laptop.h (100%) rename drivers/platform/x86/{ => lenovo}/think-lmi.c (99%) rename drivers/platform/x86/{ => lenovo}/think-lmi.h (100%) rename drivers/platform/x86/{ => lenovo}/thinkpad_acpi.c (99%) rename drivers/platform/x86/{lenovo-wmi-camera.c => lenovo/wmi-camera.c} (100%) rename drivers/platform/x86/{lenovo-wmi-hotkey-utilities.c => lenovo/wmi-hotkey-utilities.c} (100%) rename drivers/platform/x86/{lenovo-ymc.c => lenovo/ymc.c} (100%) rename drivers/platform/x86/{lenovo-yoga-tab2-pro-1380-fastcharger.c => lenovo/yoga-tab2-pro-1380-fastcharger.c} (99%) rename drivers/platform/x86/{lenovo-yogabook.c => lenovo/yogabook.c} (100%) diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa16..c14614613377 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11612,7 +11612,7 @@ M: Ike Panhc L: platform-driver-x86@vger.kernel.org S: Maintained W: http://launchpad.net/ideapad-laptop -F: drivers/platform/x86/ideapad-laptop.c +F: drivers/platform/x86/lenovo/ideapad-laptop.c IDEAPAD LAPTOP SLIDEBAR DRIVER M: Andrey Moiseev @@ -13671,11 +13671,17 @@ S: Maintained W: http://legousb.sourceforge.net/ F: drivers/usb/misc/legousbtower.c +LENOVO drivers +M: Mark Pearson +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/lenovo/* + LENOVO WMI HOTKEY UTILITIES DRIVER M: Jackie Dong L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/lenovo-wmi-hotkey-utilities.c +F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c LETSKETCH HID TABLET DRIVER M: Hans de Goede @@ -24666,14 +24672,14 @@ S: Maintained W: http://ibm-acpi.sourceforge.net W: http://thinkwiki.org/wiki/Ibm-acpi T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git -F: drivers/platform/x86/thinkpad_acpi.c +F: drivers/platform/x86/lenovo/thinkpad_acpi.c THINKPAD LMI DRIVER -M: Mark Pearson +M: Mark Pearson L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-class-firmware-attributes -F: drivers/platform/x86/think-lmi.? +F: drivers/platform/x86/lenovo/think-lmi.? THP7312 ISP DRIVER M: Laurent Pinchart diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9f39547d98f6..43055df44827 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -120,32 +120,6 @@ config GIGABYTE_WMI To compile this driver as a module, choose M here: the module will be called gigabyte-wmi. -config YOGABOOK - tristate "Lenovo Yoga Book tablet key driver" - depends on ACPI_WMI - depends on INPUT - depends on I2C - select LEDS_CLASS - select NEW_LEDS - help - Say Y here if you want to support the 'Pen' key and keyboard backlight - control on the Lenovo Yoga Book tablets. - - To compile this driver as a module, choose M here: the module will - be called lenovo-yogabook. - -config YT2_1380 - tristate "Lenovo Yoga Tablet 2 1380 fast charge driver" - depends on SERIAL_DEV_BUS - depends on EXTCON - depends on ACPI - help - Say Y here to enable support for the custom fast charging protocol - found on the Lenovo Yoga Tablet 2 1380F / 1380L models. - - To compile this driver as a module, choose M here: the module will - be called lenovo-yogabook. - config ACERHDF tristate "Acer Aspire One temperature and fan driver" depends on ACPI_EC && THERMAL @@ -459,43 +433,6 @@ config IBM_RTL state = 0 (BIOS SMIs on) state = 1 (BIOS SMIs off) -config IDEAPAD_LAPTOP - tristate "Lenovo IdeaPad Laptop Extras" - depends on ACPI - depends on RFKILL && INPUT - depends on SERIO_I8042 - depends on BACKLIGHT_CLASS_DEVICE - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on ACPI_WMI || ACPI_WMI = n - select ACPI_PLATFORM_PROFILE - select INPUT_SPARSEKMAP - select NEW_LEDS - select LEDS_CLASS - help - This is a driver for Lenovo IdeaPad netbooks contains drivers for - rfkill switch, hotkey, fan control and backlight control. - -config LENOVO_WMI_HOTKEY_UTILITIES - tristate "Lenovo Hotkey Utility WMI extras driver" - depends on ACPI_WMI - select NEW_LEDS - select LEDS_CLASS - imply IDEAPAD_LAPTOP - help - This driver provides WMI support for Lenovo customized hotkeys function, - such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin, - Gaming, ThinkBook and so on. - -config LENOVO_YMC - tristate "Lenovo Yoga Tablet Mode Control" - depends on ACPI_WMI - depends on INPUT - depends on IDEAPAD_LAPTOP - select INPUT_SPARSEKMAP - help - This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input - events for Lenovo Yoga notebooks. - config SENSORS_HDAPS tristate "Thinkpad Hard Drive Active Protection System (hdaps)" depends on INPUT @@ -514,161 +451,8 @@ config SENSORS_HDAPS Say Y here if you have an applicable laptop and want to experience the awesome power of hdaps. -config THINKPAD_ACPI - tristate "ThinkPad ACPI Laptop Extras" - depends on ACPI_EC - depends on ACPI_BATTERY - depends on INPUT - depends on RFKILL || RFKILL = n - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on BACKLIGHT_CLASS_DEVICE - depends on I2C - depends on DRM - select ACPI_PLATFORM_PROFILE - select DRM_PRIVACY_SCREEN - select HWMON - select NVRAM - select NEW_LEDS - select LEDS_CLASS - select INPUT_SPARSEKMAP - help - This is a driver for the IBM and Lenovo ThinkPad laptops. It adds - support for Fn-Fx key combinations, Bluetooth control, video - output switching, ThinkLight control, UltraBay eject and more. - For more information about this driver see - and - . - - This driver was formerly known as ibm-acpi. - - Extra functionality will be available if the rfkill (CONFIG_RFKILL) - and/or ALSA (CONFIG_SND) subsystems are available in the kernel. - Note that if you want ThinkPad-ACPI to be built-in instead of - modular, ALSA and rfkill will also have to be built-in. - - If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. - -config THINKPAD_ACPI_ALSA_SUPPORT - bool "Console audio control ALSA interface" - depends on THINKPAD_ACPI - depends on SND - depends on SND = y || THINKPAD_ACPI = SND - default y - help - Enables monitoring of the built-in console audio output control - (headphone and speakers), which is operated by the mute and (in - some ThinkPad models) volume hotkeys. - - If this option is enabled, ThinkPad-ACPI will export an ALSA card - with a single read-only mixer control, which should be used for - on-screen-display feedback purposes by the Desktop Environment. - - Optionally, the driver will also allow software control (the - ALSA mixer will be made read-write). Please refer to the driver - documentation for details. - - All IBM models have both volume and mute control. Newer Lenovo - models only have mute control (the volume hotkeys are just normal - keys and volume control is done through the main HDA mixer). - -config THINKPAD_ACPI_DEBUGFACILITIES - bool "Maintainer debug facilities" - depends on THINKPAD_ACPI - help - Enables extra stuff in the thinkpad-acpi which is completely useless - for normal use. Read the driver source to find out what it does. - - Say N here, unless you were told by a kernel maintainer to do - otherwise. - -config THINKPAD_ACPI_DEBUG - bool "Verbose debug mode" - depends on THINKPAD_ACPI - help - Enables extra debugging information, at the expense of a slightly - increase in driver size. - - If you are not sure, say N here. - -config THINKPAD_ACPI_UNSAFE_LEDS - bool "Allow control of important LEDs (unsafe)" - depends on THINKPAD_ACPI - help - Overriding LED state on ThinkPads can mask important - firmware alerts (like critical battery condition), or misled - the user into damaging the hardware (undocking or ejecting - the bay while buses are still active), etc. - - LED control on the ThinkPad is write-only (with very few - exceptions on very ancient models), which makes it - impossible to know beforehand if important information will - be lost when one changes LED state. - - Users that know what they are doing can enable this option - and the driver will allow control of every LED, including - the ones on the dock stations. - - Never enable this option on a distribution kernel. - - Say N here, unless you are building a kernel for your own - use, and need to control the important firmware LEDs. - -config THINKPAD_ACPI_VIDEO - bool "Video output control support" - depends on THINKPAD_ACPI - default y - help - Allows the thinkpad_acpi driver to provide an interface to control - the various video output ports. - - This feature often won't work well, depending on ThinkPad model, - display state, video output devices in use, whether there is a X - server running, phase of the moon, and the current mood of - Schroedinger's cat. If you can use X.org's RandR to control - your ThinkPad's video output ports instead of this feature, - don't think twice: do it and say N here to save memory and avoid - bad interactions with X.org. - - NOTE: access to this feature is limited to processes with the - CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms - where it interacts badly with X.org. - - If you are not sure, say Y here but do try to check if you could - be using X.org RandR instead. - -config THINKPAD_ACPI_HOTKEY_POLL - bool "Support NVRAM polling for hot keys" - depends on THINKPAD_ACPI - default y - help - Some thinkpad models benefit from NVRAM polling to detect a few of - the hot key press events. If you know your ThinkPad model does not - need to do NVRAM polling to support any of the hot keys you use, - unselecting this option will save about 1kB of memory. - - ThinkPads T40 and newer, R52 and newer, and X31 and newer are - unlikely to need NVRAM polling in their latest BIOS versions. - - NVRAM polling can detect at most the following keys: ThinkPad/Access - IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute, - Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12). - - If you are not sure, say Y here. The driver enables polling only if - it is strictly necessary to do so. - -config THINKPAD_LMI - tristate "Lenovo WMI-based systems management driver" - depends on ACPI_WMI - depends on DMI - select FW_ATTR_CLASS - help - This driver allows changing BIOS settings on Lenovo machines whose - BIOS support the WMI interface. - - To compile this driver as a module, choose M here: the module will - be called think-lmi. - source "drivers/platform/x86/intel/Kconfig" +source "drivers/platform/x86/lenovo/Kconfig" config ACPI_QUICKSTART tristate "ACPI Quickstart button driver" @@ -1079,18 +863,6 @@ config INSPUR_PLATFORM_PROFILE To compile this driver as a module, choose M here: the module will be called inspur-platform-profile. -config LENOVO_WMI_CAMERA - tristate "Lenovo WMI Camera Button driver" - depends on ACPI_WMI - depends on INPUT - help - This driver provides support for Lenovo camera button. The Camera - button is a GPIO device. This driver receives ACPI notifications when - the camera button is switched on/off. - - To compile this driver as a module, choose M here: the module - will be called lenovo-wmi-camera. - config DASHARO_ACPI tristate "Dasharo ACPI Platform Driver" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index abbc2644ff6d..0530a224bebd 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -58,17 +58,12 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ # Hewlett Packard Enterprise obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o -# IBM Thinkpad and Lenovo +# IBM Thinkpad (before 2005) obj-$(CONFIG_IBM_RTL) += ibm_rtl.o -obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o -obj-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += lenovo-wmi-hotkey-utilities.o -obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o -obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o -obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o -obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o -obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o -obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o + +# Lenovo +obj-y += lenovo/ # Intel obj-y += intel/ diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig new file mode 100644 index 000000000000..e9cb7372a2ca --- /dev/null +++ b/drivers/platform/x86/lenovo/Kconfig @@ -0,0 +1,233 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Lenovo X86 Platform Specific Drivers +# + +config IDEAPAD_LAPTOP + tristate "Lenovo IdeaPad Laptop Extras" + depends on ACPI + depends on RFKILL && INPUT + depends on SERIO_I8042 + depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on ACPI_WMI || ACPI_WMI = n + select ACPI_PLATFORM_PROFILE + select INPUT_SPARSEKMAP + select NEW_LEDS + select LEDS_CLASS + help + This is a driver for Lenovo IdeaPad netbooks contains drivers for + rfkill switch, hotkey, fan control and backlight control. + +config LENOVO_WMI_HOTKEY_UTILITIES + tristate "Lenovo Hotkey Utility WMI extras driver" + depends on ACPI_WMI + select NEW_LEDS + select LEDS_CLASS + imply IDEAPAD_LAPTOP + help + This driver provides WMI support for Lenovo customized hotkeys function, + such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin, + Gaming, ThinkBook and so on. + +config LENOVO_WMI_CAMERA + tristate "Lenovo WMI Camera Button driver" + depends on ACPI_WMI + depends on INPUT + help + This driver provides support for Lenovo camera button. The Camera + button is a GPIO device. This driver receives ACPI notifications when + the camera button is switched on/off. + + To compile this driver as a module, choose M here: the module + will be called lenovo-wmi-camera. + +config LENOVO_YMC + tristate "Lenovo Yoga Tablet Mode Control" + depends on ACPI_WMI + depends on INPUT + depends on IDEAPAD_LAPTOP + select INPUT_SPARSEKMAP + help + This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input + events for Lenovo Yoga notebooks. + +config THINKPAD_ACPI + tristate "ThinkPad ACPI Laptop Extras" + depends on ACPI_EC + depends on ACPI_BATTERY + depends on INPUT + depends on RFKILL || RFKILL = n + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on BACKLIGHT_CLASS_DEVICE + depends on I2C + depends on DRM + select ACPI_PLATFORM_PROFILE + select DRM_PRIVACY_SCREEN + select HWMON + select NVRAM + select NEW_LEDS + select LEDS_CLASS + select INPUT_SPARSEKMAP + help + This is a driver for the IBM and Lenovo ThinkPad laptops. It adds + support for Fn-Fx key combinations, Bluetooth control, video + output switching, ThinkLight control, UltraBay eject and more. + For more information about this driver see + and + . + + This driver was formerly known as ibm-acpi. + + Extra functionality will be available if the rfkill (CONFIG_RFKILL) + and/or ALSA (CONFIG_SND) subsystems are available in the kernel. + Note that if you want ThinkPad-ACPI to be built-in instead of + modular, ALSA and rfkill will also have to be built-in. + + If you have an IBM or Lenovo ThinkPad laptop, say Y or M here. + +config THINKPAD_ACPI_ALSA_SUPPORT + bool "Console audio control ALSA interface" + depends on THINKPAD_ACPI + depends on SND + depends on SND = y || THINKPAD_ACPI = SND + default y + help + Enables monitoring of the built-in console audio output control + (headphone and speakers), which is operated by the mute and (in + some ThinkPad models) volume hotkeys. + + If this option is enabled, ThinkPad-ACPI will export an ALSA card + with a single read-only mixer control, which should be used for + on-screen-display feedback purposes by the Desktop Environment. + + Optionally, the driver will also allow software control (the + ALSA mixer will be made read-write). Please refer to the driver + documentation for details. + + All IBM models have both volume and mute control. Newer Lenovo + models only have mute control (the volume hotkeys are just normal + keys and volume control is done through the main HDA mixer). + +config THINKPAD_ACPI_DEBUGFACILITIES + bool "Maintainer debug facilities" + depends on THINKPAD_ACPI + help + Enables extra stuff in the thinkpad-acpi which is completely useless + for normal use. Read the driver source to find out what it does. + + Say N here, unless you were told by a kernel maintainer to do + otherwise. + +config THINKPAD_ACPI_DEBUG + bool "Verbose debug mode" + depends on THINKPAD_ACPI + help + Enables extra debugging information, at the expense of a slightly + increase in driver size. + + If you are not sure, say N here. + +config THINKPAD_ACPI_UNSAFE_LEDS + bool "Allow control of important LEDs (unsafe)" + depends on THINKPAD_ACPI + help + Overriding LED state on ThinkPads can mask important + firmware alerts (like critical battery condition), or misled + the user into damaging the hardware (undocking or ejecting + the bay while buses are still active), etc. + + LED control on the ThinkPad is write-only (with very few + exceptions on very ancient models), which makes it + impossible to know beforehand if important information will + be lost when one changes LED state. + + Users that know what they are doing can enable this option + and the driver will allow control of every LED, including + the ones on the dock stations. + + Never enable this option on a distribution kernel. + + Say N here, unless you are building a kernel for your own + use, and need to control the important firmware LEDs. + +config THINKPAD_ACPI_VIDEO + bool "Video output control support" + depends on THINKPAD_ACPI + default y + help + Allows the thinkpad_acpi driver to provide an interface to control + the various video output ports. + + This feature often won't work well, depending on ThinkPad model, + display state, video output devices in use, whether there is a X + server running, phase of the moon, and the current mood of + Schroedinger's cat. If you can use X.org's RandR to control + your ThinkPad's video output ports instead of this feature, + don't think twice: do it and say N here to save memory and avoid + bad interactions with X.org. + + NOTE: access to this feature is limited to processes with the + CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms + where it interacts badly with X.org. + + If you are not sure, say Y here but do try to check if you could + be using X.org RandR instead. + +config THINKPAD_ACPI_HOTKEY_POLL + bool "Support NVRAM polling for hot keys" + depends on THINKPAD_ACPI + default y + help + Some thinkpad models benefit from NVRAM polling to detect a few of + the hot key press events. If you know your ThinkPad model does not + need to do NVRAM polling to support any of the hot keys you use, + unselecting this option will save about 1kB of memory. + + ThinkPads T40 and newer, R52 and newer, and X31 and newer are + unlikely to need NVRAM polling in their latest BIOS versions. + + NVRAM polling can detect at most the following keys: ThinkPad/Access + IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute, + Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12). + + If you are not sure, say Y here. The driver enables polling only if + it is strictly necessary to do so. + +config THINKPAD_LMI + tristate "Lenovo WMI-based systems management driver" + depends on ACPI_WMI + depends on DMI + select FW_ATTR_CLASS + help + This driver allows changing BIOS settings on Lenovo machines whose + BIOS support the WMI interface. + + To compile this driver as a module, choose M here: the module will + be called think-lmi. + +config YOGABOOK + tristate "Lenovo Yoga Book tablet key driver" + depends on ACPI_WMI + depends on INPUT + depends on I2C + select LEDS_CLASS + select NEW_LEDS + help + Say Y here if you want to support the 'Pen' key and keyboard backlight + control on the Lenovo Yoga Book tablets. + + To compile this driver as a module, choose M here: the module will + be called lenovo-yogabook. + +config YT2_1380 + tristate "Lenovo Yoga Tablet 2 1380 fast charge driver" + depends on SERIAL_DEV_BUS + depends on EXTCON + depends on ACPI + help + Say Y here to enable support for the custom fast charging protocol + found on the Lenovo Yoga Tablet 2 1380F / 1380L models. + + To compile this driver as a module, choose M here: the module will + be called lenovo-yogabook. diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile new file mode 100644 index 000000000000..f1aa2449293b --- /dev/null +++ b/drivers/platform/x86/lenovo/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/lenovo +# Lenovo x86 Platform Specific Drivers +# +obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o +obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + +lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o +lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o +lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o +lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o +lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o + +# Add 'lenovo' prefix to each module listed in lenovo-target-* +define LENOVO_OBJ_TARGET +lenovo-$(1)-y := $(1).o +obj-$(2) += lenovo-$(1).o +endef + +$(foreach target, $(basename $(lenovo-target-y)), $(eval $(call LENOVO_OBJ_TARGET,$(target),y))) +$(foreach target, $(basename $(lenovo-target-m)), $(eval $(call LENOVO_OBJ_TARGET,$(target),m))) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c similarity index 100% rename from drivers/platform/x86/ideapad-laptop.c rename to drivers/platform/x86/lenovo/ideapad-laptop.c diff --git a/drivers/platform/x86/ideapad-laptop.h b/drivers/platform/x86/lenovo/ideapad-laptop.h similarity index 100% rename from drivers/platform/x86/ideapad-laptop.h rename to drivers/platform/x86/lenovo/ideapad-laptop.h diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c similarity index 99% rename from drivers/platform/x86/think-lmi.c rename to drivers/platform/x86/lenovo/think-lmi.c index 02ede1ec99e9..34a47269e3d3 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -20,7 +20,7 @@ #include #include #include -#include "firmware_attributes_class.h" +#include "../firmware_attributes_class.h" #include "think-lmi.h" static bool debug_support; diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/lenovo/think-lmi.h similarity index 100% rename from drivers/platform/x86/think-lmi.h rename to drivers/platform/x86/lenovo/think-lmi.h diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c similarity index 99% rename from drivers/platform/x86/thinkpad_acpi.c rename to drivers/platform/x86/lenovo/thinkpad_acpi.c index e7350c9fa3aa..e1c7bd06fa12 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -81,7 +81,7 @@ #include #include -#include "dual_accel_detect.h" +#include "../dual_accel_detect.h" /* ThinkPad CMOS commands */ #define TP_CMOS_VOLUME_DOWN 0 diff --git a/drivers/platform/x86/lenovo-wmi-camera.c b/drivers/platform/x86/lenovo/wmi-camera.c similarity index 100% rename from drivers/platform/x86/lenovo-wmi-camera.c rename to drivers/platform/x86/lenovo/wmi-camera.c diff --git a/drivers/platform/x86/lenovo-wmi-hotkey-utilities.c b/drivers/platform/x86/lenovo/wmi-hotkey-utilities.c similarity index 100% rename from drivers/platform/x86/lenovo-wmi-hotkey-utilities.c rename to drivers/platform/x86/lenovo/wmi-hotkey-utilities.c diff --git a/drivers/platform/x86/lenovo-ymc.c b/drivers/platform/x86/lenovo/ymc.c similarity index 100% rename from drivers/platform/x86/lenovo-ymc.c rename to drivers/platform/x86/lenovo/ymc.c diff --git a/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c similarity index 99% rename from drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c rename to drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c index 25933cd018d1..b3fd6a35052a 100644 --- a/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c +++ b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c @@ -21,7 +21,7 @@ #include #include #include -#include "serdev_helpers.h" +#include "../serdev_helpers.h" #define YT2_1380_FC_PDEV_NAME "lenovo-yoga-tab2-pro-1380-fastcharger" #define YT2_1380_FC_SERDEV_CTRL "serial0" diff --git a/drivers/platform/x86/lenovo-yogabook.c b/drivers/platform/x86/lenovo/yogabook.c similarity index 100% rename from drivers/platform/x86/lenovo-yogabook.c rename to drivers/platform/x86/lenovo/yogabook.c From da8f2708f9b69707f4efeb432a18395e46b4666f Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 14 May 2025 22:10:52 +0200 Subject: [PATCH 08/63] platform/x86: ideapad: Expose charge_types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Ideapad models support a battery conservation mode which limits the battery charge threshold for longer battery longevity. This is currently exposed via a custom conservation_mode attribute in sysfs. The newly introduced charge_types sysfs attribute is a standardized replacement for laptops with a fixed end charge threshold. Setting it to `Long Life` would enable battery conservation mode. The standardized user space API would allow applications such as UPower to detect laptops which support this battery longevity mode and set it. Tested on an Lenovo ideapad U330p. Signed-off-by: Jelle van der Waa Suggested-By: Hans de Goede Reviewed-by: Thomas Weißschuh Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20250514201054.381320-1-jvanderwaa@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../obsolete/sysfs-platform-ideapad-laptop | 8 ++ .../ABI/testing/sysfs-platform-ideapad-laptop | 9 -- drivers/platform/x86/lenovo/Kconfig | 1 + drivers/platform/x86/lenovo/ideapad-laptop.c | 110 +++++++++++++++++- 4 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 Documentation/ABI/obsolete/sysfs-platform-ideapad-laptop diff --git a/Documentation/ABI/obsolete/sysfs-platform-ideapad-laptop b/Documentation/ABI/obsolete/sysfs-platform-ideapad-laptop new file mode 100644 index 000000000000..c1dbd19c679c --- /dev/null +++ b/Documentation/ABI/obsolete/sysfs-platform-ideapad-laptop @@ -0,0 +1,8 @@ +What: /sys/bus/platform/devices/VPC2004:*/conservation_mode +Date: Aug 2017 +KernelVersion: 4.14 +Contact: platform-driver-x86@vger.kernel.org +Description: + Controls whether the conservation mode is enabled or not. + This feature limits the maximum battery charge percentage to + around 50-60% in order to prolong the lifetime of the battery. diff --git a/Documentation/ABI/testing/sysfs-platform-ideapad-laptop b/Documentation/ABI/testing/sysfs-platform-ideapad-laptop index 4989ab266682..5ec0dee9e707 100644 --- a/Documentation/ABI/testing/sysfs-platform-ideapad-laptop +++ b/Documentation/ABI/testing/sysfs-platform-ideapad-laptop @@ -27,15 +27,6 @@ Description: * 1 -> Switched On * 0 -> Switched Off -What: /sys/bus/platform/devices/VPC2004:*/conservation_mode -Date: Aug 2017 -KernelVersion: 4.14 -Contact: platform-driver-x86@vger.kernel.org -Description: - Controls whether the conservation mode is enabled or not. - This feature limits the maximum battery charge percentage to - around 50-60% in order to prolong the lifetime of the battery. - What: /sys/bus/platform/devices/VPC2004:*/fn_lock Date: May 2018 KernelVersion: 4.18 diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index e9cb7372a2ca..05dab29a22f7 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -6,6 +6,7 @@ config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI + depends on ACPI_BATTERY depends on RFKILL && INPUT depends on SERIO_I8042 depends on BACKLIGHT_CLASS_DEVICE diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index ede483573fe0..21db9646443e 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include "ideapad-laptop.h" +#include #include #include @@ -162,6 +164,7 @@ struct ideapad_private { struct backlight_device *blightdev; struct ideapad_dytc_priv *dytc; struct dentry *debug; + struct acpi_battery_hook battery_hook; unsigned long cfg; unsigned long r_touchpad_val; struct { @@ -589,6 +592,11 @@ static ssize_t camera_power_store(struct device *dev, static DEVICE_ATTR_RW(camera_power); +static void show_conservation_mode_deprecation_warning(struct device *dev) +{ + dev_warn_once(dev, "conservation_mode attribute has been deprecated, see charge_types.\n"); +} + static ssize_t conservation_mode_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -597,6 +605,8 @@ static ssize_t conservation_mode_show(struct device *dev, unsigned long result; int err; + show_conservation_mode_deprecation_warning(dev); + err = eval_gbmd(priv->adev->handle, &result); if (err) return err; @@ -612,6 +622,8 @@ static ssize_t conservation_mode_store(struct device *dev, bool state; int err; + show_conservation_mode_deprecation_warning(dev); + err = kstrtobool(buf, &state); if (err) return err; @@ -1973,10 +1985,90 @@ static const struct dmi_system_id ctrl_ps2_aux_port_list[] = { {} }; -static void ideapad_check_features(struct ideapad_private *priv) +static int ideapad_psy_ext_set_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct ideapad_private *priv = ext_data; + + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: + return exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_ON); + case POWER_SUPPLY_CHARGE_TYPE_STANDARD: + return exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_OFF); + default: + return -EINVAL; + } +} + +static int ideapad_psy_ext_get_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ideapad_private *priv = ext_data; + unsigned long result; + int err; + + err = eval_gbmd(priv->adev->handle, &result); + if (err) + return err; + + if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result)) + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + + return 0; +} + +static int ideapad_psy_prop_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) +{ + return true; +} + +static const enum power_supply_property ideapad_power_supply_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPES, +}; + +static const struct power_supply_ext ideapad_battery_ext = { + .name = "ideapad_laptop", + .properties = ideapad_power_supply_props, + .num_properties = ARRAY_SIZE(ideapad_power_supply_props), + .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)), + .get_property = ideapad_psy_ext_get_prop, + .set_property = ideapad_psy_ext_set_prop, + .property_is_writeable = ideapad_psy_prop_is_writeable, +}; + +static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct ideapad_private *priv = container_of(hook, struct ideapad_private, battery_hook); + + return power_supply_register_extension(battery, &ideapad_battery_ext, + &priv->platform_device->dev, priv); +} + +static int ideapad_battery_remove(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &ideapad_battery_ext); + + return 0; +} + +static int ideapad_check_features(struct ideapad_private *priv) { acpi_handle handle = priv->adev->handle; unsigned long val; + int err; priv->features.set_fn_lock_led = set_fn_lock_led || dmi_check_system(set_fn_lock_led_list); @@ -1991,8 +2083,16 @@ static void ideapad_check_features(struct ideapad_private *priv) if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) priv->features.fan_mode = true; - if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) { priv->features.conservation_mode = true; + priv->battery_hook.add_battery = ideapad_battery_add; + priv->battery_hook.remove_battery = ideapad_battery_remove; + priv->battery_hook.name = "Ideapad Battery Extension"; + + err = devm_battery_hook_register(&priv->platform_device->dev, &priv->battery_hook); + if (err) + return err; + } if (acpi_has_method(handle, "DYTC")) priv->features.dytc = true; @@ -2027,6 +2127,8 @@ static void ideapad_check_features(struct ideapad_private *priv) } } } + + return 0; } #if IS_ENABLED(CONFIG_ACPI_WMI) @@ -2175,7 +2277,9 @@ static int ideapad_acpi_add(struct platform_device *pdev) if (err) return err; - ideapad_check_features(priv); + err = ideapad_check_features(priv); + if (err) + return err; ideapad_debugfs_init(priv); From 6418a8504187dc7f5b6f9d0649c03e362cb0664b Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 29 May 2025 11:18:37 -0700 Subject: [PATCH 09/63] platform/x86: thinkpad_acpi: Handle KCOV __init vs inline mismatches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When KCOV is enabled all functions get instrumented, unless the __no_sanitize_coverage attribute is used. To prepare for __no_sanitize_coverage being applied to __init functions[1], we have to handle differences in how GCC's inline optimizations get resolved. For thinkpad_acpi routines, this means forcing two functions to be inline with __always_inline. Link: https://lore.kernel.org/lkml/20250523043935.2009972-11-kees@kernel.org/ [1] Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20250529181831.work.439-kees@kernel.org Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index e1c7bd06fa12..d5bfaa3d0de5 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -559,12 +559,12 @@ static unsigned long __init tpacpi_check_quirks( return 0; } -static inline bool __pure __init tpacpi_is_lenovo(void) +static __always_inline bool __pure __init tpacpi_is_lenovo(void) { return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO; } -static inline bool __pure __init tpacpi_is_ibm(void) +static __always_inline bool __pure __init tpacpi_is_ibm(void) { return thinkpad_id.vendor == PCI_VENDOR_ID_IBM; } From 05651018f04ab00a84da8a47feb3a605ec1c7e41 Mon Sep 17 00:00:00 2001 From: Thomas Richard Date: Mon, 9 Jun 2025 14:34:49 +0200 Subject: [PATCH 10/63] platform/x86: lenovo-yoga-tab2-pro-1380-fastcharger: Use devm_pinctrl_register_mappings() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use devm_pinctrl_register_mappings(), so the core automatically unregisters the pinctrl mappings. It makes the code easier to read. Signed-off-by: Thomas Richard Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20250609-lenovo-yoga-tab2-pro-1380-fastcharger-devm-pinctrl-register-mappings-v1-1-fb601f2b80f6@bootlin.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../lenovo/yoga-tab2-pro-1380-fastcharger.c | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c index b3fd6a35052a..1b33c977f6d7 100644 --- a/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c +++ b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c @@ -240,30 +240,25 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev) int ret; /* Register pinctrl mappings for setting the UART3 pins mode */ - ret = pinctrl_register_mappings(yt2_1380_fc_pinctrl_map, - ARRAY_SIZE(yt2_1380_fc_pinctrl_map)); + ret = devm_pinctrl_register_mappings(&pdev->dev, yt2_1380_fc_pinctrl_map, + ARRAY_SIZE(yt2_1380_fc_pinctrl_map)); if (ret) return ret; /* And create the serdev to talk to the charger over the UART3 pins */ ctrl_dev = get_serdev_controller("PNP0501", "1", 0, YT2_1380_FC_SERDEV_CTRL); - if (IS_ERR(ctrl_dev)) { - ret = PTR_ERR(ctrl_dev); - goto out_pinctrl_unregister_mappings; - } + if (IS_ERR(ctrl_dev)) + return PTR_ERR(ctrl_dev); serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev)); put_device(ctrl_dev); - if (!serdev) { - ret = -ENOMEM; - goto out_pinctrl_unregister_mappings; - } + if (!serdev) + return -ENOMEM; ret = serdev_device_add(serdev); if (ret) { - dev_err_probe(&pdev->dev, ret, "adding serdev\n"); serdev_device_put(serdev); - goto out_pinctrl_unregister_mappings; + return dev_err_probe(&pdev->dev, ret, "adding serdev\n"); } /* @@ -273,20 +268,15 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev) ret = device_driver_attach(&yt2_1380_fc_serdev_driver.driver, &serdev->dev); if (ret) { /* device_driver_attach() maps EPROBE_DEFER to EAGAIN, map it back */ - ret = (ret == -EAGAIN) ? -EPROBE_DEFER : ret; - dev_err_probe(&pdev->dev, ret, "attaching serdev driver\n"); - goto out_serdev_device_remove; + serdev_device_remove(serdev); + return dev_err_probe(&pdev->dev, + (ret == -EAGAIN) ? -EPROBE_DEFER : ret, + "attaching serdev driver\n"); } /* So that yt2_1380_fc_pdev_remove() can remove the serdev */ platform_set_drvdata(pdev, serdev); return 0; - -out_serdev_device_remove: - serdev_device_remove(serdev); -out_pinctrl_unregister_mappings: - pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map); - return ret; } static void yt2_1380_fc_pdev_remove(struct platform_device *pdev) @@ -294,7 +284,6 @@ static void yt2_1380_fc_pdev_remove(struct platform_device *pdev) struct serdev_device *serdev = platform_get_drvdata(pdev); serdev_device_remove(serdev); - pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map); } static struct platform_driver yt2_1380_fc_pdev_driver = { From 5a7c909a53875e9c0c64cdf8e52b5716d8a74523 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 12 Jun 2025 09:48:35 +0200 Subject: [PATCH 11/63] platform/x86: silicom: remove unnecessary GPIO line direction check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As of commit 92ac7de3175e3 ("gpiolib: don't allow setting values on input lines"), the GPIO core makes sure values cannot be set on input lines. Remove the unnecessary check. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20250612074835.13800-1-brgl@bgdev.pl Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/silicom-platform.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/platform/x86/silicom-platform.c b/drivers/platform/x86/silicom-platform.c index 021f3fed197a..63b5da410ed5 100644 --- a/drivers/platform/x86/silicom-platform.c +++ b/drivers/platform/x86/silicom-platform.c @@ -248,13 +248,9 @@ static int silicom_gpio_direction_input(struct gpio_chip *gc, static int silicom_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { - int direction = silicom_gpio_get_direction(gc, offset); u8 *channels = gpiochip_get_data(gc); int channel = channels[offset]; - if (direction == GPIO_LINE_DIRECTION_IN) - return -EPERM; - silicom_mec_port_set(channel, !value); return 0; From d9926f09edabd81db86768fd87eff3e163f109e2 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Mon, 9 Jun 2025 12:21:13 +0200 Subject: [PATCH 12/63] platform/x86: fujitsu: use unsigned int for kstrtounit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The charge control threshold value ranges from 0-100. Signed-off-by: Jelle van der Waa Reviewed-by: Hans de Goede Acked-by: Jonathan Woithe Link: https://lore.kernel.org/r/20250609102115.36936-2-jvanderwaa@redhat.com [ij: use reverse xmas-tree order] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/fujitsu-laptop.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 162809140f68..c6ec61518160 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -180,7 +180,8 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, const char *buf, size_t count) { int cc_end_value, s006_cc_return; - int value, ret; + unsigned int value; + int ret; ret = kstrtouint(buf, 10, &value); if (ret) From dce77641056ea4fb2efab0a66d6929a5008d7235 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Mon, 9 Jun 2025 12:21:14 +0200 Subject: [PATCH 13/63] platform/x86: fujitsu: clamp charge_control_end_threshold values to 50 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow the sysfs ABI documentation that drivers should round written values to the nearest supported value instead of returning an error. Tested on a Fujitsu Lifebook U7720. Signed-off-by: Jelle van der Waa Reviewed-by: Hans de Goede Acked-by: Jonathan Woithe Link: https://lore.kernel.org/r/20250609102115.36936-3-jvanderwaa@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/fujitsu-laptop.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index c6ec61518160..931fbcdd21b8 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -187,9 +187,12 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, if (ret) return ret; - if (value < 50 || value > 100) + if (value > 100) return -EINVAL; + if (value < 50) + value = 50; + cc_end_value = value * 0x100 + 0x20; s006_cc_return = call_fext_func(fext, FUNC_S006_METHOD, CHARGE_CONTROL_RW, cc_end_value, 0x0); From d2b16853ad704ac7e1550d6faacdc53925494ebf Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Tue, 10 Jun 2025 10:41:07 +0200 Subject: [PATCH 14/63] platform: arm64: lenovo-yoga-c630: use the auxiliary device creation helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auxiliary device creation of this driver is simple enough to use the available auxiliary device creation helper. Use it and remove some boilerplate code. Signed-off-by: Jerome Brunet Reviewed-by: Bryan O'Donoghue Link: https://lore.kernel.org/r/20250610-yoga-aux-v1-1-d6115aa1683c@baylibre.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/arm64/lenovo-yoga-c630.c | 40 ++--------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c index 1f05c9a6a89d..75060c842b24 100644 --- a/drivers/platform/arm64/lenovo-yoga-c630.c +++ b/drivers/platform/arm64/lenovo-yoga-c630.c @@ -191,50 +191,16 @@ void yoga_c630_ec_unregister_notify(struct yoga_c630_ec *ec, struct notifier_blo } EXPORT_SYMBOL_GPL(yoga_c630_ec_unregister_notify); -static void yoga_c630_aux_release(struct device *dev) -{ - struct auxiliary_device *adev = to_auxiliary_dev(dev); - - kfree(adev); -} - -static void yoga_c630_aux_remove(void *data) -{ - struct auxiliary_device *adev = data; - - auxiliary_device_delete(adev); - auxiliary_device_uninit(adev); -} - static int yoga_c630_aux_init(struct device *parent, const char *name, struct yoga_c630_ec *ec) { struct auxiliary_device *adev; - int ret; - adev = kzalloc(sizeof(*adev), GFP_KERNEL); + adev = devm_auxiliary_device_create(parent, name, ec); if (!adev) - return -ENOMEM; + return -ENODEV; - adev->name = name; - adev->id = 0; - adev->dev.parent = parent; - adev->dev.release = yoga_c630_aux_release; - adev->dev.platform_data = ec; - - ret = auxiliary_device_init(adev); - if (ret) { - kfree(adev); - return ret; - } - - ret = auxiliary_device_add(adev); - if (ret) { - auxiliary_device_uninit(adev); - return ret; - } - - return devm_add_action_or_reset(parent, yoga_c630_aux_remove, adev); + return 0; } static int yoga_c630_ec_probe(struct i2c_client *client) From c9de2e5c15cba9e9be7fd124a74b9067560d4746 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 10 Jun 2025 07:55:25 +0200 Subject: [PATCH 15/63] Documentation: ABI: Update WMI device paths in ABI docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WMI driver core might append an ID to the WMI device name to avoid name collisions in case multiple WMI devices with the same GUID are present. Update all sysfs path referring to WMI devices to inform users about this important detail. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250610055526.23688-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../ABI/testing/sysfs-platform-dell-privacy-wmi | 8 ++++---- .../ABI/testing/sysfs-platform-intel-wmi-sbl-fw-update | 2 +- .../ABI/testing/sysfs-platform-intel-wmi-thunderbolt | 2 +- Documentation/admin-guide/thunderbolt.rst | 9 ++------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi b/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi index 1f1f274a6979..b4da7b2ea0ca 100644 --- a/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi +++ b/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi @@ -1,4 +1,4 @@ -What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type +What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919[-X]/dell_privacy_supported_type Date: Apr 2021 KernelVersion: 5.13 Contact: "" @@ -29,12 +29,12 @@ Description: For example to check which privacy devices are supported:: - # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type + # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919*/dell_privacy_supported_type [Microphone Mute] [supported] [Camera Shutter] [supported] [ePrivacy Screen] [unsupported] -What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state +What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919[-X]/dell_privacy_current_state Date: Apr 2021 KernelVersion: 5.13 Contact: "" @@ -66,6 +66,6 @@ Description: For example to check all supported current privacy device states:: - # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state + # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919*/dell_privacy_current_state [Microphone] [unmuted] [Camera Shutter] [unmuted] diff --git a/Documentation/ABI/testing/sysfs-platform-intel-wmi-sbl-fw-update b/Documentation/ABI/testing/sysfs-platform-intel-wmi-sbl-fw-update index 02ae1e9bbfc8..7ffd1579b8f7 100644 --- a/Documentation/ABI/testing/sysfs-platform-intel-wmi-sbl-fw-update +++ b/Documentation/ABI/testing/sysfs-platform-intel-wmi-sbl-fw-update @@ -1,4 +1,4 @@ -What: /sys/bus/wmi/devices/44FADEB1-B204-40F2-8581-394BBDC1B651/firmware_update_request +What: /sys/bus/wmi/devices/44FADEB1-B204-40F2-8581-394BBDC1B651[-X]/firmware_update_request Date: April 2020 KernelVersion: 5.7 Contact: "Jithu Joseph" diff --git a/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt index fd3a7ec79760..10ef1282c9d2 100644 --- a/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt +++ b/Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt @@ -1,4 +1,4 @@ -What: /sys/devices/platform//force_power +What: /sys/bus/wmi/devices/86CCFD48-205E-4A77-9C48-2021CBEDE341[-X]/force_power Date: September 2017 KernelVersion: 4.15 Contact: "Mario Limonciello" diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst index 240fee618e06..102c693c8f81 100644 --- a/Documentation/admin-guide/thunderbolt.rst +++ b/Documentation/admin-guide/thunderbolt.rst @@ -358,12 +358,7 @@ Forcing power Many OEMs include a method that can be used to force the power of a Thunderbolt controller to an "On" state even if nothing is connected. If supported by your machine this will be exposed by the WMI bus with -a sysfs attribute called "force_power". - -For example the intel-wmi-thunderbolt driver exposes this attribute in: - /sys/bus/wmi/devices/86CCFD48-205E-4A77-9C48-2021CBEDE341/force_power - - To force the power to on, write 1 to this attribute file. - To disable force power, write 0 to this attribute file. +a sysfs attribute called "force_power", see +Documentation/ABI/testing/sysfs-platform-intel-wmi-thunderbolt for details. Note: it's currently not possible to query the force power state of a platform. From 73f0f2b52c5ea67b3140b23f58d8079d158839c8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 10 Jun 2025 07:55:26 +0200 Subject: [PATCH 16/63] platform/x86: wmi: Fix WMI device naming issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple WMI devices with the same GUID are present inside a given system, the WMI driver core might fail to register all of them. Consider the following scenario: WMI devices ([-]): 05901221-D566-11D1-B2F0-00A0C9062910 (on PNP0C14:00) 05901221-D566-11D1-B2F0-00A0C9062910-1 (on PNP0C14:01) If the WMI core driver somehow unbinds from PNP0C14:00, the following will happen upon rebinding: 1. The WMI driver core counts all registered WMI devices with a GUID of 05901221-D566-11D1-B2F0-00A0C9062910 (count: 1). 2. The new WMI device will be named "05901221-D566-11D1-B2F0-00A0C9062910-1" because another device with the same GUID is already registered (on PNP0C14:01). 3. The new WMI device cannot be registered due to a name conflict. Use a IDA when building the WMI device name to avoid such name collisions by ensuring that a given WMI device ID is not reused. Userspace applications using udev for WMI device detection are not impacted by this change. Additionally userspace applications that do fully support the existing naming scheme are also not impacted. Only userspace applications using hardcoded sysfs paths will break. Introduce a kconfig option for restoring the old naming scheme to give developers time to fix any compatibility issues. Tested on a Asus Prime B650-Plus. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250610055526.23688-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/Kconfig | 9 +++++++++ drivers/platform/x86/wmi.c | 37 +++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 43055df44827..49ca98df47fd 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -37,6 +37,15 @@ config ACPI_WMI It is safe to enable this driver even if your DSDT doesn't define any ACPI-WMI devices. +config ACPI_WMI_LEGACY_DEVICE_NAMES + bool "Use legacy WMI device naming scheme" + depends on ACPI_WMI + help + Say Y here to force the WMI driver core to use the old WMI device naming + scheme when creating WMI devices. Doing so might be necessary for some + userspace applications but will cause the registration of WMI devices with + the same GUID to fail in some corner cases. + config WMI_BMOF tristate "WMI embedded Binary MOF driver" depends on ACPI_WMI diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index e46453750d5f..21b7e54bd7ab 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,8 @@ struct wmi_guid_count_context { int count; }; +static DEFINE_IDA(wmi_ida); + /* * If the GUID data block is marked as expensive, we must enable and * explicitily disable data collection. @@ -978,6 +981,19 @@ static int guid_count(const guid_t *guid) return context.count; } +static int wmi_dev_set_name(struct wmi_block *wblock, int count) +{ + if (IS_ENABLED(CONFIG_ACPI_WMI_LEGACY_DEVICE_NAMES)) { + if (count) + return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, + count); + else + return dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid); + } + + return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, wblock->dev.dev.id); +} + static int wmi_create_device(struct device *wmi_bus_dev, struct wmi_block *wblock, struct acpi_device *device) @@ -986,7 +1002,7 @@ static int wmi_create_device(struct device *wmi_bus_dev, struct acpi_device_info *info; acpi_handle method_handle; acpi_status status; - int count; + int count, ret; if (wblock->gblock.flags & ACPI_WMI_EVENT) { wblock->dev.dev.type = &wmi_type_event; @@ -1057,11 +1073,18 @@ static int wmi_create_device(struct device *wmi_bus_dev, if (count < 0) return count; - if (count) { - dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count); + if (count) set_bit(WMI_GUID_DUPLICATED, &wblock->flags); - } else { - dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid); + + ret = ida_alloc(&wmi_ida, GFP_KERNEL); + if (ret < 0) + return ret; + + wblock->dev.dev.id = ret; + ret = wmi_dev_set_name(wblock, count); + if (ret < 0) { + ida_free(&wmi_ida, wblock->dev.dev.id); + return ret; } device_initialize(&wblock->dev.dev); @@ -1147,6 +1170,7 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev) dev_err(wmi_bus_dev, "failed to register %pUL\n", &wblock->gblock.guid); + ida_free(&wmi_ida, wblock->dev.dev.id); put_device(&wblock->dev.dev); } } @@ -1246,7 +1270,10 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context static int wmi_remove_device(struct device *dev, void *data) { + int id = dev->id; + device_unregister(dev); + ida_free(&wmi_ida, id); return 0; } From a8fc1224f2318d3e5948671d1cad458e6372d921 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 9 Jun 2025 12:46:19 +0200 Subject: [PATCH 17/63] platform/x86: x86-android-tablets: Add generic_lipo_4v2_battery info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the asus_tf103c_battery_node to shared-psy-info.c and rename it to generic_lipo_4v2_battery_node. This is a preparation patch for adding ovc-capacity-table info to the battery nodes. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20250609104620.25896-1-hansg@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/x86-android-tablets/asus.c | 21 +++---------------- .../x86/x86-android-tablets/shared-psy-info.c | 16 ++++++++++++++ .../x86/x86-android-tablets/shared-psy-info.h | 1 + 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/asus.c b/drivers/platform/x86/x86-android-tablets/asus.c index 7dde63b9943f..97cd14c1fd23 100644 --- a/drivers/platform/x86/x86-android-tablets/asus.c +++ b/drivers/platform/x86/x86-android-tablets/asus.c @@ -206,24 +206,9 @@ static const struct software_node asus_tf103c_touchscreen_node = { .properties = asus_tf103c_touchscreen_props, }; -static const struct property_entry asus_tf103c_battery_props[] = { - PROPERTY_ENTRY_STRING("compatible", "simple-battery"), - PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"), - PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), - PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), - PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000), - PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000), - PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), - { } -}; - -static const struct software_node asus_tf103c_battery_node = { - .properties = asus_tf103c_battery_props, -}; - static const struct property_entry asus_tf103c_bq24190_props[] = { PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1), - PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node), PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), PROPERTY_ENTRY_BOOL("omit-battery-class"), PROPERTY_ENTRY_BOOL("disable-reset"), @@ -236,7 +221,7 @@ static const struct software_node asus_tf103c_bq24190_node = { static const struct property_entry asus_tf103c_ug3105_props[] = { PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1), - PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node), PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000), { } }; @@ -321,6 +306,6 @@ const struct x86_dev_info asus_tf103c_info __initconst = { .gpio_button = &asus_me176c_tf103c_lid, .gpio_button_count = 1, .gpiod_lookup_tables = asus_tf103c_gpios, - .bat_swnode = &asus_tf103c_battery_node, + .bat_swnode = &generic_lipo_4v2_battery_node, .modules = bq24190_modules, }; diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c index a46fa15acfb1..55da57477153 100644 --- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c +++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c @@ -39,6 +39,22 @@ const struct software_node fg_bq25890_supply_node = { .properties = fg_bq25890_supply_props, }; +/* Standard LiPo (max 4.2V) settings used by most devs with a LiPo battery */ +static const struct property_entry generic_lipo_4v2_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + { } +}; + +const struct software_node generic_lipo_4v2_battery_node = { + .properties = generic_lipo_4v2_battery_props, +}; + /* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV battery */ static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { PROPERTY_ENTRY_STRING("compatible", "simple-battery"), diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h index c2d2968cddc2..bcf9845ad275 100644 --- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h +++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h @@ -21,6 +21,7 @@ extern const char * const bq25890_psy[]; extern const struct software_node fg_bq24190_supply_node; extern const struct software_node fg_bq25890_supply_node; +extern const struct software_node generic_lipo_4v2_battery_node; extern const struct software_node generic_lipo_hv_4v35_battery_node; extern struct bq24190_platform_data bq24190_pdata; From be91bf40a96d567973d5c5e870d1464eb51b6c42 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 9 Jun 2025 12:46:20 +0200 Subject: [PATCH 18/63] platform/x86: x86-android-tablets: Add ovc-capacity-table info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ovc-capacity-table info to the generic battery nodes. The values come from the ug3105 driver which currently hardcodes these values. The ug3105 driver will be modified to stop hardcoding this and instead get the values from device-properties. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20250609104620.25896-2-hansg@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../x86/x86-android-tablets/shared-psy-info.c | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c index 55da57477153..fe34cedb6257 100644 --- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c +++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c @@ -39,6 +39,58 @@ const struct software_node fg_bq25890_supply_node = { .properties = fg_bq25890_supply_props, }; +static const u32 generic_lipo_battery_ovc_cap_celcius[] = { 25 }; + +static const u32 generic_lipo_4v2_battery_ovc_cap_table0[] = { + 4200000, 100, + 4150000, 95, + 4110000, 90, + 4075000, 85, + 4020000, 80, + 3982500, 75, + 3945000, 70, + 3907500, 65, + 3870000, 60, + 3853333, 55, + 3836667, 50, + 3820000, 45, + 3803333, 40, + 3786667, 35, + 3770000, 30, + 3750000, 25, + 3730000, 20, + 3710000, 15, + 3690000, 10, + 3610000, 5, + 3350000, 0 +}; + +static const u32 generic_lipo_hv_4v35_battery_ovc_cap_table0[] = { + 4300000, 100, + 4250000, 96, + 4200000, 91, + 4150000, 86, + 4110000, 82, + 4075000, 77, + 4020000, 73, + 3982500, 68, + 3945000, 64, + 3907500, 59, + 3870000, 55, + 3853333, 50, + 3836667, 45, + 3820000, 41, + 3803333, 36, + 3786667, 32, + 3770000, 27, + 3750000, 23, + 3730000, 18, + 3710000, 14, + 3690000, 9, + 3610000, 5, + 3350000, 0 +}; + /* Standard LiPo (max 4.2V) settings used by most devs with a LiPo battery */ static const struct property_entry generic_lipo_4v2_battery_props[] = { PROPERTY_ENTRY_STRING("compatible", "simple-battery"), @@ -48,6 +100,10 @@ static const struct property_entry generic_lipo_4v2_battery_props[] = { PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000), PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000), PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius", + generic_lipo_battery_ovc_cap_celcius), + PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0", + generic_lipo_4v2_battery_ovc_cap_table0), { } }; @@ -64,6 +120,10 @@ static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000), PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000), PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius", + generic_lipo_battery_ovc_cap_celcius), + PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0", + generic_lipo_hv_4v35_battery_ovc_cap_table0), { } }; From 57139e126a30ce64f111c78b1b9e37b39a2b7424 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 1 Jul 2025 20:38:21 -0700 Subject: [PATCH 19/63] platform/x86: Add lenovo-wmi-* driver Documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds documentation for new lenovo-wmi drivers. Reviewed-by: Alok Tiwari Reviewed-by: Armin Wolf Reviewed-by: Mario Limonciello Signed-off-by: Derek J. Clark Link: https://lore.kernel.org/r/20250702033826.1057762-2-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../wmi/devices/lenovo-wmi-gamezone.rst | 203 ++++++++++++++++++ .../wmi/devices/lenovo-wmi-other.rst | 108 ++++++++++ MAINTAINERS | 3 + 3 files changed, 314 insertions(+) create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst create mode 100644 Documentation/wmi/devices/lenovo-wmi-other.rst diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst new file mode 100644 index 000000000000..997263e51a7d --- /dev/null +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst @@ -0,0 +1,203 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +========================================================== +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone) +========================================================== + +Introduction +============ +The Lenovo WMI gamezone interface is broken up into multiple GUIDs, +The primary "Gamezone" GUID provides advanced features such as fan +profiles and overclocking. It is paired with multiple event GUIDs +and data block GUIDs that provide context for the various methods. + +Gamezone Data +------------- + +WMI GUID ``887B54E3-DDDC-4B2C-8B88-68A26A8835D0`` + +The Gamezone Data WMI interface provides platform-profile and fan curve +settings for devices that fall under the "Gaming Series" of Lenovo devices. +It uses a notifier chain to inform other Lenovo WMI interface drivers of the +current platform profile when it changes. + +The following platform profiles are supported: + - low-power + - balanced + - balanced-performance + - performance + - custom + +Balanced-Performance +~~~~~~~~~~~~~~~~~~~~ +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile +enabled in their BIOS. For these devices, the performance platform profile +corresponds to the BIOS Extreme Mode, while the balanced-performance +platform profile corresponds to the BIOS Performance mode. For legacy +devices, the performance platform profile will correspond with the BIOS +Performance mode. + +For some newer devices the "Extreme Mode" profile is incomplete in the BIOS +and setting it will cause undefined behavior. A BIOS bug quirk table is +provided to ensure these devices cannot set "Extreme Mode" from the driver. + +Custom Profile +~~~~~~~~~~~~~~ +The custom profile represents a hardware mode on Lenovo devices that enables +user modifications to Package Power Tracking (PPT) and fan curve settings. +When an attribute exposed by the Other Mode WMI interface is to be modified, +the Gamezone driver must first be switched to the "custom" profile manually, +or the setting will have no effect. If another profile is set from the list +of supported profiles, the BIOS will override any user PPT settings when +switching to that profile. + +Gamezone Thermal Mode Event +--------------------------- + +WMI GUID ``D320289E-8FEA-41E0-86F9-911D83151B5F`` + +The Gamezone Thermal Mode Event interface notifies the system when the platform +profile has changed, either through the hardware event (Fn+Q for laptops or +Legion + Y for Go Series), or through the Gamezone WMI interface. This event is +implemented in the Lenovo WMI Events driver (lenovo-wmi-events). + + +WMI interface description +========================= + +The WMI interface description can be decoded from the embedded binary MOF (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")] + class LENOVO_GAMEZONE_DATA { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data); + [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data); + [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data); + [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data); + [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data); + [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data); + [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data); + [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data); + [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data); + [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data); + [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data); + [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data); + [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data); + [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data); + [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data); + [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data); + [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data); + [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data); + [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data); + [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data); + [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data); + [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data); + [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data); + [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data); + [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode); + [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data); + [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data); + [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data); + [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data); + [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data); + [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data); + [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data); + [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data); + [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data); + [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data); + [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data); + [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data); + [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data); + [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data); + [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data); + [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data); + [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data); + [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data); + [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data); + [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data); + [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data); + [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data); + [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data); + [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data); + [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data); + [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data); + [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data); + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")] + class LENOVO_GAMEZONE_CPU_OC_DATA { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid; + [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue; + [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue; + [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue; + [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue; + [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue; + [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid; + [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid; + [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")] + class LENOVO_GAMEZONE_GPU_OC_DATA { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID; + [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID; + [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue; + [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq; + [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset; + [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset; + [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale; + [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid; + [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")] + class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")] + class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode; + [WmiDataId(2), read, Description("version of FN+Q")] uint32 version; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")] + class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")] + class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")] + class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode; + }; diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst new file mode 100644 index 000000000000..d7928b8dfb4b --- /dev/null +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -0,0 +1,108 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=========================================================== +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other) +=========================================================== + +Introduction +============ +Lenovo WMI Other Mode interface is broken up into multiple GUIDs, +The primary Other Mode interface provides advanced power tuning features +such as Package Power Tracking (PPT). It is paired with multiple data block +GUIDs that provide context for the various methods. + + +Other Mode +---------- + +WMI GUID ``DC2A8805-3A8C-41BA-A6F7-092E0089CD3B`` + +The Other Mode WMI interface uses the firmware_attributes class to expose +various WMI attributes provided by the interface in the sysfs. This enables +CPU and GPU power limit tuning as well as various other attributes for +devices that fall under the "Gaming Series" of Lenovo devices. Each +attribute exposed by the Other Mode interface has corresponding +capability data blocks which allow the driver to probe details about the +attribute. Each attribute has multiple pages, one for each of the platform +profiles managed by the Gamezone interface. Attributes are exposed in sysfs +under the following path: + +:: + + /sys/class/firmware-attributes/lenovo-wmi-other/attributes// + +LENOVO_CAPABILITY_DATA_01 +------------------------- + +WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` + +The LENOVO_CAPABILITY_DATA_01 interface provides information on various +power limits of integrated CPU and GPU components. + +Each attribute has the following properties: + - current_value + - default_value + - display_name + - max_value + - min_value + - scalar_increment + - type + +The following attributes are implemented: + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking + + +WMI interface description +========================= + +The WMI interface description can be decoded from the embedded binary MOF (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")] + class LENOVO_OTHER_METHOD { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value); + [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value); + [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]); + [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]); + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")] + class LENOVO_CAPABILITY_DATA_00 { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description(" IDs.")] uint32 IDs; + [WmiDataId(2), read, Description("Capability.")] uint32 Capability; + [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")] + class LENOVO_CAPABILITY_DATA_01 { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description(" IDs.")] uint32 IDs; + [WmiDataId(2), read, Description("Capability.")] uint32 Capability; + [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue; + [WmiDataId(4), read, Description("Step.")] uint32 Step; + [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue; + [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")] + class LENOVO_CAPABILITY_DATA_02 { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description(" IDs.")] uint32 IDs; + [WmiDataId(2), read, Description("Capability.")] uint32 Capability; + [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; + [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[]; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 2da44049e9e0..5b67869bbc87 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13673,8 +13673,11 @@ F: drivers/usb/misc/legousbtower.c LENOVO drivers M: Mark Pearson +M: Derek J. Clark L: platform-driver-x86@vger.kernel.org S: Maintained +F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst +F: Documentation/wmi/devices/lenovo-wmi-other.rst F: drivers/platform/x86/lenovo/* LENOVO WMI HOTKEY UTILITIES DRIVER From e521d16e76cd9ea99c585e064f4e7daf657b1451 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 1 Jul 2025 20:38:22 -0700 Subject: [PATCH 20/63] platform/x86: Add lenovo-wmi-helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds lenovo-wmi-helpers, which provides a common wrapper function for wmidev_evaluate_method that does data validation and error handling. Reviewed-by: Alok Tiwari Reviewed-by: Armin Wolf Reviewed-by: Mario Limonciello Signed-off-by: Derek J. Clark Link: https://lore.kernel.org/r/20250702033826.1057762-3-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/Kconfig | 4 ++ drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/wmi-helpers.c | 74 +++++++++++++++++++++++ drivers/platform/x86/lenovo/wmi-helpers.h | 20 ++++++ 4 files changed, 99 insertions(+) create mode 100644 drivers/platform/x86/lenovo/wmi-helpers.c create mode 100644 drivers/platform/x86/lenovo/wmi-helpers.h diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 05dab29a22f7..abb488889834 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -232,3 +232,7 @@ config YT2_1380 To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. + +config LENOVO_WMI_HELPERS + tristate + depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index f1aa2449293b..a347d657d235 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -12,6 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o +lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o # Add 'lenovo' prefix to each module listed in lenovo-target-* define LENOVO_OBJ_TARGET diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c new file mode 100644 index 000000000000..f6fef6296251 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Legion WMI helpers driver. + * + * The Lenovo Legion WMI interface is broken up into multiple GUID interfaces + * that require cross-references between GUID's for some functionality. The + * "Custom Mode" interface is a legacy interface for managing and displaying + * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface + * is a modern interface that replaces or extends the "Custom Mode" interface + * methods. The "Gamezone" interface adds advanced features such as fan + * profiles and overclocking. The "Lighting" interface adds control of various + * status lights related to different hardware components. Each of these + * drivers uses a common procedure to get data from the WMI interface, + * enumerated here. + * + * Copyright (C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include + +#include "wmi-helpers.h" + +/** + * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that + * return an integer. + * @wdev: Pointer to the WMI device to be called. + * @instance: Instance of the called method. + * @method_id: WMI Method ID for the method to be called. + * @buf: Buffer of all arguments for the given method_id. + * @size: Length of the buffer. + * @retval: Pointer for the return value to be assigned. + * + * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI + * integer. Validates the return value type and assigns the value to the + * retval pointer. + * + * Return: 0 on success, or an error code. + */ +int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, + unsigned char *buf, size_t size, u32 *retval) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *ret_obj __free(kfree) = NULL; + struct acpi_buffer input = { size, buf }; + acpi_status status; + + status = wmidev_evaluate_method(wdev, instance, method_id, &input, + &output); + if (ACPI_FAILURE(status)) + return -EIO; + + if (retval) { + ret_obj = output.pointer; + if (!ret_obj) + return -ENODATA; + + if (ret_obj->type != ACPI_TYPE_INTEGER) + return -ENXIO; + + *retval = (u32)ret_obj->integer.value; + } + + return 0; +}; +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "LENOVO_WMI_HELPERS"); + +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-helpers.h b/drivers/platform/x86/lenovo/wmi-helpers.h new file mode 100644 index 000000000000..20fd21749803 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-helpers.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright (C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_WMI_HELPERS_H_ +#define _LENOVO_WMI_HELPERS_H_ + +#include + +struct wmi_device; + +struct wmi_method_args_32 { + u32 arg0; + u32 arg1; +}; + +int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, + unsigned char *buf, size_t size, u32 *retval); + +#endif /* !_LENOVO_WMI_HELPERS_H_ */ From 949bf144bdc72e87018197ae71aa4959f17885d5 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 1 Jul 2025 20:38:23 -0700 Subject: [PATCH 21/63] platform/x86: Add Lenovo WMI Events Driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds lenovo-wmi-events driver. The events driver is designed as a general entrypoint for all Lenovo WMI Events. It acts as a notification chain head that will process event data and pass it on to registered drivers so they can react to the events. Currently only the Gamezone interface Thermal Mode Event GUID is implemented in this driver. It is documented in the Gamezone documentation. Suggested-by: Armin Wolf Reviewed-by: Alok Tiwari Reviewed-by: Armin Wolf Reviewed-by: Mario Limonciello Signed-off-by: Derek J. Clark Link: https://lore.kernel.org/r/20250702033826.1057762-4-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/Kconfig | 4 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/wmi-events.c | 196 +++++++++++++++++++++++ drivers/platform/x86/lenovo/wmi-events.h | 20 +++ 4 files changed, 221 insertions(+) create mode 100644 drivers/platform/x86/lenovo/wmi-events.c create mode 100644 drivers/platform/x86/lenovo/wmi-events.h diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index abb488889834..95dce00f257e 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -233,6 +233,10 @@ config YT2_1380 To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. +config LENOVO_WMI_EVENTS + tristate + depends on ACPI_WMI + config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index a347d657d235..814c4ba9bdbe 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -12,6 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o +lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o # Add 'lenovo' prefix to each module listed in lenovo-target-* diff --git a/drivers/platform/x86/lenovo/wmi-events.c b/drivers/platform/x86/lenovo/wmi-events.c new file mode 100644 index 000000000000..0994cd7dd504 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-events.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various + * hardware triggered events that many drivers need to have propagated. + * This driver provides a uniform entrypoint for these events so that + * any driver that needs to respond to these events can subscribe to a + * notifier chain. + * + * Copyright (C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include + +#include "wmi-events.h" +#include "wmi-gamezone.h" + +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F" + +#define LWMI_EVENT_DEVICE(guid, type) \ + .guid_string = (guid), .context = &(enum lwmi_events_type) \ + { \ + type \ + } + +static BLOCKING_NOTIFIER_HEAD(events_chain_head); + +struct lwmi_events_priv { + struct wmi_device *wdev; + enum lwmi_events_type type; +}; + +/** + * lwmi_events_register_notifier() - Add a notifier to the notifier chain. + * @nb: The notifier_block struct to register + * + * Call blocking_notifier_chain_register to register the notifier block to the + * lenovo-wmi-events driver blocking notifier chain. + * + * Return: 0 on success, %-EEXIST on error. + */ +int lwmi_events_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&events_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS"); + +/** + * lwmi_events_unregister_notifier() - Remove a notifier from the notifier + * chain. + * @nb: The notifier_block struct to unregister + * + * Call blocking_notifier_chain_unregister to unregister the notifier block + * from the lenovo-wmi-events driver blocking notifier chain. + * + * Return: 0 on success, %-ENOENT on error. + */ +int lwmi_events_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&events_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS"); + +/** + * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier + * chain. + * @data: Void pointer to the notifier_block struct to unregister. + * + * Call lwmi_events_unregister_notifier to unregister the notifier block from + * the lenovo-wmi-events driver blocking notifier chain. + * + * Return: 0 on success, %-ENOENT on error. + */ +static void devm_lwmi_events_unregister_notifier(void *data) +{ + struct notifier_block *nb = data; + + lwmi_events_unregister_notifier(nb); +} + +/** + * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain. + * @dev: The parent device of the notifier_block struct. + * @nb: The notifier_block struct to register + * + * Call lwmi_events_register_notifier to register the notifier block to the + * lenovo-wmi-events driver blocking notifier chain. Then add, as a device + * managed action, unregister_notifier to automatically unregister the + * notifier block upon its parent device removal. + * + * Return: 0 on success, or an error code. + */ +int devm_lwmi_events_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret = lwmi_events_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS"); + +/** + * lwmi_events_notify() - Call functions for the notifier call chain. + * @wdev: The parent WMI device of the driver. + * @obj: ACPI object passed by the registered WMI Event. + * + * Validate WMI event data and notify all registered drivers of the event and + * its output. + * + * Return: 0 on success, or an error code. + */ +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj) +{ + struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev); + int sel_prof; + int ret; + + switch (priv->type) { + case LWMI_EVENT_THERMAL_MODE: + if (obj->type != ACPI_TYPE_INTEGER) + return; + + sel_prof = obj->integer.value; + + switch (sel_prof) { + case LWMI_GZ_THERMAL_MODE_QUIET: + case LWMI_GZ_THERMAL_MODE_BALANCED: + case LWMI_GZ_THERMAL_MODE_PERFORMANCE: + case LWMI_GZ_THERMAL_MODE_EXTREME: + case LWMI_GZ_THERMAL_MODE_CUSTOM: + ret = blocking_notifier_call_chain(&events_chain_head, + LWMI_EVENT_THERMAL_MODE, + &sel_prof); + if (ret == NOTIFY_BAD) + dev_err(&wdev->dev, + "Failed to send notification to call chain for WMI Events\n"); + return; + default: + dev_err(&wdev->dev, "Got invalid thermal mode: %x", + sel_prof); + return; + } + break; + default: + return; + } +} + +static int lwmi_events_probe(struct wmi_device *wdev, const void *context) +{ + struct lwmi_events_priv *priv; + + if (!context) + return -EINVAL; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev = wdev; + priv->type = *(enum lwmi_events_type *)context; + dev_set_drvdata(&wdev->dev, priv); + + return 0; +} + +static const struct wmi_device_id lwmi_events_id_table[] = { + { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) }, + {} +}; + +static struct wmi_driver lwmi_events_driver = { + .driver = { + .name = "lenovo_wmi_events", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = lwmi_events_id_table, + .probe = lwmi_events_probe, + .notify = lwmi_events_notify, + .no_singleton = true, +}; + +module_wmi_driver(lwmi_events_driver); + +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo WMI Events Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-events.h b/drivers/platform/x86/lenovo/wmi-events.h new file mode 100644 index 000000000000..cd34e886912c --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-events.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright (C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_WMI_EVENTS_H_ +#define _LENOVO_WMI_EVENTS_H_ + +struct device; +struct notifier_block; + +enum lwmi_events_type { + LWMI_EVENT_THERMAL_MODE = 1, +}; + +int lwmi_events_register_notifier(struct notifier_block *nb); +int lwmi_events_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_events_register_notifier(struct device *dev, + struct notifier_block *nb); + +#endif /* !_LENOVO_WMI_EVENTS_H_ */ From e1a5fe662b593108d14cd0481019601698f9fbe8 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 1 Jul 2025 20:38:24 -0700 Subject: [PATCH 22/63] platform/x86: Add Lenovo Capability Data 01 WMI Driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds lenovo-wmi-capdata01 driver which provides the LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default_value, max_value, min_value, and step increment. Reviewed-by: Alok Tiwari Reviewed-by: Armin Wolf Signed-off-by: Derek J. Clark Link: https://lore.kernel.org/r/20250702033826.1057762-5-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/Kconfig | 4 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/wmi-capdata01.c | 302 ++++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata01.h | 25 ++ 4 files changed, 332 insertions(+) create mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c create mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 95dce00f257e..1943e3a24f43 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -233,6 +233,10 @@ config YT2_1380 To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. +config LENOVO_WMI_DATA01 + tristate + depends on ACPI_WMI + config LENOVO_WMI_EVENTS tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 814c4ba9bdbe..1ce970e4ddb8 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -12,6 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o +lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c new file mode 100644 index 000000000000..c922680b3cba --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-capdata01.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Capability Data 01 WMI Data Block driver. + * + * Lenovo Capability Data 01 provides information on tunable attributes used by + * the "Other Mode" WMI interface. The data includes if the attribute is + * supported by the hardware, the default_value, max_value, min_value, and step + * increment. Each attribute has multiple pages, one for each of the thermal + * modes managed by the Gamezone interface. + * + * Copyright (C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmi-capdata01.h" + +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" + +#define ACPI_AC_CLASS "ac_adapter" +#define ACPI_AC_NOTIFY_STATUS 0x80 + +struct lwmi_cd01_priv { + struct notifier_block acpi_nb; /* ACPI events */ + struct wmi_device *wdev; + struct cd01_list *list; +}; + +struct cd01_list { + struct mutex list_mutex; /* list R/W mutex */ + u8 count; + struct capdata01 data[]; +}; + +/** + * lwmi_cd01_component_bind() - Bind component to master device. + * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device. + * @om_dev: Pointer to the lenovo-wmi-other driver parent device. + * @data: capdata01_list object pointer used to return the capability data. + * + * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01 + * list. This is used to call lwmi_cd01_get_data to look up attribute data + * from the lenovo-wmi-other driver. + * + * Return: 0 + */ +static int lwmi_cd01_component_bind(struct device *cd01_dev, + struct device *om_dev, void *data) +{ + struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev); + struct cd01_list **cd01_list = data; + + *cd01_list = priv->list; + + return 0; +} + +static const struct component_ops lwmi_cd01_component_ops = { + .bind = lwmi_cd01_component_bind, +}; + +/** + * lwmi_cd01_get_data - Get the data of the specified attribute + * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct. + * @attribute_id: The capdata attribute ID to be found. + * @output: Pointer to a capdata01 struct to return the data. + * + * Retrieves the capability data 01 struct pointer for the given + * attribute for its specified thermal mode. + * + * Return: 0 on success, or -EINVAL. + */ +int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output) +{ + u8 idx; + + guard(mutex)(&list->list_mutex); + for (idx = 0; idx < list->count; idx++) { + if (list->data[idx].id != attribute_id) + continue; + memcpy(output, &list->data[idx], sizeof(list->data[idx])); + return 0; + }; + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01"); + +/** + * lwmi_cd01_cache() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata01 driver data. + * + * Loop through each WMI data block and cache the data. + * + * Return: 0 on success, or an error. + */ +static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) +{ + int idx; + + guard(mutex)(&priv->list->list_mutex); + for (idx = 0; idx < priv->list->count; idx++) { + union acpi_object *ret_obj __free(kfree) = NULL; + + ret_obj = wmidev_block_query(priv->wdev, idx); + if (!ret_obj) + return -ENODEV; + + if (ret_obj->type != ACPI_TYPE_BUFFER || + ret_obj->buffer.length < sizeof(priv->list->data[idx])) + continue; + + memcpy(&priv->list->data[idx], ret_obj->buffer.pointer, + ret_obj->buffer.length); + } + + return 0; +} + +/** + * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata + * @priv: lenovo-wmi-capdata01 driver data. + * + * Allocate a cd01_list struct large enough to contain data from all WMI data + * blocks provided by the interface. + * + * Return: 0 on success, or an error. + */ +static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) +{ + struct cd01_list *list; + size_t list_size; + int count, ret; + + count = wmidev_instance_count(priv->wdev); + list_size = struct_size(list, data, count); + + list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); + if (!list) + return -ENOMEM; + + ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); + if (ret) + return ret; + + list->count = count; + priv->list = list; + + return 0; +} + +/** + * lwmi_cd01_setup() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata01 driver data. + * + * Allocate a cd01_list struct large enough to contain data from all WMI data + * blocks provided by the interface. Then loop through each data block and + * cache the data. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv) +{ + int ret; + + ret = lwmi_cd01_alloc(priv); + if (ret) + return ret; + + return lwmi_cd01_cache(priv); +} + +/** + * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier. + * block call chain. + * @nb: The notifier_block registered to lenovo-wmi-events driver. + * @action: Unused. + * @data: The ACPI event. + * + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile + * of a change. + * + * Return: notifier_block status. + */ +static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct acpi_bus_event *event = data; + struct lwmi_cd01_priv *priv; + int ret; + + if (strcmp(event->device_class, ACPI_AC_CLASS) != 0) + return NOTIFY_DONE; + + priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb); + + switch (event->type) { + case ACPI_AC_NOTIFY_STATUS: + ret = lwmi_cd01_cache(priv); + if (ret) + return NOTIFY_BAD; + + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +/** + * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block. + * @data: The ACPI event notifier_block to unregister. + */ +static void lwmi_cd01_unregister(void *data) +{ + struct notifier_block *acpi_nb = data; + + unregister_acpi_notifier(acpi_nb); +} + +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) + +{ + struct lwmi_cd01_priv *priv; + int ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + + ret = lwmi_cd01_setup(priv); + if (ret) + return ret; + + priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; + + ret = register_acpi_notifier(&priv->acpi_nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb); + if (ret) + return ret; + + return component_add(&wdev->dev, &lwmi_cd01_component_ops); +} + +static void lwmi_cd01_remove(struct wmi_device *wdev) +{ + component_del(&wdev->dev, &lwmi_cd01_component_ops); +} + +static const struct wmi_device_id lwmi_cd01_id_table[] = { + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_cd01_driver = { + .driver = { + .name = "lenovo_wmi_cd01", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = lwmi_cd01_id_table, + .probe = lwmi_cd01_probe, + .remove = lwmi_cd01_remove, + .no_singleton = true, +}; + +/** + * lwmi_cd01_match() - Match rule for the master driver. + * @dev: Pointer to the capability data 01 parent device. + * @data: Unused void pointer for passing match criteria. + * + * Return: int. + */ +int lwmi_cd01_match(struct device *dev, void *data) +{ + return dev->driver == &lwmi_cd01_driver.driver; +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); + +module_wmi_driver(lwmi_cd01_driver); + +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform/x86/lenovo/wmi-capdata01.h new file mode 100644 index 000000000000..bd06c5751f68 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-capdata01.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright (C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_WMI_CAPDATA01_H_ +#define _LENOVO_WMI_CAPDATA01_H_ + +#include + +struct device; +struct cd01_list; + +struct capdata01 { + u32 id; + u32 supported; + u32 default_value; + u32 step; + u32 min_value; + u32 max_value; +}; + +int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output); +int lwmi_cd01_match(struct device *dev, void *data); + +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ From 22024ac5366f065a7b931bee5b62e2588521c4f0 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 1 Jul 2025 20:38:25 -0700 Subject: [PATCH 23/63] platform/x86: Add Lenovo Gamezone WMI Driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI platform profiles over WMI. Reviewed-by: Armin Wolf Signed-off-by: Derek J. Clark Link: https://lore.kernel.org/r/20250702033826.1057762-6-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/Kconfig | 14 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/wmi-gamezone.c | 407 +++++++++++++++++++++ drivers/platform/x86/lenovo/wmi-gamezone.h | 20 + 4 files changed, 442 insertions(+) create mode 100644 drivers/platform/x86/lenovo/wmi-gamezone.c create mode 100644 drivers/platform/x86/lenovo/wmi-gamezone.h diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 1943e3a24f43..9de050c637b7 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -244,3 +244,17 @@ config LENOVO_WMI_EVENTS config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI + +config LENOVO_WMI_GAMEZONE + tristate "Lenovo GameZone WMI Driver" + depends on ACPI_WMI + depends on DMI + select ACPI_PLATFORM_PROFILE + select LENOVO_WMI_EVENTS + select LENOVO_WMI_HELPERS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the + platform-profile firmware interface to manage power usage. + + To compile this driver as a module, choose M here: the module will + be called lenovo-wmi-gamezone. diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 1ce970e4ddb8..0722fd7ad486 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -15,6 +15,7 @@ lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o +lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o # Add 'lenovo' prefix to each module listed in lenovo-target-* define LENOVO_OBJ_TARGET diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.c b/drivers/platform/x86/lenovo/wmi-gamezone.c new file mode 100644 index 000000000000..0eb7fe8222f4 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-gamezone.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo GameZone WMI interface driver. + * + * The GameZone WMI interface provides platform profile and fan curve settings + * for devices that fall under the "Gaming Series" of Lenovo Legion devices. + * + * Copyright (C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmi-events.h" +#include "wmi-gamezone.h" +#include "wmi-helpers.h" +#include "wmi-other.h" + +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" + +#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43 +#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44 +#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45 + +static BLOCKING_NOTIFIER_HEAD(gz_chain_head); + +struct lwmi_gz_priv { + enum thermal_mode current_mode; + struct notifier_block event_nb; + struct notifier_block mode_nb; + spinlock_t gz_mode_lock; /* current_mode lock */ + struct wmi_device *wdev; + int extreme_supported; + struct device *ppdev; +}; + +struct quirk_entry { + bool extreme_supported; +}; + +static struct quirk_entry quirk_no_extreme_bug = { + .extreme_supported = false, +}; + +/** + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier. + * + * @nb: The notifier_block registered to lenovo-wmi-other driver. + * @cmd: The event type. + * @data: Thermal mode enum pointer pointer for returning the thermal mode. + * + * For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode. + * + * Return: Notifier_block status. + */ +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd, + void *data) +{ + enum thermal_mode **mode = data; + struct lwmi_gz_priv *priv; + + priv = container_of(nb, struct lwmi_gz_priv, mode_nb); + + switch (cmd) { + case LWMI_GZ_GET_THERMAL_MODE: + scoped_guard(spinlock, &priv->gz_mode_lock) { + **mode = priv->current_mode; + } + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } +} + +/** + * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier. + * block call chain. + * @nb: The notifier_block registered to lenovo-wmi-events driver. + * @cmd: The event type. + * @data: The data to be updated by the event. + * + * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile + * of a change. + * + * Return: notifier_block status. + */ +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd, + void *data) +{ + enum thermal_mode *mode = data; + struct lwmi_gz_priv *priv; + + priv = container_of(nb, struct lwmi_gz_priv, event_nb); + + switch (cmd) { + case LWMI_EVENT_THERMAL_MODE: + scoped_guard(spinlock, &priv->gz_mode_lock) { + priv->current_mode = *mode; + } + platform_profile_notify(priv->ppdev); + return NOTIFY_STOP; + default: + return NOTIFY_DONE; + } +} + +/** + * lwmi_gz_thermal_mode_supported() - Get the version of the WMI + * interface to determine the support level. + * @wdev: The Gamezone WMI device. + * @supported: Pointer to return the support level with. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev, + int *supported) +{ + return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP, + NULL, 0, supported); +} + +/** + * lwmi_gz_thermal_mode_get() - Get the current thermal mode. + * @wdev: The Gamezone interface WMI device. + * @mode: Pointer to return the thermal mode with. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev, + enum thermal_mode *mode) +{ + return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET, + NULL, 0, mode); +} + +/** + * lwmi_gz_profile_get() - Get the current platform profile. + * @dev: the Gamezone interface parent device. + * @profile: Pointer to provide the current platform profile with. + * + * Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform + * profile based on the support level of the interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_gz_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct lwmi_gz_priv *priv = dev_get_drvdata(dev); + enum thermal_mode mode; + int ret; + + ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode); + if (ret) + return ret; + + switch (mode) { + case LWMI_GZ_THERMAL_MODE_QUIET: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case LWMI_GZ_THERMAL_MODE_BALANCED: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case LWMI_GZ_THERMAL_MODE_PERFORMANCE: + if (priv->extreme_supported) { + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; + break; + } + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case LWMI_GZ_THERMAL_MODE_EXTREME: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case LWMI_GZ_THERMAL_MODE_CUSTOM: + *profile = PLATFORM_PROFILE_CUSTOM; + break; + default: + return -EINVAL; + } + + guard(spinlock)(&priv->gz_mode_lock); + priv->current_mode = mode; + + return 0; +} + +/** + * lwmi_gz_profile_set() - Set the current platform profile. + * @dev: The Gamezone interface parent device. + * @profile: Pointer to the desired platform profile. + * + * Convert the given platform profile into a thermal mode based on the support + * level of the interface, then call the WMI method to set the thermal mode. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_gz_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct lwmi_gz_priv *priv = dev_get_drvdata(dev); + struct wmi_method_args_32 args; + enum thermal_mode mode; + int ret; + + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + mode = LWMI_GZ_THERMAL_MODE_QUIET; + break; + case PLATFORM_PROFILE_BALANCED: + mode = LWMI_GZ_THERMAL_MODE_BALANCED; + break; + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + if (priv->extreme_supported) { + mode = LWMI_GZ_THERMAL_MODE_EXTREME; + break; + } + mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_CUSTOM: + mode = LWMI_GZ_THERMAL_MODE_CUSTOM; + break; + default: + return -EOPNOTSUPP; + } + + args.arg0 = mode; + + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, + LWMI_GZ_METHOD_ID_SMARTFAN_SET, + (u8 *)&args, sizeof(args), NULL); + if (ret) + return ret; + + guard(spinlock)(&priv->gz_mode_lock); + priv->current_mode = mode; + + return 0; +} + +static const struct dmi_system_id fwbug_list[] = { + { + .ident = "Legion Go 8APU1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"), + }, + .driver_data = &quirk_no_extreme_bug, + }, + { + .ident = "Legion Go S 8APU1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"), + }, + .driver_data = &quirk_no_extreme_bug, + }, + { + .ident = "Legion Go S 8ARP1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"), + }, + .driver_data = &quirk_no_extreme_bug, + }, + {}, + +}; + +/** + * lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode. + * @profile_support_ver: Version of the WMI interface. + * + * Determine if the extreme thermal mode is supported by the hardware. + * Anything version 5 or lower does not. For devices with a version 6 or + * greater do a DMI check, as some devices report a version that supports + * extreme mode but have an incomplete entry in the BIOS. To ensure this + * cannot be set, quirk them to prevent assignment. + * + * Return: bool. + */ +static bool lwmi_gz_extreme_supported(int profile_support_ver) +{ + const struct dmi_system_id *dmi_id; + struct quirk_entry *quirks; + + if (profile_support_ver < 6) + return false; + + dmi_id = dmi_first_match(fwbug_list); + if (!dmi_id) + return true; + + quirks = dmi_id->driver_data; + + return quirks->extreme_supported; +} + +/** + * lwmi_gz_platform_profile_probe - Enable and set up the platform profile + * device. + * @drvdata: Driver data for the interface. + * @choices: Container for enabled platform profiles. + * + * Determine if thermal mode is supported, and if so to what feature level. + * Then enable all supported platform profiles. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + struct lwmi_gz_priv *priv = drvdata; + int profile_support_ver; + int ret; + + ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver); + if (ret) + return ret; + + if (profile_support_ver < 1) + return -ENODEV; + + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_CUSTOM, choices); + + priv->extreme_supported = lwmi_gz_extreme_supported(profile_support_ver); + if (priv->extreme_supported) + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + + return 0; +} + +static const struct platform_profile_ops lwmi_gz_platform_profile_ops = { + .probe = lwmi_gz_platform_profile_probe, + .profile_get = lwmi_gz_profile_get, + .profile_set = lwmi_gz_profile_set, +}; + +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context) +{ + struct lwmi_gz_priv *priv; + int ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + + priv->ppdev = devm_platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone", + priv, &lwmi_gz_platform_profile_ops); + if (IS_ERR(priv->ppdev)) + return -ENODEV; + + spin_lock_init(&priv->gz_mode_lock); + + ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode); + if (ret) + return ret; + + priv->event_nb.notifier_call = lwmi_gz_event_call; + ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb); + if (ret) + return ret; + + priv->mode_nb.notifier_call = lwmi_gz_mode_call; + return devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb); +} + +static const struct wmi_device_id lwmi_gz_id_table[] = { + { LENOVO_GAMEZONE_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_gz_driver = { + .driver = { + .name = "lenovo_wmi_gamezone", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = lwmi_gz_id_table, + .probe = lwmi_gz_probe, + .no_singleton = true, +}; + +module_wmi_driver(lwmi_gz_driver); + +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS"); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); +MODULE_IMPORT_NS("LENOVO_WMI_OTHER"); +MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h new file mode 100644 index 000000000000..6b163a5eeb95 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-gamezone.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright (C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_WMI_GAMEZONE_H_ +#define _LENOVO_WMI_GAMEZONE_H_ + +enum gamezone_events_type { + LWMI_GZ_GET_THERMAL_MODE = 1, +}; + +enum thermal_mode { + LWMI_GZ_THERMAL_MODE_QUIET = 0x01, + LWMI_GZ_THERMAL_MODE_BALANCED = 0x02, + LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03, + LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */ + LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF, +}; + +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */ From edc4b183b794baefb54aa0baeb810fe3ac65d826 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 1 Jul 2025 20:38:26 -0700 Subject: [PATCH 24/63] platform/x86: Add Lenovo Other Mode WMI Driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI interface that comes on some Lenovo "Gaming Series" hardware. Provides a firmware-attributes class which enables the use of tunable knobs for SPL, SPPT, and FPPT. Reviewed-by: Alok Tiwari Reviewed-by: Armin Wolf Signed-off-by: Derek J. Clark Link: https://lore.kernel.org/r/20250702033826.1057762-7-derekjohn.clark@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/Kconfig | 15 + drivers/platform/x86/lenovo/Makefile | 1 + drivers/platform/x86/lenovo/wmi-other.c | 665 ++++++++++++++++++++++++ drivers/platform/x86/lenovo/wmi-other.h | 16 + 4 files changed, 697 insertions(+) create mode 100644 drivers/platform/x86/lenovo/wmi-other.c create mode 100644 drivers/platform/x86/lenovo/wmi-other.h diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 9de050c637b7..b76157b35296 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -258,3 +258,18 @@ config LENOVO_WMI_GAMEZONE To compile this driver as a module, choose M here: the module will be called lenovo-wmi-gamezone. + +config LENOVO_WMI_TUNING + tristate "Lenovo Other Mode WMI Driver" + depends on ACPI_WMI + select FW_ATTR_CLASS + select LENOVO_WMI_DATA01 + select LENOVO_WMI_EVENTS + select LENOVO_WMI_HELPERS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like to use the + firmware_attributes API to control various tunable settings typically exposed by + Lenovo software in Windows. + + To compile this driver as a module, choose M here: the module will + be called lenovo-wmi-other. diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 0722fd7ad486..7b2128e3a214 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -16,6 +16,7 @@ lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o +lenovo-target-$(CONFIG_LENOVO_WMI_TUNING) += wmi-other.o # Add 'lenovo' prefix to each module listed in lenovo-target-* define LENOVO_OBJ_TARGET diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c new file mode 100644 index 000000000000..2a960b278f11 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Other Mode WMI interface driver. + * + * This driver uses the fw_attributes class to expose the various WMI functions + * provided by the "Other Mode" WMI interface. This enables CPU and GPU power + * limit as well as various other attributes for devices that fall under the + * "Gaming Series" of Lenovo laptop devices. Each attribute exposed by the + * "Other Mode" interface has a corresponding Capability Data struct that + * allows the driver to probe details about the attribute such as if it is + * supported by the hardware, the default_value, max_value, min_value, and step + * increment. + * + * These attributes typically don't fit anywhere else in the sysfs and are set + * in Windows using one of Lenovo's multiple user applications. + * + * Copyright (C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmi-capdata01.h" +#include "wmi-events.h" +#include "wmi-gamezone.h" +#include "wmi-helpers.h" +#include "wmi-other.h" +#include "../firmware_attributes_class.h" + +#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" + +#define LWMI_DEVICE_ID_CPU 0x01 + +#define LWMI_FEATURE_ID_CPU_SPPT 0x01 +#define LWMI_FEATURE_ID_CPU_SPL 0x02 +#define LWMI_FEATURE_ID_CPU_FPPT 0x03 + +#define LWMI_TYPE_ID_NONE 0x00 + +#define LWMI_FEATURE_VALUE_GET 17 +#define LWMI_FEATURE_VALUE_SET 18 + +#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) +#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) +#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) +#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) + +#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" + +static BLOCKING_NOTIFIER_HEAD(om_chain_head); +static DEFINE_IDA(lwmi_om_ida); + +enum attribute_property { + DEFAULT_VAL, + MAX_VAL, + MIN_VAL, + STEP_VAL, + SUPPORTED, +}; + +struct lwmi_om_priv { + struct component_master_ops *ops; + struct cd01_list *cd01_list; /* only valid after capdata01 bind */ + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + struct notifier_block nb; + struct wmi_device *wdev; + int ida_id; +}; + +struct tunable_attr_01 { + struct capdata01 *capdata; + struct device *dev; + u32 feature_id; + u32 device_id; + u32 type_id; +}; + +static struct tunable_attr_01 ppt_pl1_spl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl2_sppt = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPPT, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl3_fppt = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_FPPT, + .type_id = LWMI_TYPE_ID_NONE, +}; + +struct capdata01_attr_group { + const struct attribute_group *attr_group; + struct tunable_attr_01 *tunable_attr; +}; + +/** + * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain + * @nb: The notifier_block struct to register + * + * Call blocking_notifier_chain_register to register the notifier block to the + * lenovo-wmi-other driver notifier chain. + * + * Return: 0 on success, %-EEXIST on error. + */ +int lwmi_om_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&om_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +/** + * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier + * chain. + * @nb: The notifier_block struct to register + * + * Call blocking_notifier_chain_unregister to unregister the notifier block from the + * lenovo-wmi-other driver notifier chain. + * + * Return: 0 on success, %-ENOENT on error. + */ +int lwmi_om_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&om_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER"); + +/** + * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking + * notifier chain. + * @data: Void pointer to the notifier_block struct to register. + * + * Call lwmi_om_unregister_notifier to unregister the notifier block from the + * lenovo-wmi-other driver notifier chain. + * + * Return: 0 on success, %-ENOENT on error. + */ +static void devm_lwmi_om_unregister_notifier(void *data) +{ + struct notifier_block *nb = data; + + lwmi_om_unregister_notifier(nb); +} + +/** + * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier + * chain. + * @dev: The parent device of the notifier_block struct. + * @nb: The notifier_block struct to register + * + * Call lwmi_om_register_notifier to register the notifier block to the + * lenovo-wmi-other driver notifier chain. Then add devm_lwmi_om_unregister_notifier + * as a device managed action to automatically unregister the notifier block + * upon parent device removal. + * + * Return: 0 on success, or an error code. + */ +int devm_lwmi_om_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret = lwmi_om_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier, + nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +/** + * lwmi_om_notifier_call() - Call functions for the notifier call chain. + * @mode: Pointer to a thermal mode enum to retrieve the data from. + * + * Call blocking_notifier_call_chain to retrieve the thermal mode from the + * lenovo-wmi-gamezone driver. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_notifier_call(enum thermal_mode *mode) +{ + int ret; + + ret = blocking_notifier_call_chain(&om_chain_head, + LWMI_GZ_GET_THERMAL_MODE, &mode); + if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK) + return -EINVAL; + + return 0; +} + +/* Attribute Methods */ + +/** + * int_type_show() - Emit the data type for an integer attribute + * @kobj: Pointer to the driver object. + * @kattr: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * + * Return: Number of characters written to buf. + */ +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +/** + * attr_capdata01_show() - Get the value of the specified attribute property + * + * @kobj: Pointer to the driver object. + * @kattr: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @tunable_attr: The attribute to be read. + * @prop: The property of this attribute to be read. + * + * Retrieves the given property from the capability data 01 struct for the + * specified attribute's "custom" thermal mode. This function is intended + * to be generic so it can be called from any integer attributes "_show" + * function. + * + * If the WMI is success the sysfs attribute is notified. + * + * Return: Either number of characters written to buf, or an error code. + */ +static ssize_t attr_capdata01_show(struct kobject *kobj, + struct kobj_attribute *kattr, char *buf, + struct tunable_attr_01 *tunable_attr, + enum attribute_property prop) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); + struct capdata01 capdata; + u32 attribute_id; + int value, ret; + + attribute_id = + FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, + LWMI_GZ_THERMAL_MODE_CUSTOM) | + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); + if (ret) + return ret; + + switch (prop) { + case DEFAULT_VAL: + value = capdata.default_value; + break; + case MAX_VAL: + value = capdata.max_value; + break; + case MIN_VAL: + value = capdata.min_value; + break; + case STEP_VAL: + value = capdata.step; + break; + default: + return -EINVAL; + } + + return sysfs_emit(buf, "%d\n", value); +} + +/** + * attr_current_value_store() - Set the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kattr: Pointer to the attribute calling this function. + * @buf: The buffer to read from, this is parsed to `int` type. + * @count: Required by sysfs attribute macros, pass in from the callee attr. + * @tunable_attr: The attribute to be stored. + * + * Sets the value of the given attribute when operating under the "custom" + * smartfan profile. The current smartfan profile is retrieved from the + * lenovo-wmi-gamezone driver and error is returned if the result is not + * "custom". This function is intended to be generic so it can be called from + * any integer attribute's "_store" function. The integer to be sent to the WMI + * method is range checked and an error code is returned if out of range. + * + * If the value is valid and WMI is success, then the sysfs attribute is + * notified. + * + * Return: Either count, or an error code. + */ +static ssize_t attr_current_value_store(struct kobject *kobj, + struct kobj_attribute *kattr, + const char *buf, size_t count, + struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args; + struct capdata01 capdata; + enum thermal_mode mode; + u32 attribute_id; + u32 value; + int ret; + + ret = lwmi_om_notifier_call(&mode); + if (ret) + return ret; + + if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM) + return -EBUSY; + + attribute_id = + FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); + if (ret) + return ret; + + ret = kstrtouint(buf, 10, &value); + if (ret) + return ret; + + if (value < capdata.min_value || value > capdata.max_value) + return -EINVAL; + + args.arg0 = attribute_id; + args.arg1 = value; + + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET, + (unsigned char *)&args, sizeof(args), NULL); + if (ret) + return ret; + + return count; +}; + +/** + * attr_current_value_show() - Get the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kattr: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @tunable_attr: The attribute to be read. + * + * Retrieves the value of the given attribute for the current smartfan profile. + * The current smartfan profile is retrieved from the lenovo-wmi-gamezone driver. + * This function is intended to be generic so it can be called from any integer + * attribute's "_show" function. + * + * If the WMI is success the sysfs attribute is notified. + * + * Return: Either number of characters written to buf, or an error code. + */ +static ssize_t attr_current_value_show(struct kobject *kobj, + struct kobj_attribute *kattr, char *buf, + struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args; + enum thermal_mode mode; + u32 attribute_id; + int retval; + int ret; + + ret = lwmi_om_notifier_call(&mode); + if (ret) + return ret; + + attribute_id = + FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + args.arg0 = attribute_id; + + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, + (unsigned char *)&args, sizeof(args), + &retval); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", retval); +} + +/* Lenovo WMI Other Mode Attribute macros */ +#define __LWMI_ATTR_RO(_func, _name) \ + { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _func##_##_name##_show, \ + } + +#define __LWMI_ATTR_RO_AS(_name, _show) \ + { \ + .attr = { .name = __stringify(_name), .mode = 0444 }, \ + .show = _show, \ + } + +#define __LWMI_ATTR_RW(_func, _name) \ + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) + +/* Shows a formatted static variable */ +#define __LWMI_ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return sysfs_emit(buf, _fmt, _val); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop = \ + __LWMI_ATTR_RO(_attrname, _prop) + +/* Attribute current value read/write */ +#define __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \ + static ssize_t _attrname##_current_value_store( \ + struct kobject *kobj, struct kobj_attribute *kattr, \ + const char *buf, size_t count) \ + { \ + return attr_current_value_store(kobj, kattr, buf, count, \ + &_attrname); \ + } \ + static ssize_t _attrname##_current_value_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return attr_current_value_show(kobj, kattr, buf, &_attrname); \ + } \ + static struct kobj_attribute attr_##_attrname##_current_value = \ + __LWMI_ATTR_RW(_attrname, current_value) + +/* Attribute property read only */ +#define __LWMI_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return attr_capdata01_show(kobj, kattr, buf, &_attrname, \ + _prop_type); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop = \ + __LWMI_ATTR_RO(_attrname, _prop) + +#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname) \ + __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \ + __LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \ + __LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \ + __LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \ + __LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \ + static struct kobj_attribute attr_##_attrname##_type = \ + __LWMI_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] = { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_default_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_max_value.attr, \ + &attr_##_attrname##_min_value.attr, \ + &attr_##_attrname##_scalar_increment.attr, \ + &attr_##_attrname##_type.attr, \ + NULL, \ + }; \ + static const struct attribute_group _attrname##_attr_group = { \ + .name = _fsname, .attrs = _attrname##_attrs \ + } + +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", + "Set the CPU sustained power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", + "Set the CPU slow package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", + "Set the CPU fast package power tracking limit"); + +static struct capdata01_attr_group cd01_attr_groups[] = { + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl }, + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt }, + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt }, + {}, +}; + +/** + * lwmi_om_fw_attr_add() - Register all firmware_attributes_class members + * @priv: The Other Mode driver data. + * + * Return: Either 0, or an error code. + */ +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) +{ + unsigned int i; + int err; + + priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL); + if (priv->ida_id < 0) + return priv->ida_id; + + priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL, + MKDEV(0, 0), NULL, "%s-%u", + LWMI_OM_FW_ATTR_BASE_PATH, + priv->ida_id); + if (IS_ERR(priv->fw_attr_dev)) { + err = PTR_ERR(priv->fw_attr_dev); + goto err_free_ida; + } + + priv->fw_attr_kset = kset_create_and_add("attributes", NULL, + &priv->fw_attr_dev->kobj); + if (!priv->fw_attr_kset) { + err = -ENOMEM; + goto err_destroy_classdev; + } + + for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) { + err = sysfs_create_group(&priv->fw_attr_kset->kobj, + cd01_attr_groups[i].attr_group); + if (err) + goto err_remove_groups; + + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev; + } + return 0; + +err_remove_groups: + while (i--) + sysfs_remove_group(&priv->fw_attr_kset->kobj, + cd01_attr_groups[i].attr_group); + + kset_unregister(priv->fw_attr_kset); + +err_destroy_classdev: + device_unregister(priv->fw_attr_dev); + +err_free_ida: + ida_free(&lwmi_om_ida, priv->ida_id); + return err; +} + +/** + * lwmi_om_fw_attr_remove() - Unregister all capability data attribute groups + * @priv: the lenovo-wmi-other driver data. + */ +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) + sysfs_remove_group(&priv->fw_attr_kset->kobj, + cd01_attr_groups[i].attr_group); + + kset_unregister(priv->fw_attr_kset); + device_unregister(priv->fw_attr_dev); +} + +/** + * lwmi_om_master_bind() - Bind all components of the other mode driver + * @dev: The lenovo-wmi-other driver basic device. + * + * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the + * lenovo-wmi-other master driver. On success, assign the capability data 01 + * list pointer to the driver data struct for later access. This pointer + * is only valid while the capdata01 interface exists. Finally, register all + * firmware attribute groups. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_master_bind(struct device *dev) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(dev); + struct cd01_list *tmp_list; + int ret; + + ret = component_bind_all(dev, &tmp_list); + if (ret) + return ret; + + priv->cd01_list = tmp_list; + if (!priv->cd01_list) + return -ENODEV; + + return lwmi_om_fw_attr_add(priv); +} + +/** + * lwmi_om_master_unbind() - Unbind all components of the other mode driver + * @dev: The lenovo-wmi-other driver basic device + * + * Unregister all capability data attribute groups. Then call + * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the + * lenovo-wmi-other master driver. Finally, free the IDA for this device. + */ +static void lwmi_om_master_unbind(struct device *dev) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(dev); + + lwmi_om_fw_attr_remove(priv); + component_unbind_all(dev, NULL); +} + +static const struct component_master_ops lwmi_om_master_ops = { + .bind = lwmi_om_master_bind, + .unbind = lwmi_om_master_unbind, +}; + +static int lwmi_other_probe(struct wmi_device *wdev, const void *context) +{ + struct component_match *master_match = NULL; + struct lwmi_om_priv *priv; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + + component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); + if (IS_ERR(master_match)) + return PTR_ERR(master_match); + + return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops, + master_match); +} + +static void lwmi_other_remove(struct wmi_device *wdev) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev); + + component_master_del(&wdev->dev, &lwmi_om_master_ops); + ida_free(&lwmi_om_ida, priv->ida_id); +} + +static const struct wmi_device_id lwmi_other_id_table[] = { + { LENOVO_OTHER_MODE_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_other_driver = { + .driver = { + .name = "lenovo_wmi_other", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = lwmi_other_id_table, + .probe = lwmi_other_probe, + .remove = lwmi_other_remove, + .no_singleton = true, +}; + +module_wmi_driver(lwmi_other_driver); + +MODULE_IMPORT_NS("LENOVO_WMI_CD01"); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-other.h b/drivers/platform/x86/lenovo/wmi-other.h new file mode 100644 index 000000000000..8ebf5602bb99 --- /dev/null +++ b/drivers/platform/x86/lenovo/wmi-other.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright (C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_WMI_OTHER_H_ +#define _LENOVO_WMI_OTHER_H_ + +struct device; +struct notifier_block; + +int lwmi_om_register_notifier(struct notifier_block *nb); +int lwmi_om_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_om_register_notifier(struct device *dev, + struct notifier_block *nb); + +#endif /* !_LENOVO_WMI_OTHER_H_ */ From fb1311b3f171bbb3c07cc7764ec981605564c83a Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:16 -0700 Subject: [PATCH 25/63] MAINTAINERS: Add link to documentation of Intel PMT ABI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a link to the documentation for the Intel Platform Monitoring Technology ABI in Documentation/ABI/testing/sysfs-class-intel_pmt Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-2-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5b67869bbc87..4af5a8ee4057 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12386,6 +12386,7 @@ F: include/linux/mfd/intel_soc_pmic* INTEL PMT DRIVERS M: David E. Box S: Supported +F: Documentation/ABI/testing/sysfs-class-intel_pmt F: drivers/platform/x86/intel/pmt/ INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT From dc957ab6aa05c118c3da0542428a4d6602aa2d2d Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:17 -0700 Subject: [PATCH 26/63] platform/x86/intel/vsec: Add private data for per-device data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a new private structure, struct vsec_priv, to hold a pointer to the platform-specific information. Although the driver didn’t previously require this per-device data, adding it now lays the groundwork for upcoming patches that will manage such data. No functional changes. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-3-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 055ca9f48fb4..59fb6568a855 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -32,6 +32,10 @@ static DEFINE_IDA(intel_vsec_ida); static DEFINE_IDA(intel_vsec_sdsi_ida); static DEFINE_XARRAY_ALLOC(auxdev_array); +struct vsec_priv { + struct intel_vsec_platform_info *info; +}; + static const char *intel_vsec_name(enum intel_vsec_id id) { switch (id) { @@ -348,6 +352,7 @@ EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC"); static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct intel_vsec_platform_info *info; + struct vsec_priv *priv; bool have_devices = false; int ret; @@ -360,6 +365,13 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id if (!info) return -EINVAL; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + pci_set_drvdata(pdev, priv); + if (intel_vsec_walk_dvsec(pdev, info)) have_devices = true; From b0631f8a5740c55b52d02174cc4c9c84cc7a16a1 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:18 -0700 Subject: [PATCH 27/63] platform/x86/intel/vsec: Create wrapper to walk PCI config space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combine three PCI config space walkers — intel_vsec_walk_dvsec(), intel_vsec_walk_vsec(), and intel_vsec_walk_header() — into a new wrapper function, intel_vsec_feature_walk(). This refactoring simplifies the probe logic and lays the groundwork for future patches that will loop over these calls. No functional changes. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-4-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 38 +++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 59fb6568a855..8bdb74d86f24 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -349,11 +349,35 @@ int intel_vsec_register(struct pci_dev *pdev, } EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC"); +static bool intel_vsec_get_features(struct pci_dev *pdev, + struct intel_vsec_platform_info *info) +{ + bool found = false; + + /* + * Both DVSEC and VSEC capabilities can exist on the same device, + * so both intel_vsec_walk_dvsec() and intel_vsec_walk_vsec() must be + * called independently. Additionally, intel_vsec_walk_header() is + * needed for devices that do not have VSEC/DVSEC but provide the + * information via device_data. + */ + if (intel_vsec_walk_dvsec(pdev, info)) + found = true; + + if (intel_vsec_walk_vsec(pdev, info)) + found = true; + + if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) && + intel_vsec_walk_header(pdev, info)) + found = true; + + return found; +} + static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct intel_vsec_platform_info *info; struct vsec_priv *priv; - bool have_devices = false; int ret; ret = pcim_enable_device(pdev); @@ -372,17 +396,7 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id priv->info = info; pci_set_drvdata(pdev, priv); - if (intel_vsec_walk_dvsec(pdev, info)) - have_devices = true; - - if (intel_vsec_walk_vsec(pdev, info)) - have_devices = true; - - if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) && - intel_vsec_walk_header(pdev, info)) - have_devices = true; - - if (!have_devices) + if (!intel_vsec_get_features(pdev, info)) return -ENODEV; return 0; From 8a67d4b49bbdebcd255abde9e652092c3de3b657 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:19 -0700 Subject: [PATCH 28/63] platform/x86/intel/vsec: Add device links to enforce dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Intel VSEC features will have dependencies on other features, requiring certain supplier drivers to be probed before their consumers. To enforce this dependency ordering, introduce device links using device_link_add(), ensuring that suppliers are fully registered before consumers are probed. - Add device link tracking by storing supplier devices and tracking their state. - Implement intel_vsec_link_devices() to establish links between suppliers and consumers based on feature dependencies. - Add get_consumer_dependencies() to retrieve supplier-consumer relationships. - Modify feature registration logic: * Consumers now check that all required suppliers are registered before being initialized. * suppliers_ready() verifies that all required supplier devices are available. - Prevent potential null consumer name issue in sysfs: - Use dev_set_name() when creating auxiliary devices to ensure a unique, non-null consumer name. - Update intel_vsec_pci_probe() to loop up to the number of possible features or when all devices are registered, whichever comes first. - Introduce VSEC_CAP_UNUSED to prevent sub-features (registered via exported APIs) from being mistakenly linked. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-5-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 223 ++++++++++++++++++++++++++++-- include/linux/intel_vsec.h | 28 +++- 2 files changed, 236 insertions(+), 15 deletions(-) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 8bdb74d86f24..aa1f7e63039d 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -15,9 +15,12 @@ #include #include +#include +#include #include #include #include +#include #include #include #include @@ -32,8 +35,17 @@ static DEFINE_IDA(intel_vsec_ida); static DEFINE_IDA(intel_vsec_sdsi_ida); static DEFINE_XARRAY_ALLOC(auxdev_array); +enum vsec_device_state { + STATE_NOT_FOUND, + STATE_REGISTERED, + STATE_SKIP, +}; + struct vsec_priv { struct intel_vsec_platform_info *info; + struct device *suppliers[VSEC_FEATURE_COUNT]; + enum vsec_device_state state[VSEC_FEATURE_COUNT]; + unsigned long found_caps; }; static const char *intel_vsec_name(enum intel_vsec_id id) @@ -95,6 +107,74 @@ static void intel_vsec_dev_release(struct device *dev) kfree(intel_vsec_dev); } +static const struct vsec_feature_dependency * +get_consumer_dependencies(struct vsec_priv *priv, int cap_id) +{ + const struct vsec_feature_dependency *deps = priv->info->deps; + int consumer_id = priv->info->num_deps; + + if (!deps) + return NULL; + + while (consumer_id--) + if (deps[consumer_id].feature == BIT(cap_id)) + return &deps[consumer_id]; + + return NULL; +} + +/* + * Although pci_device_id table is available in the pdev, this prototype is + * necessary because the code using it can be called by an exported API that + * might pass a different pdev. + */ +static const struct pci_device_id intel_vsec_pci_ids[]; + +static int intel_vsec_link_devices(struct pci_dev *pdev, struct device *dev, + int consumer_id) +{ + const struct vsec_feature_dependency *deps; + enum vsec_device_state *state; + struct device **suppliers; + struct vsec_priv *priv; + int supplier_id; + + if (!consumer_id) + return 0; + + if (!pci_match_id(intel_vsec_pci_ids, pdev)) + return 0; + + priv = pci_get_drvdata(pdev); + state = priv->state; + suppliers = priv->suppliers; + + priv->suppliers[consumer_id] = dev; + + deps = get_consumer_dependencies(priv, consumer_id); + if (!deps) + return 0; + + for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) { + struct device_link *link; + + if (state[supplier_id] != STATE_REGISTERED) + continue; + + if (!suppliers[supplier_id]) { + dev_err(dev, "Bad supplier list\n"); + return -EINVAL; + } + + link = device_link_add(dev, suppliers[supplier_id], + DL_FLAG_AUTOPROBE_CONSUMER); + if (!link) + return -EINVAL; + } + + return 0; +} + int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, struct intel_vsec_device *intel_vsec_dev, const char *name) @@ -132,19 +212,37 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, return ret; } + /* + * Assign a name now to ensure that the device link doesn't contain + * a null string for the consumer name. This is a problem when a supplier + * supplies more than one consumer and can lead to a duplicate name error + * when the link is created in sysfs. + */ + ret = dev_set_name(&auxdev->dev, "%s.%s.%d", KBUILD_MODNAME, auxdev->name, + auxdev->id); + if (ret) + goto cleanup_aux; + + ret = intel_vsec_link_devices(pdev, &auxdev->dev, intel_vsec_dev->cap_id); + if (ret) + goto cleanup_aux; + ret = auxiliary_device_add(auxdev); - if (ret < 0) { - auxiliary_device_uninit(auxdev); - return ret; - } + if (ret) + goto cleanup_aux; return devm_add_action_or_reset(parent, intel_vsec_remove_aux, auxdev); + +cleanup_aux: + auxiliary_device_uninit(auxdev); + return ret; } EXPORT_SYMBOL_NS_GPL(intel_vsec_add_aux, "INTEL_VSEC"); static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header, - struct intel_vsec_platform_info *info) + struct intel_vsec_platform_info *info, + unsigned long cap_id) { struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL; struct resource __free(kfree) *res = NULL; @@ -211,6 +309,7 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he intel_vsec_dev->quirks = info->quirks; intel_vsec_dev->base_addr = info->base_addr; intel_vsec_dev->priv_data = info->priv_data; + intel_vsec_dev->cap_id = cap_id; if (header->id == VSEC_ID_SDSI) intel_vsec_dev->ida = &intel_vsec_sdsi_ida; @@ -225,6 +324,101 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he intel_vsec_name(header->id)); } +static bool suppliers_ready(struct vsec_priv *priv, + const struct vsec_feature_dependency *consumer_deps, + int cap_id) +{ + enum vsec_device_state *state = priv->state; + int supplier_id; + + if (WARN_ON_ONCE(consumer_deps->feature != BIT(cap_id))) + return false; + + /* + * Verify that all required suppliers have been found. Return false + * immediately if any are still missing. + */ + for_each_set_bit(supplier_id, &consumer_deps->supplier_bitmap, VSEC_FEATURE_COUNT) { + if (state[supplier_id] == STATE_SKIP) + continue; + + if (state[supplier_id] == STATE_NOT_FOUND) + return false; + } + + /* + * All suppliers have been found and the consumer is ready to be + * registered. + */ + return true; +} + +static int get_cap_id(u32 header_id, unsigned long *cap_id) +{ + switch (header_id) { + case VSEC_ID_TELEMETRY: + *cap_id = ilog2(VSEC_CAP_TELEMETRY); + break; + case VSEC_ID_WATCHER: + *cap_id = ilog2(VSEC_CAP_WATCHER); + break; + case VSEC_ID_CRASHLOG: + *cap_id = ilog2(VSEC_CAP_CRASHLOG); + break; + case VSEC_ID_SDSI: + *cap_id = ilog2(VSEC_CAP_SDSI); + break; + case VSEC_ID_TPMI: + *cap_id = ilog2(VSEC_CAP_TPMI); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int intel_vsec_register_device(struct pci_dev *pdev, + struct intel_vsec_header *header, + struct intel_vsec_platform_info *info) +{ + const struct vsec_feature_dependency *consumer_deps; + struct vsec_priv *priv; + unsigned long cap_id; + int ret; + + ret = get_cap_id(header->id, &cap_id); + if (ret) + return ret; + + /* + * Only track dependencies for devices probed by the VSEC driver. + * For others using the exported APIs, add the device directly. + */ + if (!pci_match_id(intel_vsec_pci_ids, pdev)) + return intel_vsec_add_dev(pdev, header, info, cap_id); + + priv = pci_get_drvdata(pdev); + if (priv->state[cap_id] == STATE_REGISTERED || + priv->state[cap_id] == STATE_SKIP) + return -EEXIST; + + priv->found_caps |= BIT(cap_id); + + consumer_deps = get_consumer_dependencies(priv, cap_id); + if (!consumer_deps || suppliers_ready(priv, consumer_deps, cap_id)) { + ret = intel_vsec_add_dev(pdev, header, info, cap_id); + if (ret) + priv->state[cap_id] = STATE_SKIP; + else + priv->state[cap_id] = STATE_REGISTERED; + + return ret; + } + + return -EAGAIN; +} + static bool intel_vsec_walk_header(struct pci_dev *pdev, struct intel_vsec_platform_info *info) { @@ -233,7 +427,7 @@ static bool intel_vsec_walk_header(struct pci_dev *pdev, int ret; for ( ; *header; header++) { - ret = intel_vsec_add_dev(pdev, *header, info); + ret = intel_vsec_register_device(pdev, *header, info); if (!ret) have_devices = true; } @@ -281,7 +475,7 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr); header.id = PCI_DVSEC_HEADER2_ID(hdr); - ret = intel_vsec_add_dev(pdev, &header, info); + ret = intel_vsec_register_device(pdev, &header, info); if (ret) continue; @@ -326,7 +520,7 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev, header.tbir = INTEL_DVSEC_TABLE_BAR(table); header.offset = INTEL_DVSEC_TABLE_OFFSET(table); - ret = intel_vsec_add_dev(pdev, &header, info); + ret = intel_vsec_register_device(pdev, &header, info); if (ret) continue; @@ -378,7 +572,8 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id { struct intel_vsec_platform_info *info; struct vsec_priv *priv; - int ret; + int num_caps, ret; + bool found_any = false; ret = pcim_enable_device(pdev); if (ret) @@ -396,7 +591,15 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id priv->info = info; pci_set_drvdata(pdev, priv); - if (!intel_vsec_get_features(pdev, info)) + num_caps = hweight_long(info->caps); + while (num_caps--) { + found_any |= intel_vsec_get_features(pdev, info); + + if (priv->found_caps == info->caps) + break; + } + + if (!found_any) return -ENODEV; return 0; diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index bc95821f1bfb..71067afaca99 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -5,11 +5,18 @@ #include #include -#define VSEC_CAP_TELEMETRY BIT(0) -#define VSEC_CAP_WATCHER BIT(1) -#define VSEC_CAP_CRASHLOG BIT(2) -#define VSEC_CAP_SDSI BIT(3) -#define VSEC_CAP_TPMI BIT(4) +/* + * VSEC_CAP_UNUSED is reserved. It exists to prevent zero initialized + * intel_vsec devices from being automatically set to a known + * capability with ID 0 + */ +#define VSEC_CAP_UNUSED BIT(0) +#define VSEC_CAP_TELEMETRY BIT(1) +#define VSEC_CAP_WATCHER BIT(2) +#define VSEC_CAP_CRASHLOG BIT(3) +#define VSEC_CAP_SDSI BIT(4) +#define VSEC_CAP_TPMI BIT(5) +#define VSEC_FEATURE_COUNT 6 /* Intel DVSEC offsets */ #define INTEL_DVSEC_ENTRIES 0xA @@ -81,22 +88,31 @@ struct pmt_callbacks { int (*read_telem)(struct pci_dev *pdev, u32 guid, u64 *data, loff_t off, u32 count); }; +struct vsec_feature_dependency { + unsigned long feature; + unsigned long supplier_bitmap; +}; + /** * struct intel_vsec_platform_info - Platform specific data * @parent: parent device in the auxbus chain * @headers: list of headers to define the PMT client devices to create + * @deps: array of feature dependencies * @priv_data: private data, usable by parent devices, currently a callback * @caps: bitmask of PMT capabilities for the given headers * @quirks: bitmask of VSEC device quirks * @base_addr: allow a base address to be specified (rather than derived) + * @num_deps: Count feature dependencies */ struct intel_vsec_platform_info { struct device *parent; struct intel_vsec_header **headers; + const struct vsec_feature_dependency *deps; void *priv_data; unsigned long caps; unsigned long quirks; u64 base_addr; + int num_deps; }; /** @@ -110,6 +126,7 @@ struct intel_vsec_platform_info { * @priv_data: any private data needed * @quirks: specified quirks * @base_addr: base address of entries (if specified) + * @cap_id: the enumerated id of the vsec feature */ struct intel_vsec_device { struct auxiliary_device auxdev; @@ -122,6 +139,7 @@ struct intel_vsec_device { size_t priv_data_size; unsigned long quirks; u64 base_addr; + unsigned long cap_id; }; int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, From 1f3855ea7d6b03f68c2eec7a0bcd537cedcc6680 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:20 -0700 Subject: [PATCH 29/63] platform/x86/intel/vsec: Skip absent features during initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some VSEC features depend on the presence of supplier features that may not always be present. To prevent unnecessary retries and device linking during initialization, introduce logic to skip attempts to link consumers to missing suppliers. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-6-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index aa1f7e63039d..32f777b41b33 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -568,11 +568,30 @@ static bool intel_vsec_get_features(struct pci_dev *pdev, return found; } +static void intel_vsec_skip_missing_dependencies(struct pci_dev *pdev) +{ + struct vsec_priv *priv = pci_get_drvdata(pdev); + const struct vsec_feature_dependency *deps = priv->info->deps; + int consumer_id = priv->info->num_deps; + + while (consumer_id--) { + int supplier_id; + + deps = &priv->info->deps[consumer_id]; + + for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) { + if (!(BIT(supplier_id) & priv->found_caps)) + priv->state[supplier_id] = STATE_SKIP; + } + } +} + static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct intel_vsec_platform_info *info; struct vsec_priv *priv; int num_caps, ret; + int run_once = 0; bool found_any = false; ret = pcim_enable_device(pdev); @@ -597,6 +616,11 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id if (priv->found_caps == info->caps) break; + + if (!run_once) { + intel_vsec_skip_missing_dependencies(pdev); + run_once = 1; + } } if (!found_any) From e4436e98672c7993cdfd7743efd0fcaa8df7cc17 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:21 -0700 Subject: [PATCH 30/63] platform/x86/intel/vsec: Skip driverless features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a feature lacks a corresponding driver and that feature is also a supplier, registering it would be prevent the consumer driver from probing. Introduces logic to skip such features during device registration. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-7-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 32f777b41b33..30e558af6888 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -123,6 +123,26 @@ get_consumer_dependencies(struct vsec_priv *priv, int cap_id) return NULL; } +static bool vsec_driver_present(int cap_id) +{ + unsigned long bit = BIT(cap_id); + + switch (bit) { + case VSEC_CAP_TELEMETRY: + return IS_ENABLED(CONFIG_INTEL_PMT_TELEMETRY); + case VSEC_CAP_WATCHER: + return IS_ENABLED(CONFIG_INTEL_PMT_WATCHER); + case VSEC_CAP_CRASHLOG: + return IS_ENABLED(CONFIG_INTEL_PMT_CRASHLOG); + case VSEC_CAP_SDSI: + return IS_ENABLED(CONFIG_INTEL_SDSI); + case VSEC_CAP_TPMI: + return IS_ENABLED(CONFIG_INTEL_TPMI); + default: + return false; + } +} + /* * Although pci_device_id table is available in the pdev, this prototype is * necessary because the code using it can be called by an exported API that @@ -158,7 +178,8 @@ static int intel_vsec_link_devices(struct pci_dev *pdev, struct device *dev, for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) { struct device_link *link; - if (state[supplier_id] != STATE_REGISTERED) + if (state[supplier_id] != STATE_REGISTERED || + !vsec_driver_present(supplier_id)) continue; if (!suppliers[supplier_id]) { @@ -405,6 +426,11 @@ static int intel_vsec_register_device(struct pci_dev *pdev, priv->found_caps |= BIT(cap_id); + if (!vsec_driver_present(cap_id)) { + priv->state[cap_id] = STATE_SKIP; + return -ENODEV; + } + consumer_deps = get_consumer_dependencies(priv, cap_id); if (!consumer_deps || suppliers_ready(priv, consumer_deps, cap_id)) { ret = intel_vsec_add_dev(pdev, header, info, cap_id); From 10f32796e86c04f73b7f8580cc9483765ed19f49 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:22 -0700 Subject: [PATCH 31/63] platform/x86/intel/vsec: Add new Discovery feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the PCIe VSEC ID for new Intel Platform Monitoring Technology Capability Discovery feature. Discovery provides detailed information for the various Intel VSEC features. Also make the driver a supplier for TPMI and Telemetry drivers which will use the information. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-8-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 26 ++++++++++++++++++++++++-- include/linux/intel_vsec.h | 4 +++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 30e558af6888..4d76f1ac3c8c 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -66,6 +66,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id) case VSEC_ID_TPMI: return "tpmi"; + case VSEC_ID_DISCOVERY: + return "discovery"; + default: return NULL; } @@ -84,6 +87,8 @@ static bool intel_vsec_supported(u16 id, unsigned long caps) return !!(caps & VSEC_CAP_SDSI); case VSEC_ID_TPMI: return !!(caps & VSEC_CAP_TPMI); + case VSEC_ID_DISCOVERY: + return !!(caps & VSEC_CAP_DISCOVERY); default: return false; } @@ -138,6 +143,8 @@ static bool vsec_driver_present(int cap_id) return IS_ENABLED(CONFIG_INTEL_SDSI); case VSEC_CAP_TPMI: return IS_ENABLED(CONFIG_INTEL_TPMI); + case VSEC_CAP_DISCOVERY: + return IS_ENABLED(CONFIG_INTEL_PMT_DISCOVERY); default: return false; } @@ -392,6 +399,9 @@ static int get_cap_id(u32 header_id, unsigned long *cap_id) case VSEC_ID_TPMI: *cap_id = ilog2(VSEC_CAP_TPMI); break; + case VSEC_ID_DISCOVERY: + *cap_id = ilog2(VSEC_CAP_DISCOVERY); + break; default: return -EINVAL; } @@ -681,14 +691,26 @@ static const struct intel_vsec_platform_info mtl_info = { .caps = VSEC_CAP_TELEMETRY, }; +static const struct vsec_feature_dependency oobmsm_deps[] = { + { + .feature = VSEC_CAP_TELEMETRY, + .supplier_bitmap = VSEC_CAP_DISCOVERY, + }, +}; + /* OOBMSM info */ static const struct intel_vsec_platform_info oobmsm_info = { - .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI, + .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI | + VSEC_CAP_DISCOVERY, + .deps = oobmsm_deps, + .num_deps = ARRAY_SIZE(oobmsm_deps), }; /* DMR OOBMSM info */ static const struct intel_vsec_platform_info dmr_oobmsm_info = { - .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI, + .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI | VSEC_CAP_DISCOVERY, + .deps = oobmsm_deps, + .num_deps = ARRAY_SIZE(oobmsm_deps), }; /* TGL info */ diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index 71067afaca99..a07796d7d43b 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -16,7 +16,8 @@ #define VSEC_CAP_CRASHLOG BIT(3) #define VSEC_CAP_SDSI BIT(4) #define VSEC_CAP_TPMI BIT(5) -#define VSEC_FEATURE_COUNT 6 +#define VSEC_CAP_DISCOVERY BIT(6) +#define VSEC_FEATURE_COUNT 7 /* Intel DVSEC offsets */ #define INTEL_DVSEC_ENTRIES 0xA @@ -33,6 +34,7 @@ enum intel_vsec_id { VSEC_ID_TELEMETRY = 2, VSEC_ID_WATCHER = 3, VSEC_ID_CRASHLOG = 4, + VSEC_ID_DISCOVERY = 12, VSEC_ID_SDSI = 65, VSEC_ID_TPMI = 66, }; From d9a0788093565c300f7c8dd034dbfa6ac4da9aa6 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:23 -0700 Subject: [PATCH 32/63] platform/x86/intel/pmt: Add PMT Discovery driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces a new driver to enumerate and expose Intel Platform Monitoring Technology (PMT) capabilities via a simple discovery mechanism. The PMT Discovery driver parses hardware-provided discovery tables from Intel Out of Band Management Services Modules (OOBMSM) and extracts feature information for various providers (such as TPMI, Telemetry, Crash Log, etc). This unified interface simplifies the process of determining which manageability and telemetry features are supported by a given platform. This new feature is described in the Intel Platform Monitoring Technology 3.0 specification, section 6.6 Capability. Key changes and additions: New file drivers/platform/x86/intel/pmt/discovery.c: – Implements the discovery logic to map the discovery resource, read the feature discovery table, and validate feature parameters. New file drivers/platform/x86/intel/pmt/features.c: – Defines feature names, layouts, and associated capability masks. – Provides a mapping between raw hardware attributes and sysfs representations for easier integration with user-space tools. New header include/linux/intel_pmt_features.h: – Declares constants, masks, and feature identifiers used across the PMT framework. Sysfs integration: – Feature attributes are exposed under /sys/class/intel_pmt. – Each device is represented by a subfolder within the intel_pmt class, named using its DBDF (Domain:Bus:Device.Function), e.g.: features-0000:00:03.1 – Example directory layout for a device: /sys/class/intel_pmt/features-0000:00:03.1/ ├── accelerator_telemetry ├── crash_log ├── per_core_environment_telemetry ├── per_core_performance_telemetry ├── per_rmid_energy_telemetry ├── per_rmid_perf_telemetry ├── tpmi_control ├── tracing └── uncore_telemetry By exposing PMT feature details through sysfs and integrating with the existing PMT class, this driver paves the way for more streamlined integration of PMT-based manageability and telemetry tools. Link: https://www.intel.com/content/www/us/en/content-details/710389/intel-platform-monitoring-technology-intel-pmt-external-specification.html Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-9-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/Kconfig | 12 + drivers/platform/x86/intel/pmt/Makefile | 2 + drivers/platform/x86/intel/pmt/class.c | 35 +- drivers/platform/x86/intel/pmt/class.h | 2 + drivers/platform/x86/intel/pmt/discovery.c | 602 +++++++++++++++++++++ drivers/platform/x86/intel/pmt/features.c | 205 +++++++ include/linux/intel_pmt_features.h | 157 ++++++ 7 files changed, 1013 insertions(+), 2 deletions(-) create mode 100644 drivers/platform/x86/intel/pmt/discovery.c create mode 100644 drivers/platform/x86/intel/pmt/features.c create mode 100644 include/linux/intel_pmt_features.h diff --git a/drivers/platform/x86/intel/pmt/Kconfig b/drivers/platform/x86/intel/pmt/Kconfig index e916fc966221..0ad91b5112e9 100644 --- a/drivers/platform/x86/intel/pmt/Kconfig +++ b/drivers/platform/x86/intel/pmt/Kconfig @@ -38,3 +38,15 @@ config INTEL_PMT_CRASHLOG To compile this driver as a module, choose M here: the module will be called intel_pmt_crashlog. + +config INTEL_PMT_DISCOVERY + tristate "Intel Platform Monitoring Technology (PMT) Discovery driver" + depends on INTEL_VSEC + select INTEL_PMT_CLASS + help + The Intel Platform Monitoring Technology (PMT) discovery driver provides + access to details about the various PMT features and feature specific + attributes. + + To compile this driver as a module, choose M here: the module + will be called pmt_discovery. diff --git a/drivers/platform/x86/intel/pmt/Makefile b/drivers/platform/x86/intel/pmt/Makefile index 279e158c7c23..8aed7e1592e4 100644 --- a/drivers/platform/x86/intel/pmt/Makefile +++ b/drivers/platform/x86/intel/pmt/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_INTEL_PMT_TELEMETRY) += pmt_telemetry.o pmt_telemetry-y := telemetry.o obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_crashlog.o pmt_crashlog-y := crashlog.o +obj-$(CONFIG_INTEL_PMT_DISCOVERY) += pmt_discovery.o +pmt_discovery-y := discovery.o features.o diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index 7233b654bbad..a806a81ece52 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -9,11 +9,13 @@ */ #include +#include #include #include #include #include #include +#include #include "class.h" @@ -166,12 +168,41 @@ static struct attribute *intel_pmt_attrs[] = { &dev_attr_offset.attr, NULL }; -ATTRIBUTE_GROUPS(intel_pmt); -static struct class intel_pmt_class = { +static umode_t intel_pmt_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct auxiliary_device *auxdev = to_auxiliary_dev(dev->parent); + struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev); + + /* + * Place the discovery features folder in /sys/class/intel_pmt, but + * exclude the common attributes as they are not applicable. + */ + if (ivdev->cap_id == ilog2(VSEC_CAP_DISCOVERY)) + return 0; + + return attr->mode; +} + +static bool intel_pmt_group_visible(struct kobject *kobj) +{ + return true; +} +DEFINE_SYSFS_GROUP_VISIBLE(intel_pmt); + +static const struct attribute_group intel_pmt_group = { + .attrs = intel_pmt_attrs, + .is_visible = SYSFS_GROUP_VISIBLE(intel_pmt), +}; +__ATTRIBUTE_GROUPS(intel_pmt); + +struct class intel_pmt_class = { .name = "intel_pmt", .dev_groups = intel_pmt_groups, }; +EXPORT_SYMBOL_GPL(intel_pmt_class); static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, struct intel_vsec_device *ivdev, diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index b2006d57779d..39c32357ee2c 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -20,6 +20,7 @@ #define GET_ADDRESS(v) ((v) & GENMASK(31, 3)) struct pci_dev; +extern struct class intel_pmt_class; struct telem_endpoint { struct pci_dev *pcidev; @@ -48,6 +49,7 @@ struct intel_pmt_entry { unsigned long base_addr; size_t size; u32 guid; + u32 num_rmids; /* Number of Resource Monitoring IDs */ int devid; }; diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c new file mode 100644 index 000000000000..4b4fa3137ad2 --- /dev/null +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitory Technology Discovery driver + * + * Copyright (c) 2025, Intel Corporation. + * All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "class.h" + +#define MAX_FEATURE_VERSION 0 +#define DT_TBIR GENMASK(2, 0) +#define FEAT_ATTR_SIZE(x) ((x) * sizeof(u32)) +#define PMT_GUID_SIZE(x) ((x) * sizeof(u32)) +#define PMT_ACCESS_TYPE_RSVD 0xF +#define SKIP_FEATURE 1 + +struct feature_discovery_table { + u32 access_type:4; + u32 version:8; + u32 size:16; + u32 reserved:4; + u32 id; + u32 offset; + u32 reserved2; +}; + +/* Common feature table header */ +struct feature_header { + u32 attr_size:8; + u32 num_guids:8; + u32 reserved:16; +}; + +/* Feature attribute fields */ +struct caps { + u32 caps; +}; + +struct command { + u32 max_stream_size:16; + u32 max_command_size:16; +}; + +struct watcher { + u32 reserved:21; + u32 period:11; + struct command command; +}; + +struct rmid { + u32 num_rmids:16; /* Number of Resource Monitoring IDs */ + u32 reserved:16; + struct watcher watcher; +}; + +struct feature_table { + struct feature_header header; + struct caps caps; + union { + struct command command; + struct watcher watcher; + struct rmid rmid; + }; + u32 *guids; +}; + +/* For backreference in struct feature */ +struct pmt_features_priv; + +struct feature { + struct feature_table table; + struct kobject kobj; + struct pmt_features_priv *priv; + struct list_head list; + const struct attribute_group *attr_group; + enum pmt_feature_id id; +}; + +struct pmt_features_priv { + struct device *parent; + struct device *dev; + int count; + u32 mask; + struct feature feature[]; +}; + +static LIST_HEAD(pmt_feature_list); +static DEFINE_MUTEX(feature_list_lock); + +#define to_pmt_feature(x) container_of(x, struct feature, kobj) +static void pmt_feature_release(struct kobject *kobj) +{ +} + +static ssize_t caps_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct feature *feature = to_pmt_feature(kobj); + struct pmt_cap **pmt_caps; + u32 caps = feature->table.caps.caps; + ssize_t ret = 0; + + switch (feature->id) { + case FEATURE_PER_CORE_PERF_TELEM: + pmt_caps = pmt_caps_pcpt; + break; + case FEATURE_PER_CORE_ENV_TELEM: + pmt_caps = pmt_caps_pcet; + break; + case FEATURE_PER_RMID_PERF_TELEM: + pmt_caps = pmt_caps_rmid_perf; + break; + case FEATURE_ACCEL_TELEM: + pmt_caps = pmt_caps_accel; + break; + case FEATURE_UNCORE_TELEM: + pmt_caps = pmt_caps_uncore; + break; + case FEATURE_CRASH_LOG: + pmt_caps = pmt_caps_crashlog; + break; + case FEATURE_PETE_LOG: + pmt_caps = pmt_caps_pete; + break; + case FEATURE_TPMI_CTRL: + pmt_caps = pmt_caps_tpmi; + break; + case FEATURE_TRACING: + pmt_caps = pmt_caps_tracing; + break; + case FEATURE_PER_RMID_ENERGY_TELEM: + pmt_caps = pmt_caps_rmid_energy; + break; + default: + return -EINVAL; + } + + while (*pmt_caps) { + struct pmt_cap *pmt_cap = *pmt_caps; + + while (pmt_cap->name) { + ret += sysfs_emit_at(buf, ret, "%-40s Available: %s\n", pmt_cap->name, + str_yes_no(pmt_cap->mask & caps)); + pmt_cap++; + } + pmt_caps++; + } + + return ret; +} +static struct kobj_attribute caps_attribute = __ATTR_RO(caps); + +static struct watcher *get_watcher(struct feature *feature) +{ + switch (feature_layout[feature->id]) { + case LAYOUT_RMID: + return &feature->table.rmid.watcher; + case LAYOUT_WATCHER: + return &feature->table.watcher; + default: + return ERR_PTR(-EINVAL); + } +} + +static struct command *get_command(struct feature *feature) +{ + switch (feature_layout[feature->id]) { + case LAYOUT_RMID: + return &feature->table.rmid.watcher.command; + case LAYOUT_WATCHER: + return &feature->table.watcher.command; + case LAYOUT_COMMAND: + return &feature->table.command; + default: + return ERR_PTR(-EINVAL); + } +} + +static ssize_t num_rmids_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct feature *feature = to_pmt_feature(kobj); + + return sysfs_emit(buf, "%u\n", feature->table.rmid.num_rmids); +} +static struct kobj_attribute num_rmids_attribute = __ATTR_RO(num_rmids); + +static ssize_t min_watcher_period_ms_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct feature *feature = to_pmt_feature(kobj); + struct watcher *watcher = get_watcher(feature); + + if (IS_ERR(watcher)) + return PTR_ERR(watcher); + + return sysfs_emit(buf, "%u\n", watcher->period); +} +static struct kobj_attribute min_watcher_period_ms_attribute = + __ATTR_RO(min_watcher_period_ms); + +static ssize_t max_stream_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct feature *feature = to_pmt_feature(kobj); + struct command *command = get_command(feature); + + if (IS_ERR(command)) + return PTR_ERR(command); + + return sysfs_emit(buf, "%u\n", command->max_stream_size); +} +static struct kobj_attribute max_stream_size_attribute = + __ATTR_RO(max_stream_size); + +static ssize_t max_command_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct feature *feature = to_pmt_feature(kobj); + struct command *command = get_command(feature); + + if (IS_ERR(command)) + return PTR_ERR(command); + + return sysfs_emit(buf, "%u\n", command->max_command_size); +} +static struct kobj_attribute max_command_size_attribute = + __ATTR_RO(max_command_size); + +static ssize_t guids_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct feature *feature = to_pmt_feature(kobj); + int i, count = 0; + + for (i = 0; i < feature->table.header.num_guids; i++) + count += sysfs_emit_at(buf, count, "0x%x\n", + feature->table.guids[i]); + + return count; +} +static struct kobj_attribute guids_attribute = __ATTR_RO(guids); + +static struct attribute *pmt_feature_rmid_attrs[] = { + &caps_attribute.attr, + &num_rmids_attribute.attr, + &min_watcher_period_ms_attribute.attr, + &max_stream_size_attribute.attr, + &max_command_size_attribute.attr, + &guids_attribute.attr, + NULL +}; +ATTRIBUTE_GROUPS(pmt_feature_rmid); + +static const struct kobj_type pmt_feature_rmid_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .release = pmt_feature_release, + .default_groups = pmt_feature_rmid_groups, +}; + +static struct attribute *pmt_feature_watcher_attrs[] = { + &caps_attribute.attr, + &min_watcher_period_ms_attribute.attr, + &max_stream_size_attribute.attr, + &max_command_size_attribute.attr, + &guids_attribute.attr, + NULL +}; +ATTRIBUTE_GROUPS(pmt_feature_watcher); + +static const struct kobj_type pmt_feature_watcher_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .release = pmt_feature_release, + .default_groups = pmt_feature_watcher_groups, +}; + +static struct attribute *pmt_feature_command_attrs[] = { + &caps_attribute.attr, + &max_stream_size_attribute.attr, + &max_command_size_attribute.attr, + &guids_attribute.attr, + NULL +}; +ATTRIBUTE_GROUPS(pmt_feature_command); + +static const struct kobj_type pmt_feature_command_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .release = pmt_feature_release, + .default_groups = pmt_feature_command_groups, +}; + +static struct attribute *pmt_feature_guids_attrs[] = { + &caps_attribute.attr, + &guids_attribute.attr, + NULL +}; +ATTRIBUTE_GROUPS(pmt_feature_guids); + +static const struct kobj_type pmt_feature_guids_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .release = pmt_feature_release, + .default_groups = pmt_feature_guids_groups, +}; + +static int +pmt_feature_get_disc_table(struct pmt_features_priv *priv, + struct resource *disc_res, + struct feature_discovery_table *disc_tbl) +{ + void __iomem *disc_base; + + disc_base = devm_ioremap_resource(priv->dev, disc_res); + if (IS_ERR(disc_base)) + return PTR_ERR(disc_base); + + memcpy_fromio(disc_tbl, disc_base, sizeof(*disc_tbl)); + + devm_iounmap(priv->dev, disc_base); + + if (priv->mask & BIT(disc_tbl->id)) + return dev_err_probe(priv->dev, -EINVAL, "Duplicate feature: %s\n", + pmt_feature_names[disc_tbl->id]); + + /* + * Some devices may expose non-functioning entries that are + * reserved for future use. They have zero size. Do not fail + * probe for these. Just ignore them. + */ + if (disc_tbl->size == 0 || disc_tbl->access_type == PMT_ACCESS_TYPE_RSVD) + return SKIP_FEATURE; + + if (disc_tbl->version > MAX_FEATURE_VERSION) + return SKIP_FEATURE; + + if (!pmt_feature_id_is_valid(disc_tbl->id)) + return SKIP_FEATURE; + + priv->mask |= BIT(disc_tbl->id); + + return 0; +} + +static int +pmt_feature_get_feature_table(struct pmt_features_priv *priv, + struct feature *feature, + struct feature_discovery_table *disc_tbl, + struct resource *disc_res) +{ + struct feature_table *feat_tbl = &feature->table; + struct feature_header *header; + struct resource res = {}; + resource_size_t res_size; + void __iomem *feat_base, *feat_offset; + void *tbl_offset; + size_t size; + u32 *guids; + u8 tbir; + + tbir = FIELD_GET(DT_TBIR, disc_tbl->offset); + + switch (disc_tbl->access_type) { + case ACCESS_LOCAL: + if (tbir) + return dev_err_probe(priv->dev, -EINVAL, + "Unsupported BAR index %u for access type %u\n", + tbir, disc_tbl->access_type); + + + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + 1 + */ + res = DEFINE_RES_MEM(disc_res->end + disc_tbl->offset + 1, + disc_tbl->size * sizeof(u32)); + break; + + default: + return dev_err_probe(priv->dev, -EINVAL, "Unrecognized access_type %u\n", + disc_tbl->access_type); + } + + feature->id = disc_tbl->id; + + /* Get the feature table */ + feat_base = devm_ioremap_resource(priv->dev, &res); + if (IS_ERR(feat_base)) + return PTR_ERR(feat_base); + + feat_offset = feat_base; + tbl_offset = feat_tbl; + + /* Get the header */ + header = &feat_tbl->header; + memcpy_fromio(header, feat_offset, sizeof(*header)); + + /* Validate fields fit within mapped resource */ + size = sizeof(*header) + FEAT_ATTR_SIZE(header->attr_size) + + PMT_GUID_SIZE(header->num_guids); + res_size = resource_size(&res); + if (WARN(size > res_size, "Bad table size %ld > %pa", size, &res_size)) + return -EINVAL; + + /* Get the feature attributes, including capability fields */ + tbl_offset += sizeof(*header); + feat_offset += sizeof(*header); + + memcpy_fromio(tbl_offset, feat_offset, FEAT_ATTR_SIZE(header->attr_size)); + + /* Finally, get the guids */ + guids = devm_kmalloc(priv->dev, PMT_GUID_SIZE(header->num_guids), GFP_KERNEL); + if (!guids) + return -ENOMEM; + + feat_offset += FEAT_ATTR_SIZE(header->attr_size); + + memcpy_fromio(guids, feat_offset, PMT_GUID_SIZE(header->num_guids)); + + feat_tbl->guids = guids; + + devm_iounmap(priv->dev, feat_base); + + return 0; +} + +static void pmt_features_add_feat(struct feature *feature) +{ + guard(mutex)(&feature_list_lock); + list_add(&feature->list, &pmt_feature_list); +} + +static void pmt_features_remove_feat(struct feature *feature) +{ + guard(mutex)(&feature_list_lock); + list_del(&feature->list); +} + +/* Get the discovery table and use it to get the feature table */ +static int pmt_features_discovery(struct pmt_features_priv *priv, + struct feature *feature, + struct intel_vsec_device *ivdev, + int idx) +{ + struct feature_discovery_table disc_tbl = {}; /* Avoid false warning */ + struct resource *disc_res = &ivdev->resource[idx]; + const struct kobj_type *ktype; + int ret; + + ret = pmt_feature_get_disc_table(priv, disc_res, &disc_tbl); + if (ret) + return ret; + + ret = pmt_feature_get_feature_table(priv, feature, &disc_tbl, disc_res); + if (ret) + return ret; + + switch (feature_layout[feature->id]) { + case LAYOUT_RMID: + ktype = &pmt_feature_rmid_ktype; + feature->attr_group = &pmt_feature_rmid_group; + break; + case LAYOUT_WATCHER: + ktype = &pmt_feature_watcher_ktype; + feature->attr_group = &pmt_feature_watcher_group; + break; + case LAYOUT_COMMAND: + ktype = &pmt_feature_command_ktype; + feature->attr_group = &pmt_feature_command_group; + break; + case LAYOUT_CAPS_ONLY: + ktype = &pmt_feature_guids_ktype; + feature->attr_group = &pmt_feature_guids_group; + break; + default: + return -EINVAL; + } + + ret = kobject_init_and_add(&feature->kobj, ktype, &priv->dev->kobj, + pmt_feature_names[feature->id]); + if (ret) + return ret; + + kobject_uevent(&feature->kobj, KOBJ_ADD); + pmt_features_add_feat(feature); + + return 0; +} + +static void pmt_features_remove(struct auxiliary_device *auxdev) +{ + struct pmt_features_priv *priv = auxiliary_get_drvdata(auxdev); + int i; + + for (i = 0; i < priv->count; i++) { + struct feature *feature = &priv->feature[i]; + + pmt_features_remove_feat(feature); + sysfs_remove_group(&feature->kobj, feature->attr_group); + kobject_put(&feature->kobj); + } + + device_unregister(priv->dev); +} + +static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) +{ + struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev); + struct pmt_features_priv *priv; + size_t size; + int ret, i; + + size = struct_size(priv, feature, ivdev->num_resources); + priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->parent = &ivdev->pcidev->dev; + auxiliary_set_drvdata(auxdev, priv); + + priv->dev = device_create(&intel_pmt_class, &auxdev->dev, MKDEV(0, 0), priv, + "%s-%s", "features", dev_name(priv->parent)); + if (IS_ERR(priv->dev)) + return dev_err_probe(priv->dev, PTR_ERR(priv->dev), + "Could not create %s-%s device node\n", + "features", dev_name(priv->dev)); + + /* Initialize each feature */ + for (i = 0; i < ivdev->num_resources; i++) { + struct feature *feature = &priv->feature[priv->count]; + + ret = pmt_features_discovery(priv, feature, ivdev, i); + if (ret == SKIP_FEATURE) + continue; + if (ret != 0) + goto abort_probe; + + feature->priv = priv; + priv->count++; + } + + return 0; + +abort_probe: + /* + * Only fully initialized features are tracked in priv->count, which is + * incremented only after a feature is completely set up (i.e., after + * discovery and sysfs registration). If feature initialization fails, + * the failing feature's state is local and does not require rollback. + * + * Therefore, on error, we can safely call the driver's remove() routine + * pmt_features_remove() to clean up only those features that were + * fully initialized and counted. All other resources are device-managed + * and will be cleaned up automatically during device_unregister(). + */ + pmt_features_remove(auxdev); + + return ret; +} + +static const struct auxiliary_device_id pmt_features_id_table[] = { + { .name = "intel_vsec.discovery" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, pmt_features_id_table); + +static struct auxiliary_driver pmt_features_aux_driver = { + .id_table = pmt_features_id_table, + .remove = pmt_features_remove, + .probe = pmt_features_probe, +}; +module_auxiliary_driver(pmt_features_aux_driver); + +MODULE_AUTHOR("David E. Box "); +MODULE_DESCRIPTION("Intel PMT Discovery driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("INTEL_PMT"); diff --git a/drivers/platform/x86/intel/pmt/features.c b/drivers/platform/x86/intel/pmt/features.c new file mode 100644 index 000000000000..8a39cddc75c8 --- /dev/null +++ b/drivers/platform/x86/intel/pmt/features.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" + */ + +#include +#include + +#include + +const char * const pmt_feature_names[] = { + [FEATURE_PER_CORE_PERF_TELEM] = "per_core_performance_telemetry", + [FEATURE_PER_CORE_ENV_TELEM] = "per_core_environment_telemetry", + [FEATURE_PER_RMID_PERF_TELEM] = "per_rmid_perf_telemetry", + [FEATURE_ACCEL_TELEM] = "accelerator_telemetry", + [FEATURE_UNCORE_TELEM] = "uncore_telemetry", + [FEATURE_CRASH_LOG] = "crash_log", + [FEATURE_PETE_LOG] = "pete_log", + [FEATURE_TPMI_CTRL] = "tpmi_control", + [FEATURE_TRACING] = "tracing", + [FEATURE_PER_RMID_ENERGY_TELEM] = "per_rmid_energy_telemetry", +}; +EXPORT_SYMBOL_NS_GPL(pmt_feature_names, "INTEL_PMT_DISCOVERY"); + +enum feature_layout feature_layout[] = { + [FEATURE_PER_CORE_PERF_TELEM] = LAYOUT_WATCHER, + [FEATURE_PER_CORE_ENV_TELEM] = LAYOUT_WATCHER, + [FEATURE_PER_RMID_PERF_TELEM] = LAYOUT_RMID, + [FEATURE_ACCEL_TELEM] = LAYOUT_WATCHER, + [FEATURE_UNCORE_TELEM] = LAYOUT_WATCHER, + [FEATURE_CRASH_LOG] = LAYOUT_COMMAND, + [FEATURE_PETE_LOG] = LAYOUT_COMMAND, + [FEATURE_TPMI_CTRL] = LAYOUT_CAPS_ONLY, + [FEATURE_TRACING] = LAYOUT_CAPS_ONLY, + [FEATURE_PER_RMID_ENERGY_TELEM] = LAYOUT_RMID, +}; + +struct pmt_cap pmt_cap_common[] = { + {PMT_CAP_TELEM, "telemetry"}, + {PMT_CAP_WATCHER, "watcher"}, + {PMT_CAP_CRASHLOG, "crashlog"}, + {PMT_CAP_STREAMING, "streaming"}, + {PMT_CAP_THRESHOLD, "threshold"}, + {PMT_CAP_WINDOW, "window"}, + {PMT_CAP_CONFIG, "config"}, + {PMT_CAP_TRACING, "tracing"}, + {PMT_CAP_INBAND, "inband"}, + {PMT_CAP_OOB, "oob"}, + {PMT_CAP_SECURED_CHAN, "secure_chan"}, + {PMT_CAP_PMT_SP, "pmt_sp"}, + {PMT_CAP_PMT_SP_POLICY, "pmt_sp_policy"}, + {} +}; + +struct pmt_cap pmt_cap_pcpt[] = { + {PMT_CAP_PCPT_CORE_PERF, "core_performance"}, + {PMT_CAP_PCPT_CORE_C0_RES, "core_c0_residency"}, + {PMT_CAP_PCPT_CORE_ACTIVITY, "core_activity"}, + {PMT_CAP_PCPT_CACHE_PERF, "cache_performance"}, + {PMT_CAP_PCPT_QUALITY_TELEM, "quality_telemetry"}, + {} +}; + +struct pmt_cap *pmt_caps_pcpt[] = { + pmt_cap_common, + pmt_cap_pcpt, + NULL +}; + +struct pmt_cap pmt_cap_pcet[] = { + {PMT_CAP_PCET_WORKPOINT_HIST, "workpoint_histogram"}, + {PMT_CAP_PCET_CORE_CURR_TEMP, "core_current_temp"}, + {PMT_CAP_PCET_CORE_INST_RES, "core_inst_residency"}, + {PMT_CAP_PCET_QUALITY_TELEM, "quality_telemetry"}, + {PMT_CAP_PCET_CORE_CDYN_LVL, "core_cdyn_level"}, + {PMT_CAP_PCET_CORE_STRESS_LVL, "core_stress_level"}, + {PMT_CAP_PCET_CORE_DAS, "core_digital_aging_sensor"}, + {PMT_CAP_PCET_FIVR_HEALTH, "fivr_health"}, + {PMT_CAP_PCET_ENERGY, "energy"}, + {PMT_CAP_PCET_PEM_STATUS, "pem_status"}, + {PMT_CAP_PCET_CORE_C_STATE, "core_c_state"}, + {} +}; + +struct pmt_cap *pmt_caps_pcet[] = { + pmt_cap_common, + pmt_cap_pcet, + NULL +}; + +struct pmt_cap pmt_cap_rmid_perf[] = { + {PMT_CAP_RMID_CORES_PERF, "core_performance"}, + {PMT_CAP_RMID_CACHE_PERF, "cache_performance"}, + {PMT_CAP_RMID_PERF_QUAL, "performance_quality"}, + {} +}; + +struct pmt_cap *pmt_caps_rmid_perf[] = { + pmt_cap_common, + pmt_cap_rmid_perf, + NULL +}; + +struct pmt_cap pmt_cap_accel[] = { + {PMT_CAP_ACCEL_CPM_TELEM, "content_processing_module"}, + {PMT_CAP_ACCEL_TIP_TELEM, "content_turbo_ip"}, + {} +}; + +struct pmt_cap *pmt_caps_accel[] = { + pmt_cap_common, + pmt_cap_accel, + NULL +}; + +struct pmt_cap pmt_cap_uncore[] = { + {PMT_CAP_UNCORE_IO_CA_TELEM, "io_ca"}, + {PMT_CAP_UNCORE_RMID_TELEM, "rmid"}, + {PMT_CAP_UNCORE_D2D_ULA_TELEM, "d2d_ula"}, + {PMT_CAP_UNCORE_PKGC_TELEM, "package_c"}, + {} +}; + +struct pmt_cap *pmt_caps_uncore[] = { + pmt_cap_common, + pmt_cap_uncore, + NULL +}; + +struct pmt_cap pmt_cap_crashlog[] = { + {PMT_CAP_CRASHLOG_MAN_TRIG, "manual_trigger"}, + {PMT_CAP_CRASHLOG_CORE, "core"}, + {PMT_CAP_CRASHLOG_UNCORE, "uncore"}, + {PMT_CAP_CRASHLOG_TOR, "tor"}, + {PMT_CAP_CRASHLOG_S3M, "s3m"}, + {PMT_CAP_CRASHLOG_PERSISTENCY, "persistency"}, + {PMT_CAP_CRASHLOG_CLIP_GPIO, "crashlog_in_progress"}, + {PMT_CAP_CRASHLOG_PRE_RESET, "pre_reset_extraction"}, + {PMT_CAP_CRASHLOG_POST_RESET, "post_reset_extraction"}, + {} +}; + +struct pmt_cap *pmt_caps_crashlog[] = { + pmt_cap_common, + pmt_cap_crashlog, + NULL +}; + +struct pmt_cap pmt_cap_pete[] = { + {PMT_CAP_PETE_MAN_TRIG, "manual_trigger"}, + {PMT_CAP_PETE_ENCRYPTION, "encryption"}, + {PMT_CAP_PETE_PERSISTENCY, "persistency"}, + {PMT_CAP_PETE_REQ_TOKENS, "required_tokens"}, + {PMT_CAP_PETE_PROD_ENABLED, "production_enabled"}, + {PMT_CAP_PETE_DEBUG_ENABLED, "debug_enabled"}, + {} +}; + +struct pmt_cap *pmt_caps_pete[] = { + pmt_cap_common, + pmt_cap_pete, + NULL +}; + +struct pmt_cap pmt_cap_tpmi[] = { + {PMT_CAP_TPMI_MAILBOX, "mailbox"}, + {PMT_CAP_TPMI_LOCK, "bios_lock"}, + {} +}; + +struct pmt_cap *pmt_caps_tpmi[] = { + pmt_cap_common, + pmt_cap_tpmi, + NULL +}; + +struct pmt_cap pmt_cap_tracing[] = { + {PMT_CAP_TRACE_SRAR, "srar_errors"}, + {PMT_CAP_TRACE_CORRECTABLE, "correctable_errors"}, + {PMT_CAP_TRACE_MCTP, "mctp"}, + {PMT_CAP_TRACE_MRT, "memory_resiliency"}, + {} +}; + +struct pmt_cap *pmt_caps_tracing[] = { + pmt_cap_common, + pmt_cap_tracing, + NULL +}; + +struct pmt_cap pmt_cap_rmid_energy[] = { + {PMT_CAP_RMID_ENERGY, "energy"}, + {PMT_CAP_RMID_ACTIVITY, "activity"}, + {PMT_CAP_RMID_ENERGY_QUAL, "energy_quality"}, + {} +}; + +struct pmt_cap *pmt_caps_rmid_energy[] = { + pmt_cap_common, + pmt_cap_rmid_energy, + NULL +}; diff --git a/include/linux/intel_pmt_features.h b/include/linux/intel_pmt_features.h new file mode 100644 index 000000000000..53573a4a49b7 --- /dev/null +++ b/include/linux/intel_pmt_features.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _FEATURES_H +#define _FEATURES_H + +#include +#include + +/* Common masks */ +#define PMT_CAP_TELEM BIT(0) +#define PMT_CAP_WATCHER BIT(1) +#define PMT_CAP_CRASHLOG BIT(2) +#define PMT_CAP_STREAMING BIT(3) +#define PMT_CAP_THRESHOLD BIT(4) +#define PMT_CAP_WINDOW BIT(5) +#define PMT_CAP_CONFIG BIT(6) +#define PMT_CAP_TRACING BIT(7) +#define PMT_CAP_INBAND BIT(8) +#define PMT_CAP_OOB BIT(9) +#define PMT_CAP_SECURED_CHAN BIT(10) + +#define PMT_CAP_PMT_SP BIT(11) +#define PMT_CAP_PMT_SP_POLICY GENMASK(17, 12) + +/* Per Core Performance Telemetry (PCPT) specific masks */ +#define PMT_CAP_PCPT_CORE_PERF BIT(18) +#define PMT_CAP_PCPT_CORE_C0_RES BIT(19) +#define PMT_CAP_PCPT_CORE_ACTIVITY BIT(20) +#define PMT_CAP_PCPT_CACHE_PERF BIT(21) +#define PMT_CAP_PCPT_QUALITY_TELEM BIT(22) + +/* Per Core Environmental Telemetry (PCET) specific masks */ +#define PMT_CAP_PCET_WORKPOINT_HIST BIT(18) +#define PMT_CAP_PCET_CORE_CURR_TEMP BIT(19) +#define PMT_CAP_PCET_CORE_INST_RES BIT(20) +#define PMT_CAP_PCET_QUALITY_TELEM BIT(21) /* Same as PMT_CAP_PCPT */ +#define PMT_CAP_PCET_CORE_CDYN_LVL BIT(22) +#define PMT_CAP_PCET_CORE_STRESS_LVL BIT(23) +#define PMT_CAP_PCET_CORE_DAS BIT(24) +#define PMT_CAP_PCET_FIVR_HEALTH BIT(25) +#define PMT_CAP_PCET_ENERGY BIT(26) +#define PMT_CAP_PCET_PEM_STATUS BIT(27) +#define PMT_CAP_PCET_CORE_C_STATE BIT(28) + +/* Per RMID Performance Telemetry specific masks */ +#define PMT_CAP_RMID_CORES_PERF BIT(18) +#define PMT_CAP_RMID_CACHE_PERF BIT(19) +#define PMT_CAP_RMID_PERF_QUAL BIT(20) + +/* Accelerator Telemetry specific masks */ +#define PMT_CAP_ACCEL_CPM_TELEM BIT(18) +#define PMT_CAP_ACCEL_TIP_TELEM BIT(19) + +/* Uncore Telemetry specific masks */ +#define PMT_CAP_UNCORE_IO_CA_TELEM BIT(18) +#define PMT_CAP_UNCORE_RMID_TELEM BIT(19) +#define PMT_CAP_UNCORE_D2D_ULA_TELEM BIT(20) +#define PMT_CAP_UNCORE_PKGC_TELEM BIT(21) + +/* Crash Log specific masks */ +#define PMT_CAP_CRASHLOG_MAN_TRIG BIT(11) +#define PMT_CAP_CRASHLOG_CORE BIT(12) +#define PMT_CAP_CRASHLOG_UNCORE BIT(13) +#define PMT_CAP_CRASHLOG_TOR BIT(14) +#define PMT_CAP_CRASHLOG_S3M BIT(15) +#define PMT_CAP_CRASHLOG_PERSISTENCY BIT(16) +#define PMT_CAP_CRASHLOG_CLIP_GPIO BIT(17) +#define PMT_CAP_CRASHLOG_PRE_RESET BIT(18) +#define PMT_CAP_CRASHLOG_POST_RESET BIT(19) + +/* PeTe Log specific masks */ +#define PMT_CAP_PETE_MAN_TRIG BIT(11) +#define PMT_CAP_PETE_ENCRYPTION BIT(12) +#define PMT_CAP_PETE_PERSISTENCY BIT(13) +#define PMT_CAP_PETE_REQ_TOKENS BIT(14) +#define PMT_CAP_PETE_PROD_ENABLED BIT(15) +#define PMT_CAP_PETE_DEBUG_ENABLED BIT(16) + +/* TPMI control specific masks */ +#define PMT_CAP_TPMI_MAILBOX BIT(11) +#define PMT_CAP_TPMI_LOCK BIT(12) + +/* Tracing specific masks */ +#define PMT_CAP_TRACE_SRAR BIT(11) +#define PMT_CAP_TRACE_CORRECTABLE BIT(12) +#define PMT_CAP_TRACE_MCTP BIT(13) +#define PMT_CAP_TRACE_MRT BIT(14) + +/* Per RMID Energy Telemetry specific masks */ +#define PMT_CAP_RMID_ENERGY BIT(18) +#define PMT_CAP_RMID_ACTIVITY BIT(19) +#define PMT_CAP_RMID_ENERGY_QUAL BIT(20) + +enum pmt_feature_id { + FEATURE_INVALID = 0x0, + FEATURE_PER_CORE_PERF_TELEM = 0x1, + FEATURE_PER_CORE_ENV_TELEM = 0x2, + FEATURE_PER_RMID_PERF_TELEM = 0x3, + FEATURE_ACCEL_TELEM = 0x4, + FEATURE_UNCORE_TELEM = 0x5, + FEATURE_CRASH_LOG = 0x6, + FEATURE_PETE_LOG = 0x7, + FEATURE_TPMI_CTRL = 0x8, + FEATURE_RESERVED = 0x9, + FEATURE_TRACING = 0xA, + FEATURE_PER_RMID_ENERGY_TELEM = 0xB, + FEATURE_MAX = 0xB, +}; + +enum feature_layout { + LAYOUT_RMID, + LAYOUT_WATCHER, + LAYOUT_COMMAND, + LAYOUT_CAPS_ONLY, +}; + +struct pmt_cap { + u32 mask; + const char *name; +}; + +extern const char * const pmt_feature_names[]; +extern enum feature_layout feature_layout[]; +extern struct pmt_cap pmt_cap_common[]; +extern struct pmt_cap pmt_cap_pcpt[]; +extern struct pmt_cap *pmt_caps_pcpt[]; +extern struct pmt_cap pmt_cap_pcet[]; +extern struct pmt_cap *pmt_caps_pcet[]; +extern struct pmt_cap pmt_cap_rmid_perf[]; +extern struct pmt_cap *pmt_caps_rmid_perf[]; +extern struct pmt_cap pmt_cap_accel[]; +extern struct pmt_cap *pmt_caps_accel[]; +extern struct pmt_cap pmt_cap_uncore[]; +extern struct pmt_cap *pmt_caps_uncore[]; +extern struct pmt_cap pmt_cap_crashlog[]; +extern struct pmt_cap *pmt_caps_crashlog[]; +extern struct pmt_cap pmt_cap_pete[]; +extern struct pmt_cap *pmt_caps_pete[]; +extern struct pmt_cap pmt_cap_tpmi[]; +extern struct pmt_cap *pmt_caps_tpmi[]; +extern struct pmt_cap pmt_cap_s3m[]; +extern struct pmt_cap *pmt_caps_s3m[]; +extern struct pmt_cap pmt_cap_tracing[]; +extern struct pmt_cap *pmt_caps_tracing[]; +extern struct pmt_cap pmt_cap_rmid_energy[]; +extern struct pmt_cap *pmt_caps_rmid_energy[]; + +static inline bool pmt_feature_id_is_valid(enum pmt_feature_id id) +{ + if (id > FEATURE_MAX) + return false; + + if (id == FEATURE_INVALID || id == FEATURE_RESERVED) + return false; + + return true; +} +#endif From 2e7ba52110ef15d29846b40eb28b400f1fb1834a Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:24 -0700 Subject: [PATCH 33/63] docs: Add ABI documentation for intel_pmt feature directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new sysfs ABI documentation file describing the layout and content of the features-/ directory used by Intel PMT (Platform Monitoring Technology). This directory exposes telemetry and control feature details for a given PMT PCI device. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-10-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../testing/sysfs-class-intel_pmt-features | 134 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 135 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-intel_pmt-features diff --git a/Documentation/ABI/testing/sysfs-class-intel_pmt-features b/Documentation/ABI/testing/sysfs-class-intel_pmt-features new file mode 100644 index 000000000000..cddb30e5bdf6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-intel_pmt-features @@ -0,0 +1,134 @@ +What: /sys/class/intel_pmt/features-/ +Date: 2025-04-24 +KernelVersion: 6.16 +Contact: david.e.box@linux.intel.com +Description: + The `features-/` directory represents the "features" + capability exposed by Intel PMT (Platform Monitoring Technology) + for the given PCI device. + + Each directory corresponds to a PMT feature and contains + attributes describing the available telemetry, monitoring, or + control functionalities. + +Directory Structure: + + /sys/class/intel_pmt/features-/ + ├── accelerator_telemetry/ # Per-accelerator telemetry data + ├── crash_log/ # Contains system crash telemetry logs + ├── per_core_environment_telemetry/ # Environmental telemetry per core + ├── per_core_performance_telemetry/ # Performance telemetry per core + ├── per_rmid_energy_telemetry/ # Energy telemetry for RMIDs + ├── per_rmid_perf_telemetry/ # Performance telemetry for RMIDs + ├── tpmi_control/ # TPMI-related controls and telemetry + ├── tracing/ # PMT tracing features + └── uncore_telemetry/ # Uncore telemetry data + +Common Files (Present in all feature directories): + + caps + - Read-only + - Lists available capabilities for this feature. + + guids + - Read-only + - Lists GUIDs associated with this feature. + +Additional Attributes (Conditional Presence): + + max_command_size + - Read-only + - Present if the feature supports out-of-band MCTP access. + - Maximum supported MCTP command size for out-of-band PMT access (bytes). + + max_stream_size + - Read-only + - Present if the feature supports out-of-band MCTP access. + - Maximum supported MCTP stream size (bytes). + + min_watcher_period_ms + - Read-only + - Present if the feature supports the watcher API. + The watcher API provides a writable control interface that allows user + configuration of monitoring behavior, such as setting the sampling or + reporting interval. + - Minimum supported time period for the watcher interface (milliseconds). + + num_rmids + - Read-only + - Present if the feature supports RMID (Resource Monitoring ID) telemetry. + RMIDs are identifiers used by hardware to track and report resource usage, + such as memory bandwidth or energy consumption, on a per-logical-entity + basis (e.g., per core, thread, or process group). + - Maximum number of RMIDs tracked simultaneously. + +Example: +For a device with PCI BDF `0000:00:03.1`, the directory tree could look like: + + /sys/class/intel_pmt/features-0000:00:03.1/ + ├── accelerator_telemetry/ + │ ├── caps + │ ├── guids + │ ├── max_command_size + │ ├── max_stream_size + │ ├── min_watcher_period_ms + ├── crash_log/ + │ ├── caps + │ ├── guids + │ ├── max_command_size + │ ├── max_stream_size + ├── per_core_environment_telemetry/ + │ ├── caps + │ ├── guids + │ ├── max_command_size + │ ├── max_stream_size + │ ├── min_watcher_period_ms + ├── per_rmid_energy_telemetry/ + │ ├── caps + │ ├── guids + │ ├── max_command_size + │ ├── max_stream_size + │ ├── min_watcher_period_ms + │ ├── num_rmids + ├── tpmi_control/ + │ ├── caps + │ ├── guids + ├── tracing/ + │ ├── caps + │ ├── guids + ├── uncore_telemetry/ + │ ├── caps + │ ├── guids + │ ├── max_command_size + │ ├── max_stream_size + │ ├── min_watcher_period_ms + +Notes: + - Some attributes are only present if the corresponding feature supports + the capability (e.g., `max_command_size` for MCTP-capable features). + - Features supporting RMIDs include `num_rmids`. + - Features supporting the watcher API include `min_watcher_period_ms`. + - The `caps` file provides additional information about the functionality + of the feature. + +Example 'caps' content for the 'tracing' feature: + + /sys/class/intel_pmt/features-0000:00:03.1/ + ├── tracing/ + │ ├── caps + + telemetry Available: No + watcher Available: Yes + crashlog Available: No + streaming Available: No + threashold Available: No + window Available: No + config Available: Yes + tracing Available: No + inband Available: Yes + oob Available: Yes + secure_chan Available: No + pmt_sp Available: Yes + pmt_sp_policy Available: Yes + mailbox Available: Yes + bios_lock Available: Yes diff --git a/MAINTAINERS b/MAINTAINERS index 4af5a8ee4057..0a5309abba30 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12387,6 +12387,7 @@ INTEL PMT DRIVERS M: David E. Box S: Supported F: Documentation/ABI/testing/sysfs-class-intel_pmt +F: Documentation/ABI/testing/sysfs-class-intel_pmt-features F: drivers/platform/x86/intel/pmt/ INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT From 934954df0f44de5e10afc1af84c06f78149f15fe Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:25 -0700 Subject: [PATCH 34/63] platform/x86/intel/tpmi: Relocate platform info to intel_vsec.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TPMI platform information provides a mapping of OOBMSM PCI devices to logical CPUs. Since this mapping is consistent across all OOBMSM features (e.g., TPMI, PMT, SDSi), it can be leveraged by multiple drivers. To facilitate reuse, relocate the struct intel_tpmi_plat_info to intel_vsec.h, renaming it to struct oobmsm_plat_info, making it accessible to other features. While modifying headers, place them in alphabetical order. Signed-off-by: David E. Box Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20250703022832.1302928-11-david.e.box@linux.intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/plr_tpmi.c | 3 ++- .../intel/speed_select_if/isst_tpmi_core.c | 9 ++++--- .../uncore-frequency/uncore-frequency-tpmi.c | 7 ++--- drivers/platform/x86/intel/vsec_tpmi.c | 4 +-- drivers/powercap/intel_rapl_tpmi.c | 9 ++++--- include/linux/intel_tpmi.h | 27 +++---------------- include/linux/intel_vsec.h | 22 +++++++++++++++ 7 files changed, 43 insertions(+), 38 deletions(-) diff --git a/drivers/platform/x86/intel/plr_tpmi.c b/drivers/platform/x86/intel/plr_tpmi.c index 2b55347a5a93..58132da47745 100644 --- a/drivers/platform/x86/intel/plr_tpmi.c +++ b/drivers/platform/x86/intel/plr_tpmi.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -256,7 +257,7 @@ DEFINE_SHOW_STORE_ATTRIBUTE(plr_status); static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) { - struct intel_tpmi_plat_info *plat_info; + struct oobmsm_plat_info *plat_info; struct dentry *dentry; int i, num_resources; struct resource *res; diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c index 18c035710eb9..34bff2f65a83 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1546,7 +1547,7 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev) { struct tpmi_per_power_domain_info *pd_info; bool read_blocked = 0, write_blocked = 0; - struct intel_tpmi_plat_info *plat_info; + struct oobmsm_plat_info *plat_info; struct device *dev = &auxdev->dev; struct tpmi_sst_struct *tpmi_sst; u8 i, num_resources, io_die_cnt; @@ -1698,7 +1699,7 @@ EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_add, "INTEL_TPMI_SST"); void tpmi_sst_dev_remove(struct auxiliary_device *auxdev) { struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); - struct intel_tpmi_plat_info *plat_info; + struct oobmsm_plat_info *plat_info; plat_info = tpmi_get_platform_data(auxdev); if (!plat_info) @@ -1720,7 +1721,7 @@ void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev) { struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); struct tpmi_per_power_domain_info *power_domain_info; - struct intel_tpmi_plat_info *plat_info; + struct oobmsm_plat_info *plat_info; void __iomem *cp_base; plat_info = tpmi_get_platform_data(auxdev); @@ -1748,7 +1749,7 @@ void tpmi_sst_dev_resume(struct auxiliary_device *auxdev) { struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev); struct tpmi_per_power_domain_info *power_domain_info; - struct intel_tpmi_plat_info *plat_info; + struct oobmsm_plat_info *plat_info; void __iomem *cp_base; plat_info = tpmi_get_platform_data(auxdev); diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c index 44d9948ed224..6df55c8e16b7 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c @@ -22,9 +22,10 @@ #include #include #include +#include +#include #include #include -#include #include "../tpmi_power_domains.h" #include "uncore-frequency-common.h" @@ -448,7 +449,7 @@ static void remove_cluster_entries(struct tpmi_uncore_struct *tpmi_uncore) } static void set_cdie_id(int domain_id, struct tpmi_uncore_cluster_info *cluster_info, - struct intel_tpmi_plat_info *plat_info) + struct oobmsm_plat_info *plat_info) { cluster_info->cdie_id = domain_id; @@ -465,7 +466,7 @@ static void set_cdie_id(int domain_id, struct tpmi_uncore_cluster_info *cluster_ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) { bool read_blocked = 0, write_blocked = 0; - struct intel_tpmi_plat_info *plat_info; + struct oobmsm_plat_info *plat_info; struct tpmi_uncore_struct *tpmi_uncore; bool uncore_sysfs_added = false; int ret, i, pkg = 0; diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index 5c383a27bbe8..d95a0d994546 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -116,7 +116,7 @@ struct intel_tpmi_info { struct intel_vsec_device *vsec_dev; int feature_count; u64 pfs_start; - struct intel_tpmi_plat_info plat_info; + struct oobmsm_plat_info plat_info; void __iomem *tpmi_control_mem; struct dentry *dbgfs_dir; }; @@ -187,7 +187,7 @@ struct tpmi_feature_state { /* Used during auxbus device creation */ static DEFINE_IDA(intel_vsec_tpmi_ida); -struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev) +struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev) { struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); diff --git a/drivers/powercap/intel_rapl_tpmi.c b/drivers/powercap/intel_rapl_tpmi.c index af2368f4db10..82201bf4685d 100644 --- a/drivers/powercap/intel_rapl_tpmi.c +++ b/drivers/powercap/intel_rapl_tpmi.c @@ -9,9 +9,10 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include -#include -#include #include +#include +#include +#include #include #include @@ -48,7 +49,7 @@ enum tpmi_rapl_register { struct tpmi_rapl_package { struct rapl_if_priv priv; - struct intel_tpmi_plat_info *tpmi_info; + struct oobmsm_plat_info *tpmi_info; struct rapl_package *rp; void __iomem *base; struct list_head node; @@ -253,7 +254,7 @@ static int intel_rapl_tpmi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) { struct tpmi_rapl_package *trp; - struct intel_tpmi_plat_info *info; + struct oobmsm_plat_info *info; struct resource *res; u32 offset; int ret; diff --git a/include/linux/intel_tpmi.h b/include/linux/intel_tpmi.h index ff480b47ae64..94c06bf214fb 100644 --- a/include/linux/intel_tpmi.h +++ b/include/linux/intel_tpmi.h @@ -8,6 +8,8 @@ #include +struct oobmsm_plat_info; + #define TPMI_VERSION_INVALID 0xff #define TPMI_MINOR_VERSION(val) FIELD_GET(GENMASK(4, 0), val) #define TPMI_MAJOR_VERSION(val) FIELD_GET(GENMASK(7, 5), val) @@ -26,30 +28,7 @@ enum intel_tpmi_id { TPMI_INFO_ID = 0x81, /* Special ID for PCI BDF and Package ID information */ }; -/** - * struct intel_tpmi_plat_info - Platform information for a TPMI device instance - * @cdie_mask: Mask of all compute dies in the partition - * @package_id: CPU Package id - * @partition: Package partition id when multiple VSEC PCI devices per package - * @segment: PCI segment ID - * @bus_number: PCI bus number - * @device_number: PCI device number - * @function_number: PCI function number - * - * Structure to store platform data for a TPMI device instance. This - * struct is used to return data via tpmi_get_platform_data(). - */ -struct intel_tpmi_plat_info { - u16 cdie_mask; - u8 package_id; - u8 partition; - u8 segment; - u8 bus_number; - u8 device_number; - u8 function_number; -}; - -struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev); +struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev); struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index); int tpmi_get_resource_count(struct auxiliary_device *auxdev); int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id, bool *read_blocked, diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index a07796d7d43b..cd78d0b2e623 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -144,6 +144,28 @@ struct intel_vsec_device { unsigned long cap_id; }; +/** + * struct oobmsm_plat_info - Platform information for a device instance + * @cdie_mask: Mask of all compute dies in the partition + * @package_id: CPU Package id + * @partition: Package partition id when multiple VSEC PCI devices per package + * @segment: PCI segment ID + * @bus_number: PCI bus number + * @device_number: PCI device number + * @function_number: PCI function number + * + * Structure to store platform data for a OOBMSM device instance. + */ +struct oobmsm_plat_info { + u16 cdie_mask; + u8 package_id; + u8 partition; + u8 segment; + u8 bus_number; + u8 device_number; + u8 function_number; +}; + int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, struct intel_vsec_device *intel_vsec_dev, const char *name); From a885a2780937afac4f31f00d11663f50d05dfb35 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:26 -0700 Subject: [PATCH 35/63] platform/x86/intel/vsec: Set OOBMSM to CPU mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add functions, intel_vsec_set/get_mapping(), to set and retrieve the OOBMSM-to-CPU mapping data in the private data of the parent Intel VSEC driver. With this mapping information available, other Intel VSEC features on the same OOBMSM device can easily access and use the mapping data, allowing each of the OOBMSM features to map to the CPUs they provides data for. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-12-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 31 +++++++++++++++++++++++++++++++ include/linux/intel_vsec.h | 12 ++++++++++++ 2 files changed, 43 insertions(+) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 4d76f1ac3c8c..711ff4edfe21 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -44,6 +44,7 @@ enum vsec_device_state { struct vsec_priv { struct intel_vsec_platform_info *info; struct device *suppliers[VSEC_FEATURE_COUNT]; + struct oobmsm_plat_info plat_info; enum vsec_device_state state[VSEC_FEATURE_COUNT]; unsigned long found_caps; }; @@ -665,6 +666,36 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id return 0; } +int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, + struct intel_vsec_device *vsec_dev) +{ + struct vsec_priv *priv; + + priv = pci_get_drvdata(vsec_dev->pcidev); + if (!priv) + return -EINVAL; + + priv->plat_info = *plat_info; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(intel_vsec_set_mapping, "INTEL_VSEC"); + +struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev) +{ + struct vsec_priv *priv; + + if (!pci_match_id(intel_vsec_pci_ids, pdev)) + return ERR_PTR(-EINVAL); + + priv = pci_get_drvdata(pdev); + if (!priv) + return ERR_PTR(-EINVAL); + + return &priv->plat_info; +} +EXPORT_SYMBOL_NS_GPL(intel_vsec_get_mapping, "INTEL_VSEC"); + /* DG1 info */ static struct intel_vsec_header dg1_header = { .length = 0x10, diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index cd78d0b2e623..4bd0c6e7857c 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -183,11 +183,23 @@ static inline struct intel_vsec_device *auxdev_to_ivdev(struct auxiliary_device #if IS_ENABLED(CONFIG_INTEL_VSEC) int intel_vsec_register(struct pci_dev *pdev, struct intel_vsec_platform_info *info); +int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, + struct intel_vsec_device *vsec_dev); +struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev); #else static inline int intel_vsec_register(struct pci_dev *pdev, struct intel_vsec_platform_info *info) { return -ENODEV; } +static inline int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, + struct intel_vsec_device *vsec_dev) +{ + return -ENODEV; +} +static inline struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev) +{ + return ERR_PTR(-ENODEV); +} #endif #endif From c9699057521834862616ce159a47bd33920f0d9f Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:27 -0700 Subject: [PATCH 36/63] platform/x86/intel/tpmi: Get OOBMSM CPU mapping from TPMI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copy TPMI’s OOBMSM platform info into a common area within VSEC private data via intel_vsec_set_mapping(). This enables other Intel VSEC features to access the CPU mapping without additional queries. Additionally, designate the TPMI driver as a supplier for the Telemetry driver, ensuring it can obtain the necessary platform information for future feature extensions. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-13-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 2 +- drivers/platform/x86/intel/vsec_tpmi.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 711ff4edfe21..f66f0ce8559b 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -725,7 +725,7 @@ static const struct intel_vsec_platform_info mtl_info = { static const struct vsec_feature_dependency oobmsm_deps[] = { { .feature = VSEC_CAP_TELEMETRY, - .supplier_bitmap = VSEC_CAP_DISCOVERY, + .supplier_bitmap = VSEC_CAP_DISCOVERY | VSEC_CAP_TPMI, }, }; diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index d95a0d994546..7748b5557a18 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -799,6 +799,10 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) ret = tpmi_process_info(tpmi_info, pfs); if (ret) return ret; + + ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev); + if (ret) + return ret; } if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) From 86fc85c75bcd9b0f28afadd60c9f890669b42ba4 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:28 -0700 Subject: [PATCH 37/63] platform/x86/intel/pmt/discovery: Get telemetry attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add intel_pmt_get_features() in PMT Discovery to enable the PMT Telemetry driver to obtain attributes of the aggregated telemetry spaces it enumerates. The function gathers feature flags and associated data (like the number of RMIDs) from each PMT entry, laying the groundwork for a future kernel interface that will allow direct access to telemetry regions based on their capabilities. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-14-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/Kconfig | 1 + drivers/platform/x86/intel/pmt/class.h | 7 +++++ drivers/platform/x86/intel/pmt/discovery.c | 33 ++++++++++++++++++++++ drivers/platform/x86/intel/pmt/telemetry.c | 5 ++++ include/linux/intel_vsec.h | 16 +++++++++++ 5 files changed, 62 insertions(+) diff --git a/drivers/platform/x86/intel/pmt/Kconfig b/drivers/platform/x86/intel/pmt/Kconfig index 0ad91b5112e9..83ae17eab462 100644 --- a/drivers/platform/x86/intel/pmt/Kconfig +++ b/drivers/platform/x86/intel/pmt/Kconfig @@ -18,6 +18,7 @@ config INTEL_PMT_CLASS config INTEL_PMT_TELEMETRY tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver" depends on INTEL_VSEC + select INTEL_PMT_DISCOVERY select INTEL_PMT_CLASS help The Intel Platform Monitory Technology (PMT) Telemetry driver provides diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index 39c32357ee2c..fdf7e79d8c0d 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -48,6 +48,7 @@ struct intel_pmt_entry { struct pmt_callbacks *cb; unsigned long base_addr; size_t size; + u64 feature_flags; u32 guid; u32 num_rmids; /* Number of Resource Monitoring IDs */ int devid; @@ -71,4 +72,10 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry, struct intel_vsec_device *dev, int idx); void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, struct intel_pmt_namespace *ns); +#if IS_ENABLED(CONFIG_INTEL_PMT_DISCOVERY) +void intel_pmt_get_features(struct intel_pmt_entry *entry); +#else +static inline void intel_pmt_get_features(struct intel_pmt_entry *entry) {} +#endif + #endif diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index 4b4fa3137ad2..e72d43b675b4 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -583,6 +583,39 @@ static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxi return ret; } +static void pmt_get_features(struct intel_pmt_entry *entry, struct feature *f) +{ + int num_guids = f->table.header.num_guids; + int i; + + for (i = 0; i < num_guids; i++) { + if (f->table.guids[i] != entry->guid) + continue; + + entry->feature_flags |= BIT(f->id); + + if (feature_layout[f->id] == LAYOUT_RMID) + entry->num_rmids = f->table.rmid.num_rmids; + else + entry->num_rmids = 0; /* entry is kzalloc but set anyway */ + } +} + +void intel_pmt_get_features(struct intel_pmt_entry *entry) +{ + struct feature *feature; + + mutex_lock(&feature_list_lock); + list_for_each_entry(feature, &pmt_feature_list, list) { + if (feature->priv->parent != &entry->ep->pcidev->dev) + continue; + + pmt_get_features(entry, feature); + } + mutex_unlock(&feature_list_lock); +} +EXPORT_SYMBOL_NS_GPL(intel_pmt_get_features, "INTEL_PMT"); + static const struct auxiliary_device_id pmt_features_id_table[] = { { .name = "intel_vsec.discovery" }, {} diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c index ac3a9bdf5601..58d06749e417 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.c +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -9,11 +9,14 @@ */ #include +#include #include #include +#include #include #include #include +#include #include #include @@ -311,6 +314,8 @@ static int pmt_telem_probe(struct auxiliary_device *auxdev, const struct auxilia continue; priv->num_entries++; + + intel_pmt_get_features(entry); } return 0; diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index 4bd0c6e7857c..f185e9c01c90 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -4,6 +4,7 @@ #include #include +#include /* * VSEC_CAP_UNUSED is reserved. It exists to prevent zero initialized @@ -166,6 +167,21 @@ struct oobmsm_plat_info { u8 function_number; }; +struct telemetry_region { + struct oobmsm_plat_info plat_info; + void __iomem *addr; + size_t size; + u32 guid; + u32 num_rmids; +}; + +struct pmt_feature_group { + enum pmt_feature_id id; + int count; + struct kref kref; + struct telemetry_region regions[]; +}; + int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent, struct intel_vsec_device *intel_vsec_dev, const char *name); From 42dabe5442887946b16e64c6ebe91d2671a96fbb Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:29 -0700 Subject: [PATCH 38/63] platform/x86/intel/pmt/telemetry: Add API to retrieve telemetry regions by feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a new API, intel_pmt_get_regions_by_feature(), that gathers telemetry regions based on a provided capability flag. This API enables retrieval of regions with various capabilities (for example, RMID-based telemetry) and provides a unified interface for accessing them. Resource management is handled via reference counting using intel_pmt_put_feature_group(). Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-15-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/telemetry.c | 89 +++++++++++++++++++++- include/linux/intel_vsec.h | 18 +++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c index 58d06749e417..a4dfca6cac19 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.c +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -9,16 +9,21 @@ */ #include +#include +#include +#include #include #include #include #include #include +#include +#include #include #include #include #include -#include +#include #include "class.h" @@ -209,6 +214,87 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info) } EXPORT_SYMBOL_NS_GPL(pmt_telem_get_endpoint_info, "INTEL_PMT_TELEMETRY"); +static int pmt_copy_region(struct telemetry_region *region, + struct intel_pmt_entry *entry) +{ + + struct oobmsm_plat_info *plat_info; + + plat_info = intel_vsec_get_mapping(entry->ep->pcidev); + if (IS_ERR(plat_info)) + return PTR_ERR(plat_info); + + region->plat_info = *plat_info; + region->guid = entry->guid; + region->addr = entry->ep->base; + region->size = entry->size; + region->num_rmids = entry->num_rmids; + + return 0; +} + +static void pmt_feature_group_release(struct kref *kref) +{ + struct pmt_feature_group *feature_group; + + feature_group = container_of(kref, struct pmt_feature_group, kref); + kfree(feature_group); +} + +struct pmt_feature_group *intel_pmt_get_regions_by_feature(enum pmt_feature_id id) +{ + struct pmt_feature_group *feature_group __free(kfree) = NULL; + struct telemetry_region *region; + struct intel_pmt_entry *entry; + unsigned long idx; + int count = 0; + size_t size; + + if (!pmt_feature_id_is_valid(id)) + return ERR_PTR(-EINVAL); + + guard(mutex)(&ep_lock); + xa_for_each(&telem_array, idx, entry) { + if (entry->feature_flags & BIT(id)) + count++; + } + + if (!count) + return ERR_PTR(-ENOENT); + + size = struct_size(feature_group, regions, count); + feature_group = kzalloc(size, GFP_KERNEL); + if (!feature_group) + return ERR_PTR(-ENOMEM); + + feature_group->count = count; + + region = feature_group->regions; + xa_for_each(&telem_array, idx, entry) { + int ret; + + if (!(entry->feature_flags & BIT(id))) + continue; + + ret = pmt_copy_region(region, entry); + if (ret) + return ERR_PTR(ret); + + region++; + } + + kref_init(&feature_group->kref); + + return no_free_ptr(feature_group); +} +EXPORT_SYMBOL(intel_pmt_get_regions_by_feature); + +void intel_pmt_put_feature_group(struct pmt_feature_group *feature_group) +{ + kref_put(&feature_group->kref, pmt_feature_group_release); +} +EXPORT_SYMBOL(intel_pmt_put_feature_group); + int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count) { u32 offset, size; @@ -353,3 +439,4 @@ MODULE_AUTHOR("David E. Box "); MODULE_DESCRIPTION("Intel PMT Telemetry driver"); MODULE_LICENSE("GPL v2"); MODULE_IMPORT_NS("INTEL_PMT"); +MODULE_IMPORT_NS("INTEL_VSEC"); diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index f185e9c01c90..53f6fe88e369 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -4,6 +4,7 @@ #include #include +#include #include /* @@ -218,4 +219,21 @@ static inline struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pd return ERR_PTR(-ENODEV); } #endif + +#if IS_ENABLED(CONFIG_INTEL_PMT_TELEMETRY) +struct pmt_feature_group * +intel_pmt_get_regions_by_feature(enum pmt_feature_id id); + +void intel_pmt_put_feature_group(struct pmt_feature_group *feature_group); +#else +static inline struct pmt_feature_group * +intel_pmt_get_regions_by_feature(enum pmt_feature_id id) +{ + return ERR_PTR(-ENODEV); +} + +static inline void +intel_pmt_put_feature_group(struct pmt_feature_group *feature_group) {} +#endif + #endif From b9707d46a95962bb4e28ae1929015e419ad6aff7 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Wed, 2 Jul 2025 19:28:30 -0700 Subject: [PATCH 39/63] platform/x86/intel/pmt: KUNIT test for PMT Enhanced Discovery API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a KUNIT test for the intel_pmt_get_regions_by_feature() API. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20250703022832.1302928-16-david.e.box@linux.intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/Kconfig | 14 +++ drivers/platform/x86/intel/pmt/Makefile | 2 + .../platform/x86/intel/pmt/discovery-kunit.c | 116 ++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 drivers/platform/x86/intel/pmt/discovery-kunit.c diff --git a/drivers/platform/x86/intel/pmt/Kconfig b/drivers/platform/x86/intel/pmt/Kconfig index 83ae17eab462..785c206e1beb 100644 --- a/drivers/platform/x86/intel/pmt/Kconfig +++ b/drivers/platform/x86/intel/pmt/Kconfig @@ -51,3 +51,17 @@ config INTEL_PMT_DISCOVERY To compile this driver as a module, choose M here: the module will be called pmt_discovery. + +config INTEL_PMT_KUNIT_TEST + tristate "KUnit tests for Intel PMT driver" + depends on INTEL_PMT_DISCOVERY + depends on KUNIT + help + Enable this option to compile and run a suite of KUnit tests for the Intel + Platform Monitoring Technology (PMT) driver. These tests are designed to + validate the driver's functionality, error handling, and overall stability, + helping developers catch regressions and ensure code quality during changes. + + This option is intended for development and testing environments. It is + recommended to disable it in production builds. To compile this driver as a + module, choose M here: the module will be called pmt-discovery-kunit. diff --git a/drivers/platform/x86/intel/pmt/Makefile b/drivers/platform/x86/intel/pmt/Makefile index 8aed7e1592e4..47f692c091c9 100644 --- a/drivers/platform/x86/intel/pmt/Makefile +++ b/drivers/platform/x86/intel/pmt/Makefile @@ -12,3 +12,5 @@ obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_crashlog.o pmt_crashlog-y := crashlog.o obj-$(CONFIG_INTEL_PMT_DISCOVERY) += pmt_discovery.o pmt_discovery-y := discovery.o features.o +obj-$(CONFIG_INTEL_PMT_KUNIT_TEST) += pmt-discovery-kunit.o +pmt-discovery-kunit-y := discovery-kunit.o diff --git a/drivers/platform/x86/intel/pmt/discovery-kunit.c b/drivers/platform/x86/intel/pmt/discovery-kunit.c new file mode 100644 index 000000000000..b4493fb96738 --- /dev/null +++ b/drivers/platform/x86/intel/pmt/discovery-kunit.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitory Technology Discovery KUNIT tests + * + * Copyright (c) 2025, Intel Corporation. + * All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include + +#define PMT_FEATURE_COUNT (FEATURE_MAX + 1) + +static void +validate_pmt_regions(struct kunit *test, struct pmt_feature_group *feature_group, int feature_id) +{ + int i; + + kunit_info(test, "Feature ID %d [%s] has %d regions.\n", feature_id, + pmt_feature_names[feature_id], feature_group->count); + + for (i = 0; i < feature_group->count; i++) { + struct telemetry_region *region = &feature_group->regions[i]; + + kunit_info(test, " - Region %d: cdie_mask=%u, package_id=%u, partition=%u, segment=%u,", + i, region->plat_info.cdie_mask, region->plat_info.package_id, + region->plat_info.partition, region->plat_info.segment); + kunit_info(test, "\t\tbus=%u, device=%u, function=%u, guid=0x%x,", + region->plat_info.bus_number, region->plat_info.device_number, + region->plat_info.function_number, region->guid); + kunit_info(test, "\t\taddr=%p, size=%lu, num_rmids=%u", region->addr, region->size, + region->num_rmids); + + + KUNIT_ASSERT_GE(test, region->plat_info.cdie_mask, 0); + KUNIT_ASSERT_GE(test, region->plat_info.package_id, 0); + KUNIT_ASSERT_GE(test, region->plat_info.partition, 0); + KUNIT_ASSERT_GE(test, region->plat_info.segment, 0); + KUNIT_ASSERT_GE(test, region->plat_info.bus_number, 0); + KUNIT_ASSERT_GE(test, region->plat_info.device_number, 0); + KUNIT_ASSERT_GE(test, region->plat_info.function_number, 0); + + KUNIT_ASSERT_NE(test, region->guid, 0); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, (__force const void *)region->addr); + } +} + +static void linebreak(struct kunit *test) +{ + kunit_info(test, "*****************************************************************************\n"); +} + +static void test_intel_pmt_get_regions_by_feature(struct kunit *test) +{ + struct pmt_feature_group *feature_group; + int num_available = 0; + int feature_id; + + /* Iterate through all possible feature IDs */ + for (feature_id = 1; feature_id < PMT_FEATURE_COUNT; feature_id++, linebreak(test)) { + const char *name; + + if (!pmt_feature_id_is_valid(feature_id)) + continue; + + name = pmt_feature_names[feature_id]; + + feature_group = intel_pmt_get_regions_by_feature(feature_id); + if (IS_ERR(feature_group)) { + if (PTR_ERR(feature_group) == -ENOENT) + kunit_warn(test, "intel_pmt_get_regions_by_feature() reporting feature %d [%s] is not present.\n", + feature_id, name); + else + kunit_warn(test, "intel_pmt_get_regions_by_feature() returned error %ld while attempt to lookup %d [%s].\n", + PTR_ERR(feature_group), feature_id, name); + + continue; + } + + if (!feature_group) { + kunit_warn(test, "Feature ID %d: %s is not available.\n", feature_id, name); + continue; + } + + num_available++; + + validate_pmt_regions(test, feature_group, feature_id); + + intel_pmt_put_feature_group(feature_group); + } + + if (num_available == 0) + kunit_warn(test, "No PMT region groups were available for any feature ID (0-10).\n"); +} + +static struct kunit_case intel_pmt_discovery_test_cases[] = { + KUNIT_CASE(test_intel_pmt_get_regions_by_feature), + {} +}; + +static struct kunit_suite intel_pmt_discovery_test_suite = { + .name = "pmt_discovery_test", + .test_cases = intel_pmt_discovery_test_cases, +}; + +kunit_test_suite(intel_pmt_discovery_test_suite); + +MODULE_IMPORT_NS("INTEL_PMT_DISCOVERY"); +MODULE_AUTHOR("David E. Box "); +MODULE_DESCRIPTION("Intel PMT Discovery KUNIT test driver"); +MODULE_LICENSE("GPL"); From 56036d6af41a473a8129fc960a5ab3673eda13d5 Mon Sep 17 00:00:00 2001 From: Stuart Hayes Date: Mon, 9 Jun 2025 13:46:57 -0500 Subject: [PATCH 40/63] platform/x86: dell_rbu: Remove unused struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop using an entire struct packet_data just for its embedded list_head. Signed-off-by: Stuart Hayes Link: https://lore.kernel.org/r/20250609184659.7210-4-stuart.w.hayes@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell_rbu.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c index 9dd9f2cb074f..45c0a72e494a 100644 --- a/drivers/platform/x86/dell/dell_rbu.c +++ b/drivers/platform/x86/dell/dell_rbu.c @@ -77,14 +77,14 @@ struct packet_data { int ordernum; }; -static struct packet_data packet_data_head; +static struct list_head packet_data_list; static struct platform_device *rbu_device; static int context; static void init_packet_head(void) { - INIT_LIST_HEAD(&packet_data_head.list); + INIT_LIST_HEAD(&packet_data_list); rbu_data.packet_read_count = 0; rbu_data.num_packets = 0; rbu_data.packetsize = 0; @@ -183,7 +183,7 @@ static int create_packet(void *data, size_t length) __must_hold(&rbu_data.lock) /* initialize the newly created packet headers */ INIT_LIST_HEAD(&newpacket->list); - list_add_tail(&newpacket->list, &packet_data_head.list); + list_add_tail(&newpacket->list, &packet_data_list); memcpy(newpacket->data, data, length); @@ -292,7 +292,7 @@ static int packet_read_list(char *data, size_t * pread_length) remaining_bytes = *pread_length; bytes_read = rbu_data.packet_read_count; - list_for_each_entry(newpacket, &packet_data_head.list, list) { + list_for_each_entry(newpacket, &packet_data_list, list) { bytes_copied = do_packet_read(pdest, newpacket, remaining_bytes, bytes_read, &temp_count); remaining_bytes -= bytes_copied; @@ -315,7 +315,7 @@ static void packet_empty_list(void) { struct packet_data *newpacket, *tmp; - list_for_each_entry_safe(newpacket, tmp, &packet_data_head.list, list) { + list_for_each_entry_safe(newpacket, tmp, &packet_data_list, list) { list_del(&newpacket->list); /* From 428f6f3a56ac85f37a07a3fe5149b593185d5c4c Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 8 Jul 2025 16:41:01 -0700 Subject: [PATCH 41/63] platform/x86/intel/pmt/discovery: Fix size_t specifiers for 32-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When building i386 allmodconfig, there are two warnings in the newly added discovery code: drivers/platform/x86/intel/pmt/discovery.c: In function 'pmt_feature_get_feature_table': drivers/platform/x86/intel/pmt/discovery.c:427:35: error: format '%ld' expects argument of type 'long int', but argument 2 has type 'size_t' {aka 'unsigned int'} [-Werror=format=] 427 | if (WARN(size > res_size, "Bad table size %ld > %pa", size, &res_size)) | ^~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~ | | | size_t {aka unsigned int} ... drivers/platform/x86/intel/pmt/discovery.c:427:53: note: format string is defined here 427 | if (WARN(size > res_size, "Bad table size %ld > %pa", size, &res_size)) | ~~^ | | | long int | %d drivers/platform/x86/intel/pmt/discovery-kunit.c: In function 'validate_pmt_regions': include/linux/kern_levels.h:5:25: error: format '%lu' expects argument of type 'long unsigned int', but argument 4 has type 'size_t' {aka 'unsigned int'} [-Werror=format=] ... drivers/platform/x86/intel/pmt/discovery-kunit.c:35:17: note: in expansion of macro 'kunit_info' 35 | kunit_info(test, "\t\taddr=%p, size=%lu, num_rmids=%u", region->addr, region->size, | ^~~~~~~~~~ size_t is 'unsigned long' for 64-bit platforms but 'unsigned int' for 32-bit platforms, so '%ld' is not correct. Use the proper size_t specifier, '%zu', to resolve the warnings on 32-bit platforms while not affecting 64-bit platforms. Reported-by: Randy Dunlap Reported-by: kernelci.org bot Fixes: d9a078809356 ("platform/x86/intel/pmt: Add PMT Discovery driver") Fixes: b9707d46a959 ("platform/x86/intel/pmt: KUNIT test for PMT Enhanced Discovery API") Closes: https://lore.kernel.org/all/CACo-S-29Degjym-azsJNSd1yofLOB2_Rf5xpa9b7L-14OPn7wQ@mail.gmail.com/ Signed-off-by: Nathan Chancellor Link: https://lore.kernel.org/r/20250708-discovery-pmt-fix-32-bit-formats-v1-1-296a5fc9c3d4@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/discovery-kunit.c | 2 +- drivers/platform/x86/intel/pmt/discovery.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/discovery-kunit.c b/drivers/platform/x86/intel/pmt/discovery-kunit.c index b4493fb96738..f44eb41d58f6 100644 --- a/drivers/platform/x86/intel/pmt/discovery-kunit.c +++ b/drivers/platform/x86/intel/pmt/discovery-kunit.c @@ -32,7 +32,7 @@ validate_pmt_regions(struct kunit *test, struct pmt_feature_group *feature_group kunit_info(test, "\t\tbus=%u, device=%u, function=%u, guid=0x%x,", region->plat_info.bus_number, region->plat_info.device_number, region->plat_info.function_number, region->guid); - kunit_info(test, "\t\taddr=%p, size=%lu, num_rmids=%u", region->addr, region->size, + kunit_info(test, "\t\taddr=%p, size=%zu, num_rmids=%u", region->addr, region->size, region->num_rmids); diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index e72d43b675b4..1a680a042a98 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -424,7 +424,7 @@ pmt_feature_get_feature_table(struct pmt_features_priv *priv, size = sizeof(*header) + FEAT_ATTR_SIZE(header->attr_size) + PMT_GUID_SIZE(header->num_guids); res_size = resource_size(&res); - if (WARN(size > res_size, "Bad table size %ld > %pa", size, &res_size)) + if (WARN(size > res_size, "Bad table size %zu > %pa", size, &res_size)) return -EINVAL; /* Get the feature attributes, including capability fields */ From bde430fb669d03a0025fd90485dc26acfafd9b4f Mon Sep 17 00:00:00 2001 From: Suma Hegde Date: Wed, 9 Jul 2025 10:54:13 +0000 Subject: [PATCH 42/63] platform/x86/amd/hsmp: Enhance the print messages to prevent confusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the HSMP ACPI device is present, loading the amd_hsmp.ko module incorrectly displays the message "HSMP is not supported on Family:%x model:%x\n" despite being supported by the hsmp_acpi.ko module, leading to confusion. To address this issue, relocate the acpi_dev_present() check to the beginning of the hsmp_plt_init() and revise the print message to better reflect the current support status. Additionally, add more error messages in the error paths and debug messages to indicate successful probing for both hsmp_acpi.ko and amd_hsmp.ko modules. Reviewed-by: Naveen Krishna Chatradhi Signed-off-by: Suma Hegde Link: https://lore.kernel.org/r/20250709105413.2487851-1-suma.hegde@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 9 +++++++-- drivers/platform/x86/amd/hsmp/plat.c | 28 ++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 2f1faa82d13e..d974c2289f5a 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -587,8 +587,10 @@ static int hsmp_acpi_probe(struct platform_device *pdev) if (!hsmp_pdev->is_probed) { hsmp_pdev->num_sockets = amd_num_nodes(); - if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) + if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) { + dev_err(&pdev->dev, "Wrong number of sockets\n"); return -ENODEV; + } hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, sizeof(*hsmp_pdev->sock), @@ -605,9 +607,12 @@ static int hsmp_acpi_probe(struct platform_device *pdev) if (!hsmp_pdev->is_probed) { ret = hsmp_misc_register(&pdev->dev); - if (ret) + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device\n"); return ret; + } hsmp_pdev->is_probed = true; + dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); } return 0; diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index e3874c47ed9e..f8aa844d33e4 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -215,7 +217,14 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) return ret; } - return hsmp_misc_register(&pdev->dev); + ret = hsmp_misc_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device\n"); + return ret; + } + + dev_dbg(&pdev->dev, "AMD HSMP is probed successfully\n"); + return 0; } static void hsmp_pltdrv_remove(struct platform_device *pdev) @@ -287,15 +296,20 @@ static int __init hsmp_plt_init(void) { int ret = -ENODEV; + if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1)) { + if (IS_ENABLED(CONFIG_AMD_HSMP_ACPI)) + pr_debug("HSMP is supported through ACPI on this platform, please use hsmp_acpi.ko\n"); + else + pr_info("HSMP is supported through ACPI on this platform, please enable AMD_HSMP_ACPI config\n"); + return -ENODEV; + } + if (!legacy_hsmp_support()) { - pr_info("HSMP is not supported on Family:%x model:%x\n", + pr_info("HSMP interface is either disabled or not supported on family:%x model:%x\n", boot_cpu_data.x86, boot_cpu_data.x86_model); return ret; } - if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1)) - return -ENODEV; - hsmp_pdev = get_hsmp_pdev(); if (!hsmp_pdev) return -ENOMEM; @@ -305,8 +319,10 @@ static int __init hsmp_plt_init(void) * if we have N SMN/DF interfaces that ideally means N sockets */ hsmp_pdev->num_sockets = amd_num_nodes(); - if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) + if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) { + pr_err("Wrong number of sockets\n"); return ret; + } ret = platform_driver_register(&amd_hsmp_driver); if (ret) From 6382c27389c266e90b31151a79d81fa6e122d2d6 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 11 Jul 2025 09:27:09 +0200 Subject: [PATCH 43/63] platform/x86/intel/pmt/discovery: fix format string warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When -Wformat-security is enabled, this new code triggers it: drivers/platform/x86/intel/pmt/discovery.c: In function 'pmt_features_discovery': drivers/platform/x86/intel/pmt/discovery.c:505:36: error: format not a string literal and no format arguments [-Werror=format-security] 505 | pmt_feature_names[feature->id]); Fixes: d9a078809356 ("platform/x86/intel/pmt: Add PMT Discovery driver") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250711072718.2748415-1-arnd@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/discovery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index 1a680a042a98..32713a194a55 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -502,7 +502,7 @@ static int pmt_features_discovery(struct pmt_features_priv *priv, } ret = kobject_init_and_add(&feature->kobj, ktype, &priv->dev->kobj, - pmt_feature_names[feature->id]); + "%s", pmt_feature_names[feature->id]); if (ret) return ret; From 6e38b9fcbfa3053e1b5d2806a7233078d712bd34 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 9 Jul 2025 17:17:28 +0200 Subject: [PATCH 44/63] platform/x86: lenovo: gamezone needs "other mode" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registering the "other mode" notifier fails if that is disabled: x86_64-linux-ld: drivers/platform/x86/lenovo/wmi-gamezone.o: in function `lwmi_gz_probe': wmi-gamezone.c:(.text+0x336): undefined reference to `devm_lwmi_om_register_notifier' This could be fixed by adding a stub helper, but a Kconfig 'select' seems simpler here. Fixes: 22024ac5366f ("platform/x86: Add Lenovo Gamezone WMI Driver") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250709151734.1268435-1-arnd@kernel.org [ij: retained the other selects as wmi-gamezone is using them directly.] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lenovo/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index b76157b35296..d22b774e0236 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -252,6 +252,7 @@ config LENOVO_WMI_GAMEZONE select ACPI_PLATFORM_PROFILE select LENOVO_WMI_EVENTS select LENOVO_WMI_HELPERS + select LENOVO_WMI_TUNING help Say Y here if you have a WMI aware Lenovo Legion device and would like to use the platform-profile firmware interface to manage power usage. From 5a9fffd8a533bfb2688ec69dd6d1b6e53ef1177a Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 14 Jul 2025 10:15:43 +0200 Subject: [PATCH 45/63] platform/x86/intel/pmt: fix build dependency for kunit test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When INTEL_PMT_TELEMETRY is in a loadable module, the discovery test cannot be built-in: x86_64-linux-ld: drivers/platform/x86/intel/pmt/discovery-kunit.o: in function `test_intel_pmt_get_regions_by_feature': discovery-kunit.c:(.text+0x29d): undefined reference to `intel_pmt_get_regions_by_feature' x86_64-linux-ld: discovery-kunit.c:(.text+0x2c3): undefined reference to `intel_pmt_put_feature_group' Add a Kconfig dependency to prevent this. Fixes: b9707d46a959 ("platform/x86/intel/pmt: KUNIT test for PMT Enhanced Discovery API") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20250714081559.4056777-1-arnd@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/intel/pmt/Kconfig b/drivers/platform/x86/intel/pmt/Kconfig index 785c206e1beb..7363446b7773 100644 --- a/drivers/platform/x86/intel/pmt/Kconfig +++ b/drivers/platform/x86/intel/pmt/Kconfig @@ -55,6 +55,7 @@ config INTEL_PMT_DISCOVERY config INTEL_PMT_KUNIT_TEST tristate "KUnit tests for Intel PMT driver" depends on INTEL_PMT_DISCOVERY + depends on INTEL_PMT_TELEMETRY || !INTEL_PMT_TELEMETRY depends on KUNIT help Enable this option to compile and run a suite of KUnit tests for the Intel From cf685b3826e62a5e8bdc61135c0a60d128673e1b Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 15 Jul 2025 14:24:38 +0200 Subject: [PATCH 46/63] platform/x86: dell-uart-backlight: Use blacklight power constant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backlight subsystem has gotten its own power constants. Replace FB_BLANK_UNBLANK with BACKLIGHT_POWER_ON. Signed-off-by: Thomas Zimmermann Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20250715122643.137027-2-tzimmermann@suse.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-uart-backlight.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c index 8f868f845350..f323a667dc2d 100644 --- a/drivers/platform/x86/dell/dell-uart-backlight.c +++ b/drivers/platform/x86/dell/dell-uart-backlight.c @@ -305,7 +305,7 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev) dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA); /* Initialize bl_power to a known value */ - ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK); + ret = dell_uart_set_bl_power(dell_bl, BACKLIGHT_POWER_ON); if (ret) return ret; From 4ff3aeb664f7dfe824ba91ffb0b203397a8d431e Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 18 Jul 2025 12:23:05 -0500 Subject: [PATCH 47/63] platform/x86/amd: pmc: Add Lenovo Yoga 6 13ALC6 to pmc quirk list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo Yoga 6 13ACL6 82ND has a similar BIOS problem as other Lenovo laptops from that vintage that causes a rather long resume from suspend. Add it to the quirk list that manipulates the scratch register to avoid the issue. Reported-by: Adam Berglund Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4434 Tested-by: Adam Berglund Signed-off-by: Mario Limonciello Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20250718172307.1928744-1-superm1@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmc/pmc-quirks.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index 131f10b68308..ded4c84f5ed1 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -190,6 +190,15 @@ static const struct dmi_system_id fwbug_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"), } }, + /* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */ + { + .ident = "Lenovo Yoga 6 13ALC6", + .driver_data = &quirk_s2idle_bug, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "82ND"), + } + }, /* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */ { .ident = "HP Laptop 15s-eq2xxx", From de2884c6cdd3d133704ce37393590dd1c761500c Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Wed, 2 Jul 2025 20:28:43 +0200 Subject: [PATCH 48/63] platform/x86: samsung-laptop: Expose charge_types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support the newly introduced charge_types sysfs attribute as a replacement for the custom `battery_life_extender` attribute. Setting charge_types to `Long Life` enables battery life extending mode. This change is similar to the recent Ideapad patch adding support for charge_types. Signed-off-by: Jelle van der Waa Link: https://lore.kernel.org/r/20250702182844.107706-1-jvanderwaa@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../ABI/obsolete/sysfs-driver-samsung-laptop | 10 ++ .../ABI/testing/sysfs-driver-samsung-laptop | 11 -- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/samsung-laptop.c | 110 ++++++++++++++++++ 4 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 Documentation/ABI/obsolete/sysfs-driver-samsung-laptop diff --git a/Documentation/ABI/obsolete/sysfs-driver-samsung-laptop b/Documentation/ABI/obsolete/sysfs-driver-samsung-laptop new file mode 100644 index 000000000000..204c3f3a1d78 --- /dev/null +++ b/Documentation/ABI/obsolete/sysfs-driver-samsung-laptop @@ -0,0 +1,10 @@ +What: /sys/devices/platform/samsung/battery_life_extender +Date: December 1, 2011 +KernelVersion: 3.3 +Contact: Corentin Chary +Description: Max battery charge level can be modified, battery cycle + life can be extended by reducing the max battery charge + level. + + - 0 means normal battery mode (100% charge) + - 1 means battery life extender mode (80% charge) diff --git a/Documentation/ABI/testing/sysfs-driver-samsung-laptop b/Documentation/ABI/testing/sysfs-driver-samsung-laptop index 28c9c040de5d..408cb0ddf4aa 100644 --- a/Documentation/ABI/testing/sysfs-driver-samsung-laptop +++ b/Documentation/ABI/testing/sysfs-driver-samsung-laptop @@ -20,17 +20,6 @@ Description: Some Samsung laptops have different "performance levels" and it's still unknown if this value even changes anything, other than making the user feel a bit better. -What: /sys/devices/platform/samsung/battery_life_extender -Date: December 1, 2011 -KernelVersion: 3.3 -Contact: Corentin Chary -Description: Max battery charge level can be modified, battery cycle - life can be extended by reducing the max battery charge - level. - - - 0 means normal battery mode (100% charge) - - 1 means battery life extender mode (80% charge) - What: /sys/devices/platform/samsung/usb_charge Date: December 1, 2011 KernelVersion: 3.3 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 49ca98df47fd..6d238e120dce 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -619,6 +619,7 @@ config SAMSUNG_LAPTOP tristate "Samsung Laptop driver" depends on RFKILL || RFKILL = n depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on ACPI_BATTERY depends on BACKLIGHT_CLASS_DEVICE select LEDS_CLASS select NEW_LEDS diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index decde4c9a3d9..9d43a12db73c 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include /* @@ -348,6 +350,8 @@ struct samsung_laptop { struct notifier_block pm_nb; + struct acpi_battery_hook battery_hook; + bool handle_backlight; bool has_stepping_quirk; @@ -697,6 +701,11 @@ static ssize_t set_performance_level(struct device *dev, static DEVICE_ATTR(performance_level, 0644, get_performance_level, set_performance_level); +static void show_battery_life_extender_deprecation_warning(struct device *dev) +{ + dev_warn_once(dev, "battery_life_extender attribute has been deprecated, see charge_types.\n"); +} + static int read_battery_life_extender(struct samsung_laptop *samsung) { const struct sabi_commands *commands = &samsung->config->commands; @@ -739,6 +748,8 @@ static ssize_t get_battery_life_extender(struct device *dev, struct samsung_laptop *samsung = dev_get_drvdata(dev); int ret; + show_battery_life_extender_deprecation_warning(dev); + ret = read_battery_life_extender(samsung); if (ret < 0) return ret; @@ -753,6 +764,8 @@ static ssize_t set_battery_life_extender(struct device *dev, struct samsung_laptop *samsung = dev_get_drvdata(dev); int ret, value; + show_battery_life_extender_deprecation_warning(dev); + if (!count || kstrtoint(buf, 0, &value) != 0) return -EINVAL; @@ -766,6 +779,84 @@ static ssize_t set_battery_life_extender(struct device *dev, static DEVICE_ATTR(battery_life_extender, 0644, get_battery_life_extender, set_battery_life_extender); +static int samsung_psy_ext_set_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct samsung_laptop *samsung = ext_data; + + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE: + return write_battery_life_extender(samsung, 1); + case POWER_SUPPLY_CHARGE_TYPE_STANDARD: + return write_battery_life_extender(samsung, 0); + default: + return -EINVAL; + } +} + +static int samsung_psy_ext_get_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct samsung_laptop *samsung = ext_data; + int ret; + + ret = read_battery_life_extender(samsung); + if (ret < 0) + return ret; + + if (ret == 1) + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + + return 0; +} + +static int samsung_psy_prop_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp) +{ + return true; +} + +static const enum power_supply_property samsung_power_supply_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPES, +}; + +static const struct power_supply_ext samsung_battery_ext = { + .name = "samsung_laptop", + .properties = samsung_power_supply_props, + .num_properties = ARRAY_SIZE(samsung_power_supply_props), + .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)), + .get_property = samsung_psy_ext_get_prop, + .set_property = samsung_psy_ext_set_prop, + .property_is_writeable = samsung_psy_prop_is_writeable, +}; + +static int samsung_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct samsung_laptop *samsung = container_of(hook, struct samsung_laptop, battery_hook); + + return power_supply_register_extension(battery, &samsung_battery_ext, + &samsung->platform_device->dev, samsung); +} + +static int samsung_battery_remove(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &samsung_battery_ext); + + return 0; +} + static int read_usb_charge(struct samsung_laptop *samsung) { const struct sabi_commands *commands = &samsung->config->commands; @@ -1043,6 +1134,21 @@ static int __init samsung_lid_handling_init(struct samsung_laptop *samsung) return retval; } +static int __init samsung_battery_hook_init(struct samsung_laptop *samsung) +{ + int retval = 0; + + if (samsung->config->commands.get_battery_life_extender != 0xFFFF) { + samsung->battery_hook.add_battery = samsung_battery_add; + samsung->battery_hook.remove_battery = samsung_battery_remove; + samsung->battery_hook.name = "Samsung Battery Extension"; + retval = devm_battery_hook_register(&samsung->platform_device->dev, + &samsung->battery_hook); + } + + return retval; +} + static int kbd_backlight_enable(struct samsung_laptop *samsung) { const struct sabi_commands *commands = &samsung->config->commands; @@ -1604,6 +1710,10 @@ static int __init samsung_init(void) if (ret) goto error_lid_handling; + ret = samsung_battery_hook_init(samsung); + if (ret) + goto error_lid_handling; + samsung_debugfs_init(samsung); samsung->pm_nb.notifier_call = samsung_pm_notification; From 54d5cd4719c5e87f33d271c9ac2e393147d934f8 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:31 -0400 Subject: [PATCH 49/63] platform/x86/intel/pmt: fix a crashlog NULL pointer access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usage of the intel_pmt_read() for binary sysfs, requires a pcidev. The current use of the endpoint value is only valid for telemetry endpoint usage. Without the ep, the crashlog usage causes the following NULL pointer exception: BUG: kernel NULL pointer dereference, address: 0000000000000000 Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:intel_pmt_read+0x3b/0x70 [pmt_class] Code: Call Trace: ? sysfs_kf_bin_read+0xc0/0xe0 kernfs_fop_read_iter+0xac/0x1a0 vfs_read+0x26d/0x350 ksys_read+0x6b/0xe0 __x64_sys_read+0x1d/0x30 x64_sys_call+0x1bc8/0x1d70 do_syscall_64+0x6d/0x110 Augment struct intel_pmt_entry with a pointer to the pcidev to avoid the NULL pointer exception. Fixes: 045a513040cc ("platform/x86/intel/pmt: Use PMT callbacks") Cc: stable@vger.kernel.org Reviewed-by: David E. Box Reviewed-by: Tejas Upadhyay Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-2-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/class.c | 3 ++- drivers/platform/x86/intel/pmt/class.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index a806a81ece52..5dd80109f591 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -99,7 +99,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj, if (count > entry->size - off) count = entry->size - off; - count = pmt_telem_read_mmio(entry->ep->pcidev, entry->cb, entry->header.guid, buf, + count = pmt_telem_read_mmio(entry->pcidev, entry->cb, entry->header.guid, buf, entry->base, off, count); return count; @@ -283,6 +283,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, return -EINVAL; } + entry->pcidev = pci_dev; entry->guid = header->guid; entry->size = header->size; entry->cb = ivdev->priv_data; diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index fdf7e79d8c0d..ebd49e2cb2d5 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -40,6 +40,7 @@ struct intel_pmt_header { struct intel_pmt_entry { struct telem_endpoint *ep; + struct pci_dev *pcidev; struct intel_pmt_header header; struct bin_attribute pmt_bin_attr; struct kobject *kobj; From 0ba9e9cf76f2487654bc9bca38218780fa53030e Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:32 -0400 Subject: [PATCH 50/63] drm/xe: Correct the rev value for the DVSEC entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By definition, the Designated Vendor Specific Extended Capability (DVSEC) revision should be 1. Add the rev value to be correct. Fixes: 0c45e76fcc62 ("drm/xe/vsec: Support BMG devices") Signed-off-by: Michael J. Ruhl Reviewed-by: David E. Box Link: https://lore.kernel.org/r/20250713172943.7335-3-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/gpu/drm/xe/xe_vsec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_vsec.c b/drivers/gpu/drm/xe/xe_vsec.c index b378848d3b7b..1bf7e709e110 100644 --- a/drivers/gpu/drm/xe/xe_vsec.c +++ b/drivers/gpu/drm/xe/xe_vsec.c @@ -24,6 +24,7 @@ #define BMG_DEVICE_ID 0xE2F8 static struct intel_vsec_header bmg_telemetry = { + .rev = 1, .length = 0x10, .id = VSEC_ID_TELEMETRY, .num_entries = 2, @@ -33,6 +34,7 @@ static struct intel_vsec_header bmg_telemetry = { }; static struct intel_vsec_header bmg_punit_crashlog = { + .rev = 1, .length = 0x10, .id = VSEC_ID_CRASHLOG, .num_entries = 1, @@ -42,6 +44,7 @@ static struct intel_vsec_header bmg_punit_crashlog = { }; static struct intel_vsec_header bmg_oobmsm_crashlog = { + .rev = 1, .length = 0x10, .id = VSEC_ID_CRASHLOG, .num_entries = 1, From 5b27388171a18cf6842c700520086ec50194e858 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:33 -0400 Subject: [PATCH 51/63] drm/xe: Correct BMG VSEC header sizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The intel_vsec_header information for the crashlog feature is incorrect. Update the VSEC header with correct sizing and count. Since the crashlog entries are "merged" (num_entries = 2), the separate capabilities entries must be merged as well. Fixes: 0c45e76fcc62 ("drm/xe/vsec: Support BMG devices") Acked-by: Rodrigo Vivi Signed-off-by: Michael J. Ruhl Reviewed-by: David E. Box Link: https://lore.kernel.org/r/20250713172943.7335-4-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/gpu/drm/xe/xe_vsec.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vsec.c b/drivers/gpu/drm/xe/xe_vsec.c index 1bf7e709e110..56930ad42962 100644 --- a/drivers/gpu/drm/xe/xe_vsec.c +++ b/drivers/gpu/drm/xe/xe_vsec.c @@ -33,30 +33,19 @@ static struct intel_vsec_header bmg_telemetry = { .offset = BMG_DISCOVERY_OFFSET, }; -static struct intel_vsec_header bmg_punit_crashlog = { +static struct intel_vsec_header bmg_crashlog = { .rev = 1, .length = 0x10, .id = VSEC_ID_CRASHLOG, - .num_entries = 1, - .entry_size = 4, + .num_entries = 2, + .entry_size = 6, .tbir = 0, .offset = BMG_DISCOVERY_OFFSET + 0x60, }; -static struct intel_vsec_header bmg_oobmsm_crashlog = { - .rev = 1, - .length = 0x10, - .id = VSEC_ID_CRASHLOG, - .num_entries = 1, - .entry_size = 4, - .tbir = 0, - .offset = BMG_DISCOVERY_OFFSET + 0x78, -}; - static struct intel_vsec_header *bmg_capabilities[] = { &bmg_telemetry, - &bmg_punit_crashlog, - &bmg_oobmsm_crashlog, + &bmg_crashlog, NULL }; From ba22fe0cffedf5156731fbac729a638f18d17e6b Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:34 -0400 Subject: [PATCH 52/63] platform/x86/intel/pmt: white space cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Noticed two white space issues; cleaned them. Reviewed-by: David E. Box Reviewed-by: Ilpo Järvinen Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-5-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 6a9eb3c4b313..d40c8e212733 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -143,7 +143,7 @@ enable_show(struct device *dev, struct device_attribute *attr, char *buf) static ssize_t enable_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) + const char *buf, size_t count) { struct crashlog_entry *entry; bool enabled; @@ -177,7 +177,7 @@ trigger_show(struct device *dev, struct device_attribute *attr, char *buf) static ssize_t trigger_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) + const char *buf, size_t count) { struct crashlog_entry *entry; bool trigger; From 75a496aa054326d9ebf27b39e1af8b5b770311ba Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:35 -0400 Subject: [PATCH 53/63] platform/x86/intel/pmt: mutex clean up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header file for mutex usage and mutex_destroy() cleanup code is absent from the crashlog.c module. Add the header file and mutex_destroy(). Reviewed-by: Ilpo Järvinen Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-6-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index d40c8e212733..6e32fc1f8f1d 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -262,8 +263,12 @@ static void pmt_crashlog_remove(struct auxiliary_device *auxdev) struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev); int i; - for (i = 0; i < priv->num_entries; i++) - intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns); + for (i = 0; i < priv->num_entries; i++) { + struct crashlog_entry *crashlog = &priv->entry[i]; + + intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns); + mutex_destroy(&crashlog->control_mutex); + } } static int pmt_crashlog_probe(struct auxiliary_device *auxdev, From 4f8fa22d108006b8ec0b94bb67a1bd537a2bf141 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:36 -0400 Subject: [PATCH 54/63] platform/x86/intel/pmt: use guard(mutex) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the mutex paths to use the new guard() mechanism. With the removal of goto, do some minor cleanup of the current logic path. Reviewed-by: David E. Box Reviewed-by: Ilpo Järvinen Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-7-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 33 +++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 6e32fc1f8f1d..c3ca95854aba 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -156,9 +157,9 @@ enable_store(struct device *dev, struct device_attribute *attr, if (result) return result; - mutex_lock(&entry->control_mutex); + guard(mutex)(&entry->control_mutex); + pmt_crashlog_set_disable(&entry->entry, !enabled); - mutex_unlock(&entry->control_mutex); return count; } @@ -190,26 +191,24 @@ trigger_store(struct device *dev, struct device_attribute *attr, if (result) return result; - mutex_lock(&entry->control_mutex); + guard(mutex)(&entry->control_mutex); if (!trigger) { pmt_crashlog_set_clear(&entry->entry); - } else if (pmt_crashlog_complete(&entry->entry)) { - /* we cannot trigger a new crash if one is still pending */ - result = -EEXIST; - goto err; - } else if (pmt_crashlog_disabled(&entry->entry)) { - /* if device is currently disabled, return busy */ - result = -EBUSY; - goto err; - } else { - pmt_crashlog_set_execute(&entry->entry); + return count; } - result = count; -err: - mutex_unlock(&entry->control_mutex); - return result; + /* we cannot trigger a new crash if one is still pending */ + if (pmt_crashlog_complete(&entry->entry)) + return -EEXIST; + + /* if device is currently disabled, return busy */ + if (pmt_crashlog_disabled(&entry->entry)) + return -EBUSY; + + pmt_crashlog_set_execute(&entry->entry); + + return count; } static DEVICE_ATTR_RW(trigger); From 147c18d8efaa1fa006fdd0b901d51092dc3b2189 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:37 -0400 Subject: [PATCH 55/63] platform/x86/intel/pmt: re-order trigger logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting the clear bit or checking the complete bit before checking to see if crashlog is disabled seems incorrect. Check disable before accessing any other bits. Reviewed-by: Ilpo Järvinen Reviewed-by: David E. Box Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-8-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index c3ca95854aba..440d2045e90d 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -193,6 +193,10 @@ trigger_store(struct device *dev, struct device_attribute *attr, guard(mutex)(&entry->control_mutex); + /* if device is currently disabled, return busy */ + if (pmt_crashlog_disabled(&entry->entry)) + return -EBUSY; + if (!trigger) { pmt_crashlog_set_clear(&entry->entry); return count; @@ -202,10 +206,6 @@ trigger_store(struct device *dev, struct device_attribute *attr, if (pmt_crashlog_complete(&entry->entry)) return -EEXIST; - /* if device is currently disabled, return busy */ - if (pmt_crashlog_disabled(&entry->entry)) - return -EBUSY; - pmt_crashlog_set_execute(&entry->entry); return count; From 5c7bfa1088274bc3820eb2083f8091ff8ad397ec Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:38 -0400 Subject: [PATCH 56/63] platform/x86/intel/pmt: correct types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A couple of local variables do not match the return types of some of the functions. Update the mismatched types to match. Reviewed-by: Ilpo Järvinen Reviewed-by: David E. Box Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-9-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 440d2045e90d..881f4abdae14 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -138,7 +138,7 @@ static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct intel_pmt_entry *entry = dev_get_drvdata(dev); - int enabled = !pmt_crashlog_disabled(entry); + bool enabled = !pmt_crashlog_disabled(entry); return sprintf(buf, "%d\n", enabled); } @@ -169,7 +169,7 @@ static ssize_t trigger_show(struct device *dev, struct device_attribute *attr, char *buf) { struct intel_pmt_entry *entry; - int trigger; + bool trigger; entry = dev_get_drvdata(dev); trigger = pmt_crashlog_complete(entry); From 8ab4f88d46c70bade0633b56a0f03e20102bf10c Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:39 -0400 Subject: [PATCH 57/63] platform/x86/intel/pmt: decouple sysfs and namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PMT namespace includes the crashlog sysfs attribute information. Other crashlog version/types may need different sysfs attributes. Coupling the attributes with the namespace blocks this usage. Decouple sysfs attributes from the name space and add them to the specific entry. Reviewed-by: Ilpo Järvinen Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-10-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/class.c | 12 ++++++------ drivers/platform/x86/intel/pmt/class.h | 2 +- drivers/platform/x86/intel/pmt/crashlog.c | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index 5dd80109f591..edcce340ea67 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -316,8 +316,8 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry, entry->kobj = &dev->kobj; - if (ns->attr_grp) { - ret = sysfs_create_group(entry->kobj, ns->attr_grp); + if (entry->attr_grp) { + ret = sysfs_create_group(entry->kobj, entry->attr_grp); if (ret) goto fail_sysfs_create_group; } @@ -358,8 +358,8 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry, fail_add_endpoint: sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr); fail_ioremap: - if (ns->attr_grp) - sysfs_remove_group(entry->kobj, ns->attr_grp); + if (entry->attr_grp) + sysfs_remove_group(entry->kobj, entry->attr_grp); fail_sysfs_create_group: device_unregister(dev); fail_dev_create: @@ -401,8 +401,8 @@ void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, if (entry->size) sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr); - if (ns->attr_grp) - sysfs_remove_group(entry->kobj, ns->attr_grp); + if (entry->attr_grp) + sysfs_remove_group(entry->kobj, entry->attr_grp); device_unregister(dev); xa_erase(ns->xa, entry->devid); diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index ebd49e2cb2d5..3c5ad5f52bca 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -43,6 +43,7 @@ struct intel_pmt_entry { struct pci_dev *pcidev; struct intel_pmt_header header; struct bin_attribute pmt_bin_attr; + const struct attribute_group *attr_grp; struct kobject *kobj; void __iomem *disc_table; void __iomem *base; @@ -58,7 +59,6 @@ struct intel_pmt_entry { struct intel_pmt_namespace { const char *name; struct xarray *xa; - const struct attribute_group *attr_grp; int (*pmt_header_decode)(struct intel_pmt_entry *entry, struct device *dev); int (*pmt_add_endpoint)(struct intel_vsec_device *ivdev, diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 881f4abdae14..23b3971da40a 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -243,6 +243,8 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, /* Size is measured in DWORDS, but accessor returns bytes */ header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET)); + entry->attr_grp = &pmt_crashlog_group; + return 0; } @@ -250,7 +252,6 @@ static DEFINE_XARRAY_ALLOC(crashlog_array); static struct intel_pmt_namespace pmt_crashlog_ns = { .name = "crashlog", .xa = &crashlog_array, - .attr_grp = &pmt_crashlog_group, .pmt_header_decode = pmt_crashlog_header_decode, }; From f57b32cb4adb28b62f61c4729f7b85f55518cb2b Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:40 -0400 Subject: [PATCH 58/63] platform/x86/intel/pmt: add register access helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The control register is used in a read/modify/write pattern. The status register is used in a read/check bit pattern. Add helpers to eliminate common code. Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-11-michael.j.ruhl@intel.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 58 +++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 23b3971da40a..ed781548e59d 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -64,20 +64,40 @@ struct pmt_crashlog_priv { /* * I/O */ + +/* Read, modify, write the control register, setting or clearing @bit based on @set */ +static void pmt_crashlog_rmw(struct intel_pmt_entry *entry, u32 bit, bool set) +{ + u32 reg = readl(entry->disc_table + CONTROL_OFFSET); + + reg &= ~CRASHLOG_FLAG_TRIGGER_MASK; + + if (set) + reg |= bit; + else + reg &= ~bit; + + writel(reg, entry->disc_table + CONTROL_OFFSET); +} + +/* Read the status register and see if the specified @bit is set */ +static bool pmt_crashlog_rc(struct intel_pmt_entry *entry, u32 bit) +{ + u32 reg = readl(entry->disc_table + CONTROL_OFFSET); + + return !!(reg & bit); +} + static bool pmt_crashlog_complete(struct intel_pmt_entry *entry) { - u32 control = readl(entry->disc_table + CONTROL_OFFSET); - /* return current value of the crashlog complete flag */ - return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE); + return pmt_crashlog_rc(entry, CRASHLOG_FLAG_TRIGGER_COMPLETE); } static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry) { - u32 control = readl(entry->disc_table + CONTROL_OFFSET); - /* return current value of the crashlog disabled flag */ - return !!(control & CRASHLOG_FLAG_DISABLE); + return pmt_crashlog_rc(entry, CRASHLOG_FLAG_DISABLE); } static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) @@ -98,37 +118,17 @@ static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry, bool disable) { - u32 control = readl(entry->disc_table + CONTROL_OFFSET); - - /* clear trigger bits so we are only modifying disable flag */ - control &= ~CRASHLOG_FLAG_TRIGGER_MASK; - - if (disable) - control |= CRASHLOG_FLAG_DISABLE; - else - control &= ~CRASHLOG_FLAG_DISABLE; - - writel(control, entry->disc_table + CONTROL_OFFSET); + pmt_crashlog_rmw(entry, CRASHLOG_FLAG_DISABLE, disable); } static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry) { - u32 control = readl(entry->disc_table + CONTROL_OFFSET); - - control &= ~CRASHLOG_FLAG_TRIGGER_MASK; - control |= CRASHLOG_FLAG_TRIGGER_CLEAR; - - writel(control, entry->disc_table + CONTROL_OFFSET); + pmt_crashlog_rmw(entry, CRASHLOG_FLAG_TRIGGER_CLEAR, true); } static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry) { - u32 control = readl(entry->disc_table + CONTROL_OFFSET); - - control &= ~CRASHLOG_FLAG_TRIGGER_MASK; - control |= CRASHLOG_FLAG_TRIGGER_EXECUTE; - - writel(control, entry->disc_table + CONTROL_OFFSET); + pmt_crashlog_rmw(entry, CRASHLOG_FLAG_TRIGGER_EXECUTE, true); } /* From 66df9fa783aadc2a5ae8ca11ead0b13032d24e7e Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:41 -0400 Subject: [PATCH 59/63] platform/x86/intel/pmt: refactor base parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To support an upcoming crashlog change, use the parent of struct intel_pmt_entry, struct crashlog_entry, as a main parameter. Using struct crashlog_entry will allow for a more flexible interface to control the crashlog feature. - Refactor to use struct crashlog_entry in place of struct intel_pmt_entry - Rename variables from "entry" to "crashlog" Reviewed-by: Ilpo Järvinen Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-12-michael.j.ruhl@intel.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 58 ++++++++++++----------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index ed781548e59d..087b7110ddd2 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -66,8 +66,9 @@ struct pmt_crashlog_priv { */ /* Read, modify, write the control register, setting or clearing @bit based on @set */ -static void pmt_crashlog_rmw(struct intel_pmt_entry *entry, u32 bit, bool set) +static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) { + struct intel_pmt_entry *entry = &crashlog->entry; u32 reg = readl(entry->disc_table + CONTROL_OFFSET); reg &= ~CRASHLOG_FLAG_TRIGGER_MASK; @@ -81,23 +82,24 @@ static void pmt_crashlog_rmw(struct intel_pmt_entry *entry, u32 bit, bool set) } /* Read the status register and see if the specified @bit is set */ -static bool pmt_crashlog_rc(struct intel_pmt_entry *entry, u32 bit) +static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit) { + struct intel_pmt_entry *entry = &crashlog->entry; u32 reg = readl(entry->disc_table + CONTROL_OFFSET); return !!(reg & bit); } -static bool pmt_crashlog_complete(struct intel_pmt_entry *entry) +static bool pmt_crashlog_complete(struct crashlog_entry *crashlog) { /* return current value of the crashlog complete flag */ - return pmt_crashlog_rc(entry, CRASHLOG_FLAG_TRIGGER_COMPLETE); + return pmt_crashlog_rc(crashlog, CRASHLOG_FLAG_TRIGGER_COMPLETE); } -static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry) +static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog) { /* return current value of the crashlog disabled flag */ - return pmt_crashlog_rc(entry, CRASHLOG_FLAG_DISABLE); + return pmt_crashlog_rc(crashlog, CRASHLOG_FLAG_DISABLE); } static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) @@ -115,20 +117,20 @@ static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) return crash_type == CRASH_TYPE_OOBMSM && version == 0; } -static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry, +static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog, bool disable) { - pmt_crashlog_rmw(entry, CRASHLOG_FLAG_DISABLE, disable); + pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_DISABLE, disable); } -static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry) +static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(entry, CRASHLOG_FLAG_TRIGGER_CLEAR, true); + pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_TRIGGER_CLEAR, true); } -static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry) +static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(entry, CRASHLOG_FLAG_TRIGGER_EXECUTE, true); + pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_TRIGGER_EXECUTE, true); } /* @@ -137,8 +139,8 @@ static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry) static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct intel_pmt_entry *entry = dev_get_drvdata(dev); - bool enabled = !pmt_crashlog_disabled(entry); + struct crashlog_entry *crashlog = dev_get_drvdata(dev); + bool enabled = !pmt_crashlog_disabled(crashlog); return sprintf(buf, "%d\n", enabled); } @@ -147,19 +149,19 @@ static ssize_t enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct crashlog_entry *entry; + struct crashlog_entry *crashlog; bool enabled; int result; - entry = dev_get_drvdata(dev); + crashlog = dev_get_drvdata(dev); result = kstrtobool(buf, &enabled); if (result) return result; - guard(mutex)(&entry->control_mutex); + guard(mutex)(&crashlog->control_mutex); - pmt_crashlog_set_disable(&entry->entry, !enabled); + pmt_crashlog_set_disable(crashlog, !enabled); return count; } @@ -168,11 +170,11 @@ static DEVICE_ATTR_RW(enable); static ssize_t trigger_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct intel_pmt_entry *entry; + struct crashlog_entry *crashlog; bool trigger; - entry = dev_get_drvdata(dev); - trigger = pmt_crashlog_complete(entry); + crashlog = dev_get_drvdata(dev); + trigger = pmt_crashlog_complete(crashlog); return sprintf(buf, "%d\n", trigger); } @@ -181,32 +183,32 @@ static ssize_t trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct crashlog_entry *entry; + struct crashlog_entry *crashlog; bool trigger; int result; - entry = dev_get_drvdata(dev); + crashlog = dev_get_drvdata(dev); result = kstrtobool(buf, &trigger); if (result) return result; - guard(mutex)(&entry->control_mutex); + guard(mutex)(&crashlog->control_mutex); /* if device is currently disabled, return busy */ - if (pmt_crashlog_disabled(&entry->entry)) + if (pmt_crashlog_disabled(crashlog)) return -EBUSY; if (!trigger) { - pmt_crashlog_set_clear(&entry->entry); + pmt_crashlog_set_clear(crashlog); return count; } /* we cannot trigger a new crash if one is still pending */ - if (pmt_crashlog_complete(&entry->entry)) + if (pmt_crashlog_complete(crashlog)) return -EEXIST; - pmt_crashlog_set_execute(&entry->entry); + pmt_crashlog_set_execute(crashlog); return count; } From 5623fa6859a6cd49366421317e3c5ab183583624 Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:42 -0400 Subject: [PATCH 60/63] platform/x86/intel/pmt: use a version struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for supporting multiple crashlog versions, use a struct to keep bit offset info for the status and control bits. Reviewed-by: Ilpo Järvinen Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-13-michael.j.ruhl@intel.com [ij: move crashlog_type1_ver0 to its final place & add consts to crashlog_info] Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 92 ++++++++++++++++------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index 087b7110ddd2..db54f881ba57 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -24,21 +24,6 @@ /* Crashlog discovery header types */ #define CRASH_TYPE_OOBMSM 1 -/* Control Flags */ -#define CRASHLOG_FLAG_DISABLE BIT(28) - -/* - * Bits 29 and 30 control the state of bit 31. - * - * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured. - * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31. - * Bit 31 is the read-only status with a 1 indicating log is complete. - */ -#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29) -#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30) -#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31) -#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28) - /* Crashlog Discovery Header */ #define CONTROL_OFFSET 0x0 #define GUID_OFFSET 0x4 @@ -50,10 +35,50 @@ /* size is in bytes */ #define GET_SIZE(v) ((v) * sizeof(u32)) +/* + * Type 1 Version 0 + * status and control registers are combined. + * + * Bits 29 and 30 control the state of bit 31. + * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured. + * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31. + * Bit 31 is the read-only status with a 1 indicating log is complete. + */ +#define TYPE1_VER0_STATUS_OFFSET 0x00 +#define TYPE1_VER0_CONTROL_OFFSET 0x00 + +#define TYPE1_VER0_DISABLE BIT(28) +#define TYPE1_VER0_CLEAR BIT(29) +#define TYPE1_VER0_EXECUTE BIT(30) +#define TYPE1_VER0_COMPLETE BIT(31) +#define TYPE1_VER0_TRIGGER_MASK GENMASK(31, 28) + +/* After offset, order alphabetically, not bit ordered */ +struct crashlog_status { + u32 offset; + u32 cleared; + u32 complete; + u32 disabled; +}; + +struct crashlog_control { + u32 offset; + u32 trigger_mask; + u32 clear; + u32 disable; + u32 manual; +}; + +struct crashlog_info { + const struct crashlog_status status; + const struct crashlog_control control; +}; + struct crashlog_entry { /* entry must be first member of struct */ struct intel_pmt_entry entry; struct mutex control_mutex; + const struct crashlog_info *info; }; struct pmt_crashlog_priv { @@ -68,24 +93,25 @@ struct pmt_crashlog_priv { /* Read, modify, write the control register, setting or clearing @bit based on @set */ static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) { + const struct crashlog_control *control = &crashlog->info->control; struct intel_pmt_entry *entry = &crashlog->entry; - u32 reg = readl(entry->disc_table + CONTROL_OFFSET); + u32 reg = readl(entry->disc_table + control->offset); - reg &= ~CRASHLOG_FLAG_TRIGGER_MASK; + reg &= ~control->trigger_mask; if (set) reg |= bit; else reg &= ~bit; - writel(reg, entry->disc_table + CONTROL_OFFSET); + writel(reg, entry->disc_table + control->offset); } /* Read the status register and see if the specified @bit is set */ static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit) { - struct intel_pmt_entry *entry = &crashlog->entry; - u32 reg = readl(entry->disc_table + CONTROL_OFFSET); + const struct crashlog_status *status = &crashlog->info->status; + u32 reg = readl(crashlog->entry.disc_table + status->offset); return !!(reg & bit); } @@ -93,13 +119,13 @@ static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit) static bool pmt_crashlog_complete(struct crashlog_entry *crashlog) { /* return current value of the crashlog complete flag */ - return pmt_crashlog_rc(crashlog, CRASHLOG_FLAG_TRIGGER_COMPLETE); + return pmt_crashlog_rc(crashlog, crashlog->info->status.complete); } static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog) { /* return current value of the crashlog disabled flag */ - return pmt_crashlog_rc(crashlog, CRASHLOG_FLAG_DISABLE); + return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled); } static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) @@ -120,17 +146,17 @@ static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog, bool disable) { - pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_DISABLE, disable); + pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable); } static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_TRIGGER_CLEAR, true); + pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true); } static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog) { - pmt_crashlog_rmw(crashlog, CRASHLOG_FLAG_TRIGGER_EXECUTE, true); + pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true); } /* @@ -224,6 +250,19 @@ static const struct attribute_group pmt_crashlog_group = { .attrs = pmt_crashlog_attrs, }; +static const struct crashlog_info crashlog_type1_ver0 = { + .status.offset = TYPE1_VER0_STATUS_OFFSET, + .status.cleared = TYPE1_VER0_CLEAR, + .status.complete = TYPE1_VER0_COMPLETE, + .status.disabled = TYPE1_VER0_DISABLE, + + .control.offset = TYPE1_VER0_CONTROL_OFFSET, + .control.trigger_mask = TYPE1_VER0_TRIGGER_MASK, + .control.clear = TYPE1_VER0_CLEAR, + .control.disable = TYPE1_VER0_DISABLE, + .control.manual = TYPE1_VER0_EXECUTE, +}; + static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, struct device *dev) { @@ -234,9 +273,10 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, if (!pmt_crashlog_supported(entry)) return 1; - /* initialize control mutex */ + /* initialize the crashlog struct */ crashlog = container_of(entry, struct crashlog_entry, entry); mutex_init(&crashlog->control_mutex); + crashlog->info = &crashlog_type1_ver0; header->access_type = GET_ACCESS(readl(disc_table)); header->guid = readl(disc_table + GUID_OFFSET); From 2c402a801c19568de97b86a77b25d13448dc080a Mon Sep 17 00:00:00 2001 From: "Michael J. Ruhl" Date: Sun, 13 Jul 2025 13:29:43 -0400 Subject: [PATCH 61/63] platform/x86/intel/pmt: support BMG crashlog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Battlemage GPU has the type 1 version 2 crashlog feature. Update the crashlog driver to support this crashlog version. Signed-off-by: Michael J. Ruhl Link: https://lore.kernel.org/r/20250713172943.7335-14-michael.j.ruhl@intel.com [ij: make crashlog_type1_ver2 static] Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/pmt/crashlog.c | 262 ++++++++++++++++++++-- 1 file changed, 249 insertions(+), 13 deletions(-) diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index db54f881ba57..b0393c9c5b4b 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -53,25 +53,59 @@ #define TYPE1_VER0_COMPLETE BIT(31) #define TYPE1_VER0_TRIGGER_MASK GENMASK(31, 28) +/* + * Type 1 Version 2 + * status and control are different registers + */ +#define TYPE1_VER2_STATUS_OFFSET 0x00 +#define TYPE1_VER2_CONTROL_OFFSET 0x14 + +/* status register */ +#define TYPE1_VER2_CLEAR_SUPPORT BIT(20) +#define TYPE1_VER2_REARMED BIT(25) +#define TYPE1_VER2_ERROR BIT(26) +#define TYPE1_VER2_CONSUMED BIT(27) +#define TYPE1_VER2_DISABLED BIT(28) +#define TYPE1_VER2_CLEARED BIT(29) +#define TYPE1_VER2_IN_PROGRESS BIT(30) +#define TYPE1_VER2_COMPLETE BIT(31) + +/* control register */ +#define TYPE1_VER2_CONSUME BIT(25) +#define TYPE1_VER2_REARM BIT(28) +#define TYPE1_VER2_EXECUTE BIT(29) +#define TYPE1_VER2_CLEAR BIT(30) +#define TYPE1_VER2_DISABLE BIT(31) +#define TYPE1_VER2_TRIGGER_MASK \ + (TYPE1_VER2_EXECUTE | TYPE1_VER2_CLEAR | TYPE1_VER2_DISABLE) + /* After offset, order alphabetically, not bit ordered */ struct crashlog_status { u32 offset; + u32 clear_supported; u32 cleared; u32 complete; + u32 consumed; u32 disabled; + u32 error; + u32 in_progress; + u32 rearmed; }; struct crashlog_control { u32 offset; u32 trigger_mask; u32 clear; + u32 consume; u32 disable; u32 manual; + u32 rearm; }; struct crashlog_info { const struct crashlog_status status; const struct crashlog_control control; + const struct attribute_group *attr_grp; }; struct crashlog_entry { @@ -128,19 +162,23 @@ static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog) return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled); } -static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) +static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version) { u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET); - u32 crash_type, version; - crash_type = GET_TYPE(discovery_header); - version = GET_VERSION(discovery_header); + *crash_type = GET_TYPE(discovery_header); + *version = GET_VERSION(discovery_header); /* - * Currently we only recognize OOBMSM version 0 devices. - * We can ignore all other crashlog devices in the system. + * Currently we only recognize OOBMSM (type 1) and version 0 or 2 + * devices. + * + * Ignore all other crashlog devices in the system. */ - return crash_type == CRASH_TYPE_OOBMSM && version == 0; + if (*crash_type == CRASH_TYPE_OOBMSM && (*version == 0 || *version == 2)) + return true; + + return false; } static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog, @@ -159,9 +197,115 @@ static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog) pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true); } +static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog) +{ + return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared); +} + +static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog) +{ + return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed); +} + +static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog) +{ + pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true); +} + +static bool pmt_crashlog_error(struct crashlog_entry *crashlog) +{ + return pmt_crashlog_rc(crashlog, crashlog->info->status.error); +} + +static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog) +{ + return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed); +} + +static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog) +{ + pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true); +} + /* * sysfs */ +static ssize_t +clear_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct crashlog_entry *crashlog = dev_get_drvdata(dev); + bool cleared = pmt_crashlog_cleared(crashlog); + + return sysfs_emit(buf, "%d\n", cleared); +} + +static ssize_t +clear_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct crashlog_entry *crashlog; + bool clear; + int result; + + crashlog = dev_get_drvdata(dev); + + result = kstrtobool(buf, &clear); + if (result) + return result; + + /* set bit only */ + if (!clear) + return -EINVAL; + + guard(mutex)(&crashlog->control_mutex); + + pmt_crashlog_set_clear(crashlog); + + return count; +} +static DEVICE_ATTR_RW(clear); + +static ssize_t +consumed_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct crashlog_entry *crashlog = dev_get_drvdata(dev); + bool consumed = pmt_crashlog_consumed(crashlog); + + return sysfs_emit(buf, "%d\n", consumed); +} + +static ssize_t +consumed_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct crashlog_entry *crashlog; + bool consumed; + int result; + + crashlog = dev_get_drvdata(dev); + + result = kstrtobool(buf, &consumed); + if (result) + return result; + + /* set bit only */ + if (!consumed) + return -EINVAL; + + guard(mutex)(&crashlog->control_mutex); + + if (pmt_crashlog_disabled(crashlog)) + return -EBUSY; + + if (!pmt_crashlog_complete(crashlog)) + return -EEXIST; + + pmt_crashlog_set_consumed(crashlog); + + return count; +} +static DEVICE_ATTR_RW(consumed); + static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -193,6 +337,51 @@ enable_store(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(enable); +static ssize_t +error_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct crashlog_entry *crashlog = dev_get_drvdata(dev); + bool error = pmt_crashlog_error(crashlog); + + return sysfs_emit(buf, "%d\n", error); +} +static DEVICE_ATTR_RO(error); + +static ssize_t +rearm_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct crashlog_entry *crashlog = dev_get_drvdata(dev); + int rearmed = pmt_crashlog_rearm(crashlog); + + return sysfs_emit(buf, "%d\n", rearmed); +} + +static ssize_t +rearm_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct crashlog_entry *crashlog; + bool rearm; + int result; + + crashlog = dev_get_drvdata(dev); + + result = kstrtobool(buf, &rearm); + if (result) + return result; + + /* set only */ + if (!rearm) + return -EINVAL; + + guard(mutex)(&crashlog->control_mutex); + + pmt_crashlog_set_rearm(crashlog); + + return count; +} +static DEVICE_ATTR_RW(rearm); + static ssize_t trigger_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -240,14 +429,28 @@ trigger_store(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(trigger); -static struct attribute *pmt_crashlog_attrs[] = { +static struct attribute *pmt_crashlog_type1_ver0_attrs[] = { &dev_attr_enable.attr, &dev_attr_trigger.attr, NULL }; -static const struct attribute_group pmt_crashlog_group = { - .attrs = pmt_crashlog_attrs, +static struct attribute *pmt_crashlog_type1_ver2_attrs[] = { + &dev_attr_clear.attr, + &dev_attr_consumed.attr, + &dev_attr_enable.attr, + &dev_attr_error.attr, + &dev_attr_rearm.attr, + &dev_attr_trigger.attr, + NULL +}; + +static const struct attribute_group pmt_crashlog_type1_ver0_group = { + .attrs = pmt_crashlog_type1_ver0_attrs, +}; + +static const struct attribute_group pmt_crashlog_type1_ver2_group = { + .attrs = pmt_crashlog_type1_ver2_attrs, }; static const struct crashlog_info crashlog_type1_ver0 = { @@ -261,22 +464,55 @@ static const struct crashlog_info crashlog_type1_ver0 = { .control.clear = TYPE1_VER0_CLEAR, .control.disable = TYPE1_VER0_DISABLE, .control.manual = TYPE1_VER0_EXECUTE, + .attr_grp = &pmt_crashlog_type1_ver0_group, }; +static const struct crashlog_info crashlog_type1_ver2 = { + .status.offset = TYPE1_VER2_STATUS_OFFSET, + .status.clear_supported = TYPE1_VER2_CLEAR_SUPPORT, + .status.cleared = TYPE1_VER2_CLEARED, + .status.complete = TYPE1_VER2_COMPLETE, + .status.consumed = TYPE1_VER2_CONSUMED, + .status.disabled = TYPE1_VER2_DISABLED, + .status.error = TYPE1_VER2_ERROR, + .status.in_progress = TYPE1_VER2_IN_PROGRESS, + .status.rearmed = TYPE1_VER2_REARMED, + + .control.offset = TYPE1_VER2_CONTROL_OFFSET, + .control.trigger_mask = TYPE1_VER2_TRIGGER_MASK, + .control.clear = TYPE1_VER2_CLEAR, + .control.consume = TYPE1_VER2_CONSUME, + .control.disable = TYPE1_VER2_DISABLE, + .control.manual = TYPE1_VER2_EXECUTE, + .control.rearm = TYPE1_VER2_REARM, + .attr_grp = &pmt_crashlog_type1_ver2_group, +}; + +static const struct crashlog_info *select_crashlog_info(u32 type, u32 version) +{ + if (version == 0) + return &crashlog_type1_ver0; + + return &crashlog_type1_ver2; +} + static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, struct device *dev) { void __iomem *disc_table = entry->disc_table; struct intel_pmt_header *header = &entry->header; struct crashlog_entry *crashlog; + u32 version; + u32 type; - if (!pmt_crashlog_supported(entry)) + if (!pmt_crashlog_supported(entry, &type, &version)) return 1; /* initialize the crashlog struct */ crashlog = container_of(entry, struct crashlog_entry, entry); mutex_init(&crashlog->control_mutex); - crashlog->info = &crashlog_type1_ver0; + + crashlog->info = select_crashlog_info(type, version); header->access_type = GET_ACCESS(readl(disc_table)); header->guid = readl(disc_table + GUID_OFFSET); @@ -285,7 +521,7 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, /* Size is measured in DWORDS, but accessor returns bytes */ header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET)); - entry->attr_grp = &pmt_crashlog_group; + entry->attr_grp = crashlog->info->attr_grp; return 0; } From 232b41d3c2ce8cf4641a174416676458bf0de5b2 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Fri, 18 Jul 2025 18:33:04 +0200 Subject: [PATCH 62/63] platform/x86: oxpec: Fix turbo register for G1 AMD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out that the AMD variant of the G1 uses different EC registers than the Intel variant. Differentiate them and apply the correct ones to the AMD variant. Fixes: b369395c895b ("platform/x86: oxpec: Add support for the OneXPlayer G1") Signed-off-by: Antheas Kapenekakis Link: https://lore.kernel.org/r/20250718163305.159232-1-lkml@antheas.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/oxpec.c | 37 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 06759036945d..9839e8cb82ce 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -58,7 +58,8 @@ enum oxp_board { oxp_mini_amd_a07, oxp_mini_amd_pro, oxp_x1, - oxp_g1, + oxp_g1_i, + oxp_g1_a, }; static enum oxp_board board; @@ -247,14 +248,14 @@ static const struct dmi_system_id dmi_table[] = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 A"), }, - .driver_data = (void *)oxp_g1, + .driver_data = (void *)oxp_g1_a, }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 i"), }, - .driver_data = (void *)oxp_g1, + .driver_data = (void *)oxp_g1_i, }, { .matches = { @@ -352,7 +353,8 @@ static umode_t tt_toggle_is_visible(struct kobject *kobj, case oxp_mini_amd_a07: case oxp_mini_amd_pro: case oxp_x1: - case oxp_g1: + case oxp_g1_i: + case oxp_g1_a: return attr->mode; default: break; @@ -381,12 +383,13 @@ static ssize_t tt_toggle_store(struct device *dev, case aok_zoe_a1: case oxp_fly: case oxp_mini_amd_pro: + case oxp_g1_a: reg = OXP_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; case oxp_2: case oxp_x1: - case oxp_g1: + case oxp_g1_i: reg = OXP_2_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; @@ -426,12 +429,13 @@ static ssize_t tt_toggle_show(struct device *dev, case aok_zoe_a1: case oxp_fly: case oxp_mini_amd_pro: + case oxp_g1_a: reg = OXP_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; case oxp_2: case oxp_x1: - case oxp_g1: + case oxp_g1_i: reg = OXP_2_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; @@ -520,7 +524,8 @@ static bool oxp_psy_ext_supported(void) { switch (board) { case oxp_x1: - case oxp_g1: + case oxp_g1_i: + case oxp_g1_a: case oxp_fly: return true; default: @@ -659,7 +664,8 @@ static int oxp_pwm_enable(void) case oxp_mini_amd_a07: case oxp_mini_amd_pro: case oxp_x1: - case oxp_g1: + case oxp_g1_i: + case oxp_g1_a: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); default: return -EINVAL; @@ -686,7 +692,8 @@ static int oxp_pwm_disable(void) case oxp_mini_amd_a07: case oxp_mini_amd_pro: case oxp_x1: - case oxp_g1: + case oxp_g1_i: + case oxp_g1_a: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); default: return -EINVAL; @@ -713,7 +720,8 @@ static int oxp_pwm_read(long *val) case oxp_mini_amd_a07: case oxp_mini_amd_pro: case oxp_x1: - case oxp_g1: + case oxp_g1_i: + case oxp_g1_a: return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); default: return -EOPNOTSUPP; @@ -742,7 +750,7 @@ static int oxp_pwm_fan_speed(long *val) return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val); case oxp_2: case oxp_x1: - case oxp_g1: + case oxp_g1_i: return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val); case aok_zoe_a1: case aya_neo_2: @@ -757,6 +765,7 @@ static int oxp_pwm_fan_speed(long *val) case oxp_mini_amd: case oxp_mini_amd_a07: case oxp_mini_amd_pro: + case oxp_g1_a: return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); default: return -EOPNOTSUPP; @@ -776,7 +785,7 @@ static int oxp_pwm_input_write(long val) return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val); case oxp_2: case oxp_x1: - case oxp_g1: + case oxp_g1_i: /* scale to range [0-184] */ val = (val * 184) / 255; return write_to_ec(OXP_SENSOR_PWM_REG, val); @@ -796,6 +805,7 @@ static int oxp_pwm_input_write(long val) case aok_zoe_a1: case oxp_fly: case oxp_mini_amd_pro: + case oxp_g1_a: return write_to_ec(OXP_SENSOR_PWM_REG, val); default: return -EOPNOTSUPP; @@ -816,7 +826,7 @@ static int oxp_pwm_input_read(long *val) break; case oxp_2: case oxp_x1: - case oxp_g1: + case oxp_g1_i: ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); if (ret) return ret; @@ -842,6 +852,7 @@ static int oxp_pwm_input_read(long *val) case aok_zoe_a1: case oxp_fly: case oxp_mini_amd_pro: + case oxp_g1_a: default: ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); if (ret) From 1798561befd8be1e52feb54f850efcab5a595f43 Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Fri, 18 Jul 2025 18:33:05 +0200 Subject: [PATCH 63/63] platform/x86: oxpec: Add support for OneXPlayer X1 Mini Pro (Strix Point) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OneXPlayer X1 Mini Pro (which is the Strix Point variant of the Mini) uses the same registers as the X1 Mini, so re-use the quirk. Signed-off-by: Antheas Kapenekakis Link: https://lore.kernel.org/r/20250718163305.159232-2-lkml@antheas.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/oxpec.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 9839e8cb82ce..eb076bb4099b 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -292,6 +292,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)oxp_x1, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Mini Pro"), + }, + .driver_data = (void *)oxp_x1, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),