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 whitelisted 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 .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 51 static int telem_major; 52 static 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 63 #define TELEM_ARGS_SIZE_MAX 30 64 65 /** 66 * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC. 67 * @command: One of WILCO_EC_TELEM_GET_* command codes. 68 * @reserved: Must be 0. 69 * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0. 70 */ 71 struct wilco_ec_telem_request { 72 u8 command; 73 u8 reserved; 74 u8 args[TELEM_ARGS_SIZE_MAX]; 75 } __packed; 76 77 /* 78 * The following telem_args_get_* structs are embedded within the |args| field 79 * of wilco_ec_telem_request. 80 */ 81 82 struct telem_args_get_log { 83 u8 log_type; 84 u8 log_index; 85 } __packed; 86 87 /* 88 * Get a piece of info about the EC firmware version: 89 * 0 = label 90 * 1 = svn_rev 91 * 2 = model_no 92 * 3 = build_date 93 * 4 = frio_version 94 */ 95 struct telem_args_get_version { 96 u8 index; 97 } __packed; 98 99 struct telem_args_get_fan_info { 100 u8 command; 101 u8 fan_number; 102 u8 arg; 103 } __packed; 104 105 struct telem_args_get_diag_info { 106 u8 type; 107 u8 sub_type; 108 } __packed; 109 110 struct telem_args_get_temp_info { 111 u8 command; 112 u8 index; 113 u8 field; 114 u8 zone; 115 } __packed; 116 117 struct telem_args_get_temp_read { 118 u8 sensor_index; 119 } __packed; 120 121 struct telem_args_get_batt_ext_info { 122 u8 var_args[5]; 123 } __packed; 124 125 /** 126 * check_telem_request() - Ensure that a request from userspace is valid. 127 * @rq: Request buffer copied from userspace. 128 * @size: Number of bytes copied from userspace. 129 * 130 * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero, 131 * -EMSGSIZE if the request is too long. 132 * 133 * We do not want to allow userspace to send arbitrary telemetry commands to 134 * the EC. Therefore we check to ensure that 135 * 1. The request follows the format of struct wilco_ec_telem_request. 136 * 2. The supplied command code is one of the whitelisted commands. 137 * 3. The request only contains the necessary data for the header and arguments. 138 */ 139 static int check_telem_request(struct wilco_ec_telem_request *rq, 140 size_t size) 141 { 142 size_t max_size = offsetof(struct wilco_ec_telem_request, args); 143 144 if (rq->reserved) 145 return -EINVAL; 146 147 switch (rq->command) { 148 case WILCO_EC_TELEM_GET_LOG: 149 max_size += sizeof(struct telem_args_get_log); 150 break; 151 case WILCO_EC_TELEM_GET_VERSION: 152 max_size += sizeof(struct telem_args_get_version); 153 break; 154 case WILCO_EC_TELEM_GET_FAN_INFO: 155 max_size += sizeof(struct telem_args_get_fan_info); 156 break; 157 case WILCO_EC_TELEM_GET_DIAG_INFO: 158 max_size += sizeof(struct telem_args_get_diag_info); 159 break; 160 case WILCO_EC_TELEM_GET_TEMP_INFO: 161 max_size += sizeof(struct telem_args_get_temp_info); 162 break; 163 case WILCO_EC_TELEM_GET_TEMP_READ: 164 max_size += sizeof(struct telem_args_get_temp_read); 165 break; 166 case WILCO_EC_TELEM_GET_BATT_EXT_INFO: 167 max_size += sizeof(struct telem_args_get_batt_ext_info); 168 break; 169 default: 170 return -EINVAL; 171 } 172 173 return (size <= max_size) ? 0 : -EMSGSIZE; 174 } 175 176 /** 177 * struct telem_device_data - Data for a Wilco EC device that queries telemetry. 178 * @cdev: Char dev that userspace reads and polls from. 179 * @dev: Device associated with the %cdev. 180 * @ec: Wilco EC that we will be communicating with using the mailbox interface. 181 * @available: Boolean of if the device can be opened. 182 */ 183 struct telem_device_data { 184 struct device dev; 185 struct cdev cdev; 186 struct wilco_ec_device *ec; 187 atomic_t available; 188 }; 189 190 #define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE 191 192 /** 193 * struct telem_session_data - Data that exists between open() and release(). 194 * @dev_data: Pointer to get back to the device data and EC. 195 * @request: Command and arguments sent to EC. 196 * @response: Response buffer of data from EC. 197 * @has_msg: Is there data available to read from a previous write? 198 */ 199 struct telem_session_data { 200 struct telem_device_data *dev_data; 201 struct wilco_ec_telem_request request; 202 u8 response[TELEM_RESPONSE_SIZE]; 203 bool has_msg; 204 }; 205 206 /** 207 * telem_open() - Callback for when the device node is opened. 208 * @inode: inode for this char device node. 209 * @filp: file for this char device node. 210 * 211 * We need to ensure that after writing a command to the device, 212 * the same userspace process reads the corresponding result. 213 * Therefore, we increment a refcount on opening the device, so that 214 * only one process can communicate with the EC at a time. 215 * 216 * Return: 0 on success, or negative error code on failure. 217 */ 218 static int telem_open(struct inode *inode, struct file *filp) 219 { 220 struct telem_device_data *dev_data; 221 struct telem_session_data *sess_data; 222 223 /* Ensure device isn't already open */ 224 dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev); 225 if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0) 226 return -EBUSY; 227 228 get_device(&dev_data->dev); 229 230 sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL); 231 if (!sess_data) { 232 atomic_set(&dev_data->available, 1); 233 return -ENOMEM; 234 } 235 sess_data->dev_data = dev_data; 236 sess_data->has_msg = false; 237 238 nonseekable_open(inode, filp); 239 filp->private_data = sess_data; 240 241 return 0; 242 } 243 244 static ssize_t telem_write(struct file *filp, const char __user *buf, 245 size_t count, loff_t *pos) 246 { 247 struct telem_session_data *sess_data = filp->private_data; 248 struct wilco_ec_message msg = {}; 249 int ret; 250 251 if (count > sizeof(sess_data->request)) 252 return -EMSGSIZE; 253 if (copy_from_user(&sess_data->request, buf, count)) 254 return -EFAULT; 255 ret = check_telem_request(&sess_data->request, count); 256 if (ret < 0) 257 return ret; 258 259 memset(sess_data->response, 0, sizeof(sess_data->response)); 260 msg.type = WILCO_EC_MSG_TELEMETRY; 261 msg.request_data = &sess_data->request; 262 msg.request_size = sizeof(sess_data->request); 263 msg.response_data = sess_data->response; 264 msg.response_size = sizeof(sess_data->response); 265 266 ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg); 267 if (ret < 0) 268 return ret; 269 if (ret != sizeof(sess_data->response)) 270 return -EMSGSIZE; 271 272 sess_data->has_msg = true; 273 274 return count; 275 } 276 277 static ssize_t telem_read(struct file *filp, char __user *buf, size_t count, 278 loff_t *pos) 279 { 280 struct telem_session_data *sess_data = filp->private_data; 281 282 if (!sess_data->has_msg) 283 return -ENODATA; 284 if (count > sizeof(sess_data->response)) 285 return -EINVAL; 286 287 if (copy_to_user(buf, sess_data->response, count)) 288 return -EFAULT; 289 290 sess_data->has_msg = false; 291 292 return count; 293 } 294 295 static int telem_release(struct inode *inode, struct file *filp) 296 { 297 struct telem_session_data *sess_data = filp->private_data; 298 299 atomic_set(&sess_data->dev_data->available, 1); 300 put_device(&sess_data->dev_data->dev); 301 kfree(sess_data); 302 303 return 0; 304 } 305 306 static const struct file_operations telem_fops = { 307 .open = telem_open, 308 .write = telem_write, 309 .read = telem_read, 310 .release = telem_release, 311 .llseek = no_llseek, 312 .owner = THIS_MODULE, 313 }; 314 315 /** 316 * telem_device_free() - Callback to free the telem_device_data structure. 317 * @d: The device embedded in our device data, which we have been ref counting. 318 * 319 * Once all open file descriptors are closed and the device has been removed, 320 * the refcount of the device will fall to 0 and this will be called. 321 */ 322 static void telem_device_free(struct device *d) 323 { 324 struct telem_device_data *dev_data; 325 326 dev_data = container_of(d, struct telem_device_data, dev); 327 kfree(dev_data); 328 } 329 330 /** 331 * telem_device_probe() - Callback when creating a new device. 332 * @pdev: platform device that we will be receiving telems from. 333 * 334 * This finds a free minor number for the device, allocates and initializes 335 * some device data, and creates a new device and char dev node. 336 * 337 * Return: 0 on success, negative error code on failure. 338 */ 339 static int telem_device_probe(struct platform_device *pdev) 340 { 341 struct telem_device_data *dev_data; 342 int error, minor; 343 344 /* Get the next available device number */ 345 minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL); 346 if (minor < 0) { 347 error = minor; 348 dev_err(&pdev->dev, "Failed to find minor number: %d", error); 349 return error; 350 } 351 352 dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); 353 if (!dev_data) { 354 ida_simple_remove(&telem_ida, minor); 355 return -ENOMEM; 356 } 357 358 /* Initialize the device data */ 359 dev_data->ec = dev_get_platdata(&pdev->dev); 360 atomic_set(&dev_data->available, 1); 361 platform_set_drvdata(pdev, dev_data); 362 363 /* Initialize the device */ 364 dev_data->dev.devt = MKDEV(telem_major, minor); 365 dev_data->dev.class = &telem_class; 366 dev_data->dev.release = telem_device_free; 367 dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor); 368 device_initialize(&dev_data->dev); 369 370 /* Initialize the character device and add it to userspace */; 371 cdev_init(&dev_data->cdev, &telem_fops); 372 error = cdev_device_add(&dev_data->cdev, &dev_data->dev); 373 if (error) { 374 put_device(&dev_data->dev); 375 ida_simple_remove(&telem_ida, minor); 376 return error; 377 } 378 379 return 0; 380 } 381 382 static int telem_device_remove(struct platform_device *pdev) 383 { 384 struct telem_device_data *dev_data = platform_get_drvdata(pdev); 385 386 cdev_device_del(&dev_data->cdev, &dev_data->dev); 387 put_device(&dev_data->dev); 388 ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); 389 390 return 0; 391 } 392 393 static struct platform_driver telem_driver = { 394 .probe = telem_device_probe, 395 .remove = telem_device_remove, 396 .driver = { 397 .name = DRV_NAME, 398 }, 399 }; 400 401 static int __init telem_module_init(void) 402 { 403 dev_t dev_num = 0; 404 int ret; 405 406 ret = class_register(&telem_class); 407 if (ret) { 408 pr_err(DRV_NAME ": Failed registering class: %d", ret); 409 return ret; 410 } 411 412 /* Request the kernel for device numbers, starting with minor=0 */ 413 ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME); 414 if (ret) { 415 pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret); 416 goto destroy_class; 417 } 418 telem_major = MAJOR(dev_num); 419 420 ret = platform_driver_register(&telem_driver); 421 if (ret < 0) { 422 pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); 423 goto unregister_region; 424 } 425 426 return 0; 427 428 unregister_region: 429 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 430 destroy_class: 431 class_unregister(&telem_class); 432 ida_destroy(&telem_ida); 433 return ret; 434 } 435 436 static void __exit telem_module_exit(void) 437 { 438 platform_driver_unregister(&telem_driver); 439 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 440 class_unregister(&telem_class); 441 ida_destroy(&telem_ida); 442 } 443 444 module_init(telem_module_init); 445 module_exit(telem_module_exit); 446 447 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 448 MODULE_DESCRIPTION("Wilco EC telemetry driver"); 449 MODULE_LICENSE("GPL"); 450 MODULE_ALIAS("platform:" DRV_NAME); 451