Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 // Expose the Chromebook Pixel lightbar to userspace
0003 //
0004 // Copyright (C) 2014 Google, Inc.
0005 
0006 #include <linux/ctype.h>
0007 #include <linux/delay.h>
0008 #include <linux/device.h>
0009 #include <linux/fs.h>
0010 #include <linux/kobject.h>
0011 #include <linux/module.h>
0012 #include <linux/platform_data/cros_ec_commands.h>
0013 #include <linux/platform_data/cros_ec_proto.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/sched.h>
0016 #include <linux/types.h>
0017 #include <linux/uaccess.h>
0018 #include <linux/slab.h>
0019 
0020 #define DRV_NAME "cros-ec-lightbar"
0021 
0022 /* Rate-limit the lightbar interface to prevent DoS. */
0023 static unsigned long lb_interval_jiffies = 50 * HZ / 1000;
0024 
0025 /*
0026  * Whether or not we have given userspace control of the lightbar.
0027  * If this is true, we won't do anything during suspend/resume.
0028  */
0029 static bool userspace_control;
0030 
0031 static ssize_t interval_msec_show(struct device *dev,
0032                   struct device_attribute *attr, char *buf)
0033 {
0034     unsigned long msec = lb_interval_jiffies * 1000 / HZ;
0035 
0036     return scnprintf(buf, PAGE_SIZE, "%lu\n", msec);
0037 }
0038 
0039 static ssize_t interval_msec_store(struct device *dev,
0040                    struct device_attribute *attr,
0041                    const char *buf, size_t count)
0042 {
0043     unsigned long msec;
0044 
0045     if (kstrtoul(buf, 0, &msec))
0046         return -EINVAL;
0047 
0048     lb_interval_jiffies = msec * HZ / 1000;
0049 
0050     return count;
0051 }
0052 
0053 static DEFINE_MUTEX(lb_mutex);
0054 /* Return 0 if able to throttle correctly, error otherwise */
0055 static int lb_throttle(void)
0056 {
0057     static unsigned long last_access;
0058     unsigned long now, next_timeslot;
0059     long delay;
0060     int ret = 0;
0061 
0062     mutex_lock(&lb_mutex);
0063 
0064     now = jiffies;
0065     next_timeslot = last_access + lb_interval_jiffies;
0066 
0067     if (time_before(now, next_timeslot)) {
0068         delay = (long)(next_timeslot) - (long)now;
0069         set_current_state(TASK_INTERRUPTIBLE);
0070         if (schedule_timeout(delay) > 0) {
0071             /* interrupted - just abort */
0072             ret = -EINTR;
0073             goto out;
0074         }
0075         now = jiffies;
0076     }
0077 
0078     last_access = now;
0079 out:
0080     mutex_unlock(&lb_mutex);
0081 
0082     return ret;
0083 }
0084 
0085 static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec)
0086 {
0087     struct cros_ec_command *msg;
0088     int len;
0089 
0090     len = max(sizeof(struct ec_params_lightbar),
0091           sizeof(struct ec_response_lightbar));
0092 
0093     msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
0094     if (!msg)
0095         return NULL;
0096 
0097     msg->version = 0;
0098     msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset;
0099     msg->outsize = sizeof(struct ec_params_lightbar);
0100     msg->insize = sizeof(struct ec_response_lightbar);
0101 
0102     return msg;
0103 }
0104 
0105 static int get_lightbar_version(struct cros_ec_dev *ec,
0106                 uint32_t *ver_ptr, uint32_t *flg_ptr)
0107 {
0108     struct ec_params_lightbar *param;
0109     struct ec_response_lightbar *resp;
0110     struct cros_ec_command *msg;
0111     int ret;
0112 
0113     msg = alloc_lightbar_cmd_msg(ec);
0114     if (!msg)
0115         return 0;
0116 
0117     param = (struct ec_params_lightbar *)msg->data;
0118     param->cmd = LIGHTBAR_CMD_VERSION;
0119     msg->outsize = sizeof(param->cmd);
0120     msg->result = sizeof(resp->version);
0121     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0122     if (ret < 0 && ret != -EINVAL) {
0123         ret = 0;
0124         goto exit;
0125     }
0126 
0127     switch (msg->result) {
0128     case EC_RES_INVALID_PARAM:
0129         /* Pixel had no version command. */
0130         if (ver_ptr)
0131             *ver_ptr = 0;
0132         if (flg_ptr)
0133             *flg_ptr = 0;
0134         ret = 1;
0135         goto exit;
0136 
0137     case EC_RES_SUCCESS:
0138         resp = (struct ec_response_lightbar *)msg->data;
0139 
0140         /* Future devices w/lightbars should implement this command */
0141         if (ver_ptr)
0142             *ver_ptr = resp->version.num;
0143         if (flg_ptr)
0144             *flg_ptr = resp->version.flags;
0145         ret = 1;
0146         goto exit;
0147     }
0148 
0149     /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */
0150     ret = 0;
0151 exit:
0152     kfree(msg);
0153     return ret;
0154 }
0155 
0156 static ssize_t version_show(struct device *dev,
0157                 struct device_attribute *attr, char *buf)
0158 {
0159     uint32_t version = 0, flags = 0;
0160     struct cros_ec_dev *ec = to_cros_ec_dev(dev);
0161     int ret;
0162 
0163     ret = lb_throttle();
0164     if (ret)
0165         return ret;
0166 
0167     /* This should always succeed, because we check during init. */
0168     if (!get_lightbar_version(ec, &version, &flags))
0169         return -EIO;
0170 
0171     return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags);
0172 }
0173 
0174 static ssize_t brightness_store(struct device *dev,
0175                 struct device_attribute *attr,
0176                 const char *buf, size_t count)
0177 {
0178     struct ec_params_lightbar *param;
0179     struct cros_ec_command *msg;
0180     int ret;
0181     unsigned int val;
0182     struct cros_ec_dev *ec = to_cros_ec_dev(dev);
0183 
0184     if (kstrtouint(buf, 0, &val))
0185         return -EINVAL;
0186 
0187     msg = alloc_lightbar_cmd_msg(ec);
0188     if (!msg)
0189         return -ENOMEM;
0190 
0191     param = (struct ec_params_lightbar *)msg->data;
0192     param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS;
0193     param->set_brightness.num = val;
0194     ret = lb_throttle();
0195     if (ret)
0196         goto exit;
0197 
0198     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0199     if (ret < 0)
0200         goto exit;
0201 
0202     ret = count;
0203 exit:
0204     kfree(msg);
0205     return ret;
0206 }
0207 
0208 
0209 /*
0210  * We expect numbers, and we'll keep reading until we find them, skipping over
0211  * any whitespace (sysfs guarantees that the input is null-terminated). Every
0212  * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first
0213  * parsing error, if we don't parse any numbers, or if we have numbers left
0214  * over.
0215  */
0216 static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr,
0217                  const char *buf, size_t count)
0218 {
0219     struct ec_params_lightbar *param;
0220     struct cros_ec_command *msg;
0221     struct cros_ec_dev *ec = to_cros_ec_dev(dev);
0222     unsigned int val[4];
0223     int ret, i = 0, j = 0, ok = 0;
0224 
0225     msg = alloc_lightbar_cmd_msg(ec);
0226     if (!msg)
0227         return -ENOMEM;
0228 
0229     do {
0230         /* Skip any whitespace */
0231         while (*buf && isspace(*buf))
0232             buf++;
0233 
0234         if (!*buf)
0235             break;
0236 
0237         ret = sscanf(buf, "%i", &val[i++]);
0238         if (ret == 0)
0239             goto exit;
0240 
0241         if (i == 4) {
0242             param = (struct ec_params_lightbar *)msg->data;
0243             param->cmd = LIGHTBAR_CMD_SET_RGB;
0244             param->set_rgb.led = val[0];
0245             param->set_rgb.red = val[1];
0246             param->set_rgb.green = val[2];
0247             param->set_rgb.blue = val[3];
0248             /*
0249              * Throttle only the first of every four transactions,
0250              * so that the user can update all four LEDs at once.
0251              */
0252             if ((j++ % 4) == 0) {
0253                 ret = lb_throttle();
0254                 if (ret)
0255                     goto exit;
0256             }
0257 
0258             ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0259             if (ret < 0)
0260                 goto exit;
0261 
0262             i = 0;
0263             ok = 1;
0264         }
0265 
0266         /* Skip over the number we just read */
0267         while (*buf && !isspace(*buf))
0268             buf++;
0269 
0270     } while (*buf);
0271 
0272 exit:
0273     kfree(msg);
0274     return (ok && i == 0) ? count : -EINVAL;
0275 }
0276 
0277 static char const *seqname[] = {
0278     "ERROR", "S5", "S3", "S0", "S5S3", "S3S0",
0279     "S0S3", "S3S5", "STOP", "RUN", "KONAMI",
0280     "TAP", "PROGRAM",
0281 };
0282 
0283 static ssize_t sequence_show(struct device *dev,
0284                  struct device_attribute *attr, char *buf)
0285 {
0286     struct ec_params_lightbar *param;
0287     struct ec_response_lightbar *resp;
0288     struct cros_ec_command *msg;
0289     int ret;
0290     struct cros_ec_dev *ec = to_cros_ec_dev(dev);
0291 
0292     msg = alloc_lightbar_cmd_msg(ec);
0293     if (!msg)
0294         return -ENOMEM;
0295 
0296     param = (struct ec_params_lightbar *)msg->data;
0297     param->cmd = LIGHTBAR_CMD_GET_SEQ;
0298     ret = lb_throttle();
0299     if (ret)
0300         goto exit;
0301 
0302     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0303     if (ret < 0) {
0304         ret = scnprintf(buf, PAGE_SIZE, "XFER / EC ERROR %d / %d\n",
0305                 ret, msg->result);
0306         goto exit;
0307     }
0308 
0309     resp = (struct ec_response_lightbar *)msg->data;
0310     if (resp->get_seq.num >= ARRAY_SIZE(seqname))
0311         ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num);
0312     else
0313         ret = scnprintf(buf, PAGE_SIZE, "%s\n",
0314                 seqname[resp->get_seq.num]);
0315 
0316 exit:
0317     kfree(msg);
0318     return ret;
0319 }
0320 
0321 static int lb_send_empty_cmd(struct cros_ec_dev *ec, uint8_t cmd)
0322 {
0323     struct ec_params_lightbar *param;
0324     struct cros_ec_command *msg;
0325     int ret;
0326 
0327     msg = alloc_lightbar_cmd_msg(ec);
0328     if (!msg)
0329         return -ENOMEM;
0330 
0331     param = (struct ec_params_lightbar *)msg->data;
0332     param->cmd = cmd;
0333 
0334     ret = lb_throttle();
0335     if (ret)
0336         goto error;
0337 
0338     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0339     if (ret < 0)
0340         goto error;
0341 
0342     ret = 0;
0343 error:
0344     kfree(msg);
0345 
0346     return ret;
0347 }
0348 
0349 static int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable)
0350 {
0351     struct ec_params_lightbar *param;
0352     struct cros_ec_command *msg;
0353     int ret;
0354 
0355     msg = alloc_lightbar_cmd_msg(ec);
0356     if (!msg)
0357         return -ENOMEM;
0358 
0359     param = (struct ec_params_lightbar *)msg->data;
0360 
0361     param->cmd = LIGHTBAR_CMD_MANUAL_SUSPEND_CTRL;
0362     param->manual_suspend_ctrl.enable = enable;
0363 
0364     ret = lb_throttle();
0365     if (ret)
0366         goto error;
0367 
0368     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0369     if (ret < 0)
0370         goto error;
0371 
0372     ret = 0;
0373 error:
0374     kfree(msg);
0375 
0376     return ret;
0377 }
0378 
0379 static ssize_t sequence_store(struct device *dev, struct device_attribute *attr,
0380                   const char *buf, size_t count)
0381 {
0382     struct ec_params_lightbar *param;
0383     struct cros_ec_command *msg;
0384     unsigned int num;
0385     int ret, len;
0386     struct cros_ec_dev *ec = to_cros_ec_dev(dev);
0387 
0388     for (len = 0; len < count; len++)
0389         if (!isalnum(buf[len]))
0390             break;
0391 
0392     for (num = 0; num < ARRAY_SIZE(seqname); num++)
0393         if (!strncasecmp(seqname[num], buf, len))
0394             break;
0395 
0396     if (num >= ARRAY_SIZE(seqname)) {
0397         ret = kstrtouint(buf, 0, &num);
0398         if (ret)
0399             return ret;
0400     }
0401 
0402     msg = alloc_lightbar_cmd_msg(ec);
0403     if (!msg)
0404         return -ENOMEM;
0405 
0406     param = (struct ec_params_lightbar *)msg->data;
0407     param->cmd = LIGHTBAR_CMD_SEQ;
0408     param->seq.num = num;
0409     ret = lb_throttle();
0410     if (ret)
0411         goto exit;
0412 
0413     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0414     if (ret < 0)
0415         goto exit;
0416 
0417     ret = count;
0418 exit:
0419     kfree(msg);
0420     return ret;
0421 }
0422 
0423 static ssize_t program_store(struct device *dev, struct device_attribute *attr,
0424                  const char *buf, size_t count)
0425 {
0426     int extra_bytes, max_size, ret;
0427     struct ec_params_lightbar *param;
0428     struct cros_ec_command *msg;
0429     struct cros_ec_dev *ec = to_cros_ec_dev(dev);
0430 
0431     /*
0432      * We might need to reject the program for size reasons. The EC
0433      * enforces a maximum program size, but we also don't want to try
0434      * and send a program that is too big for the protocol. In order
0435      * to ensure the latter, we also need to ensure we have extra bytes
0436      * to represent the rest of the packet.
0437      */
0438     extra_bytes = sizeof(*param) - sizeof(param->set_program.data);
0439     max_size = min(EC_LB_PROG_LEN, ec->ec_dev->max_request - extra_bytes);
0440     if (count > max_size) {
0441         dev_err(dev, "Program is %u bytes, too long to send (max: %u)",
0442             (unsigned int)count, max_size);
0443 
0444         return -EINVAL;
0445     }
0446 
0447     msg = alloc_lightbar_cmd_msg(ec);
0448     if (!msg)
0449         return -ENOMEM;
0450 
0451     ret = lb_throttle();
0452     if (ret)
0453         goto exit;
0454 
0455     dev_info(dev, "Copying %zu byte program to EC", count);
0456 
0457     param = (struct ec_params_lightbar *)msg->data;
0458     param->cmd = LIGHTBAR_CMD_SET_PROGRAM;
0459 
0460     param->set_program.size = count;
0461     memcpy(param->set_program.data, buf, count);
0462 
0463     /*
0464      * We need to set the message size manually or else it will use
0465      * EC_LB_PROG_LEN. This might be too long, and the program
0466      * is unlikely to use all of the space.
0467      */
0468     msg->outsize = count + extra_bytes;
0469 
0470     ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
0471     if (ret < 0)
0472         goto exit;
0473 
0474     ret = count;
0475 exit:
0476     kfree(msg);
0477 
0478     return ret;
0479 }
0480 
0481 static ssize_t userspace_control_show(struct device *dev,
0482                       struct device_attribute *attr,
0483                       char *buf)
0484 {
0485     return scnprintf(buf, PAGE_SIZE, "%d\n", userspace_control);
0486 }
0487 
0488 static ssize_t userspace_control_store(struct device *dev,
0489                        struct device_attribute *attr,
0490                        const char *buf,
0491                        size_t count)
0492 {
0493     bool enable;
0494     int ret;
0495 
0496     ret = strtobool(buf, &enable);
0497     if (ret < 0)
0498         return ret;
0499 
0500     userspace_control = enable;
0501 
0502     return count;
0503 }
0504 
0505 /* Module initialization */
0506 
0507 static DEVICE_ATTR_RW(interval_msec);
0508 static DEVICE_ATTR_RO(version);
0509 static DEVICE_ATTR_WO(brightness);
0510 static DEVICE_ATTR_WO(led_rgb);
0511 static DEVICE_ATTR_RW(sequence);
0512 static DEVICE_ATTR_WO(program);
0513 static DEVICE_ATTR_RW(userspace_control);
0514 
0515 static struct attribute *__lb_cmds_attrs[] = {
0516     &dev_attr_interval_msec.attr,
0517     &dev_attr_version.attr,
0518     &dev_attr_brightness.attr,
0519     &dev_attr_led_rgb.attr,
0520     &dev_attr_sequence.attr,
0521     &dev_attr_program.attr,
0522     &dev_attr_userspace_control.attr,
0523     NULL,
0524 };
0525 
0526 static const struct attribute_group cros_ec_lightbar_attr_group = {
0527     .name = "lightbar",
0528     .attrs = __lb_cmds_attrs,
0529 };
0530 
0531 static int cros_ec_lightbar_probe(struct platform_device *pd)
0532 {
0533     struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
0534     struct cros_ec_platform *pdata = dev_get_platdata(ec_dev->dev);
0535     struct device *dev = &pd->dev;
0536     int ret;
0537 
0538     /*
0539      * Only instantiate the lightbar if the EC name is 'cros_ec'. Other EC
0540      * devices like 'cros_pd' doesn't have a lightbar.
0541      */
0542     if (strcmp(pdata->ec_name, CROS_EC_DEV_NAME) != 0)
0543         return -ENODEV;
0544 
0545     /*
0546      * Ask then for the lightbar version, if it's 0 then the 'cros_ec'
0547      * doesn't have a lightbar.
0548      */
0549     if (!get_lightbar_version(ec_dev, NULL, NULL))
0550         return -ENODEV;
0551 
0552     /* Take control of the lightbar from the EC. */
0553     lb_manual_suspend_ctrl(ec_dev, 1);
0554 
0555     ret = sysfs_create_group(&ec_dev->class_dev.kobj,
0556                  &cros_ec_lightbar_attr_group);
0557     if (ret < 0)
0558         dev_err(dev, "failed to create %s attributes. err=%d\n",
0559             cros_ec_lightbar_attr_group.name, ret);
0560 
0561     return ret;
0562 }
0563 
0564 static int cros_ec_lightbar_remove(struct platform_device *pd)
0565 {
0566     struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
0567 
0568     sysfs_remove_group(&ec_dev->class_dev.kobj,
0569                &cros_ec_lightbar_attr_group);
0570 
0571     /* Let the EC take over the lightbar again. */
0572     lb_manual_suspend_ctrl(ec_dev, 0);
0573 
0574     return 0;
0575 }
0576 
0577 static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
0578 {
0579     struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
0580 
0581     if (userspace_control)
0582         return 0;
0583 
0584     return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_RESUME);
0585 }
0586 
0587 static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
0588 {
0589     struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
0590 
0591     if (userspace_control)
0592         return 0;
0593 
0594     return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_SUSPEND);
0595 }
0596 
0597 static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops,
0598              cros_ec_lightbar_suspend, cros_ec_lightbar_resume);
0599 
0600 static struct platform_driver cros_ec_lightbar_driver = {
0601     .driver = {
0602         .name = DRV_NAME,
0603         .pm = &cros_ec_lightbar_pm_ops,
0604     },
0605     .probe = cros_ec_lightbar_probe,
0606     .remove = cros_ec_lightbar_remove,
0607 };
0608 
0609 module_platform_driver(cros_ec_lightbar_driver);
0610 
0611 MODULE_LICENSE("GPL");
0612 MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace");
0613 MODULE_ALIAS("platform:" DRV_NAME);