1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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")
44static struct class telem_class = {
45 .owner = THIS_MODULE,
46 .name = TELEM_CLASS_NAME,
47};
48
49
50#define TELEM_MAX_DEV 128
51static int telem_major;
52static DEFINE_IDA(telem_ida);
53
54
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
63#define TELEM_ARGS_SIZE_MAX 30
64
65
66
67
68
69
70
71struct wilco_ec_telem_request {
72 u8 command;
73 u8 reserved;
74 u8 args[TELEM_ARGS_SIZE_MAX];
75} __packed;
76
77
78
79
80
81
82struct telem_args_get_log {
83 u8 log_type;
84 u8 log_index;
85} __packed;
86
87
88
89
90
91
92
93
94
95struct telem_args_get_version {
96 u8 index;
97} __packed;
98
99struct telem_args_get_fan_info {
100 u8 command;
101 u8 fan_number;
102 u8 arg;
103} __packed;
104
105struct telem_args_get_diag_info {
106 u8 type;
107 u8 sub_type;
108} __packed;
109
110struct telem_args_get_temp_info {
111 u8 command;
112 u8 index;
113 u8 field;
114 u8 zone;
115} __packed;
116
117struct telem_args_get_temp_read {
118 u8 sensor_index;
119} __packed;
120
121struct telem_args_get_batt_ext_info {
122 u8 var_args[5];
123} __packed;
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139static int check_telem_request(struct wilco_ec_telem_request *rq,
140 size_t size)
141{
142 size_t max_size = offsetof(struct wilco_ec_telem_request, args);
143
144 if (rq->reserved)
145 return -EINVAL;
146
147 switch (rq->command) {
148 case WILCO_EC_TELEM_GET_LOG:
149 max_size += sizeof(struct telem_args_get_log);
150 break;
151 case WILCO_EC_TELEM_GET_VERSION:
152 max_size += sizeof(struct telem_args_get_version);
153 break;
154 case WILCO_EC_TELEM_GET_FAN_INFO:
155 max_size += sizeof(struct telem_args_get_fan_info);
156 break;
157 case WILCO_EC_TELEM_GET_DIAG_INFO:
158 max_size += sizeof(struct telem_args_get_diag_info);
159 break;
160 case WILCO_EC_TELEM_GET_TEMP_INFO:
161 max_size += sizeof(struct telem_args_get_temp_info);
162 break;
163 case WILCO_EC_TELEM_GET_TEMP_READ:
164 max_size += sizeof(struct telem_args_get_temp_read);
165 break;
166 case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
167 max_size += sizeof(struct telem_args_get_batt_ext_info);
168 break;
169 default:
170 return -EINVAL;
171 }
172
173 return (size <= max_size) ? 0 : -EMSGSIZE;
174}
175
176
177
178
179
180
181
182
183struct telem_device_data {
184 struct device dev;
185 struct cdev cdev;
186 struct wilco_ec_device *ec;
187 atomic_t available;
188};
189
190#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
191
192
193
194
195
196
197
198
199struct telem_session_data {
200 struct telem_device_data *dev_data;
201 struct wilco_ec_telem_request request;
202 u8 response[TELEM_RESPONSE_SIZE];
203 bool has_msg;
204};
205
206
207
208
209
210
211
212
213
214
215
216
217
218static int telem_open(struct inode *inode, struct file *filp)
219{
220 struct telem_device_data *dev_data;
221 struct telem_session_data *sess_data;
222
223
224 dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
225 if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
226 return -EBUSY;
227
228 get_device(&dev_data->dev);
229
230 sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
231 if (!sess_data) {
232 atomic_set(&dev_data->available, 1);
233 return -ENOMEM;
234 }
235 sess_data->dev_data = dev_data;
236 sess_data->has_msg = false;
237
238 nonseekable_open(inode, filp);
239 filp->private_data = sess_data;
240
241 return 0;
242}
243
244static ssize_t telem_write(struct file *filp, const char __user *buf,
245 size_t count, loff_t *pos)
246{
247 struct telem_session_data *sess_data = filp->private_data;
248 struct wilco_ec_message msg = {};
249 int ret;
250
251 if (count > sizeof(sess_data->request))
252 return -EMSGSIZE;
253 if (copy_from_user(&sess_data->request, buf, count))
254 return -EFAULT;
255 ret = check_telem_request(&sess_data->request, count);
256 if (ret < 0)
257 return ret;
258
259 memset(sess_data->response, 0, sizeof(sess_data->response));
260 msg.type = WILCO_EC_MSG_TELEMETRY;
261 msg.request_data = &sess_data->request;
262 msg.request_size = sizeof(sess_data->request);
263 msg.response_data = sess_data->response;
264 msg.response_size = sizeof(sess_data->response);
265
266 ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
267 if (ret < 0)
268 return ret;
269 if (ret != sizeof(sess_data->response))
270 return -EMSGSIZE;
271
272 sess_data->has_msg = true;
273
274 return count;
275}
276
277static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
278 loff_t *pos)
279{
280 struct telem_session_data *sess_data = filp->private_data;
281
282 if (!sess_data->has_msg)
283 return -ENODATA;
284 if (count > sizeof(sess_data->response))
285 return -EINVAL;
286
287 if (copy_to_user(buf, sess_data->response, count))
288 return -EFAULT;
289
290 sess_data->has_msg = false;
291
292 return count;
293}
294
295static int telem_release(struct inode *inode, struct file *filp)
296{
297 struct telem_session_data *sess_data = filp->private_data;
298
299 atomic_set(&sess_data->dev_data->available, 1);
300 put_device(&sess_data->dev_data->dev);
301 kfree(sess_data);
302
303 return 0;
304}
305
306static const struct file_operations telem_fops = {
307 .open = telem_open,
308 .write = telem_write,
309 .read = telem_read,
310 .release = telem_release,
311 .llseek = no_llseek,
312 .owner = THIS_MODULE,
313};
314
315
316
317
318
319
320
321
322static void telem_device_free(struct device *d)
323{
324 struct telem_device_data *dev_data;
325
326 dev_data = container_of(d, struct telem_device_data, dev);
327 kfree(dev_data);
328}
329
330
331
332
333
334
335
336
337
338
339static int telem_device_probe(struct platform_device *pdev)
340{
341 struct telem_device_data *dev_data;
342 int error, minor;
343
344
345 minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
346 if (minor < 0) {
347 error = minor;
348 dev_err(&pdev->dev, "Failed to find minor number: %d", error);
349 return error;
350 }
351
352 dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
353 if (!dev_data) {
354 ida_simple_remove(&telem_ida, minor);
355 return -ENOMEM;
356 }
357
358
359 dev_data->ec = dev_get_platdata(&pdev->dev);
360 atomic_set(&dev_data->available, 1);
361 platform_set_drvdata(pdev, dev_data);
362
363
364 dev_data->dev.devt = MKDEV(telem_major, minor);
365 dev_data->dev.class = &telem_class;
366 dev_data->dev.release = telem_device_free;
367 dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
368 device_initialize(&dev_data->dev);
369
370 ;
371 cdev_init(&dev_data->cdev, &telem_fops);
372 error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
373 if (error) {
374 put_device(&dev_data->dev);
375 ida_simple_remove(&telem_ida, minor);
376 return error;
377 }
378
379 return 0;
380}
381
382static int telem_device_remove(struct platform_device *pdev)
383{
384 struct telem_device_data *dev_data = platform_get_drvdata(pdev);
385
386 cdev_device_del(&dev_data->cdev, &dev_data->dev);
387 put_device(&dev_data->dev);
388 ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
389
390 return 0;
391}
392
393static struct platform_driver telem_driver = {
394 .probe = telem_device_probe,
395 .remove = telem_device_remove,
396 .driver = {
397 .name = DRV_NAME,
398 },
399};
400
401static int __init telem_module_init(void)
402{
403 dev_t dev_num = 0;
404 int ret;
405
406 ret = class_register(&telem_class);
407 if (ret) {
408 pr_err(DRV_NAME ": Failed registering class: %d", ret);
409 return ret;
410 }
411
412
413 ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
414 if (ret) {
415 pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret);
416 goto destroy_class;
417 }
418 telem_major = MAJOR(dev_num);
419
420 ret = platform_driver_register(&telem_driver);
421 if (ret < 0) {
422 pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
423 goto unregister_region;
424 }
425
426 return 0;
427
428unregister_region:
429 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
430destroy_class:
431 class_unregister(&telem_class);
432 ida_destroy(&telem_ida);
433 return ret;
434}
435
436static void __exit telem_module_exit(void)
437{
438 platform_driver_unregister(&telem_driver);
439 unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
440 class_unregister(&telem_class);
441 ida_destroy(&telem_ida);
442}
443
444module_init(telem_module_init);
445module_exit(telem_module_exit);
446
447MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
448MODULE_DESCRIPTION("Wilco EC telemetry driver");
449MODULE_LICENSE("GPL");
450MODULE_ALIAS("platform:" DRV_NAME);
451