at v4.8-rc4 365 lines 8.8 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 if (u_cmd.outsize != s_cmd->outsize || 155 u_cmd.insize != s_cmd->insize) { 156 ret = -EINVAL; 157 goto exit; 158 } 159 160 s_cmd->command += ec->cmd_offset; 161 ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd); 162 /* Only copy data to userland if data was received. */ 163 if (ret < 0) 164 goto exit; 165 166 if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) 167 ret = -EFAULT; 168exit: 169 kfree(s_cmd); 170 return ret; 171} 172 173static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg) 174{ 175 struct cros_ec_device *ec_dev = ec->ec_dev; 176 struct cros_ec_readmem s_mem = { }; 177 long num; 178 179 /* Not every platform supports direct reads */ 180 if (!ec_dev->cmd_readmem) 181 return -ENOTTY; 182 183 if (copy_from_user(&s_mem, arg, sizeof(s_mem))) 184 return -EFAULT; 185 186 num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, 187 s_mem.buffer); 188 if (num <= 0) 189 return num; 190 191 if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) 192 return -EFAULT; 193 194 return 0; 195} 196 197static long ec_device_ioctl(struct file *filp, unsigned int cmd, 198 unsigned long arg) 199{ 200 struct cros_ec_dev *ec = filp->private_data; 201 202 if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) 203 return -ENOTTY; 204 205 switch (cmd) { 206 case CROS_EC_DEV_IOCXCMD: 207 return ec_device_ioctl_xcmd(ec, (void __user *)arg); 208 case CROS_EC_DEV_IOCRDMEM: 209 return ec_device_ioctl_readmem(ec, (void __user *)arg); 210 } 211 212 return -ENOTTY; 213} 214 215/* Module initialization */ 216static const struct file_operations fops = { 217 .open = ec_device_open, 218 .release = ec_device_release, 219 .read = ec_device_read, 220 .unlocked_ioctl = ec_device_ioctl, 221#ifdef CONFIG_COMPAT 222 .compat_ioctl = ec_device_ioctl, 223#endif 224}; 225 226static void __remove(struct device *dev) 227{ 228 struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, 229 class_dev); 230 kfree(ec); 231} 232 233static int ec_device_probe(struct platform_device *pdev) 234{ 235 int retval = -ENOMEM; 236 struct device *dev = &pdev->dev; 237 struct cros_ec_platform *ec_platform = dev_get_platdata(dev); 238 dev_t devno = MKDEV(ec_major, pdev->id); 239 struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); 240 241 if (!ec) 242 return retval; 243 244 dev_set_drvdata(dev, ec); 245 ec->ec_dev = dev_get_drvdata(dev->parent); 246 ec->dev = dev; 247 ec->cmd_offset = ec_platform->cmd_offset; 248 device_initialize(&ec->class_dev); 249 cdev_init(&ec->cdev, &fops); 250 251 /* 252 * Add the character device 253 * Link cdev to the class device to be sure device is not used 254 * before unbinding it. 255 */ 256 ec->cdev.kobj.parent = &ec->class_dev.kobj; 257 retval = cdev_add(&ec->cdev, devno, 1); 258 if (retval) { 259 dev_err(dev, ": failed to add character device\n"); 260 goto cdev_add_failed; 261 } 262 263 /* 264 * Add the class device 265 * Link to the character device for creating the /dev entry 266 * in devtmpfs. 267 */ 268 ec->class_dev.devt = ec->cdev.dev; 269 ec->class_dev.class = &cros_class; 270 ec->class_dev.parent = dev; 271 ec->class_dev.release = __remove; 272 273 retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); 274 if (retval) { 275 dev_err(dev, "dev_set_name failed => %d\n", retval); 276 goto set_named_failed; 277 } 278 279 retval = device_add(&ec->class_dev); 280 if (retval) { 281 dev_err(dev, "device_register failed => %d\n", retval); 282 goto dev_reg_failed; 283 } 284 285 return 0; 286 287dev_reg_failed: 288set_named_failed: 289 dev_set_drvdata(dev, NULL); 290 cdev_del(&ec->cdev); 291cdev_add_failed: 292 kfree(ec); 293 return retval; 294} 295 296static int ec_device_remove(struct platform_device *pdev) 297{ 298 struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); 299 cdev_del(&ec->cdev); 300 device_unregister(&ec->class_dev); 301 return 0; 302} 303 304static const struct platform_device_id cros_ec_id[] = { 305 { "cros-ec-ctl", 0 }, 306 { /* sentinel */ }, 307}; 308MODULE_DEVICE_TABLE(platform, cros_ec_id); 309 310static struct platform_driver cros_ec_dev_driver = { 311 .driver = { 312 .name = "cros-ec-ctl", 313 }, 314 .probe = ec_device_probe, 315 .remove = ec_device_remove, 316}; 317 318static int __init cros_ec_dev_init(void) 319{ 320 int ret; 321 dev_t dev = 0; 322 323 ret = class_register(&cros_class); 324 if (ret) { 325 pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); 326 return ret; 327 } 328 329 /* Get a range of minor numbers (starting with 0) to work with */ 330 ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); 331 if (ret < 0) { 332 pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); 333 goto failed_chrdevreg; 334 } 335 ec_major = MAJOR(dev); 336 337 /* Register the driver */ 338 ret = platform_driver_register(&cros_ec_dev_driver); 339 if (ret < 0) { 340 pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); 341 goto failed_devreg; 342 } 343 return 0; 344 345failed_devreg: 346 unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); 347failed_chrdevreg: 348 class_unregister(&cros_class); 349 return ret; 350} 351 352static void __exit cros_ec_dev_exit(void) 353{ 354 platform_driver_unregister(&cros_ec_dev_driver); 355 unregister_chrdev(ec_major, CROS_EC_DEV_NAME); 356 class_unregister(&cros_class); 357} 358 359module_init(cros_ec_dev_init); 360module_exit(cros_ec_dev_exit); 361 362MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); 363MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); 364MODULE_VERSION("1.0"); 365MODULE_LICENSE("GPL");