cregit-Linux how code gets into the kernel

Release 4.11 drivers/platform/chrome/cros_ec_lightbar.c

/*
 * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace
 *
 * Copyright (C) 2014 Google, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */


#define pr_fmt(fmt) "cros_ec_lightbar: " fmt

#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kobject.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#include "cros_ec_dev.h"

/* Rate-limit the lightbar interface to prevent DoS. */

static unsigned long lb_interval_jiffies = 50 * HZ / 1000;


static ssize_t interval_msec_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned long msec = lb_interval_jiffies * 1000 / HZ; return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson42100.00%1100.00%
Total42100.00%1100.00%


static ssize_t interval_msec_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long msec; if (kstrtoul(buf, 0, &msec)) return -EINVAL; lb_interval_jiffies = msec * HZ / 1000; return count; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson55100.00%1100.00%
Total55100.00%1100.00%

static DEFINE_MUTEX(lb_mutex); /* Return 0 if able to throttle correctly, error otherwise */
static int lb_throttle(void) { static unsigned long last_access; unsigned long now, next_timeslot; long delay; int ret = 0; mutex_lock(&lb_mutex); now = jiffies; next_timeslot = last_access + lb_interval_jiffies; if (time_before(now, next_timeslot)) { delay = (long)(next_timeslot) - (long)now; set_current_state(TASK_INTERRUPTIBLE); if (schedule_timeout(delay) > 0) { /* interrupted - just abort */ ret = -EINTR; goto out; } now = jiffies; } last_access = now; out: mutex_unlock(&lb_mutex); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson112100.00%1100.00%
Total112100.00%1100.00%


static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec) { struct cros_ec_command *msg; int len; len = max(sizeof(struct ec_params_lightbar), sizeof(struct ec_response_lightbar)); msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL); if (!msg) return NULL; msg->version = 0; msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset; msg->outsize = sizeof(struct ec_params_lightbar); msg->insize = sizeof(struct ec_response_lightbar); return msg; }

Contributors

PersonTokensPropCommitsCommitProp
Javier Martinez Canillas9090.00%133.33%
Gwendal Grignou88.00%133.33%
Bill Richardson22.00%133.33%
Total100100.00%3100.00%


static int get_lightbar_version(struct cros_ec_dev *ec, uint32_t *ver_ptr, uint32_t *flg_ptr) { struct ec_params_lightbar *param; struct ec_response_lightbar *resp; struct cros_ec_command *msg; int ret; msg = alloc_lightbar_cmd_msg(ec); if (!msg) return 0; param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_VERSION; ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) { ret = 0; goto exit; } switch (msg->result) { case EC_RES_INVALID_PARAM: /* Pixel had no version command. */ if (ver_ptr) *ver_ptr = 0; if (flg_ptr) *flg_ptr = 0; ret = 1; goto exit; case EC_RES_SUCCESS: resp = (struct ec_response_lightbar *)msg->data; /* Future devices w/lightbars should implement this command */ if (ver_ptr) *ver_ptr = resp->version.num; if (flg_ptr) *flg_ptr = resp->version.flags; ret = 1; goto exit; } /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ ret = 0; exit: kfree(msg); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson13971.28%133.33%
Javier Martinez Canillas5025.64%133.33%
Gwendal Grignou63.08%133.33%
Total195100.00%3100.00%


static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { uint32_t version = 0, flags = 0; struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); int ret; ret = lb_throttle(); if (ret) return ret; /* This should always succeed, because we check during init. */ if (!get_lightbar_version(ec, &version, &flags)) return -EIO; return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson8188.04%133.33%
Gwendal Grignou77.61%133.33%
Javier Martinez Canillas44.35%133.33%
Total92100.00%3100.00%


static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ec_params_lightbar *param; struct cros_ec_command *msg; int ret; unsigned int val; struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); if (kstrtouint(buf, 0, &val)) return -EINVAL; msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS; param->set_brightness.num = val; ret = lb_throttle(); if (ret) goto exit; ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; if (msg->result != EC_RES_SUCCESS) { ret = -EINVAL; goto exit; } ret = count; exit: kfree(msg); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson12067.80%125.00%
Javier Martinez Canillas4324.29%125.00%
Gwendal Grignou126.78%125.00%
Stephen Barber21.13%125.00%
Total177100.00%4100.00%

