Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Telemetry communication for Wilco EC
0004  *
0005  * Copyright 2019 Google LLC
0006  *
0007  * The Wilco Embedded Controller is able to send telemetry data
0008  * which is useful for enterprise applications. A daemon running on
0009  * the OS sends a command to the EC via a write() to a char device,
0010  * and can read the response with a read(). The write() request is
0011  * verified by the driver to ensure that it is performing only one
0012  * of the allowlisted commands, and that no extraneous data is
0013  * being transmitted to the EC. The response is passed directly
0014  * back to the reader with no modification.
0015  *
0016  * The character device will appear as /dev/wilco_telemN, where N
0017  * is some small non-negative integer, starting with 0. Only one
0018  * process may have the file descriptor open at a time. The calling
0019  * userspace program needs to keep the device file descriptor open
0020  * between the calls to write() and read() in order to preserve the
0021  * response. Up to 32 bytes will be available for reading.
0022  *
0023  * For testing purposes, try requesting the EC's firmware build
0024  * date, by sending the WILCO_EC_TELEM_GET_VERSION command with
0025  * argument index=3. i.e. write [0x38, 0x00, 0x03]
0026  * to the device node. An ASCII string of the build date is
0027  * returned.
0028  */
0029 
0030 #include <linux/cdev.h>
0031 #include <linux/device.h>
0032 #include <linux/fs.h>
0033 #include <linux/module.h>
0034 #include <linux/platform_data/wilco-ec.h>
0035 #include <linux/platform_device.h>
0036 #include <linux/slab.h>
0037 #include <linux/types.h>
0038 #include <linux/uaccess.h>
0039 
0040 #define TELEM_DEV_NAME      "wilco_telem"
0041 #define TELEM_CLASS_NAME    TELEM_DEV_NAME
0042 #define DRV_NAME        TELEM_DEV_NAME
0043 #define TELEM_DEV_NAME_FMT  (TELEM_DEV_NAME "%d")
0044 static struct class telem_class = {
0045     .owner  = THIS_MODULE,
0046     .name   = TELEM_CLASS_NAME,
0047 };
0048 
0049 /* Keep track of all the device numbers used. */
0050 #define TELEM_MAX_DEV 128
0051 static int telem_major;
0052 static DEFINE_IDA(telem_ida);
0053 
0054 /* EC telemetry command codes */
0055 #define WILCO_EC_TELEM_GET_LOG          0x99
0056 #define WILCO_EC_TELEM_GET_VERSION      0x38
0057 #define WILCO_EC_TELEM_GET_FAN_INFO     0x2E
0058 #define WILCO_EC_TELEM_GET_DIAG_INFO        0xFA
0059 #define WILCO_EC_TELEM_GET_TEMP_INFO        0x95
0060 #define WILCO_EC_TELEM_GET_TEMP_READ        0x2C
0061 #define WILCO_EC_TELEM_GET_BATT_EXT_INFO    0x07
0062 #define WILCO_EC_TELEM_GET_BATT_PPID_INFO   0x8A
0063 
0064 #define TELEM_ARGS_SIZE_MAX 30
0065 
0066 /*
0067  * The following telem_args_get_* structs are embedded within the |args| field
0068  * of wilco_ec_telem_request.
0069  */
0070 
0071 struct telem_args_get_log {
0072     u8 log_type;
0073     u8 log_index;
0074 } __packed;
0075 
0076 /*
0077  * Get a piece of info about the EC firmware version:
0078  * 0 = label
0079  * 1 = svn_rev
0080  * 2 = model_no
0081  * 3 = build_date
0082  * 4 = frio_version
0083  */
0084 struct telem_args_get_version {
0085     u8 index;
0086 } __packed;
0087 
0088 struct telem_args_get_fan_info {
0089     u8 command;
0090     u8 fan_number;
0091     u8 arg;
0092 } __packed;
0093 
0094 struct telem_args_get_diag_info {
0095     u8 type;
0096     u8 sub_type;
0097 } __packed;
0098 
0099 struct telem_args_get_temp_info {
0100     u8 command;
0101     u8 index;
0102     u8 field;
0103     u8 zone;
0104 } __packed;
0105 
0106 struct telem_args_get_temp_read {
0107     u8 sensor_index;
0108 } __packed;
0109 
0110 struct telem_args_get_batt_ext_info {
0111     u8 var_args[5];
0112 } __packed;
0113 
0114 struct telem_args_get_batt_ppid_info {
0115     u8 always1; /* Should always be 1 */
0116 } __packed;
0117 
0118 /**
0119  * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
0120  * @command: One of WILCO_EC_TELEM_GET_* command codes.
0121  * @reserved: Must be 0.
0122  * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
0123  */
0124 struct wilco_ec_telem_request {
0125     u8 command;
0126     u8 reserved;
0127     union {
0128         u8 buf[TELEM_ARGS_SIZE_MAX];
0129         struct telem_args_get_log       get_log;
0130         struct telem_args_get_version       get_version;
0131         struct telem_args_get_fan_info      get_fan_info;
0132         struct telem_args_get_diag_info     get_diag_info;
0133         struct telem_args_get_temp_info     get_temp_info;
0134         struct telem_args_get_temp_read     get_temp_read;
0135         struct telem_args_get_batt_ext_info get_batt_ext_info;
0136         struct telem_args_get_batt_ppid_info    get_batt_ppid_info;
0137     } args;
0138 } __packed;
0139 
0140 /**
0141  * check_telem_request() - Ensure that a request from userspace is valid.
0142  * @rq: Request buffer copied from userspace.
0143  * @size: Number of bytes copied from userspace.
0144  *
0145  * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
0146  *         -EMSGSIZE if the request is too long.
0147  *
0148  * We do not want to allow userspace to send arbitrary telemetry commands to
0149  * the EC. Therefore we check to ensure that
0150  * 1. The request follows the format of struct wilco_ec_telem_request.
0151  * 2. The supplied command code is one of the allowlisted commands.
0152  * 3. The request only contains the necessary data for the header and arguments.
0153  */
0154 static int check_telem_request(struct wilco_ec_telem_request *rq,
0155                    size_t size)
0156 {
0157     size_t max_size = offsetof(struct wilco_ec_telem_request, args);
0158 
0159     if (rq->reserved)
0160         return -EINVAL;
0161 
0162     switch (rq->command) {
0163     case WILCO_EC_TELEM_GET_LOG:
0164         max_size += sizeof(rq->args.get_log);
0165         break;
0166     case WILCO_EC_TELEM_GET_VERSION:
0167         max_size += sizeof(rq->args.get_version);
0168         break;
0169     case WILCO_EC_TELEM_GET_FAN_INFO:
0170         max_size += sizeof(rq->args.get_fan_info);
0171         break;
0172     case WILCO_EC_TELEM_GET_DIAG_INFO:
0173         max_size += sizeof(rq->args.get_diag_info);
0174         break;
0175     case WILCO_EC_TELEM_GET_TEMP_INFO:
0176         max_size += sizeof(rq->args.get_temp_info);
0177         break;
0178     case WILCO_EC_TELEM_GET_TEMP_READ:
0179         max_size += sizeof(rq->args.get_temp_read);
0180         break;
0181     case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
0182         max_size += sizeof(rq->args.get_batt_ext_info);
0183         break;
0184     case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
0185         if (rq->args.get_batt_ppid_info.always1 != 1)
0186             return -EINVAL;
0187 
0188         max_size += sizeof(rq->args.get_batt_ppid_info);
0189         break;
0190     default:
0191         return -EINVAL;
0192     }
0193 
0194     return (size <= max_size) ? 0 : -EMSGSIZE;
0195 }
0196 
0197 /**
0198  * struct telem_device_data - Data for a Wilco EC device that queries telemetry.
0199  * @cdev: Char dev that userspace reads and polls from.
0200  * @dev: Device associated with the %cdev.
0201  * @ec: Wilco EC that we will be communicating with using the mailbox interface.
0202  * @available: Boolean of if the device can be opened.
0203  */
0204 struct telem_device_data {
0205     struct device dev;
0206     struct cdev cdev;
0207     struct wilco_ec_device *ec;
0208     atomic_t available;
0209 };
0210 
0211 #define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
0212 
0213 /**
0214  * struct telem_session_data - Data that exists between open() and release().
0215  * @dev_data: Pointer to get back to the device data and EC.
0216  * @request: Command and arguments sent to EC.
0217  * @response: Response buffer of data from EC.
0218  * @has_msg: Is there data available to read from a previous write?
0219  */
0220 struct telem_session_data {
0221     struct telem_device_data *dev_data;
0222     struct wilco_ec_telem_request request;
0223     u8 response[TELEM_RESPONSE_SIZE];
0224     bool has_msg;
0225 };
0226 
0227 /**
0228  * telem_open() - Callback for when the device node is opened.
0229  * @inode: inode for this char device node.
0230  * @filp: file for this char device node.
0231  *
0232  * We need to ensure that after writing a command to the device,
0233  * the same userspace process reads the corresponding result.
0234  * Therefore, we increment a refcount on opening the device, so that
0235  * only one process can communicate with the EC at a time.
0236  *
0237  * Return: 0 on success, or negative error code on failure.
0238  */
0239 static int telem_open(struct inode *inode, struct file *filp)
0240 {
0241     struct telem_device_data *dev_data;
0242     struct telem_session_data *sess_data;
0243 
0244     /* Ensure device isn't already open */
0245     dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
0246     if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
0247         return -EBUSY;
0248 
0249     get_device(&dev_data->dev);
0250 
0251     sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
0252     if (!sess_data) {
0253         atomic_set(&dev_data->available, 1);
0254         return -ENOMEM;
0255     }
0256     sess_data->dev_data = dev_data;
0257     sess_data->has_msg = false;
0258 
0259     stream_open(inode, filp);
0260     filp->private_data = sess_data;
0261 
0262     return 0;
0263 }
0264 
0265 static ssize_t telem_write(struct file *filp, const char __user *buf,
0266                size_t count, loff_t *pos)
0267 {
0268     struct telem_session_data *sess_data = filp->private_data;
0269     struct wilco_ec_message msg = {};
0270     int ret;
0271 
0272     if (count > sizeof(sess_data->request))
0273         return -EMSGSIZE;
0274     memset(&sess_data->request, 0, sizeof(sess_data->request));
0275     if (copy_from_user(&sess_data->request, buf, count))
0276         return -EFAULT;
0277     ret = check_telem_request(&sess_data->request, count);
0278     if (ret < 0)
0279         return ret;
0280 
0281     memset(sess_data->response, 0, sizeof(sess_data->response));
0282     msg.type = WILCO_EC_MSG_TELEMETRY;
0283     msg.request_data = &sess_data->request;
0284     msg.request_size = sizeof(sess_data->request);
0285     msg.response_data = sess_data->response;
0286     msg.response_size = sizeof(sess_data->response);
0287 
0288     ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
0289     if (ret < 0)
0290         return ret;
0291     if (ret != sizeof(sess_data->response))
0292         return -EMSGSIZE;
0293 
0294     sess_data->has_msg = true;
0295 
0296     return count;
0297 }
0298 
0299 static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
0300               loff_t *pos)
0301 {
0302     struct telem_session_data *sess_data = filp->private_data;
0303 
0304     if (!sess_data->has_msg)
0305         return -ENODATA;
0306     if (count > sizeof(sess_data->response))
0307         return -EINVAL;
0308 
0309     if (copy_to_user(buf, sess_data->response, count))
0310         return -EFAULT;
0311 
0312     sess_data->has_msg = false;
0313 
0314     return count;
0315 }
0316 
0317 static int telem_release(struct inode *inode, struct file *filp)
0318 {
0319     struct telem_session_data *sess_data = filp->private_data;
0320 
0321     atomic_set(&sess_data->dev_data->available, 1);
0322     put_device(&sess_data->dev_data->dev);
0323     kfree(sess_data);
0324 
0325     return 0;
0326 }
0327 
0328 static const struct file_operations telem_fops = {
0329     .open = telem_open,
0330     .write = telem_write,
0331     .read = telem_read,
0332     .release = telem_release,
0333     .llseek = no_llseek,
0334     .owner = THIS_MODULE,
0335 };
0336 
0337 /**
0338  * telem_device_free() - Callback to free the telem_device_data structure.
0339  * @d: The device embedded in our device data, which we have been ref counting.
0340  *
0341  * Once all open file descriptors are closed and the device has been removed,
0342  * the refcount of the device will fall to 0 and this will be called.
0343  */
0344 static void telem_device_free(struct device *d)
0345 {
0346     struct telem_device_data *dev_data;
0347 
0348     dev_data = container_of(d, struct telem_device_data, dev);
0349     kfree(dev_data);
0350 }
0351 
0352 /**
0353  * telem_device_probe() - Callback when creating a new device.
0354  * @pdev: platform device that we will be receiving telems from.
0355  *
0356  * This finds a free minor number for the device, allocates and initializes
0357  * some device data, and creates a new device and char dev node.
0358  *
0359  * Return: 0 on success, negative error code on failure.
0360  */
0361 static int telem_device_probe(struct platform_device *pdev)
0362 {
0363     struct telem_device_data *dev_data;
0364     int error, minor;
0365 
0366     /* Get the next available device number */
0367     minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
0368     if (minor < 0) {
0369         error = minor;
0370         dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
0371         return error;
0372     }
0373 
0374     dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
0375     if (!dev_data) {
0376         ida_simple_remove(&telem_ida, minor);
0377         return -ENOMEM;
0378     }
0379 
0380     /* Initialize the device data */
0381     dev_data->ec = dev_get_platdata(&pdev->dev);
0382     atomic_set(&dev_data->available, 1);
0383     platform_set_drvdata(pdev, dev_data);
0384 
0385     /* Initialize the device */
0386     dev_data->dev.devt = MKDEV(telem_major, minor);
0387     dev_data->dev.class = &telem_class;
0388     dev_data->dev.release = telem_device_free;
0389     dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
0390     device_initialize(&dev_data->dev);
0391 
0392     /* Initialize the character device and add it to userspace */;
0393     cdev_init(&dev_data->cdev, &telem_fops);
0394     error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
0395     if (error) {
0396         put_device(&dev_data->dev);
0397         ida_simple_remove(&telem_ida, minor);
0398         return error;
0399     }
0400 
0401     return 0;
0402 }
0403 
0404 static int telem_device_remove(struct platform_device *pdev)
0405 {
0406     struct telem_device_data *dev_data = platform_get_drvdata(pdev);
0407 
0408     cdev_device_del(&dev_data->cdev, &dev_data->dev);
0409     ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
0410     put_device(&dev_data->dev);
0411 
0412     return 0;
0413 }
0414 
0415 static struct platform_driver telem_driver = {
0416     .probe = telem_device_probe,
0417     .remove = telem_device_remove,
0418     .driver = {
0419         .name = DRV_NAME,
0420     },
0421 };
0422 
0423 static int __init telem_module_init(void)
0424 {
0425     dev_t dev_num = 0;
0426     int ret;
0427 
0428     ret = class_register(&telem_class);
0429     if (ret) {
0430         pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
0431         return ret;
0432     }
0433 
0434     /* Request the kernel for device numbers, starting with minor=0 */
0435     ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
0436     if (ret) {
0437         pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
0438         goto destroy_class;
0439     }
0440     telem_major = MAJOR(dev_num);
0441 
0442     ret = platform_driver_register(&telem_driver);
0443     if (ret < 0) {
0444         pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
0445         goto unregister_region;
0446     }
0447 
0448     return 0;
0449 
0450 unregister_region:
0451     unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
0452 destroy_class:
0453     class_unregister(&telem_class);
0454     ida_destroy(&telem_ida);
0455     return ret;
0456 }
0457 
0458 static void __exit telem_module_exit(void)
0459 {
0460     platform_driver_unregister(&telem_driver);
0461     unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
0462     class_unregister(&telem_class);
0463     ida_destroy(&telem_ida);
0464 }
0465 
0466 module_init(telem_module_init);
0467 module_exit(telem_module_exit);
0468 
0469 MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
0470 MODULE_DESCRIPTION("Wilco EC telemetry driver");
0471 MODULE_LICENSE("GPL");
0472 MODULE_ALIAS("platform:" DRV_NAME);