brintos

brintos / linux-shallow public Read only

0
0
Text · 13.8 KiB · 1e69f61 Raw
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