linux/drivers/platform/chrome/cros_ec_dev.c
<<
>>
Prefs
   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");
 353