// SPDX-License-Identifier: GPL-2.0 /* * ACPI event handling for Wilco Embedded Controller * * Copyright 2019 Google LLC * * The Wilco Embedded Controller can create custom events that * are not handled as standard ACPI objects. These events can * contain information about changes in EC controlled features, * such as errors and events in the dock or display. For example, * an event is triggered if the dock is plugged into a display * incorrectly. These events are needed for telemetry and * diagnostics reasons, and for possibly alerting the user. * These events are triggered by the EC with an ACPI Notify(0x90), * and then the BIOS reads the event buffer from EC RAM via an * ACPI method. When the OS receives these events via ACPI, * it passes them along to this driver. The events are put into * a queue which can be read by a userspace daemon via a char device * that implements read() and poll(). The event queue acts as a * circular buffer of size 64, so if there are no userspace consumers * the kernel will not run out of memory. The char device will appear at * /dev/wilco_event{n}, where n is some small non-negative integer, * starting from 0. Standard ACPI events such as the battery getting * plugged/unplugged can also come through this path, but they are * dealt with via other paths, and are ignored here. * To test, you can tail the binary data with * $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"' * and then create an event by plugging/unplugging the battery. */ #include <linux/acpi.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/fs.h> #include <linux/idr.h> #include <linux/io.h> #include <linux/list.h> #include <linux/module.h> #include <linux/poll.h> #include <linux/spinlock.h> #include <linux/uaccess.h> #include <linux/wait.h> /* ACPI Notify event code indicating event data is available. */ #define EC_ACPI_NOTIFY_EVENT … /* ACPI Method to execute to retrieve event data buffer from the EC. */ #define EC_ACPI_GET_EVENT … /* Maximum number of words in event data returned by the EC. */ #define EC_ACPI_MAX_EVENT_WORDS … #define EC_ACPI_MAX_EVENT_SIZE … /* Node will appear in /dev/EVENT_DEV_NAME */ #define EVENT_DEV_NAME … #define EVENT_CLASS_NAME … #define DRV_NAME … #define EVENT_DEV_NAME_FMT … static struct class event_class = …; /* Keep track of all the device numbers used. */ #define EVENT_MAX_DEV … static int event_major; static DEFINE_IDA(event_ida); /* Size of circular queue of events. */ #define MAX_NUM_EVENTS … /** * struct ec_event - Extended event returned by the EC. * @size: Number of 16bit words in structure after the size word. * @type: Extended event type, meaningless for us. * @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS. */ struct ec_event { … } __packed; #define ec_event_num_words(ev) … #define ec_event_size(ev) … /** * struct ec_event_queue - Circular queue for events. * @capacity: Number of elements the queue can hold. * @head: Next index to write to. * @tail: Next index to read from. * @entries: Array of events. */ struct ec_event_queue { … }; /* Maximum number of events to store in ec_event_queue */ static int queue_size = …; module_param(queue_size, int, 0644); static struct ec_event_queue *event_queue_new(int capacity) { … } static inline bool event_queue_empty(struct ec_event_queue *q) { … } static inline bool event_queue_full(struct ec_event_queue *q) { … } static struct ec_event *event_queue_pop(struct ec_event_queue *q) { … } /* * If full, overwrite the oldest event and return it so the caller * can kfree it. If not full, return NULL. */ static struct ec_event *event_queue_push(struct ec_event_queue *q, struct ec_event *ev) { … } static void event_queue_free(struct ec_event_queue *q) { … } /** * struct event_device_data - Data for a Wilco EC device that responds to ACPI. * @events: Circular queue of EC events to be provided to userspace. * @queue_lock: Protect the queue from simultaneous read/writes. * @wq: Wait queue to notify processes when events are available or the * device has been removed. * @cdev: Char dev that userspace reads() and polls() from. * @dev: Device associated with the %cdev. * @exist: Has the device been not been removed? Once a device has been removed, * writes, reads, and new opens will fail. * @available: Guarantee only one client can open() file and read from queue. * * There will be one of these structs for each ACPI device registered. This data * is the queue of events received from ACPI that still need to be read from * userspace, the device and char device that userspace is using, a wait queue * used to notify different threads when something has changed, plus a flag * on whether the ACPI device has been removed. */ struct event_device_data { … }; /** * enqueue_events() - Place EC events in queue to be read by userspace. * @adev: Device the events came from. * @buf: Buffer of event data. * @length: Length of event data buffer. * * %buf contains a number of ec_event's, packed one after the other. * Each ec_event is of variable length. Start with the first event, copy it * into a persistent ec_event, store that entry in the queue, move on * to the next ec_event in buf, and repeat. * * Return: 0 on success or negative error code on failure. */ static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) { … } /** * event_device_notify() - Callback when EC generates an event over ACPI. * @adev: The device that the event is coming from. * @value: Value passed to Notify() in ACPI. * * This function will read the events from the device and enqueue them. */ static void event_device_notify(struct acpi_device *adev, u32 value) { … } static int event_open(struct inode *inode, struct file *filp) { … } static __poll_t event_poll(struct file *filp, poll_table *wait) { … } /** * event_read() - Callback for passing event data to userspace via read(). * @filp: The file we are reading from. * @buf: Pointer to userspace buffer to fill with one event. * @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE. * @pos: File position pointer, irrelevant since we don't support seeking. * * Removes the first event from the queue, places it in the passed buffer. * * If there are no events in the queue, then one of two things happens, * depending on if the file was opened in nonblocking mode: If in nonblocking * mode, then return -EAGAIN to say there's no data. If in blocking mode, then * block until an event is available. * * Return: Number of bytes placed in buffer, negative error code on failure. */ static ssize_t event_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { … } static int event_release(struct inode *inode, struct file *filp) { … } static const struct file_operations event_fops = …; /** * free_device_data() - Callback to free the event_device_data structure. * @d: The device embedded in our device data, which we have been ref counting. * * This is called only after event_device_remove() has been called and all * userspace programs have called event_release() on all the open file * descriptors. */ static void free_device_data(struct device *d) { … } static void hangup_device(struct event_device_data *dev_data) { … } /** * event_device_add() - Callback when creating a new device. * @adev: ACPI device that we will be receiving events from. * * This finds a free minor number for the device, allocates and initializes * some device data, and creates a new device and char dev node. * * The device data is freed in free_device_data(), which is called when * %dev_data->dev is release()ed. This happens after all references to * %dev_data->dev are dropped, which happens once both event_device_remove() * has been called and every open()ed file descriptor has been release()ed. * * Return: 0 on success, negative error code on failure. */ static int event_device_add(struct acpi_device *adev) { … } static void event_device_remove(struct acpi_device *adev) { … } static const struct acpi_device_id event_acpi_ids[] = …; MODULE_DEVICE_TABLE(acpi, event_acpi_ids); static struct acpi_driver event_driver = …; static int __init event_module_init(void) { … } static void __exit event_module_exit(void) { … } module_init(…) …; module_exit(event_module_exit); MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …;