1/* 2 * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space 3 * 4 * Copyright (C) 2014 Google, Inc. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20#include <linux/fs.h> 21#include <linux/module.h> 22#include <linux/platform_device.h> 23#include <linux/uaccess.h> 24 25#include "cros_ec_dev.h" 26 27/* Device variables */ 28#define CROS_MAX_DEV 128 29static struct class *cros_class; 30static int ec_major; 31 32/* Basic communication */ 33static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) 34{ 35 struct ec_response_get_version *resp; 36 static const char * const current_image_name[] = { 37 "unknown", "read-only", "read-write", "invalid", 38 }; 39 struct cros_ec_command msg = { 40 .version = 0, 41 .command = EC_CMD_GET_VERSION, 42 .outdata = { 0 }, 43 .outsize = 0, 44 .indata = { 0 }, 45 .insize = sizeof(*resp), 46 }; 47 int ret; 48 49 ret = cros_ec_cmd_xfer(ec, &msg); 50 if (ret < 0) 51 return ret; 52 53 if (msg.result != EC_RES_SUCCESS) { 54 snprintf(str, maxlen, 55 "%s\nUnknown EC version: EC returned %d\n", 56 CROS_EC_DEV_VERSION, msg.result); 57 return 0; 58 } 59 60 resp = (struct ec_response_get_version *)msg.indata; 61 if (resp->current_image >= ARRAY_SIZE(current_image_name)) 62 resp->current_image = 3; /* invalid */ 63 64 snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, 65 resp->version_string_ro, resp->version_string_rw, 66 current_image_name[resp->current_image]); 67 68 return 0; 69} 70 71/* Device file ops */ 72static int ec_device_open(struct inode *inode, struct file *filp) 73{ 74 filp->private_data = container_of(inode->i_cdev, 75 struct cros_ec_device, cdev); 76 return 0; 77} 78 79static int ec_device_release(struct inode *inode, struct file *filp) 80{ 81 return 0; 82} 83 84static ssize_t ec_device_read(struct file *filp, char __user *buffer, 85 size_t length, loff_t *offset) 86{ 87 struct cros_ec_device *ec = filp->private_data; 88 char msg[sizeof(struct ec_response_get_version) + 89 sizeof(CROS_EC_DEV_VERSION)]; 90 size_t count; 91 int ret; 92 93 if (*offset != 0) 94 return 0; 95 96 ret = ec_get_version(ec, msg, sizeof(msg)); 97 if (ret) 98 return ret; 99 100 count = min(length, strlen(msg)); 101 102 if (copy_to_user(buffer, msg, count)) 103 return -EFAULT; 104 105 *offset = count; 106 return count; 107} 108 109/* Ioctls */ 110static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) 111{ 112 long ret; 113 struct cros_ec_command s_cmd = { }; 114 115 if (copy_from_user(&s_cmd, arg, sizeof(s_cmd))) 116 return -EFAULT; 117 118 ret = cros_ec_cmd_xfer(ec, &s_cmd); 119 /* Only copy data to userland if data was received. */ 120 if (ret < 0) 121 return ret; 122 123 if (copy_to_user(arg, &s_cmd, sizeof(s_cmd))) 124 return -EFAULT; 125 126 return 0; 127} 128 129static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) 130{ 131 struct cros_ec_readmem s_mem = { }; 132 long num; 133 134 /* Not every platform supports direct reads */ 135 if (!ec->cmd_readmem) 136 return -ENOTTY; 137 138 if (copy_from_user(&s_mem, arg, sizeof(s_mem))) 139 return -EFAULT; 140 141 num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer); 142 if (num <= 0) 143 return num; 144 145 if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) 146 return -EFAULT; 147 148 return 0; 149} 150 151static long ec_device_ioctl(struct file *filp, unsigned int cmd, 152 unsigned long arg) 153{ 154 struct cros_ec_device *ec = filp->private_data; 155 156 if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) 157 return -ENOTTY; 158 159 switch (cmd) { 160 case CROS_EC_DEV_IOCXCMD: 161 return ec_device_ioctl_xcmd(ec, (void __user *)arg); 162 case CROS_EC_DEV_IOCRDMEM: 163 return ec_device_ioctl_readmem(ec, (void __user *)arg); 164 } 165 166 return -ENOTTY; 167} 168 169/* Module initialization */ 170static const struct file_operations fops = { 171 .open = ec_device_open, 172 .release = ec_device_release, 173 .read = ec_device_read, 174 .unlocked_ioctl = ec_device_ioctl, 175}; 176 177static int ec_device_probe(struct platform_device *pdev) 178{ 179 struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 180 int retval = -ENOTTY; 181 dev_t devno = MKDEV(ec_major, 0); 182 183 /* Instantiate it (and remember the EC) */ 184 cdev_init(&ec->cdev, &fops); 185 186 retval = cdev_add(&ec->cdev, devno, 1); 187 if (retval) { 188 dev_err(&pdev->dev, ": failed to add character device\n"); 189 return retval; 190 } 191 192 ec->vdev = device_create(cros_class, NULL, devno, ec, 193 CROS_EC_DEV_NAME); 194 if (IS_ERR(ec->vdev)) { 195 retval = PTR_ERR(ec->vdev); 196 dev_err(&pdev->dev, ": failed to create device\n"); 197 cdev_del(&ec->cdev); 198 return retval; 199 } 200 201 /* Initialize extra interfaces */ 202 ec_dev_sysfs_init(ec); 203 ec_dev_lightbar_init(ec); 204 205 return 0; 206} 207 208static int ec_device_remove(struct platform_device *pdev) 209{ 210 struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); 211 212 ec_dev_lightbar_remove(ec); 213 ec_dev_sysfs_remove(ec); 214 device_destroy(cros_class, MKDEV(ec_major, 0)); 215 cdev_del(&ec->cdev); 216 return 0; 217} 218 219static struct platform_driver cros_ec_dev_driver = { 220 .driver = { 221 .name = "cros-ec-ctl", 222 }, 223 .probe = ec_device_probe, 224 .remove = ec_device_remove, 225}; 226 227static int __init cros_ec_dev_init(void) 228{ 229 int ret; 230 dev_t dev = 0; 231 232 cros_class = class_create(THIS_MODULE, "chromeos"); 233 if (IS_ERR(cros_class)) { 234 pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); 235 return PTR_ERR(cros_class); 236 } 237 238 /* Get a range of minor numbers (starting with 0) to work with */ 239 ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); 240 if (ret < 0) { 241 pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); 242 goto failed_chrdevreg; 243 } 244 ec_major = MAJOR(dev); 245 246 /* Register the driver */ 247 ret = platform_driver_register(&cros_ec_dev_driver); 248 if (ret < 0) { 249 pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); 250 goto failed_devreg; 251 } 252 return 0; 253 254failed_devreg: 255 unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); 256failed_chrdevreg: 257 class_destroy(cros_class); 258 return ret; 259} 260 261static void __exit cros_ec_dev_exit(void) 262{ 263 platform_driver_unregister(&cros_ec_dev_driver); 264 unregister_chrdev(ec_major, CROS_EC_DEV_NAME); 265 class_destroy(cros_class); 266} 267 268module_init(cros_ec_dev_init); 269module_exit(cros_ec_dev_exit); 270 271MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); 272MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); 273MODULE_VERSION("1.0"); 274MODULE_LICENSE("GPL"); 275