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