xref: /openbmc/linux/drivers/platform/x86/wmi.c (revision 6e33e81b)
11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b4f9fe12SLen Brown /*
3b4f9fe12SLen Brown  *  ACPI-WMI mapping driver
4b4f9fe12SLen Brown  *
5b4f9fe12SLen Brown  *  Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
6b4f9fe12SLen Brown  *
7b4f9fe12SLen Brown  *  GUID parsing code from ldm.c is:
8b4f9fe12SLen Brown  *   Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org>
9b4f9fe12SLen Brown  *   Copyright (c) 2001-2007 Anton Altaparmakov
10b4f9fe12SLen Brown  *   Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com>
11b4f9fe12SLen Brown  *
122c9c5664SDarren Hart (VMware)  *  WMI bus infrastructure by Andrew Lutomirski and Darren Hart:
132c9c5664SDarren Hart (VMware)  *    Copyright (C) 2015 Andrew Lutomirski
142c9c5664SDarren Hart (VMware)  *    Copyright (C) 2017 VMware, Inc. All Rights Reserved.
15b4f9fe12SLen Brown  */
16b4f9fe12SLen Brown 
178e07514dSDmitry Torokhov #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
188e07514dSDmitry Torokhov 
19b4f9fe12SLen Brown #include <linux/acpi.h>
201c95ace7SBarnabás Pőcze #include <linux/bits.h>
21dea878d8SBarnabás Pőcze #include <linux/build_bug.h>
22b60ee4e0SMario Limonciello #include <linux/device.h>
23b60ee4e0SMario Limonciello #include <linux/init.h>
24b60ee4e0SMario Limonciello #include <linux/kernel.h>
25b60ee4e0SMario Limonciello #include <linux/list.h>
2644b6b766SMario Limonciello #include <linux/miscdevice.h>
277c52d551SPaul Gortmaker #include <linux/module.h>
289599ed91SAndy Lutomirski #include <linux/platform_device.h>
29b60ee4e0SMario Limonciello #include <linux/slab.h>
306133913aSBarnabás Pőcze #include <linux/sysfs.h>
31b60ee4e0SMario Limonciello #include <linux/types.h>
3244b6b766SMario Limonciello #include <linux/uaccess.h>
33538d7eb8SAndy Shevchenko #include <linux/uuid.h>
34b60ee4e0SMario Limonciello #include <linux/wmi.h>
35df23e2beSPeter Zijlstra #include <linux/fs.h>
3644b6b766SMario Limonciello #include <uapi/linux/wmi.h>
37b4f9fe12SLen Brown 
38b4f9fe12SLen Brown MODULE_AUTHOR("Carlos Corbacho");
39b4f9fe12SLen Brown MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
40b4f9fe12SLen Brown MODULE_LICENSE("GPL");
41b4f9fe12SLen Brown 
42762e1a2fSDmitry Torokhov static LIST_HEAD(wmi_block_list);
43b4f9fe12SLen Brown 
44b4f9fe12SLen Brown struct guid_block {
4567f472fdSBarnabás Pőcze 	guid_t guid;
46b4f9fe12SLen Brown 	union {
47b4f9fe12SLen Brown 		char object_id[2];
48b4f9fe12SLen Brown 		struct {
49b4f9fe12SLen Brown 			unsigned char notify_id;
50b4f9fe12SLen Brown 			unsigned char reserved;
51b4f9fe12SLen Brown 		};
52b4f9fe12SLen Brown 	};
53b4f9fe12SLen Brown 	u8 instance_count;
54b4f9fe12SLen Brown 	u8 flags;
55dea878d8SBarnabás Pőcze } __packed;
56dea878d8SBarnabás Pőcze static_assert(sizeof(typeof_member(struct guid_block, guid)) == 16);
57dea878d8SBarnabás Pőcze static_assert(sizeof(struct guid_block) == 20);
58dea878d8SBarnabás Pőcze static_assert(__alignof__(struct guid_block) == 1);
59b4f9fe12SLen Brown 
60a90b38c5SHans de Goede enum {	/* wmi_block flags */
61a90b38c5SHans de Goede 	WMI_READ_TAKES_NO_ARGS,
6299188786SHans de Goede 	WMI_PROBED,
63a90b38c5SHans de Goede };
64a90b38c5SHans de Goede 
65b4f9fe12SLen Brown struct wmi_block {
66844af950SAndy Lutomirski 	struct wmi_device dev;
67b4f9fe12SLen Brown 	struct list_head list;
68b4f9fe12SLen Brown 	struct guid_block gblock;
6944b6b766SMario Limonciello 	struct miscdevice char_dev;
7044b6b766SMario Limonciello 	struct mutex char_mutex;
71b0e86302SAndy Lutomirski 	struct acpi_device *acpi_device;
72b4f9fe12SLen Brown 	wmi_notify_handler handler;
73b4f9fe12SLen Brown 	void *handler_data;
7444b6b766SMario Limonciello 	u64 req_buf_size;
75a90b38c5SHans de Goede 	unsigned long flags;
76b4f9fe12SLen Brown };
77b4f9fe12SLen Brown 
78b4f9fe12SLen Brown 
79b4f9fe12SLen Brown /*
80b4f9fe12SLen Brown  * If the GUID data block is marked as expensive, we must enable and
81b4f9fe12SLen Brown  * explicitily disable data collection.
82b4f9fe12SLen Brown  */
831c95ace7SBarnabás Pőcze #define ACPI_WMI_EXPENSIVE   BIT(0)
841c95ace7SBarnabás Pőcze #define ACPI_WMI_METHOD      BIT(1)	/* GUID is a method */
851c95ace7SBarnabás Pőcze #define ACPI_WMI_STRING      BIT(2)	/* GUID takes & returns a string */
861c95ace7SBarnabás Pőcze #define ACPI_WMI_EVENT       BIT(3)	/* GUID is an event */
87b4f9fe12SLen Brown 
8890ab5ee9SRusty Russell static bool debug_event;
89fc3155b2SThomas Renninger module_param(debug_event, bool, 0444);
90fc3155b2SThomas Renninger MODULE_PARM_DESC(debug_event,
91fc3155b2SThomas Renninger 		 "Log WMI Events [0/1]");
92fc3155b2SThomas Renninger 
9390ab5ee9SRusty Russell static bool debug_dump_wdg;
94a929aae0SThomas Renninger module_param(debug_dump_wdg, bool, 0444);
95a929aae0SThomas Renninger MODULE_PARM_DESC(debug_dump_wdg,
96a929aae0SThomas Renninger 		 "Dump available WMI interfaces [0/1]");
97a929aae0SThomas Renninger 
98b4f9fe12SLen Brown static const struct acpi_device_id wmi_device_ids[] = {
99b4f9fe12SLen Brown 	{"PNP0C14", 0},
100b4f9fe12SLen Brown 	{"pnp0c14", 0},
1019bf9ca95SBarnabás Pőcze 	{ }
102b4f9fe12SLen Brown };
103b4f9fe12SLen Brown MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
104b4f9fe12SLen Brown 
105134038b0SMario Limonciello /* allow duplicate GUIDs as these device drivers use struct wmi_driver */
106134038b0SMario Limonciello static const char * const allow_duplicates[] = {
107134038b0SMario Limonciello 	"05901221-D566-11D1-B2F0-00A0C9062910",	/* wmi-bmof */
108a77272c1SArmin Wolf 	"8A42EA14-4F2A-FD45-6422-0087F7A7E608",	/* dell-wmi-ddv */
109134038b0SMario Limonciello 	NULL
110134038b0SMario Limonciello };
111134038b0SMario Limonciello 
112b4f9fe12SLen Brown /*
113b4f9fe12SLen Brown  * GUID parsing functions
114b4f9fe12SLen Brown  */
115b4f9fe12SLen Brown 
find_guid(const char * guid_string,struct wmi_block ** out)116b0179b80SBarnabás Pőcze static acpi_status find_guid(const char *guid_string, struct wmi_block **out)
117b4f9fe12SLen Brown {
118f9dffc14SAndy Shevchenko 	guid_t guid_input;
119b4f9fe12SLen Brown 	struct wmi_block *wblock;
120b4f9fe12SLen Brown 
121b0179b80SBarnabás Pőcze 	if (!guid_string)
122b0179b80SBarnabás Pőcze 		return AE_BAD_PARAMETER;
123b0179b80SBarnabás Pőcze 
124f9dffc14SAndy Shevchenko 	if (guid_parse(guid_string, &guid_input))
125b0179b80SBarnabás Pőcze 		return AE_BAD_PARAMETER;
126b4f9fe12SLen Brown 
127cedb3b2aSAndy Shevchenko 	list_for_each_entry(wblock, &wmi_block_list, list) {
1281ce69d2bSBarnabás Pőcze 		if (guid_equal(&wblock->gblock.guid, &guid_input)) {
129b4f9fe12SLen Brown 			if (out)
130b4f9fe12SLen Brown 				*out = wblock;
131b0179b80SBarnabás Pőcze 
132b0179b80SBarnabás Pőcze 			return AE_OK;
133b4f9fe12SLen Brown 		}
134b4f9fe12SLen Brown 	}
135b0179b80SBarnabás Pőcze 
136b0179b80SBarnabás Pőcze 	return AE_NOT_FOUND;
137b4f9fe12SLen Brown }
138b4f9fe12SLen Brown 
guid_parse_and_compare(const char * string,const guid_t * guid)139028e6e20SAndy Shevchenko static bool guid_parse_and_compare(const char *string, const guid_t *guid)
140028e6e20SAndy Shevchenko {
141028e6e20SAndy Shevchenko 	guid_t guid_input;
142028e6e20SAndy Shevchenko 
143028e6e20SAndy Shevchenko 	if (guid_parse(string, &guid_input))
144028e6e20SAndy Shevchenko 		return false;
145028e6e20SAndy Shevchenko 
146028e6e20SAndy Shevchenko 	return guid_equal(&guid_input, guid);
147028e6e20SAndy Shevchenko }
148028e6e20SAndy Shevchenko 
find_guid_context(struct wmi_block * wblock,struct wmi_driver * wdriver)149a48e2338SMattias Jacobsson static const void *find_guid_context(struct wmi_block *wblock,
150a48e2338SMattias Jacobsson 				     struct wmi_driver *wdriver)
151a48e2338SMattias Jacobsson {
152a48e2338SMattias Jacobsson 	const struct wmi_device_id *id;
153a48e2338SMattias Jacobsson 
1546e0bc588SBarnabás Pőcze 	id = wdriver->id_table;
1556e0bc588SBarnabás Pőcze 	if (!id)
156a48e2338SMattias Jacobsson 		return NULL;
157a48e2338SMattias Jacobsson 
158a48e2338SMattias Jacobsson 	while (*id->guid_string) {
159028e6e20SAndy Shevchenko 		if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid))
160a48e2338SMattias Jacobsson 			return id->context;
161a48e2338SMattias Jacobsson 		id++;
162a48e2338SMattias Jacobsson 	}
163a48e2338SMattias Jacobsson 	return NULL;
164a48e2338SMattias Jacobsson }
165a48e2338SMattias Jacobsson 
get_subobj_info(acpi_handle handle,const char * pathname,struct acpi_device_info ** info)166d4fc91adSAndy Lutomirski static int get_subobj_info(acpi_handle handle, const char *pathname,
167d4fc91adSAndy Lutomirski 			   struct acpi_device_info **info)
168d4fc91adSAndy Lutomirski {
169d4fc91adSAndy Lutomirski 	struct acpi_device_info *dummy_info, **info_ptr;
170d4fc91adSAndy Lutomirski 	acpi_handle subobj_handle;
171d4fc91adSAndy Lutomirski 	acpi_status status;
172d4fc91adSAndy Lutomirski 
173d4fc91adSAndy Lutomirski 	status = acpi_get_handle(handle, (char *)pathname, &subobj_handle);
174d4fc91adSAndy Lutomirski 	if (status == AE_NOT_FOUND)
175d4fc91adSAndy Lutomirski 		return -ENOENT;
176d4fc91adSAndy Lutomirski 	else if (ACPI_FAILURE(status))
177d4fc91adSAndy Lutomirski 		return -EIO;
178d4fc91adSAndy Lutomirski 
179d4fc91adSAndy Lutomirski 	info_ptr = info ? info : &dummy_info;
180d4fc91adSAndy Lutomirski 	status = acpi_get_object_info(subobj_handle, info_ptr);
181d4fc91adSAndy Lutomirski 	if (ACPI_FAILURE(status))
182d4fc91adSAndy Lutomirski 		return -EIO;
183d4fc91adSAndy Lutomirski 
184d4fc91adSAndy Lutomirski 	if (!info)
185d4fc91adSAndy Lutomirski 		kfree(dummy_info);
186d4fc91adSAndy Lutomirski 
187d4fc91adSAndy Lutomirski 	return 0;
188d4fc91adSAndy Lutomirski }
189d4fc91adSAndy Lutomirski 
wmi_method_enable(struct wmi_block * wblock,bool enable)190285dd01aSBarnabás Pőcze static acpi_status wmi_method_enable(struct wmi_block *wblock, bool enable)
191b4f9fe12SLen Brown {
19243aacf83SBarnabás Pőcze 	struct guid_block *block;
193b4f9fe12SLen Brown 	char method[5];
194b4f9fe12SLen Brown 	acpi_status status;
195b4f9fe12SLen Brown 	acpi_handle handle;
196b4f9fe12SLen Brown 
197b4f9fe12SLen Brown 	block = &wblock->gblock;
198b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
199b4f9fe12SLen Brown 
200b4f9fe12SLen Brown 	snprintf(method, 5, "WE%02X", block->notify_id);
2018122ab66SZhang Rui 	status = acpi_execute_simple_method(handle, method, enable);
202736b48aaSBarnabás Pőcze 	if (status == AE_NOT_FOUND)
203b4f9fe12SLen Brown 		return AE_OK;
204736b48aaSBarnabás Pőcze 
205736b48aaSBarnabás Pőcze 	return status;
206b4f9fe12SLen Brown }
207b4f9fe12SLen Brown 
20857f2ce89SBarnabás Pőcze #define WMI_ACPI_METHOD_NAME_SIZE 5
20957f2ce89SBarnabás Pőcze 
get_acpi_method_name(const struct wmi_block * wblock,const char method,char buffer[static WMI_ACPI_METHOD_NAME_SIZE])21057f2ce89SBarnabás Pőcze static inline void get_acpi_method_name(const struct wmi_block *wblock,
21157f2ce89SBarnabás Pőcze 					const char method,
21257f2ce89SBarnabás Pőcze 					char buffer[static WMI_ACPI_METHOD_NAME_SIZE])
21357f2ce89SBarnabás Pőcze {
21457f2ce89SBarnabás Pőcze 	static_assert(ARRAY_SIZE(wblock->gblock.object_id) == 2);
21557f2ce89SBarnabás Pőcze 	static_assert(WMI_ACPI_METHOD_NAME_SIZE >= 5);
21657f2ce89SBarnabás Pőcze 
21757f2ce89SBarnabás Pőcze 	buffer[0] = 'W';
21857f2ce89SBarnabás Pőcze 	buffer[1] = method;
21957f2ce89SBarnabás Pőcze 	buffer[2] = wblock->gblock.object_id[0];
22057f2ce89SBarnabás Pőcze 	buffer[3] = wblock->gblock.object_id[1];
22157f2ce89SBarnabás Pőcze 	buffer[4] = '\0';
22257f2ce89SBarnabás Pőcze }
22357f2ce89SBarnabás Pőcze 
get_param_acpi_type(const struct wmi_block * wblock)22451142a08SBarnabás Pőcze static inline acpi_object_type get_param_acpi_type(const struct wmi_block *wblock)
22551142a08SBarnabás Pőcze {
22651142a08SBarnabás Pőcze 	if (wblock->gblock.flags & ACPI_WMI_STRING)
22751142a08SBarnabás Pőcze 		return ACPI_TYPE_STRING;
22851142a08SBarnabás Pőcze 	else
22951142a08SBarnabás Pőcze 		return ACPI_TYPE_BUFFER;
23051142a08SBarnabás Pőcze }
23151142a08SBarnabás Pőcze 
get_event_data(const struct wmi_block * wblock,struct acpi_buffer * out)23225be44f6SBarnabás Pőcze static acpi_status get_event_data(const struct wmi_block *wblock, struct acpi_buffer *out)
23325be44f6SBarnabás Pőcze {
23425be44f6SBarnabás Pőcze 	union acpi_object param = {
23525be44f6SBarnabás Pőcze 		.integer = {
23625be44f6SBarnabás Pőcze 			.type = ACPI_TYPE_INTEGER,
23725be44f6SBarnabás Pőcze 			.value = wblock->gblock.notify_id,
23825be44f6SBarnabás Pőcze 		}
23925be44f6SBarnabás Pőcze 	};
24025be44f6SBarnabás Pőcze 	struct acpi_object_list input = {
24125be44f6SBarnabás Pőcze 		.count = 1,
24225be44f6SBarnabás Pőcze 		.pointer = &param,
24325be44f6SBarnabás Pőcze 	};
24425be44f6SBarnabás Pőcze 
24525be44f6SBarnabás Pőcze 	return acpi_evaluate_object(wblock->acpi_device->handle, "_WED", &input, out);
24625be44f6SBarnabás Pőcze }
24725be44f6SBarnabás Pőcze 
248b4f9fe12SLen Brown /*
249b4f9fe12SLen Brown  * Exported WMI functions
250b4f9fe12SLen Brown  */
25144b6b766SMario Limonciello 
25244b6b766SMario Limonciello /**
25344b6b766SMario Limonciello  * set_required_buffer_size - Sets the buffer size needed for performing IOCTL
25444b6b766SMario Limonciello  * @wdev: A wmi bus device from a driver
2555a707af1SAndy Shevchenko  * @length: Required buffer size
25644b6b766SMario Limonciello  *
257b4cc9795SArmin Wolf  * Allocates memory needed for buffer, stores the buffer size in that memory.
258b4cc9795SArmin Wolf  *
259b4cc9795SArmin Wolf  * Return: 0 on success or a negative error code for failure.
26044b6b766SMario Limonciello  */
set_required_buffer_size(struct wmi_device * wdev,u64 length)26144b6b766SMario Limonciello int set_required_buffer_size(struct wmi_device *wdev, u64 length)
26244b6b766SMario Limonciello {
26344b6b766SMario Limonciello 	struct wmi_block *wblock;
26444b6b766SMario Limonciello 
26544b6b766SMario Limonciello 	wblock = container_of(wdev, struct wmi_block, dev);
26644b6b766SMario Limonciello 	wblock->req_buf_size = length;
26744b6b766SMario Limonciello 
26844b6b766SMario Limonciello 	return 0;
26944b6b766SMario Limonciello }
27044b6b766SMario Limonciello EXPORT_SYMBOL_GPL(set_required_buffer_size);
27144b6b766SMario Limonciello 
272b4f9fe12SLen Brown /**
2732a2b13aeSArmin Wolf  * wmi_instance_count - Get number of WMI object instances
2742a2b13aeSArmin Wolf  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
2752a2b13aeSArmin Wolf  *
2762a2b13aeSArmin Wolf  * Get the number of WMI object instances.
2772a2b13aeSArmin Wolf  *
2782a2b13aeSArmin Wolf  * Returns: Number of WMI object instances or negative error code.
2792a2b13aeSArmin Wolf  */
wmi_instance_count(const char * guid_string)2802a2b13aeSArmin Wolf int wmi_instance_count(const char *guid_string)
2812a2b13aeSArmin Wolf {
2822a2b13aeSArmin Wolf 	struct wmi_block *wblock;
2832a2b13aeSArmin Wolf 	acpi_status status;
2842a2b13aeSArmin Wolf 
2852a2b13aeSArmin Wolf 	status = find_guid(guid_string, &wblock);
2862a2b13aeSArmin Wolf 	if (ACPI_FAILURE(status)) {
2872a2b13aeSArmin Wolf 		if (status == AE_BAD_PARAMETER)
2882a2b13aeSArmin Wolf 			return -EINVAL;
2892a2b13aeSArmin Wolf 
2902a2b13aeSArmin Wolf 		return -ENODEV;
2912a2b13aeSArmin Wolf 	}
2922a2b13aeSArmin Wolf 
2932a2b13aeSArmin Wolf 	return wmidev_instance_count(&wblock->dev);
2942a2b13aeSArmin Wolf }
2952a2b13aeSArmin Wolf EXPORT_SYMBOL_GPL(wmi_instance_count);
2962a2b13aeSArmin Wolf 
2972a2b13aeSArmin Wolf /**
2982a2b13aeSArmin Wolf  * wmidev_instance_count - Get number of WMI object instances
2992a2b13aeSArmin Wolf  * @wdev: A wmi bus device from a driver
3002a2b13aeSArmin Wolf  *
3012a2b13aeSArmin Wolf  * Get the number of WMI object instances.
3022a2b13aeSArmin Wolf  *
3032a2b13aeSArmin Wolf  * Returns: Number of WMI object instances.
3042a2b13aeSArmin Wolf  */
wmidev_instance_count(struct wmi_device * wdev)3052a2b13aeSArmin Wolf u8 wmidev_instance_count(struct wmi_device *wdev)
3062a2b13aeSArmin Wolf {
3072a2b13aeSArmin Wolf 	struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
3082a2b13aeSArmin Wolf 
3092a2b13aeSArmin Wolf 	return wblock->gblock.instance_count;
3102a2b13aeSArmin Wolf }
3112a2b13aeSArmin Wolf EXPORT_SYMBOL_GPL(wmidev_instance_count);
3122a2b13aeSArmin Wolf 
3132a2b13aeSArmin Wolf /**
314d54bd4bcSArmin Wolf  * wmi_evaluate_method - Evaluate a WMI method (deprecated)
315b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
316b4f9fe12SLen Brown  * @instance: Instance index
317b4f9fe12SLen Brown  * @method_id: Method ID to call
3185a707af1SAndy Shevchenko  * @in: Buffer containing input for the method call
3195a707af1SAndy Shevchenko  * @out: Empty buffer to return the method results
320b4f9fe12SLen Brown  *
321b4cc9795SArmin Wolf  * Call an ACPI-WMI method, the caller must free @out.
322b4cc9795SArmin Wolf  *
323b4cc9795SArmin Wolf  * Return: acpi_status signaling success or error.
324b4f9fe12SLen Brown  */
wmi_evaluate_method(const char * guid_string,u8 instance,u32 method_id,const struct acpi_buffer * in,struct acpi_buffer * out)325bba08f35SBarnabás Pőcze acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method_id,
326bba08f35SBarnabás Pőcze 				const struct acpi_buffer *in, struct acpi_buffer *out)
327b4f9fe12SLen Brown {
328722c856dSMario Limonciello 	struct wmi_block *wblock = NULL;
329b0179b80SBarnabás Pőcze 	acpi_status status;
330722c856dSMario Limonciello 
331b0179b80SBarnabás Pőcze 	status = find_guid(guid_string, &wblock);
332b0179b80SBarnabás Pőcze 	if (ACPI_FAILURE(status))
333b0179b80SBarnabás Pőcze 		return status;
334b0179b80SBarnabás Pőcze 
335722c856dSMario Limonciello 	return wmidev_evaluate_method(&wblock->dev, instance, method_id,
336722c856dSMario Limonciello 				      in, out);
337722c856dSMario Limonciello }
338722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmi_evaluate_method);
339722c856dSMario Limonciello 
340722c856dSMario Limonciello /**
341722c856dSMario Limonciello  * wmidev_evaluate_method - Evaluate a WMI method
342722c856dSMario Limonciello  * @wdev: A wmi bus device from a driver
343722c856dSMario Limonciello  * @instance: Instance index
344722c856dSMario Limonciello  * @method_id: Method ID to call
3455a707af1SAndy Shevchenko  * @in: Buffer containing input for the method call
3465a707af1SAndy Shevchenko  * @out: Empty buffer to return the method results
347722c856dSMario Limonciello  *
348b4cc9795SArmin Wolf  * Call an ACPI-WMI method, the caller must free @out.
349b4cc9795SArmin Wolf  *
350b4cc9795SArmin Wolf  * Return: acpi_status signaling success or error.
351722c856dSMario Limonciello  */
wmidev_evaluate_method(struct wmi_device * wdev,u8 instance,u32 method_id,const struct acpi_buffer * in,struct acpi_buffer * out)352bba08f35SBarnabás Pőcze acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id,
353bba08f35SBarnabás Pőcze 				   const struct acpi_buffer *in, struct acpi_buffer *out)
354722c856dSMario Limonciello {
35543aacf83SBarnabás Pőcze 	struct guid_block *block;
35643aacf83SBarnabás Pőcze 	struct wmi_block *wblock;
357b4f9fe12SLen Brown 	acpi_handle handle;
358b4f9fe12SLen Brown 	struct acpi_object_list input;
359b4f9fe12SLen Brown 	union acpi_object params[3];
36057f2ce89SBarnabás Pőcze 	char method[WMI_ACPI_METHOD_NAME_SIZE];
361b4f9fe12SLen Brown 
362722c856dSMario Limonciello 	wblock = container_of(wdev, struct wmi_block, dev);
363b4f9fe12SLen Brown 	block = &wblock->gblock;
364b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
365b4f9fe12SLen Brown 
366b4f9fe12SLen Brown 	if (!(block->flags & ACPI_WMI_METHOD))
367b4f9fe12SLen Brown 		return AE_BAD_DATA;
368b4f9fe12SLen Brown 
3696afa1e2aSPali Rohár 	if (block->instance_count <= instance)
370b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
371b4f9fe12SLen Brown 
372b4f9fe12SLen Brown 	input.count = 2;
373b4f9fe12SLen Brown 	input.pointer = params;
374b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
375b4f9fe12SLen Brown 	params[0].integer.value = instance;
376b4f9fe12SLen Brown 	params[1].type = ACPI_TYPE_INTEGER;
377b4f9fe12SLen Brown 	params[1].integer.value = method_id;
378b4f9fe12SLen Brown 
379b4f9fe12SLen Brown 	if (in) {
380b4f9fe12SLen Brown 		input.count = 3;
381b4f9fe12SLen Brown 
38251142a08SBarnabás Pőcze 		params[2].type = get_param_acpi_type(wblock);
383b4f9fe12SLen Brown 		params[2].buffer.length = in->length;
384b4f9fe12SLen Brown 		params[2].buffer.pointer = in->pointer;
385b4f9fe12SLen Brown 	}
386b4f9fe12SLen Brown 
38757f2ce89SBarnabás Pőcze 	get_acpi_method_name(wblock, 'M', method);
388b4f9fe12SLen Brown 
38921397cacSBarnabás Pőcze 	return acpi_evaluate_object(handle, method, &input, out);
390b4f9fe12SLen Brown }
391722c856dSMario Limonciello EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
392b4f9fe12SLen Brown 
__query_block(struct wmi_block * wblock,u8 instance,struct acpi_buffer * out)39356a37025SAndy Lutomirski static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
394b4f9fe12SLen Brown 				 struct acpi_buffer *out)
395b4f9fe12SLen Brown {
39643aacf83SBarnabás Pőcze 	struct guid_block *block;
39754f14c27SZhang Rui 	acpi_handle handle;
398b4f9fe12SLen Brown 	acpi_status status, wc_status = AE_ERROR;
3998122ab66SZhang Rui 	struct acpi_object_list input;
4008122ab66SZhang Rui 	union acpi_object wq_params[1];
40157f2ce89SBarnabás Pőcze 	char wc_method[WMI_ACPI_METHOD_NAME_SIZE];
40257f2ce89SBarnabás Pőcze 	char method[WMI_ACPI_METHOD_NAME_SIZE];
403b4f9fe12SLen Brown 
40456a37025SAndy Lutomirski 	if (!out)
405b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
406b4f9fe12SLen Brown 
407b4f9fe12SLen Brown 	block = &wblock->gblock;
408b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
409b4f9fe12SLen Brown 
4106afa1e2aSPali Rohár 	if (block->instance_count <= instance)
411b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
412b4f9fe12SLen Brown 
413b4f9fe12SLen Brown 	/* Check GUID is a data block */
414b4f9fe12SLen Brown 	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
415b4f9fe12SLen Brown 		return AE_ERROR;
416b4f9fe12SLen Brown 
417b4f9fe12SLen Brown 	input.count = 1;
418b4f9fe12SLen Brown 	input.pointer = wq_params;
419b4f9fe12SLen Brown 	wq_params[0].type = ACPI_TYPE_INTEGER;
420b4f9fe12SLen Brown 	wq_params[0].integer.value = instance;
421b4f9fe12SLen Brown 
422a90b38c5SHans de Goede 	if (instance == 0 && test_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags))
423d4fc91adSAndy Lutomirski 		input.count = 0;
424d4fc91adSAndy Lutomirski 
425b4f9fe12SLen Brown 	/*
426b4f9fe12SLen Brown 	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
427b4f9fe12SLen Brown 	 * enable collection.
428b4f9fe12SLen Brown 	 */
429b4f9fe12SLen Brown 	if (block->flags & ACPI_WMI_EXPENSIVE) {
43057f2ce89SBarnabás Pőcze 		get_acpi_method_name(wblock, 'C', wc_method);
431b4f9fe12SLen Brown 
432b4f9fe12SLen Brown 		/*
433b4f9fe12SLen Brown 		 * Some GUIDs break the specification by declaring themselves
434b4f9fe12SLen Brown 		 * expensive, but have no corresponding WCxx method. So we
435b4f9fe12SLen Brown 		 * should not fail if this happens.
436b4f9fe12SLen Brown 		 */
437bad9da86SKelsey Skunberg 		wc_status = acpi_execute_simple_method(handle, wc_method, 1);
438b4f9fe12SLen Brown 	}
439b4f9fe12SLen Brown 
44057f2ce89SBarnabás Pőcze 	get_acpi_method_name(wblock, 'Q', method);
441b4f9fe12SLen Brown 	status = acpi_evaluate_object(handle, method, &input, out);
442b4f9fe12SLen Brown 
443b4f9fe12SLen Brown 	/*
444b4f9fe12SLen Brown 	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
445b4f9fe12SLen Brown 	 * the WQxx method failed - we should disable collection anyway.
446b4f9fe12SLen Brown 	 */
447b4f9fe12SLen Brown 	if ((block->flags & ACPI_WMI_EXPENSIVE) && ACPI_SUCCESS(wc_status)) {
4481975718cSBarnabás Pőcze 		/*
4491975718cSBarnabás Pőcze 		 * Ignore whether this WCxx call succeeds or not since
4501975718cSBarnabás Pőcze 		 * the previously executed WQxx method call might have
4511975718cSBarnabás Pőcze 		 * succeeded, and returning the failing status code
4521975718cSBarnabás Pőcze 		 * of this call would throw away the result of the WQxx
4531975718cSBarnabás Pőcze 		 * call, potentially leaking memory.
4541975718cSBarnabás Pőcze 		 */
4551975718cSBarnabás Pőcze 		acpi_execute_simple_method(handle, wc_method, 0);
456b4f9fe12SLen Brown 	}
457b4f9fe12SLen Brown 
458b4f9fe12SLen Brown 	return status;
459b4f9fe12SLen Brown }
46056a37025SAndy Lutomirski 
46156a37025SAndy Lutomirski /**
46256a37025SAndy Lutomirski  * wmi_query_block - Return contents of a WMI block (deprecated)
46356a37025SAndy Lutomirski  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
46456a37025SAndy Lutomirski  * @instance: Instance index
4655a707af1SAndy Shevchenko  * @out: Empty buffer to return the contents of the data block to
46656a37025SAndy Lutomirski  *
467b4cc9795SArmin Wolf  * Query a ACPI-WMI block, the caller must free @out.
468b4cc9795SArmin Wolf  *
469b4cc9795SArmin Wolf  * Return: ACPI object containing the content of the WMI block.
47056a37025SAndy Lutomirski  */
wmi_query_block(const char * guid_string,u8 instance,struct acpi_buffer * out)47156a37025SAndy Lutomirski acpi_status wmi_query_block(const char *guid_string, u8 instance,
47256a37025SAndy Lutomirski 			    struct acpi_buffer *out)
47356a37025SAndy Lutomirski {
47456a37025SAndy Lutomirski 	struct wmi_block *wblock;
475b0179b80SBarnabás Pőcze 	acpi_status status;
47656a37025SAndy Lutomirski 
477b0179b80SBarnabás Pőcze 	status = find_guid(guid_string, &wblock);
478b0179b80SBarnabás Pőcze 	if (ACPI_FAILURE(status))
479b0179b80SBarnabás Pőcze 		return status;
48056a37025SAndy Lutomirski 
48156a37025SAndy Lutomirski 	return __query_block(wblock, instance, out);
48256a37025SAndy Lutomirski }
483b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_query_block);
484b4f9fe12SLen Brown 
485b4cc9795SArmin Wolf /**
486b4cc9795SArmin Wolf  * wmidev_block_query - Return contents of a WMI block
487b4cc9795SArmin Wolf  * @wdev: A wmi bus device from a driver
488b4cc9795SArmin Wolf  * @instance: Instance index
489b4cc9795SArmin Wolf  *
490b4cc9795SArmin Wolf  * Query an ACPI-WMI block, the caller must free the result.
491b4cc9795SArmin Wolf  *
492b4cc9795SArmin Wolf  * Return: ACPI object containing the content of the WMI block.
493b4cc9795SArmin Wolf  */
wmidev_block_query(struct wmi_device * wdev,u8 instance)49456a37025SAndy Lutomirski union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
49556a37025SAndy Lutomirski {
49656a37025SAndy Lutomirski 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
49756a37025SAndy Lutomirski 	struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
49856a37025SAndy Lutomirski 
49956a37025SAndy Lutomirski 	if (ACPI_FAILURE(__query_block(wblock, instance, &out)))
50056a37025SAndy Lutomirski 		return NULL;
50156a37025SAndy Lutomirski 
502c06a2fdeSBarnabás Pőcze 	return out.pointer;
50356a37025SAndy Lutomirski }
50456a37025SAndy Lutomirski EXPORT_SYMBOL_GPL(wmidev_block_query);
50556a37025SAndy Lutomirski 
506b4f9fe12SLen Brown /**
507d54bd4bcSArmin Wolf  * wmi_set_block - Write to a WMI block (deprecated)
508b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
509b4f9fe12SLen Brown  * @instance: Instance index
5105a707af1SAndy Shevchenko  * @in: Buffer containing new values for the data block
511b4f9fe12SLen Brown  *
512b4cc9795SArmin Wolf  * Write the contents of the input buffer to an ACPI-WMI data block.
513b4cc9795SArmin Wolf  *
514b4cc9795SArmin Wolf  * Return: acpi_status signaling success or error.
515b4f9fe12SLen Brown  */
wmi_set_block(const char * guid_string,u8 instance,const struct acpi_buffer * in)516b4f9fe12SLen Brown acpi_status wmi_set_block(const char *guid_string, u8 instance,
517b4f9fe12SLen Brown 			  const struct acpi_buffer *in)
518b4f9fe12SLen Brown {
519b4f9fe12SLen Brown 	struct wmi_block *wblock = NULL;
52043aacf83SBarnabás Pőcze 	struct guid_block *block;
521b4f9fe12SLen Brown 	acpi_handle handle;
522b4f9fe12SLen Brown 	struct acpi_object_list input;
523b4f9fe12SLen Brown 	union acpi_object params[2];
52457f2ce89SBarnabás Pőcze 	char method[WMI_ACPI_METHOD_NAME_SIZE];
525b0179b80SBarnabás Pőcze 	acpi_status status;
526b4f9fe12SLen Brown 
527b0179b80SBarnabás Pőcze 	if (!in)
528b4f9fe12SLen Brown 		return AE_BAD_DATA;
529b4f9fe12SLen Brown 
530b0179b80SBarnabás Pőcze 	status = find_guid(guid_string, &wblock);
531b0179b80SBarnabás Pőcze 	if (ACPI_FAILURE(status))
532b0179b80SBarnabás Pőcze 		return status;
533b4f9fe12SLen Brown 
534b4f9fe12SLen Brown 	block = &wblock->gblock;
535b0e86302SAndy Lutomirski 	handle = wblock->acpi_device->handle;
536b4f9fe12SLen Brown 
5376afa1e2aSPali Rohár 	if (block->instance_count <= instance)
538b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
539b4f9fe12SLen Brown 
540b4f9fe12SLen Brown 	/* Check GUID is a data block */
541b4f9fe12SLen Brown 	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
542b4f9fe12SLen Brown 		return AE_ERROR;
543b4f9fe12SLen Brown 
544b4f9fe12SLen Brown 	input.count = 2;
545b4f9fe12SLen Brown 	input.pointer = params;
546b4f9fe12SLen Brown 	params[0].type = ACPI_TYPE_INTEGER;
547b4f9fe12SLen Brown 	params[0].integer.value = instance;
54851142a08SBarnabás Pőcze 	params[1].type = get_param_acpi_type(wblock);
549b4f9fe12SLen Brown 	params[1].buffer.length = in->length;
550b4f9fe12SLen Brown 	params[1].buffer.pointer = in->pointer;
551b4f9fe12SLen Brown 
55257f2ce89SBarnabás Pőcze 	get_acpi_method_name(wblock, 'S', method);
553b4f9fe12SLen Brown 
554b4f9fe12SLen Brown 	return acpi_evaluate_object(handle, method, &input, NULL);
555b4f9fe12SLen Brown }
556b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_set_block);
557b4f9fe12SLen Brown 
wmi_dump_wdg(const struct guid_block * g)55837830662SDmitry Torokhov static void wmi_dump_wdg(const struct guid_block *g)
559a929aae0SThomas Renninger {
56067f472fdSBarnabás Pőcze 	pr_info("%pUL:\n", &g->guid);
561cd3921f8SPali Rohár 	if (g->flags & ACPI_WMI_EVENT)
562cd3921f8SPali Rohár 		pr_info("\tnotify_id: 0x%02X\n", g->notify_id);
563cd3921f8SPali Rohár 	else
564cd3921f8SPali Rohár 		pr_info("\tobject_id: %2pE\n", g->object_id);
5658e07514dSDmitry Torokhov 	pr_info("\tinstance_count: %d\n", g->instance_count);
5668e07514dSDmitry Torokhov 	pr_info("\tflags: %#x", g->flags);
567a929aae0SThomas Renninger 	if (g->flags) {
568a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_EXPENSIVE)
5698e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_EXPENSIVE");
570a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_METHOD)
5718e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_METHOD");
572a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_STRING)
5738e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_STRING");
574a929aae0SThomas Renninger 		if (g->flags & ACPI_WMI_EVENT)
5758e07514dSDmitry Torokhov 			pr_cont(" ACPI_WMI_EVENT");
576a929aae0SThomas Renninger 	}
5778e07514dSDmitry Torokhov 	pr_cont("\n");
578a929aae0SThomas Renninger 
579a929aae0SThomas Renninger }
580a929aae0SThomas Renninger 
wmi_notify_debug(u32 value,void * context)581fc3155b2SThomas Renninger static void wmi_notify_debug(u32 value, void *context)
582fc3155b2SThomas Renninger {
583fc3155b2SThomas Renninger 	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
584fc3155b2SThomas Renninger 	union acpi_object *obj;
5851492616aSAxel Lin 	acpi_status status;
586fc3155b2SThomas Renninger 
5871492616aSAxel Lin 	status = wmi_get_event_data(value, &response);
5881492616aSAxel Lin 	if (status != AE_OK) {
5898e07514dSDmitry Torokhov 		pr_info("bad event status 0x%x\n", status);
5901492616aSAxel Lin 		return;
5911492616aSAxel Lin 	}
592fc3155b2SThomas Renninger 
593c06a2fdeSBarnabás Pőcze 	obj = response.pointer;
594fc3155b2SThomas Renninger 	if (!obj)
595fc3155b2SThomas Renninger 		return;
596fc3155b2SThomas Renninger 
5971c23ab91SBarnabás Pőcze 	pr_info("DEBUG: event 0x%02X ", value);
598fc3155b2SThomas Renninger 	switch (obj->type) {
599fc3155b2SThomas Renninger 	case ACPI_TYPE_BUFFER:
6001c23ab91SBarnabás Pőcze 		pr_cont("BUFFER_TYPE - length %u\n", obj->buffer.length);
601fc3155b2SThomas Renninger 		break;
602fc3155b2SThomas Renninger 	case ACPI_TYPE_STRING:
6038e07514dSDmitry Torokhov 		pr_cont("STRING_TYPE - %s\n", obj->string.pointer);
604fc3155b2SThomas Renninger 		break;
605fc3155b2SThomas Renninger 	case ACPI_TYPE_INTEGER:
6068e07514dSDmitry Torokhov 		pr_cont("INTEGER_TYPE - %llu\n", obj->integer.value);
607fc3155b2SThomas Renninger 		break;
608fc3155b2SThomas Renninger 	case ACPI_TYPE_PACKAGE:
6091c23ab91SBarnabás Pőcze 		pr_cont("PACKAGE_TYPE - %u elements\n", obj->package.count);
610fc3155b2SThomas Renninger 		break;
611fc3155b2SThomas Renninger 	default:
6128e07514dSDmitry Torokhov 		pr_cont("object type 0x%X\n", obj->type);
613fc3155b2SThomas Renninger 	}
6141492616aSAxel Lin 	kfree(obj);
615fc3155b2SThomas Renninger }
616fc3155b2SThomas Renninger 
617b4f9fe12SLen Brown /**
618d54bd4bcSArmin Wolf  * wmi_install_notify_handler - Register handler for WMI events (deprecated)
6195a707af1SAndy Shevchenko  * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
620b4f9fe12SLen Brown  * @handler: Function to handle notifications
621b4f9fe12SLen Brown  * @data: Data to be returned to handler when event is fired
622b4f9fe12SLen Brown  *
623b4f9fe12SLen Brown  * Register a handler for events sent to the ACPI-WMI mapper device.
624b4cc9795SArmin Wolf  *
625b4cc9795SArmin Wolf  * Return: acpi_status signaling success or error.
626b4f9fe12SLen Brown  */
wmi_install_notify_handler(const char * guid,wmi_notify_handler handler,void * data)627b4f9fe12SLen Brown acpi_status wmi_install_notify_handler(const char *guid,
628bba08f35SBarnabás Pőcze 				       wmi_notify_handler handler,
629bba08f35SBarnabás Pőcze 				       void *data)
630b4f9fe12SLen Brown {
631b4f9fe12SLen Brown 	struct wmi_block *block;
63258f6425eSColin King 	acpi_status status = AE_NOT_EXIST;
633f9dffc14SAndy Shevchenko 	guid_t guid_input;
634b4f9fe12SLen Brown 
635b4f9fe12SLen Brown 	if (!guid || !handler)
636b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
637b4f9fe12SLen Brown 
638f9dffc14SAndy Shevchenko 	if (guid_parse(guid, &guid_input))
639538d7eb8SAndy Shevchenko 		return AE_BAD_PARAMETER;
640b4f9fe12SLen Brown 
641cedb3b2aSAndy Shevchenko 	list_for_each_entry(block, &wmi_block_list, list) {
64258f6425eSColin King 		acpi_status wmi_status;
64358f6425eSColin King 
64467f472fdSBarnabás Pőcze 		if (guid_equal(&block->gblock.guid, &guid_input)) {
64558f6425eSColin King 			if (block->handler &&
64658f6425eSColin King 			    block->handler != wmi_notify_debug)
647b4f9fe12SLen Brown 				return AE_ALREADY_ACQUIRED;
648b4f9fe12SLen Brown 
649b4f9fe12SLen Brown 			block->handler = handler;
650b4f9fe12SLen Brown 			block->handler_data = data;
651b4f9fe12SLen Brown 
652285dd01aSBarnabás Pőcze 			wmi_status = wmi_method_enable(block, true);
65358f6425eSColin King 			if ((wmi_status != AE_OK) ||
65458f6425eSColin King 			    ((wmi_status == AE_OK) && (status == AE_NOT_EXIST)))
65558f6425eSColin King 				status = wmi_status;
65658f6425eSColin King 		}
65758f6425eSColin King 	}
658b4f9fe12SLen Brown 
659b4f9fe12SLen Brown 	return status;
660b4f9fe12SLen Brown }
661b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
662b4f9fe12SLen Brown 
663b4f9fe12SLen Brown /**
664d54bd4bcSArmin Wolf  * wmi_remove_notify_handler - Unregister handler for WMI events (deprecated)
6655a707af1SAndy Shevchenko  * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
666b4f9fe12SLen Brown  *
667b4f9fe12SLen Brown  * Unregister handler for events sent to the ACPI-WMI mapper device.
668b4cc9795SArmin Wolf  *
669b4cc9795SArmin Wolf  * Return: acpi_status signaling success or error.
670b4f9fe12SLen Brown  */
wmi_remove_notify_handler(const char * guid)671b4f9fe12SLen Brown acpi_status wmi_remove_notify_handler(const char *guid)
672b4f9fe12SLen Brown {
673b4f9fe12SLen Brown 	struct wmi_block *block;
67458f6425eSColin King 	acpi_status status = AE_NOT_EXIST;
675f9dffc14SAndy Shevchenko 	guid_t guid_input;
676b4f9fe12SLen Brown 
677b4f9fe12SLen Brown 	if (!guid)
678b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
679b4f9fe12SLen Brown 
680f9dffc14SAndy Shevchenko 	if (guid_parse(guid, &guid_input))
681538d7eb8SAndy Shevchenko 		return AE_BAD_PARAMETER;
682b4f9fe12SLen Brown 
683cedb3b2aSAndy Shevchenko 	list_for_each_entry(block, &wmi_block_list, list) {
68458f6425eSColin King 		acpi_status wmi_status;
68558f6425eSColin King 
68667f472fdSBarnabás Pőcze 		if (guid_equal(&block->gblock.guid, &guid_input)) {
68758f6425eSColin King 			if (!block->handler ||
68858f6425eSColin King 			    block->handler == wmi_notify_debug)
689b4f9fe12SLen Brown 				return AE_NULL_ENTRY;
690b4f9fe12SLen Brown 
691fc3155b2SThomas Renninger 			if (debug_event) {
692fc3155b2SThomas Renninger 				block->handler = wmi_notify_debug;
69358f6425eSColin King 				status = AE_OK;
694fc3155b2SThomas Renninger 			} else {
695285dd01aSBarnabás Pőcze 				wmi_status = wmi_method_enable(block, false);
696b4f9fe12SLen Brown 				block->handler = NULL;
697b4f9fe12SLen Brown 				block->handler_data = NULL;
69858f6425eSColin King 				if ((wmi_status != AE_OK) ||
69958f6425eSColin King 				    ((wmi_status == AE_OK) &&
70058f6425eSColin King 				     (status == AE_NOT_EXIST)))
70158f6425eSColin King 					status = wmi_status;
702fc3155b2SThomas Renninger 			}
70358f6425eSColin King 		}
70458f6425eSColin King 	}
70558f6425eSColin King 
706b4f9fe12SLen Brown 	return status;
707b4f9fe12SLen Brown }
708b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
709b4f9fe12SLen Brown 
710b4f9fe12SLen Brown /**
711d54bd4bcSArmin Wolf  * wmi_get_event_data - Get WMI data associated with an event (deprecated)
712b4f9fe12SLen Brown  *
7133e9b988eSAnisse Astier  * @event: Event to find
714b4cc9795SArmin Wolf  * @out: Buffer to hold event data
715b4f9fe12SLen Brown  *
716b4cc9795SArmin Wolf  * Get extra data associated with an WMI event, the caller needs to free @out.
717b4cc9795SArmin Wolf  *
718b4cc9795SArmin Wolf  * Return: acpi_status signaling success or error.
719b4f9fe12SLen Brown  */
wmi_get_event_data(u32 event,struct acpi_buffer * out)720b4f9fe12SLen Brown acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
721b4f9fe12SLen Brown {
722b4f9fe12SLen Brown 	struct wmi_block *wblock;
723b4f9fe12SLen Brown 
724cedb3b2aSAndy Shevchenko 	list_for_each_entry(wblock, &wmi_block_list, list) {
725f5431bf1SBarnabás Pőcze 		struct guid_block *gblock = &wblock->gblock;
726b4f9fe12SLen Brown 
72725be44f6SBarnabás Pőcze 		if ((gblock->flags & ACPI_WMI_EVENT) && gblock->notify_id == event)
72825be44f6SBarnabás Pőcze 			return get_event_data(wblock, out);
729b4f9fe12SLen Brown 	}
730b4f9fe12SLen Brown 
731b4f9fe12SLen Brown 	return AE_NOT_FOUND;
732b4f9fe12SLen Brown }
733b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_get_event_data);
734b4f9fe12SLen Brown 
735b4f9fe12SLen Brown /**
736b4f9fe12SLen Brown  * wmi_has_guid - Check if a GUID is available
737b4f9fe12SLen Brown  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
738b4f9fe12SLen Brown  *
739b4cc9795SArmin Wolf  * Check if a given GUID is defined by _WDG.
740b4cc9795SArmin Wolf  *
741b4cc9795SArmin Wolf  * Return: True if GUID is available, false otherwise.
742b4f9fe12SLen Brown  */
wmi_has_guid(const char * guid_string)743b4f9fe12SLen Brown bool wmi_has_guid(const char *guid_string)
744b4f9fe12SLen Brown {
745b0179b80SBarnabás Pőcze 	return ACPI_SUCCESS(find_guid(guid_string, NULL));
746b4f9fe12SLen Brown }
747b4f9fe12SLen Brown EXPORT_SYMBOL_GPL(wmi_has_guid);
748b4f9fe12SLen Brown 
749e7488e58SYurii Pavlovskyi /**
750d54bd4bcSArmin Wolf  * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated)
751e7488e58SYurii Pavlovskyi  * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
752e7488e58SYurii Pavlovskyi  *
753e7488e58SYurii Pavlovskyi  * Find the _UID of ACPI device associated with this WMI GUID.
754e7488e58SYurii Pavlovskyi  *
755b4cc9795SArmin Wolf  * Return: The ACPI _UID field value or NULL if the WMI GUID was not found.
756e7488e58SYurii Pavlovskyi  */
wmi_get_acpi_device_uid(const char * guid_string)757e7488e58SYurii Pavlovskyi char *wmi_get_acpi_device_uid(const char *guid_string)
758e7488e58SYurii Pavlovskyi {
759e7488e58SYurii Pavlovskyi 	struct wmi_block *wblock = NULL;
760b0179b80SBarnabás Pőcze 	acpi_status status;
761e7488e58SYurii Pavlovskyi 
762b0179b80SBarnabás Pőcze 	status = find_guid(guid_string, &wblock);
763b0179b80SBarnabás Pőcze 	if (ACPI_FAILURE(status))
764e7488e58SYurii Pavlovskyi 		return NULL;
765e7488e58SYurii Pavlovskyi 
766e7488e58SYurii Pavlovskyi 	return acpi_device_uid(wblock->acpi_device);
767e7488e58SYurii Pavlovskyi }
768e7488e58SYurii Pavlovskyi EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid);
769e7488e58SYurii Pavlovskyi 
7702f89e23bSGreg Kroah-Hartman #define dev_to_wblock(__dev)	container_of_const(__dev, struct wmi_block, dev.dev)
7712f89e23bSGreg Kroah-Hartman #define dev_to_wdev(__dev)	container_of_const(__dev, struct wmi_device, dev)
772844af950SAndy Lutomirski 
drv_to_wdrv(struct device_driver * drv)773e7b2e334SBarnabás Pőcze static inline struct wmi_driver *drv_to_wdrv(struct device_driver *drv)
774e7b2e334SBarnabás Pőcze {
775e7b2e334SBarnabás Pőcze 	return container_of(drv, struct wmi_driver, driver);
776e7b2e334SBarnabás Pőcze }
777e7b2e334SBarnabás Pőcze 
778b4f9fe12SLen Brown /*
7791caab3c1SMatthew Garrett  * sysfs interface
7801caab3c1SMatthew Garrett  */
modalias_show(struct device * dev,struct device_attribute * attr,char * buf)781614ef432SDmitry Torokhov static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
7821caab3c1SMatthew Garrett 			     char *buf)
7831caab3c1SMatthew Garrett {
784844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
7851caab3c1SMatthew Garrett 
7866133913aSBarnabás Pőcze 	return sysfs_emit(buf, "wmi:%pUL\n", &wblock->gblock.guid);
7871caab3c1SMatthew Garrett }
788e80b89a5SGreg Kroah-Hartman static DEVICE_ATTR_RO(modalias);
789614ef432SDmitry Torokhov 
guid_show(struct device * dev,struct device_attribute * attr,char * buf)790844af950SAndy Lutomirski static ssize_t guid_show(struct device *dev, struct device_attribute *attr,
791844af950SAndy Lutomirski 			 char *buf)
792844af950SAndy Lutomirski {
793844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
794844af950SAndy Lutomirski 
7956133913aSBarnabás Pőcze 	return sysfs_emit(buf, "%pUL\n", &wblock->gblock.guid);
796844af950SAndy Lutomirski }
797844af950SAndy Lutomirski static DEVICE_ATTR_RO(guid);
798844af950SAndy Lutomirski 
instance_count_show(struct device * dev,struct device_attribute * attr,char * buf)799d79b1074SAndy Lutomirski static ssize_t instance_count_show(struct device *dev,
800d79b1074SAndy Lutomirski 				   struct device_attribute *attr, char *buf)
801d79b1074SAndy Lutomirski {
802d79b1074SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
803d79b1074SAndy Lutomirski 
8046133913aSBarnabás Pőcze 	return sysfs_emit(buf, "%d\n", (int)wblock->gblock.instance_count);
805d79b1074SAndy Lutomirski }
806d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(instance_count);
807d79b1074SAndy Lutomirski 
expensive_show(struct device * dev,struct device_attribute * attr,char * buf)808d79b1074SAndy Lutomirski static ssize_t expensive_show(struct device *dev,
809d79b1074SAndy Lutomirski 			      struct device_attribute *attr, char *buf)
810d79b1074SAndy Lutomirski {
811d79b1074SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
812d79b1074SAndy Lutomirski 
8136133913aSBarnabás Pőcze 	return sysfs_emit(buf, "%d\n",
814d79b1074SAndy Lutomirski 			  (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0);
815d79b1074SAndy Lutomirski }
816d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(expensive);
817d79b1074SAndy Lutomirski 
818e80b89a5SGreg Kroah-Hartman static struct attribute *wmi_attrs[] = {
819e80b89a5SGreg Kroah-Hartman 	&dev_attr_modalias.attr,
820844af950SAndy Lutomirski 	&dev_attr_guid.attr,
821d79b1074SAndy Lutomirski 	&dev_attr_instance_count.attr,
822d79b1074SAndy Lutomirski 	&dev_attr_expensive.attr,
823cd3e3d29SBarnabás Pőcze 	NULL
824614ef432SDmitry Torokhov };
825e80b89a5SGreg Kroah-Hartman ATTRIBUTE_GROUPS(wmi);
8261caab3c1SMatthew Garrett 
notify_id_show(struct device * dev,struct device_attribute * attr,char * buf)827d79b1074SAndy Lutomirski static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr,
828d79b1074SAndy Lutomirski 			      char *buf)
829d79b1074SAndy Lutomirski {
830d79b1074SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
831d79b1074SAndy Lutomirski 
8326133913aSBarnabás Pőcze 	return sysfs_emit(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id);
833d79b1074SAndy Lutomirski }
834d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(notify_id);
835d79b1074SAndy Lutomirski 
836d79b1074SAndy Lutomirski static struct attribute *wmi_event_attrs[] = {
837d79b1074SAndy Lutomirski 	&dev_attr_notify_id.attr,
838cd3e3d29SBarnabás Pőcze 	NULL
839d79b1074SAndy Lutomirski };
840d79b1074SAndy Lutomirski ATTRIBUTE_GROUPS(wmi_event);
841d79b1074SAndy Lutomirski 
object_id_show(struct device * dev,struct device_attribute * attr,char * buf)842d79b1074SAndy Lutomirski static ssize_t object_id_show(struct device *dev, struct device_attribute *attr,
843d79b1074SAndy Lutomirski 			      char *buf)
844d79b1074SAndy Lutomirski {
845d79b1074SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
846d79b1074SAndy Lutomirski 
8476133913aSBarnabás Pőcze 	return sysfs_emit(buf, "%c%c\n", wblock->gblock.object_id[0],
848d79b1074SAndy Lutomirski 			  wblock->gblock.object_id[1]);
849d79b1074SAndy Lutomirski }
850d79b1074SAndy Lutomirski static DEVICE_ATTR_RO(object_id);
851d79b1074SAndy Lutomirski 
setable_show(struct device * dev,struct device_attribute * attr,char * buf)852fd70da6aSDarren Hart (VMware) static ssize_t setable_show(struct device *dev, struct device_attribute *attr,
853d4fc91adSAndy Lutomirski 			    char *buf)
854d4fc91adSAndy Lutomirski {
855d4fc91adSAndy Lutomirski 	struct wmi_device *wdev = dev_to_wdev(dev);
856d4fc91adSAndy Lutomirski 
8576133913aSBarnabás Pőcze 	return sysfs_emit(buf, "%d\n", (int)wdev->setable);
858d4fc91adSAndy Lutomirski }
859fd70da6aSDarren Hart (VMware) static DEVICE_ATTR_RO(setable);
860d4fc91adSAndy Lutomirski 
861d4fc91adSAndy Lutomirski static struct attribute *wmi_data_attrs[] = {
862d4fc91adSAndy Lutomirski 	&dev_attr_object_id.attr,
863fd70da6aSDarren Hart (VMware) 	&dev_attr_setable.attr,
864cd3e3d29SBarnabás Pőcze 	NULL
865d4fc91adSAndy Lutomirski };
866d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_data);
867d4fc91adSAndy Lutomirski 
868d4fc91adSAndy Lutomirski static struct attribute *wmi_method_attrs[] = {
869d79b1074SAndy Lutomirski 	&dev_attr_object_id.attr,
870cd3e3d29SBarnabás Pőcze 	NULL
871d79b1074SAndy Lutomirski };
872d4fc91adSAndy Lutomirski ATTRIBUTE_GROUPS(wmi_method);
873d79b1074SAndy Lutomirski 
wmi_dev_uevent(const struct device * dev,struct kobj_uevent_env * env)8742a81ada3SGreg Kroah-Hartman static int wmi_dev_uevent(const struct device *dev, struct kobj_uevent_env *env)
8751caab3c1SMatthew Garrett {
8762a81ada3SGreg Kroah-Hartman 	const struct wmi_block *wblock = dev_to_wblock(dev);
8771caab3c1SMatthew Garrett 
87867f472fdSBarnabás Pőcze 	if (add_uevent_var(env, "MODALIAS=wmi:%pUL", &wblock->gblock.guid))
8791caab3c1SMatthew Garrett 		return -ENOMEM;
8801caab3c1SMatthew Garrett 
88167f472fdSBarnabás Pőcze 	if (add_uevent_var(env, "WMI_GUID=%pUL", &wblock->gblock.guid))
8821caab3c1SMatthew Garrett 		return -ENOMEM;
8831caab3c1SMatthew Garrett 
8841caab3c1SMatthew Garrett 	return 0;
8851caab3c1SMatthew Garrett }
8861caab3c1SMatthew Garrett 
wmi_dev_release(struct device * dev)887844af950SAndy Lutomirski static void wmi_dev_release(struct device *dev)
8881caab3c1SMatthew Garrett {
889844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
890c64eefd4SDmitry Torokhov 
891844af950SAndy Lutomirski 	kfree(wblock);
8921caab3c1SMatthew Garrett }
8931caab3c1SMatthew Garrett 
wmi_dev_match(struct device * dev,struct device_driver * driver)894844af950SAndy Lutomirski static int wmi_dev_match(struct device *dev, struct device_driver *driver)
895844af950SAndy Lutomirski {
896e7b2e334SBarnabás Pőcze 	struct wmi_driver *wmi_driver = drv_to_wdrv(driver);
897844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
898844af950SAndy Lutomirski 	const struct wmi_device_id *id = wmi_driver->id_table;
899844af950SAndy Lutomirski 
900c355ec65SMattias Jacobsson 	if (id == NULL)
901c355ec65SMattias Jacobsson 		return 0;
902c355ec65SMattias Jacobsson 
903eacc95eaSMattias Jacobsson 	while (*id->guid_string) {
904028e6e20SAndy Shevchenko 		if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid))
905844af950SAndy Lutomirski 			return 1;
906844af950SAndy Lutomirski 
907844af950SAndy Lutomirski 		id++;
908844af950SAndy Lutomirski 	}
909844af950SAndy Lutomirski 
910844af950SAndy Lutomirski 	return 0;
911844af950SAndy Lutomirski }
wmi_char_open(struct inode * inode,struct file * filp)91244b6b766SMario Limonciello static int wmi_char_open(struct inode *inode, struct file *filp)
91344b6b766SMario Limonciello {
914fb7b06b5SArmin Wolf 	/*
915fb7b06b5SArmin Wolf 	 * The miscdevice already stores a pointer to itself
916fb7b06b5SArmin Wolf 	 * inside filp->private_data
917fb7b06b5SArmin Wolf 	 */
918fb7b06b5SArmin Wolf 	struct wmi_block *wblock = container_of(filp->private_data, struct wmi_block, char_dev);
91944b6b766SMario Limonciello 
92044b6b766SMario Limonciello 	filp->private_data = wblock;
92144b6b766SMario Limonciello 
92244b6b766SMario Limonciello 	return nonseekable_open(inode, filp);
92344b6b766SMario Limonciello }
92444b6b766SMario Limonciello 
wmi_char_read(struct file * filp,char __user * buffer,size_t length,loff_t * offset)92544b6b766SMario Limonciello static ssize_t wmi_char_read(struct file *filp, char __user *buffer,
92644b6b766SMario Limonciello 			     size_t length, loff_t *offset)
92744b6b766SMario Limonciello {
92844b6b766SMario Limonciello 	struct wmi_block *wblock = filp->private_data;
92944b6b766SMario Limonciello 
93044b6b766SMario Limonciello 	return simple_read_from_buffer(buffer, length, offset,
93144b6b766SMario Limonciello 				       &wblock->req_buf_size,
93244b6b766SMario Limonciello 				       sizeof(wblock->req_buf_size));
93344b6b766SMario Limonciello }
93444b6b766SMario Limonciello 
wmi_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)93544b6b766SMario Limonciello static long wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
93644b6b766SMario Limonciello {
93744b6b766SMario Limonciello 	struct wmi_ioctl_buffer __user *input =
93844b6b766SMario Limonciello 		(struct wmi_ioctl_buffer __user *) arg;
93944b6b766SMario Limonciello 	struct wmi_block *wblock = filp->private_data;
94043aacf83SBarnabás Pőcze 	struct wmi_ioctl_buffer *buf;
94143aacf83SBarnabás Pőcze 	struct wmi_driver *wdriver;
94244b6b766SMario Limonciello 	int ret;
94344b6b766SMario Limonciello 
94444b6b766SMario Limonciello 	if (_IOC_TYPE(cmd) != WMI_IOC)
94544b6b766SMario Limonciello 		return -ENOTTY;
94644b6b766SMario Limonciello 
94744b6b766SMario Limonciello 	/* make sure we're not calling a higher instance than exists*/
94844b6b766SMario Limonciello 	if (_IOC_NR(cmd) >= wblock->gblock.instance_count)
94944b6b766SMario Limonciello 		return -EINVAL;
95044b6b766SMario Limonciello 
95144b6b766SMario Limonciello 	mutex_lock(&wblock->char_mutex);
95244b6b766SMario Limonciello 	buf = wblock->handler_data;
95344b6b766SMario Limonciello 	if (get_user(buf->length, &input->length)) {
95444b6b766SMario Limonciello 		dev_dbg(&wblock->dev.dev, "Read length from user failed\n");
95544b6b766SMario Limonciello 		ret = -EFAULT;
95644b6b766SMario Limonciello 		goto out_ioctl;
95744b6b766SMario Limonciello 	}
95844b6b766SMario Limonciello 	/* if it's too small, abort */
95944b6b766SMario Limonciello 	if (buf->length < wblock->req_buf_size) {
96044b6b766SMario Limonciello 		dev_err(&wblock->dev.dev,
96144b6b766SMario Limonciello 			"Buffer %lld too small, need at least %lld\n",
96244b6b766SMario Limonciello 			buf->length, wblock->req_buf_size);
96344b6b766SMario Limonciello 		ret = -EINVAL;
96444b6b766SMario Limonciello 		goto out_ioctl;
96544b6b766SMario Limonciello 	}
96644b6b766SMario Limonciello 	/* if it's too big, warn, driver will only use what is needed */
96744b6b766SMario Limonciello 	if (buf->length > wblock->req_buf_size)
96844b6b766SMario Limonciello 		dev_warn(&wblock->dev.dev,
96944b6b766SMario Limonciello 			"Buffer %lld is bigger than required %lld\n",
97044b6b766SMario Limonciello 			buf->length, wblock->req_buf_size);
97144b6b766SMario Limonciello 
97244b6b766SMario Limonciello 	/* copy the structure from userspace */
97344b6b766SMario Limonciello 	if (copy_from_user(buf, input, wblock->req_buf_size)) {
97444b6b766SMario Limonciello 		dev_dbg(&wblock->dev.dev, "Copy %llu from user failed\n",
97544b6b766SMario Limonciello 			wblock->req_buf_size);
97644b6b766SMario Limonciello 		ret = -EFAULT;
97744b6b766SMario Limonciello 		goto out_ioctl;
97844b6b766SMario Limonciello 	}
97944b6b766SMario Limonciello 
98044b6b766SMario Limonciello 	/* let the driver do any filtering and do the call */
981e7b2e334SBarnabás Pőcze 	wdriver = drv_to_wdrv(wblock->dev.dev.driver);
9825e3e2297SMario Limonciello 	if (!try_module_get(wdriver->driver.owner)) {
9835e3e2297SMario Limonciello 		ret = -EBUSY;
9845e3e2297SMario Limonciello 		goto out_ioctl;
9855e3e2297SMario Limonciello 	}
98644b6b766SMario Limonciello 	ret = wdriver->filter_callback(&wblock->dev, cmd, buf);
98744b6b766SMario Limonciello 	module_put(wdriver->driver.owner);
98844b6b766SMario Limonciello 	if (ret)
98944b6b766SMario Limonciello 		goto out_ioctl;
99044b6b766SMario Limonciello 
99144b6b766SMario Limonciello 	/* return the result (only up to our internal buffer size) */
99244b6b766SMario Limonciello 	if (copy_to_user(input, buf, wblock->req_buf_size)) {
99344b6b766SMario Limonciello 		dev_dbg(&wblock->dev.dev, "Copy %llu to user failed\n",
99444b6b766SMario Limonciello 			wblock->req_buf_size);
99544b6b766SMario Limonciello 		ret = -EFAULT;
99644b6b766SMario Limonciello 	}
99744b6b766SMario Limonciello 
99844b6b766SMario Limonciello out_ioctl:
99944b6b766SMario Limonciello 	mutex_unlock(&wblock->char_mutex);
100044b6b766SMario Limonciello 	return ret;
100144b6b766SMario Limonciello }
100244b6b766SMario Limonciello 
100344b6b766SMario Limonciello static const struct file_operations wmi_fops = {
100444b6b766SMario Limonciello 	.owner		= THIS_MODULE,
100544b6b766SMario Limonciello 	.read		= wmi_char_read,
100644b6b766SMario Limonciello 	.open		= wmi_char_open,
100744b6b766SMario Limonciello 	.unlocked_ioctl	= wmi_ioctl,
10081832f2d8SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
100944b6b766SMario Limonciello };
1010844af950SAndy Lutomirski 
wmi_dev_probe(struct device * dev)1011844af950SAndy Lutomirski static int wmi_dev_probe(struct device *dev)
1012844af950SAndy Lutomirski {
1013844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
1014e7b2e334SBarnabás Pőcze 	struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
1015844af950SAndy Lutomirski 	int ret = 0;
101644b6b766SMario Limonciello 	char *buf;
1017844af950SAndy Lutomirski 
1018285dd01aSBarnabás Pőcze 	if (ACPI_FAILURE(wmi_method_enable(wblock, true)))
1019844af950SAndy Lutomirski 		dev_warn(dev, "failed to enable device -- probing anyway\n");
1020844af950SAndy Lutomirski 
1021844af950SAndy Lutomirski 	if (wdriver->probe) {
1022440c4983SMattias Jacobsson 		ret = wdriver->probe(dev_to_wdev(dev),
1023440c4983SMattias Jacobsson 				find_guid_context(wblock, wdriver));
102444b6b766SMario Limonciello 		if (ret != 0)
102544b6b766SMario Limonciello 			goto probe_failure;
1026844af950SAndy Lutomirski 	}
1027844af950SAndy Lutomirski 
102844b6b766SMario Limonciello 	/* driver wants a character device made */
102944b6b766SMario Limonciello 	if (wdriver->filter_callback) {
103044b6b766SMario Limonciello 		/* check that required buffer size declared by driver or MOF */
103144b6b766SMario Limonciello 		if (!wblock->req_buf_size) {
103244b6b766SMario Limonciello 			dev_err(&wblock->dev.dev,
103344b6b766SMario Limonciello 				"Required buffer size not set\n");
103444b6b766SMario Limonciello 			ret = -EINVAL;
103544b6b766SMario Limonciello 			goto probe_failure;
103644b6b766SMario Limonciello 		}
103744b6b766SMario Limonciello 
10386fb74107SKees Cook 		wblock->handler_data = kmalloc(wblock->req_buf_size,
10396fb74107SKees Cook 					       GFP_KERNEL);
104044b6b766SMario Limonciello 		if (!wblock->handler_data) {
104144b6b766SMario Limonciello 			ret = -ENOMEM;
104244b6b766SMario Limonciello 			goto probe_failure;
104344b6b766SMario Limonciello 		}
104444b6b766SMario Limonciello 
10457f166addSAndy Shevchenko 		buf = kasprintf(GFP_KERNEL, "wmi/%s", wdriver->driver.name);
104644b6b766SMario Limonciello 		if (!buf) {
104744b6b766SMario Limonciello 			ret = -ENOMEM;
104844b6b766SMario Limonciello 			goto probe_string_failure;
104944b6b766SMario Limonciello 		}
105044b6b766SMario Limonciello 		wblock->char_dev.minor = MISC_DYNAMIC_MINOR;
105144b6b766SMario Limonciello 		wblock->char_dev.name = buf;
105244b6b766SMario Limonciello 		wblock->char_dev.fops = &wmi_fops;
105344b6b766SMario Limonciello 		wblock->char_dev.mode = 0444;
105444b6b766SMario Limonciello 		ret = misc_register(&wblock->char_dev);
105544b6b766SMario Limonciello 		if (ret) {
1056501f7e52SJoe Perches 			dev_warn(dev, "failed to register char dev: %d\n", ret);
105744b6b766SMario Limonciello 			ret = -ENOMEM;
105844b6b766SMario Limonciello 			goto probe_misc_failure;
105944b6b766SMario Limonciello 		}
106044b6b766SMario Limonciello 	}
106144b6b766SMario Limonciello 
106299188786SHans de Goede 	set_bit(WMI_PROBED, &wblock->flags);
106344b6b766SMario Limonciello 	return 0;
106444b6b766SMario Limonciello 
106544b6b766SMario Limonciello probe_misc_failure:
106644b6b766SMario Limonciello 	kfree(buf);
106744b6b766SMario Limonciello probe_string_failure:
106844b6b766SMario Limonciello 	kfree(wblock->handler_data);
106944b6b766SMario Limonciello probe_failure:
1070285dd01aSBarnabás Pőcze 	if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
107144b6b766SMario Limonciello 		dev_warn(dev, "failed to disable device\n");
1072844af950SAndy Lutomirski 	return ret;
1073844af950SAndy Lutomirski }
1074844af950SAndy Lutomirski 
wmi_dev_remove(struct device * dev)1075fc7a6209SUwe Kleine-König static void wmi_dev_remove(struct device *dev)
1076844af950SAndy Lutomirski {
1077844af950SAndy Lutomirski 	struct wmi_block *wblock = dev_to_wblock(dev);
1078e7b2e334SBarnabás Pőcze 	struct wmi_driver *wdriver = drv_to_wdrv(dev->driver);
1079844af950SAndy Lutomirski 
108099188786SHans de Goede 	clear_bit(WMI_PROBED, &wblock->flags);
108199188786SHans de Goede 
108244b6b766SMario Limonciello 	if (wdriver->filter_callback) {
108344b6b766SMario Limonciello 		misc_deregister(&wblock->char_dev);
108444b6b766SMario Limonciello 		kfree(wblock->char_dev.name);
10856fb74107SKees Cook 		kfree(wblock->handler_data);
108644b6b766SMario Limonciello 	}
108744b6b766SMario Limonciello 
1088844af950SAndy Lutomirski 	if (wdriver->remove)
10892b329f56SUwe Kleine-König 		wdriver->remove(dev_to_wdev(dev));
1090844af950SAndy Lutomirski 
1091285dd01aSBarnabás Pőcze 	if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
1092844af950SAndy Lutomirski 		dev_warn(dev, "failed to disable device\n");
1093844af950SAndy Lutomirski }
1094844af950SAndy Lutomirski 
1095844af950SAndy Lutomirski static struct class wmi_bus_class = {
1096844af950SAndy Lutomirski 	.name = "wmi_bus",
10971caab3c1SMatthew Garrett };
10981caab3c1SMatthew Garrett 
1099844af950SAndy Lutomirski static struct bus_type wmi_bus_type = {
1100844af950SAndy Lutomirski 	.name = "wmi",
1101844af950SAndy Lutomirski 	.dev_groups = wmi_groups,
1102844af950SAndy Lutomirski 	.match = wmi_dev_match,
1103844af950SAndy Lutomirski 	.uevent = wmi_dev_uevent,
1104844af950SAndy Lutomirski 	.probe = wmi_dev_probe,
1105844af950SAndy Lutomirski 	.remove = wmi_dev_remove,
1106844af950SAndy Lutomirski };
1107844af950SAndy Lutomirski 
110869372c1dSBhumika Goyal static const struct device_type wmi_type_event = {
1109d79b1074SAndy Lutomirski 	.name = "event",
1110d79b1074SAndy Lutomirski 	.groups = wmi_event_groups,
1111d79b1074SAndy Lutomirski 	.release = wmi_dev_release,
1112d79b1074SAndy Lutomirski };
1113d79b1074SAndy Lutomirski 
111469372c1dSBhumika Goyal static const struct device_type wmi_type_method = {
1115d79b1074SAndy Lutomirski 	.name = "method",
1116d4fc91adSAndy Lutomirski 	.groups = wmi_method_groups,
1117d79b1074SAndy Lutomirski 	.release = wmi_dev_release,
1118d79b1074SAndy Lutomirski };
1119d79b1074SAndy Lutomirski 
112069372c1dSBhumika Goyal static const struct device_type wmi_type_data = {
1121d79b1074SAndy Lutomirski 	.name = "data",
1122d4fc91adSAndy Lutomirski 	.groups = wmi_data_groups,
1123d79b1074SAndy Lutomirski 	.release = wmi_dev_release,
1124d79b1074SAndy Lutomirski };
1125d79b1074SAndy Lutomirski 
1126134038b0SMario Limonciello /*
1127134038b0SMario Limonciello  * _WDG is a static list that is only parsed at startup,
1128134038b0SMario Limonciello  * so it's safe to count entries without extra protection.
1129134038b0SMario Limonciello  */
guid_count(const guid_t * guid)1130134038b0SMario Limonciello static int guid_count(const guid_t *guid)
1131134038b0SMario Limonciello {
1132134038b0SMario Limonciello 	struct wmi_block *wblock;
1133134038b0SMario Limonciello 	int count = 0;
1134134038b0SMario Limonciello 
1135134038b0SMario Limonciello 	list_for_each_entry(wblock, &wmi_block_list, list) {
1136134038b0SMario Limonciello 		if (guid_equal(&wblock->gblock.guid, guid))
1137134038b0SMario Limonciello 			count++;
1138134038b0SMario Limonciello 	}
1139134038b0SMario Limonciello 
1140134038b0SMario Limonciello 	return count;
1141134038b0SMario Limonciello }
1142134038b0SMario Limonciello 
wmi_create_device(struct device * wmi_bus_dev,struct wmi_block * wblock,struct acpi_device * device)1143fd70da6aSDarren Hart (VMware) static int wmi_create_device(struct device *wmi_bus_dev,
11447f5809bfSAndy Lutomirski 			     struct wmi_block *wblock,
11457f5809bfSAndy Lutomirski 			     struct acpi_device *device)
11461caab3c1SMatthew Garrett {
1147d4fc91adSAndy Lutomirski 	struct acpi_device_info *info;
114857f2ce89SBarnabás Pőcze 	char method[WMI_ACPI_METHOD_NAME_SIZE];
1149d4fc91adSAndy Lutomirski 	int result;
1150134038b0SMario Limonciello 	uint count;
1151d4fc91adSAndy Lutomirski 
115284eacf7eSBarnabás Pőcze 	if (wblock->gblock.flags & ACPI_WMI_EVENT) {
1153fd70da6aSDarren Hart (VMware) 		wblock->dev.dev.type = &wmi_type_event;
1154fd70da6aSDarren Hart (VMware) 		goto out_init;
1155fd70da6aSDarren Hart (VMware) 	}
1156d4fc91adSAndy Lutomirski 
115784eacf7eSBarnabás Pőcze 	if (wblock->gblock.flags & ACPI_WMI_METHOD) {
1158fd70da6aSDarren Hart (VMware) 		wblock->dev.dev.type = &wmi_type_method;
115944b6b766SMario Limonciello 		mutex_init(&wblock->char_mutex);
1160fd70da6aSDarren Hart (VMware) 		goto out_init;
1161fd70da6aSDarren Hart (VMware) 	}
1162fd70da6aSDarren Hart (VMware) 
1163fd70da6aSDarren Hart (VMware) 	/*
1164fd70da6aSDarren Hart (VMware) 	 * Data Block Query Control Method (WQxx by convention) is
1165fd70da6aSDarren Hart (VMware) 	 * required per the WMI documentation. If it is not present,
1166fd70da6aSDarren Hart (VMware) 	 * we ignore this data block.
1167fd70da6aSDarren Hart (VMware) 	 */
116857f2ce89SBarnabás Pőcze 	get_acpi_method_name(wblock, 'Q', method);
1169d4fc91adSAndy Lutomirski 	result = get_subobj_info(device->handle, method, &info);
1170d4fc91adSAndy Lutomirski 
1171fd70da6aSDarren Hart (VMware) 	if (result) {
1172fd70da6aSDarren Hart (VMware) 		dev_warn(wmi_bus_dev,
1173501f7e52SJoe Perches 			 "%s data block query control method not found\n",
1174fd70da6aSDarren Hart (VMware) 			 method);
1175fd70da6aSDarren Hart (VMware) 		return result;
1176fd70da6aSDarren Hart (VMware) 	}
1177fd70da6aSDarren Hart (VMware) 
1178fd70da6aSDarren Hart (VMware) 	wblock->dev.dev.type = &wmi_type_data;
1179d4fc91adSAndy Lutomirski 
1180d4fc91adSAndy Lutomirski 	/*
1181d4fc91adSAndy Lutomirski 	 * The Microsoft documentation specifically states:
1182d4fc91adSAndy Lutomirski 	 *
1183d4fc91adSAndy Lutomirski 	 *   Data blocks registered with only a single instance
1184d4fc91adSAndy Lutomirski 	 *   can ignore the parameter.
1185d4fc91adSAndy Lutomirski 	 *
1186fd70da6aSDarren Hart (VMware) 	 * ACPICA will get mad at us if we call the method with the wrong number
1187fd70da6aSDarren Hart (VMware) 	 * of arguments, so check what our method expects.  (On some Dell
1188fd70da6aSDarren Hart (VMware) 	 * laptops, WQxx may not be a method at all.)
1189d4fc91adSAndy Lutomirski 	 */
1190fd70da6aSDarren Hart (VMware) 	if (info->type != ACPI_TYPE_METHOD || info->param_count == 0)
1191a90b38c5SHans de Goede 		set_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags);
1192d4fc91adSAndy Lutomirski 
1193d4fc91adSAndy Lutomirski 	kfree(info);
1194d4fc91adSAndy Lutomirski 
119557f2ce89SBarnabás Pőcze 	get_acpi_method_name(wblock, 'S', method);
1196d4fc91adSAndy Lutomirski 	result = get_subobj_info(device->handle, method, NULL);
1197d4fc91adSAndy Lutomirski 
1198fd70da6aSDarren Hart (VMware) 	if (result == 0)
1199fd70da6aSDarren Hart (VMware) 		wblock->dev.setable = true;
1200d4fc91adSAndy Lutomirski 
1201fd70da6aSDarren Hart (VMware)  out_init:
1202fd70da6aSDarren Hart (VMware) 	wblock->dev.dev.bus = &wmi_bus_type;
1203fd70da6aSDarren Hart (VMware) 	wblock->dev.dev.parent = wmi_bus_dev;
1204fd70da6aSDarren Hart (VMware) 
1205134038b0SMario Limonciello 	count = guid_count(&wblock->gblock.guid);
1206134038b0SMario Limonciello 	if (count)
1207134038b0SMario Limonciello 		dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
1208134038b0SMario Limonciello 	else
120967f472fdSBarnabás Pőcze 		dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
1210c64eefd4SDmitry Torokhov 
12116ee50aaaSDarren Hart (VMware) 	device_initialize(&wblock->dev.dev);
1212fd70da6aSDarren Hart (VMware) 
1213fd70da6aSDarren Hart (VMware) 	return 0;
12141caab3c1SMatthew Garrett }
12151caab3c1SMatthew Garrett 
wmi_free_devices(struct acpi_device * device)1216b0e86302SAndy Lutomirski static void wmi_free_devices(struct acpi_device *device)
12171caab3c1SMatthew Garrett {
1218c64eefd4SDmitry Torokhov 	struct wmi_block *wblock, *next;
12191caab3c1SMatthew Garrett 
12201caab3c1SMatthew Garrett 	/* Delete devices for all the GUIDs */
1221023b9565SDmitry Torokhov 	list_for_each_entry_safe(wblock, next, &wmi_block_list, list) {
1222b0e86302SAndy Lutomirski 		if (wblock->acpi_device == device) {
1223023b9565SDmitry Torokhov 			list_del(&wblock->list);
1224844af950SAndy Lutomirski 			device_unregister(&wblock->dev.dev);
1225023b9565SDmitry Torokhov 		}
12261caab3c1SMatthew Garrett 	}
1227b0e86302SAndy Lutomirski }
12281caab3c1SMatthew Garrett 
guid_already_parsed_for_legacy(struct acpi_device * device,const guid_t * guid)1229134038b0SMario Limonciello static bool guid_already_parsed_for_legacy(struct acpi_device *device, const guid_t *guid)
1230d1f9e497SCarlos Corbacho {
1231d1f9e497SCarlos Corbacho 	struct wmi_block *wblock;
1232d1f9e497SCarlos Corbacho 
1233b0e86302SAndy Lutomirski 	list_for_each_entry(wblock, &wmi_block_list, list) {
1234134038b0SMario Limonciello 		/* skip warning and register if we know the driver will use struct wmi_driver */
1235134038b0SMario Limonciello 		for (int i = 0; allow_duplicates[i] != NULL; i++) {
12366bf06f14SAndy Shevchenko 			if (guid_parse_and_compare(allow_duplicates[i], guid))
1237134038b0SMario Limonciello 				return false;
1238134038b0SMario Limonciello 		}
123967f472fdSBarnabás Pőcze 		if (guid_equal(&wblock->gblock.guid, guid)) {
1240b0e86302SAndy Lutomirski 			/*
1241b0e86302SAndy Lutomirski 			 * Because we historically didn't track the relationship
1242b0e86302SAndy Lutomirski 			 * between GUIDs and ACPI nodes, we don't know whether
1243b0e86302SAndy Lutomirski 			 * we need to suppress GUIDs that are unique on a
1244b0e86302SAndy Lutomirski 			 * given node but duplicated across nodes.
1245b0e86302SAndy Lutomirski 			 */
1246b0e86302SAndy Lutomirski 			dev_warn(&device->dev, "duplicate WMI GUID %pUL (first instance was on %s)\n",
1247b0e86302SAndy Lutomirski 				 guid, dev_name(&wblock->acpi_device->dev));
1248d1f9e497SCarlos Corbacho 			return true;
1249b0e86302SAndy Lutomirski 		}
1250b0e86302SAndy Lutomirski 	}
1251c64eefd4SDmitry Torokhov 
1252d1f9e497SCarlos Corbacho 	return false;
1253d1f9e497SCarlos Corbacho }
1254d1f9e497SCarlos Corbacho 
12551caab3c1SMatthew Garrett /*
1256b4f9fe12SLen Brown  * Parse the _WDG method for the GUID data blocks
1257b4f9fe12SLen Brown  */
parse_wdg(struct device * wmi_bus_dev,struct acpi_device * device)1258844af950SAndy Lutomirski static int parse_wdg(struct device *wmi_bus_dev, struct acpi_device *device)
1259b4f9fe12SLen Brown {
1260b4f9fe12SLen Brown 	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
126137830662SDmitry Torokhov 	const struct guid_block *gblock;
12626ee50aaaSDarren Hart (VMware) 	struct wmi_block *wblock, *next;
12636ee50aaaSDarren Hart (VMware) 	union acpi_object *obj;
1264b4f9fe12SLen Brown 	acpi_status status;
1265b4f9fe12SLen Brown 	u32 i, total;
1266328d6725SArmin Wolf 	int retval;
1267b4f9fe12SLen Brown 
12687f5809bfSAndy Lutomirski 	status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out);
1269b4f9fe12SLen Brown 	if (ACPI_FAILURE(status))
1270c64eefd4SDmitry Torokhov 		return -ENXIO;
1271b4f9fe12SLen Brown 
1272c06a2fdeSBarnabás Pőcze 	obj = out.pointer;
12733d2c63ebSDmitry Torokhov 	if (!obj)
1274c64eefd4SDmitry Torokhov 		return -ENXIO;
1275b4f9fe12SLen Brown 
127664ed0ab8SDmitry Torokhov 	if (obj->type != ACPI_TYPE_BUFFER) {
1277328d6725SArmin Wolf 		kfree(obj);
1278328d6725SArmin Wolf 		return -ENXIO;
127964ed0ab8SDmitry Torokhov 	}
1280b4f9fe12SLen Brown 
128137830662SDmitry Torokhov 	gblock = (const struct guid_block *)obj->buffer.pointer;
1282b4f9fe12SLen Brown 	total = obj->buffer.length / sizeof(struct guid_block);
1283b4f9fe12SLen Brown 
1284b4f9fe12SLen Brown 	for (i = 0; i < total; i++) {
1285a929aae0SThomas Renninger 		if (debug_dump_wdg)
1286a929aae0SThomas Renninger 			wmi_dump_wdg(&gblock[i]);
1287a929aae0SThomas Renninger 
1288*6e33e81bSArmin Wolf 		if (!gblock[i].instance_count) {
1289*6e33e81bSArmin Wolf 			dev_info(wmi_bus_dev, FW_INFO "%pUL has zero instances\n", &gblock[i].guid);
1290*6e33e81bSArmin Wolf 			continue;
1291*6e33e81bSArmin Wolf 		}
1292*6e33e81bSArmin Wolf 
1293134038b0SMario Limonciello 		if (guid_already_parsed_for_legacy(device, &gblock[i].guid))
1294a1c31bcdSAndy Lutomirski 			continue;
1295a1c31bcdSAndy Lutomirski 
12967410b8e6SBarnabás Pőcze 		wblock = kzalloc(sizeof(*wblock), GFP_KERNEL);
12976ee50aaaSDarren Hart (VMware) 		if (!wblock) {
1298328d6725SArmin Wolf 			dev_err(wmi_bus_dev, "Failed to allocate %pUL\n", &gblock[i].guid);
1299328d6725SArmin Wolf 			continue;
13006ee50aaaSDarren Hart (VMware) 		}
130158f6425eSColin King 
1302b0e86302SAndy Lutomirski 		wblock->acpi_device = device;
130358f6425eSColin King 		wblock->gblock = gblock[i];
130458f6425eSColin King 
130584eacf7eSBarnabás Pőcze 		retval = wmi_create_device(wmi_bus_dev, wblock, device);
1306fd70da6aSDarren Hart (VMware) 		if (retval) {
1307fd70da6aSDarren Hart (VMware) 			kfree(wblock);
1308fd70da6aSDarren Hart (VMware) 			continue;
1309fd70da6aSDarren Hart (VMware) 		}
131058f6425eSColin King 
131158f6425eSColin King 		list_add_tail(&wblock->list, &wmi_block_list);
1312b4f9fe12SLen Brown 
1313fc3155b2SThomas Renninger 		if (debug_event) {
1314fc3155b2SThomas Renninger 			wblock->handler = wmi_notify_debug;
1315285dd01aSBarnabás Pőcze 			wmi_method_enable(wblock, true);
1316fc3155b2SThomas Renninger 		}
1317b4f9fe12SLen Brown 	}
1318b4f9fe12SLen Brown 
13196ee50aaaSDarren Hart (VMware) 	/*
13206ee50aaaSDarren Hart (VMware) 	 * Now that all of the devices are created, add them to the
13216ee50aaaSDarren Hart (VMware) 	 * device tree and probe subdrivers.
13226ee50aaaSDarren Hart (VMware) 	 */
13236ee50aaaSDarren Hart (VMware) 	list_for_each_entry_safe(wblock, next, &wmi_block_list, list) {
13246ee50aaaSDarren Hart (VMware) 		if (wblock->acpi_device != device)
13256ee50aaaSDarren Hart (VMware) 			continue;
13266ee50aaaSDarren Hart (VMware) 
13276ee50aaaSDarren Hart (VMware) 		retval = device_add(&wblock->dev.dev);
13286ee50aaaSDarren Hart (VMware) 		if (retval) {
1329501f7e52SJoe Perches 			dev_err(wmi_bus_dev, "failed to register %pUL\n",
133067f472fdSBarnabás Pőcze 				&wblock->gblock.guid);
13316ee50aaaSDarren Hart (VMware) 			if (debug_event)
1332285dd01aSBarnabás Pőcze 				wmi_method_enable(wblock, false);
13336ee50aaaSDarren Hart (VMware) 			list_del(&wblock->list);
13346ee50aaaSDarren Hart (VMware) 			put_device(&wblock->dev.dev);
13356ee50aaaSDarren Hart (VMware) 		}
13366ee50aaaSDarren Hart (VMware) 	}
1337c64eefd4SDmitry Torokhov 
1338328d6725SArmin Wolf 	kfree(obj);
1339328d6725SArmin Wolf 
1340328d6725SArmin Wolf 	return 0;
1341b4f9fe12SLen Brown }
1342b4f9fe12SLen Brown 
1343b4f9fe12SLen Brown /*
1344b4f9fe12SLen Brown  * WMI can have EmbeddedControl access regions. In which case, we just want to
1345b4f9fe12SLen Brown  * hand these off to the EC driver.
1346b4f9fe12SLen Brown  */
1347b4f9fe12SLen Brown static acpi_status
acpi_wmi_ec_space_handler(u32 function,acpi_physical_address address,u32 bits,u64 * value,void * handler_context,void * region_context)1348b4f9fe12SLen Brown acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
1349439913ffSLin Ming 			  u32 bits, u64 *value,
1350b4f9fe12SLen Brown 			  void *handler_context, void *region_context)
1351b4f9fe12SLen Brown {
1352b4f9fe12SLen Brown 	int result = 0, i = 0;
1353b4f9fe12SLen Brown 	u8 temp = 0;
1354b4f9fe12SLen Brown 
1355b4f9fe12SLen Brown 	if ((address > 0xFF) || !value)
1356b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
1357b4f9fe12SLen Brown 
1358b4f9fe12SLen Brown 	if (function != ACPI_READ && function != ACPI_WRITE)
1359b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
1360b4f9fe12SLen Brown 
1361b4f9fe12SLen Brown 	if (bits != 8)
1362b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
1363b4f9fe12SLen Brown 
1364b4f9fe12SLen Brown 	if (function == ACPI_READ) {
1365b4f9fe12SLen Brown 		result = ec_read(address, &temp);
1366439913ffSLin Ming 		(*value) |= ((u64)temp) << i;
1367b4f9fe12SLen Brown 	} else {
1368b4f9fe12SLen Brown 		temp = 0xff & ((*value) >> i);
1369b4f9fe12SLen Brown 		result = ec_write(address, temp);
1370b4f9fe12SLen Brown 	}
1371b4f9fe12SLen Brown 
1372b4f9fe12SLen Brown 	switch (result) {
1373b4f9fe12SLen Brown 	case -EINVAL:
1374b4f9fe12SLen Brown 		return AE_BAD_PARAMETER;
1375b4f9fe12SLen Brown 	case -ENODEV:
1376b4f9fe12SLen Brown 		return AE_NOT_FOUND;
1377b4f9fe12SLen Brown 	case -ETIME:
1378b4f9fe12SLen Brown 		return AE_TIME;
1379b4f9fe12SLen Brown 	default:
1380b4f9fe12SLen Brown 		return AE_OK;
1381b4f9fe12SLen Brown 	}
1382b4f9fe12SLen Brown }
1383b4f9fe12SLen Brown 
acpi_wmi_notify_handler(acpi_handle handle,u32 event,void * context)13841686f544SAndy Lutomirski static void acpi_wmi_notify_handler(acpi_handle handle, u32 event,
13851686f544SAndy Lutomirski 				    void *context)
1386b4f9fe12SLen Brown {
1387264e8de2SJakob Koschel 	struct wmi_block *wblock = NULL, *iter;
1388b4f9fe12SLen Brown 
1389264e8de2SJakob Koschel 	list_for_each_entry(iter, &wmi_block_list, list) {
1390264e8de2SJakob Koschel 		struct guid_block *block = &iter->gblock;
1391b4f9fe12SLen Brown 
1392264e8de2SJakob Koschel 		if (iter->acpi_device->handle == handle &&
1393b0e86302SAndy Lutomirski 		    (block->flags & ACPI_WMI_EVENT) &&
13943ecace31SBarnabás Pőcze 		    (block->notify_id == event)) {
1395264e8de2SJakob Koschel 			wblock = iter;
13961686f544SAndy Lutomirski 			break;
13971686f544SAndy Lutomirski 		}
13981686f544SAndy Lutomirski 	}
13991686f544SAndy Lutomirski 
1400264e8de2SJakob Koschel 	if (!wblock)
14011686f544SAndy Lutomirski 		return;
14021686f544SAndy Lutomirski 
14031686f544SAndy Lutomirski 	/* If a driver is bound, then notify the driver. */
140499188786SHans de Goede 	if (test_bit(WMI_PROBED, &wblock->flags) && wblock->dev.dev.driver) {
1405e7b2e334SBarnabás Pőcze 		struct wmi_driver *driver = drv_to_wdrv(wblock->dev.dev.driver);
14061686f544SAndy Lutomirski 		struct acpi_buffer evdata = { ACPI_ALLOCATE_BUFFER, NULL };
14071686f544SAndy Lutomirski 		acpi_status status;
14081686f544SAndy Lutomirski 
14098c33915dSHans de Goede 		if (!driver->no_notify_data) {
141025be44f6SBarnabás Pőcze 			status = get_event_data(wblock, &evdata);
14111686f544SAndy Lutomirski 			if (ACPI_FAILURE(status)) {
141225be44f6SBarnabás Pőcze 				dev_warn(&wblock->dev.dev, "failed to get event data\n");
14131686f544SAndy Lutomirski 				return;
14141686f544SAndy Lutomirski 			}
14158c33915dSHans de Goede 		}
14161686f544SAndy Lutomirski 
14171686f544SAndy Lutomirski 		if (driver->notify)
1418c06a2fdeSBarnabás Pőcze 			driver->notify(&wblock->dev, evdata.pointer);
14191686f544SAndy Lutomirski 
14201686f544SAndy Lutomirski 		kfree(evdata.pointer);
14211686f544SAndy Lutomirski 	} else if (wblock->handler) {
14221686f544SAndy Lutomirski 		/* Legacy handler */
1423b4f9fe12SLen Brown 		wblock->handler(event, wblock->handler_data);
14241686f544SAndy Lutomirski 	}
14251686f544SAndy Lutomirski 
14266701cc8fSAndy Shevchenko 	if (debug_event)
14271c23ab91SBarnabás Pőcze 		pr_info("DEBUG: GUID %pUL event 0x%02X\n", &wblock->gblock.guid, event);
1428b4f9fe12SLen Brown 
1429b4f9fe12SLen Brown 	acpi_bus_generate_netlink_event(
14301686f544SAndy Lutomirski 		wblock->acpi_device->pnp.device_class,
14311686f544SAndy Lutomirski 		dev_name(&wblock->dev.dev),
1432b4f9fe12SLen Brown 		event, 0);
1433b4f9fe12SLen Brown }
1434b4f9fe12SLen Brown 
acpi_wmi_remove(struct platform_device * device)14350f16136bSUwe Kleine-König static void acpi_wmi_remove(struct platform_device *device)
1436b4f9fe12SLen Brown {
14379599ed91SAndy Lutomirski 	struct acpi_device *acpi_device = ACPI_COMPANION(&device->dev);
14389599ed91SAndy Lutomirski 
1439b8d4d350SMikalai Ramanovich 	acpi_remove_notify_handler(acpi_device->handle, ACPI_ALL_NOTIFY,
14401686f544SAndy Lutomirski 				   acpi_wmi_notify_handler);
14419599ed91SAndy Lutomirski 	acpi_remove_address_space_handler(acpi_device->handle,
1442b4f9fe12SLen Brown 				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
14439599ed91SAndy Lutomirski 	wmi_free_devices(acpi_device);
1444c06a2fdeSBarnabás Pőcze 	device_unregister(dev_get_drvdata(&device->dev));
1445b4f9fe12SLen Brown }
1446b4f9fe12SLen Brown 
acpi_wmi_probe(struct platform_device * device)14479599ed91SAndy Lutomirski static int acpi_wmi_probe(struct platform_device *device)
1448b4f9fe12SLen Brown {
14499599ed91SAndy Lutomirski 	struct acpi_device *acpi_device;
1450844af950SAndy Lutomirski 	struct device *wmi_bus_dev;
1451b4f9fe12SLen Brown 	acpi_status status;
1452c64eefd4SDmitry Torokhov 	int error;
1453b4f9fe12SLen Brown 
14549599ed91SAndy Lutomirski 	acpi_device = ACPI_COMPANION(&device->dev);
14559599ed91SAndy Lutomirski 	if (!acpi_device) {
14569599ed91SAndy Lutomirski 		dev_err(&device->dev, "ACPI companion is missing\n");
14579599ed91SAndy Lutomirski 		return -ENODEV;
14589599ed91SAndy Lutomirski 	}
14599599ed91SAndy Lutomirski 
14609599ed91SAndy Lutomirski 	status = acpi_install_address_space_handler(acpi_device->handle,
1461b4f9fe12SLen Brown 						    ACPI_ADR_SPACE_EC,
1462b4f9fe12SLen Brown 						    &acpi_wmi_ec_space_handler,
1463b4f9fe12SLen Brown 						    NULL, NULL);
14645212cd67SDmitry Torokhov 	if (ACPI_FAILURE(status)) {
146546492ee4SAndy Lutomirski 		dev_err(&device->dev, "Error installing EC region handler\n");
1466b4f9fe12SLen Brown 		return -ENODEV;
14675212cd67SDmitry Torokhov 	}
1468b4f9fe12SLen Brown 
14699599ed91SAndy Lutomirski 	status = acpi_install_notify_handler(acpi_device->handle,
1470b8d4d350SMikalai Ramanovich 					     ACPI_ALL_NOTIFY,
14711686f544SAndy Lutomirski 					     acpi_wmi_notify_handler,
14721686f544SAndy Lutomirski 					     NULL);
14731686f544SAndy Lutomirski 	if (ACPI_FAILURE(status)) {
14741686f544SAndy Lutomirski 		dev_err(&device->dev, "Error installing notify handler\n");
14751686f544SAndy Lutomirski 		error = -ENODEV;
14761686f544SAndy Lutomirski 		goto err_remove_ec_handler;
14771686f544SAndy Lutomirski 	}
14781686f544SAndy Lutomirski 
1479844af950SAndy Lutomirski 	wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0),
1480844af950SAndy Lutomirski 				    NULL, "wmi_bus-%s", dev_name(&device->dev));
1481844af950SAndy Lutomirski 	if (IS_ERR(wmi_bus_dev)) {
1482844af950SAndy Lutomirski 		error = PTR_ERR(wmi_bus_dev);
14831686f544SAndy Lutomirski 		goto err_remove_notify_handler;
1484844af950SAndy Lutomirski 	}
14859599ed91SAndy Lutomirski 	dev_set_drvdata(&device->dev, wmi_bus_dev);
1486844af950SAndy Lutomirski 
14879599ed91SAndy Lutomirski 	error = parse_wdg(wmi_bus_dev, acpi_device);
1488c64eefd4SDmitry Torokhov 	if (error) {
14898e07514dSDmitry Torokhov 		pr_err("Failed to parse WDG method\n");
1490844af950SAndy Lutomirski 		goto err_remove_busdev;
1491b4f9fe12SLen Brown 	}
1492b4f9fe12SLen Brown 
1493c64eefd4SDmitry Torokhov 	return 0;
149446492ee4SAndy Lutomirski 
1495844af950SAndy Lutomirski err_remove_busdev:
149656afb8d4SYongxin Liu 	device_unregister(wmi_bus_dev);
1497844af950SAndy Lutomirski 
14981686f544SAndy Lutomirski err_remove_notify_handler:
1499b8d4d350SMikalai Ramanovich 	acpi_remove_notify_handler(acpi_device->handle, ACPI_ALL_NOTIFY,
15001686f544SAndy Lutomirski 				   acpi_wmi_notify_handler);
15011686f544SAndy Lutomirski 
15021686f544SAndy Lutomirski err_remove_ec_handler:
15039599ed91SAndy Lutomirski 	acpi_remove_address_space_handler(acpi_device->handle,
150446492ee4SAndy Lutomirski 					  ACPI_ADR_SPACE_EC,
150546492ee4SAndy Lutomirski 					  &acpi_wmi_ec_space_handler);
150646492ee4SAndy Lutomirski 
150746492ee4SAndy Lutomirski 	return error;
1508b4f9fe12SLen Brown }
1509b4f9fe12SLen Brown 
__wmi_driver_register(struct wmi_driver * driver,struct module * owner)1510844af950SAndy Lutomirski int __must_check __wmi_driver_register(struct wmi_driver *driver,
1511844af950SAndy Lutomirski 				       struct module *owner)
1512844af950SAndy Lutomirski {
1513844af950SAndy Lutomirski 	driver->driver.owner = owner;
1514844af950SAndy Lutomirski 	driver->driver.bus = &wmi_bus_type;
1515844af950SAndy Lutomirski 
1516844af950SAndy Lutomirski 	return driver_register(&driver->driver);
1517844af950SAndy Lutomirski }
1518844af950SAndy Lutomirski EXPORT_SYMBOL(__wmi_driver_register);
1519844af950SAndy Lutomirski 
1520b4cc9795SArmin Wolf /**
1521b4cc9795SArmin Wolf  * wmi_driver_unregister() - Unregister a WMI driver
1522b4cc9795SArmin Wolf  * @driver: WMI driver to unregister
1523b4cc9795SArmin Wolf  *
1524b4cc9795SArmin Wolf  * Unregisters a WMI driver from the WMI bus.
1525b4cc9795SArmin Wolf  */
wmi_driver_unregister(struct wmi_driver * driver)1526844af950SAndy Lutomirski void wmi_driver_unregister(struct wmi_driver *driver)
1527844af950SAndy Lutomirski {
1528844af950SAndy Lutomirski 	driver_unregister(&driver->driver);
1529844af950SAndy Lutomirski }
1530844af950SAndy Lutomirski EXPORT_SYMBOL(wmi_driver_unregister);
1531844af950SAndy Lutomirski 
1532c710765aSUwe Kleine-König static struct platform_driver acpi_wmi_driver = {
1533c710765aSUwe Kleine-König 	.driver = {
1534c710765aSUwe Kleine-König 		.name = "acpi-wmi",
1535c710765aSUwe Kleine-König 		.acpi_match_table = wmi_device_ids,
1536c710765aSUwe Kleine-König 	},
1537c710765aSUwe Kleine-König 	.probe = acpi_wmi_probe,
15380f16136bSUwe Kleine-König 	.remove_new = acpi_wmi_remove,
1539c710765aSUwe Kleine-König };
1540c710765aSUwe Kleine-König 
acpi_wmi_init(void)1541b4f9fe12SLen Brown static int __init acpi_wmi_init(void)
1542b4f9fe12SLen Brown {
1543c64eefd4SDmitry Torokhov 	int error;
1544b4f9fe12SLen Brown 
1545b4f9fe12SLen Brown 	if (acpi_disabled)
1546b4f9fe12SLen Brown 		return -ENODEV;
1547b4f9fe12SLen Brown 
1548844af950SAndy Lutomirski 	error = class_register(&wmi_bus_class);
1549c64eefd4SDmitry Torokhov 	if (error)
1550c64eefd4SDmitry Torokhov 		return error;
1551b4f9fe12SLen Brown 
1552844af950SAndy Lutomirski 	error = bus_register(&wmi_bus_type);
1553844af950SAndy Lutomirski 	if (error)
1554844af950SAndy Lutomirski 		goto err_unreg_class;
1555844af950SAndy Lutomirski 
15569599ed91SAndy Lutomirski 	error = platform_driver_register(&acpi_wmi_driver);
1557c64eefd4SDmitry Torokhov 	if (error) {
1558c64eefd4SDmitry Torokhov 		pr_err("Error loading mapper\n");
1559844af950SAndy Lutomirski 		goto err_unreg_bus;
15601caab3c1SMatthew Garrett 	}
15611caab3c1SMatthew Garrett 
15628e07514dSDmitry Torokhov 	return 0;
1563844af950SAndy Lutomirski 
1564844af950SAndy Lutomirski err_unreg_bus:
1565844af950SAndy Lutomirski 	bus_unregister(&wmi_bus_type);
1566844af950SAndy Lutomirski 
156797277717SAlexey Khoroshilov err_unreg_class:
156897277717SAlexey Khoroshilov 	class_unregister(&wmi_bus_class);
156997277717SAlexey Khoroshilov 
1570844af950SAndy Lutomirski 	return error;
1571b4f9fe12SLen Brown }
1572b4f9fe12SLen Brown 
acpi_wmi_exit(void)1573b4f9fe12SLen Brown static void __exit acpi_wmi_exit(void)
1574b4f9fe12SLen Brown {
15759599ed91SAndy Lutomirski 	platform_driver_unregister(&acpi_wmi_driver);
1576844af950SAndy Lutomirski 	bus_unregister(&wmi_bus_type);
1577303d1fccSMario Limonciello 	class_unregister(&wmi_bus_class);
1578b4f9fe12SLen Brown }
1579b4f9fe12SLen Brown 
158098b8e4e5SRafael J. Wysocki subsys_initcall_sync(acpi_wmi_init);
1581b4f9fe12SLen Brown module_exit(acpi_wmi_exit);
1582