// SPDX-License-Identifier: GPL-2.0-or-later /* * ACPI-WMI mapping driver * * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk> * * GUID parsing code from ldm.c is: * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org> * Copyright (c) 2001-2007 Anton Altaparmakov * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com> * * WMI bus infrastructure by Andrew Lutomirski and Darren Hart: * Copyright (C) 2015 Andrew Lutomirski * Copyright (C) 2017 VMware, Inc. All Rights Reserved.
*/
struct wmi_guid_count_context { const guid_t *guid; int count;
};
static DEFINE_IDA(wmi_ida);
/* * If the GUID data block is marked as expensive, we must enable and * explicitily disable data collection.
*/ #define ACPI_WMI_EXPENSIVE BIT(0) #define ACPI_WMI_METHOD BIT(1) /* GUID is a method */ #define ACPI_WMI_STRING BIT(2) /* GUID takes & returns a string */ #define ACPI_WMI_EVENT BIT(3) /* GUID is an event */
/* Legacy GUID-based functions are restricted to only see * a single WMI device for each GUID.
*/ if (test_bit(WMI_GUID_DUPLICATED, &wblock->flags)) return 0;
if (guid_equal(guid, &wblock->gblock.guid)) return 1;
if (wblock->dev.dev.type == &wmi_type_method) return 0;
if (wblock->dev.dev.type == &wmi_type_event) { /* * Windows always enables/disables WMI events, even when they are * not marked as being expensive. We follow this behavior for * compatibility reasons.
*/
snprintf(method, sizeof(method), "WE%02X", wblock->gblock.notify_id);
} else { if (!(wblock->gblock.flags & ACPI_WMI_EXPENSIVE)) return 0;
get_acpi_method_name(wblock, 'C', method);
}
/* * Not all WMI devices marked as expensive actually implement the * necessary ACPI method. Ignore this missing ACPI method to match * the behaviour of the Windows driver.
*/
status = acpi_get_handle(wblock->acpi_device->handle, method, &handle); if (ACPI_FAILURE(status)) return 0;
status = acpi_execute_simple_method(handle, NULL, enable); if (ACPI_FAILURE(status)) return -EIO;
/** * wmi_instance_count - Get number of WMI object instances * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * * Get the number of WMI object instances. * * Returns: Number of WMI object instances or negative error code.
*/ int wmi_instance_count(constchar *guid_string)
{ struct wmi_device *wdev; int ret;
wdev = wmi_find_device_by_guid(guid_string); if (IS_ERR(wdev)) return PTR_ERR(wdev);
ret = wmidev_instance_count(wdev);
wmi_device_put(wdev);
/** * wmidev_instance_count - Get number of WMI object instances * @wdev: A wmi bus device from a driver * * Get the number of WMI object instances. * * Returns: Number of WMI object instances.
*/
u8 wmidev_instance_count(struct wmi_device *wdev)
{ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
/** * wmi_query_block - Return contents of a WMI block (deprecated) * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * @instance: Instance index * @out: Empty buffer to return the contents of the data block to * * Query a ACPI-WMI block, the caller must free @out. * * Return: ACPI object containing the content of the WMI block.
*/
acpi_status wmi_query_block(constchar *guid_string, u8 instance, struct acpi_buffer *out)
{ struct wmi_block *wblock; struct wmi_device *wdev;
acpi_status status;
wdev = wmi_find_device_by_guid(guid_string); if (IS_ERR(wdev)) return AE_ERROR;
if (wmi_device_enable(wdev, true) < 0)
dev_warn(&wdev->dev, "Failed to enable device\n");
/** * wmidev_block_query - Return contents of a WMI block * @wdev: A wmi bus device from a driver * @instance: Instance index * * Query an ACPI-WMI block, the caller must free the result. * * Return: ACPI object containing the content of the WMI block.
*/ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
{ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
if (ACPI_FAILURE(__query_block(wblock, instance, &out))) return NULL;
/** * wmi_set_block - Write to a WMI block (deprecated) * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * @instance: Instance index * @in: Buffer containing new values for the data block * * Write the contents of the input buffer to an ACPI-WMI data block. * * Return: acpi_status signaling success or error.
*/
acpi_status wmi_set_block(constchar *guid_string, u8 instance, conststruct acpi_buffer *in)
{ struct wmi_device *wdev;
acpi_status status;
wdev = wmi_find_device_by_guid(guid_string); if (IS_ERR(wdev)) return AE_ERROR;
if (wmi_device_enable(wdev, true) < 0)
dev_warn(&wdev->dev, "Failed to enable device\n");
status = wmidev_block_set(wdev, instance, in);
if (wmi_device_enable(wdev, false) < 0)
dev_warn(&wdev->dev, "Failed to disable device\n");
/** * wmidev_block_set - Write to a WMI block * @wdev: A wmi bus device from a driver * @instance: Instance index * @in: Buffer containing new values for the data block * * Write contents of the input buffer to an ACPI-WMI data block. * * Return: acpi_status signaling success or error.
*/
acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, conststruct acpi_buffer *in)
{ struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
acpi_handle handle = wblock->acpi_device->handle; struct guid_block *block = &wblock->gblock; char method[WMI_ACPI_METHOD_NAME_SIZE]; struct acpi_object_list input; union acpi_object params[2];
if (!in) return AE_BAD_DATA;
if (block->instance_count <= instance) return AE_BAD_PARAMETER;
/* Check GUID is a data block */ if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD)) return AE_ERROR;
/** * wmi_install_notify_handler - Register handler for WMI events (deprecated) * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * @handler: Function to handle notifications * @data: Data to be returned to handler when event is fired * * Register a handler for events sent to the ACPI-WMI mapper device. * * Return: acpi_status signaling success or error.
*/
acpi_status wmi_install_notify_handler(constchar *guid,
wmi_notify_handler handler, void *data)
{ struct wmi_block *wblock; struct wmi_device *wdev;
acpi_status status;
wdev = wmi_find_device_by_guid(guid); if (IS_ERR(wdev)) return AE_ERROR;
down_write(&wblock->notify_lock); if (!wblock->handler) {
status = AE_NULL_ENTRY;
} else { if (wmi_device_enable(wdev, false) < 0)
dev_warn(&wblock->dev.dev, "Failed to disable device\n");
/** * wmi_has_guid - Check if a GUID is available * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * * Check if a given GUID is defined by _WDG. * * Return: True if GUID is available, false otherwise.
*/ bool wmi_has_guid(constchar *guid_string)
{ struct wmi_device *wdev;
wdev = wmi_find_device_by_guid(guid_string); if (IS_ERR(wdev)) returnfalse;
wmi_device_put(wdev);
returntrue;
}
EXPORT_SYMBOL_GPL(wmi_has_guid);
/** * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated) * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba * * Find the _UID of ACPI device associated with this WMI GUID. * * Return: The ACPI _UID field value or NULL if the WMI GUID was not found.
*/ char *wmi_get_acpi_device_uid(constchar *guid_string)
{ struct wmi_block *wblock; struct wmi_device *wdev; char *uid;
wdev = wmi_find_device_by_guid(guid_string); if (IS_ERR(wdev)) return NULL;
/* When driver_override is set, only bind to the matching driver */ if (wblock->dev.driver_override) return !strcmp(wblock->dev.driver_override, driver->name);
if (id == NULL) return 0;
while (*id->guid_string) { if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid)) return 1;
/* Some older WMI drivers will break if instantiated multiple times, * so they are blocked from probing WMI devices with a duplicated GUID. * * New WMI drivers should support being instantiated multiple times.
*/ if (test_bit(WMI_GUID_DUPLICATED, &wblock->flags) && !wdriver->no_singleton) {
dev_warn(dev, "Legacy driver %s cannot be instantiated multiple times\n",
dev->driver->name);
return -ENODEV;
}
if (wdriver->notify) { if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data) return -ENODEV;
}
if (wmi_device_enable(to_wmi_device(dev), true) < 0)
dev_warn(dev, "failed to enable device -- probing anyway\n");
/* * We have to make sure that all devres-managed resources are released first because * some might still want to access the underlying WMI device.
*/
ret = devm_add_action_or_reset(dev, wmi_dev_disable, dev); if (ret < 0) return ret;
if (wdriver->probe) {
ret = wdriver->probe(to_wmi_device(dev),
find_guid_context(wblock, wdriver)); if (ret) return ret;
}
if (dev->driver) {
wdriver = to_wmi_driver(dev->driver);
wblock = dev_to_wblock(dev);
/* * Some machines return bogus WMI event data when disabling * the WMI event. Because of this we must prevent the associated * WMI driver from receiving new WMI events before disabling it.
*/
down_write(&wblock->notify_lock);
wblock->driver_ready = false;
up_write(&wblock->notify_lock);
if (wdriver->shutdown)
wdriver->shutdown(to_wmi_device(dev));
/* * We still need to disable the WMI device here since devres-managed resources * like wmi_dev_disable() will not be release during shutdown.
*/ if (wmi_device_enable(to_wmi_device(dev), false) < 0)
dev_warn(dev, "Failed to disable device\n");
}
}
/* * Data Block Query Control Method (WQxx by convention) is * required per the WMI documentation. If it is not present, * we ignore this data block.
*/
get_acpi_method_name(wblock, 'Q', method);
status = acpi_get_handle(device->handle, method, &method_handle); if (ACPI_FAILURE(status)) {
dev_warn(wmi_bus_dev,
FW_BUG "%s data block query control method not found\n",
method);
return -ENXIO;
}
status = acpi_get_object_info(method_handle, &info); if (ACPI_FAILURE(status)) return -EIO;
wblock->dev.dev.type = &wmi_type_data;
/* * The Microsoft documentation specifically states: * * Data blocks registered with only a single instance * can ignore the parameter. * * ACPICA will get mad at us if we call the method with the wrong number * of arguments, so check what our method expects. (On some Dell * laptops, WQxx may not be a method at all.)
*/ if (info->type != ACPI_TYPE_METHOD || info->param_count == 0)
set_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags);
kfree(info);
get_acpi_method_name(wblock, 'S', method); if (acpi_has_method(device->handle, method))
wblock->dev.setable = true;
/* * Many aggregate WMI drivers do not use -EPROBE_DEFER when they * are unable to find a WMI device during probe, instead they require * all WMI devices associated with an platform device to become available * at once. This device link thus prevents WMI drivers from probing until * the associated platform device has finished probing (and has registered * all discovered WMI devices).
*/
link = device_link_add(&wdev->dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); if (!link) return -EINVAL;
return device_add(&wdev->dev);
}
/* * Parse the _WDG method for the GUID data blocks
*/ staticint parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
{ struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; conststruct guid_block *gblock; bool event_data_available; struct wmi_block *wblock; union acpi_object *obj;
acpi_status status;
u32 i, total; int retval;
status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out); if (ACPI_FAILURE(status)) return -ENXIO;
obj = out.pointer; if (!obj) return -ENXIO;
if (obj->type != ACPI_TYPE_BUFFER) {
kfree(obj); return -ENXIO;
}
for (i = 0; i < total; i++) { if (!gblock[i].instance_count) {
dev_info(wmi_bus_dev, FW_INFO "%pUL has zero instances\n", &gblock[i].guid); continue;
}
wblock = kzalloc(sizeof(*wblock), GFP_KERNEL); if (!wblock) continue;
status = acpi_evaluate_object(wblock->acpi_device->handle, "_WED", &input, &data); if (ACPI_FAILURE(status)) {
dev_warn(&wblock->dev.dev, "Failed to get event data\n"); return -EIO;
}
if (!(wblock->gblock.flags & ACPI_WMI_EVENT && wblock->gblock.notify_id == *event)) return 0;
/* The ACPI WMI specification says that _WED should be * evaluated every time an notification is received, even * if no consumers are present. * * Some firmware implementations actually depend on this * by using a queue for events which will fill up if the * WMI driver core stops evaluating _WED due to missing * WMI event consumers.
*/ if (!test_bit(WMI_NO_EVENT_DATA, &wblock->flags)) {
ret = wmi_get_notify_data(wblock, &obj); if (ret < 0) return -EIO;
}
down_read(&wblock->notify_lock);
if (wblock->dev.dev.driver && wblock->driver_ready)
wmi_notify_driver(wblock, obj);
if (wblock->handler)
wblock->handler(obj, wblock->handler_data);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.