288 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * debugfs attributes for Wilco EC4 *5 * Copyright 2019 Google LLC6 *7 * See Documentation/ABI/testing/debugfs-wilco-ec for usage.8 */9 10#include <linux/ctype.h>11#include <linux/debugfs.h>12#include <linux/fs.h>13#include <linux/mod_devicetable.h>14#include <linux/module.h>15#include <linux/platform_data/wilco-ec.h>16#include <linux/platform_device.h>17 18#define DRV_NAME "wilco-ec-debugfs"19 20/* The raw bytes will take up more space when represented as a hex string */21#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4)22 23struct wilco_ec_debugfs {24 struct wilco_ec_device *ec;25 struct dentry *dir;26 size_t response_size;27 u8 raw_data[EC_MAILBOX_DATA_SIZE];28 u8 formatted_data[FORMATTED_BUFFER_SIZE];29};30static struct wilco_ec_debugfs *debug_info;31 32/**33 * parse_hex_sentence() - Convert a ascii hex representation into byte array.34 * @in: Input buffer of ascii.35 * @isize: Length of input buffer.36 * @out: Output buffer.37 * @osize: Length of output buffer, e.g. max number of bytes to parse.38 *39 * An valid input is a series of ascii hexadecimal numbers, separated by spaces.40 * An example valid input is41 * " 00 f2 0 000076 6 0 ff"42 *43 * If an individual "word" within the hex sentence is longer than MAX_WORD_SIZE,44 * then the sentence is illegal, and parsing will fail.45 *46 * Return: Number of bytes parsed, or negative error code on failure.47 */48static int parse_hex_sentence(const char *in, int isize, u8 *out, int osize)49{50 int n_parsed = 0;51 int word_start = 0;52 int word_end;53 int word_len;54 /* Temp buffer for holding a "word" of chars that represents one byte */55 #define MAX_WORD_SIZE 1656 char tmp[MAX_WORD_SIZE + 1];57 u8 byte;58 59 while (word_start < isize && n_parsed < osize) {60 /* Find the start of the next word */61 while (word_start < isize && isspace(in[word_start]))62 word_start++;63 /* reached the end of the input before next word? */64 if (word_start >= isize)65 break;66 67 /* Find the end of this word */68 word_end = word_start;69 while (word_end < isize && !isspace(in[word_end]))70 word_end++;71 72 /* Copy to a tmp NULL terminated string */73 word_len = word_end - word_start;74 if (word_len > MAX_WORD_SIZE)75 return -EINVAL;76 memcpy(tmp, in + word_start, word_len);77 tmp[word_len] = '\0';78 79 /*80 * Convert from hex string, place in output. If fails to parse,81 * just return -EINVAL because specific error code is only82 * relevant for this one word, returning it would be confusing.83 */84 if (kstrtou8(tmp, 16, &byte))85 return -EINVAL;86 out[n_parsed++] = byte;87 88 word_start = word_end;89 }90 return n_parsed;91}92 93/* The message type takes up two bytes*/94#define TYPE_AND_DATA_SIZE ((EC_MAILBOX_DATA_SIZE) + 2)95 96static ssize_t raw_write(struct file *file, const char __user *user_buf,97 size_t count, loff_t *ppos)98{99 char *buf = debug_info->formatted_data;100 struct wilco_ec_message msg;101 u8 request_data[TYPE_AND_DATA_SIZE];102 ssize_t kcount;103 int ret;104 105 if (count > FORMATTED_BUFFER_SIZE)106 return -EINVAL;107 108 kcount = simple_write_to_buffer(buf, FORMATTED_BUFFER_SIZE, ppos,109 user_buf, count);110 if (kcount < 0)111 return kcount;112 113 ret = parse_hex_sentence(buf, kcount, request_data, TYPE_AND_DATA_SIZE);114 if (ret < 0)115 return ret;116 /* Need at least two bytes for message type and one byte of data */117 if (ret < 3)118 return -EINVAL;119 120 msg.type = request_data[0] << 8 | request_data[1];121 msg.flags = 0;122 msg.request_data = request_data + 2;123 msg.request_size = ret - 2;124 memset(debug_info->raw_data, 0, sizeof(debug_info->raw_data));125 msg.response_data = debug_info->raw_data;126 msg.response_size = EC_MAILBOX_DATA_SIZE;127 128 ret = wilco_ec_mailbox(debug_info->ec, &msg);129 if (ret < 0)130 return ret;131 debug_info->response_size = ret;132 133 return count;134}135 136static ssize_t raw_read(struct file *file, char __user *user_buf, size_t count,137 loff_t *ppos)138{139 int fmt_len = 0;140 141 if (debug_info->response_size) {142 fmt_len = hex_dump_to_buffer(debug_info->raw_data,143 debug_info->response_size,144 16, 1, debug_info->formatted_data,145 sizeof(debug_info->formatted_data),146 true);147 /* Only return response the first time it is read */148 debug_info->response_size = 0;149 }150 151 return simple_read_from_buffer(user_buf, count, ppos,152 debug_info->formatted_data, fmt_len);153}154 155static const struct file_operations fops_raw = {156 .owner = THIS_MODULE,157 .read = raw_read,158 .write = raw_write,159};160 161#define CMD_KB_CHROME 0x88162#define SUB_CMD_H1_GPIO 0x0A163#define SUB_CMD_TEST_EVENT 0x0B164 165struct ec_request {166 u8 cmd; /* Always CMD_KB_CHROME */167 u8 reserved;168 u8 sub_cmd;169} __packed;170 171struct ec_response {172 u8 status; /* 0 if allowed */173 u8 val;174} __packed;175 176static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val)177{178 struct ec_request rq;179 struct ec_response rs;180 struct wilco_ec_message msg;181 int ret;182 183 memset(&rq, 0, sizeof(rq));184 rq.cmd = CMD_KB_CHROME;185 rq.sub_cmd = sub_cmd;186 187 memset(&msg, 0, sizeof(msg));188 msg.type = WILCO_EC_MSG_LEGACY;189 msg.request_data = &rq;190 msg.request_size = sizeof(rq);191 msg.response_data = &rs;192 msg.response_size = sizeof(rs);193 ret = wilco_ec_mailbox(ec, &msg);194 if (ret < 0)195 return ret;196 if (rs.status)197 return -EIO;198 199 *out_val = rs.val;200 201 return 0;202}203 204/**205 * h1_gpio_get() - Gets h1 gpio status.206 * @arg: The wilco EC device.207 * @val: BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL208 */209static int h1_gpio_get(void *arg, u64 *val)210{211 int ret;212 213 ret = send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val);214 if (ret == 0)215 *val &= 0xFF;216 return ret;217}218 219DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n");220 221/**222 * test_event_set() - Sends command to EC to cause an EC test event.223 * @arg: The wilco EC device.224 * @val: unused.225 */226static int test_event_set(void *arg, u64 val)227{228 u8 ret;229 230 return send_ec_cmd(arg, SUB_CMD_TEST_EVENT, &ret);231}232 233/* Format is unused since it is only required for get method which is NULL */234DEFINE_DEBUGFS_ATTRIBUTE(fops_test_event, NULL, test_event_set, "%llu\n");235 236/**237 * wilco_ec_debugfs_probe() - Create the debugfs node238 * @pdev: The platform device, probably created in core.c239 *240 * Try to create a debugfs node. If it fails, then we don't want to change241 * behavior at all, this is for debugging after all. Just fail silently.242 *243 * Return: 0 always.244 */245static int wilco_ec_debugfs_probe(struct platform_device *pdev)246{247 struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);248 249 debug_info = devm_kzalloc(&pdev->dev, sizeof(*debug_info), GFP_KERNEL);250 if (!debug_info)251 return 0;252 debug_info->ec = ec;253 debug_info->dir = debugfs_create_dir("wilco_ec", NULL);254 debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw);255 debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec,256 &fops_h1_gpio);257 debugfs_create_file("test_event", 0200, debug_info->dir, ec,258 &fops_test_event);259 260 return 0;261}262 263static void wilco_ec_debugfs_remove(struct platform_device *pdev)264{265 debugfs_remove_recursive(debug_info->dir);266}267 268static const struct platform_device_id wilco_ec_debugfs_id[] = {269 { DRV_NAME, 0 },270 {}271};272MODULE_DEVICE_TABLE(platform, wilco_ec_debugfs_id);273 274static struct platform_driver wilco_ec_debugfs_driver = {275 .driver = {276 .name = DRV_NAME,277 },278 .probe = wilco_ec_debugfs_probe,279 .remove_new = wilco_ec_debugfs_remove,280 .id_table = wilco_ec_debugfs_id,281};282 283module_platform_driver(wilco_ec_debugfs_driver);284 285MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");286MODULE_LICENSE("GPL v2");287MODULE_DESCRIPTION("Wilco EC debugfs driver");288