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