platform/x86: wmi: Implement proper shutdown handling

When performing a system shutdown under Windows, all WMI clients are
terminated. This means that the ACPI BIOS might expect all WMI devices
to be disabled when shutting down.

Emulate this behaviour by disabling all active WMI devices during
shutdown. Also introduce a new WMI driver callback to allow WMI drivers
to perform any device-specific actions before disabling the WMI device.

Tested on a Dell Inspiron 3505.

Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://lore.kernel.org/r/20241005213825.701887-2-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
This commit is contained in:
Armin Wolf 2024-10-05 23:38:25 +02:00 committed by Ilpo Järvinen
parent 066c779b09
commit d12586e107
No known key found for this signature in database
GPG Key ID: 59AC4F6153E5CE31
3 changed files with 35 additions and 1 deletions

View File

@ -64,6 +64,7 @@ to matching WMI devices using a struct wmi_device_id table:
.id_table = foo_id_table, .id_table = foo_id_table,
.probe = foo_probe, .probe = foo_probe,
.remove = foo_remove, /* optional, devres is preferred */ .remove = foo_remove, /* optional, devres is preferred */
.shutdown = foo_shutdown, /* optional, called during shutdown */
.notify = foo_notify, /* optional, for event handling */ .notify = foo_notify, /* optional, for event handling */
.no_notify_data = true, /* optional, enables events containing no additional data */ .no_notify_data = true, /* optional, enables events containing no additional data */
.no_singleton = true, /* required for new WMI drivers */ .no_singleton = true, /* required for new WMI drivers */
@ -79,6 +80,10 @@ to unregister interfaces to other kernel subsystems and release resources, devre
This simplifies error handling during probe and often allows to omit this callback entirely, see This simplifies error handling during probe and often allows to omit this callback entirely, see
Documentation/driver-api/driver-model/devres.rst for details. Documentation/driver-api/driver-model/devres.rst for details.
The shutdown() callback is called during shutdown, reboot or kexec. Its sole purpose is to disable
the WMI device and put it in a well-known state for the WMI driver to pick up later after reboot
or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
Please note that new WMI drivers are required to be able to be instantiated multiple times, Please note that new WMI drivers are required to be able to be instantiated multiple times,
and are forbidden from using any deprecated GUID-based WMI functions. This means that the and are forbidden from using any deprecated GUID-based WMI functions. This means that the
WMI driver should be prepared for the scenario that multiple matching WMI devices are present WMI driver should be prepared for the scenario that multiple matching WMI devices are present
@ -123,7 +128,7 @@ ACPI object is being done by the WMI subsystem, not the driver.
The WMI driver core will take care that the notify() callback will only be called after The WMI driver core will take care that the notify() callback will only be called after
the probe() callback has been called, and that no events are being received by the driver the probe() callback has been called, and that no events are being received by the driver
right before and after calling its remove() callback. right before and after calling its remove() or shutdown() callback.
However WMI driver developers should be aware that multiple WMI events can be received concurrently, However WMI driver developers should be aware that multiple WMI events can be received concurrently,
so any locking (if necessary) needs to be provided by the WMI driver itself. so any locking (if necessary) needs to be provided by the WMI driver itself.

View File

@ -884,6 +884,32 @@ static void wmi_dev_remove(struct device *dev)
dev_warn(dev, "failed to disable device\n"); dev_warn(dev, "failed to disable device\n");
} }
static void wmi_dev_shutdown(struct device *dev)
{
struct wmi_driver *wdriver;
struct wmi_block *wblock;
if (dev->driver) {
wdriver = drv_to_wdrv(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));
if (ACPI_FAILURE(wmi_method_enable(wblock, false)))
dev_warn(dev, "Failed to disable device\n");
}
}
static struct class wmi_bus_class = { static struct class wmi_bus_class = {
.name = "wmi_bus", .name = "wmi_bus",
}; };
@ -895,6 +921,7 @@ static const struct bus_type wmi_bus_type = {
.uevent = wmi_dev_uevent, .uevent = wmi_dev_uevent,
.probe = wmi_dev_probe, .probe = wmi_dev_probe,
.remove = wmi_dev_remove, .remove = wmi_dev_remove,
.shutdown = wmi_dev_shutdown,
}; };
static const struct device_type wmi_type_event = { static const struct device_type wmi_type_event = {

View File

@ -56,6 +56,7 @@ u8 wmidev_instance_count(struct wmi_device *wdev);
* @no_singleton: Driver can be instantiated multiple times * @no_singleton: Driver can be instantiated multiple times
* @probe: Callback for device binding * @probe: Callback for device binding
* @remove: Callback for device unbinding * @remove: Callback for device unbinding
* @shutdown: Callback for device shutdown
* @notify: Callback for receiving WMI events * @notify: Callback for receiving WMI events
* *
* This represents WMI drivers which handle WMI devices. * This represents WMI drivers which handle WMI devices.
@ -68,6 +69,7 @@ struct wmi_driver {
int (*probe)(struct wmi_device *wdev, const void *context); int (*probe)(struct wmi_device *wdev, const void *context);
void (*remove)(struct wmi_device *wdev); void (*remove)(struct wmi_device *wdev);
void (*shutdown)(struct wmi_device *wdev);
void (*notify)(struct wmi_device *device, union acpi_object *data); void (*notify)(struct wmi_device *device, union acpi_object *data);
}; };