blob: 2f409982048056f231b4cfd86f0a86b6f312cb4a [file] [log] [blame]
Bill Richardsone7c256f2015-02-02 12:26:25 +01001/*
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>
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020023#include <linux/slab.h>
Bill Richardsone7c256f2015-02-02 12:26:25 +010024#include <linux/uaccess.h>
25
26#include "cros_ec_dev.h"
27
28/* Device variables */
29#define CROS_MAX_DEV 128
Bill Richardsone7c256f2015-02-02 12:26:25 +010030static int ec_major;
31
Gwendal Grignou57b33ff2015-06-09 13:04:47 +020032static 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
Bill Richardsone7c256f2015-02-02 12:26:25 +010044/* Basic communication */
Gwendal Grignou57b33ff2015-06-09 13:04:47 +020045static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen)
Bill Richardsone7c256f2015-02-02 12:26:25 +010046{
47 struct ec_response_get_version *resp;
48 static const char * const current_image_name[] = {
49 "unknown", "read-only", "read-write", "invalid",
50 };
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020051 struct cros_ec_command *msg;
Bill Richardsone7c256f2015-02-02 12:26:25 +010052 int ret;
53
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020054 msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
55 if (!msg)
56 return -ENOMEM;
Bill Richardsone7c256f2015-02-02 12:26:25 +010057
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020058 msg->version = 0;
Gwendal Grignou57b33ff2015-06-09 13:04:47 +020059 msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020060 msg->insize = sizeof(*resp);
61 msg->outsize = 0;
62
Gwendal Grignou57b33ff2015-06-09 13:04:47 +020063 ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020064 if (ret < 0)
65 goto exit;
66
67 if (msg->result != EC_RES_SUCCESS) {
Bill Richardsone7c256f2015-02-02 12:26:25 +010068 snprintf(str, maxlen,
69 "%s\nUnknown EC version: EC returned %d\n",
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020070 CROS_EC_DEV_VERSION, msg->result);
71 ret = -EINVAL;
72 goto exit;
Bill Richardsone7c256f2015-02-02 12:26:25 +010073 }
74
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020075 resp = (struct ec_response_get_version *)msg->data;
Bill Richardsone7c256f2015-02-02 12:26:25 +010076 if (resp->current_image >= ARRAY_SIZE(current_image_name))
77 resp->current_image = 3; /* invalid */
78
Olof Johanssonef59c252015-03-03 13:22:32 +010079 snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION,
Bill Richardsone7c256f2015-02-02 12:26:25 +010080 resp->version_string_ro, resp->version_string_rw,
81 current_image_name[resp->current_image]);
82
Javier Martinez Canillasa8411782015-06-09 13:04:42 +020083 ret = 0;
84exit:
85 kfree(msg);
86 return ret;
Bill Richardsone7c256f2015-02-02 12:26:25 +010087}
88
89/* Device file ops */
90static int ec_device_open(struct inode *inode, struct file *filp)
91{
Gwendal Grignou57b33ff2015-06-09 13:04:47 +020092 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);
Bill Richardsone7c256f2015-02-02 12:26:25 +010096 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{
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200107 struct cros_ec_dev *ec = filp->private_data;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100108 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 */
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200130static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg)
Bill Richardsone7c256f2015-02-02 12:26:25 +0100131{
132 long ret;
Javier Martinez Canillasa8411782015-06-09 13:04:42 +0200133 struct cros_ec_command u_cmd;
134 struct cros_ec_command *s_cmd;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100135
Javier Martinez Canillasa8411782015-06-09 13:04:42 +0200136 if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
Bill Richardsone7c256f2015-02-02 12:26:25 +0100137 return -EFAULT;
138
Javier Martinez Canillasa8411782015-06-09 13:04:42 +0200139 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
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200149 s_cmd->command += ec->cmd_offset;
150 ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100151 /* Only copy data to userland if data was received. */
152 if (ret < 0)
Javier Martinez Canillasa8411782015-06-09 13:04:42 +0200153 goto exit;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100154
Javier Martinez Canillasa8411782015-06-09 13:04:42 +0200155 if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + u_cmd.insize))
156 ret = -EFAULT;
157exit:
158 kfree(s_cmd);
159 return ret;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100160}
161
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200162static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg)
Bill Richardsone7c256f2015-02-02 12:26:25 +0100163{
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200164 struct cros_ec_device *ec_dev = ec->ec_dev;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100165 struct cros_ec_readmem s_mem = { };
166 long num;
167
168 /* Not every platform supports direct reads */
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200169 if (!ec_dev->cmd_readmem)
Bill Richardsone7c256f2015-02-02 12:26:25 +0100170 return -ENOTTY;
171
172 if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
173 return -EFAULT;
174
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200175 num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes,
176 s_mem.buffer);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100177 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{
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200189 struct cros_ec_dev *ec = filp->private_data;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100190
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
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200212static 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
Bill Richardsone7c256f2015-02-02 12:26:25 +0100219static int ec_device_probe(struct platform_device *pdev)
220{
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200221 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);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100226
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200227 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);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100235 cdev_init(&ec->cdev, &fops);
236
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200237 /*
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;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100243 retval = cdev_add(&ec->cdev, devno, 1);
244 if (retval) {
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200245 dev_err(dev, ": failed to add character device\n");
246 goto cdev_add_failed;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100247 }
248
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200249 /*
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;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100263 }
264
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200265 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 }
Bill Richardson71af4b52015-02-02 12:26:27 +0100270
Bill Richardsone7c256f2015-02-02 12:26:25 +0100271 return 0;
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200272
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;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100280}
281
282static int ec_device_remove(struct platform_device *pdev)
283{
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200284 struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100285 cdev_del(&ec->cdev);
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200286 device_unregister(&ec->class_dev);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100287 return 0;
288}
289
Javier Martinez Canillasafbf8ec2015-06-22 08:27:20 +0200290static const struct platform_device_id cros_ec_id[] = {
291 { "cros-ec-ctl", 0 },
292 { /* sentinel */ },
293};
294MODULE_DEVICE_TABLE(platform, cros_ec_id);
295
Bill Richardsone7c256f2015-02-02 12:26:25 +0100296static struct platform_driver cros_ec_dev_driver = {
297 .driver = {
298 .name = "cros-ec-ctl",
299 },
300 .probe = ec_device_probe,
301 .remove = ec_device_remove,
302};
303
304static int __init cros_ec_dev_init(void)
305{
306 int ret;
307 dev_t dev = 0;
308
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200309 ret = class_register(&cros_class);
310 if (ret) {
Bill Richardsone7c256f2015-02-02 12:26:25 +0100311 pr_err(CROS_EC_DEV_NAME ": failed to register device class\n");
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200312 return ret;
Bill Richardsone7c256f2015-02-02 12:26:25 +0100313 }
314
315 /* Get a range of minor numbers (starting with 0) to work with */
316 ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME);
317 if (ret < 0) {
318 pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n");
319 goto failed_chrdevreg;
320 }
321 ec_major = MAJOR(dev);
322
323 /* Register the driver */
324 ret = platform_driver_register(&cros_ec_dev_driver);
325 if (ret < 0) {
326 pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret);
327 goto failed_devreg;
328 }
329 return 0;
330
331failed_devreg:
332 unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV);
333failed_chrdevreg:
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200334 class_unregister(&cros_class);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100335 return ret;
336}
337
338static void __exit cros_ec_dev_exit(void)
339{
340 platform_driver_unregister(&cros_ec_dev_driver);
341 unregister_chrdev(ec_major, CROS_EC_DEV_NAME);
Gwendal Grignou57b33ff2015-06-09 13:04:47 +0200342 class_unregister(&cros_class);
Bill Richardsone7c256f2015-02-02 12:26:25 +0100343}
344
345module_init(cros_ec_dev_init);
346module_exit(cros_ec_dev_exit);
347
348MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>");
349MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller");
350MODULE_VERSION("1.0");
351MODULE_LICENSE("GPL");