cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

cros_ec_lightbar.c (13980B)


      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/module.h>
     12#include <linux/platform_data/cros_ec_commands.h>
     13#include <linux/platform_data/cros_ec_proto.h>
     14#include <linux/platform_device.h>
     15#include <linux/sched.h>
     16#include <linux/types.h>
     17#include <linux/uaccess.h>
     18#include <linux/slab.h>
     19
     20#define DRV_NAME "cros-ec-lightbar"
     21
     22/* Rate-limit the lightbar interface to prevent DoS. */
     23static unsigned long lb_interval_jiffies = 50 * HZ / 1000;
     24
     25/*
     26 * Whether or not we have given userspace control of the lightbar.
     27 * If this is true, we won't do anything during suspend/resume.
     28 */
     29static bool userspace_control;
     30
     31static ssize_t interval_msec_show(struct device *dev,
     32				  struct device_attribute *attr, char *buf)
     33{
     34	unsigned long msec = lb_interval_jiffies * 1000 / HZ;
     35
     36	return scnprintf(buf, PAGE_SIZE, "%lu\n", msec);
     37}
     38
     39static ssize_t interval_msec_store(struct device *dev,
     40				   struct device_attribute *attr,
     41				   const char *buf, size_t count)
     42{
     43	unsigned long msec;
     44
     45	if (kstrtoul(buf, 0, &msec))
     46		return -EINVAL;
     47
     48	lb_interval_jiffies = msec * HZ / 1000;
     49
     50	return count;
     51}
     52
     53static DEFINE_MUTEX(lb_mutex);
     54/* Return 0 if able to throttle correctly, error otherwise */
     55static int lb_throttle(void)
     56{
     57	static unsigned long last_access;
     58	unsigned long now, next_timeslot;
     59	long delay;
     60	int ret = 0;
     61
     62	mutex_lock(&lb_mutex);
     63
     64	now = jiffies;
     65	next_timeslot = last_access + lb_interval_jiffies;
     66
     67	if (time_before(now, next_timeslot)) {
     68		delay = (long)(next_timeslot) - (long)now;
     69		set_current_state(TASK_INTERRUPTIBLE);
     70		if (schedule_timeout(delay) > 0) {
     71			/* interrupted - just abort */
     72			ret = -EINTR;
     73			goto out;
     74		}
     75		now = jiffies;
     76	}
     77
     78	last_access = now;
     79out:
     80	mutex_unlock(&lb_mutex);
     81
     82	return ret;
     83}
     84
     85static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec)
     86{
     87	struct cros_ec_command *msg;
     88	int len;
     89
     90	len = max(sizeof(struct ec_params_lightbar),
     91		  sizeof(struct ec_response_lightbar));
     92
     93	msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
     94	if (!msg)
     95		return NULL;
     96
     97	msg->version = 0;
     98	msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset;
     99	msg->outsize = sizeof(struct ec_params_lightbar);
    100	msg->insize = sizeof(struct ec_response_lightbar);
    101
    102	return msg;
    103}
    104
    105static int get_lightbar_version(struct cros_ec_dev *ec,
    106				uint32_t *ver_ptr, uint32_t *flg_ptr)
    107{
    108	struct ec_params_lightbar *param;
    109	struct ec_response_lightbar *resp;
    110	struct cros_ec_command *msg;
    111	int ret;
    112
    113	msg = alloc_lightbar_cmd_msg(ec);
    114	if (!msg)
    115		return 0;
    116
    117	param = (struct ec_params_lightbar *)msg->data;
    118	param->cmd = LIGHTBAR_CMD_VERSION;
    119	msg->outsize = sizeof(param->cmd);
    120	msg->result = sizeof(resp->version);
    121	ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
    122	if (ret < 0 && ret != -EINVAL) {
    123		ret = 0;
    124		goto exit;
    125	}
    126
    127	switch (msg->result) {
    128	case EC_RES_INVALID_PARAM:
    129		/* Pixel had no version command. */
    130		if (ver_ptr)
    131			*ver_ptr = 0;
    132		if (flg_ptr)
    133			*flg_ptr = 0;
    134		ret = 1;
    135		goto exit;
    136
    137	case EC_RES_SUCCESS:
    138		resp = (struct ec_response_lightbar *)msg->data;
    139
    140		/* Future devices w/lightbars should implement this command */
    141		if (ver_ptr)
    142			*ver_ptr = resp->version.num;
    143		if (flg_ptr)
    144			*flg_ptr = resp->version.flags;
    145		ret = 1;
    146		goto exit;
    147	}
    148
    149	/* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */
    150	ret = 0;
    151exit:
    152	kfree(msg);
    153	return ret;
    154}
    155
    156static ssize_t version_show(struct device *dev,
    157			    struct device_attribute *attr, char *buf)
    158{
    159	uint32_t version = 0, flags = 0;
    160	struct cros_ec_dev *ec = to_cros_ec_dev(dev);
    161	int ret;
    162
    163	ret = lb_throttle();
    164	if (ret)
    165		return ret;
    166
    167	/* This should always succeed, because we check during init. */
    168	if (!get_lightbar_version(ec, &version, &flags))
    169		return -EIO;
    170
    171	return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags);
    172}
    173
    174static ssize_t brightness_store(struct device *dev,
    175				struct device_attribute *attr,
    176				const char *buf, size_t count)
    177{
    178	struct ec_params_lightbar *param;
    179	struct cros_ec_command *msg;
    180	int ret;
    181	unsigned int val;
    182	struct cros_ec_dev *ec = to_cros_ec_dev(dev);
    183
    184	if (kstrtouint(buf, 0, &val))
    185		return -EINVAL;
    186
    187	msg = alloc_lightbar_cmd_msg(ec);
    188	if (!msg)
    189		return -ENOMEM;
    190
    191	param = (struct ec_params_lightbar *)msg->data;
    192	param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS;
    193	param->set_brightness.num = val;
    194	ret = lb_throttle();
    195	if (ret)
    196		goto exit;
    197
    198	ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
    199	if (ret < 0)
    200		goto exit;
    201
    202	ret = count;
    203exit:
    204	kfree(msg);
    205	return ret;
    206}
    207
    208
    209/*
    210 * We expect numbers, and we'll keep reading until we find them, skipping over
    211 * any whitespace (sysfs guarantees that the input is null-terminated). Every
    212 * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first
    213 * parsing error, if we don't parse any numbers, or if we have numbers left
    214 * over.
    215 */
    216static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr,
    217			     const char *buf, size_t count)
    218{
    219	struct ec_params_lightbar *param;
    220	struct cros_ec_command *msg;
    221	struct cros_ec_dev *ec = to_cros_ec_dev(dev);
    222	unsigned int val[4];
    223	int ret, i = 0, j = 0, ok = 0;
    224
    225	msg = alloc_lightbar_cmd_msg(ec);
    226	if (!msg)
    227		return -ENOMEM;
    228
    229	do {
    230		/* Skip any whitespace */
    231		while (*buf && isspace(*buf))
    232			buf++;
    233
    234		if (!*buf)
    235			break;
    236
    237		ret = sscanf(buf, "%i", &val[i++]);
    238		if (ret == 0)
    239			goto exit;
    240
    241		if (i == 4) {
    242			param = (struct ec_params_lightbar *)msg->data;
    243			param->cmd = LIGHTBAR_CMD_SET_RGB;
    244			param->set_rgb.led = val[0];
    245			param->set_rgb.red = val[1];
    246			param->set_rgb.green = val[2];
    247			param->set_rgb.blue = val[3];
    248			/*
    249			 * Throttle only the first of every four transactions,
    250			 * so that the user can update all four LEDs at once.
    251			 */
    252			if ((j++ % 4) == 0) {
    253				ret = lb_throttle();
    254				if (ret)
    255					goto exit;
    256			}
    257
    258			ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
    259			if (ret < 0)
    260				goto exit;
    261
    262			i = 0;
    263			ok = 1;
    264		}
    265
    266		/* Skip over the number we just read */
    267		while (*buf && !isspace(*buf))
    268			buf++;
    269
    270	} while (*buf);
    271
    272exit:
    273	kfree(msg);
    274	return (ok && i == 0) ? count : -EINVAL;
    275}
    276
    277static char const *seqname[] = {
    278	"ERROR", "S5", "S3", "S0", "S5S3", "S3S0",
    279	"S0S3", "S3S5", "STOP", "RUN", "KONAMI",
    280	"TAP", "PROGRAM",
    281};
    282
    283static ssize_t sequence_show(struct device *dev,
    284			     struct device_attribute *attr, char *buf)
    285{
    286	struct ec_params_lightbar *param;
    287	struct ec_response_lightbar *resp;
    288	struct cros_ec_command *msg;
    289	int ret;
    290	struct cros_ec_dev *ec = to_cros_ec_dev(dev);
    291
    292	msg = alloc_lightbar_cmd_msg(ec);
    293	if (!msg)
    294		return -ENOMEM;
    295
    296	param = (struct ec_params_lightbar *)msg->data;
    297	param->cmd = LIGHTBAR_CMD_GET_SEQ;
    298	ret = lb_throttle();
    299	if (ret)
    300		goto exit;
    301
    302	ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
    303	if (ret < 0) {
    304		ret = scnprintf(buf, PAGE_SIZE, "XFER / EC ERROR %d / %d\n",
    305				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 = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num);
    312	else
    313		ret = scnprintf(buf, PAGE_SIZE, "%s\n",
    314				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 EC
    433	 * enforces a maximum program size, but we also don't want to try
    434	 * and send a program that is too big for the protocol. In order
    435	 * to ensure the latter, we also need to ensure we have extra bytes
    436	 * 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 use
    465	 * EC_LB_PROG_LEN. This might be too long, and the program
    466	 * 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 scnprintf(buf, PAGE_SIZE, "%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 = strtobool(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 EC
    540	 * 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 int 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	return 0;
    575}
    576
    577static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
    578{
    579	struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
    580
    581	if (userspace_control)
    582		return 0;
    583
    584	return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_RESUME);
    585}
    586
    587static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
    588{
    589	struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
    590
    591	if (userspace_control)
    592		return 0;
    593
    594	return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_SUSPEND);
    595}
    596
    597static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops,
    598			 cros_ec_lightbar_suspend, cros_ec_lightbar_resume);
    599
    600static struct platform_driver cros_ec_lightbar_driver = {
    601	.driver = {
    602		.name = DRV_NAME,
    603		.pm = &cros_ec_lightbar_pm_ops,
    604	},
    605	.probe = cros_ec_lightbar_probe,
    606	.remove = cros_ec_lightbar_remove,
    607};
    608
    609module_platform_driver(cros_ec_lightbar_driver);
    610
    611MODULE_LICENSE("GPL");
    612MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace");
    613MODULE_ALIAS("platform:" DRV_NAME);