Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data

A capdata00 attribute (0x04050000) describes the presence of Fan Test
Data. Query it, and bind Fan Test Data as a component of capdata00
accordingly. The component master of capdata00 may pass a callback while
binding to retrieve fan info from Fan Test Data.

Summarizing this scheme:

lenovo-wmi-other <-> capdata00 <-> capdata_fan
|- master |- component |
|- sub-master |- sub-component

The callback will be called once both the master and the sub-component
are bound to the sub-master (component).

This scheme is essential to solve these issues:
- The component framework only supports one aggregation per master
- A binding is only established until all components are found
- The Fan Test Data interface may be missing on some devices
- To get rid of queries for the presence of WMI GUIDs
- The notifier framework cannot cleanly connect capdata_fan to
lenovo-wmi-other without introducing assumptions on probing sequence

capdata00 is registered as a component and a sub-master on probe,
instead of chaining the registrations in one's bind callback. This is
because calling (un)registration methods of the component framework
causes deadlock in (un)bind callbacks, i.e., it's impossible to register
capdata00 as a sub-master/component in its component/sub-master bind
callback, and vice versa.

Signed-off-by: Rong Zhang <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
Link: https://patch.msgid.link/20260120182104.163424-7-i@rong.moe
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Rong Zhang and committed by
Ilpo Järvinen
67d9a39c 012a8f96

