at v4.7-rc5 359 lines 8.7 kB view raw
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/slab.h> 24#include <linux/uaccess.h> 25 26#include "cros_ec_dev.h" 27 28/* Device variables */ 29#define CROS_MAX_DEV 128 30static int ec_major; 31 32static const struct attribute_group *cros_ec_groups[] = { 33 &cros_ec_attr_group, 34 &cros_ec_lightbar_attr_group, 35 &cros_ec_vbc_attr_group, 36 NULL, 37}; 38 39static struct class cros_class = { 40 .owner = THIS_MODULE, 41 .name = "chromeos", 42 .dev_groups = cros_ec_groups, 43}; 44 45/* Basic communication */ 46static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen) 47{ 48 struct ec_response_get_version *resp; 49 static const char * const current_image_name[] = { 50 "unknown", "read-only", "read-write", "invalid", 51 }; 52 struct cros_ec_command *msg; 53 int ret; 54 55 msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); 56 if (!msg) 57 return -ENOMEM; 58 59 msg->version = 0; 60 msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; 61 msg->insize = sizeof(*resp); 62 msg->outsize = 0; 63 64 ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 65 if (ret < 0) 66 goto exit; 67 68 if (msg->result != EC_RES_SUCCESS) { 69 snprintf(str, maxlen, 70 "%s\nUnknown EC version: EC returned %d\n", 71 CROS_EC_DEV_VERSION, msg->result); 72 ret = -EINVAL; 73 goto exit; 74 } 75 76 resp = (struct ec_response_get_version *)msg->data; 77 if (resp->current_image >= ARRAY_SIZE(current_image_name)) 78 resp->current_image = 3; /* invalid */ 79 80 snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, 81 resp->version_string_ro, resp->version_string_rw, 82 current_image_name[resp->current_image]); 83 84 ret = 0; 85exit: 86 kfree(msg); 87 return ret; 88} 89 90/* Device file ops */ 91static int ec_device_open(struct inode *inode, struct file *filp) 92{ 93 struct cros_ec_dev *ec = container_of(inode->i_cdev, 94 struct cros_ec_dev, cdev); 95 filp->private_data = ec; 96 nonseekable_open(inode, filp); 97 return 0; 98} 99 100static int ec_device_release(struct inode *inode, struct file *filp) 101{ 102 return 0; 103} 104 105static ssize_t ec_device_read(struct file *filp, char __user *buffer, 106 size_t length, loff_t *offset) 107{ 108 struct cros_ec_dev *ec = filp->private_data; 109 char msg[sizeof(struct ec_response_get_version) + 110 sizeof(CROS_EC_DEV_VERSION)]; 111 size_t count; 112 int ret; 113 114 if (*offset != 0) 115 return 0; 116 117 ret = ec_get_version(ec, msg, sizeof(msg)); 118 if (ret) 119 return ret; 120 121 count = min(length, strlen(msg)); 122 123 if (copy_to_user(buffer, msg, count)) 124 return -EFAULT; 125 126 *offset = count; 127 return count; 128} 129 130/* Ioctls */ 131static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) 132{ 133 long ret; 134 struct cros_ec_command u_cmd; 135 struct cros_ec_command *s_cmd; 136 137 if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) 138 return -EFAULT; 139 140 if ((u_cmd.outsize > EC_MAX_MSG_BYTES) || 141 (u_cmd.insize > EC_MAX_MSG_BYTES)) 142 return -EINVAL; 143 144 s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize), 145 GFP_KERNEL); 146 if (!s_cmd) 147 return -ENOMEM; 148 149 if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) { 150 ret = -EFAULT; 151 goto exit; 152 } 153 154 s_cmd->command += ec->cmd_offset; 155 ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd); 156 /* Only copy data to userland if data was received. */ 157 if (ret < 0) 158 goto exit; 159 160 if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + u_cmd.insize)) 161 ret = -EFAULT; 162exit: 163 kfree(s_cmd); 164 return ret; 165} 166 167static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg) 168{ 169 struct cros_ec_device *ec_dev = ec->ec_dev; 170 struct cros_ec_readmem s_mem = { }; 171 long num; 172 173 /* Not every platform supports direct reads */ 174 if (!ec_dev->cmd_readmem) 175 return -ENOTTY; 176 177 if (copy_from_user(&s_mem, arg, sizeof(s_mem))) 178 return -EFAULT; 179 180 num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, 181 s_mem.buffer); 182 if (num <= 0) 183 return num; 184 185 if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) 186 return -EFAULT; 187 188 return 0; 189} 190 191static long ec_device_ioctl(struct file *filp, unsigned int cmd, 192 unsigned long arg) 193{ 194 struct cros_ec_dev *ec = filp->private_data; 195 196 if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) 197 return -ENOTTY; 198 199 switch (cmd) { 200 case CROS_EC_DEV_IOCXCMD: 201 return ec_device_ioctl_xcmd(ec, (void __user *)arg); 202 case CROS_EC_DEV_IOCRDMEM: 203 return ec_device_ioctl_readmem(ec, (void __user *)arg); 204 } 205 206 return -ENOTTY; 207} 208 209/* Module initialization */ 210static const struct file_operations fops = { 211 .open = ec_device_open, 212 .release = ec_device_release, 213 .read = ec_device_read, 214 .unlocked_ioctl = ec_device_ioctl, 215#ifdef CONFIG_COMPAT 216 .compat_ioctl = ec_device_ioctl, 217#endif 218}; 219 220static void __remove(struct device *dev) 221{ 222 struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, 223 class_dev); 224 kfree(ec); 225} 226 227static int ec_device_probe(struct platform_device *pdev) 228{ 229 int retval = -ENOMEM; 230 struct device *dev = &pdev->dev; 231 struct cros_ec_platform *ec_platform = dev_get_platdata(dev); 232 dev_t devno = MKDEV(ec_major, pdev->id); 233 struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); 234 235 if (!ec) 236 return retval; 237 238 dev_set_drvdata(dev, ec); 239 ec->ec_dev = dev_get_drvdata(dev->parent); 240 ec->dev = dev; 241 ec->cmd_offset = ec_platform->cmd_offset; 242 device_initialize(&ec->class_dev); 243 cdev_init(&ec->cdev, &fops); 244 245 /* 246 * Add the character device 247 * Link cdev to the class device to be sure device is not used 248 * before unbinding it. 249 */ 250 ec->cdev.kobj.parent = &ec->class_dev.kobj; 251 retval = cdev_add(&ec->cdev, devno, 1); 252 if (retval) { 253 dev_err(dev, ": failed to add character device\n"); 254 goto cdev_add_failed; 255 } 256 257 /* 258 * Add the class device 259 * Link to the character device for creating the /dev entry 260 * in devtmpfs. 261 */ 262 ec->class_dev.devt = ec->cdev.dev; 263 ec->class_dev.class = &cros_class; 264 ec->class_dev.parent = dev; 265 ec->class_dev.release = __remove; 266 267 retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); 268 if (retval) { 269 dev_err(dev, "dev_set_name failed => %d\n", retval); 270 goto set_named_failed; 271 } 272 273 retval = device_add(&ec->class_dev); 274 if (retval) { 275 dev_err(dev, "device_register failed => %d\n", retval); 276 goto dev_reg_failed; 277 } 278 279 return 0; 280 281dev_reg_failed: 282set_named_failed: 283 dev_set_drvdata(dev, NULL); 284 cdev_del(&ec->cdev); 285cdev_add_failed: 286 kfree(ec); 287 return retval; 288} 289 290static int ec_device_remove(struct platform_device *pdev) 291{ 292 struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); 293 cdev_del(&ec->cdev); 294 device_unregister(&ec->class_dev); 295 return 0; 296} 297 298static const struct platform_device_id cros_ec_id[] = { 299 { "cros-ec-ctl", 0 }, 300 { /* sentinel */ }, 301}; 302MODULE_DEVICE_TABLE(platform, cros_ec_id); 303 304static struct platform_driver cros_ec_dev_driver = { 305 .driver = { 306 .name = "cros-ec-ctl", 307 }, 308 .probe = ec_device_probe, 309 .remove = ec_device_remove, 310}; 311 312static int __init cros_ec_dev_init(void) 313{ 314 int ret; 315 dev_t dev = 0; 316 317 ret = class_register(&cros_class); 318 if (ret) { 319 pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); 320 return ret; 321 } 322 323 /* Get a range of minor numbers (starting with 0) to work with */ 324 ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); 325 if (ret < 0) { 326 pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); 327 goto failed_chrdevreg; 328 } 329 ec_major = MAJOR(dev); 330 331 /* Register the driver */ 332 ret = platform_driver_register(&cros_ec_dev_driver); 333 if (ret < 0) { 334 pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); 335 goto failed_devreg; 336 } 337 return 0; 338 339failed_devreg: 340 unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); 341failed_chrdevreg: 342 class_unregister(&cros_class); 343 return ret; 344} 345 346static void __exit cros_ec_dev_exit(void) 347{ 348 platform_driver_unregister(&cros_ec_dev_driver); 349 unregister_chrdev(ec_major, CROS_EC_DEV_NAME); 350 class_unregister(&cros_class); 351} 352 353module_init(cros_ec_dev_init); 354module_exit(cros_ec_dev_exit); 355 356MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); 357MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); 358MODULE_VERSION("1.0"); 359MODULE_LICENSE("GPL");