xref: /openbmc/linux/arch/x86/kernel/cpuid.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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