+299 -6
+279 -1
drivers/platform/x86/lenovo/wmi-capdata.c
··· 27 27 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 28 28 29 29 #include <linux/acpi.h> 30 + #include <linux/bitfield.h> 30 31 #include <linux/bug.h> 31 32 #include <linux/cleanup.h> 32 33 #include <linux/component.h> ··· 56 55 #define ACPI_AC_CLASS "ac_adapter" 57 56 #define ACPI_AC_NOTIFY_STATUS 0x80 58 57 58 + #define LWMI_FEATURE_ID_FAN_TEST 0x05 59 + 60 + #define LWMI_ATTR_ID_FAN_TEST \ 61 + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ 62 + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST)) 63 + 59 64 enum lwmi_cd_type { 60 65 LENOVO_CAPABILITY_DATA_00, 61 66 LENOVO_CAPABILITY_DATA_01, 62 67 LENOVO_FAN_TEST_DATA, 68 + CD_TYPE_NONE = -1, 63 69 }; 64 70 65 71 #define LWMI_CD_TABLE_ITEM(_type) \ ··· 88 80 struct notifier_block acpi_nb; /* ACPI events */ 89 81 struct wmi_device *wdev; 90 82 struct cd_list *list; 83 + 84 + /* 85 + * A capdata device may be a component master of another capdata device. 86 + * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan 87 + * |- master |- component 88 + * |- sub-master 89 + * |- sub-component 90 + */ 91 + struct lwmi_cd_sub_master_priv { 92 + struct device *master_dev; 93 + cd_list_cb_t master_cb; 94 + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */ 95 + bool registered; /* Has the sub-master been registered? */ 96 + } *sub_master; 91 97 }; 92 98 93 99 struct cd_list { ··· 165 143 EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); 166 144 167 145 /** 146 + * lwmi_cd_call_master_cb() - Call the master callback for the sub-component. 147 + * @priv: Pointer to the capability data private data. 148 + * 149 + * Call the master callback and pass the sub-component list to it if the 150 + * dependency chain (master <-> sub-master <-> sub-component) is complete. 151 + */ 152 + static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv) 153 + { 154 + struct cd_list *sub_component_list = priv->sub_master->sub_component_list; 155 + 156 + /* 157 + * Call the callback only if the dependency chain is ready: 158 + * - Binding between master and sub-master: fills master_dev and master_cb 159 + * - Binding between sub-master and sub-component: fills sub_component_list 160 + * 161 + * If a binding has been unbound before the other binding is bound, the 162 + * corresponding members filled by the former are guaranteed to be cleared. 163 + * 164 + * This function is only called in bind callbacks, and the component 165 + * framework guarantees bind/unbind callbacks may never execute 166 + * simultaneously, which implies that it's impossible to have a race 167 + * condition. 168 + * 169 + * Hence, this check is sufficient to ensure that the callback is called 170 + * at most once and with the correct state, without relying on a specific 171 + * sequence of binding establishment. 172 + */ 173 + if (!sub_component_list || 174 + !priv->sub_master->master_dev || 175 + !priv->sub_master->master_cb) 176 + return; 177 + 178 + if (PTR_ERR(sub_component_list) == -ENODEV) 179 + sub_component_list = NULL; 180 + else if (WARN_ON(IS_ERR(sub_component_list))) 181 + return; 182 + 183 + priv->sub_master->master_cb(priv->sub_master->master_dev, 184 + sub_component_list); 185 + 186 + /* 187 + * Userspace may unbind a device from its driver and bind it again 188 + * through sysfs. Let's call this operation "reprobe" to distinguish it 189 + * from component "rebind". 190 + * 191 + * When reprobing capdata00/01 or the master device, the master device 192 + * is unbound from us with appropriate cleanup before we bind to it and 193 + * call master_cb. Everything is fine in this case. 194 + * 195 + * When reprobing capdata_fan, the master device has never been unbound 196 + * from us (hence no cleanup is done)[1], but we call master_cb the 197 + * second time. To solve this issue, we clear master_cb and master_dev 198 + * so we won't call master_cb twice while a binding is still complete. 199 + * 200 + * Note that we can't clear sub_component_list, otherwise reprobing 201 + * capdata01 or the master device causes master_cb to be never called 202 + * after we rebind to the master device. 203 + * 204 + * [1]: The master device does not need capdata_fan in run time, so 205 + * losing capdata_fan will not break the binding to the master device. 206 + */ 207 + priv->sub_master->master_cb = NULL; 208 + priv->sub_master->master_dev = NULL; 209 + } 210 + 211 + /** 168 212 * lwmi_cd_component_bind() - Bind component to master device. 169 213 * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. 170 214 * @om_dev: Pointer to the lenovo-wmi-other driver parent device. ··· 239 151 * On lenovo-wmi-other's master bind, provide a pointer to the local capdata 240 152 * list. This is used to call lwmi_cd*_get_data to look up attribute data 241 153 * from the lenovo-wmi-other driver. 154 + * 155 + * If cd_dev is a sub-master, try to call the master callback. 242 156 * 243 157 * Return: 0 244 158 */ ··· 253 163 switch (priv->list->type) { 254 164 case LENOVO_CAPABILITY_DATA_00: 255 165 binder->cd00_list = priv->list; 166 + 167 + priv->sub_master->master_dev = om_dev; 168 + priv->sub_master->master_cb = binder->cd_fan_list_cb; 169 + lwmi_cd_call_master_cb(priv); 170 + 256 171 break; 257 172 case LENOVO_CAPABILITY_DATA_01: 258 173 binder->cd01_list = priv->list; ··· 269 174 return 0; 270 175 } 271 176 177 + /** 178 + * lwmi_cd_component_unbind() - Unbind component to master device. 179 + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. 180 + * @om_dev: Pointer to the lenovo-wmi-other driver parent device. 181 + * @data: Unused. 182 + * 183 + * If cd_dev is a sub-master, clear the collected data from the master device to 184 + * prevent the binding establishment between the sub-master and the sub- 185 + * component (if it's about to happen) from calling the master callback. 186 + */ 187 + static void lwmi_cd_component_unbind(struct device *cd_dev, 188 + struct device *om_dev, void *data) 189 + { 190 + struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); 191 + 192 + switch (priv->list->type) { 193 + case LENOVO_CAPABILITY_DATA_00: 194 + priv->sub_master->master_dev = NULL; 195 + priv->sub_master->master_cb = NULL; 196 + return; 197 + default: 198 + return; 199 + } 200 + } 201 + 272 202 static const struct component_ops lwmi_cd_component_ops = { 273 203 .bind = lwmi_cd_component_bind, 204 + .unbind = lwmi_cd_component_unbind, 205 + }; 206 + 207 + /** 208 + * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device 209 + * @dev: The sub-master capdata basic device. 210 + * 211 + * Call component_bind_all to bind the sub-component device to the sub-master 212 + * device. On success, collect the pointer to the sub-component list and try 213 + * to call the master callback. 214 + * 215 + * Return: 0 on success, or an error code. 216 + */ 217 + static int lwmi_cd_sub_master_bind(struct device *dev) 218 + { 219 + struct lwmi_cd_priv *priv = dev_get_drvdata(dev); 220 + struct cd_list *sub_component_list; 221 + int ret; 222 + 223 + ret = component_bind_all(dev, &sub_component_list); 224 + if (ret) 225 + return ret; 226 + 227 + priv->sub_master->sub_component_list = sub_component_list; 228 + lwmi_cd_call_master_cb(priv); 229 + 230 + return 0; 231 + } 232 + 233 + /** 234 + * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device 235 + * @dev: The sub-master capdata basic device 236 + * 237 + * Clear the collected pointer to the sub-component list to prevent the binding 238 + * establishment between the sub-master and the sub-component (if it's about to 239 + * happen) from calling the master callback. Then, call component_unbind_all to 240 + * unbind the sub-component device from the sub-master device. 241 + */ 242 + static void lwmi_cd_sub_master_unbind(struct device *dev) 243 + { 244 + struct lwmi_cd_priv *priv = dev_get_drvdata(dev); 245 + 246 + priv->sub_master->sub_component_list = NULL; 247 + 248 + component_unbind_all(dev, NULL); 249 + } 250 + 251 + static const struct component_master_ops lwmi_cd_sub_master_ops = { 252 + .bind = lwmi_cd_sub_master_bind, 253 + .unbind = lwmi_cd_sub_master_unbind, 254 + }; 255 + 256 + /** 257 + * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component 258 + * @priv: Pointer to the sub-master capdata device private data. 259 + * @sub_component_type: Type of the sub-component. 260 + * 261 + * Match the sub-component type and register the current capdata device as a 262 + * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub- 263 + * component as non-existent without registering sub-master. 264 + * 265 + * Return: 0 on success, or an error code. 266 + */ 267 + static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, 268 + enum lwmi_cd_type sub_component_type) 269 + { 270 + struct component_match *master_match = NULL; 271 + int ret; 272 + 273 + priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL); 274 + if (!priv->sub_master) 275 + return -ENOMEM; 276 + 277 + if (sub_component_type == CD_TYPE_NONE) { 278 + /* The master callback will be called with NULL on bind. */ 279 + priv->sub_master->sub_component_list = ERR_PTR(-ENODEV); 280 + priv->sub_master->registered = false; 281 + return 0; 282 + } 283 + 284 + /* 285 + * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack 286 + * data cannot be used here. Steal one from lwmi_cd_table. 287 + */ 288 + component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match, 289 + (void *)&lwmi_cd_table[sub_component_type].type); 290 + if (IS_ERR(master_match)) 291 + return PTR_ERR(master_match); 292 + 293 + ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops, 294 + master_match); 295 + if (ret) 296 + return ret; 297 + 298 + priv->sub_master->registered = true; 299 + return 0; 300 + } 301 + 302 + /** 303 + * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered 304 + * @priv: Pointer to the sub-master capdata device private data. 305 + */ 306 + static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) 307 + { 308 + if (!priv->sub_master->registered) 309 + return; 310 + 311 + component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); 312 + priv->sub_master->registered = false; 313 + } 314 + 315 + /** 316 + * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device. 317 + * @sc_dev: Pointer to the sub-component capdata parent device. 318 + * @sm_dev: Pointer to the sub-master capdata parent device. 319 + * @data: Pointer used to return the capability data list pointer. 320 + * 321 + * On sub-master's bind, provide a pointer to the local capdata list. 322 + * This is used by the sub-master to call the master callback. 323 + * 324 + * Return: 0 325 + */ 326 + static int lwmi_cd_sub_component_bind(struct device *sc_dev, 327 + struct device *sm_dev, void *data) 328 + { 329 + struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev); 330 + struct cd_list **listp = data; 331 + 332 + *listp = priv->list; 333 + 334 + return 0; 335 + } 336 + 337 + static const struct component_ops lwmi_cd_sub_component_ops = { 338 + .bind = lwmi_cd_sub_component_bind, 274 339 }; 275 340 276 341 /* ··· 726 471 goto out; 727 472 728 473 switch (info->type) { 729 - case LENOVO_CAPABILITY_DATA_00: 474 + case LENOVO_CAPABILITY_DATA_00: { 475 + enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA; 476 + struct capdata00 capdata00; 477 + 478 + ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00); 479 + if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { 480 + dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); 481 + sub_component_type = CD_TYPE_NONE; 482 + } 483 + 484 + /* Sub-master (capdata00) <-> sub-component (capdata_fan) */ 485 + ret = lwmi_cd_sub_master_add(priv, sub_component_type); 486 + if (ret) 487 + goto out; 488 + 489 + /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */ 730 490 ret = component_add(&wdev->dev, &lwmi_cd_component_ops); 491 + if (ret) 492 + lwmi_cd_sub_master_del(priv); 493 + 731 494 goto out; 495 + } 732 496 case LENOVO_CAPABILITY_DATA_01: 733 497 priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; 734 498 ··· 763 489 ret = component_add(&wdev->dev, &lwmi_cd_component_ops); 764 490 goto out; 765 491 case LENOVO_FAN_TEST_DATA: 492 + ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops); 766 493 goto out; 767 494 default: 768 495 return -EINVAL; ··· 785 510 786 511 switch (priv->list->type) { 787 512 case LENOVO_CAPABILITY_DATA_00: 513 + lwmi_cd_sub_master_del(priv); 514 + fallthrough; 788 515 case LENOVO_CAPABILITY_DATA_01: 789 516 component_del(&wdev->dev, &lwmi_cd_component_ops); 790 517 break; 791 518 case LENOVO_FAN_TEST_DATA: 519 + component_del(&wdev->dev, &lwmi_cd_sub_component_ops); 792 520 break; 793 521 default: 794 522 WARN_ON(1);
+20
drivers/platform/x86/lenovo/wmi-capdata.h
··· 5 5 #ifndef _LENOVO_WMI_CAPDATA_H_ 6 6 #define _LENOVO_WMI_CAPDATA_H_ 7 7 8 + #include <linux/bits.h> 8 9 #include <linux/types.h> 10 + 11 + #define LWMI_SUPP_VALID BIT(0) 12 + #define LWMI_SUPP_MAY_GET (LWMI_SUPP_VALID | BIT(1)) 13 + #define LWMI_SUPP_MAY_SET (LWMI_SUPP_VALID | BIT(2)) 14 + 15 + #define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) 16 + #define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) 17 + #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) 18 + #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) 19 + 20 + #define LWMI_DEVICE_ID_FAN 0x04 9 21 10 22 struct component_match; 11 23 struct device; ··· 44 32 u32 max_rpm; 45 33 }; 46 34 35 + typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list); 36 + 47 37 struct lwmi_cd_binder { 48 38 struct cd_list *cd00_list; 49 39 struct cd_list *cd01_list; 40 + /* 41 + * May be called during or after the bind callback. 42 + * Will be called with NULL if capdata_fan does not exist. 43 + * The pointer is only valid in the callback; never keep it for later use! 44 + */ 45 + cd_list_cb_t cd_fan_list_cb; 50 46 }; 51 47 52 48 void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
-5
drivers/platform/x86/lenovo/wmi-other.c
··· 54 54 #define LWMI_FEATURE_VALUE_GET 17 55 55 #define LWMI_FEATURE_VALUE_SET 18 56 56 57 - #define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) 58 - #define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) 59 - #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) 60 - #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) 61 - 62 57 #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" 63 58 64 59 static BLOCKING_NOTIFIER_HEAD(om_chain_head);