blob: 3492aa36bf09a60d363c16191ddb136cc9f444e4 [file] [log] [blame]
Thomas Gleixnera94da202019-05-24 12:04:02 +02001// SPDX-License-Identifier: GPL-2.0-or-later
Linus Torvalds1da177e2005-04-16 15:20:36 -07002/* ----------------------------------------------------------------------- *
H. Peter Anvin2347d932008-02-04 16:47:59 +01003 *
4 * Copyright 2000-2008 H. Peter Anvin - All Rights Reserved
Linus Torvalds1da177e2005-04-16 15:20:36 -07005 *
Linus Torvalds1da177e2005-04-16 15:20:36 -07006 * ----------------------------------------------------------------------- */
7
8/*
Linus Torvalds1da177e2005-04-16 15:20:36 -07009 * x86 CPUID access device
10 *
11 * This device is accessed by lseek() to the appropriate CPUID level
12 * and then read in chunks of 16 bytes. A larger size means multiple
13 * reads of consecutive levels.
14 *
H. Peter Anvin2347d932008-02-04 16:47:59 +010015 * The lower 32 bits of the file position is used as the incoming %eax,
16 * and the upper 32 bits of the file position as the incoming %ecx,
17 * the latter intended for "counting" eax levels like eax=4.
18 *
Linus Torvalds1da177e2005-04-16 15:20:36 -070019 * This driver uses /dev/cpu/%d/cpuid where %d is the minor number, and on
20 * an SMP box will direct the access to CPU %d.
21 */
22
23#include <linux/module.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070024
25#include <linux/types.h>
26#include <linux/errno.h>
27#include <linux/fcntl.h>
28#include <linux/init.h>
29#include <linux/poll.h>
30#include <linux/smp.h>
31#include <linux/major.h>
32#include <linux/fs.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070033#include <linux/device.h>
34#include <linux/cpu.h>
35#include <linux/notifier.h>
Jaswinder Singh Rajputf634fa92008-12-31 16:29:48 +053036#include <linux/uaccess.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090037#include <linux/gfp.h>
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070038#include <linux/completion.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070039
40#include <asm/processor.h>
41#include <asm/msr.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070042
gregkh@suse.de8874b412005-03-23 09:56:34 -080043static struct class *cpuid_class;
Thomas Gleixneree92be92016-11-21 13:07:34 +010044static enum cpuhp_state cpuhp_cpuid_state;
Linus Torvalds1da177e2005-04-16 15:20:36 -070045
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070046struct cpuid_regs_done {
47 struct cpuid_regs regs;
48 struct completion done;
49};
50
Linus Torvalds1da177e2005-04-16 15:20:36 -070051static void cpuid_smp_cpuid(void *cmd_block)
52{
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070053 struct cpuid_regs_done *cmd = cmd_block;
Linus Torvalds1da177e2005-04-16 15:20:36 -070054
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070055 cpuid_count(cmd->regs.eax, cmd->regs.ecx,
56 &cmd->regs.eax, &cmd->regs.ebx,
57 &cmd->regs.ecx, &cmd->regs.edx);
58
59 complete(&cmd->done);
Linus Torvalds1da177e2005-04-16 15:20:36 -070060}
Linus Torvalds1da177e2005-04-16 15:20:36 -070061
Linus Torvalds1da177e2005-04-16 15:20:36 -070062static ssize_t cpuid_read(struct file *file, char __user *buf,
Jaswinder Singh Rajputf634fa92008-12-31 16:29:48 +053063 size_t count, loff_t *ppos)
Linus Torvalds1da177e2005-04-16 15:20:36 -070064{
65 char __user *tmp = buf;
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070066 struct cpuid_regs_done cmd;
Al Viro496ad9a2013-01-23 17:07:38 -050067 int cpu = iminor(file_inode(file));
H. Peter Anvin2347d932008-02-04 16:47:59 +010068 u64 pos = *ppos;
H. Peter Anvin9ea2b822008-08-25 17:35:25 -070069 ssize_t bytes = 0;
70 int err = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -070071
72 if (count % 16)
73 return -EINVAL; /* Invalid chunk size */
74
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070075 init_completion(&cmd.done);
Daniel Marjamaki6b7f4302006-01-06 00:12:13 -080076 for (; count; count -= 16) {
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070077 call_single_data_t csd = {
78 .func = cpuid_smp_cpuid,
79 .info = &cmd,
80 };
81
82 cmd.regs.eax = pos;
83 cmd.regs.ecx = pos >> 32;
84
85 err = smp_call_function_single_async(cpu, &csd);
H. Peter Anvin4b46ca72008-08-25 17:28:20 -070086 if (err)
H. Peter Anvin9ea2b822008-08-25 17:35:25 -070087 break;
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070088 wait_for_completion(&cmd.done);
89 if (copy_to_user(tmp, &cmd.regs, 16)) {
H. Peter Anvin9ea2b822008-08-25 17:35:25 -070090 err = -EFAULT;
91 break;
92 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070093 tmp += 16;
H. Peter Anvin9ea2b822008-08-25 17:35:25 -070094 bytes += 16;
H. Peter Anvin2347d932008-02-04 16:47:59 +010095 *ppos = ++pos;
Eric Dumazet67bbd7a2018-03-23 14:58:18 -070096 reinit_completion(&cmd.done);
Linus Torvalds1da177e2005-04-16 15:20:36 -070097 }
98
H. Peter Anvin9ea2b822008-08-25 17:35:25 -070099 return bytes ? bytes : err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100}
101
102static int cpuid_open(struct inode *inode, struct file *file)
103{
Jonathan Corbet5119e922008-05-15 09:12:01 -0600104 unsigned int cpu;
105 struct cpuinfo_x86 *c;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700106
Al Viro496ad9a2013-01-23 17:07:38 -0500107 cpu = iminor(file_inode(file));
John Kacur5a943612009-10-08 17:20:15 +0200108 if (cpu >= nr_cpu_ids || !cpu_online(cpu))
109 return -ENXIO; /* No such CPU */
110
Jonathan Corbet5119e922008-05-15 09:12:01 -0600111 c = &cpu_data(cpu);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700112 if (c->cpuid_level < 0)
John Kacur5a943612009-10-08 17:20:15 +0200113 return -EIO; /* CPUID not supported */
114
115 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116}
117
118/*
119 * File operations we support
120 */
Arjan van de Ven5dfe4c92007-02-12 00:55:31 -0800121static const struct file_operations cpuid_fops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700122 .owner = THIS_MODULE,
Al Virob25472f2015-12-05 22:04:48 -0500123 .llseek = no_seek_end_llseek,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124 .read = cpuid_read,
125 .open = cpuid_open,
126};
127
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100128static int cpuid_device_create(unsigned int cpu)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129{
Greg Kroah-Hartman07accdc2006-08-07 22:19:37 -0700130 struct device *dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131
Greg Kroah-Hartmana9b12612008-07-21 20:03:34 -0700132 dev = device_create(cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL,
133 "cpu%d", cpu);
Fabian Frederickcbda45a2014-10-17 22:01:17 +0200134 return PTR_ERR_OR_ZERO(dev);
Akinobu Mita1f503e72007-10-19 20:35:03 +0200135}
136
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100137static int cpuid_device_destroy(unsigned int cpu)
Akinobu Mita1f503e72007-10-19 20:35:03 +0200138{
139 device_destroy(cpuid_class, MKDEV(CPUID_MAJOR, cpu));
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100140 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700141}
142
Al Viro2c9ede52011-07-23 20:24:48 -0400143static char *cpuid_devnode(struct device *dev, umode_t *mode)
Kay Sievers07e9bb82009-04-30 15:23:42 +0200144{
145 return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt));
146}
147
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148static int __init cpuid_init(void)
149{
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100150 int err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700151
H. Peter Anvin0b962d42009-12-15 15:13:07 -0800152 if (__register_chrdev(CPUID_MAJOR, 0, NR_CPUS,
153 "cpu/cpuid", &cpuid_fops)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700154 printk(KERN_ERR "cpuid: unable to get major %d for cpuid\n",
155 CPUID_MAJOR);
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100156 return -EBUSY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700157 }
gregkh@suse.de8874b412005-03-23 09:56:34 -0800158 cpuid_class = class_create(THIS_MODULE, "cpuid");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700159 if (IS_ERR(cpuid_class)) {
160 err = PTR_ERR(cpuid_class);
161 goto out_chrdev;
162 }
Kay Sieverse454cea2009-09-18 23:01:12 +0200163 cpuid_class->devnode = cpuid_devnode;
Srivatsa S. Bhat4b660b32014-03-11 02:06:44 +0530164
Thomas Gleixneree92be92016-11-21 13:07:34 +0100165 err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/cpuid:online",
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100166 cpuid_device_create, cpuid_device_destroy);
Thomas Gleixneree92be92016-11-21 13:07:34 +0100167 if (err < 0)
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100168 goto out_class;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700169
Thomas Gleixneree92be92016-11-21 13:07:34 +0100170 cpuhp_cpuid_state = err;
Sebastian Andrzej Siewior8c07b492016-11-17 19:35:23 +0100171 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700172
173out_class:
gregkh@suse.de8874b412005-03-23 09:56:34 -0800174 class_destroy(cpuid_class);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175out_chrdev:
H. Peter Anvin0b962d42009-12-15 15:13:07 -0800176 __unregister_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 return err;
178}
Thomas Gleixneree92be92016-11-21 13:07:34 +0100179module_init(cpuid_init);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180
181static void __exit cpuid_exit(void)
182{
Thomas Gleixneree92be92016-11-21 13:07:34 +0100183 cpuhp_remove_state(cpuhp_cpuid_state);
gregkh@suse.de8874b412005-03-23 09:56:34 -0800184 class_destroy(cpuid_class);
Russ Andersonda482472010-01-26 20:37:22 -0600185 __unregister_chrdev(CPUID_MAJOR, 0, NR_CPUS, "cpu/cpuid");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700186}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700187module_exit(cpuid_exit);
188
189MODULE_AUTHOR("H. Peter Anvin <hpa@zytor.com>");
190MODULE_DESCRIPTION("x86 generic CPUID driver");
191MODULE_LICENSE("GPL");