476 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Telemetry communication for Wilco EC4 *5 * Copyright 2019 Google LLC6 *7 * The Wilco Embedded Controller is able to send telemetry data8 * which is useful for enterprise applications. A daemon running on9 * 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 is11 * verified by the driver to ensure that it is performing only one12 * of the allowlisted commands, and that no extraneous data is13 * being transmitted to the EC. The response is passed directly14 * back to the reader with no modification.15 *16 * The character device will appear as /dev/wilco_telemN, where N17 * is some small non-negative integer, starting with 0. Only one18 * process may have the file descriptor open at a time. The calling19 * userspace program needs to keep the device file descriptor open20 * between the calls to write() and read() in order to preserve the21 * response. Up to 32 bytes will be available for reading.22 *23 * For testing purposes, try requesting the EC's firmware build24 * date, by sending the WILCO_EC_TELEM_GET_VERSION command with25 * argument index=3. i.e. write [0x38, 0x00, 0x03]26 * to the device node. An ASCII string of the build date is27 * returned.28 */29 30#include <linux/cdev.h>31#include <linux/device.h>32#include <linux/fs.h>33#include <linux/mod_devicetable.h>34#include <linux/module.h>35#include <linux/platform_data/wilco-ec.h>36#include <linux/platform_device.h>37#include <linux/slab.h>38#include <linux/types.h>39#include <linux/uaccess.h>40 41#define TELEM_DEV_NAME "wilco_telem"42#define TELEM_CLASS_NAME TELEM_DEV_NAME43#define DRV_NAME TELEM_DEV_NAME44#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d")45static struct class telem_class = {46 .name = TELEM_CLASS_NAME,47};48 49/* Keep track of all the device numbers used. */50#define TELEM_MAX_DEV 12851static int telem_major;52static DEFINE_IDA(telem_ida);53 54/* EC telemetry command codes */55#define WILCO_EC_TELEM_GET_LOG 0x9956#define WILCO_EC_TELEM_GET_VERSION 0x3857#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E58#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA59#define WILCO_EC_TELEM_GET_TEMP_INFO 0x9560#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C61#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x0762#define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A63 64#define TELEM_ARGS_SIZE_MAX 3065 66/*67 * The following telem_args_get_* structs are embedded within the |args| field68 * of wilco_ec_telem_request.69 */70 71struct telem_args_get_log {72 u8 log_type;73 u8 log_index;74} __packed;75 76/*77 * Get a piece of info about the EC firmware version:78 * 0 = label79 * 1 = svn_rev80 * 2 = model_no81 * 3 = build_date82 * 4 = frio_version83 */84struct telem_args_get_version {85 u8 index;86} __packed;87 88struct telem_args_get_fan_info {89 u8 command;90 u8 fan_number;91 u8 arg;92} __packed;93 94struct telem_args_get_diag_info {95 u8 type;96 u8 sub_type;97} __packed;98 99struct telem_args_get_temp_info {100 u8 command;101 u8 index;102 u8 field;103 u8 zone;104} __packed;105 106struct telem_args_get_temp_read {107 u8 sensor_index;108} __packed;109 110struct telem_args_get_batt_ext_info {111 u8 var_args[5];112} __packed;113 114struct telem_args_get_batt_ppid_info {115 u8 always1; /* Should always be 1 */116} __packed;117 118/**119 * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.120 * @command: One of WILCO_EC_TELEM_GET_* command codes.121 * @reserved: Must be 0.122 * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.123 */124struct wilco_ec_telem_request {125 u8 command;126 u8 reserved;127 union {128 u8 buf[TELEM_ARGS_SIZE_MAX];129 struct telem_args_get_log get_log;130 struct telem_args_get_version get_version;131 struct telem_args_get_fan_info get_fan_info;132 struct telem_args_get_diag_info get_diag_info;133 struct telem_args_get_temp_info get_temp_info;134 struct telem_args_get_temp_read get_temp_read;135 struct telem_args_get_batt_ext_info get_batt_ext_info;136 struct telem_args_get_batt_ppid_info get_batt_ppid_info;137 } args;138} __packed;139 140/**141 * check_telem_request() - Ensure that a request from userspace is valid.142 * @rq: Request buffer copied from userspace.143 * @size: Number of bytes copied from userspace.144 *145 * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,146 * -EMSGSIZE if the request is too long.147 *148 * We do not want to allow userspace to send arbitrary telemetry commands to149 * the EC. Therefore we check to ensure that150 * 1. The request follows the format of struct wilco_ec_telem_request.151 * 2. The supplied command code is one of the allowlisted commands.152 * 3. The request only contains the necessary data for the header and arguments.153 */154static int check_telem_request(struct wilco_ec_telem_request *rq,155 size_t size)156{157 size_t max_size = offsetof(struct wilco_ec_telem_request, args);158 159 if (rq->reserved)160 return -EINVAL;161 162 switch (rq->command) {163 case WILCO_EC_TELEM_GET_LOG:164 max_size += sizeof(rq->args.get_log);165 break;166 case WILCO_EC_TELEM_GET_VERSION:167 max_size += sizeof(rq->args.get_version);168 break;169 case WILCO_EC_TELEM_GET_FAN_INFO:170 max_size += sizeof(rq->args.get_fan_info);171 break;172 case WILCO_EC_TELEM_GET_DIAG_INFO:173 max_size += sizeof(rq->args.get_diag_info);174 break;175 case WILCO_EC_TELEM_GET_TEMP_INFO:176 max_size += sizeof(rq->args.get_temp_info);177 break;178 case WILCO_EC_TELEM_GET_TEMP_READ:179 max_size += sizeof(rq->args.get_temp_read);180 break;181 case WILCO_EC_TELEM_GET_BATT_EXT_INFO:182 max_size += sizeof(rq->args.get_batt_ext_info);183 break;184 case WILCO_EC_TELEM_GET_BATT_PPID_INFO:185 if (rq->args.get_batt_ppid_info.always1 != 1)186 return -EINVAL;187 188 max_size += sizeof(rq->args.get_batt_ppid_info);189 break;190 default:191 return -EINVAL;192 }193 194 return (size <= max_size) ? 0 : -EMSGSIZE;195}196 197/**198 * struct telem_device_data - Data for a Wilco EC device that queries telemetry.199 * @cdev: Char dev that userspace reads and polls from.200 * @dev: Device associated with the %cdev.201 * @ec: Wilco EC that we will be communicating with using the mailbox interface.202 * @available: Boolean of if the device can be opened.203 */204struct telem_device_data {205 struct device dev;206 struct cdev cdev;207 struct wilco_ec_device *ec;208 atomic_t available;209};210 211#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE212 213/**214 * struct telem_session_data - Data that exists between open() and release().215 * @dev_data: Pointer to get back to the device data and EC.216 * @request: Command and arguments sent to EC.217 * @response: Response buffer of data from EC.218 * @has_msg: Is there data available to read from a previous write?219 */220struct telem_session_data {221 struct telem_device_data *dev_data;222 struct wilco_ec_telem_request request;223 u8 response[TELEM_RESPONSE_SIZE];224 bool has_msg;225};226 227/**228 * telem_open() - Callback for when the device node is opened.229 * @inode: inode for this char device node.230 * @filp: file for this char device node.231 *232 * We need to ensure that after writing a command to the device,233 * the same userspace process reads the corresponding result.234 * Therefore, we increment a refcount on opening the device, so that235 * only one process can communicate with the EC at a time.236 *237 * Return: 0 on success, or negative error code on failure.238 */239static int telem_open(struct inode *inode, struct file *filp)240{241 struct telem_device_data *dev_data;242 struct telem_session_data *sess_data;243 244 /* Ensure device isn't already open */245 dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);246 if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)247 return -EBUSY;248 249 get_device(&dev_data->dev);250 251 sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);252 if (!sess_data) {253 atomic_set(&dev_data->available, 1);254 return -ENOMEM;255 }256 sess_data->dev_data = dev_data;257 sess_data->has_msg = false;258 259 stream_open(inode, filp);260 filp->private_data = sess_data;261 262 return 0;263}264 265static ssize_t telem_write(struct file *filp, const char __user *buf,266 size_t count, loff_t *pos)267{268 struct telem_session_data *sess_data = filp->private_data;269 struct wilco_ec_message msg = {};270 int ret;271 272 if (count > sizeof(sess_data->request))273 return -EMSGSIZE;274 memset(&sess_data->request, 0, sizeof(sess_data->request));275 if (copy_from_user(&sess_data->request, buf, count))276 return -EFAULT;277 ret = check_telem_request(&sess_data->request, count);278 if (ret < 0)279 return ret;280 281 memset(sess_data->response, 0, sizeof(sess_data->response));282 msg.type = WILCO_EC_MSG_TELEMETRY;283 msg.request_data = &sess_data->request;284 msg.request_size = sizeof(sess_data->request);285 msg.response_data = sess_data->response;286 msg.response_size = sizeof(sess_data->response);287 288 ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);289 if (ret < 0)290 return ret;291 if (ret != sizeof(sess_data->response))292 return -EMSGSIZE;293 294 sess_data->has_msg = true;295 296 return count;297}298 299static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,300 loff_t *pos)301{302 struct telem_session_data *sess_data = filp->private_data;303 304 if (!sess_data->has_msg)305 return -ENODATA;306 if (count > sizeof(sess_data->response))307 return -EINVAL;308 309 if (copy_to_user(buf, sess_data->response, count))310 return -EFAULT;311 312 sess_data->has_msg = false;313 314 return count;315}316 317static int telem_release(struct inode *inode, struct file *filp)318{319 struct telem_session_data *sess_data = filp->private_data;320 321 atomic_set(&sess_data->dev_data->available, 1);322 put_device(&sess_data->dev_data->dev);323 kfree(sess_data);324 325 return 0;326}327 328static const struct file_operations telem_fops = {329 .open = telem_open,330 .write = telem_write,331 .read = telem_read,332 .release = telem_release,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 */343static 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 initializes356 * some device data, and creates a new device and char dev node.357 *358 * Return: 0 on success, negative error code on failure.359 */360static 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 403static 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 412static const struct platform_device_id telem_id[] = {413 { DRV_NAME, 0 },414 {}415};416MODULE_DEVICE_TABLE(platform, telem_id);417 418static struct platform_driver telem_driver = {419 .probe = telem_device_probe,420 .remove_new = telem_device_remove,421 .driver = {422 .name = DRV_NAME,423 },424 .id_table = telem_id,425};426 427static int __init telem_module_init(void)428{429 dev_t dev_num = 0;430 int ret;431 432 ret = class_register(&telem_class);433 if (ret) {434 pr_err(DRV_NAME ": Failed registering class: %d\n", ret);435 return ret;436 }437 438 /* Request the kernel for device numbers, starting with minor=0 */439 ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);440 if (ret) {441 pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);442 goto destroy_class;443 }444 telem_major = MAJOR(dev_num);445 446 ret = platform_driver_register(&telem_driver);447 if (ret < 0) {448 pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);449 goto unregister_region;450 }451 452 return 0;453 454unregister_region:455 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);456destroy_class:457 class_unregister(&telem_class);458 ida_destroy(&telem_ida);459 return ret;460}461 462static void __exit telem_module_exit(void)463{464 platform_driver_unregister(&telem_driver);465 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);466 class_unregister(&telem_class);467 ida_destroy(&telem_ida);468}469 470module_init(telem_module_init);471module_exit(telem_module_exit);472 473MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");474MODULE_DESCRIPTION("Wilco EC telemetry driver");475MODULE_LICENSE("GPL");476