]> Git Repo - J-linux.git/blob - drivers/platform/chrome/wilco_ec/telemetry.c
Merge tag 'kbuild-v6.9' of git://git.kernel.org/pub/scm/linux/kernel/git/masahiroy...
[J-linux.git] / drivers / platform / chrome / wilco_ec / telemetry.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Telemetry communication for Wilco EC
4  *
5  * Copyright 2019 Google LLC
6  *
7  * The Wilco Embedded Controller is able to send telemetry data
8  * which is useful for enterprise applications. A daemon running on
9  * the OS sends a command to the EC via a write() to a char device,
10  * and can read the response with a read(). The write() request is
11  * verified by the driver to ensure that it is performing only one
12  * of the allowlisted commands, and that no extraneous data is
13  * being transmitted to the EC. The response is passed directly
14  * back to the reader with no modification.
15  *
16  * The character device will appear as /dev/wilco_telemN, where N
17  * is some small non-negative integer, starting with 0. Only one
18  * process may have the file descriptor open at a time. The calling
19  * userspace program needs to keep the device file descriptor open
20  * between the calls to write() and read() in order to preserve the
21  * response. Up to 32 bytes will be available for reading.
22  *
23  * For testing purposes, try requesting the EC's firmware build
24  * date, by sending the WILCO_EC_TELEM_GET_VERSION command with
25  * argument index=3. i.e. write [0x38, 0x00, 0x03]
26  * to the device node. An ASCII string of the build date is
27  * returned.
28  */
29
30 #include <linux/cdev.h>
31 #include <linux/device.h>
32 #include <linux/fs.h>
33 #include <linux/module.h>
34 #include <linux/platform_data/wilco-ec.h>
35 #include <linux/platform_device.h>
36 #include <linux/slab.h>
37 #include <linux/types.h>
38 #include <linux/uaccess.h>
39
40 #define TELEM_DEV_NAME          "wilco_telem"
41 #define TELEM_CLASS_NAME        TELEM_DEV_NAME
42 #define DRV_NAME                TELEM_DEV_NAME
43 #define TELEM_DEV_NAME_FMT      (TELEM_DEV_NAME "%d")
44 static struct class telem_class = {
45         .name   = TELEM_CLASS_NAME,
46 };
47
48 /* Keep track of all the device numbers used. */
49 #define TELEM_MAX_DEV 128
50 static int telem_major;
51 static DEFINE_IDA(telem_ida);
52
53 /* EC telemetry command codes */
54 #define WILCO_EC_TELEM_GET_LOG                  0x99
55 #define WILCO_EC_TELEM_GET_VERSION              0x38
56 #define WILCO_EC_TELEM_GET_FAN_INFO             0x2E
57 #define WILCO_EC_TELEM_GET_DIAG_INFO            0xFA
58 #define WILCO_EC_TELEM_GET_TEMP_INFO            0x95
59 #define WILCO_EC_TELEM_GET_TEMP_READ            0x2C
60 #define WILCO_EC_TELEM_GET_BATT_EXT_INFO        0x07
61 #define WILCO_EC_TELEM_GET_BATT_PPID_INFO       0x8A
62
63 #define TELEM_ARGS_SIZE_MAX     30
64
65 /*
66  * The following telem_args_get_* structs are embedded within the |args| field
67  * of wilco_ec_telem_request.
68  */
69
70 struct telem_args_get_log {
71         u8 log_type;
72         u8 log_index;
73 } __packed;
74
75 /*
76  * Get a piece of info about the EC firmware version:
77  * 0 = label
78  * 1 = svn_rev
79  * 2 = model_no
80  * 3 = build_date
81  * 4 = frio_version
82  */
83 struct telem_args_get_version {
84         u8 index;
85 } __packed;
86
87 struct telem_args_get_fan_info {
88         u8 command;
89         u8 fan_number;
90         u8 arg;
91 } __packed;
92
93 struct telem_args_get_diag_info {
94         u8 type;
95         u8 sub_type;
96 } __packed;
97
98 struct telem_args_get_temp_info {
99         u8 command;
100         u8 index;
101         u8 field;
102         u8 zone;
103 } __packed;
104
105 struct telem_args_get_temp_read {
106         u8 sensor_index;
107 } __packed;
108
109 struct telem_args_get_batt_ext_info {
110         u8 var_args[5];
111 } __packed;
112
113 struct telem_args_get_batt_ppid_info {
114         u8 always1; /* Should always be 1 */
115 } __packed;
116
117 /**
118  * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
119  * @command: One of WILCO_EC_TELEM_GET_* command codes.
120  * @reserved: Must be 0.
121  * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
122  */
123 struct wilco_ec_telem_request {
124         u8 command;
125         u8 reserved;
126         union {
127                 u8 buf[TELEM_ARGS_SIZE_MAX];
128                 struct telem_args_get_log               get_log;
129                 struct telem_args_get_version           get_version;
130                 struct telem_args_get_fan_info          get_fan_info;
131                 struct telem_args_get_diag_info         get_diag_info;
132                 struct telem_args_get_temp_info         get_temp_info;
133                 struct telem_args_get_temp_read         get_temp_read;
134                 struct telem_args_get_batt_ext_info     get_batt_ext_info;
135                 struct telem_args_get_batt_ppid_info    get_batt_ppid_info;
136         } args;
137 } __packed;
138
139 /**
140  * check_telem_request() - Ensure that a request from userspace is valid.
141  * @rq: Request buffer copied from userspace.
142  * @size: Number of bytes copied from userspace.
143  *
144  * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
145  *         -EMSGSIZE if the request is too long.
146  *
147  * We do not want to allow userspace to send arbitrary telemetry commands to
148  * the EC. Therefore we check to ensure that
149  * 1. The request follows the format of struct wilco_ec_telem_request.
150  * 2. The supplied command code is one of the allowlisted commands.
151  * 3. The request only contains the necessary data for the header and arguments.
152  */
153 static int check_telem_request(struct wilco_ec_telem_request *rq,
154                                size_t size)
155 {
156         size_t max_size = offsetof(struct wilco_ec_telem_request, args);
157
158         if (rq->reserved)
159                 return -EINVAL;
160
161         switch (rq->command) {
162         case WILCO_EC_TELEM_GET_LOG:
163                 max_size += sizeof(rq->args.get_log);
164                 break;
165         case WILCO_EC_TELEM_GET_VERSION:
166                 max_size += sizeof(rq->args.get_version);
167                 break;
168         case WILCO_EC_TELEM_GET_FAN_INFO:
169                 max_size += sizeof(rq->args.get_fan_info);
170                 break;
171         case WILCO_EC_TELEM_GET_DIAG_INFO:
172                 max_size += sizeof(rq->args.get_diag_info);
173                 break;
174         case WILCO_EC_TELEM_GET_TEMP_INFO:
175                 max_size += sizeof(rq->args.get_temp_info);
176                 break;
177         case WILCO_EC_TELEM_GET_TEMP_READ:
178                 max_size += sizeof(rq->args.get_temp_read);
179                 break;
180         case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
181                 max_size += sizeof(rq->args.get_batt_ext_info);
182                 break;
183         case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
184                 if (rq->args.get_batt_ppid_info.always1 != 1)
185                         return -EINVAL;
186
187                 max_size += sizeof(rq->args.get_batt_ppid_info);
188                 break;
189         default:
190                 return -EINVAL;
191         }
192
193         return (size <= max_size) ? 0 : -EMSGSIZE;
194 }
195
196 /**
197  * struct telem_device_data - Data for a Wilco EC device that queries telemetry.
198  * @cdev: Char dev that userspace reads and polls from.
199  * @dev: Device associated with the %cdev.
200  * @ec: Wilco EC that we will be communicating with using the mailbox interface.
201  * @available: Boolean of if the device can be opened.
202  */
203 struct telem_device_data {
204         struct device dev;
205         struct cdev cdev;
206         struct wilco_ec_device *ec;
207         atomic_t available;
208 };
209
210 #define TELEM_RESPONSE_SIZE     EC_MAILBOX_DATA_SIZE
211
212 /**
213  * struct telem_session_data - Data that exists between open() and release().
214  * @dev_data: Pointer to get back to the device data and EC.
215  * @request: Command and arguments sent to EC.
216  * @response: Response buffer of data from EC.
217  * @has_msg: Is there data available to read from a previous write?
218  */
219 struct telem_session_data {
220         struct telem_device_data *dev_data;
221         struct wilco_ec_telem_request request;
222         u8 response[TELEM_RESPONSE_SIZE];
223         bool has_msg;
224 };
225
226 /**
227  * telem_open() - Callback for when the device node is opened.
228  * @inode: inode for this char device node.
229  * @filp: file for this char device node.
230  *
231  * We need to ensure that after writing a command to the device,
232  * the same userspace process reads the corresponding result.
233  * Therefore, we increment a refcount on opening the device, so that
234  * only one process can communicate with the EC at a time.
235  *
236  * Return: 0 on success, or negative error code on failure.
237  */
238 static int telem_open(struct inode *inode, struct file *filp)
239 {
240         struct telem_device_data *dev_data;
241         struct telem_session_data *sess_data;
242
243         /* Ensure device isn't already open */
244         dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
245         if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
246                 return -EBUSY;
247
248         get_device(&dev_data->dev);
249
250         sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
251         if (!sess_data) {
252                 atomic_set(&dev_data->available, 1);
253                 return -ENOMEM;
254         }
255         sess_data->dev_data = dev_data;
256         sess_data->has_msg = false;
257
258         stream_open(inode, filp);
259         filp->private_data = sess_data;
260
261         return 0;
262 }
263
264 static ssize_t telem_write(struct file *filp, const char __user *buf,
265                            size_t count, loff_t *pos)
266 {
267         struct telem_session_data *sess_data = filp->private_data;
268         struct wilco_ec_message msg = {};
269         int ret;
270
271         if (count > sizeof(sess_data->request))
272                 return -EMSGSIZE;
273         memset(&sess_data->request, 0, sizeof(sess_data->request));
274         if (copy_from_user(&sess_data->request, buf, count))
275                 return -EFAULT;
276         ret = check_telem_request(&sess_data->request, count);
277         if (ret < 0)
278                 return ret;
279
280         memset(sess_data->response, 0, sizeof(sess_data->response));
281         msg.type = WILCO_EC_MSG_TELEMETRY;
282         msg.request_data = &sess_data->request;
283         msg.request_size = sizeof(sess_data->request);
284         msg.response_data = sess_data->response;
285         msg.response_size = sizeof(sess_data->response);
286
287         ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
288         if (ret < 0)
289                 return ret;
290         if (ret != sizeof(sess_data->response))
291                 return -EMSGSIZE;
292
293         sess_data->has_msg = true;
294
295         return count;
296 }
297
298 static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
299                           loff_t *pos)
300 {
301         struct telem_session_data *sess_data = filp->private_data;
302
303         if (!sess_data->has_msg)
304                 return -ENODATA;
305         if (count > sizeof(sess_data->response))
306                 return -EINVAL;
307
308         if (copy_to_user(buf, sess_data->response, count))
309                 return -EFAULT;
310
311         sess_data->has_msg = false;
312
313         return count;
314 }
315
316 static int telem_release(struct inode *inode, struct file *filp)
317 {
318         struct telem_session_data *sess_data = filp->private_data;
319
320         atomic_set(&sess_data->dev_data->available, 1);
321         put_device(&sess_data->dev_data->dev);
322         kfree(sess_data);
323
324         return 0;
325 }
326
327 static const struct file_operations telem_fops = {
328         .open = telem_open,
329         .write = telem_write,
330         .read = telem_read,
331         .release = telem_release,
332         .llseek = no_llseek,
333         .owner = THIS_MODULE,
334 };
335
336 /**
337  * telem_device_free() - Callback to free the telem_device_data structure.
338  * @d: The device embedded in our device data, which we have been ref counting.
339  *
340  * Once all open file descriptors are closed and the device has been removed,
341  * the refcount of the device will fall to 0 and this will be called.
342  */
343 static void telem_device_free(struct device *d)
344 {
345         struct telem_device_data *dev_data;
346
347         dev_data = container_of(d, struct telem_device_data, dev);
348         kfree(dev_data);
349 }
350
351 /**
352  * telem_device_probe() - Callback when creating a new device.
353  * @pdev: platform device that we will be receiving telems from.
354  *
355  * This finds a free minor number for the device, allocates and initializes
356  * some device data, and creates a new device and char dev node.
357  *
358  * Return: 0 on success, negative error code on failure.
359  */
360 static int telem_device_probe(struct platform_device *pdev)
361 {
362         struct telem_device_data *dev_data;
363         int error, minor;
364
365         /* Get the next available device number */
366         minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
367         if (minor < 0) {
368                 error = minor;
369                 dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
370                 return error;
371         }
372
373         dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
374         if (!dev_data) {
375                 ida_free(&telem_ida, minor);
376                 return -ENOMEM;
377         }
378
379         /* Initialize the device data */
380         dev_data->ec = dev_get_platdata(&pdev->dev);
381         atomic_set(&dev_data->available, 1);
382         platform_set_drvdata(pdev, dev_data);
383
384         /* Initialize the device */
385         dev_data->dev.devt = MKDEV(telem_major, minor);
386         dev_data->dev.class = &telem_class;
387         dev_data->dev.release = telem_device_free;
388         dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
389         device_initialize(&dev_data->dev);
390
391         /* Initialize the character device and add it to userspace */;
392         cdev_init(&dev_data->cdev, &telem_fops);
393         error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
394         if (error) {
395                 put_device(&dev_data->dev);
396                 ida_free(&telem_ida, minor);
397                 return error;
398         }
399
400         return 0;
401 }
402
403 static void telem_device_remove(struct platform_device *pdev)
404 {
405         struct telem_device_data *dev_data = platform_get_drvdata(pdev);
406
407         cdev_device_del(&dev_data->cdev, &dev_data->dev);
408         ida_free(&telem_ida, MINOR(dev_data->dev.devt));
409         put_device(&dev_data->dev);
410 }
411
412 static struct platform_driver telem_driver = {
413         .probe = telem_device_probe,
414         .remove_new = telem_device_remove,
415         .driver = {
416                 .name = DRV_NAME,
417         },
418 };
419
420 static int __init telem_module_init(void)
421 {
422         dev_t dev_num = 0;
423         int ret;
424
425         ret = class_register(&telem_class);
426         if (ret) {
427                 pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
428                 return ret;
429         }
430
431         /* Request the kernel for device numbers, starting with minor=0 */
432         ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
433         if (ret) {
434                 pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
435                 goto destroy_class;
436         }
437         telem_major = MAJOR(dev_num);
438
439         ret = platform_driver_register(&telem_driver);
440         if (ret < 0) {
441                 pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
442                 goto unregister_region;
443         }
444
445         return 0;
446
447 unregister_region:
448         unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
449 destroy_class:
450         class_unregister(&telem_class);
451         ida_destroy(&telem_ida);
452         return ret;
453 }
454
455 static void __exit telem_module_exit(void)
456 {
457         platform_driver_unregister(&telem_driver);
458         unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
459         class_unregister(&telem_class);
460         ida_destroy(&telem_ida);
461 }
462
463 module_init(telem_module_init);
464 module_exit(telem_module_exit);
465
466 MODULE_AUTHOR("Nick Crews <[email protected]>");
467 MODULE_DESCRIPTION("Wilco EC telemetry driver");
468 MODULE_LICENSE("GPL");
469 MODULE_ALIAS("platform:" DRV_NAME);
This page took 0.05357 seconds and 4 git commands to generate.