1
2
3
4
5
6
7
8
9
10#include <linux/cpufeature.h>
11#include <linux/cpuhotplug.h>
12#include <linux/fs.h>
13#include <linux/hashtable.h>
14#include <linux/miscdevice.h>
15#include <linux/module.h>
16#include <linux/pci.h>
17#include <linux/sched/signal.h>
18#include <linux/slab.h>
19#include <linux/uaccess.h>
20#include <uapi/linux/isst_if.h>
21
22#include "isst_if_common.h"
23
24#define MSR_THREAD_ID_INFO 0x53
25#define MSR_CPU_BUS_NUMBER 0x128
26
27static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX];
28
29static int punit_msr_white_list[] = {
30 MSR_TURBO_RATIO_LIMIT,
31 MSR_CONFIG_TDP_CONTROL,
32};
33
34struct isst_valid_cmd_ranges {
35 u16 cmd;
36 u16 sub_cmd_beg;
37 u16 sub_cmd_end;
38};
39
40struct isst_cmd_set_req_type {
41 u16 cmd;
42 u16 sub_cmd;
43 u16 param;
44};
45
46static const struct isst_valid_cmd_ranges isst_valid_cmds[] = {
47 {0xD0, 0x00, 0x03},
48 {0x7F, 0x00, 0x0B},
49 {0x7F, 0x10, 0x12},
50 {0x7F, 0x20, 0x23},
51};
52
53static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = {
54 {0xD0, 0x00, 0x08},
55 {0xD0, 0x01, 0x08},
56 {0xD0, 0x02, 0x08},
57 {0xD0, 0x03, 0x08},
58 {0x7F, 0x02, 0x00},
59 {0x7F, 0x08, 0x00},
60};
61
62struct isst_cmd {
63 struct hlist_node hnode;
64 u64 data;
65 u32 cmd;
66 int cpu;
67 int mbox_cmd_type;
68 u32 param;
69};
70
71static DECLARE_HASHTABLE(isst_hash, 8);
72static DEFINE_MUTEX(isst_hash_lock);
73
74static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param,
75 u32 data)
76{
77 struct isst_cmd *sst_cmd;
78
79 sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL);
80 if (!sst_cmd)
81 return -ENOMEM;
82
83 sst_cmd->cpu = cpu;
84 sst_cmd->cmd = cmd;
85 sst_cmd->mbox_cmd_type = mbox_cmd_type;
86 sst_cmd->param = param;
87 sst_cmd->data = data;
88
89 hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd);
90
91 return 0;
92}
93
94static void isst_delete_hash(void)
95{
96 struct isst_cmd *sst_cmd;
97 struct hlist_node *tmp;
98 int i;
99
100 hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) {
101 hash_del(&sst_cmd->hnode);
102 kfree(sst_cmd);
103 }
104}
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type,
122 u32 param, u64 data)
123{
124 struct isst_cmd *sst_cmd;
125 int full_cmd, ret;
126
127 full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16;
128 full_cmd |= (sub_cmd & GENMASK_ULL(15, 0));
129 mutex_lock(&isst_hash_lock);
130 hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) {
131 if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu &&
132 sst_cmd->mbox_cmd_type == mbox_cmd_type) {
133 sst_cmd->param = param;
134 sst_cmd->data = data;
135 mutex_unlock(&isst_hash_lock);
136 return 0;
137 }
138 }
139
140 ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data);
141 mutex_unlock(&isst_hash_lock);
142
143 return ret;
144}
145EXPORT_SYMBOL_GPL(isst_store_cmd);
146
147static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb,
148 struct isst_cmd *sst_cmd)
149{
150 struct isst_if_mbox_cmd mbox_cmd;
151 int wr_only;
152
153 mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16;
154 mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0);
155 mbox_cmd.parameter = sst_cmd->param;
156 mbox_cmd.req_data = sst_cmd->data;
157 mbox_cmd.logical_cpu = sst_cmd->cpu;
158 (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1);
159}
160
161
162
163
164
165
166
167
168void isst_resume_common(void)
169{
170 struct isst_cmd *sst_cmd;
171 int i;
172
173 hash_for_each(isst_hash, i, sst_cmd, hnode) {
174 struct isst_if_cmd_cb *cb;
175
176 if (sst_cmd->mbox_cmd_type) {
177 cb = &punit_callbacks[ISST_IF_DEV_MBOX];
178 if (cb->registered)
179 isst_mbox_resume_command(cb, sst_cmd);
180 } else {
181 wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
182 sst_cmd->data);
183 }
184 }
185}
186EXPORT_SYMBOL_GPL(isst_resume_common);
187
188static void isst_restore_msr_local(int cpu)
189{
190 struct isst_cmd *sst_cmd;
191 int i;
192
193 mutex_lock(&isst_hash_lock);
194 for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
195 if (!punit_msr_white_list[i])
196 break;
197
198 hash_for_each_possible(isst_hash, sst_cmd, hnode,
199 punit_msr_white_list[i]) {
200 if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu)
201 wrmsrl_safe(sst_cmd->cmd, sst_cmd->data);
202 }
203 }
204 mutex_unlock(&isst_hash_lock);
205}
206
207
208
209
210
211
212
213
214
215
216bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd)
217{
218 int i;
219
220 if (cmd->logical_cpu >= nr_cpu_ids)
221 return true;
222
223 for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) {
224 if (cmd->command == isst_valid_cmds[i].cmd &&
225 (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg &&
226 cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) {
227 return false;
228 }
229 }
230
231 return true;
232}
233EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid);
234
235
236
237
238
239
240
241
242
243bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd)
244{
245 int i;
246
247 for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) {
248 if (cmd->command == isst_cmd_set_reqs[i].cmd &&
249 cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd &&
250 cmd->parameter == isst_cmd_set_reqs[i].param) {
251 return true;
252 }
253 }
254
255 return false;
256}
257EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req);
258
259static int isst_if_get_platform_info(void __user *argp)
260{
261 struct isst_if_platform_info info;
262
263 info.api_version = ISST_IF_API_VERSION,
264 info.driver_version = ISST_IF_DRIVER_VERSION,
265 info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT,
266 info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered;
267 info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered;
268
269 if (copy_to_user(argp, &info, sizeof(info)))
270 return -EFAULT;
271
272 return 0;
273}
274
275
276struct isst_if_cpu_info {
277
278 int bus_info[2];
279 int punit_cpu_id;
280};
281
282static struct isst_if_cpu_info *isst_cpu_info;
283
284
285
286
287
288
289
290
291
292
293
294
295
296struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
297{
298 int bus_number;
299
300 if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
301 cpu >= num_possible_cpus())
302 return NULL;
303
304 bus_number = isst_cpu_info[cpu].bus_info[bus_no];
305 if (bus_number < 0)
306 return NULL;
307
308 return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn));
309}
310EXPORT_SYMBOL_GPL(isst_if_get_pci_dev);
311
312static int isst_if_cpu_online(unsigned int cpu)
313{
314 u64 data;
315 int ret;
316
317 ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data);
318 if (ret) {
319
320 isst_cpu_info[cpu].bus_info[0] = -1;
321 isst_cpu_info[cpu].bus_info[1] = -1;
322 } else {
323 isst_cpu_info[cpu].bus_info[0] = data & 0xff;
324 isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff;
325 }
326
327 ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
328 if (ret) {
329 isst_cpu_info[cpu].punit_cpu_id = -1;
330 return ret;
331 }
332 isst_cpu_info[cpu].punit_cpu_id = data;
333
334 isst_restore_msr_local(cpu);
335
336 return 0;
337}
338
339static int isst_if_online_id;
340
341static int isst_if_cpu_info_init(void)
342{
343 int ret;
344
345 isst_cpu_info = kcalloc(num_possible_cpus(),
346 sizeof(*isst_cpu_info),
347 GFP_KERNEL);
348 if (!isst_cpu_info)
349 return -ENOMEM;
350
351 ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
352 "platform/x86/isst-if:online",
353 isst_if_cpu_online, NULL);
354 if (ret < 0) {
355 kfree(isst_cpu_info);
356 return ret;
357 }
358
359 isst_if_online_id = ret;
360
361 return 0;
362}
363
364static void isst_if_cpu_info_exit(void)
365{
366 cpuhp_remove_state(isst_if_online_id);
367 kfree(isst_cpu_info);
368};
369
370static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume)
371{
372 struct isst_if_cpu_map *cpu_map;
373
374 cpu_map = (struct isst_if_cpu_map *)cmd_ptr;
375 if (cpu_map->logical_cpu >= nr_cpu_ids ||
376 cpu_map->logical_cpu >= num_possible_cpus())
377 return -EINVAL;
378
379 *write_only = 0;
380 cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id;
381
382 return 0;
383}
384
385static bool match_punit_msr_white_list(int msr)
386{
387 int i;
388
389 for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
390 if (punit_msr_white_list[i] == msr)
391 return true;
392 }
393
394 return false;
395}
396
397static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
398{
399 struct isst_if_msr_cmd *msr_cmd;
400 int ret;
401
402 msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr;
403
404 if (!match_punit_msr_white_list(msr_cmd->msr))
405 return -EINVAL;
406
407 if (msr_cmd->logical_cpu >= nr_cpu_ids)
408 return -EINVAL;
409
410 if (msr_cmd->read_write) {
411 if (!capable(CAP_SYS_ADMIN))
412 return -EPERM;
413
414 ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu,
415 msr_cmd->msr,
416 msr_cmd->data);
417 *write_only = 1;
418 if (!ret && !resume)
419 ret = isst_store_cmd(0, msr_cmd->msr,
420 msr_cmd->logical_cpu,
421 0, 0, msr_cmd->data);
422 } else {
423 u64 data;
424
425 ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu,
426 msr_cmd->msr, &data);
427 if (!ret) {
428 msr_cmd->data = data;
429 *write_only = 0;
430 }
431 }
432
433
434 return ret;
435}
436
437static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb)
438{
439 unsigned char __user *ptr;
440 u32 cmd_count;
441 u8 *cmd_ptr;
442 long ret;
443 int i;
444
445
446 if (copy_from_user(&cmd_count, argp, sizeof(cmd_count)))
447 return -EFAULT;
448
449 if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT)
450 return -EINVAL;
451
452 cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL);
453 if (!cmd_ptr)
454 return -ENOMEM;
455
456
457 ptr = argp + cb->offset;
458
459 for (i = 0; i < cmd_count; ++i) {
460 int wr_only;
461
462 if (signal_pending(current)) {
463 ret = -EINTR;
464 break;
465 }
466
467 if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) {
468 ret = -EFAULT;
469 break;
470 }
471
472 ret = cb->cmd_callback(cmd_ptr, &wr_only, 0);
473 if (ret)
474 break;
475
476 if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) {
477 ret = -EFAULT;
478 break;
479 }
480
481 ptr += cb->cmd_size;
482 }
483
484 kfree(cmd_ptr);
485
486 return i ? i : ret;
487}
488
489static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
490 unsigned long arg)
491{
492 void __user *argp = (void __user *)arg;
493 struct isst_if_cmd_cb cmd_cb;
494 struct isst_if_cmd_cb *cb;
495 long ret = -ENOTTY;
496
497 switch (cmd) {
498 case ISST_IF_GET_PLATFORM_INFO:
499 ret = isst_if_get_platform_info(argp);
500 break;
501 case ISST_IF_GET_PHY_ID:
502 cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map);
503 cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map);
504 cmd_cb.cmd_callback = isst_if_proc_phyid_req;
505 ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
506 break;
507 case ISST_IF_IO_CMD:
508 cb = &punit_callbacks[ISST_IF_DEV_MMIO];
509 if (cb->registered)
510 ret = isst_if_exec_multi_cmd(argp, cb);
511 break;
512 case ISST_IF_MBOX_COMMAND:
513 cb = &punit_callbacks[ISST_IF_DEV_MBOX];
514 if (cb->registered)
515 ret = isst_if_exec_multi_cmd(argp, cb);
516 break;
517 case ISST_IF_MSR_COMMAND:
518 cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd);
519 cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd);
520 cmd_cb.cmd_callback = isst_if_msr_cmd_req;
521 ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
522 break;
523 default:
524 break;
525 }
526
527 return ret;
528}
529
530static DEFINE_MUTEX(punit_misc_dev_lock);
531static int misc_usage_count;
532static int misc_device_ret;
533static int misc_device_open;
534
535static int isst_if_open(struct inode *inode, struct file *file)
536{
537 int i, ret = 0;
538
539
540 mutex_lock(&punit_misc_dev_lock);
541 for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
542 struct isst_if_cmd_cb *cb = &punit_callbacks[i];
543
544 if (cb->registered && !try_module_get(cb->owner)) {
545 ret = -ENODEV;
546 break;
547 }
548 }
549 if (ret) {
550 int j;
551
552 for (j = 0; j < i; ++j) {
553 struct isst_if_cmd_cb *cb;
554
555 cb = &punit_callbacks[j];
556 if (cb->registered)
557 module_put(cb->owner);
558 }
559 } else {
560 misc_device_open++;
561 }
562 mutex_unlock(&punit_misc_dev_lock);
563
564 return ret;
565}
566
567static int isst_if_relase(struct inode *inode, struct file *f)
568{
569 int i;
570
571 mutex_lock(&punit_misc_dev_lock);
572 misc_device_open--;
573 for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
574 struct isst_if_cmd_cb *cb = &punit_callbacks[i];
575
576 if (cb->registered)
577 module_put(cb->owner);
578 }
579 mutex_unlock(&punit_misc_dev_lock);
580
581 return 0;
582}
583
584static const struct file_operations isst_if_char_driver_ops = {
585 .open = isst_if_open,
586 .unlocked_ioctl = isst_if_def_ioctl,
587 .release = isst_if_relase,
588};
589
590static struct miscdevice isst_if_char_driver = {
591 .minor = MISC_DYNAMIC_MINOR,
592 .name = "isst_interface",
593 .fops = &isst_if_char_driver_ops,
594};
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb)
612{
613 if (misc_device_ret)
614 return misc_device_ret;
615
616 if (device_type >= ISST_IF_DEV_MAX)
617 return -EINVAL;
618
619 mutex_lock(&punit_misc_dev_lock);
620 if (misc_device_open) {
621 mutex_unlock(&punit_misc_dev_lock);
622 return -EAGAIN;
623 }
624 if (!misc_usage_count) {
625 int ret;
626
627 misc_device_ret = misc_register(&isst_if_char_driver);
628 if (misc_device_ret)
629 goto unlock_exit;
630
631 ret = isst_if_cpu_info_init();
632 if (ret) {
633 misc_deregister(&isst_if_char_driver);
634 misc_device_ret = ret;
635 goto unlock_exit;
636 }
637 }
638 memcpy(&punit_callbacks[device_type], cb, sizeof(*cb));
639 punit_callbacks[device_type].registered = 1;
640 misc_usage_count++;
641unlock_exit:
642 mutex_unlock(&punit_misc_dev_lock);
643
644 return misc_device_ret;
645}
646EXPORT_SYMBOL_GPL(isst_if_cdev_register);
647
648
649
650
651
652
653
654
655
656
657void isst_if_cdev_unregister(int device_type)
658{
659 mutex_lock(&punit_misc_dev_lock);
660 misc_usage_count--;
661 punit_callbacks[device_type].registered = 0;
662 if (device_type == ISST_IF_DEV_MBOX)
663 isst_delete_hash();
664 if (!misc_usage_count && !misc_device_ret) {
665 misc_deregister(&isst_if_char_driver);
666 isst_if_cpu_info_exit();
667 }
668 mutex_unlock(&punit_misc_dev_lock);
669}
670EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
671
672MODULE_LICENSE("GPL v2");
673