linux/drivers/platform/chrome/wilco_ec/event.c

// 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();