1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // Expose the Chromebook Pixel lightbar to userspace |
3 | // |
4 | // Copyright (C) 2014 Google, Inc. |
5 | |
6 | #include <linux/ctype.h> |
7 | #include <linux/delay.h> |
8 | #include <linux/device.h> |
9 | #include <linux/fs.h> |
10 | #include <linux/kobject.h> |
11 | #include <linux/kstrtox.h> |
12 | #include <linux/module.h> |
13 | #include <linux/platform_data/cros_ec_commands.h> |
14 | #include <linux/platform_data/cros_ec_proto.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/sched.h> |
17 | #include <linux/types.h> |
18 | #include <linux/uaccess.h> |
19 | #include <linux/slab.h> |
20 | |
21 | #define DRV_NAME "cros-ec-lightbar" |
22 | |
23 | /* Rate-limit the lightbar interface to prevent DoS. */ |
24 | static unsigned long lb_interval_jiffies = 50 * HZ / 1000; |
25 | |
26 | /* |
27 | * Whether or not we have given userspace control of the lightbar. |
28 | * If this is true, we won't do anything during suspend/resume. |
29 | */ |
30 | static bool userspace_control; |
31 | |
32 | static ssize_t interval_msec_show(struct device *dev, |
33 | struct device_attribute *attr, char *buf) |
34 | { |
35 | unsigned long msec = lb_interval_jiffies * 1000 / HZ; |
36 | |
37 | return sysfs_emit(buf, fmt: "%lu\n" , msec); |
38 | } |
39 | |
40 | static ssize_t interval_msec_store(struct device *dev, |
41 | struct device_attribute *attr, |
42 | const char *buf, size_t count) |
43 | { |
44 | unsigned long msec; |
45 | |
46 | if (kstrtoul(s: buf, base: 0, res: &msec)) |
47 | return -EINVAL; |
48 | |
49 | lb_interval_jiffies = msec * HZ / 1000; |
50 | |
51 | return count; |
52 | } |
53 | |
54 | static DEFINE_MUTEX(lb_mutex); |
55 | /* Return 0 if able to throttle correctly, error otherwise */ |
56 | static int lb_throttle(void) |
57 | { |
58 | static unsigned long last_access; |
59 | unsigned long now, next_timeslot; |
60 | long delay; |
61 | int ret = 0; |
62 | |
63 | mutex_lock(&lb_mutex); |
64 | |
65 | now = jiffies; |
66 | next_timeslot = last_access + lb_interval_jiffies; |
67 | |
68 | if (time_before(now, next_timeslot)) { |
69 | delay = (long)(next_timeslot) - (long)now; |
70 | set_current_state(TASK_INTERRUPTIBLE); |
71 | if (schedule_timeout(timeout: delay) > 0) { |
72 | /* interrupted - just abort */ |
73 | ret = -EINTR; |
74 | goto out; |
75 | } |
76 | now = jiffies; |
77 | } |
78 | |
79 | last_access = now; |
80 | out: |
81 | mutex_unlock(lock: &lb_mutex); |
82 | |
83 | return ret; |
84 | } |
85 | |
86 | static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec) |
87 | { |
88 | struct cros_ec_command *msg; |
89 | int len; |
90 | |
91 | len = max(sizeof(struct ec_params_lightbar), |
92 | sizeof(struct ec_response_lightbar)); |
93 | |
94 | msg = kmalloc(size: sizeof(*msg) + len, GFP_KERNEL); |
95 | if (!msg) |
96 | return NULL; |
97 | |
98 | msg->version = 0; |
99 | msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset; |
100 | msg->outsize = sizeof(struct ec_params_lightbar); |
101 | msg->insize = sizeof(struct ec_response_lightbar); |
102 | |
103 | return msg; |
104 | } |
105 | |
106 | static int get_lightbar_version(struct cros_ec_dev *ec, |
107 | uint32_t *ver_ptr, uint32_t *flg_ptr) |
108 | { |
109 | struct ec_params_lightbar *param; |
110 | struct ec_response_lightbar *resp; |
111 | struct cros_ec_command *msg; |
112 | int ret; |
113 | |
114 | msg = alloc_lightbar_cmd_msg(ec); |
115 | if (!msg) |
116 | return 0; |
117 | |
118 | param = (struct ec_params_lightbar *)msg->data; |
119 | param->cmd = LIGHTBAR_CMD_VERSION; |
120 | msg->outsize = sizeof(param->cmd); |
121 | msg->result = sizeof(resp->version); |
122 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
123 | if (ret < 0 && ret != -EINVAL) { |
124 | ret = 0; |
125 | goto exit; |
126 | } |
127 | |
128 | switch (msg->result) { |
129 | case EC_RES_INVALID_PARAM: |
130 | /* Pixel had no version command. */ |
131 | if (ver_ptr) |
132 | *ver_ptr = 0; |
133 | if (flg_ptr) |
134 | *flg_ptr = 0; |
135 | ret = 1; |
136 | goto exit; |
137 | |
138 | case EC_RES_SUCCESS: |
139 | resp = (struct ec_response_lightbar *)msg->data; |
140 | |
141 | /* Future devices w/lightbars should implement this command */ |
142 | if (ver_ptr) |
143 | *ver_ptr = resp->version.num; |
144 | if (flg_ptr) |
145 | *flg_ptr = resp->version.flags; |
146 | ret = 1; |
147 | goto exit; |
148 | } |
149 | |
150 | /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ |
151 | ret = 0; |
152 | exit: |
153 | kfree(objp: msg); |
154 | return ret; |
155 | } |
156 | |
157 | static ssize_t version_show(struct device *dev, |
158 | struct device_attribute *attr, char *buf) |
159 | { |
160 | uint32_t version = 0, flags = 0; |
161 | struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
162 | int ret; |
163 | |
164 | ret = lb_throttle(); |
165 | if (ret) |
166 | return ret; |
167 | |
168 | /* This should always succeed, because we check during init. */ |
169 | if (!get_lightbar_version(ec, ver_ptr: &version, flg_ptr: &flags)) |
170 | return -EIO; |
171 | |
172 | return sysfs_emit(buf, fmt: "%d %d\n" , version, flags); |
173 | } |
174 | |
175 | static ssize_t brightness_store(struct device *dev, |
176 | struct device_attribute *attr, |
177 | const char *buf, size_t count) |
178 | { |
179 | struct ec_params_lightbar *param; |
180 | struct cros_ec_command *msg; |
181 | int ret; |
182 | unsigned int val; |
183 | struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
184 | |
185 | if (kstrtouint(s: buf, base: 0, res: &val)) |
186 | return -EINVAL; |
187 | |
188 | msg = alloc_lightbar_cmd_msg(ec); |
189 | if (!msg) |
190 | return -ENOMEM; |
191 | |
192 | param = (struct ec_params_lightbar *)msg->data; |
193 | param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS; |
194 | param->set_brightness.num = val; |
195 | ret = lb_throttle(); |
196 | if (ret) |
197 | goto exit; |
198 | |
199 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
200 | if (ret < 0) |
201 | goto exit; |
202 | |
203 | ret = count; |
204 | exit: |
205 | kfree(objp: msg); |
206 | return ret; |
207 | } |
208 | |
209 | |
210 | /* |
211 | * We expect numbers, and we'll keep reading until we find them, skipping over |
212 | * any whitespace (sysfs guarantees that the input is null-terminated). Every |
213 | * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first |
214 | * parsing error, if we don't parse any numbers, or if we have numbers left |
215 | * over. |
216 | */ |
217 | static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, |
218 | const char *buf, size_t count) |
219 | { |
220 | struct ec_params_lightbar *param; |
221 | struct cros_ec_command *msg; |
222 | struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
223 | unsigned int val[4]; |
224 | int ret, i = 0, j = 0, ok = 0; |
225 | |
226 | msg = alloc_lightbar_cmd_msg(ec); |
227 | if (!msg) |
228 | return -ENOMEM; |
229 | |
230 | do { |
231 | /* Skip any whitespace */ |
232 | while (*buf && isspace(*buf)) |
233 | buf++; |
234 | |
235 | if (!*buf) |
236 | break; |
237 | |
238 | ret = sscanf(buf, "%i" , &val[i++]); |
239 | if (ret == 0) |
240 | goto exit; |
241 | |
242 | if (i == 4) { |
243 | param = (struct ec_params_lightbar *)msg->data; |
244 | param->cmd = LIGHTBAR_CMD_SET_RGB; |
245 | param->set_rgb.led = val[0]; |
246 | param->set_rgb.red = val[1]; |
247 | param->set_rgb.green = val[2]; |
248 | param->set_rgb.blue = val[3]; |
249 | /* |
250 | * Throttle only the first of every four transactions, |
251 | * so that the user can update all four LEDs at once. |
252 | */ |
253 | if ((j++ % 4) == 0) { |
254 | ret = lb_throttle(); |
255 | if (ret) |
256 | goto exit; |
257 | } |
258 | |
259 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
260 | if (ret < 0) |
261 | goto exit; |
262 | |
263 | i = 0; |
264 | ok = 1; |
265 | } |
266 | |
267 | /* Skip over the number we just read */ |
268 | while (*buf && !isspace(*buf)) |
269 | buf++; |
270 | |
271 | } while (*buf); |
272 | |
273 | exit: |
274 | kfree(objp: msg); |
275 | return (ok && i == 0) ? count : -EINVAL; |
276 | } |
277 | |
278 | static char const *seqname[] = { |
279 | "ERROR" , "S5" , "S3" , "S0" , "S5S3" , "S3S0" , |
280 | "S0S3" , "S3S5" , "STOP" , "RUN" , "KONAMI" , |
281 | "TAP" , "PROGRAM" , |
282 | }; |
283 | |
284 | static ssize_t sequence_show(struct device *dev, |
285 | struct device_attribute *attr, char *buf) |
286 | { |
287 | struct ec_params_lightbar *param; |
288 | struct ec_response_lightbar *resp; |
289 | struct cros_ec_command *msg; |
290 | int ret; |
291 | struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
292 | |
293 | msg = alloc_lightbar_cmd_msg(ec); |
294 | if (!msg) |
295 | return -ENOMEM; |
296 | |
297 | param = (struct ec_params_lightbar *)msg->data; |
298 | param->cmd = LIGHTBAR_CMD_GET_SEQ; |
299 | ret = lb_throttle(); |
300 | if (ret) |
301 | goto exit; |
302 | |
303 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
304 | if (ret < 0) { |
305 | ret = sysfs_emit(buf, fmt: "XFER / EC ERROR %d / %d\n" , ret, msg->result); |
306 | goto exit; |
307 | } |
308 | |
309 | resp = (struct ec_response_lightbar *)msg->data; |
310 | if (resp->get_seq.num >= ARRAY_SIZE(seqname)) |
311 | ret = sysfs_emit(buf, fmt: "%d\n" , resp->get_seq.num); |
312 | else |
313 | ret = sysfs_emit(buf, fmt: "%s\n" , seqname[resp->get_seq.num]); |
314 | |
315 | exit: |
316 | kfree(objp: msg); |
317 | return ret; |
318 | } |
319 | |
320 | static int lb_send_empty_cmd(struct cros_ec_dev *ec, uint8_t cmd) |
321 | { |
322 | struct ec_params_lightbar *param; |
323 | struct cros_ec_command *msg; |
324 | int ret; |
325 | |
326 | msg = alloc_lightbar_cmd_msg(ec); |
327 | if (!msg) |
328 | return -ENOMEM; |
329 | |
330 | param = (struct ec_params_lightbar *)msg->data; |
331 | param->cmd = cmd; |
332 | |
333 | ret = lb_throttle(); |
334 | if (ret) |
335 | goto error; |
336 | |
337 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
338 | if (ret < 0) |
339 | goto error; |
340 | |
341 | ret = 0; |
342 | error: |
343 | kfree(objp: msg); |
344 | |
345 | return ret; |
346 | } |
347 | |
348 | static int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) |
349 | { |
350 | struct ec_params_lightbar *param; |
351 | struct cros_ec_command *msg; |
352 | int ret; |
353 | |
354 | msg = alloc_lightbar_cmd_msg(ec); |
355 | if (!msg) |
356 | return -ENOMEM; |
357 | |
358 | param = (struct ec_params_lightbar *)msg->data; |
359 | |
360 | param->cmd = LIGHTBAR_CMD_MANUAL_SUSPEND_CTRL; |
361 | param->manual_suspend_ctrl.enable = enable; |
362 | |
363 | ret = lb_throttle(); |
364 | if (ret) |
365 | goto error; |
366 | |
367 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
368 | if (ret < 0) |
369 | goto error; |
370 | |
371 | ret = 0; |
372 | error: |
373 | kfree(objp: msg); |
374 | |
375 | return ret; |
376 | } |
377 | |
378 | static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, |
379 | const char *buf, size_t count) |
380 | { |
381 | struct ec_params_lightbar *param; |
382 | struct cros_ec_command *msg; |
383 | unsigned int num; |
384 | int ret, len; |
385 | struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
386 | |
387 | for (len = 0; len < count; len++) |
388 | if (!isalnum(buf[len])) |
389 | break; |
390 | |
391 | for (num = 0; num < ARRAY_SIZE(seqname); num++) |
392 | if (!strncasecmp(s1: seqname[num], s2: buf, n: len)) |
393 | break; |
394 | |
395 | if (num >= ARRAY_SIZE(seqname)) { |
396 | ret = kstrtouint(s: buf, base: 0, res: &num); |
397 | if (ret) |
398 | return ret; |
399 | } |
400 | |
401 | msg = alloc_lightbar_cmd_msg(ec); |
402 | if (!msg) |
403 | return -ENOMEM; |
404 | |
405 | param = (struct ec_params_lightbar *)msg->data; |
406 | param->cmd = LIGHTBAR_CMD_SEQ; |
407 | param->seq.num = num; |
408 | ret = lb_throttle(); |
409 | if (ret) |
410 | goto exit; |
411 | |
412 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
413 | if (ret < 0) |
414 | goto exit; |
415 | |
416 | ret = count; |
417 | exit: |
418 | kfree(objp: msg); |
419 | return ret; |
420 | } |
421 | |
422 | static ssize_t program_store(struct device *dev, struct device_attribute *attr, |
423 | const char *buf, size_t count) |
424 | { |
425 | int , max_size, ret; |
426 | struct ec_params_lightbar *param; |
427 | struct cros_ec_command *msg; |
428 | struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
429 | |
430 | /* |
431 | * We might need to reject the program for size reasons. The EC |
432 | * enforces a maximum program size, but we also don't want to try |
433 | * and send a program that is too big for the protocol. In order |
434 | * to ensure the latter, we also need to ensure we have extra bytes |
435 | * to represent the rest of the packet. |
436 | */ |
437 | extra_bytes = sizeof(*param) - sizeof(param->set_program.data); |
438 | max_size = min(EC_LB_PROG_LEN, ec->ec_dev->max_request - extra_bytes); |
439 | if (count > max_size) { |
440 | dev_err(dev, "Program is %u bytes, too long to send (max: %u)" , |
441 | (unsigned int)count, max_size); |
442 | |
443 | return -EINVAL; |
444 | } |
445 | |
446 | msg = alloc_lightbar_cmd_msg(ec); |
447 | if (!msg) |
448 | return -ENOMEM; |
449 | |
450 | ret = lb_throttle(); |
451 | if (ret) |
452 | goto exit; |
453 | |
454 | dev_info(dev, "Copying %zu byte program to EC" , count); |
455 | |
456 | param = (struct ec_params_lightbar *)msg->data; |
457 | param->cmd = LIGHTBAR_CMD_SET_PROGRAM; |
458 | |
459 | param->set_program.size = count; |
460 | memcpy(param->set_program.data, buf, count); |
461 | |
462 | /* |
463 | * We need to set the message size manually or else it will use |
464 | * EC_LB_PROG_LEN. This might be too long, and the program |
465 | * is unlikely to use all of the space. |
466 | */ |
467 | msg->outsize = count + extra_bytes; |
468 | |
469 | ret = cros_ec_cmd_xfer_status(ec_dev: ec->ec_dev, msg); |
470 | if (ret < 0) |
471 | goto exit; |
472 | |
473 | ret = count; |
474 | exit: |
475 | kfree(objp: msg); |
476 | |
477 | return ret; |
478 | } |
479 | |
480 | static ssize_t userspace_control_show(struct device *dev, |
481 | struct device_attribute *attr, |
482 | char *buf) |
483 | { |
484 | return sysfs_emit(buf, fmt: "%d\n" , userspace_control); |
485 | } |
486 | |
487 | static ssize_t userspace_control_store(struct device *dev, |
488 | struct device_attribute *attr, |
489 | const char *buf, |
490 | size_t count) |
491 | { |
492 | bool enable; |
493 | int ret; |
494 | |
495 | ret = kstrtobool(s: buf, res: &enable); |
496 | if (ret < 0) |
497 | return ret; |
498 | |
499 | userspace_control = enable; |
500 | |
501 | return count; |
502 | } |
503 | |
504 | /* Module initialization */ |
505 | |
506 | static DEVICE_ATTR_RW(interval_msec); |
507 | static DEVICE_ATTR_RO(version); |
508 | static DEVICE_ATTR_WO(brightness); |
509 | static DEVICE_ATTR_WO(led_rgb); |
510 | static DEVICE_ATTR_RW(sequence); |
511 | static DEVICE_ATTR_WO(program); |
512 | static DEVICE_ATTR_RW(userspace_control); |
513 | |
514 | static struct attribute *__lb_cmds_attrs[] = { |
515 | &dev_attr_interval_msec.attr, |
516 | &dev_attr_version.attr, |
517 | &dev_attr_brightness.attr, |
518 | &dev_attr_led_rgb.attr, |
519 | &dev_attr_sequence.attr, |
520 | &dev_attr_program.attr, |
521 | &dev_attr_userspace_control.attr, |
522 | NULL, |
523 | }; |
524 | |
525 | static const struct attribute_group cros_ec_lightbar_attr_group = { |
526 | .name = "lightbar" , |
527 | .attrs = __lb_cmds_attrs, |
528 | }; |
529 | |
530 | static int cros_ec_lightbar_probe(struct platform_device *pd) |
531 | { |
532 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: pd->dev.parent); |
533 | struct cros_ec_platform *pdata = dev_get_platdata(dev: ec_dev->dev); |
534 | struct device *dev = &pd->dev; |
535 | int ret; |
536 | |
537 | /* |
538 | * Only instantiate the lightbar if the EC name is 'cros_ec'. Other EC |
539 | * devices like 'cros_pd' doesn't have a lightbar. |
540 | */ |
541 | if (strcmp(pdata->ec_name, CROS_EC_DEV_NAME) != 0) |
542 | return -ENODEV; |
543 | |
544 | /* |
545 | * Ask then for the lightbar version, if it's 0 then the 'cros_ec' |
546 | * doesn't have a lightbar. |
547 | */ |
548 | if (!get_lightbar_version(ec: ec_dev, NULL, NULL)) |
549 | return -ENODEV; |
550 | |
551 | /* Take control of the lightbar from the EC. */ |
552 | lb_manual_suspend_ctrl(ec: ec_dev, enable: 1); |
553 | |
554 | ret = sysfs_create_group(kobj: &ec_dev->class_dev.kobj, |
555 | grp: &cros_ec_lightbar_attr_group); |
556 | if (ret < 0) |
557 | dev_err(dev, "failed to create %s attributes. err=%d\n" , |
558 | cros_ec_lightbar_attr_group.name, ret); |
559 | |
560 | return ret; |
561 | } |
562 | |
563 | static void cros_ec_lightbar_remove(struct platform_device *pd) |
564 | { |
565 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: pd->dev.parent); |
566 | |
567 | sysfs_remove_group(kobj: &ec_dev->class_dev.kobj, |
568 | grp: &cros_ec_lightbar_attr_group); |
569 | |
570 | /* Let the EC take over the lightbar again. */ |
571 | lb_manual_suspend_ctrl(ec: ec_dev, enable: 0); |
572 | } |
573 | |
574 | static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) |
575 | { |
576 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: dev->parent); |
577 | |
578 | if (userspace_control) |
579 | return 0; |
580 | |
581 | return lb_send_empty_cmd(ec: ec_dev, cmd: LIGHTBAR_CMD_RESUME); |
582 | } |
583 | |
584 | static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev) |
585 | { |
586 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: dev->parent); |
587 | |
588 | if (userspace_control) |
589 | return 0; |
590 | |
591 | return lb_send_empty_cmd(ec: ec_dev, cmd: LIGHTBAR_CMD_SUSPEND); |
592 | } |
593 | |
594 | static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops, |
595 | cros_ec_lightbar_suspend, cros_ec_lightbar_resume); |
596 | |
597 | static struct platform_driver cros_ec_lightbar_driver = { |
598 | .driver = { |
599 | .name = DRV_NAME, |
600 | .pm = &cros_ec_lightbar_pm_ops, |
601 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
602 | }, |
603 | .probe = cros_ec_lightbar_probe, |
604 | .remove_new = cros_ec_lightbar_remove, |
605 | }; |
606 | |
607 | module_platform_driver(cros_ec_lightbar_driver); |
608 | |
609 | MODULE_LICENSE("GPL" ); |
610 | MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace" ); |
611 | MODULE_ALIAS("platform:" DRV_NAME); |
612 | |