File Index
Files and directories
Sign in
FILE

cros_ec_chardev.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/chrome/cros_ec_chardev.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Miscellaneous character driver for ChromeOS Embedded Controller
  4 *
  5 * Copyright 2014 Google, Inc.
  6 * Copyright 2019 Google LLC
  7 *
  8 * This file is a rework and part of the code is ported from
  9 * drivers/mfd/cros_ec_dev.c that was originally written by
 10 * Bill Richardson.
 11 */
 12
 13#include <linux/init.h>
 14#include <linux/device.h>
 15#include <linux/fs.h>
 16#include <linux/miscdevice.h>
 17#include <linux/mod_devicetable.h>
 18#include <linux/module.h>
 19#include <linux/notifier.h>
 20#include <linux/platform_data/cros_ec_chardev.h>
 21#include <linux/platform_data/cros_ec_commands.h>
 22#include <linux/platform_data/cros_ec_proto.h>
 23#include <linux/platform_device.h>
 24#include <linux/poll.h>
 25#include <linux/slab.h>
 26#include <linux/types.h>
 27#include <linux/uaccess.h>
 28
 29#define DRV_NAME		"cros-ec-chardev"
 30
 31/* Arbitrary bounded size for the event queue */
 32#define CROS_MAX_EVENT_LEN	PAGE_SIZE
 33
 34struct chardev_priv {
 35	struct cros_ec_device *ec_dev;
 36	struct notifier_block notifier;
 37	wait_queue_head_t wait_event;
 38	unsigned long event_mask;
 39	struct list_head events;
 40	size_t event_len;
 41	u16 cmd_offset;
 42};
 43
 44struct ec_event {
 45	struct list_head node;
 46	size_t size;
 47	u8 event_type;
 48	u8 data[];
 49};
 50
 51static int ec_get_version(struct chardev_priv *priv, char *str, int maxlen)
 52{
 53	static const char * const current_image_name[] = {
 54		"unknown", "read-only", "read-write", "invalid",
 55	};
 56	struct ec_response_get_version *resp;
 57	struct cros_ec_command *msg;
 58	int ret;
 59
 60	msg = kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
 61	if (!msg)
 62		return -ENOMEM;
 63
 64	msg->command = EC_CMD_GET_VERSION + priv->cmd_offset;
 65	msg->insize = sizeof(*resp);
 66
 67	ret = cros_ec_cmd_xfer_status(priv->ec_dev, msg);
 68	if (ret < 0) {
 69		snprintf(str, maxlen,
 70			 "Unknown EC version, returned error: %d\n",
 71			 msg->result);
 72		goto exit;
 73	}
 74
 75	resp = (struct ec_response_get_version *)msg->data;
 76	if (resp->current_image >= ARRAY_SIZE(current_image_name))
 77		resp->current_image = 3; /* invalid */
 78
 79	snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION,
 80		 resp->version_string_ro, resp->version_string_rw,
 81		 current_image_name[resp->current_image]);
 82
 83	ret = 0;
 84exit:
 85	kfree(msg);
 86	return ret;
 87}
 88
 89static int cros_ec_chardev_mkbp_event(struct notifier_block *nb,
 90				      unsigned long queued_during_suspend,
 91				      void *_notify)
 92{
 93	struct chardev_priv *priv = container_of(nb, struct chardev_priv,
 94						 notifier);
 95	struct cros_ec_device *ec_dev = priv->ec_dev;
 96	struct ec_event *event;
 97	unsigned long event_bit = 1 << ec_dev->event_data.event_type;
 98	int total_size = sizeof(*event) + ec_dev->event_size;
 99
100	if (!(event_bit & priv->event_mask) ||
101	    (priv->event_len + total_size) > CROS_MAX_EVENT_LEN)
102		return NOTIFY_DONE;
103
104	event = kzalloc(total_size, GFP_KERNEL);
105	if (!event)
106		return NOTIFY_DONE;
107
108	event->size = ec_dev->event_size;
109	event->event_type = ec_dev->event_data.event_type;
110	memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size);
111
112	spin_lock(&priv->wait_event.lock);
113	list_add_tail(&event->node, &priv->events);
114	priv->event_len += total_size;
115	wake_up_locked(&priv->wait_event);
116	spin_unlock(&priv->wait_event.lock);
117
118	return NOTIFY_OK;
119}
120
121static struct ec_event *cros_ec_chardev_fetch_event(struct chardev_priv *priv,
122						    bool fetch, bool block)
123{
124	struct ec_event *event;
125	int err;
126
127	spin_lock(&priv->wait_event.lock);
128	if (!block && list_empty(&priv->events)) {
129		event = ERR_PTR(-EWOULDBLOCK);
130		goto out;
131	}
132
133	if (!fetch) {
134		event = NULL;
135		goto out;
136	}
137
138	err = wait_event_interruptible_locked(priv->wait_event,
139					      !list_empty(&priv->events));
140	if (err) {
141		event = ERR_PTR(err);
142		goto out;
143	}
144
145	event = list_first_entry(&priv->events, struct ec_event, node);
146	list_del(&event->node);
147	priv->event_len -= sizeof(*event) + event->size;
148
149out:
150	spin_unlock(&priv->wait_event.lock);
151	return event;
152}
153
154/*
155 * Device file ops
156 */
157static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
158{
159	struct miscdevice *mdev = filp->private_data;
160	struct cros_ec_dev *ec = dev_get_drvdata(mdev->parent);
161	struct cros_ec_device *ec_dev = ec->ec_dev;
162	struct chardev_priv *priv;
163	int ret;
164
165	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
166	if (!priv)
167		return -ENOMEM;
168
169	priv->ec_dev = ec_dev;
170	priv->cmd_offset = ec->cmd_offset;
171	filp->private_data = priv;
172	INIT_LIST_HEAD(&priv->events);
173	init_waitqueue_head(&priv->wait_event);
174	nonseekable_open(inode, filp);
175
176	priv->notifier.notifier_call = cros_ec_chardev_mkbp_event;
177	ret = blocking_notifier_chain_register(&ec_dev->event_notifier,
178					       &priv->notifier);
179	if (ret) {
180		dev_err(ec_dev->dev, "failed to register event notifier\n");
181		kfree(priv);
182	}
183
184	return ret;
185}
186
187static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait)
188{
189	struct chardev_priv *priv = filp->private_data;
190
191	poll_wait(filp, &priv->wait_event, wait);
192
193	if (list_empty(&priv->events))
194		return 0;
195
196	return EPOLLIN | EPOLLRDNORM;
197}
198
199static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer,
200				     size_t length, loff_t *offset)
201{
202	char msg[sizeof(struct ec_response_get_version) +
203		 sizeof(CROS_EC_DEV_VERSION)];
204	struct chardev_priv *priv = filp->private_data;
205	size_t count;
206	int ret;
207
208	if (priv->event_mask) { /* queued MKBP event */
209		struct ec_event *event;
210
211		event = cros_ec_chardev_fetch_event(priv, length != 0,
212						!(filp->f_flags & O_NONBLOCK));
213		if (IS_ERR(event))
214			return PTR_ERR(event);
215		/*
216		 * length == 0 is special - no IO is done but we check
217		 * for error conditions.
218		 */
219		if (length == 0)
220			return 0;
221
222		/* The event is 1 byte of type plus the payload */
223		count = min(length, event->size + 1);
224		ret = copy_to_user(buffer, &event->event_type, count);
225		kfree(event);
226		if (ret) /* the copy failed */
227			return -EFAULT;
228		*offset = count;
229		return count;
230	}
231
232	/*
233	 * Legacy behavior if no event mask is defined
234	 */
235	if (*offset != 0)
236		return 0;
237
238	ret = ec_get_version(priv, msg, sizeof(msg));
239	if (ret)
240		return ret;
241
242	count = min(length, strlen(msg));
243
244	if (copy_to_user(buffer, msg, count))
245		return -EFAULT;
246
247	*offset = count;
248	return count;
249}
250
251static int cros_ec_chardev_release(struct inode *inode, struct file *filp)
252{
253	struct chardev_priv *priv = filp->private_data;
254	struct cros_ec_device *ec_dev = priv->ec_dev;
255	struct ec_event *event, *e;
256
257	blocking_notifier_chain_unregister(&ec_dev->event_notifier,
258					   &priv->notifier);
259
260	list_for_each_entry_safe(event, e, &priv->events, node) {
261		list_del(&event->node);
262		kfree(event);
263	}
264	kfree(priv);
265
266	return 0;
267}
268
269/*
270 * Ioctls
271 */
272static long cros_ec_chardev_ioctl_xcmd(struct chardev_priv *priv, void __user *arg)
273{
274	struct cros_ec_command *s_cmd;
275	struct cros_ec_command u_cmd;
276	long ret;
277
278	if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
279		return -EFAULT;
280
281	if (u_cmd.outsize > EC_MAX_MSG_BYTES ||
282	    u_cmd.insize > EC_MAX_MSG_BYTES)
283		return -EINVAL;
284
285	s_cmd = kzalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize),
286			GFP_KERNEL);
287	if (!s_cmd)
288		return -ENOMEM;
289
290	if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) {
291		ret = -EFAULT;
292		goto exit;
293	}
294
295	if (u_cmd.outsize != s_cmd->outsize ||
296	    u_cmd.insize != s_cmd->insize) {
297		ret = -EINVAL;
298		goto exit;
299	}
300
301	s_cmd->command += priv->cmd_offset;
302	ret = cros_ec_cmd_xfer(priv->ec_dev, s_cmd);
303	/* Only copy data to userland if data was received. */
304	if (ret < 0)
305		goto exit;
306
307	if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize))
308		ret = -EFAULT;
309exit:
310	kfree(s_cmd);
311	return ret;
312}
313
314static long cros_ec_chardev_ioctl_readmem(struct chardev_priv *priv, void __user *arg)
315{
316	struct cros_ec_device *ec_dev = priv->ec_dev;
317	struct cros_ec_readmem s_mem = { };
318	long num;
319
320	/* Not every platform supports direct reads */
321	if (!ec_dev->cmd_readmem)
322		return -ENOTTY;
323
324	if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
325		return -EFAULT;
326
327	if (s_mem.bytes > sizeof(s_mem.buffer))
328		return -EINVAL;
329
330	num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes,
331				  s_mem.buffer);
332	if (num <= 0)
333		return num;
334
335	if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem)))
336		return -EFAULT;
337
338	return num;
339}
340
341static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd,
342				   unsigned long arg)
343{
344	struct chardev_priv *priv = filp->private_data;
345
346	if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
347		return -ENOTTY;
348
349	switch (cmd) {
350	case CROS_EC_DEV_IOCXCMD:
351		return cros_ec_chardev_ioctl_xcmd(priv, (void __user *)arg);
352	case CROS_EC_DEV_IOCRDMEM:
353		return cros_ec_chardev_ioctl_readmem(priv, (void __user *)arg);
354	case CROS_EC_DEV_IOCEVENTMASK:
355		priv->event_mask = arg;
356		return 0;
357	}
358
359	return -ENOTTY;
360}
361
362static const struct file_operations chardev_fops = {
363	.open		= cros_ec_chardev_open,
364	.poll		= cros_ec_chardev_poll,
365	.read		= cros_ec_chardev_read,
366	.release	= cros_ec_chardev_release,
367	.unlocked_ioctl	= cros_ec_chardev_ioctl,
368#ifdef CONFIG_COMPAT
369	.compat_ioctl	= cros_ec_chardev_ioctl,
370#endif
371};
372
373static int cros_ec_chardev_probe(struct platform_device *pdev)
374{
375	struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent);
376	struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev);
377	struct miscdevice *misc;
378
379	/* Create a char device: we want to create it anew */
380	misc = devm_kzalloc(&pdev->dev, sizeof(*misc), GFP_KERNEL);
381	if (!misc)
382		return -ENOMEM;
383
384	misc->minor = MISC_DYNAMIC_MINOR;
385	misc->fops = &chardev_fops;
386	misc->name = ec_platform->ec_name;
387	misc->parent = pdev->dev.parent;
388
389	dev_set_drvdata(&pdev->dev, misc);
390
391	return misc_register(misc);
392}
393
394static void cros_ec_chardev_remove(struct platform_device *pdev)
395{
396	struct miscdevice *misc = dev_get_drvdata(&pdev->dev);
397
398	misc_deregister(misc);
399}
400
401static const struct platform_device_id cros_ec_chardev_id[] = {
402	{ DRV_NAME, 0 },
403	{}
404};
405MODULE_DEVICE_TABLE(platform, cros_ec_chardev_id);
406
407static struct platform_driver cros_ec_chardev_driver = {
408	.driver = {
409		.name = DRV_NAME,
410	},
411	.probe = cros_ec_chardev_probe,
412	.remove = cros_ec_chardev_remove,
413	.id_table = cros_ec_chardev_id,
414};
415
416module_platform_driver(cros_ec_chardev_driver);
417
418MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>");
419MODULE_DESCRIPTION("ChromeOS EC Miscellaneous Character Driver");
420MODULE_LICENSE("GPL");
~/ Read-only file preview.