// 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/build_bug.h>
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/uuid.h>
#include <linux/wmi.h>
#include <linux/fs.h>
#include "internal.h"
MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
MODULE_LICENSE("GPL");
struct guid_block {
guid_t guid;
union {
char object_id[2];
struct {
unsigned char notify_id;
unsigned char reserved;
};
};
u8 instance_count;
u8 flags;
} __packed;
static_assert(sizeof(typeof_member(struct guid_block, guid)) == 16);
static_assert(sizeof(struct guid_block) == 20);
static_assert(__alignof__(struct guid_block) == 1);
enum { /* wmi_block flags */
WMI_READ_TAKES_NO_ARGS,
WMI_GUID_DUPLICATED,
WMI_NO_EVENT_DATA,
};
struct wmi_block {
struct wmi_device dev;
struct guid_block gblock;
struct acpi_device *acpi_device;
struct rw_semaphore notify_lock; /* Protects notify callback add/remove */
wmi_notify_handler handler;
void *handler_data;
bool driver_ready;
unsigned long flags;
};
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 */
static const struct acpi_device_id wmi_device_ids[] = {
{"PNP0C14", 0},
{"pnp0c14", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
#define dev_to_wblock(__dev) container_of_const(__dev, struct wmi_block, dev.dev)
/*
* GUID parsing functions
*/
static bool guid_parse_and_compare(const char *string, const guid_t *guid)
{
guid_t guid_input;
if (guid_parse(string, &guid_input))
return false;
return guid_equal(&guid_input, guid);
}
static const void *find_guid_context(struct wmi_block *wblock,
struct wmi_driver *wdriver)
{
const struct wmi_device_id *id;
id = wdriver->id_table;
if (!id)
return NULL;
while (*id->guid_string) {
if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid))
return id->context;
id++;
}
return NULL;
}
#define WMI_ACPI_METHOD_NAME_SIZE 5
static inline void get_acpi_method_name(const struct wmi_block *wblock,
const char method,
char buffer[static WMI_ACPI_METHOD_NAME_SIZE])
{
static_assert(ARRAY_SIZE(wblock->gblock.object_id) == 2);
static_assert(WMI_ACPI_METHOD_NAME_SIZE >= 5);
buffer[0] = 'W';
buffer[1] = method;
buffer[2] = wblock->gblock.object_id[0];
buffer[3] = wblock->gblock.object_id[1];
buffer[4] = '\0';
}
static int wmidev_match_guid(struct device *dev, const void *data)
{
struct wmi_block *wblock = dev_to_wblock(dev);
const guid_t *guid = data;
/* 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;
return 0;
}
static const struct bus_type wmi_bus_type;
static const struct device_type wmi_type_event;
static const struct device_type wmi_type_method;
static int wmi_device_enable(struct wmi_device *wdev, bool enable)
{
struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
char method[WMI_ACPI_METHOD_NAME_SIZE];
acpi_handle handle;
acpi_status status;
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
|