telemetry.c (13231B)
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") 44static struct class telem_class = { 45 .owner = THIS_MODULE, 46 .name = TELEM_CLASS_NAME, 47}; 48 49/* Keep track of all the device numbers used. */ 50#define TELEM_MAX_DEV 128 51static int telem_major; 52static DEFINE_IDA(telem_ida); 53 54/* EC telemetry command codes */ 55#define WILCO_EC_TELEM_GET_LOG 0x99 56#define WILCO_EC_TELEM_GET_VERSION 0x38 57#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E 58#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA 59#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95 60#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C 61#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07 62#define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A 63 64#define TELEM_ARGS_SIZE_MAX 30 65 66/* 67 * The following telem_args_get_* structs are embedded within the |args| field 68 * 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 = label 79 * 1 = svn_rev 80 * 2 = model_no 81 * 3 = build_date 82 * 4 = frio_version 83 */ 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 to 149 * the EC. Therefore we check to ensure that 150 * 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_SIZE 212 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 that 235 * 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 .llseek = no_llseek, 334 .owner = THIS_MODULE, 335}; 336 337/** 338 * telem_device_free() - Callback to free the telem_device_data structure. 339 * @d: The device embedded in our device data, which we have been ref counting. 340 * 341 * Once all open file descriptors are closed and the device has been removed, 342 * the refcount of the device will fall to 0 and this will be called. 343 */ 344static void telem_device_free(struct device *d) 345{ 346 struct telem_device_data *dev_data; 347 348 dev_data = container_of(d, struct telem_device_data, dev); 349 kfree(dev_data); 350} 351 352/** 353 * telem_device_probe() - Callback when creating a new device. 354 * @pdev: platform device that we will be receiving telems from. 355 * 356 * This finds a free minor number for the device, allocates and initializes 357 * some device data, and creates a new device and char dev node. 358 * 359 * Return: 0 on success, negative error code on failure. 360 */ 361static int telem_device_probe(struct platform_device *pdev) 362{ 363 struct telem_device_data *dev_data; 364 int error, minor; 365 366 /* Get the next available device number */ 367 minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL); 368 if (minor < 0) { 369 error = minor; 370 dev_err(&pdev->dev, "Failed to find minor number: %d\n", error); 371 return error; 372 } 373 374 dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); 375 if (!dev_data) { 376 ida_simple_remove(&telem_ida, minor); 377 return -ENOMEM; 378 } 379 380 /* Initialize the device data */ 381 dev_data->ec = dev_get_platdata(&pdev->dev); 382 atomic_set(&dev_data->available, 1); 383 platform_set_drvdata(pdev, dev_data); 384 385 /* Initialize the device */ 386 dev_data->dev.devt = MKDEV(telem_major, minor); 387 dev_data->dev.class = &telem_class; 388 dev_data->dev.release = telem_device_free; 389 dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor); 390 device_initialize(&dev_data->dev); 391 392 /* Initialize the character device and add it to userspace */; 393 cdev_init(&dev_data->cdev, &telem_fops); 394 error = cdev_device_add(&dev_data->cdev, &dev_data->dev); 395 if (error) { 396 put_device(&dev_data->dev); 397 ida_simple_remove(&telem_ida, minor); 398 return error; 399 } 400 401 return 0; 402} 403 404static int telem_device_remove(struct platform_device *pdev) 405{ 406 struct telem_device_data *dev_data = platform_get_drvdata(pdev); 407 408 cdev_device_del(&dev_data->cdev, &dev_data->dev); 409 ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); 410 put_device(&dev_data->dev); 411 412 return 0; 413} 414 415static struct platform_driver telem_driver = { 416 .probe = telem_device_probe, 417 .remove = telem_device_remove, 418 .driver = { 419 .name = DRV_NAME, 420 }, 421}; 422 423static int __init telem_module_init(void) 424{ 425 dev_t dev_num = 0; 426 int ret; 427 428 ret = class_register(&telem_class); 429 if (ret) { 430 pr_err(DRV_NAME ": Failed registering class: %d\n", ret); 431 return ret; 432 } 433 434 /* Request the kernel for device numbers, starting with minor=0 */ 435 ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME); 436 if (ret) { 437 pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret); 438 goto destroy_class; 439 } 440 telem_major = MAJOR(dev_num); 441 442 ret = platform_driver_register(&telem_driver); 443 if (ret < 0) { 444 pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); 445 goto unregister_region; 446 } 447 448 return 0; 449 450unregister_region: 451 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 452destroy_class: 453 class_unregister(&telem_class); 454 ida_destroy(&telem_ida); 455 return ret; 456} 457 458static void __exit telem_module_exit(void) 459{ 460 platform_driver_unregister(&telem_driver); 461 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 462 class_unregister(&telem_class); 463 ida_destroy(&telem_ida); 464} 465 466module_init(telem_module_init); 467module_exit(telem_module_exit); 468 469MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 470MODULE_DESCRIPTION("Wilco EC telemetry driver"); 471MODULE_LICENSE("GPL"); 472MODULE_ALIAS("platform:" DRV_NAME);