/* * We expect numbers, and we'll keep reading until we find them, skipping over * any whitespace (sysfs guarantees that the input is null-terminated). Every * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first * parsing error, if we don't parse any numbers, or if we have numbers left * over. */
static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ec_params_lightbar *param; struct cros_ec_command *msg; struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); unsigned int val[4]; int ret, i = 0, j = 0, ok = 0; msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; do { /* Skip any whitespace */ while (*buf && isspace(*buf)) buf++; if (!*buf) break; ret = sscanf(buf, "%i", &val[i++]); if (ret == 0) goto exit; if (i == 4) { param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SET_RGB; param->set_rgb.led = val[0]; param->set_rgb.red = val[1]; param->set_rgb.green = val[2]; param->set_rgb.blue = val[3]; /* * Throttle only the first of every four transactions, * so that the user can update all four LEDs at once. */ if ((j++ % 4) == 0) { ret = lb_throttle(); if (ret) goto exit; } ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; if (msg->result != EC_RES_SUCCESS) goto exit; i = 0; ok = 1; } /* Skip over the number we just read */ while (*buf && !isspace(*buf)) buf++; } while (*buf); exit: kfree(msg); return (ok && i == 0) ? count : -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson26183.12%120.00%
Javier Martinez Canillas309.55%120.00%
Gwendal Grignou123.82%120.00%
Christian Engelmayer61.91%120.00%
Stephen Barber51.59%120.00%
Total314100.00%5100.00%

static char const *seqname[] = { "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", };
static ssize_t sequence_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ec_params_lightbar *param; struct ec_response_lightbar *resp; struct cros_ec_command *msg; int ret; struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_GET_SEQ; ret = lb_throttle(); if (ret) goto exit; ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; if (msg->result != EC_RES_SUCCESS) { ret = scnprintf(buf, PAGE_SIZE, "ERROR: EC returned %d\n", msg->result); goto exit; } resp = (struct ec_response_lightbar *)msg->data; if (resp->get_seq.num >= ARRAY_SIZE(seqname)) ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); else ret = scnprintf(buf, PAGE_SIZE, "%s\n", seqname[resp->get_seq.num]); exit: kfree(msg); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson15772.02%133.33%
Javier Martinez Canillas4922.48%133.33%
Gwendal Grignou125.50%133.33%
Total218100.00%3100.00%


static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ec_params_lightbar *param; struct cros_ec_command *msg; unsigned int num; int ret, len; struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); for (len = 0; len < count; len++) if (!isalnum(buf[len])) break; for (num = 0; num < ARRAY_SIZE(seqname); num++) if (!strncasecmp(seqname[num], buf, len)) break; if (num >= ARRAY_SIZE(seqname)) { ret = kstrtouint(buf, 0, &num); if (ret) return ret; } msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_SEQ; param->seq.num = num; ret = lb_throttle(); if (ret) goto exit; ret = cros_ec_cmd_xfer(ec->ec_dev, msg); if (ret < 0) goto exit; if (msg->result != EC_RES_SUCCESS) { ret = -EINVAL; goto exit; } ret = count; exit: kfree(msg); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson19578.00%125.00%
Christian Engelmayer4216.80%125.00%
Gwendal Grignou93.60%125.00%
Javier Martinez Canillas41.60%125.00%
Total250100.00%4100.00%

/* Module initialization */ static DEVICE_ATTR_RW(interval_msec); static DEVICE_ATTR_RO(version); static DEVICE_ATTR_WO(brightness); static DEVICE_ATTR_WO(led_rgb); static DEVICE_ATTR_RW(sequence); static struct attribute *__lb_cmds_attrs[] = { &dev_attr_interval_msec.attr, &dev_attr_version.attr, &dev_attr_brightness.attr, &dev_attr_led_rgb.attr, &dev_attr_sequence.attr, NULL, };
static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n) { struct device *dev = container_of(kobj, struct device, kobj); struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, class_dev); struct platform_device *pdev = to_platform_device(ec->dev); struct cros_ec_platform *pdata = pdev->dev.platform_data; int is_cros_ec; is_cros_ec = strcmp(pdata->ec_name, CROS_EC_DEV_NAME); if (is_cros_ec != 0) return 0; /* Only instantiate this stuff if the EC has a lightbar */ if (get_lightbar_version(ec, NULL, NULL)) return a->mode; else return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Gwendal Grignou6959.48%125.00%
Clinton Sprain2622.41%125.00%
Bill Richardson2017.24%125.00%
Geliang Tang10.86%125.00%
Total116100.00%4100.00%

struct attribute_group cros_ec_lightbar_attr_group = { .name = "lightbar", .attrs = __lb_cmds_attrs, .is_visible = cros_ec_lightbar_attrs_are_visible, };

Overall Contributors

PersonTokensPropCommitsCommitProp
Bill Richardson135572.69%112.50%
Javier Martinez Canillas27314.65%112.50%
Gwendal Grignou1548.26%112.50%
Christian Engelmayer482.58%225.00%
Clinton Sprain261.39%112.50%
Stephen Barber70.38%112.50%
Geliang Tang10.05%112.50%
Total1864100.00%8100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.