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