18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Telemetry communication for Wilco EC 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2019 Google LLC 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * The Wilco Embedded Controller is able to send telemetry data 88c2ecf20Sopenharmony_ci * which is useful for enterprise applications. A daemon running on 98c2ecf20Sopenharmony_ci * the OS sends a command to the EC via a write() to a char device, 108c2ecf20Sopenharmony_ci * and can read the response with a read(). The write() request is 118c2ecf20Sopenharmony_ci * verified by the driver to ensure that it is performing only one 128c2ecf20Sopenharmony_ci * of the allowlisted commands, and that no extraneous data is 138c2ecf20Sopenharmony_ci * being transmitted to the EC. The response is passed directly 148c2ecf20Sopenharmony_ci * back to the reader with no modification. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * The character device will appear as /dev/wilco_telemN, where N 178c2ecf20Sopenharmony_ci * is some small non-negative integer, starting with 0. Only one 188c2ecf20Sopenharmony_ci * process may have the file descriptor open at a time. The calling 198c2ecf20Sopenharmony_ci * userspace program needs to keep the device file descriptor open 208c2ecf20Sopenharmony_ci * between the calls to write() and read() in order to preserve the 218c2ecf20Sopenharmony_ci * response. Up to 32 bytes will be available for reading. 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * For testing purposes, try requesting the EC's firmware build 248c2ecf20Sopenharmony_ci * date, by sending the WILCO_EC_TELEM_GET_VERSION command with 258c2ecf20Sopenharmony_ci * argument index=3. i.e. write [0x38, 0x00, 0x03] 268c2ecf20Sopenharmony_ci * to the device node. An ASCII string of the build date is 278c2ecf20Sopenharmony_ci * returned. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <linux/cdev.h> 318c2ecf20Sopenharmony_ci#include <linux/device.h> 328c2ecf20Sopenharmony_ci#include <linux/fs.h> 338c2ecf20Sopenharmony_ci#include <linux/module.h> 348c2ecf20Sopenharmony_ci#include <linux/platform_data/wilco-ec.h> 358c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 368c2ecf20Sopenharmony_ci#include <linux/slab.h> 378c2ecf20Sopenharmony_ci#include <linux/types.h> 388c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define TELEM_DEV_NAME "wilco_telem" 418c2ecf20Sopenharmony_ci#define TELEM_CLASS_NAME TELEM_DEV_NAME 428c2ecf20Sopenharmony_ci#define DRV_NAME TELEM_DEV_NAME 438c2ecf20Sopenharmony_ci#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d") 448c2ecf20Sopenharmony_cistatic struct class telem_class = { 458c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 468c2ecf20Sopenharmony_ci .name = TELEM_CLASS_NAME, 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* Keep track of all the device numbers used. */ 508c2ecf20Sopenharmony_ci#define TELEM_MAX_DEV 128 518c2ecf20Sopenharmony_cistatic int telem_major; 528c2ecf20Sopenharmony_cistatic DEFINE_IDA(telem_ida); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* EC telemetry command codes */ 558c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_LOG 0x99 568c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_VERSION 0x38 578c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E 588c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA 598c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95 608c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C 618c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07 628c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci#define TELEM_ARGS_SIZE_MAX 30 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci/* 678c2ecf20Sopenharmony_ci * The following telem_args_get_* structs are embedded within the |args| field 688c2ecf20Sopenharmony_ci * of wilco_ec_telem_request. 698c2ecf20Sopenharmony_ci */ 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistruct telem_args_get_log { 728c2ecf20Sopenharmony_ci u8 log_type; 738c2ecf20Sopenharmony_ci u8 log_index; 748c2ecf20Sopenharmony_ci} __packed; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* 778c2ecf20Sopenharmony_ci * Get a piece of info about the EC firmware version: 788c2ecf20Sopenharmony_ci * 0 = label 798c2ecf20Sopenharmony_ci * 1 = svn_rev 808c2ecf20Sopenharmony_ci * 2 = model_no 818c2ecf20Sopenharmony_ci * 3 = build_date 828c2ecf20Sopenharmony_ci * 4 = frio_version 838c2ecf20Sopenharmony_ci */ 848c2ecf20Sopenharmony_cistruct telem_args_get_version { 858c2ecf20Sopenharmony_ci u8 index; 868c2ecf20Sopenharmony_ci} __packed; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistruct telem_args_get_fan_info { 898c2ecf20Sopenharmony_ci u8 command; 908c2ecf20Sopenharmony_ci u8 fan_number; 918c2ecf20Sopenharmony_ci u8 arg; 928c2ecf20Sopenharmony_ci} __packed; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistruct telem_args_get_diag_info { 958c2ecf20Sopenharmony_ci u8 type; 968c2ecf20Sopenharmony_ci u8 sub_type; 978c2ecf20Sopenharmony_ci} __packed; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistruct telem_args_get_temp_info { 1008c2ecf20Sopenharmony_ci u8 command; 1018c2ecf20Sopenharmony_ci u8 index; 1028c2ecf20Sopenharmony_ci u8 field; 1038c2ecf20Sopenharmony_ci u8 zone; 1048c2ecf20Sopenharmony_ci} __packed; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistruct telem_args_get_temp_read { 1078c2ecf20Sopenharmony_ci u8 sensor_index; 1088c2ecf20Sopenharmony_ci} __packed; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistruct telem_args_get_batt_ext_info { 1118c2ecf20Sopenharmony_ci u8 var_args[5]; 1128c2ecf20Sopenharmony_ci} __packed; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistruct telem_args_get_batt_ppid_info { 1158c2ecf20Sopenharmony_ci u8 always1; /* Should always be 1 */ 1168c2ecf20Sopenharmony_ci} __packed; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci/** 1198c2ecf20Sopenharmony_ci * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC. 1208c2ecf20Sopenharmony_ci * @command: One of WILCO_EC_TELEM_GET_* command codes. 1218c2ecf20Sopenharmony_ci * @reserved: Must be 0. 1228c2ecf20Sopenharmony_ci * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0. 1238c2ecf20Sopenharmony_ci */ 1248c2ecf20Sopenharmony_cistruct wilco_ec_telem_request { 1258c2ecf20Sopenharmony_ci u8 command; 1268c2ecf20Sopenharmony_ci u8 reserved; 1278c2ecf20Sopenharmony_ci union { 1288c2ecf20Sopenharmony_ci u8 buf[TELEM_ARGS_SIZE_MAX]; 1298c2ecf20Sopenharmony_ci struct telem_args_get_log get_log; 1308c2ecf20Sopenharmony_ci struct telem_args_get_version get_version; 1318c2ecf20Sopenharmony_ci struct telem_args_get_fan_info get_fan_info; 1328c2ecf20Sopenharmony_ci struct telem_args_get_diag_info get_diag_info; 1338c2ecf20Sopenharmony_ci struct telem_args_get_temp_info get_temp_info; 1348c2ecf20Sopenharmony_ci struct telem_args_get_temp_read get_temp_read; 1358c2ecf20Sopenharmony_ci struct telem_args_get_batt_ext_info get_batt_ext_info; 1368c2ecf20Sopenharmony_ci struct telem_args_get_batt_ppid_info get_batt_ppid_info; 1378c2ecf20Sopenharmony_ci } args; 1388c2ecf20Sopenharmony_ci} __packed; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci/** 1418c2ecf20Sopenharmony_ci * check_telem_request() - Ensure that a request from userspace is valid. 1428c2ecf20Sopenharmony_ci * @rq: Request buffer copied from userspace. 1438c2ecf20Sopenharmony_ci * @size: Number of bytes copied from userspace. 1448c2ecf20Sopenharmony_ci * 1458c2ecf20Sopenharmony_ci * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero, 1468c2ecf20Sopenharmony_ci * -EMSGSIZE if the request is too long. 1478c2ecf20Sopenharmony_ci * 1488c2ecf20Sopenharmony_ci * We do not want to allow userspace to send arbitrary telemetry commands to 1498c2ecf20Sopenharmony_ci * the EC. Therefore we check to ensure that 1508c2ecf20Sopenharmony_ci * 1. The request follows the format of struct wilco_ec_telem_request. 1518c2ecf20Sopenharmony_ci * 2. The supplied command code is one of the allowlisted commands. 1528c2ecf20Sopenharmony_ci * 3. The request only contains the necessary data for the header and arguments. 1538c2ecf20Sopenharmony_ci */ 1548c2ecf20Sopenharmony_cistatic int check_telem_request(struct wilco_ec_telem_request *rq, 1558c2ecf20Sopenharmony_ci size_t size) 1568c2ecf20Sopenharmony_ci{ 1578c2ecf20Sopenharmony_ci size_t max_size = offsetof(struct wilco_ec_telem_request, args); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (rq->reserved) 1608c2ecf20Sopenharmony_ci return -EINVAL; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci switch (rq->command) { 1638c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_LOG: 1648c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_log); 1658c2ecf20Sopenharmony_ci break; 1668c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_VERSION: 1678c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_version); 1688c2ecf20Sopenharmony_ci break; 1698c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_FAN_INFO: 1708c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_fan_info); 1718c2ecf20Sopenharmony_ci break; 1728c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_DIAG_INFO: 1738c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_diag_info); 1748c2ecf20Sopenharmony_ci break; 1758c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_TEMP_INFO: 1768c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_temp_info); 1778c2ecf20Sopenharmony_ci break; 1788c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_TEMP_READ: 1798c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_temp_read); 1808c2ecf20Sopenharmony_ci break; 1818c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_BATT_EXT_INFO: 1828c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_batt_ext_info); 1838c2ecf20Sopenharmony_ci break; 1848c2ecf20Sopenharmony_ci case WILCO_EC_TELEM_GET_BATT_PPID_INFO: 1858c2ecf20Sopenharmony_ci if (rq->args.get_batt_ppid_info.always1 != 1) 1868c2ecf20Sopenharmony_ci return -EINVAL; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci max_size += sizeof(rq->args.get_batt_ppid_info); 1898c2ecf20Sopenharmony_ci break; 1908c2ecf20Sopenharmony_ci default: 1918c2ecf20Sopenharmony_ci return -EINVAL; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci return (size <= max_size) ? 0 : -EMSGSIZE; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci/** 1988c2ecf20Sopenharmony_ci * struct telem_device_data - Data for a Wilco EC device that queries telemetry. 1998c2ecf20Sopenharmony_ci * @cdev: Char dev that userspace reads and polls from. 2008c2ecf20Sopenharmony_ci * @dev: Device associated with the %cdev. 2018c2ecf20Sopenharmony_ci * @ec: Wilco EC that we will be communicating with using the mailbox interface. 2028c2ecf20Sopenharmony_ci * @available: Boolean of if the device can be opened. 2038c2ecf20Sopenharmony_ci */ 2048c2ecf20Sopenharmony_cistruct telem_device_data { 2058c2ecf20Sopenharmony_ci struct device dev; 2068c2ecf20Sopenharmony_ci struct cdev cdev; 2078c2ecf20Sopenharmony_ci struct wilco_ec_device *ec; 2088c2ecf20Sopenharmony_ci atomic_t available; 2098c2ecf20Sopenharmony_ci}; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci/** 2148c2ecf20Sopenharmony_ci * struct telem_session_data - Data that exists between open() and release(). 2158c2ecf20Sopenharmony_ci * @dev_data: Pointer to get back to the device data and EC. 2168c2ecf20Sopenharmony_ci * @request: Command and arguments sent to EC. 2178c2ecf20Sopenharmony_ci * @response: Response buffer of data from EC. 2188c2ecf20Sopenharmony_ci * @has_msg: Is there data available to read from a previous write? 2198c2ecf20Sopenharmony_ci */ 2208c2ecf20Sopenharmony_cistruct telem_session_data { 2218c2ecf20Sopenharmony_ci struct telem_device_data *dev_data; 2228c2ecf20Sopenharmony_ci struct wilco_ec_telem_request request; 2238c2ecf20Sopenharmony_ci u8 response[TELEM_RESPONSE_SIZE]; 2248c2ecf20Sopenharmony_ci bool has_msg; 2258c2ecf20Sopenharmony_ci}; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci/** 2288c2ecf20Sopenharmony_ci * telem_open() - Callback for when the device node is opened. 2298c2ecf20Sopenharmony_ci * @inode: inode for this char device node. 2308c2ecf20Sopenharmony_ci * @filp: file for this char device node. 2318c2ecf20Sopenharmony_ci * 2328c2ecf20Sopenharmony_ci * We need to ensure that after writing a command to the device, 2338c2ecf20Sopenharmony_ci * the same userspace process reads the corresponding result. 2348c2ecf20Sopenharmony_ci * Therefore, we increment a refcount on opening the device, so that 2358c2ecf20Sopenharmony_ci * only one process can communicate with the EC at a time. 2368c2ecf20Sopenharmony_ci * 2378c2ecf20Sopenharmony_ci * Return: 0 on success, or negative error code on failure. 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_cistatic int telem_open(struct inode *inode, struct file *filp) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct telem_device_data *dev_data; 2428c2ecf20Sopenharmony_ci struct telem_session_data *sess_data; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci /* Ensure device isn't already open */ 2458c2ecf20Sopenharmony_ci dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev); 2468c2ecf20Sopenharmony_ci if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0) 2478c2ecf20Sopenharmony_ci return -EBUSY; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci get_device(&dev_data->dev); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL); 2528c2ecf20Sopenharmony_ci if (!sess_data) { 2538c2ecf20Sopenharmony_ci atomic_set(&dev_data->available, 1); 2548c2ecf20Sopenharmony_ci return -ENOMEM; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci sess_data->dev_data = dev_data; 2578c2ecf20Sopenharmony_ci sess_data->has_msg = false; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci nonseekable_open(inode, filp); 2608c2ecf20Sopenharmony_ci filp->private_data = sess_data; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci return 0; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic ssize_t telem_write(struct file *filp, const char __user *buf, 2668c2ecf20Sopenharmony_ci size_t count, loff_t *pos) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct telem_session_data *sess_data = filp->private_data; 2698c2ecf20Sopenharmony_ci struct wilco_ec_message msg = {}; 2708c2ecf20Sopenharmony_ci int ret; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci if (count > sizeof(sess_data->request)) 2738c2ecf20Sopenharmony_ci return -EMSGSIZE; 2748c2ecf20Sopenharmony_ci memset(&sess_data->request, 0, sizeof(sess_data->request)); 2758c2ecf20Sopenharmony_ci if (copy_from_user(&sess_data->request, buf, count)) 2768c2ecf20Sopenharmony_ci return -EFAULT; 2778c2ecf20Sopenharmony_ci ret = check_telem_request(&sess_data->request, count); 2788c2ecf20Sopenharmony_ci if (ret < 0) 2798c2ecf20Sopenharmony_ci return ret; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci memset(sess_data->response, 0, sizeof(sess_data->response)); 2828c2ecf20Sopenharmony_ci msg.type = WILCO_EC_MSG_TELEMETRY; 2838c2ecf20Sopenharmony_ci msg.request_data = &sess_data->request; 2848c2ecf20Sopenharmony_ci msg.request_size = sizeof(sess_data->request); 2858c2ecf20Sopenharmony_ci msg.response_data = sess_data->response; 2868c2ecf20Sopenharmony_ci msg.response_size = sizeof(sess_data->response); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg); 2898c2ecf20Sopenharmony_ci if (ret < 0) 2908c2ecf20Sopenharmony_ci return ret; 2918c2ecf20Sopenharmony_ci if (ret != sizeof(sess_data->response)) 2928c2ecf20Sopenharmony_ci return -EMSGSIZE; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci sess_data->has_msg = true; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci return count; 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cistatic ssize_t telem_read(struct file *filp, char __user *buf, size_t count, 3008c2ecf20Sopenharmony_ci loff_t *pos) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci struct telem_session_data *sess_data = filp->private_data; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci if (!sess_data->has_msg) 3058c2ecf20Sopenharmony_ci return -ENODATA; 3068c2ecf20Sopenharmony_ci if (count > sizeof(sess_data->response)) 3078c2ecf20Sopenharmony_ci return -EINVAL; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci if (copy_to_user(buf, sess_data->response, count)) 3108c2ecf20Sopenharmony_ci return -EFAULT; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci sess_data->has_msg = false; 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci return count; 3158c2ecf20Sopenharmony_ci} 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_cistatic int telem_release(struct inode *inode, struct file *filp) 3188c2ecf20Sopenharmony_ci{ 3198c2ecf20Sopenharmony_ci struct telem_session_data *sess_data = filp->private_data; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci atomic_set(&sess_data->dev_data->available, 1); 3228c2ecf20Sopenharmony_ci put_device(&sess_data->dev_data->dev); 3238c2ecf20Sopenharmony_ci kfree(sess_data); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci return 0; 3268c2ecf20Sopenharmony_ci} 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cistatic const struct file_operations telem_fops = { 3298c2ecf20Sopenharmony_ci .open = telem_open, 3308c2ecf20Sopenharmony_ci .write = telem_write, 3318c2ecf20Sopenharmony_ci .read = telem_read, 3328c2ecf20Sopenharmony_ci .release = telem_release, 3338c2ecf20Sopenharmony_ci .llseek = no_llseek, 3348c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3358c2ecf20Sopenharmony_ci}; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci/** 3388c2ecf20Sopenharmony_ci * telem_device_free() - Callback to free the telem_device_data structure. 3398c2ecf20Sopenharmony_ci * @d: The device embedded in our device data, which we have been ref counting. 3408c2ecf20Sopenharmony_ci * 3418c2ecf20Sopenharmony_ci * Once all open file descriptors are closed and the device has been removed, 3428c2ecf20Sopenharmony_ci * the refcount of the device will fall to 0 and this will be called. 3438c2ecf20Sopenharmony_ci */ 3448c2ecf20Sopenharmony_cistatic void telem_device_free(struct device *d) 3458c2ecf20Sopenharmony_ci{ 3468c2ecf20Sopenharmony_ci struct telem_device_data *dev_data; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci dev_data = container_of(d, struct telem_device_data, dev); 3498c2ecf20Sopenharmony_ci kfree(dev_data); 3508c2ecf20Sopenharmony_ci} 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci/** 3538c2ecf20Sopenharmony_ci * telem_device_probe() - Callback when creating a new device. 3548c2ecf20Sopenharmony_ci * @pdev: platform device that we will be receiving telems from. 3558c2ecf20Sopenharmony_ci * 3568c2ecf20Sopenharmony_ci * This finds a free minor number for the device, allocates and initializes 3578c2ecf20Sopenharmony_ci * some device data, and creates a new device and char dev node. 3588c2ecf20Sopenharmony_ci * 3598c2ecf20Sopenharmony_ci * Return: 0 on success, negative error code on failure. 3608c2ecf20Sopenharmony_ci */ 3618c2ecf20Sopenharmony_cistatic int telem_device_probe(struct platform_device *pdev) 3628c2ecf20Sopenharmony_ci{ 3638c2ecf20Sopenharmony_ci struct telem_device_data *dev_data; 3648c2ecf20Sopenharmony_ci int error, minor; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci /* Get the next available device number */ 3678c2ecf20Sopenharmony_ci minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL); 3688c2ecf20Sopenharmony_ci if (minor < 0) { 3698c2ecf20Sopenharmony_ci error = minor; 3708c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to find minor number: %d\n", error); 3718c2ecf20Sopenharmony_ci return error; 3728c2ecf20Sopenharmony_ci } 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); 3758c2ecf20Sopenharmony_ci if (!dev_data) { 3768c2ecf20Sopenharmony_ci ida_simple_remove(&telem_ida, minor); 3778c2ecf20Sopenharmony_ci return -ENOMEM; 3788c2ecf20Sopenharmony_ci } 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci /* Initialize the device data */ 3818c2ecf20Sopenharmony_ci dev_data->ec = dev_get_platdata(&pdev->dev); 3828c2ecf20Sopenharmony_ci atomic_set(&dev_data->available, 1); 3838c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, dev_data); 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci /* Initialize the device */ 3868c2ecf20Sopenharmony_ci dev_data->dev.devt = MKDEV(telem_major, minor); 3878c2ecf20Sopenharmony_ci dev_data->dev.class = &telem_class; 3888c2ecf20Sopenharmony_ci dev_data->dev.release = telem_device_free; 3898c2ecf20Sopenharmony_ci dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor); 3908c2ecf20Sopenharmony_ci device_initialize(&dev_data->dev); 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci /* Initialize the character device and add it to userspace */; 3938c2ecf20Sopenharmony_ci cdev_init(&dev_data->cdev, &telem_fops); 3948c2ecf20Sopenharmony_ci error = cdev_device_add(&dev_data->cdev, &dev_data->dev); 3958c2ecf20Sopenharmony_ci if (error) { 3968c2ecf20Sopenharmony_ci put_device(&dev_data->dev); 3978c2ecf20Sopenharmony_ci ida_simple_remove(&telem_ida, minor); 3988c2ecf20Sopenharmony_ci return error; 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci return 0; 4028c2ecf20Sopenharmony_ci} 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_cistatic int telem_device_remove(struct platform_device *pdev) 4058c2ecf20Sopenharmony_ci{ 4068c2ecf20Sopenharmony_ci struct telem_device_data *dev_data = platform_get_drvdata(pdev); 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci cdev_device_del(&dev_data->cdev, &dev_data->dev); 4098c2ecf20Sopenharmony_ci ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); 4108c2ecf20Sopenharmony_ci put_device(&dev_data->dev); 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci return 0; 4138c2ecf20Sopenharmony_ci} 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_cistatic struct platform_driver telem_driver = { 4168c2ecf20Sopenharmony_ci .probe = telem_device_probe, 4178c2ecf20Sopenharmony_ci .remove = telem_device_remove, 4188c2ecf20Sopenharmony_ci .driver = { 4198c2ecf20Sopenharmony_ci .name = DRV_NAME, 4208c2ecf20Sopenharmony_ci }, 4218c2ecf20Sopenharmony_ci}; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_cistatic int __init telem_module_init(void) 4248c2ecf20Sopenharmony_ci{ 4258c2ecf20Sopenharmony_ci dev_t dev_num = 0; 4268c2ecf20Sopenharmony_ci int ret; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci ret = class_register(&telem_class); 4298c2ecf20Sopenharmony_ci if (ret) { 4308c2ecf20Sopenharmony_ci pr_err(DRV_NAME ": Failed registering class: %d\n", ret); 4318c2ecf20Sopenharmony_ci return ret; 4328c2ecf20Sopenharmony_ci } 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci /* Request the kernel for device numbers, starting with minor=0 */ 4358c2ecf20Sopenharmony_ci ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME); 4368c2ecf20Sopenharmony_ci if (ret) { 4378c2ecf20Sopenharmony_ci pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret); 4388c2ecf20Sopenharmony_ci goto destroy_class; 4398c2ecf20Sopenharmony_ci } 4408c2ecf20Sopenharmony_ci telem_major = MAJOR(dev_num); 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci ret = platform_driver_register(&telem_driver); 4438c2ecf20Sopenharmony_ci if (ret < 0) { 4448c2ecf20Sopenharmony_ci pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); 4458c2ecf20Sopenharmony_ci goto unregister_region; 4468c2ecf20Sopenharmony_ci } 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci return 0; 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ciunregister_region: 4518c2ecf20Sopenharmony_ci unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 4528c2ecf20Sopenharmony_cidestroy_class: 4538c2ecf20Sopenharmony_ci class_unregister(&telem_class); 4548c2ecf20Sopenharmony_ci ida_destroy(&telem_ida); 4558c2ecf20Sopenharmony_ci return ret; 4568c2ecf20Sopenharmony_ci} 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_cistatic void __exit telem_module_exit(void) 4598c2ecf20Sopenharmony_ci{ 4608c2ecf20Sopenharmony_ci platform_driver_unregister(&telem_driver); 4618c2ecf20Sopenharmony_ci unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); 4628c2ecf20Sopenharmony_ci class_unregister(&telem_class); 4638c2ecf20Sopenharmony_ci ida_destroy(&telem_ida); 4648c2ecf20Sopenharmony_ci} 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_cimodule_init(telem_module_init); 4678c2ecf20Sopenharmony_cimodule_exit(telem_module_exit); 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); 4708c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Wilco EC telemetry driver"); 4718c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 4728c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 473