1212 *
1313 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
1414 * - Initial implementation (formerly named lenovo-wmi-capdata01)
15+ *
16+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
17+ * - Unified implementation
1518 */
1619
20+ #define pr_fmt (fmt ) KBUILD_MODNAME ": " fmt
21+
1722#include <linux/acpi.h>
23+ #include <linux/bug.h>
1824#include <linux/cleanup.h>
1925#include <linux/component.h>
2026#include <linux/container_of.h>
2127#include <linux/device.h>
28+ #include <linux/dev_printk.h>
29+ #include <linux/err.h>
2230#include <linux/export.h>
2331#include <linux/gfp_types.h>
2432#include <linux/module.h>
2533#include <linux/mutex.h>
2634#include <linux/mutex_types.h>
2735#include <linux/notifier.h>
2836#include <linux/overflow.h>
37+ #include <linux/stddef.h>
2938#include <linux/types.h>
3039#include <linux/wmi.h>
3140
3645#define ACPI_AC_CLASS "ac_adapter"
3746#define ACPI_AC_NOTIFY_STATUS 0x80
3847
48+ enum lwmi_cd_type {
49+ LENOVO_CAPABILITY_DATA_01 ,
50+ };
51+
52+ #define LWMI_CD_TABLE_ITEM (_type ) \
53+ [_type] = { \
54+ .name = #_type, \
55+ .type = _type, \
56+ }
57+
58+ static const struct lwmi_cd_info {
59+ const char * name ;
60+ enum lwmi_cd_type type ;
61+ } lwmi_cd_table [] = {
62+ LWMI_CD_TABLE_ITEM (LENOVO_CAPABILITY_DATA_01 ),
63+ };
64+
3965struct lwmi_cd_priv {
4066 struct notifier_block acpi_nb ; /* ACPI events */
4167 struct wmi_device * wdev ;
@@ -44,15 +70,63 @@ struct lwmi_cd_priv {
4470
4571struct cd_list {
4672 struct mutex list_mutex ; /* list R/W mutex */
73+ enum lwmi_cd_type type ;
4774 u8 count ;
48- struct capdata01 data [];
75+
76+ union {
77+ DECLARE_FLEX_ARRAY (struct capdata01 , cd01 );
78+ };
4979};
5080
81+ static struct wmi_driver lwmi_cd_driver ;
82+
83+ /**
84+ * lwmi_cd_match() - Match rule for the master driver.
85+ * @dev: Pointer to the capability data parent device.
86+ * @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
87+ *
88+ * Return: int.
89+ */
90+ static int lwmi_cd_match (struct device * dev , void * type )
91+ {
92+ struct lwmi_cd_priv * priv ;
93+
94+ if (dev -> driver != & lwmi_cd_driver .driver )
95+ return false;
96+
97+ priv = dev_get_drvdata (dev );
98+ return priv -> list -> type == * (enum lwmi_cd_type * )type ;
99+ }
100+
101+ /**
102+ * lwmi_cd_match_add_all() - Add all match rule for the master driver.
103+ * @master: Pointer to the master device.
104+ * @matchptr: Pointer to the returned component_match pointer.
105+ *
106+ * Adds all component matches to the list stored in @matchptr for the @master
107+ * device. @matchptr must be initialized to NULL.
108+ */
109+ void lwmi_cd_match_add_all (struct device * master , struct component_match * * matchptr )
110+ {
111+ int i ;
112+
113+ if (WARN_ON (* matchptr ))
114+ return ;
115+
116+ for (i = 0 ; i < ARRAY_SIZE (lwmi_cd_table ); i ++ ) {
117+ component_match_add (master , matchptr , lwmi_cd_match ,
118+ (void * )& lwmi_cd_table [i ].type );
119+ if (IS_ERR (* matchptr ))
120+ return ;
121+ }
122+ }
123+ EXPORT_SYMBOL_NS_GPL (lwmi_cd_match_add_all , "LENOVO_WMI_CAPDATA" );
124+
51125/**
52126 * lwmi_cd_component_bind() - Bind component to master device.
53127 * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
54128 * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
55- * @data: cd_list object pointer used to return the capability data.
129+ * @data: lwmi_cd_binder object pointer used to return the capability data.
56130 *
57131 * On lenovo-wmi-other's master bind, provide a pointer to the local capdata
58132 * list. This is used to call lwmi_cd*_get_data to look up attribute data
@@ -64,9 +138,15 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
64138 struct device * om_dev , void * data )
65139{
66140 struct lwmi_cd_priv * priv = dev_get_drvdata (cd_dev );
67- struct cd_list * * cd_list = data ;
141+ struct lwmi_cd_binder * binder = data ;
68142
69- * cd_list = priv -> list ;
143+ switch (priv -> list -> type ) {
144+ case LENOVO_CAPABILITY_DATA_01 :
145+ binder -> cd01_list = priv -> list ;
146+ break ;
147+ default :
148+ return - EINVAL ;
149+ }
70150
71151 return 0 ;
72152}
@@ -75,31 +155,36 @@ static const struct component_ops lwmi_cd_component_ops = {
75155 .bind = lwmi_cd_component_bind ,
76156};
77157
78- /**
79- * lwmi_cd01_get_data - Get the data of the specified attribute
158+ /*
159+ * lwmi_cd*_get_data - Get the data of the specified attribute
80160 * @list: The lenovo-wmi-capdata pointer to its cd_list struct.
81161 * @attribute_id: The capdata attribute ID to be found.
82- * @output: Pointer to a capdata01 struct to return the data.
162+ * @output: Pointer to a capdata* struct to return the data.
83163 *
84- * Retrieves the capability data 01 struct pointer for the given
85- * attribute for its specified thermal mode .
164+ * Retrieves the capability data struct pointer for the given
165+ * attribute.
86166 *
87167 * Return: 0 on success, or -EINVAL.
88168 */
89- int lwmi_cd01_get_data (struct cd_list * list , u32 attribute_id , struct capdata01 * output )
90- {
91- u8 idx ;
92-
93- guard (mutex )(& list -> list_mutex );
94- for (idx = 0 ; idx < list -> count ; idx ++ ) {
95- if (list -> data [idx ].id != attribute_id )
96- continue ;
97- memcpy (output , & list -> data [idx ], sizeof (list -> data [idx ]));
98- return 0 ;
169+ #define DEF_LWMI_CDXX_GET_DATA (_cdxx , _cd_type , _output_t ) \
170+ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
171+ { \
172+ u8 idx; \
173+ \
174+ if (WARN_ON(list->type != _cd_type)) \
175+ return -EINVAL; \
176+ \
177+ guard(mutex)(&list->list_mutex); \
178+ for (idx = 0; idx < list->count; idx++) { \
179+ if (list->_cdxx[idx].id != attribute_id) \
180+ continue; \
181+ memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
182+ return 0; \
183+ } \
184+ return -EINVAL; \
99185 }
100186
101- return - EINVAL ;
102- }
187+ DEF_LWMI_CDXX_GET_DATA (cd01 , LENOVO_CAPABILITY_DATA_01 , struct capdata01 );
103188EXPORT_SYMBOL_NS_GPL (lwmi_cd01_get_data , "LENOVO_WMI_CAPDATA" );
104189
105190/**
@@ -112,22 +197,32 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
112197 */
113198static int lwmi_cd_cache (struct lwmi_cd_priv * priv )
114199{
200+ size_t size ;
115201 int idx ;
202+ void * p ;
203+
204+ switch (priv -> list -> type ) {
205+ case LENOVO_CAPABILITY_DATA_01 :
206+ p = & priv -> list -> cd01 [0 ];
207+ size = sizeof (priv -> list -> cd01 [0 ]);
208+ break ;
209+ default :
210+ return - EINVAL ;
211+ }
116212
117213 guard (mutex )(& priv -> list -> list_mutex );
118- for (idx = 0 ; idx < priv -> list -> count ; idx ++ ) {
214+ for (idx = 0 ; idx < priv -> list -> count ; idx ++ , p += size ) {
119215 union acpi_object * ret_obj __free (kfree ) = NULL ;
120216
121217 ret_obj = wmidev_block_query (priv -> wdev , idx );
122218 if (!ret_obj )
123219 return - ENODEV ;
124220
125221 if (ret_obj -> type != ACPI_TYPE_BUFFER ||
126- ret_obj -> buffer .length < sizeof ( priv -> list -> data [ idx ]) )
222+ ret_obj -> buffer .length < size )
127223 continue ;
128224
129- memcpy (& priv -> list -> data [idx ], ret_obj -> buffer .pointer ,
130- ret_obj -> buffer .length );
225+ memcpy (p , ret_obj -> buffer .pointer , size );
131226 }
132227
133228 return 0 ;
@@ -136,20 +231,28 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
136231/**
137232 * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
138233 * @priv: lenovo-wmi-capdata driver data.
234+ * @type: The type of capability data.
139235 *
140236 * Allocate a cd_list struct large enough to contain data from all WMI data
141237 * blocks provided by the interface.
142238 *
143239 * Return: 0 on success, or an error.
144240 */
145- static int lwmi_cd_alloc (struct lwmi_cd_priv * priv )
241+ static int lwmi_cd_alloc (struct lwmi_cd_priv * priv , enum lwmi_cd_type type )
146242{
147243 struct cd_list * list ;
148244 size_t list_size ;
149245 int count , ret ;
150246
151247 count = wmidev_instance_count (priv -> wdev );
152- list_size = struct_size (list , data , count );
248+
249+ switch (type ) {
250+ case LENOVO_CAPABILITY_DATA_01 :
251+ list_size = struct_size (list , cd01 , count );
252+ break ;
253+ default :
254+ return - EINVAL ;
255+ }
153256
154257 list = devm_kzalloc (& priv -> wdev -> dev , list_size , GFP_KERNEL );
155258 if (!list )
@@ -159,6 +262,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
159262 if (ret )
160263 return ret ;
161264
265+ list -> type = type ;
162266 list -> count = count ;
163267 priv -> list = list ;
164268
@@ -168,18 +272,19 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv)
168272/**
169273 * lwmi_cd_setup() - Cache all WMI data block information
170274 * @priv: lenovo-wmi-capdata driver data.
275+ * @type: The type of capability data.
171276 *
172277 * Allocate a cd_list struct large enough to contain data from all WMI data
173278 * blocks provided by the interface. Then loop through each data block and
174279 * cache the data.
175280 *
176281 * Return: 0 on success, or an error code.
177282 */
178- static int lwmi_cd_setup (struct lwmi_cd_priv * priv )
283+ static int lwmi_cd_setup (struct lwmi_cd_priv * priv , enum lwmi_cd_type type )
179284{
180285 int ret ;
181286
182- ret = lwmi_cd_alloc (priv );
287+ ret = lwmi_cd_alloc (priv , type );
183288 if (ret )
184289 return ret ;
185290
@@ -235,40 +340,72 @@ static void lwmi_cd01_unregister(void *data)
235340
236341static int lwmi_cd_probe (struct wmi_device * wdev , const void * context )
237342{
343+ const struct lwmi_cd_info * info = context ;
238344 struct lwmi_cd_priv * priv ;
239345 int ret ;
240346
347+ if (!info )
348+ return - EINVAL ;
349+
241350 priv = devm_kzalloc (& wdev -> dev , sizeof (* priv ), GFP_KERNEL );
242351 if (!priv )
243352 return - ENOMEM ;
244353
245354 priv -> wdev = wdev ;
246355 dev_set_drvdata (& wdev -> dev , priv );
247356
248- ret = lwmi_cd_setup (priv );
357+ ret = lwmi_cd_setup (priv , info -> type );
249358 if (ret )
250- return ret ;
359+ goto out ;
251360
252- priv -> acpi_nb .notifier_call = lwmi_cd01_notifier_call ;
361+ switch (info -> type ) {
362+ case LENOVO_CAPABILITY_DATA_01 :
363+ priv -> acpi_nb .notifier_call = lwmi_cd01_notifier_call ;
253364
254- ret = register_acpi_notifier (& priv -> acpi_nb );
255- if (ret )
256- return ret ;
365+ ret = register_acpi_notifier (& priv -> acpi_nb );
366+ if (ret )
367+ goto out ;
257368
258- ret = devm_add_action_or_reset (& wdev -> dev , lwmi_cd01_unregister , & priv -> acpi_nb );
259- if (ret )
260- return ret ;
369+ ret = devm_add_action_or_reset (& wdev -> dev , lwmi_cd01_unregister ,
370+ & priv -> acpi_nb );
371+ if (ret )
372+ goto out ;
261373
262- return component_add (& wdev -> dev , & lwmi_cd_component_ops );
374+ ret = component_add (& wdev -> dev , & lwmi_cd_component_ops );
375+ goto out ;
376+ default :
377+ return - EINVAL ;
378+ }
379+ out :
380+ if (ret ) {
381+ dev_err (& wdev -> dev , "failed to register %s: %d\n" ,
382+ info -> name , ret );
383+ } else {
384+ dev_dbg (& wdev -> dev , "registered %s with %u items\n" ,
385+ info -> name , priv -> list -> count );
386+ }
387+ return ret ;
263388}
264389
265390static void lwmi_cd_remove (struct wmi_device * wdev )
266391{
267- component_del (& wdev -> dev , & lwmi_cd_component_ops );
392+ struct lwmi_cd_priv * priv = dev_get_drvdata (& wdev -> dev );
393+
394+ switch (priv -> list -> type ) {
395+ case LENOVO_CAPABILITY_DATA_01 :
396+ component_del (& wdev -> dev , & lwmi_cd_component_ops );
397+ break ;
398+ default :
399+ WARN_ON (1 );
400+ }
268401}
269402
403+ #define LWMI_CD_WDEV_ID (_type ) \
404+ .guid_string = _type##_GUID, \
405+ .context = &lwmi_cd_table[_type],
406+
270407static const struct wmi_device_id lwmi_cd_id_table [] = {
271- { LENOVO_CAPABILITY_DATA_01_GUID , NULL },
408+ { LWMI_CD_WDEV_ID ( LENOVO_CAPABILITY_DATA_01 ) },
272409 {}
273410};
274411
@@ -283,22 +420,10 @@ static struct wmi_driver lwmi_cd_driver = {
283420 .no_singleton = true,
284421};
285422
286- /**
287- * lwmi_cd01_match() - Match rule for the master driver.
288- * @dev: Pointer to the capability data 01 parent device.
289- * @data: Unused void pointer for passing match criteria.
290- *
291- * Return: int.
292- */
293- int lwmi_cd01_match (struct device * dev , void * data )
294- {
295- return dev -> driver == & lwmi_cd_driver .driver ;
296- }
297- EXPORT_SYMBOL_NS_GPL (lwmi_cd01_match , "LENOVO_WMI_CAPDATA" );
298-
299423module_wmi_driver (lwmi_cd_driver );
300424
301425MODULE_DEVICE_TABLE (wmi , lwmi_cd_id_table );
302426MODULE_AUTHOR ("Derek J. Clark <derekjohn.clark@gmail.com>" );
427+ MODULE_AUTHOR ("Rong Zhang <i@rong.moe>" );
303428MODULE_DESCRIPTION ("Lenovo Capability Data WMI Driver" );
304429MODULE_LICENSE ("GPL" );
0 commit comments