diff options
author | Bill Richardson <wfrichar@chromium.org> | 2015-02-02 06:26:28 -0500 |
---|---|---|
committer | Olof Johansson <olof@lixom.net> | 2015-02-26 18:45:16 -0500 |
commit | f3f837e52b14bf84c2db65f622b5c31cd261100c (patch) | |
tree | ea8c44173cd685e13f683fd63f205b9586a35fa9 | |
parent | 71af4b52cc22a8d0f7b66a51427a804741a045b6 (diff) |
platform/chrome: Expose Chrome OS Lightbar to users
This adds some sysfs entries to provide userspace control of the
four-element LED "lightbar" on the Chromebook Pixel. This only instantiates
the lightbar controls if the device actually exists.
To prevent DoS attacks, this interface is limited to 20 accesses/second,
although that rate can be adjusted by a privileged user.
On Chromebooks without a lightbar, this should have no effect. On the
Chromebook Pixel, you should be able to do things like this:
$ cd /sys/devices/virtual/chromeos/cros_ec/lightbar
$ echo 0x80 > brightness
$ echo 255 > brightness
$
$ cat sequence
S0
$ echo konami > sequence
$ cat sequence
KONAMI
$
$ cat sequence
S0
And
$ cd /sys/devices/virtual/chromeos/cros_ec/lightbar
$ echo stop > sequence
$ echo "4 255 255 255" > led_rgb
$ echo "0 255 0 0 1 0 255 0 2 0 0 255 3 255 255 0" > led_rgb
$ echo run > sequence
Test the DoS prevention with this:
$ cd /sys/devices/virtual/chromeos/cros_ec/lightbar
$ echo 500 > interval_msec
$ time (cat version version version version version version version)
Signed-off-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-by: Olof Johansson <olofj@chromium.org>
Tested-by: Doug Anderson <dianders@chromium.org>
Reviewed-by: Benson Leung <bleung@chromium.org>
Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
Tested-by: Gwendal Grignou <gwendal@chromium.org>
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Signed-off-by: Olof Johansson <olof@lixom.net>
-rw-r--r-- | drivers/platform/chrome/Makefile | 2 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.c | 2 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.h | 3 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lightbar.c | 367 |
4 files changed, 373 insertions, 1 deletions
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 34b631ceff67..bd8d8601e875 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile | |||
@@ -1,6 +1,6 @@ | |||
1 | 1 | ||
2 | obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o | 2 | obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o |
3 | obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o | 3 | obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o |
4 | cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o | 4 | cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o cros_ec_lightbar.o |
5 | obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o | 5 | obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o |
6 | obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o | 6 | obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o |
diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c index 33f37ad36892..ce714317a59e 100644 --- a/drivers/platform/chrome/cros_ec_dev.c +++ b/drivers/platform/chrome/cros_ec_dev.c | |||
@@ -200,6 +200,7 @@ static int ec_device_probe(struct platform_device *pdev) | |||
200 | 200 | ||
201 | /* Initialize extra interfaces */ | 201 | /* Initialize extra interfaces */ |
202 | ec_dev_sysfs_init(ec); | 202 | ec_dev_sysfs_init(ec); |
203 | ec_dev_lightbar_init(ec); | ||
203 | 204 | ||
204 | return 0; | 205 | return 0; |
205 | } | 206 | } |
@@ -208,6 +209,7 @@ static int ec_device_remove(struct platform_device *pdev) | |||
208 | { | 209 | { |
209 | struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); | 210 | struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); |
210 | 211 | ||
212 | ec_dev_lightbar_remove(ec); | ||
211 | ec_dev_sysfs_remove(ec); | 213 | ec_dev_sysfs_remove(ec); |
212 | device_destroy(cros_class, MKDEV(ec_major, 0)); | 214 | device_destroy(cros_class, MKDEV(ec_major, 0)); |
213 | cdev_del(&ec->cdev); | 215 | cdev_del(&ec->cdev); |
diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h index f03613290ecf..45d67f7e518c 100644 --- a/drivers/platform/chrome/cros_ec_dev.h +++ b/drivers/platform/chrome/cros_ec_dev.h | |||
@@ -47,4 +47,7 @@ struct cros_ec_readmem { | |||
47 | void ec_dev_sysfs_init(struct cros_ec_device *); | 47 | void ec_dev_sysfs_init(struct cros_ec_device *); |
48 | void ec_dev_sysfs_remove(struct cros_ec_device *); | 48 | void ec_dev_sysfs_remove(struct cros_ec_device *); |
49 | 49 | ||
50 | void ec_dev_lightbar_init(struct cros_ec_device *); | ||
51 | void ec_dev_lightbar_remove(struct cros_ec_device *); | ||
52 | |||
50 | #endif /* _CROS_EC_DEV_H_ */ | 53 | #endif /* _CROS_EC_DEV_H_ */ |
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c new file mode 100644 index 000000000000..35fc892e4c95 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lightbar.c | |||
@@ -0,0 +1,367 @@ | |||
1 | /* | ||
2 | * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace | ||
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 | #define pr_fmt(fmt) "cros_ec_lightbar: " fmt | ||
21 | |||
22 | #include <linux/ctype.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/device.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/kobject.h> | ||
27 | #include <linux/mfd/cros_ec.h> | ||
28 | #include <linux/mfd/cros_ec_commands.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/sched.h> | ||
32 | #include <linux/types.h> | ||
33 | #include <linux/uaccess.h> | ||
34 | |||
35 | #include "cros_ec_dev.h" | ||
36 | |||
37 | /* Rate-limit the lightbar interface to prevent DoS. */ | ||
38 | static unsigned long lb_interval_jiffies = 50 * HZ / 1000; | ||
39 | |||
40 | static ssize_t interval_msec_show(struct device *dev, | ||
41 | struct device_attribute *attr, char *buf) | ||
42 | { | ||
43 | unsigned long msec = lb_interval_jiffies * 1000 / HZ; | ||
44 | |||
45 | return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); | ||
46 | } | ||
47 | |||
48 | static ssize_t interval_msec_store(struct device *dev, | ||
49 | struct device_attribute *attr, | ||
50 | const char *buf, size_t count) | ||
51 | { | ||
52 | unsigned long msec; | ||
53 | |||
54 | if (kstrtoul(buf, 0, &msec)) | ||
55 | return -EINVAL; | ||
56 | |||
57 | lb_interval_jiffies = msec * HZ / 1000; | ||
58 | |||
59 | return count; | ||
60 | } | ||
61 | |||
62 | static DEFINE_MUTEX(lb_mutex); | ||
63 | /* Return 0 if able to throttle correctly, error otherwise */ | ||
64 | static int lb_throttle(void) | ||
65 | { | ||
66 | static unsigned long last_access; | ||
67 | unsigned long now, next_timeslot; | ||
68 | long delay; | ||
69 | int ret = 0; | ||
70 | |||
71 | mutex_lock(&lb_mutex); | ||
72 | |||
73 | now = jiffies; | ||
74 | next_timeslot = last_access + lb_interval_jiffies; | ||
75 | |||
76 | if (time_before(now, next_timeslot)) { | ||
77 | delay = (long)(next_timeslot) - (long)now; | ||
78 | set_current_state(TASK_INTERRUPTIBLE); | ||
79 | if (schedule_timeout(delay) > 0) { | ||
80 | /* interrupted - just abort */ | ||
81 | ret = -EINTR; | ||
82 | goto out; | ||
83 | } | ||
84 | now = jiffies; | ||
85 | } | ||
86 | |||
87 | last_access = now; | ||
88 | out: | ||
89 | mutex_unlock(&lb_mutex); | ||
90 | |||
91 | return ret; | ||
92 | } | ||
93 | |||
94 | #define INIT_MSG(P, R) { \ | ||
95 | .command = EC_CMD_LIGHTBAR_CMD, \ | ||
96 | .outsize = sizeof(*P), \ | ||
97 | .insize = sizeof(*R), \ | ||
98 | } | ||
99 | |||
100 | static int get_lightbar_version(struct cros_ec_device *ec, | ||
101 | uint32_t *ver_ptr, uint32_t *flg_ptr) | ||
102 | { | ||
103 | struct ec_params_lightbar *param; | ||
104 | struct ec_response_lightbar *resp; | ||
105 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
106 | int ret; | ||
107 | |||
108 | param = (struct ec_params_lightbar *)msg.outdata; | ||
109 | param->cmd = LIGHTBAR_CMD_VERSION; | ||
110 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
111 | if (ret < 0) | ||
112 | return 0; | ||
113 | |||
114 | switch (msg.result) { | ||
115 | case EC_RES_INVALID_PARAM: | ||
116 | /* Pixel had no version command. */ | ||
117 | if (ver_ptr) | ||
118 | *ver_ptr = 0; | ||
119 | if (flg_ptr) | ||
120 | *flg_ptr = 0; | ||
121 | return 1; | ||
122 | |||
123 | case EC_RES_SUCCESS: | ||
124 | resp = (struct ec_response_lightbar *)msg.indata; | ||
125 | |||
126 | /* Future devices w/lightbars should implement this command */ | ||
127 | if (ver_ptr) | ||
128 | *ver_ptr = resp->version.num; | ||
129 | if (flg_ptr) | ||
130 | *flg_ptr = resp->version.flags; | ||
131 | return 1; | ||
132 | } | ||
133 | |||
134 | /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ | ||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static ssize_t version_show(struct device *dev, | ||
139 | struct device_attribute *attr, char *buf) | ||
140 | { | ||
141 | uint32_t version, flags; | ||
142 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
143 | int ret; | ||
144 | |||
145 | ret = lb_throttle(); | ||
146 | if (ret) | ||
147 | return ret; | ||
148 | |||
149 | /* This should always succeed, because we check during init. */ | ||
150 | if (!get_lightbar_version(ec, &version, &flags)) | ||
151 | return -EIO; | ||
152 | |||
153 | return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); | ||
154 | } | ||
155 | |||
156 | static ssize_t brightness_store(struct device *dev, | ||
157 | struct device_attribute *attr, | ||
158 | const char *buf, size_t count) | ||
159 | { | ||
160 | struct ec_params_lightbar *param; | ||
161 | struct ec_response_lightbar *resp; | ||
162 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
163 | int ret; | ||
164 | unsigned int val; | ||
165 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
166 | |||
167 | if (kstrtouint(buf, 0, &val)) | ||
168 | return -EINVAL; | ||
169 | |||
170 | param = (struct ec_params_lightbar *)msg.outdata; | ||
171 | param->cmd = LIGHTBAR_CMD_BRIGHTNESS; | ||
172 | param->brightness.num = val; | ||
173 | ret = lb_throttle(); | ||
174 | if (ret) | ||
175 | return ret; | ||
176 | |||
177 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
178 | if (ret < 0) | ||
179 | return ret; | ||
180 | |||
181 | if (msg.result != EC_RES_SUCCESS) | ||
182 | return -EINVAL; | ||
183 | |||
184 | return count; | ||
185 | } | ||
186 | |||
187 | |||
188 | /* | ||
189 | * We expect numbers, and we'll keep reading until we find them, skipping over | ||
190 | * any whitespace (sysfs guarantees that the input is null-terminated). Every | ||
191 | * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first | ||
192 | * parsing error, if we don't parse any numbers, or if we have numbers left | ||
193 | * over. | ||
194 | */ | ||
195 | static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, | ||
196 | const char *buf, size_t count) | ||
197 | { | ||
198 | struct ec_params_lightbar *param; | ||
199 | struct ec_response_lightbar *resp; | ||
200 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
201 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
202 | unsigned int val[4]; | ||
203 | int ret, i = 0, j = 0, ok = 0; | ||
204 | |||
205 | do { | ||
206 | /* Skip any whitespace */ | ||
207 | while (*buf && isspace(*buf)) | ||
208 | buf++; | ||
209 | |||
210 | if (!*buf) | ||
211 | break; | ||
212 | |||
213 | ret = sscanf(buf, "%i", &val[i++]); | ||
214 | if (ret == 0) | ||
215 | return -EINVAL; | ||
216 | |||
217 | if (i == 4) { | ||
218 | param = (struct ec_params_lightbar *)msg.outdata; | ||
219 | param->cmd = LIGHTBAR_CMD_RGB; | ||
220 | param->rgb.led = val[0]; | ||
221 | param->rgb.red = val[1]; | ||
222 | param->rgb.green = val[2]; | ||
223 | param->rgb.blue = val[3]; | ||
224 | /* | ||
225 | * Throttle only the first of every four transactions, | ||
226 | * so that the user can update all four LEDs at once. | ||
227 | */ | ||
228 | if ((j++ % 4) == 0) { | ||
229 | ret = lb_throttle(); | ||
230 | if (ret) | ||
231 | return ret; | ||
232 | } | ||
233 | |||
234 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
235 | if (ret < 0) | ||
236 | return ret; | ||
237 | |||
238 | if (msg.result != EC_RES_SUCCESS) | ||
239 | return -EINVAL; | ||
240 | |||
241 | i = 0; | ||
242 | ok = 1; | ||
243 | } | ||
244 | |||
245 | /* Skip over the number we just read */ | ||
246 | while (*buf && !isspace(*buf)) | ||
247 | buf++; | ||
248 | |||
249 | } while (*buf); | ||
250 | |||
251 | return (ok && i == 0) ? count : -EINVAL; | ||
252 | } | ||
253 | |||
254 | static const char const *seqname[] = { | ||
255 | "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", | ||
256 | "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", | ||
257 | }; | ||
258 | |||
259 | static ssize_t sequence_show(struct device *dev, | ||
260 | struct device_attribute *attr, char *buf) | ||
261 | { | ||
262 | struct ec_params_lightbar *param; | ||
263 | struct ec_response_lightbar *resp; | ||
264 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
265 | int ret; | ||
266 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
267 | |||
268 | param = (struct ec_params_lightbar *)msg.outdata; | ||
269 | param->cmd = LIGHTBAR_CMD_GET_SEQ; | ||
270 | ret = lb_throttle(); | ||
271 | if (ret) | ||
272 | return ret; | ||
273 | |||
274 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
275 | if (ret < 0) | ||
276 | return ret; | ||
277 | |||
278 | if (msg.result != EC_RES_SUCCESS) | ||
279 | return scnprintf(buf, PAGE_SIZE, | ||
280 | "ERROR: EC returned %d\n", msg.result); | ||
281 | |||
282 | resp = (struct ec_response_lightbar *)msg.indata; | ||
283 | if (resp->get_seq.num >= ARRAY_SIZE(seqname)) | ||
284 | return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); | ||
285 | else | ||
286 | return scnprintf(buf, PAGE_SIZE, "%s\n", | ||
287 | seqname[resp->get_seq.num]); | ||
288 | } | ||
289 | |||
290 | static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, | ||
291 | const char *buf, size_t count) | ||
292 | { | ||
293 | struct ec_params_lightbar *param; | ||
294 | struct ec_response_lightbar *resp; | ||
295 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
296 | unsigned int num; | ||
297 | int ret, len; | ||
298 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
299 | |||
300 | for (len = 0; len < count; len++) | ||
301 | if (!isalnum(buf[len])) | ||
302 | break; | ||
303 | |||
304 | for (num = 0; num < ARRAY_SIZE(seqname); num++) | ||
305 | if (!strncasecmp(seqname[num], buf, len)) | ||
306 | break; | ||
307 | |||
308 | if (num >= ARRAY_SIZE(seqname)) { | ||
309 | ret = kstrtouint(buf, 0, &num); | ||
310 | if (ret) | ||
311 | return ret; | ||
312 | } | ||
313 | |||
314 | param = (struct ec_params_lightbar *)msg.outdata; | ||
315 | param->cmd = LIGHTBAR_CMD_SEQ; | ||
316 | param->seq.num = num; | ||
317 | ret = lb_throttle(); | ||
318 | if (ret) | ||
319 | return ret; | ||
320 | |||
321 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
322 | if (ret < 0) | ||
323 | return ret; | ||
324 | |||
325 | if (msg.result != EC_RES_SUCCESS) | ||
326 | return -EINVAL; | ||
327 | |||
328 | return count; | ||
329 | } | ||
330 | |||
331 | /* Module initialization */ | ||
332 | |||
333 | static DEVICE_ATTR_RW(interval_msec); | ||
334 | static DEVICE_ATTR_RO(version); | ||
335 | static DEVICE_ATTR_WO(brightness); | ||
336 | static DEVICE_ATTR_WO(led_rgb); | ||
337 | static DEVICE_ATTR_RW(sequence); | ||
338 | static struct attribute *__lb_cmds_attrs[] = { | ||
339 | &dev_attr_interval_msec.attr, | ||
340 | &dev_attr_version.attr, | ||
341 | &dev_attr_brightness.attr, | ||
342 | &dev_attr_led_rgb.attr, | ||
343 | &dev_attr_sequence.attr, | ||
344 | NULL, | ||
345 | }; | ||
346 | static struct attribute_group lb_cmds_attr_group = { | ||
347 | .name = "lightbar", | ||
348 | .attrs = __lb_cmds_attrs, | ||
349 | }; | ||
350 | |||
351 | void ec_dev_lightbar_init(struct cros_ec_device *ec) | ||
352 | { | ||
353 | int ret = 0; | ||
354 | |||
355 | /* Only instantiate this stuff if the EC has a lightbar */ | ||
356 | if (!get_lightbar_version(ec, NULL, NULL)) | ||
357 | return; | ||
358 | |||
359 | ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); | ||
360 | if (ret) | ||
361 | pr_warn("sysfs_create_group() failed: %d\n", ret); | ||
362 | } | ||
363 | |||
364 | void ec_dev_lightbar_remove(struct cros_ec_device *ec) | ||
365 | { | ||
366 | sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); | ||
367 | } | ||