Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,39 @@
# SPDX-License-Identifier: GPL-2.0-only
config WILCO_EC
tristate "ChromeOS Wilco Embedded Controller"
depends on X86 || COMPILE_TEST
depends on ACPI && CROS_EC_LPC && LEDS_CLASS
depends on HAS_IOPORT
help
If you say Y here, you get support for talking to the ChromeOS
Wilco EC over an eSPI bus. This uses a simple byte-level protocol
with a checksum.
To compile this driver as a module, choose M here: the
module will be called wilco_ec.
config WILCO_EC_DEBUGFS
tristate "Enable raw access to EC via debugfs"
depends on WILCO_EC
help
If you say Y here, you get support for sending raw commands to
the Wilco EC via debugfs. These commands do not do any byte
manipulation and allow for testing arbitrary commands. This
interface is intended for debug only and will not be present
on production devices.
config WILCO_EC_EVENTS
tristate "Enable event forwarding from EC to userspace"
depends on WILCO_EC
help
If you say Y here, you get support for the EC to send events
(such as power state changes) to userspace. The EC sends the events
over ACPI, and a driver queues up the events to be read by a
userspace daemon from /dev/wilco_event using read() and poll().
config WILCO_EC_TELEMETRY
tristate "Enable querying telemetry data from EC"
depends on WILCO_EC
help
If you say Y here, you get support to query EC telemetry data from
/dev/wilco_telem0 using write() and then read().
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
wilco_ec-objs := core.o keyboard_leds.o mailbox.o \
properties.o sysfs.o
obj-$(CONFIG_WILCO_EC) += wilco_ec.o
wilco_ec_debugfs-objs := debugfs.o
obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o
wilco_ec_events-objs := event.o
obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o
wilco_ec_telem-objs := telemetry.o
obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Core driver for Wilco Embedded Controller
*
* Copyright 2018 Google LLC
*
* This is the entry point for the drivers that control the Wilco EC.
*/
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/platform_device.h>
#include "../cros_ec_lpc_mec.h"
#define DRV_NAME "wilco-ec"
static struct resource *wilco_get_resource(struct platform_device *pdev,
int index)
{
struct device *dev = &pdev->dev;
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_IO, index);
if (!res) {
dev_dbg(dev, "Couldn't find IO resource %d\n", index);
return res;
}
return devm_request_region(dev, res->start, resource_size(res),
dev_name(dev));
}
static int wilco_ec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct wilco_ec_device *ec;
int ret;
ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
if (!ec)
return -ENOMEM;
platform_set_drvdata(pdev, ec);
ec->dev = dev;
mutex_init(&ec->mailbox_lock);
ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE;
ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL);
if (!ec->data_buffer)
return -ENOMEM;
/* Prepare access to IO regions provided by ACPI */
ec->io_data = wilco_get_resource(pdev, 0); /* Host Data */
ec->io_command = wilco_get_resource(pdev, 1); /* Host Command */
ec->io_packet = wilco_get_resource(pdev, 2); /* MEC EMI */
if (!ec->io_data || !ec->io_command || !ec->io_packet)
return -ENODEV;
/* Initialize cros_ec register interface for communication */
cros_ec_lpc_mec_init(ec->io_packet->start,
ec->io_packet->start + EC_MAILBOX_DATA_SIZE);
/*
* Register a child device that will be found by the debugfs driver.
* Ignore failure.
*/
ec->debugfs_pdev = platform_device_register_data(dev,
"wilco-ec-debugfs",
PLATFORM_DEVID_AUTO,
NULL, 0);
/* Register a child device that will be found by the RTC driver. */
ec->rtc_pdev = platform_device_register_data(dev, "rtc-wilco-ec",
PLATFORM_DEVID_AUTO,
NULL, 0);
if (IS_ERR(ec->rtc_pdev)) {
dev_err(dev, "Failed to create RTC platform device\n");
ret = PTR_ERR(ec->rtc_pdev);
goto unregister_debugfs;
}
/* Set up the keyboard backlight LEDs. */
ret = wilco_keyboard_leds_init(ec);
if (ret < 0) {
dev_err(dev,
"Failed to initialize keyboard LEDs: %d\n",
ret);
goto unregister_rtc;
}
ret = wilco_ec_add_sysfs(ec);
if (ret < 0) {
dev_err(dev, "Failed to create sysfs entries: %d\n", ret);
goto unregister_rtc;
}
/* Register child device to be found by charger config driver. */
ec->charger_pdev = platform_device_register_data(dev, "wilco-charger",
PLATFORM_DEVID_AUTO,
NULL, 0);
if (IS_ERR(ec->charger_pdev)) {
dev_err(dev, "Failed to create charger platform device\n");
ret = PTR_ERR(ec->charger_pdev);
goto remove_sysfs;
}
/* Register child device that will be found by the telemetry driver. */
ec->telem_pdev = platform_device_register_data(dev, "wilco_telem",
PLATFORM_DEVID_AUTO,
ec, sizeof(*ec));
if (IS_ERR(ec->telem_pdev)) {
dev_err(dev, "Failed to create telemetry platform device\n");
ret = PTR_ERR(ec->telem_pdev);
goto unregister_charge_config;
}
return 0;
unregister_charge_config:
platform_device_unregister(ec->charger_pdev);
remove_sysfs:
wilco_ec_remove_sysfs(ec);
unregister_rtc:
platform_device_unregister(ec->rtc_pdev);
unregister_debugfs:
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
return ret;
}
static void wilco_ec_remove(struct platform_device *pdev)
{
struct wilco_ec_device *ec = platform_get_drvdata(pdev);
platform_device_unregister(ec->telem_pdev);
platform_device_unregister(ec->charger_pdev);
wilco_ec_remove_sysfs(ec);
platform_device_unregister(ec->rtc_pdev);
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
}
static const struct acpi_device_id wilco_ec_acpi_device_ids[] = {
{ "GOOG000C", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, wilco_ec_acpi_device_ids);
static const struct platform_device_id wilco_ec_id[] = {
{ DRV_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(platform, wilco_ec_id);
static struct platform_driver wilco_ec_driver = {
.driver = {
.name = DRV_NAME,
.acpi_match_table = wilco_ec_acpi_device_ids,
},
.probe = wilco_ec_probe,
.remove = wilco_ec_remove,
.id_table = wilco_ec_id,
};
module_platform_driver(wilco_ec_driver);
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
MODULE_AUTHOR("Duncan Laurie <dlaurie@chromium.org>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ChromeOS Wilco Embedded Controller driver");
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: GPL-2.0
/*
* debugfs attributes for Wilco EC
*
* Copyright 2019 Google LLC
*
* See Documentation/ABI/testing/debugfs-wilco-ec for usage.
*/
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/platform_device.h>
#define DRV_NAME "wilco-ec-debugfs"
/* The raw bytes will take up more space when represented as a hex string */
#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)
struct wilco_ec_debugfs {
struct wilco_ec_device *ec;
struct dentry *dir;
size_t response_size;
u8 raw_data[EC_MAILBOX_DATA_SIZE];
u8 formatted_data[FORMATTED_BUFFER_SIZE];
};
static struct wilco_ec_debugfs *debug_info;
/**
* parse_hex_sentence() - Convert a ascii hex representation into byte array.
* @in: Input buffer of ascii.
* @isize: Length of input buffer.
* @out: Output buffer.
* @osize: Length of output buffer, e.g. max number of bytes to parse.
*
* An valid input is a series of ascii hexadecimal numbers, separated by spaces.
* An example valid input is
* " 00 f2 0 000076 6 0 ff"
*
* If an individual "word" within the hex sentence is longer than MAX_WORD_SIZE,
* then the sentence is illegal, and parsing will fail.
*
* Return: Number of bytes parsed, or negative error code on failure.
*/
static int parse_hex_sentence(const char *in, int isize, u8 *out, int osize)
{
int n_parsed = 0;
int word_start = 0;
int word_end;
int word_len;
/* Temp buffer for holding a "word" of chars that represents one byte */
#define MAX_WORD_SIZE 16
char tmp[MAX_WORD_SIZE + 1];
u8 byte;
while (word_start < isize && n_parsed < osize) {
/* Find the start of the next word */
while (word_start < isize && isspace(in[word_start]))
word_start++;
/* reached the end of the input before next word? */
if (word_start >= isize)
break;
/* Find the end of this word */
word_end = word_start;
while (word_end < isize && !isspace(in[word_end]))
word_end++;
/* Copy to a tmp NULL terminated string */
word_len = word_end - word_start;
if (word_len > MAX_WORD_SIZE)
return -EINVAL;
memcpy(tmp, in + word_start, word_len);
tmp[word_len] = '\0';
/*
* Convert from hex string, place in output. If fails to parse,
* just return -EINVAL because specific error code is only
* relevant for this one word, returning it would be confusing.
*/
if (kstrtou8(tmp, 16, &byte))
return -EINVAL;
out[n_parsed++] = byte;
word_start = word_end;
}
return n_parsed;
}
/* The message type takes up two bytes*/
#define TYPE_AND_DATA_SIZE ((EC_MAILBOX_DATA_SIZE) + 2)
static ssize_t raw_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char *buf = debug_info->formatted_data;
struct wilco_ec_message msg;
u8 request_data[TYPE_AND_DATA_SIZE];
ssize_t kcount;
int ret;
if (count > FORMATTED_BUFFER_SIZE)
return -EINVAL;
kcount = simple_write_to_buffer(buf, FORMATTED_BUFFER_SIZE, ppos,
user_buf, count);
if (kcount < 0)
return kcount;
ret = parse_hex_sentence(buf, kcount, request_data, TYPE_AND_DATA_SIZE);
if (ret < 0)
return ret;
/* Need at least two bytes for message type and one byte of data */
if (ret < 3)
return -EINVAL;
msg.type = request_data[0] << 8 | request_data[1];
msg.flags = 0;
msg.request_data = request_data + 2;
msg.request_size = ret - 2;
memset(debug_info->raw_data, 0, sizeof(debug_info->raw_data));
msg.response_data = debug_info->raw_data;
msg.response_size = EC_MAILBOX_DATA_SIZE;
ret = wilco_ec_mailbox(debug_info->ec, &msg);
if (ret < 0)
return ret;
debug_info->response_size = ret;
return count;
}
static ssize_t raw_read(struct file *file, char __user *user_buf, size_t count,
loff_t *ppos)
{
int fmt_len = 0;
if (debug_info->response_size) {
fmt_len = hex_dump_to_buffer(debug_info->raw_data,
debug_info->response_size,
16, 1, debug_info->formatted_data,
sizeof(debug_info->formatted_data),
true);
/* Only return response the first time it is read */
debug_info->response_size = 0;
}
return simple_read_from_buffer(user_buf, count, ppos,
debug_info->formatted_data, fmt_len);
}
static const struct file_operations fops_raw = {
.owner = THIS_MODULE,
.read = raw_read,
.write = raw_write,
};
#define CMD_KB_CHROME 0x88
#define SUB_CMD_H1_GPIO 0x0A
#define SUB_CMD_TEST_EVENT 0x0B
struct ec_request {
u8 cmd; /* Always CMD_KB_CHROME */
u8 reserved;
u8 sub_cmd;
} __packed;
struct ec_response {
u8 status; /* 0 if allowed */
u8 val;
} __packed;
static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val)
{
struct ec_request rq;
struct ec_response rs;
struct wilco_ec_message msg;
int ret;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_KB_CHROME;
rq.sub_cmd = sub_cmd;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
msg.request_data = &rq;
msg.request_size = sizeof(rq);
msg.response_data = &rs;
msg.response_size = sizeof(rs);
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0)
return ret;
if (rs.status)
return -EIO;
*out_val = rs.val;
return 0;
}
/**
* h1_gpio_get() - Gets h1 gpio status.
* @arg: The wilco EC device.
* @val: BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL
*/
static int h1_gpio_get(void *arg, u64 *val)
{
int ret;
ret = send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val);
if (ret == 0)
*val &= 0xFF;
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n");
/**
* test_event_set() - Sends command to EC to cause an EC test event.
* @arg: The wilco EC device.
* @val: unused.
*/
static int test_event_set(void *arg, u64 val)
{
u8 ret;
return send_ec_cmd(arg, SUB_CMD_TEST_EVENT, &ret);
}
/* Format is unused since it is only required for get method which is NULL */
DEFINE_DEBUGFS_ATTRIBUTE(fops_test_event, NULL, test_event_set, "%llu\n");
/**
* wilco_ec_debugfs_probe() - Create the debugfs node
* @pdev: The platform device, probably created in core.c
*
* Try to create a debugfs node. If it fails, then we don't want to change
* behavior at all, this is for debugging after all. Just fail silently.
*
* Return: 0 always.
*/
static int wilco_ec_debugfs_probe(struct platform_device *pdev)
{
struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
debug_info = devm_kzalloc(&pdev->dev, sizeof(*debug_info), GFP_KERNEL);
if (!debug_info)
return 0;
debug_info->ec = ec;
debug_info->dir = debugfs_create_dir("wilco_ec", NULL);
debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw);
debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec,
&fops_h1_gpio);
debugfs_create_file("test_event", 0200, debug_info->dir, ec,
&fops_test_event);
return 0;
}
static void wilco_ec_debugfs_remove(struct platform_device *pdev)
{
debugfs_remove_recursive(debug_info->dir);
}
static const struct platform_device_id wilco_ec_debugfs_id[] = {
{ DRV_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(platform, wilco_ec_debugfs_id);
static struct platform_driver wilco_ec_debugfs_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = wilco_ec_debugfs_probe,
.remove = wilco_ec_debugfs_remove,
.id_table = wilco_ec_debugfs_id,
};
module_platform_driver(wilco_ec_debugfs_driver);
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Wilco EC debugfs driver");
@@ -0,0 +1,592 @@
// 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/platform_device.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 0x90
/* ACPI Method to execute to retrieve event data buffer from the EC. */
#define EC_ACPI_GET_EVENT "QSET"
/* Maximum number of words in event data returned by the EC. */
#define EC_ACPI_MAX_EVENT_WORDS 6
#define EC_ACPI_MAX_EVENT_SIZE \
(sizeof(struct ec_event) + (EC_ACPI_MAX_EVENT_WORDS) * sizeof(u16))
/* Node will appear in /dev/EVENT_DEV_NAME */
#define EVENT_DEV_NAME "wilco_event"
#define EVENT_CLASS_NAME EVENT_DEV_NAME
#define DRV_NAME EVENT_DEV_NAME
#define EVENT_DEV_NAME_FMT (EVENT_DEV_NAME "%d")
static struct class event_class = {
.name = EVENT_CLASS_NAME,
};
/* Keep track of all the device numbers used. */
#define EVENT_MAX_DEV 128
static int event_major;
static DEFINE_IDA(event_ida);
/* Size of circular queue of events. */
#define MAX_NUM_EVENTS 64
/**
* 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 {
u16 size;
u16 type;
u16 event[];
} __packed;
#define ec_event_num_words(ev) (ev->size - 1)
#define ec_event_size(ev) (sizeof(*ev) + (ec_event_num_words(ev) * sizeof(u16)))
/**
* 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 {
int capacity;
int head;
int tail;
struct ec_event *entries[] __counted_by(capacity);
};
/* Maximum number of events to store in ec_event_queue */
static int queue_size = 64;
module_param(queue_size, int, 0644);
static struct ec_event_queue *event_queue_new(int capacity)
{
struct ec_event_queue *q;
q = kzalloc_flex(*q, entries, capacity);
if (!q)
return NULL;
q->capacity = capacity;
return q;
}
static inline bool event_queue_empty(struct ec_event_queue *q)
{
/* head==tail when both full and empty, but head==NULL when empty */
return q->head == q->tail && !q->entries[q->head];
}
static inline bool event_queue_full(struct ec_event_queue *q)
{
/* head==tail when both full and empty, but head!=NULL when full */
return q->head == q->tail && q->entries[q->head];
}
static struct ec_event *event_queue_pop(struct ec_event_queue *q)
{
struct ec_event *ev;
if (event_queue_empty(q))
return NULL;
ev = q->entries[q->tail];
q->entries[q->tail] = NULL;
q->tail = (q->tail + 1) % q->capacity;
return ev;
}
/*
* 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)
{
struct ec_event *popped = NULL;
if (event_queue_full(q))
popped = event_queue_pop(q);
q->entries[q->head] = ev;
q->head = (q->head + 1) % q->capacity;
return popped;
}
static void event_queue_free(struct ec_event_queue *q)
{
struct ec_event *event;
while ((event = event_queue_pop(q)) != NULL)
kfree(event);
kfree(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 {
struct ec_event_queue *events;
spinlock_t queue_lock;
wait_queue_head_t wq;
struct device dev;
struct cdev cdev;
bool exist;
atomic_t available;
};
/**
* enqueue_events() - Place EC events in queue to be read by userspace.
* @dev: 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 device *dev, const u8 *buf, u32 length)
{
struct event_device_data *dev_data = dev_get_drvdata(dev);
struct ec_event *event, *queue_event, *old_event;
size_t num_words, event_size;
u32 offset = 0;
while (offset < length) {
event = (struct ec_event *)(buf + offset);
num_words = ec_event_num_words(event);
event_size = ec_event_size(event);
if (num_words > EC_ACPI_MAX_EVENT_WORDS) {
dev_err(dev, "Too many event words: %zu > %d\n",
num_words, EC_ACPI_MAX_EVENT_WORDS);
return -EOVERFLOW;
}
/* Ensure event does not overflow the available buffer */
if ((offset + event_size) > length) {
dev_err(dev, "Event exceeds buffer: %zu > %d\n",
offset + event_size, length);
return -EOVERFLOW;
}
/* Point to the next event in the buffer */
offset += event_size;
/* Copy event into the queue */
queue_event = kmemdup(event, event_size, GFP_KERNEL);
if (!queue_event)
return -ENOMEM;
spin_lock(&dev_data->queue_lock);
old_event = event_queue_push(dev_data->events, queue_event);
spin_unlock(&dev_data->queue_lock);
kfree(old_event);
wake_up_interruptible(&dev_data->wq);
}
return 0;
}
/**
* event_device_notify() - Callback when EC generates an event over ACPI.
* @handle: ACPI handle of the device that the event is coming from.
* @value: Value passed to Notify() in ACPI.
* @data: Notify handler data.
*
* This function will read the events from the device and enqueue them.
*/
static void event_device_notify(acpi_handle handle, u32 value, void *data)
{
struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct device *dev = data;
struct acpi_device *adev = ACPI_COMPANION(dev);
union acpi_object *obj;
acpi_status status;
if (value != EC_ACPI_NOTIFY_EVENT) {
dev_err(dev, "Invalid event: 0x%08x\n", value);
return;
}
/* Execute ACPI method to get event data buffer. */
status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT,
NULL, &event_buffer);
if (ACPI_FAILURE(status)) {
dev_err(dev, "Error executing ACPI method %s()\n",
EC_ACPI_GET_EVENT);
return;
}
obj = (union acpi_object *)event_buffer.pointer;
if (!obj) {
dev_err(dev, "Nothing returned from %s()\n",
EC_ACPI_GET_EVENT);
return;
}
if (obj->type != ACPI_TYPE_BUFFER) {
dev_err(dev, "Invalid object returned from %s()\n",
EC_ACPI_GET_EVENT);
kfree(obj);
return;
}
if (obj->buffer.length < sizeof(struct ec_event)) {
dev_err(dev, "Invalid buffer length %d from %s()\n",
obj->buffer.length, EC_ACPI_GET_EVENT);
kfree(obj);
return;
}
enqueue_events(dev, obj->buffer.pointer, obj->buffer.length);
kfree(obj);
}
static int event_open(struct inode *inode, struct file *filp)
{
struct event_device_data *dev_data;
dev_data = container_of(inode->i_cdev, struct event_device_data, cdev);
if (!dev_data->exist)
return -ENODEV;
if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
return -EBUSY;
/* Increase refcount on device so dev_data is not freed */
get_device(&dev_data->dev);
stream_open(inode, filp);
filp->private_data = dev_data;
return 0;
}
static __poll_t event_poll(struct file *filp, poll_table *wait)
{
struct event_device_data *dev_data = filp->private_data;
__poll_t mask = 0;
poll_wait(filp, &dev_data->wq, wait);
if (!dev_data->exist)
return EPOLLHUP;
if (!event_queue_empty(dev_data->events))
mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI;
return mask;
}
/**
* 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)
{
struct event_device_data *dev_data = filp->private_data;
struct ec_event *event;
ssize_t n_bytes_written = 0;
int err;
/* We only will give them the entire event at once */
if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE)
return -EINVAL;
spin_lock(&dev_data->queue_lock);
while (event_queue_empty(dev_data->events)) {
spin_unlock(&dev_data->queue_lock);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
err = wait_event_interruptible(dev_data->wq,
!event_queue_empty(dev_data->events) ||
!dev_data->exist);
if (err)
return err;
/* Device was removed as we waited? */
if (!dev_data->exist)
return -ENODEV;
spin_lock(&dev_data->queue_lock);
}
event = event_queue_pop(dev_data->events);
spin_unlock(&dev_data->queue_lock);
n_bytes_written = ec_event_size(event);
if (copy_to_user(buf, event, n_bytes_written))
n_bytes_written = -EFAULT;
kfree(event);
return n_bytes_written;
}
static int event_release(struct inode *inode, struct file *filp)
{
struct event_device_data *dev_data = filp->private_data;
atomic_set(&dev_data->available, 1);
put_device(&dev_data->dev);
return 0;
}
static const struct file_operations event_fops = {
.open = event_open,
.poll = event_poll,
.read = event_read,
.release = event_release,
.owner = THIS_MODULE,
};
/**
* 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)
{
struct event_device_data *dev_data;
dev_data = container_of(d, struct event_device_data, dev);
event_queue_free(dev_data->events);
kfree(dev_data);
}
static void hangup_device(struct event_device_data *dev_data)
{
dev_data->exist = false;
/* Wake up the waiting processes so they can close. */
wake_up_interruptible(&dev_data->wq);
put_device(&dev_data->dev);
}
/**
* event_device_probe() - Callback when creating a new device.
* @pdev: Platform 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_probe(struct platform_device *pdev)
{
struct event_device_data *dev_data;
struct acpi_device *adev;
int error, minor;
adev = ACPI_COMPANION(&pdev->dev);
if (!adev)
return -ENODEV;
minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL);
if (minor < 0) {
error = minor;
dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
return error;
}
dev_data = kzalloc_obj(*dev_data);
if (!dev_data) {
error = -ENOMEM;
goto free_minor;
}
/* Initialize the device data. */
platform_set_drvdata(pdev, dev_data);
dev_data->events = event_queue_new(queue_size);
if (!dev_data->events) {
kfree(dev_data);
error = -ENOMEM;
goto free_minor;
}
spin_lock_init(&dev_data->queue_lock);
init_waitqueue_head(&dev_data->wq);
dev_data->exist = true;
atomic_set(&dev_data->available, 1);
/* Initialize the device. */
dev_data->dev.devt = MKDEV(event_major, minor);
dev_data->dev.class = &event_class;
dev_data->dev.release = free_device_data;
dev_set_name(&dev_data->dev, EVENT_DEV_NAME_FMT, minor);
device_initialize(&dev_data->dev);
/* Initialize the character device, and add it to userspace. */
cdev_init(&dev_data->cdev, &event_fops);
error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
if (error)
goto free_dev_data;
/* Install an ACPI notify handler. */
error = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
event_device_notify, &pdev->dev);
if (error)
goto free_cdev;
return 0;
free_cdev:
cdev_device_del(&dev_data->cdev, &dev_data->dev);
free_dev_data:
hangup_device(dev_data);
free_minor:
ida_free(&event_ida, minor);
return error;
}
static void event_device_remove(struct platform_device *pdev)
{
struct event_device_data *dev_data = platform_get_drvdata(pdev);
acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
ACPI_DEVICE_NOTIFY, event_device_notify);
cdev_device_del(&dev_data->cdev, &dev_data->dev);
ida_free(&event_ida, MINOR(dev_data->dev.devt));
hangup_device(dev_data);
}
static const struct acpi_device_id event_acpi_ids[] = {
{ "GOOG000D", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, event_acpi_ids);
static struct platform_driver event_driver = {
.probe = event_device_probe,
.remove = event_device_remove,
.driver = {
.name = DRV_NAME,
.acpi_match_table = event_acpi_ids,
},
};
static int __init event_module_init(void)
{
dev_t dev_num = 0;
int ret;
ret = class_register(&event_class);
if (ret) {
pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
return ret;
}
/* Request device numbers, starting with minor=0. Save the major num. */
ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME);
if (ret) {
pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
goto destroy_class;
}
event_major = MAJOR(dev_num);
ret = platform_driver_register(&event_driver);
if (ret < 0) {
pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
goto unregister_region;
}
return 0;
unregister_region:
unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
destroy_class:
class_unregister(&event_class);
ida_destroy(&event_ida);
return ret;
}
static void __exit event_module_exit(void)
{
platform_driver_unregister(&event_driver);
unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV);
class_unregister(&event_class);
ida_destroy(&event_ida);
}
module_init(event_module_init);
module_exit(event_module_exit);
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
MODULE_DESCRIPTION("Wilco EC ACPI event driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Keyboard backlight LED driver for the Wilco Embedded Controller
*
* Copyright 2019 Google LLC
*
* Since the EC will never change the backlight level of its own accord,
* we don't need to implement a brightness_get() method.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/slab.h>
#define WILCO_EC_COMMAND_KBBL 0x75
#define WILCO_KBBL_MODE_FLAG_PWM BIT(1) /* Set brightness by percent. */
#define WILCO_KBBL_DEFAULT_BRIGHTNESS 0
struct wilco_keyboard_leds {
struct wilco_ec_device *ec;
struct led_classdev keyboard;
};
enum wilco_kbbl_subcommand {
WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00,
WILCO_KBBL_SUBCMD_GET_STATE = 0x01,
WILCO_KBBL_SUBCMD_SET_STATE = 0x02,
};
/**
* struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control.
* @command: Always WILCO_EC_COMMAND_KBBL.
* @status: Set by EC to 0 on success, 0xFF on failure.
* @subcmd: One of enum wilco_kbbl_subcommand.
* @reserved3: Should be 0.
* @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM.
* @reserved5to8: Should be 0.
* @percent: Brightness in 0-100. Only meaningful in PWM mode.
* @reserved10to15: Should be 0.
*/
struct wilco_keyboard_leds_msg {
u8 command;
u8 status;
u8 subcmd;
u8 reserved3;
u8 mode;
u8 reserved5to8[4];
u8 percent;
u8 reserved10to15[6];
} __packed;
/* Send a request, get a response, and check that the response is good. */
static int send_kbbl_msg(struct wilco_ec_device *ec,
struct wilco_keyboard_leds_msg *request,
struct wilco_keyboard_leds_msg *response)
{
struct wilco_ec_message msg;
int ret;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
msg.request_data = request;
msg.request_size = sizeof(*request);
msg.response_data = response;
msg.response_size = sizeof(*response);
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0) {
dev_err(ec->dev,
"Failed sending keyboard LEDs command: %d\n", ret);
return ret;
}
return 0;
}
static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness)
{
struct wilco_keyboard_leds_msg request;
struct wilco_keyboard_leds_msg response;
int ret;
memset(&request, 0, sizeof(request));
request.command = WILCO_EC_COMMAND_KBBL;
request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE;
request.mode = WILCO_KBBL_MODE_FLAG_PWM;
request.percent = brightness;
ret = send_kbbl_msg(ec, &request, &response);
if (ret < 0)
return ret;
if (response.status) {
dev_err(ec->dev,
"EC reported failure sending keyboard LEDs command: %d\n",
response.status);
return -EIO;
}
return 0;
}
static int kbbl_exist(struct wilco_ec_device *ec, bool *exists)
{
struct wilco_keyboard_leds_msg request;
struct wilco_keyboard_leds_msg response;
int ret;
memset(&request, 0, sizeof(request));
request.command = WILCO_EC_COMMAND_KBBL;
request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES;
ret = send_kbbl_msg(ec, &request, &response);
if (ret < 0)
return ret;
*exists = response.status != 0xFF;
return 0;
}
/**
* kbbl_init() - Initialize the state of the keyboard backlight.
* @ec: EC device to talk to.
*
* Gets the current brightness, ensuring that the BIOS already initialized the
* backlight to PWM mode. If not in PWM mode, then the current brightness is
* meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS.
*
* Return: Final brightness of the keyboard, or negative error code on failure.
*/
static int kbbl_init(struct wilco_ec_device *ec)
{
struct wilco_keyboard_leds_msg request;
struct wilco_keyboard_leds_msg response;
int ret;
memset(&request, 0, sizeof(request));
request.command = WILCO_EC_COMMAND_KBBL;
request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE;
ret = send_kbbl_msg(ec, &request, &response);
if (ret < 0)
return ret;
if (response.status) {
dev_err(ec->dev,
"EC reported failure sending keyboard LEDs command: %d\n",
response.status);
return -EIO;
}
if (response.mode & WILCO_KBBL_MODE_FLAG_PWM)
return response.percent;
ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS);
if (ret < 0)
return ret;
return WILCO_KBBL_DEFAULT_BRIGHTNESS;
}
static int wilco_keyboard_leds_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct wilco_keyboard_leds *wkl =
container_of(cdev, struct wilco_keyboard_leds, keyboard);
return set_kbbl(wkl->ec, brightness);
}
int wilco_keyboard_leds_init(struct wilco_ec_device *ec)
{
struct wilco_keyboard_leds *wkl;
bool leds_exist;
int ret;
ret = kbbl_exist(ec, &leds_exist);
if (ret < 0) {
dev_err(ec->dev,
"Failed checking keyboard LEDs support: %d\n", ret);
return ret;
}
if (!leds_exist)
return 0;
wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL);
if (!wkl)
return -ENOMEM;
wkl->ec = ec;
wkl->keyboard.name = "platform::kbd_backlight";
wkl->keyboard.max_brightness = 100;
wkl->keyboard.flags = LED_CORE_SUSPENDRESUME;
wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set;
ret = kbbl_init(ec);
if (ret < 0)
return ret;
wkl->keyboard.brightness = ret;
return devm_led_classdev_register(ec->dev, &wkl->keyboard);
}
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Mailbox interface for Wilco Embedded Controller
*
* Copyright 2018 Google LLC
*
* The Wilco EC is similar to a typical ChromeOS embedded controller.
* It uses the same MEC based low-level communication and a similar
* protocol, but with some important differences. The EC firmware does
* not support the same mailbox commands so it is not registered as a
* cros_ec device type.
*
* Most messages follow a standard format, but there are some exceptions
* and an interface is provided to do direct/raw transactions that do not
* make assumptions about byte placement.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/platform_device.h>
#include "../cros_ec_lpc_mec.h"
/* Version of mailbox interface */
#define EC_MAILBOX_VERSION 0
/* Command to start mailbox transaction */
#define EC_MAILBOX_START_COMMAND 0xda
/* Version of EC protocol */
#define EC_MAILBOX_PROTO_VERSION 3
/* Number of header bytes to be counted as data bytes */
#define EC_MAILBOX_DATA_EXTRA 2
/* Maximum timeout */
#define EC_MAILBOX_TIMEOUT HZ
/* EC response flags */
#define EC_CMDR_DATA BIT(0) /* Data ready for host to read */
#define EC_CMDR_PENDING BIT(1) /* Write pending to EC */
#define EC_CMDR_BUSY BIT(2) /* EC is busy processing a command */
#define EC_CMDR_CMD BIT(3) /* Last host write was a command */
/**
* wilco_ec_response_timed_out() - Wait for EC response.
* @ec: EC device.
*
* Return: true if EC timed out, false if EC did not time out.
*/
static bool wilco_ec_response_timed_out(struct wilco_ec_device *ec)
{
unsigned long timeout = jiffies + EC_MAILBOX_TIMEOUT;
do {
if (!(inb(ec->io_command->start) &
(EC_CMDR_PENDING | EC_CMDR_BUSY)))
return false;
usleep_range(100, 200);
} while (time_before(jiffies, timeout));
return true;
}
/**
* wilco_ec_checksum() - Compute 8-bit checksum over data range.
* @data: Data to checksum.
* @size: Number of bytes to checksum.
*
* Return: 8-bit checksum of provided data.
*/
static u8 wilco_ec_checksum(const void *data, size_t size)
{
u8 *data_bytes = (u8 *)data;
u8 checksum = 0;
size_t i;
for (i = 0; i < size; i++)
checksum += data_bytes[i];
return checksum;
}
/**
* wilco_ec_prepare() - Prepare the request structure for the EC.
* @msg: EC message with request information.
* @rq: EC request structure to fill.
*/
static void wilco_ec_prepare(struct wilco_ec_message *msg,
struct wilco_ec_request *rq)
{
memset(rq, 0, sizeof(*rq));
rq->struct_version = EC_MAILBOX_PROTO_VERSION;
rq->mailbox_id = msg->type;
rq->mailbox_version = EC_MAILBOX_VERSION;
rq->data_size = msg->request_size;
/* Checksum header and data */
rq->checksum = wilco_ec_checksum(rq, sizeof(*rq));
rq->checksum += wilco_ec_checksum(msg->request_data, msg->request_size);
rq->checksum = -rq->checksum;
}
/**
* wilco_ec_transfer() - Perform actual data transfer.
* @ec: EC device.
* @msg: EC message data for request and response.
* @rq: Filled in request structure
*
* Context: ec->mailbox_lock should be held while using this function.
* Return: number of bytes received or negative error code on failure.
*/
static int wilco_ec_transfer(struct wilco_ec_device *ec,
struct wilco_ec_message *msg,
struct wilco_ec_request *rq)
{
struct wilco_ec_response *rs;
int ret;
u8 flag;
/* Write request header, then data */
ret = cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq);
if (ret < 0)
return ret;
ret = cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, sizeof(*rq), msg->request_size,
msg->request_data);
if (ret < 0)
return ret;
/* Start the command */
outb(EC_MAILBOX_START_COMMAND, ec->io_command->start);
/* For some commands (eg shutdown) the EC will not respond, that's OK */
if (msg->flags & WILCO_EC_FLAG_NO_RESPONSE) {
dev_dbg(ec->dev, "EC does not respond to this command\n");
return 0;
}
/* Wait for it to complete */
if (wilco_ec_response_timed_out(ec)) {
dev_dbg(ec->dev, "response timed out\n");
return -ETIMEDOUT;
}
/* Check result */
flag = inb(ec->io_data->start);
if (flag) {
dev_dbg(ec->dev, "bad response: 0x%02x\n", flag);
return -EIO;
}
/* Read back response */
rs = ec->data_buffer;
ret = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0,
sizeof(*rs) + EC_MAILBOX_DATA_SIZE,
(u8 *)rs);
if (ret < 0)
return ret;
if (ret) {
dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum);
return -EBADMSG;
}
if (rs->result) {
dev_dbg(ec->dev, "EC reported failure: 0x%02x\n", rs->result);
return -EBADMSG;
}
if (rs->data_size != EC_MAILBOX_DATA_SIZE) {
dev_dbg(ec->dev, "unexpected packet size (%u != %u)\n",
rs->data_size, EC_MAILBOX_DATA_SIZE);
return -EMSGSIZE;
}
if (rs->data_size < msg->response_size) {
dev_dbg(ec->dev, "EC didn't return enough data (%u < %zu)\n",
rs->data_size, msg->response_size);
return -EMSGSIZE;
}
memcpy(msg->response_data, rs->data, msg->response_size);
return rs->data_size;
}
/**
* wilco_ec_mailbox() - Send EC request and receive EC response.
* @ec: EC device.
* @msg: EC message data for request and response.
*
* On entry msg->type, msg->request_size, and msg->request_data should all be
* filled in. If desired, msg->flags can be set.
*
* If a response is expected, msg->response_size should be set, and
* msg->response_data should point to a buffer with enough space. On exit
* msg->response_data will be filled.
*
* Return: number of bytes received or negative error code on failure.
*/
int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg)
{
struct wilco_ec_request *rq;
int ret;
dev_dbg(ec->dev, "type=%04x flags=%02x rslen=%zu rqlen=%zu\n",
msg->type, msg->flags, msg->response_size, msg->request_size);
mutex_lock(&ec->mailbox_lock);
/* Prepare request packet */
rq = ec->data_buffer;
wilco_ec_prepare(msg, rq);
ret = wilco_ec_transfer(ec, msg, rq);
mutex_unlock(&ec->mailbox_lock);
return ret;
}
EXPORT_SYMBOL_GPL(wilco_ec_mailbox);
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 Google LLC
*/
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/unaligned.h>
/* Operation code; what the EC should do with the property */
enum ec_property_op {
EC_OP_GET = 0,
EC_OP_SET = 1,
};
struct ec_property_request {
u8 op; /* One of enum ec_property_op */
u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
u8 length;
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
} __packed;
struct ec_property_response {
u8 reserved[2];
u8 op; /* One of enum ec_property_op */
u8 property_id[4]; /* The 32 bit PID is stored Little Endian */
u8 length;
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
} __packed;
static int send_property_msg(struct wilco_ec_device *ec,
struct ec_property_request *rq,
struct ec_property_response *rs)
{
struct wilco_ec_message ec_msg;
int ret;
memset(&ec_msg, 0, sizeof(ec_msg));
ec_msg.type = WILCO_EC_MSG_PROPERTY;
ec_msg.request_data = rq;
ec_msg.request_size = sizeof(*rq);
ec_msg.response_data = rs;
ec_msg.response_size = sizeof(*rs);
ret = wilco_ec_mailbox(ec, &ec_msg);
if (ret < 0)
return ret;
if (rs->op != rq->op)
return -EBADMSG;
if (memcmp(rq->property_id, rs->property_id, sizeof(rs->property_id)))
return -EBADMSG;
return 0;
}
int wilco_ec_get_property(struct wilco_ec_device *ec,
struct wilco_ec_property_msg *prop_msg)
{
struct ec_property_request rq;
struct ec_property_response rs;
int ret;
memset(&rq, 0, sizeof(rq));
rq.op = EC_OP_GET;
put_unaligned_le32(prop_msg->property_id, rq.property_id);
ret = send_property_msg(ec, &rq, &rs);
if (ret < 0)
return ret;
prop_msg->length = rs.length;
memcpy(prop_msg->data, rs.data, rs.length);
return 0;
}
EXPORT_SYMBOL_GPL(wilco_ec_get_property);
int wilco_ec_set_property(struct wilco_ec_device *ec,
struct wilco_ec_property_msg *prop_msg)
{
struct ec_property_request rq;
struct ec_property_response rs;
int ret;
memset(&rq, 0, sizeof(rq));
rq.op = EC_OP_SET;
put_unaligned_le32(prop_msg->property_id, rq.property_id);
rq.length = prop_msg->length;
memcpy(rq.data, prop_msg->data, prop_msg->length);
ret = send_property_msg(ec, &rq, &rs);
if (ret < 0)
return ret;
if (rs.length != prop_msg->length)
return -EBADMSG;
return 0;
}
EXPORT_SYMBOL_GPL(wilco_ec_set_property);
int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
u8 *val)
{
struct wilco_ec_property_msg msg;
int ret;
msg.property_id = property_id;
ret = wilco_ec_get_property(ec, &msg);
if (ret < 0)
return ret;
if (msg.length != 1)
return -EBADMSG;
*val = msg.data[0];
return 0;
}
EXPORT_SYMBOL_GPL(wilco_ec_get_byte_property);
int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id,
u8 val)
{
struct wilco_ec_property_msg msg;
msg.property_id = property_id;
msg.data[0] = val;
msg.length = 1;
return wilco_ec_set_property(ec, &msg);
}
EXPORT_SYMBOL_GPL(wilco_ec_set_byte_property);
@@ -0,0 +1,250 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 Google LLC
*
* Sysfs properties to view and modify EC-controlled features on Wilco devices.
* The entries will appear under /sys/bus/platform/devices/GOOG000C:00/
*
* See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#define CMD_KB_CMOS 0x7C
#define SUB_CMD_KB_CMOS_AUTO_ON 0x03
struct boot_on_ac_request {
u8 cmd; /* Always CMD_KB_CMOS */
u8 reserved1;
u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */
u8 reserved3to5[3];
u8 val; /* Either 0 or 1 */
u8 reserved7;
} __packed;
#define CMD_USB_CHARGE 0x39
enum usb_charge_op {
USB_CHARGE_GET = 0,
USB_CHARGE_SET = 1,
};
struct usb_charge_request {
u8 cmd; /* Always CMD_USB_CHARGE */
u8 reserved;
u8 op; /* One of enum usb_charge_op */
u8 val; /* When setting, either 0 or 1 */
} __packed;
struct usb_charge_response {
u8 reserved;
u8 status; /* Set by EC to 0 on success, other value on failure */
u8 val; /* When getting, set by EC to either 0 or 1 */
} __packed;
#define CMD_EC_INFO 0x38
enum get_ec_info_op {
CMD_GET_EC_LABEL = 0,
CMD_GET_EC_REV = 1,
CMD_GET_EC_MODEL = 2,
CMD_GET_EC_BUILD_DATE = 3,
};
struct get_ec_info_req {
u8 cmd; /* Always CMD_EC_INFO */
u8 reserved;
u8 op; /* One of enum get_ec_info_op */
} __packed;
struct get_ec_info_resp {
u8 reserved[2];
char value[9]; /* __nonstring: might not be null terminated */
} __packed;
static ssize_t boot_on_ac_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct wilco_ec_device *ec = dev_get_drvdata(dev);
struct boot_on_ac_request rq;
struct wilco_ec_message msg;
int ret;
u8 val;
ret = kstrtou8(buf, 10, &val);
if (ret < 0)
return ret;
if (val > 1)
return -EINVAL;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_KB_CMOS;
rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON;
rq.val = val;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
msg.request_data = &rq;
msg.request_size = sizeof(rq);
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_WO(boot_on_ac);
static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op)
{
struct wilco_ec_device *ec = dev_get_drvdata(dev);
struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op };
struct get_ec_info_resp resp;
int ret;
struct wilco_ec_message msg = {
.type = WILCO_EC_MSG_LEGACY,
.request_data = &req,
.request_size = sizeof(req),
.response_data = &resp,
.response_size = sizeof(resp),
};
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%.*s\n", (int)sizeof(resp.value), (char *)&resp.value);
}
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return get_info(dev, buf, CMD_GET_EC_LABEL);
}
static DEVICE_ATTR_RO(version);
static ssize_t build_revision_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return get_info(dev, buf, CMD_GET_EC_REV);
}
static DEVICE_ATTR_RO(build_revision);
static ssize_t build_date_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return get_info(dev, buf, CMD_GET_EC_BUILD_DATE);
}
static DEVICE_ATTR_RO(build_date);
static ssize_t model_number_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return get_info(dev, buf, CMD_GET_EC_MODEL);
}
static DEVICE_ATTR_RO(model_number);
static int send_usb_charge(struct wilco_ec_device *ec,
struct usb_charge_request *rq,
struct usb_charge_response *rs)
{
struct wilco_ec_message msg;
int ret;
memset(&msg, 0, sizeof(msg));
msg.type = WILCO_EC_MSG_LEGACY;
msg.request_data = rq;
msg.request_size = sizeof(*rq);
msg.response_data = rs;
msg.response_size = sizeof(*rs);
ret = wilco_ec_mailbox(ec, &msg);
if (ret < 0)
return ret;
if (rs->status)
return -EIO;
return 0;
}
static ssize_t usb_charge_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wilco_ec_device *ec = dev_get_drvdata(dev);
struct usb_charge_request rq;
struct usb_charge_response rs;
int ret;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_USB_CHARGE;
rq.op = USB_CHARGE_GET;
ret = send_usb_charge(ec, &rq, &rs);
if (ret < 0)
return ret;
return sysfs_emit(buf, "%d\n", rs.val);
}
static ssize_t usb_charge_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct wilco_ec_device *ec = dev_get_drvdata(dev);
struct usb_charge_request rq;
struct usb_charge_response rs;
int ret;
u8 val;
ret = kstrtou8(buf, 10, &val);
if (ret < 0)
return ret;
if (val > 1)
return -EINVAL;
memset(&rq, 0, sizeof(rq));
rq.cmd = CMD_USB_CHARGE;
rq.op = USB_CHARGE_SET;
rq.val = val;
ret = send_usb_charge(ec, &rq, &rs);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_RW(usb_charge);
static struct attribute *wilco_dev_attrs[] = {
&dev_attr_boot_on_ac.attr,
&dev_attr_build_date.attr,
&dev_attr_build_revision.attr,
&dev_attr_model_number.attr,
&dev_attr_usb_charge.attr,
&dev_attr_version.attr,
NULL,
};
static const struct attribute_group wilco_dev_attr_group = {
.attrs = wilco_dev_attrs,
};
int wilco_ec_add_sysfs(struct wilco_ec_device *ec)
{
return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group);
}
void wilco_ec_remove_sysfs(struct wilco_ec_device *ec)
{
sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group);
}
@@ -0,0 +1,474 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Telemetry communication for Wilco EC
*
* Copyright 2019 Google LLC
*
* The Wilco Embedded Controller is able to send telemetry data
* which is useful for enterprise applications. A daemon running on
* the OS sends a command to the EC via a write() to a char device,
* and can read the response with a read(). The write() request is
* verified by the driver to ensure that it is performing only one
* of the allowlisted commands, and that no extraneous data is
* being transmitted to the EC. The response is passed directly
* back to the reader with no modification.
*
* The character device will appear as /dev/wilco_telemN, where N
* is some small non-negative integer, starting with 0. Only one
* process may have the file descriptor open at a time. The calling
* userspace program needs to keep the device file descriptor open
* between the calls to write() and read() in order to preserve the
* response. Up to 32 bytes will be available for reading.
*
* For testing purposes, try requesting the EC's firmware build
* date, by sending the WILCO_EC_TELEM_GET_VERSION command with
* argument index=3. i.e. write [0x38, 0x00, 0x03]
* to the device node. An ASCII string of the build date is
* returned.
*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#define TELEM_DEV_NAME "wilco_telem"
#define TELEM_CLASS_NAME TELEM_DEV_NAME
#define DRV_NAME TELEM_DEV_NAME
#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d")
static struct class telem_class = {
.name = TELEM_CLASS_NAME,
};
/* Keep track of all the device numbers used. */
#define TELEM_MAX_DEV 128
static int telem_major;
static DEFINE_IDA(telem_ida);
/* EC telemetry command codes */
#define WILCO_EC_TELEM_GET_LOG 0x99
#define WILCO_EC_TELEM_GET_VERSION 0x38
#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E
#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA
#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95
#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C
#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07
#define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A
#define TELEM_ARGS_SIZE_MAX 30
/*
* The following telem_args_get_* structs are embedded within the |args| field
* of wilco_ec_telem_request.
*/
struct telem_args_get_log {
u8 log_type;
u8 log_index;
} __packed;
/*
* Get a piece of info about the EC firmware version:
* 0 = label
* 1 = svn_rev
* 2 = model_no
* 3 = build_date
* 4 = frio_version
*/
struct telem_args_get_version {
u8 index;
} __packed;
struct telem_args_get_fan_info {
u8 command;
u8 fan_number;
u8 arg;
} __packed;
struct telem_args_get_diag_info {
u8 type;
u8 sub_type;
} __packed;
struct telem_args_get_temp_info {
u8 command;
u8 index;
u8 field;
u8 zone;
} __packed;
struct telem_args_get_temp_read {
u8 sensor_index;
} __packed;
struct telem_args_get_batt_ext_info {
u8 var_args[5];
} __packed;
struct telem_args_get_batt_ppid_info {
u8 always1; /* Should always be 1 */
} __packed;
/**
* struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
* @command: One of WILCO_EC_TELEM_GET_* command codes.
* @reserved: Must be 0.
* @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
*/
struct wilco_ec_telem_request {
u8 command;
u8 reserved;
union {
u8 buf[TELEM_ARGS_SIZE_MAX];
struct telem_args_get_log get_log;
struct telem_args_get_version get_version;
struct telem_args_get_fan_info get_fan_info;
struct telem_args_get_diag_info get_diag_info;
struct telem_args_get_temp_info get_temp_info;
struct telem_args_get_temp_read get_temp_read;
struct telem_args_get_batt_ext_info get_batt_ext_info;
struct telem_args_get_batt_ppid_info get_batt_ppid_info;
} args;
} __packed;
/**
* check_telem_request() - Ensure that a request from userspace is valid.
* @rq: Request buffer copied from userspace.
* @size: Number of bytes copied from userspace.
*
* Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
* -EMSGSIZE if the request is too long.
*
* We do not want to allow userspace to send arbitrary telemetry commands to
* the EC. Therefore we check to ensure that
* 1. The request follows the format of struct wilco_ec_telem_request.
* 2. The supplied command code is one of the allowlisted commands.
* 3. The request only contains the necessary data for the header and arguments.
*/
static int check_telem_request(struct wilco_ec_telem_request *rq,
size_t size)
{
size_t max_size = offsetof(struct wilco_ec_telem_request, args);
if (rq->reserved)
return -EINVAL;
switch (rq->command) {
case WILCO_EC_TELEM_GET_LOG:
max_size += sizeof(rq->args.get_log);
break;
case WILCO_EC_TELEM_GET_VERSION:
max_size += sizeof(rq->args.get_version);
break;
case WILCO_EC_TELEM_GET_FAN_INFO:
max_size += sizeof(rq->args.get_fan_info);
break;
case WILCO_EC_TELEM_GET_DIAG_INFO:
max_size += sizeof(rq->args.get_diag_info);
break;
case WILCO_EC_TELEM_GET_TEMP_INFO:
max_size += sizeof(rq->args.get_temp_info);
break;
case WILCO_EC_TELEM_GET_TEMP_READ:
max_size += sizeof(rq->args.get_temp_read);
break;
case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
max_size += sizeof(rq->args.get_batt_ext_info);
break;
case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
if (rq->args.get_batt_ppid_info.always1 != 1)
return -EINVAL;
max_size += sizeof(rq->args.get_batt_ppid_info);
break;
default:
return -EINVAL;
}
return (size <= max_size) ? 0 : -EMSGSIZE;
}
/**
* struct telem_device_data - Data for a Wilco EC device that queries telemetry.
* @cdev: Char dev that userspace reads and polls from.
* @dev: Device associated with the %cdev.
* @ec: Wilco EC that we will be communicating with using the mailbox interface.
* @available: Boolean of if the device can be opened.
*/
struct telem_device_data {
struct device dev;
struct cdev cdev;
struct wilco_ec_device *ec;
atomic_t available;
};
#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
/**
* struct telem_session_data - Data that exists between open() and release().
* @dev_data: Pointer to get back to the device data and EC.
* @request: Command and arguments sent to EC.
* @response: Response buffer of data from EC.
* @has_msg: Is there data available to read from a previous write?
*/
struct telem_session_data {
struct telem_device_data *dev_data;
struct wilco_ec_telem_request request;
u8 response[TELEM_RESPONSE_SIZE];
bool has_msg;
};
/**
* telem_open() - Callback for when the device node is opened.
* @inode: inode for this char device node.
* @filp: file for this char device node.
*
* We need to ensure that after writing a command to the device,
* the same userspace process reads the corresponding result.
* Therefore, we increment a refcount on opening the device, so that
* only one process can communicate with the EC at a time.
*
* Return: 0 on success, or negative error code on failure.
*/
static int telem_open(struct inode *inode, struct file *filp)
{
struct telem_device_data *dev_data;
struct telem_session_data *sess_data;
/* Ensure device isn't already open */
dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
return -EBUSY;
get_device(&dev_data->dev);
sess_data = kzalloc_obj(*sess_data);
if (!sess_data) {
atomic_set(&dev_data->available, 1);
return -ENOMEM;
}
sess_data->dev_data = dev_data;
sess_data->has_msg = false;
stream_open(inode, filp);
filp->private_data = sess_data;
return 0;
}
static ssize_t telem_write(struct file *filp, const char __user *buf,
size_t count, loff_t *pos)
{
struct telem_session_data *sess_data = filp->private_data;
struct wilco_ec_message msg = {};
int ret;
if (count > sizeof(sess_data->request))
return -EMSGSIZE;
memset(&sess_data->request, 0, sizeof(sess_data->request));
if (copy_from_user(&sess_data->request, buf, count))
return -EFAULT;
ret = check_telem_request(&sess_data->request, count);
if (ret < 0)
return ret;
memset(sess_data->response, 0, sizeof(sess_data->response));
msg.type = WILCO_EC_MSG_TELEMETRY;
msg.request_data = &sess_data->request;
msg.request_size = sizeof(sess_data->request);
msg.response_data = sess_data->response;
msg.response_size = sizeof(sess_data->response);
ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
if (ret < 0)
return ret;
if (ret != sizeof(sess_data->response))
return -EMSGSIZE;
sess_data->has_msg = true;
return count;
}
static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
loff_t *pos)
{
struct telem_session_data *sess_data = filp->private_data;
if (!sess_data->has_msg)
return -ENODATA;
if (count > sizeof(sess_data->response))
return -EINVAL;
if (copy_to_user(buf, sess_data->response, count))
return -EFAULT;
sess_data->has_msg = false;
return count;
}
static int telem_release(struct inode *inode, struct file *filp)
{
struct telem_session_data *sess_data = filp->private_data;
atomic_set(&sess_data->dev_data->available, 1);
put_device(&sess_data->dev_data->dev);
kfree(sess_data);
return 0;
}
static const struct file_operations telem_fops = {
.open = telem_open,
.write = telem_write,
.read = telem_read,
.release = telem_release,
.owner = THIS_MODULE,
};
/**
* telem_device_free() - Callback to free the telem_device_data structure.
* @d: The device embedded in our device data, which we have been ref counting.
*
* Once all open file descriptors are closed and the device has been removed,
* the refcount of the device will fall to 0 and this will be called.
*/
static void telem_device_free(struct device *d)
{
struct telem_device_data *dev_data;
dev_data = container_of(d, struct telem_device_data, dev);
kfree(dev_data);
}
/**
* telem_device_probe() - Callback when creating a new device.
* @pdev: platform device that we will be receiving telems 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.
*
* Return: 0 on success, negative error code on failure.
*/
static int telem_device_probe(struct platform_device *pdev)
{
struct telem_device_data *dev_data;
int error, minor;
/* Get the next available device number */
minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
if (minor < 0) {
error = minor;
dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
return error;
}
dev_data = kzalloc_obj(*dev_data);
if (!dev_data) {
ida_free(&telem_ida, minor);
return -ENOMEM;
}
/* Initialize the device data */
dev_data->ec = dev_get_platdata(&pdev->dev);
atomic_set(&dev_data->available, 1);
platform_set_drvdata(pdev, dev_data);
/* Initialize the device */
dev_data->dev.devt = MKDEV(telem_major, minor);
dev_data->dev.class = &telem_class;
dev_data->dev.release = telem_device_free;
dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
device_initialize(&dev_data->dev);
/* Initialize the character device and add it to userspace */
cdev_init(&dev_data->cdev, &telem_fops);
error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
if (error) {
put_device(&dev_data->dev);
ida_free(&telem_ida, minor);
return error;
}
return 0;
}
static void telem_device_remove(struct platform_device *pdev)
{
struct telem_device_data *dev_data = platform_get_drvdata(pdev);
cdev_device_del(&dev_data->cdev, &dev_data->dev);
ida_free(&telem_ida, MINOR(dev_data->dev.devt));
put_device(&dev_data->dev);
}
static const struct platform_device_id telem_id[] = {
{ DRV_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(platform, telem_id);
static struct platform_driver telem_driver = {
.probe = telem_device_probe,
.remove = telem_device_remove,
.driver = {
.name = DRV_NAME,
},
.id_table = telem_id,
};
static int __init telem_module_init(void)
{
dev_t dev_num = 0;
int ret;
ret = class_register(&telem_class);
if (ret) {
pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
return ret;
}
/* Request the kernel for device numbers, starting with minor=0 */
ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
if (ret) {
pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
goto destroy_class;
}
telem_major = MAJOR(dev_num);
ret = platform_driver_register(&telem_driver);
if (ret < 0) {
pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
goto unregister_region;
}
return 0;
unregister_region:
unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
destroy_class:
class_unregister(&telem_class);
ida_destroy(&telem_ida);
return ret;
}
static void __exit telem_module_exit(void)
{
platform_driver_unregister(&telem_driver);
unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
class_unregister(&telem_class);
ida_destroy(&telem_ida);
}
module_init(telem_module_init);
module_exit(telem_module_exit);
MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
MODULE_DESCRIPTION("Wilco EC telemetry driver");
MODULE_LICENSE("GPL");