root/drivers/platform/chrome/wilco_ec/telemetry.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. check_telem_request
  2. telem_open
  3. telem_write
  4. telem_read
  5. telem_release
  6. telem_device_free
  7. telem_device_probe
  8. telem_device_remove
  9. telem_module_init
  10. telem_module_exit

   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         .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 #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 
  71 struct 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  */
  84 struct telem_args_get_version {
  85         u8 index;
  86 } __packed;
  87 
  88 struct telem_args_get_fan_info {
  89         u8 command;
  90         u8 fan_number;
  91         u8 arg;
  92 } __packed;
  93 
  94 struct telem_args_get_diag_info {
  95         u8 type;
  96         u8 sub_type;
  97 } __packed;
  98 
  99 struct telem_args_get_temp_info {
 100         u8 command;
 101         u8 index;
 102         u8 field;
 103         u8 zone;
 104 } __packed;
 105 
 106 struct telem_args_get_temp_read {
 107         u8 sensor_index;
 108 } __packed;
 109 
 110 struct telem_args_get_batt_ext_info {
 111         u8 var_args[5];
 112 } __packed;
 113 
 114 struct 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  */
 124 struct 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  */
 154 static 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  */
 204 struct 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  */
 220 struct 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  */
 239 static 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         nonseekable_open(inode, filp);
 260         filp->private_data = sess_data;
 261 
 262         return 0;
 263 }
 264 
 265 static 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 
 299 static 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 
 317 static 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 
 328 static 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  */
 344 static 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  */
 361 static 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", 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 
 404 static 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 
 415 static struct platform_driver telem_driver = {
 416         .probe = telem_device_probe,
 417         .remove = telem_device_remove,
 418         .driver = {
 419                 .name = DRV_NAME,
 420         },
 421 };
 422 
 423 static 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", 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", 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 
 450 unregister_region:
 451         unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
 452 destroy_class:
 453         class_unregister(&telem_class);
 454         ida_destroy(&telem_ida);
 455         return ret;
 456 }
 457 
 458 static 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 
 466 module_init(telem_module_init);
 467 module_exit(telem_module_exit);
 468 
 469 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
 470 MODULE_DESCRIPTION("Wilco EC telemetry driver");
 471 MODULE_LICENSE("GPL");
 472 MODULE_ALIAS("platform:" DRV_NAME);

/* [<][>][^][v][top][bottom][index][help] */