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