From e7c256fbfb157885d36ffcf03d981fa8b21e8fec Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:25 +0100 Subject: platform/chrome: Add Chrome OS EC userspace device interface This patch adds a device interface to access the Chrome OS Embedded Controller from user-space. Signed-off-by: Bill Richardson Reviewed-by: Simon Glass Signed-off-by: Javier Martinez Canillas Reviewed-by: Gwendal Grignou Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_dev.c | 268 ++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 drivers/platform/chrome/cros_ec_dev.c (limited to 'drivers/platform/chrome/cros_ec_dev.c') diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c new file mode 100644 index 000000000000..94c1442d5104 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -0,0 +1,268 @@ +/* + * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space + * + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "cros_ec_dev.h" + +/* Device variables */ +#define CROS_MAX_DEV 128 +static struct class *cros_class; +static int ec_major; + +/* Basic communication */ +static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) +{ + struct ec_response_get_version *resp; + static const char * const current_image_name[] = { + "unknown", "read-only", "read-write", "invalid", + }; + struct cros_ec_command msg = { + .version = 0, + .command = EC_CMD_GET_VERSION, + .outdata = { 0 }, + .outsize = 0, + .indata = { 0 }, + .insize = sizeof(*resp), + }; + int ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) { + snprintf(str, maxlen, + "%s\nUnknown EC version: EC returned %d\n", + CROS_EC_DEV_VERSION, msg.result); + return 0; + } + + resp = (struct ec_response_get_version *)msg.indata; + if (resp->current_image >= ARRAY_SIZE(current_image_name)) + resp->current_image = 3; /* invalid */ + + snprintf(str, maxlen, "%s\n%s\n%s\n\%s\n", CROS_EC_DEV_VERSION, + resp->version_string_ro, resp->version_string_rw, + current_image_name[resp->current_image]); + + return 0; +} + +/* Device file ops */ +static int ec_device_open(struct inode *inode, struct file *filp) +{ + filp->private_data = container_of(inode->i_cdev, + struct cros_ec_device, cdev); + return 0; +} + +static int ec_device_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static ssize_t ec_device_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + struct cros_ec_device *ec = filp->private_data; + char msg[sizeof(struct ec_response_get_version) + + sizeof(CROS_EC_DEV_VERSION)]; + size_t count; + int ret; + + if (*offset != 0) + return 0; + + ret = ec_get_version(ec, msg, sizeof(msg)); + if (ret) + return ret; + + count = min(length, strlen(msg)); + + if (copy_to_user(buffer, msg, count)) + return -EFAULT; + + *offset = count; + return count; +} + +/* Ioctls */ +static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) +{ + long ret; + struct cros_ec_command s_cmd = { }; + + if (copy_from_user(&s_cmd, arg, sizeof(s_cmd))) + return -EFAULT; + + ret = cros_ec_cmd_xfer(ec, &s_cmd); + /* Only copy data to userland if data was received. */ + if (ret < 0) + return ret; + + if (copy_to_user(arg, &s_cmd, sizeof(s_cmd))) + return -EFAULT; + + return 0; +} + +static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) +{ + struct cros_ec_readmem s_mem = { }; + long num; + + /* Not every platform supports direct reads */ + if (!ec->cmd_readmem) + return -ENOTTY; + + if (copy_from_user(&s_mem, arg, sizeof(s_mem))) + return -EFAULT; + + num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer); + if (num <= 0) + return num; + + if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) + return -EFAULT; + + return 0; +} + +static long ec_device_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct cros_ec_device *ec = filp->private_data; + + if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) + return -ENOTTY; + + switch (cmd) { + case CROS_EC_DEV_IOCXCMD: + return ec_device_ioctl_xcmd(ec, (void __user *)arg); + case CROS_EC_DEV_IOCRDMEM: + return ec_device_ioctl_readmem(ec, (void __user *)arg); + } + + return -ENOTTY; +} + +/* Module initialization */ +static const struct file_operations fops = { + .open = ec_device_open, + .release = ec_device_release, + .read = ec_device_read, + .unlocked_ioctl = ec_device_ioctl, +}; + +static int ec_device_probe(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + int retval = -ENOTTY; + dev_t devno = MKDEV(ec_major, 0); + + /* Instantiate it (and remember the EC) */ + cdev_init(&ec->cdev, &fops); + + retval = cdev_add(&ec->cdev, devno, 1); + if (retval) { + dev_err(&pdev->dev, ": failed to add character device\n"); + return retval; + } + + ec->vdev = device_create(cros_class, NULL, devno, ec, + CROS_EC_DEV_NAME); + if (IS_ERR(ec->vdev)) { + retval = PTR_ERR(ec->vdev); + dev_err(&pdev->dev, ": failed to create device\n"); + cdev_del(&ec->cdev); + return retval; + } + + return 0; +} + +static int ec_device_remove(struct platform_device *pdev) +{ + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + + device_destroy(cros_class, MKDEV(ec_major, 0)); + cdev_del(&ec->cdev); + return 0; +} + +static struct platform_driver cros_ec_dev_driver = { + .driver = { + .name = "cros-ec-ctl", + }, + .probe = ec_device_probe, + .remove = ec_device_remove, +}; + +static int __init cros_ec_dev_init(void) +{ + int ret; + dev_t dev = 0; + + cros_class = class_create(THIS_MODULE, "chromeos"); + if (IS_ERR(cros_class)) { + pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); + return PTR_ERR(cros_class); + } + + /* Get a range of minor numbers (starting with 0) to work with */ + ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); + if (ret < 0) { + pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); + goto failed_chrdevreg; + } + ec_major = MAJOR(dev); + + /* Register the driver */ + ret = platform_driver_register(&cros_ec_dev_driver); + if (ret < 0) { + pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); + goto failed_devreg; + } + return 0; + +failed_devreg: + unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); +failed_chrdevreg: + class_destroy(cros_class); + return ret; +} + +static void __exit cros_ec_dev_exit(void) +{ + platform_driver_unregister(&cros_ec_dev_driver); + unregister_chrdev(ec_major, CROS_EC_DEV_NAME); + class_destroy(cros_class); +} + +module_init(cros_ec_dev_init); +module_exit(cros_ec_dev_exit); + +MODULE_AUTHOR("Bill Richardson "); +MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 71af4b52cc22a8d0f7b66a51427a804741a045b6 Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:27 +0100 Subject: platform/chrome: Create sysfs attributes for the ChromeOS EC This adds the first few sysfs attributes for the Chrome OS EC. These controls are made available under /sys/devices/virtual/chromeos/cros_ec flashinfo - display current flash info reboot - tell the EC to reboot in various ways version - information about the EC software and hardware Future changes will build on this to add additional controls. From a root shell, you should be able to do things like this: cd /sys/devices/virtual/chromeos/cros_ec cat flashinfo cat version echo rw > reboot cat version echo ro > reboot cat version echo rw > reboot cat version echo cold > reboot That last command will reboot the AP too. Signed-off-by: Bill Richardson Reviewed-by: Olof Johansson Signed-off-by: Javier Martinez Canillas Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/Makefile | 3 +- drivers/platform/chrome/cros_ec_dev.c | 4 + drivers/platform/chrome/cros_ec_dev.h | 3 + drivers/platform/chrome/cros_ec_sysfs.c | 271 ++++++++++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/chrome/cros_ec_sysfs.c (limited to 'drivers/platform/chrome/cros_ec_dev.c') diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index ec682900f889..34b631ceff67 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o -obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_dev.o +cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o +obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 94c1442d5104..33f37ad36892 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -198,6 +198,9 @@ static int ec_device_probe(struct platform_device *pdev) return retval; } + /* Initialize extra interfaces */ + ec_dev_sysfs_init(ec); + return 0; } @@ -205,6 +208,7 @@ static int ec_device_remove(struct platform_device *pdev) { struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + ec_dev_sysfs_remove(ec); device_destroy(cros_class, MKDEV(ec_major, 0)); cdev_del(&ec->cdev); return 0; diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index 15c54c4c5531..f03613290ecf 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -44,4 +44,7 @@ struct cros_ec_readmem { #define CROS_EC_DEV_IOCXCMD _IOWR(CROS_EC_DEV_IOC, 0, struct cros_ec_command) #define CROS_EC_DEV_IOCRDMEM _IOWR(CROS_EC_DEV_IOC, 1, struct cros_ec_readmem) +void ec_dev_sysfs_init(struct cros_ec_device *); +void ec_dev_sysfs_remove(struct cros_ec_device *); + #endif /* _CROS_EC_DEV_H_ */ diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c new file mode 100644 index 000000000000..fb62ab6cc659 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -0,0 +1,271 @@ +/* + * cros_ec_sysfs - expose the Chrome OS EC through sysfs + * + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define pr_fmt(fmt) "cros_ec_sysfs: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_dev.h" + +/* Accessor functions */ + +static ssize_t show_ec_reboot(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int count = 0; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "ro|rw|cancel|cold|disable-jump|hibernate"); + count += scnprintf(buf + count, PAGE_SIZE - count, + " [at-shutdown]\n"); + return count; +} + +static ssize_t store_ec_reboot(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + static const struct { + const char * const str; + uint8_t cmd; + uint8_t flags; + } words[] = { + {"cancel", EC_REBOOT_CANCEL, 0}, + {"ro", EC_REBOOT_JUMP_RO, 0}, + {"rw", EC_REBOOT_JUMP_RW, 0}, + {"cold", EC_REBOOT_COLD, 0}, + {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, + {"hibernate", EC_REBOOT_HIBERNATE, 0}, + {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, + }; + struct cros_ec_command msg = { 0 }; + struct ec_params_reboot_ec *param = + (struct ec_params_reboot_ec *)msg.outdata; + int got_cmd = 0, offset = 0; + int i; + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + param->flags = 0; + while (1) { + /* Find word to start scanning */ + while (buf[offset] && isspace(buf[offset])) + offset++; + if (!buf[offset]) + break; + + for (i = 0; i < ARRAY_SIZE(words); i++) { + if (!strncasecmp(words[i].str, buf+offset, + strlen(words[i].str))) { + if (words[i].flags) { + param->flags |= words[i].flags; + } else { + param->cmd = words[i].cmd; + got_cmd = 1; + } + break; + } + } + + /* On to the next word, if any */ + while (buf[offset] && !isspace(buf[offset])) + offset++; + } + + if (!got_cmd) + return -EINVAL; + + msg.command = EC_CMD_REBOOT_EC; + msg.outsize = sizeof(param); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) { + dev_dbg(ec->dev, "EC result %d\n", msg.result); + return -EINVAL; + } + + return count; +} + +static ssize_t show_ec_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + static const char * const image_names[] = {"unknown", "RO", "RW"}; + struct ec_response_get_version *r_ver; + struct ec_response_get_chip_info *r_chip; + struct ec_response_board_version *r_board; + struct cros_ec_command msg = { 0 }; + int ret; + int count = 0; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + /* Get versions. RW may change. */ + msg.command = EC_CMD_GET_VERSION; + msg.insize = sizeof(*r_ver); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + r_ver = (struct ec_response_get_version *)msg.indata; + /* Strings should be null-terminated, but let's be sure. */ + r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; + r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "RO version: %s\n", r_ver->version_string_ro); + count += scnprintf(buf + count, PAGE_SIZE - count, + "RW version: %s\n", r_ver->version_string_rw); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Firmware copy: %s\n", + (r_ver->current_image < ARRAY_SIZE(image_names) ? + image_names[r_ver->current_image] : "?")); + + /* Get build info. */ + msg.command = EC_CMD_GET_BUILD_INFO; + msg.insize = sizeof(msg.indata); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: EC error %d\n", msg.result); + else { + msg.indata[sizeof(msg.indata) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "Build info: %s\n", msg.indata); + } + + /* Get chip info. */ + msg.command = EC_CMD_GET_CHIP_INFO; + msg.insize = sizeof(*r_chip); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip info: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip info: EC error %d\n", msg.result); + else { + r_chip = (struct ec_response_get_chip_info *)msg.indata; + + r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; + r_chip->name[sizeof(r_chip->name) - 1] = '\0'; + r_chip->revision[sizeof(r_chip->revision) - 1] = '\0'; + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip vendor: %s\n", r_chip->vendor); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip name: %s\n", r_chip->name); + count += scnprintf(buf + count, PAGE_SIZE - count, + "Chip revision: %s\n", r_chip->revision); + } + + /* Get board version */ + msg.command = EC_CMD_GET_BOARD_VERSION; + msg.insize = sizeof(*r_board); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: XFER ERROR %d\n", ret); + else if (msg.result != EC_RES_SUCCESS) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: EC error %d\n", msg.result); + else { + r_board = (struct ec_response_board_version *)msg.indata; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "Board version: %d\n", + r_board->board_version); + } + + return count; +} + +static ssize_t show_ec_flashinfo(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_response_flash_info *resp; + struct cros_ec_command msg = { 0 }; + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + /* The flash info shouldn't ever change, but ask each time anyway. */ + msg.command = EC_CMD_FLASH_INFO; + msg.insize = sizeof(*resp); + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + resp = (struct ec_response_flash_info *)msg.indata; + + return scnprintf(buf, PAGE_SIZE, + "FlashSize %d\nWriteSize %d\n" + "EraseSize %d\nProtectSize %d\n", + resp->flash_size, resp->write_block_size, + resp->erase_block_size, resp->protect_block_size); +} + +/* Module initialization */ + +static DEVICE_ATTR(reboot, S_IWUSR | S_IRUGO, show_ec_reboot, store_ec_reboot); +static DEVICE_ATTR(version, S_IRUGO, show_ec_version, NULL); +static DEVICE_ATTR(flashinfo, S_IRUGO, show_ec_flashinfo, NULL); + +static struct attribute *__ec_attrs[] = { + &dev_attr_reboot.attr, + &dev_attr_version.attr, + &dev_attr_flashinfo.attr, + NULL, +}; + +static struct attribute_group ec_attr_group = { + .attrs = __ec_attrs, +}; + +void ec_dev_sysfs_init(struct cros_ec_device *ec) +{ + int error; + + error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); + if (error) + pr_warn("failed to create group: %d\n", error); +} + +void ec_dev_sysfs_remove(struct cros_ec_device *ec) +{ + sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); +} -- cgit v1.2.3 From f3f837e52b14bf84c2db65f622b5c31cd261100c Mon Sep 17 00:00:00 2001 From: Bill Richardson Date: Mon, 2 Feb 2015 12:26:28 +0100 Subject: platform/chrome: Expose Chrome OS Lightbar to users This adds some sysfs entries to provide userspace control of the four-element LED "lightbar" on the Chromebook Pixel. This only instantiates the lightbar controls if the device actually exists. To prevent DoS attacks, this interface is limited to 20 accesses/second, although that rate can be adjusted by a privileged user. On Chromebooks without a lightbar, this should have no effect. On the Chromebook Pixel, you should be able to do things like this: $ cd /sys/devices/virtual/chromeos/cros_ec/lightbar $ echo 0x80 > brightness $ echo 255 > brightness $ $ cat sequence S0 $ echo konami > sequence $ cat sequence KONAMI $ $ cat sequence S0 And $ cd /sys/devices/virtual/chromeos/cros_ec/lightbar $ echo stop > sequence $ echo "4 255 255 255" > led_rgb $ echo "0 255 0 0 1 0 255 0 2 0 0 255 3 255 255 0" > led_rgb $ echo run > sequence Test the DoS prevention with this: $ cd /sys/devices/virtual/chromeos/cros_ec/lightbar $ echo 500 > interval_msec $ time (cat version version version version version version version) Signed-off-by: Bill Richardson Reviewed-by: Olof Johansson Tested-by: Doug Anderson Reviewed-by: Benson Leung Signed-off-by: Javier Martinez Canillas Tested-by: Gwendal Grignou Reviewed-by: Gwendal Grignou Signed-off-by: Olof Johansson --- drivers/platform/chrome/Makefile | 2 +- drivers/platform/chrome/cros_ec_dev.c | 2 + drivers/platform/chrome/cros_ec_dev.h | 3 + drivers/platform/chrome/cros_ec_lightbar.c | 367 +++++++++++++++++++++++++++++ 4 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/chrome/cros_ec_lightbar.c (limited to 'drivers/platform/chrome/cros_ec_dev.c') diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 34b631ceff67..bd8d8601e875 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,6 +1,6 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o -cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o +cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o cros_ec_lightbar.o obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 33f37ad36892..ce714317a59e 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -200,6 +200,7 @@ static int ec_device_probe(struct platform_device *pdev) /* Initialize extra interfaces */ ec_dev_sysfs_init(ec); + ec_dev_lightbar_init(ec); return 0; } @@ -208,6 +209,7 @@ static int ec_device_remove(struct platform_device *pdev) { struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); + ec_dev_lightbar_remove(ec); ec_dev_sysfs_remove(ec); device_destroy(cros_class, MKDEV(ec_major, 0)); cdev_del(&ec->cdev); diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index f03613290ecf..45d67f7e518c 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h @@ -47,4 +47,7 @@ struct cros_ec_readmem { void ec_dev_sysfs_init(struct cros_ec_device *); void ec_dev_sysfs_remove(struct cros_ec_device *); +void ec_dev_lightbar_init(struct cros_ec_device *); +void ec_dev_lightbar_remove(struct cros_ec_device *); + #endif /* _CROS_EC_DEV_H_ */ diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c new file mode 100644 index 000000000000..35fc892e4c95 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -0,0 +1,367 @@ +/* + * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace + * + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define pr_fmt(fmt) "cros_ec_lightbar: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_dev.h" + +/* Rate-limit the lightbar interface to prevent DoS. */ +static unsigned long lb_interval_jiffies = 50 * HZ / 1000; + +static ssize_t interval_msec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long msec = lb_interval_jiffies * 1000 / HZ; + + return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); +} + +static ssize_t interval_msec_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long msec; + + if (kstrtoul(buf, 0, &msec)) + return -EINVAL; + + lb_interval_jiffies = msec * HZ / 1000; + + return count; +} + +static DEFINE_MUTEX(lb_mutex); +/* Return 0 if able to throttle correctly, error otherwise */ +static int lb_throttle(void) +{ + static unsigned long last_access; + unsigned long now, next_timeslot; + long delay; + int ret = 0; + + mutex_lock(&lb_mutex); + + now = jiffies; + next_timeslot = last_access + lb_interval_jiffies; + + if (time_before(now, next_timeslot)) { + delay = (long)(next_timeslot) - (long)now; + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(delay) > 0) { + /* interrupted - just abort */ + ret = -EINTR; + goto out; + } + now = jiffies; + } + + last_access = now; +out: + mutex_unlock(&lb_mutex); + + return ret; +} + +#define INIT_MSG(P, R) { \ + .command = EC_CMD_LIGHTBAR_CMD, \ + .outsize = sizeof(*P), \ + .insize = sizeof(*R), \ + } + +static int get_lightbar_version(struct cros_ec_device *ec, + uint32_t *ver_ptr, uint32_t *flg_ptr) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_VERSION; + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return 0; + + switch (msg.result) { + case EC_RES_INVALID_PARAM: + /* Pixel had no version command. */ + if (ver_ptr) + *ver_ptr = 0; + if (flg_ptr) + *flg_ptr = 0; + return 1; + + case EC_RES_SUCCESS: + resp = (struct ec_response_lightbar *)msg.indata; + + /* Future devices w/lightbars should implement this command */ + if (ver_ptr) + *ver_ptr = resp->version.num; + if (flg_ptr) + *flg_ptr = resp->version.flags; + return 1; + } + + /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ + return 0; +} + +static ssize_t version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint32_t version, flags; + struct cros_ec_device *ec = dev_get_drvdata(dev); + int ret; + + ret = lb_throttle(); + if (ret) + return ret; + + /* This should always succeed, because we check during init. */ + if (!get_lightbar_version(ec, &version, &flags)) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); +} + +static ssize_t brightness_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + unsigned int val; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_BRIGHTNESS; + param->brightness.num = val; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + return count; +} + + +/* + * We expect numbers, and we'll keep reading until we find them, skipping over + * any whitespace (sysfs guarantees that the input is null-terminated). Every + * four numbers are sent to the lightbar as . We fail at the first + * parsing error, if we don't parse any numbers, or if we have numbers left + * over. + */ +static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + struct cros_ec_device *ec = dev_get_drvdata(dev); + unsigned int val[4]; + int ret, i = 0, j = 0, ok = 0; + + do { + /* Skip any whitespace */ + while (*buf && isspace(*buf)) + buf++; + + if (!*buf) + break; + + ret = sscanf(buf, "%i", &val[i++]); + if (ret == 0) + return -EINVAL; + + if (i == 4) { + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_RGB; + param->rgb.led = val[0]; + param->rgb.red = val[1]; + param->rgb.green = val[2]; + param->rgb.blue = val[3]; + /* + * Throttle only the first of every four transactions, + * so that the user can update all four LEDs at once. + */ + if ((j++ % 4) == 0) { + ret = lb_throttle(); + if (ret) + return ret; + } + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + i = 0; + ok = 1; + } + + /* Skip over the number we just read */ + while (*buf && !isspace(*buf)) + buf++; + + } while (*buf); + + return (ok && i == 0) ? count : -EINVAL; +} + +static const char const *seqname[] = { + "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", + "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", +}; + +static ssize_t sequence_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + int ret; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_GET_SEQ; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return scnprintf(buf, PAGE_SIZE, + "ERROR: EC returned %d\n", msg.result); + + resp = (struct ec_response_lightbar *)msg.indata; + if (resp->get_seq.num >= ARRAY_SIZE(seqname)) + return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); + else + return scnprintf(buf, PAGE_SIZE, "%s\n", + seqname[resp->get_seq.num]); +} + +static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_params_lightbar *param; + struct ec_response_lightbar *resp; + struct cros_ec_command msg = INIT_MSG(param, resp); + unsigned int num; + int ret, len; + struct cros_ec_device *ec = dev_get_drvdata(dev); + + for (len = 0; len < count; len++) + if (!isalnum(buf[len])) + break; + + for (num = 0; num < ARRAY_SIZE(seqname); num++) + if (!strncasecmp(seqname[num], buf, len)) + break; + + if (num >= ARRAY_SIZE(seqname)) { + ret = kstrtouint(buf, 0, &num); + if (ret) + return ret; + } + + param = (struct ec_params_lightbar *)msg.outdata; + param->cmd = LIGHTBAR_CMD_SEQ; + param->seq.num = num; + ret = lb_throttle(); + if (ret) + return ret; + + ret = cros_ec_cmd_xfer(ec, &msg); + if (ret < 0) + return ret; + + if (msg.result != EC_RES_SUCCESS) + return -EINVAL; + + return count; +} + +/* Module initialization */ + +static DEVICE_ATTR_RW(interval_msec); +static DEVICE_ATTR_RO(version); +static DEVICE_ATTR_WO(brightness); +static DEVICE_ATTR_WO(led_rgb); +static DEVICE_ATTR_RW(sequence); +static struct attribute *__lb_cmds_attrs[] = { + &dev_attr_interval_msec.attr, + &dev_attr_version.attr, + &dev_attr_brightness.attr, + &dev_attr_led_rgb.attr, + &dev_attr_sequence.attr, + NULL, +}; +static struct attribute_group lb_cmds_attr_group = { + .name = "lightbar", + .attrs = __lb_cmds_attrs, +}; + +void ec_dev_lightbar_init(struct cros_ec_device *ec) +{ + int ret = 0; + + /* Only instantiate this stuff if the EC has a lightbar */ + if (!get_lightbar_version(ec, NULL, NULL)) + return; + + ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); + if (ret) + pr_warn("sysfs_create_group() failed: %d\n", ret); +} + +void ec_dev_lightbar_remove(struct cros_ec_device *ec) +{ + sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); +} -- cgit v1.2.3 From ef59c25d1be9539890c03c4915cdd4b868191b6f Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Tue, 3 Mar 2015 13:22:32 +0100 Subject: platform/chrome: cros_ec_dev - fix Unknown escape '%' warning Fix the following sparse warning: drivers/platform/chrome/cros_ec_dev.c:64:45: sparse: Unknown escape '%' Reported-by: Dan Carpenter Signed-off-by: Olof Johansson Reviewed-by: Gwendal Grignou Signed-off-by: Javier Martinez Canillas Signed-off-by: Olof Johansson --- drivers/platform/chrome/cros_ec_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/platform/chrome/cros_ec_dev.c') diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index ce714317a59e..6090d0b2826f 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c @@ -61,7 +61,7 @@ static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) if (resp->current_image >= ARRAY_SIZE(current_image_name)) resp->current_image = 3; /* invalid */ - snprintf(str, maxlen, "%s\n%s\n%s\n\%s\n", CROS_EC_DEV_VERSION, + snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, resp->version_string_ro, resp->version_string_rw, current_image_name[resp->current_image]); -- cgit v1.2.3