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_simple_remove(&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_simple_remove(&telem_ida, minor); 397 return error; 398 } 399 400 return 0; 401 } 402 403 static int 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_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); 409 put_device(&dev_data->dev); 410 411 return 0; 412 } 413 414 static struct platform_driver telem_driver = { 415 .probe = telem_device_probe, 416 .remove = telem_device_remove, 417 .driver = { 418 .name = DRV_NAME, 419 }, 420 }; 421 422 static int __init telem_module_init(void) 423 { 424 dev_t dev_num = 0; 425 int ret; 426 427 ret = class_register(&telem_class); 428 if (ret) { 429 pr_err(DRV_NAME ": Failed registering class: %d\n", ret); 430 return ret; 431 } 432 433 /* Request the kernel for device numbers, starting with minor=0 */ 434 ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME); 435 if (ret) { 436 pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret); 437 goto destroy_class; 438 } 439 telem_major = MAJOR(dev_num); 440 441 ret = platform_driver_register(&telem_driver); 442 if (ret < 0) { 443 pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); 444 goto unregister_region; 445 } 446 447 return 0; 448 449 unregister_region: 450 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 451 destroy_class: 452 class_unregister(&telem_class); 453 ida_destroy(&telem_ida); 454 return ret; 455 } 456 457 static void __exit telem_module_exit(void) 458 { 459 platform_driver_unregister(&telem_driver); 460 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 461 class_unregister(&telem_class); 462 ida_destroy(&telem_ida); 463 } 464 465 module_init(telem_module_init); 466 module_exit(telem_module_exit); 467 468 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 469 MODULE_DESCRIPTION("Wilco EC telemetry driver"); 470 MODULE_LICENSE("GPL"); 471 MODULE_ALIAS("platform:" DRV_NAME); 472