1a94da204SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29a163ed8SThomas Gleixner /* ----------------------------------------------------------------------- *
39a163ed8SThomas Gleixner *
42347d933SH. Peter Anvin * Copyright 2000-2008 H. Peter Anvin - All Rights Reserved
59a163ed8SThomas Gleixner *
69a163ed8SThomas Gleixner * ----------------------------------------------------------------------- */
79a163ed8SThomas Gleixner
89a163ed8SThomas Gleixner /*
99a163ed8SThomas Gleixner * x86 CPUID access device
109a163ed8SThomas Gleixner *
119a163ed8SThomas Gleixner * This device is accessed by lseek() to the appropriate CPUID level
129a163ed8SThomas Gleixner * and then read in chunks of 16 bytes. A larger size means multiple
139a163ed8SThomas Gleixner * reads of consecutive levels.
149a163ed8SThomas Gleixner *
152347d933SH. Peter Anvin * The lower 32 bits of the file position is used as the incoming %eax,
162347d933SH. Peter Anvin * and the upper 32 bits of the file position as the incoming %ecx,
172347d933SH. Peter Anvin * the latter intended for "counting" eax levels like eax=4.
182347d933SH. Peter Anvin *
199a163ed8SThomas Gleixner * This driver uses /dev/cpu/%d/cpuid where %d is the minor number, and on
209a163ed8SThomas Gleixner * an SMP box will direct the access to CPU %d.
219a163ed8SThomas Gleixner */
229a163ed8SThomas Gleixner
239a163ed8SThomas Gleixner #include <linux/module.h>
249a163ed8SThomas Gleixner
259a163ed8SThomas Gleixner #include <linux/types.h>
269a163ed8SThomas Gleixner #include <linux/errno.h>
279a163ed8SThomas Gleixner #include <linux/fcntl.h>
289a163ed8SThomas Gleixner #include <linux/init.h>
299a163ed8SThomas Gleixner #include <linux/poll.h>
309a163ed8SThomas Gleixner #include <linux/smp.h>
319a163ed8SThomas Gleixner #include <linux/major.h>
329a163ed8SThomas Gleixner #include <linux/fs.h>
339a163ed8SThomas Gleixner #include <linux/device.h>
349a163ed8SThomas Gleixner #include <linux/cpu.h>
359a163ed8SThomas Gleixner #include <linux/notifier.h>
36f634fa94SJaswinder Singh Rajput #include <linux/uaccess.h>
375a0e3ad6STejun Heo #include <linux/gfp.h>
3867bbd7a8SEric Dumazet #include <linux/completion.h>
399a163ed8SThomas Gleixner
409a163ed8SThomas Gleixner #include <asm/processor.h>
419a163ed8SThomas Gleixner #include <asm/msr.h>
429a163ed8SThomas Gleixner
43ee92be9bSThomas Gleixner static enum cpuhp_state cpuhp_cpuid_state;
449a163ed8SThomas Gleixner
4567bbd7a8SEric Dumazet struct cpuid_regs_done {
4667bbd7a8SEric Dumazet struct cpuid_regs regs;
4767bbd7a8SEric Dumazet struct completion done;
4867bbd7a8SEric Dumazet };
4967bbd7a8SEric Dumazet
cpuid_smp_cpuid(void * cmd_block)509a163ed8SThomas Gleixner static void cpuid_smp_cpuid(void *cmd_block)
519a163ed8SThomas Gleixner {
5267bbd7a8SEric Dumazet struct cpuid_regs_done *cmd = cmd_block;
539a163ed8SThomas Gleixner
5467bbd7a8SEric Dumazet cpuid_count(cmd->regs.eax, cmd->regs.ecx,
5567bbd7a8SEric Dumazet &cmd->regs.eax, &cmd->regs.ebx,
5667bbd7a8SEric Dumazet &cmd->regs.ecx, &cmd->regs.edx);
5767bbd7a8SEric Dumazet
5867bbd7a8SEric Dumazet complete(&cmd->done);
599a163ed8SThomas Gleixner }
609a163ed8SThomas Gleixner
cpuid_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)619a163ed8SThomas Gleixner static ssize_t cpuid_read(struct file *file, char __user *buf,
629a163ed8SThomas Gleixner size_t count, loff_t *ppos)
639a163ed8SThomas Gleixner {
649a163ed8SThomas Gleixner char __user *tmp = buf;
6567bbd7a8SEric Dumazet struct cpuid_regs_done cmd;
66496ad9aaSAl Viro int cpu = iminor(file_inode(file));
672347d933SH. Peter Anvin u64 pos = *ppos;
689ea2b82eSH. Peter Anvin ssize_t bytes = 0;
699ea2b82eSH. Peter Anvin int err = 0;
709a163ed8SThomas Gleixner
719a163ed8SThomas Gleixner if (count % 16)
729a163ed8SThomas Gleixner return -EINVAL; /* Invalid chunk size */
739a163ed8SThomas Gleixner
7467bbd7a8SEric Dumazet init_completion(&cmd.done);
759a163ed8SThomas Gleixner for (; count; count -= 16) {
76545b8c8dSPeter Zijlstra call_single_data_t csd;
77545b8c8dSPeter Zijlstra
78545b8c8dSPeter Zijlstra INIT_CSD(&csd, cpuid_smp_cpuid, &cmd);
7967bbd7a8SEric Dumazet
8067bbd7a8SEric Dumazet cmd.regs.eax = pos;
8167bbd7a8SEric Dumazet cmd.regs.ecx = pos >> 32;
8267bbd7a8SEric Dumazet
8367bbd7a8SEric Dumazet err = smp_call_function_single_async(cpu, &csd);
844b46ca70SH. Peter Anvin if (err)
859ea2b82eSH. Peter Anvin break;
8667bbd7a8SEric Dumazet wait_for_completion(&cmd.done);
8767bbd7a8SEric Dumazet if (copy_to_user(tmp, &cmd.regs, 16)) {
889ea2b82eSH. Peter Anvin err = -EFAULT;
899ea2b82eSH. Peter Anvin break;
909ea2b82eSH. Peter Anvin }
919a163ed8SThomas Gleixner tmp += 16;
929ea2b82eSH. Peter Anvin bytes += 16;
932347d933SH. Peter Anvin *ppos = ++pos;
9467bbd7a8SEric Dumazet reinit_completion(&cmd.done);
959a163ed8SThomas Gleixner }
969a163ed8SThomas Gleixner
979ea2b82eSH. Peter Anvin return bytes ? bytes : err;
989a163ed8SThomas Gleixner }
999a163ed8SThomas Gleixner
cpuid_open(struct inode * inode,struct file * file)1009a163ed8SThomas Gleixner static int cpuid_open(struct inode *inode, struct file *file)
1019a163ed8SThomas Gleixner {
1025119e92eSJonathan Corbet unsigned int cpu;
1035119e92eSJonathan Corbet struct cpuinfo_x86 *c;
1045119e92eSJonathan Corbet
105496ad9aaSAl Viro cpu = iminor(file_inode(file));
1065a943617SJohn Kacur if (cpu >= nr_cpu_ids || !cpu_online(cpu))
1075a943617SJohn Kacur return -ENXIO; /* No such CPU */
1085a943617SJohn Kacur
1095119e92eSJonathan Corbet c = &cpu_data(cpu);
1109a163ed8SThomas Gleixner if (c->cpuid_level < 0)
1115a943617SJohn Kacur return -EIO; /* CPUID not supported */
1125a943617SJohn Kacur
1135a943617SJohn Kacur return 0;
1149a163ed8SThomas Gleixner }
1159a163ed8SThomas Gleixner
1169a163ed8SThomas Gleixner /*
1179a163ed8SThomas Gleixner * File operations we support
1189a163ed8SThomas Gleixner */
1199a163ed8SThomas Gleixner static const struct file_operations cpuid_fops = {
1209a163ed8SThomas Gleixner .owner = THIS_MODULE,
121b25472f9SAl Viro .llseek = no_seek_end_llseek,
1229a163ed8SThomas Gleixner .read = cpuid_read,
1239a163ed8SThomas Gleixner .open = cpuid_open,
1249a163ed8SThomas Gleixner };
1259a163ed8SThomas Gleixner
cpuid_devnode(const struct device * dev,umode_t * mode)126*f4a5fbfaSIvan Orlov static char *cpuid_devnode(const struct device *dev, umode_t *mode)
127*f4a5fbfaSIvan Orlov {
128*f4a5fbfaSIvan Orlov return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt));
129*f4a5fbfaSIvan Orlov }
130*f4a5fbfaSIvan Orlov
131*f4a5fbfaSIvan Orlov static const struct class cpuid_class = {
132*f4a5fbfaSIvan Orlov .name = "cpuid",
133*f4a5fbfaSIvan Orlov .devnode = cpuid_devnode,
134*f4a5fbfaSIvan Orlov };
135*f4a5fbfaSIvan Orlov
cpuid_device_create(unsigned int cpu)1368c07b494SSebastian Andrzej Siewior static int cpuid_device_create(unsigned int cpu)
1379a163ed8SThomas Gleixner {
1389a163ed8SThomas Gleixner struct device *dev;
1399a163ed8SThomas Gleixner
140*f4a5fbfaSIvan Orlov dev = device_create(&cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL,
141a9b12619SGreg Kroah-Hartman "cpu%d", cpu);
142cbda45a2SFabian Frederick return PTR_ERR_OR_ZERO(dev);
1431f503e77SAkinobu Mita }
1441f503e77SAkinobu Mita
cpuid_device_destroy(unsigned int cpu)1458c07b494SSebastian Andrzej Siewior static int cpuid_device_destroy(unsigned int cpu)
1461f503e77SAkinobu Mita {
147*f4a5fbfaSIvan Orlov device_destroy(&cpuid_class, MKDEV(CPUID_MAJOR, cpu));
1488c07b494SSebastian Andrzej Siewior return 0;
1499a163ed8SThomas Gleixner }
1509a163ed8SThomas Gleixner
cpuid_init(void)1519a163ed8SThomas Gleixner static int __init cpuid_init(void)
1529a163ed8SThomas Gleixner {
1538c07b494SSebastian Andrzej Siewior int err;
1549a163ed8SThomas Gleixner
1550b962d47SH. Peter Anvin if (__register_chrdev(CPUID_MAJOR, 0, NR_CPUS,
1560b962d47SH. Peter Anvin "cpu/cpuid", &cpuid_fops)) {
1579a163ed8SThomas Gleixner printk(KERN_ERR "cpuid: unable to get major %d for cpuid\n",
1589a163ed8SThomas Gleixner CPUID_MAJOR);
1598c07b494SSebastian Andrzej Siewior return -EBUSY;
1609a163ed8SThomas Gleixner }
161*f4a5fbfaSIvan Orlov err = class_register(&cpuid_class);
162*f4a5fbfaSIvan Orlov if (err)
1639a163ed8SThomas Gleixner goto out_chrdev;
1644b660b38SSrivatsa S. Bhat
165ee92be9bSThomas Gleixner err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/cpuid:online",
1668c07b494SSebastian Andrzej Siewior cpuid_device_create, cpuid_device_destroy);
167ee92be9bSThomas Gleixner if (err < 0)
1689a163ed8SThomas Gleixner goto out_class;
1699a163ed8SThomas Gleixner
170ee92be9bSThomas Gleixner cpuhp_cpuid_state = err;
1718c07b494SSebastian Andrzej Siewior return 0;
1729a163ed8SThomas Gleixner
1739a163ed8SThomas Gleixner out_class:
174*f4a5fbfaSIvan Orlov class_unregister(&cpuid_class);
1759a163ed8SThomas Gleixner out_chrdev:
1760b962d47SH. Peter Anvin __unregister_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid");
1779a163ed8SThomas Gleixner return err;
1789a163ed8SThomas Gleixner }
179ee92be9bSThomas Gleixner module_init(cpuid_init);
1809a163ed8SThomas Gleixner
cpuid_exit(void)1819a163ed8SThomas Gleixner static void __exit cpuid_exit(void)
1829a163ed8SThomas Gleixner {
183ee92be9bSThomas Gleixner cpuhp_remove_state(cpuhp_cpuid_state);
184*f4a5fbfaSIvan Orlov class_unregister(&cpuid_class);
185da482474SRuss Anderson __unregister_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid");
1869a163ed8SThomas Gleixner }
1879a163ed8SThomas Gleixner module_exit(cpuid_exit);
1889a163ed8SThomas Gleixner
1899a163ed8SThomas Gleixner MODULE_AUTHOR("H. Peter Anvin <hpa@zytor.com>");
1909a163ed8SThomas Gleixner MODULE_DESCRIPTION("x86 generic CPUID driver");
1919a163ed8SThomas Gleixner MODULE_LICENSE("GPL");
192