2005-04-17 02:20:36 +04:00
/* ----------------------------------------------------------------------- *
2008-02-04 18:47:59 +03:00
*
* Copyright 2000 - 2008 H . Peter Anvin - All Rights Reserved
2005-04-17 02:20:36 +04:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , Inc . , 675 Mass Ave , Cambridge MA 0213 9 ,
* USA ; either version 2 of the License , or ( at your option ) any later
* version ; incorporated herein by reference .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* x86 CPUID access device
*
* This device is accessed by lseek ( ) to the appropriate CPUID level
* and then read in chunks of 16 bytes . A larger size means multiple
* reads of consecutive levels .
*
2008-02-04 18:47:59 +03:00
* The lower 32 bits of the file position is used as the incoming % eax ,
* and the upper 32 bits of the file position as the incoming % ecx ,
* the latter intended for " counting " eax levels like eax = 4.
*
2005-04-17 02:20:36 +04:00
* This driver uses / dev / cpu / % d / cpuid where % d is the minor number , and on
* an SMP box will direct the access to CPU % d .
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/fcntl.h>
# include <linux/init.h>
# include <linux/poll.h>
# include <linux/smp.h>
# include <linux/major.h>
# include <linux/fs.h>
# include <linux/smp_lock.h>
# include <linux/device.h>
# include <linux/cpu.h>
# include <linux/notifier.h>
# include <asm/processor.h>
# include <asm/msr.h>
# include <asm/uaccess.h>
# include <asm/system.h>
2005-03-23 20:56:34 +03:00
static struct class * cpuid_class ;
2005-04-17 02:20:36 +04:00
2008-02-04 18:47:59 +03:00
struct cpuid_regs {
u32 eax , ebx , ecx , edx ;
2005-04-17 02:20:36 +04:00
} ;
static void cpuid_smp_cpuid ( void * cmd_block )
{
2008-02-04 18:47:59 +03:00
struct cpuid_regs * cmd = ( struct cpuid_regs * ) cmd_block ;
2005-04-17 02:20:36 +04:00
2008-02-04 18:47:59 +03:00
cpuid_count ( cmd - > eax , cmd - > ecx ,
& cmd - > eax , & cmd - > ebx , & cmd - > ecx , & cmd - > edx ) ;
2005-04-17 02:20:36 +04:00
}
static loff_t cpuid_seek ( struct file * file , loff_t offset , int orig )
{
loff_t ret ;
2008-02-04 18:47:59 +03:00
struct inode * inode = file - > f_mapping - > host ;
2005-04-17 02:20:36 +04:00
2008-02-04 18:47:59 +03:00
mutex_lock ( & inode - > i_mutex ) ;
2005-04-17 02:20:36 +04:00
switch ( orig ) {
case 0 :
file - > f_pos = offset ;
ret = file - > f_pos ;
break ;
case 1 :
file - > f_pos + = offset ;
ret = file - > f_pos ;
break ;
default :
ret = - EINVAL ;
}
2008-02-04 18:47:59 +03:00
mutex_unlock ( & inode - > i_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
static ssize_t cpuid_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
char __user * tmp = buf ;
2008-02-04 18:47:59 +03:00
struct cpuid_regs cmd ;
2006-12-08 13:36:42 +03:00
int cpu = iminor ( file - > f_path . dentry - > d_inode ) ;
2008-02-04 18:47:59 +03:00
u64 pos = * ppos ;
2005-04-17 02:20:36 +04:00
if ( count % 16 )
return - EINVAL ; /* Invalid chunk size */
2006-01-06 11:12:13 +03:00
for ( ; count ; count - = 16 ) {
2008-02-04 18:47:59 +03:00
cmd . eax = pos ;
cmd . ecx = pos > > 32 ;
smp_call_function_single ( cpu , cpuid_smp_cpuid , & cmd , 1 , 1 ) ;
if ( copy_to_user ( tmp , & cmd , 16 ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
tmp + = 16 ;
2008-02-04 18:47:59 +03:00
* ppos = + + pos ;
2005-04-17 02:20:36 +04:00
}
return tmp - buf ;
}
static int cpuid_open ( struct inode * inode , struct file * file )
{
2006-12-08 13:36:42 +03:00
unsigned int cpu = iminor ( file - > f_path . dentry - > d_inode ) ;
2007-10-19 22:35:04 +04:00
struct cpuinfo_x86 * c = & cpu_data ( cpu ) ;
2005-04-17 02:20:36 +04:00
if ( cpu > = NR_CPUS | | ! cpu_online ( cpu ) )
return - ENXIO ; /* No such CPU */
if ( c - > cpuid_level < 0 )
return - EIO ; /* CPUID not supported */
return 0 ;
}
/*
* File operations we support
*/
2007-02-12 11:55:31 +03:00
static const struct file_operations cpuid_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = cpuid_seek ,
. read = cpuid_read ,
. open = cpuid_open ,
} ;
2007-10-19 22:35:03 +04:00
static __cpuinit int cpuid_device_create ( int cpu )
2005-04-17 02:20:36 +04:00
{
2006-08-08 09:19:37 +04:00
struct device * dev ;
2005-04-17 02:20:36 +04:00
2007-10-19 22:35:03 +04:00
dev = device_create ( cpuid_class , NULL , MKDEV ( CPUID_MAJOR , cpu ) ,
" cpu%d " , cpu ) ;
return IS_ERR ( dev ) ? PTR_ERR ( dev ) : 0 ;
}
static void cpuid_device_destroy ( int cpu )
{
device_destroy ( cpuid_class , MKDEV ( CPUID_MAJOR , cpu ) ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-17 20:04:36 +04:00
static int __cpuinit cpuid_class_cpu_callback ( struct notifier_block * nfb ,
unsigned long action ,
void * hcpu )
2005-04-17 02:20:36 +04:00
{
unsigned int cpu = ( unsigned long ) hcpu ;
2007-10-19 22:35:03 +04:00
int err = 0 ;
2005-04-17 02:20:36 +04:00
switch ( action ) {
2007-10-19 22:35:03 +04:00
case CPU_UP_PREPARE :
err = cpuid_device_create ( cpu ) ;
2005-04-17 02:20:36 +04:00
break ;
2007-10-19 22:35:03 +04:00
case CPU_UP_CANCELED :
2008-03-23 22:28:24 +03:00
case CPU_UP_CANCELED_FROZEN :
2005-04-17 02:20:36 +04:00
case CPU_DEAD :
2007-10-19 22:35:03 +04:00
cpuid_device_destroy ( cpu ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2007-10-19 22:35:03 +04:00
return err ? NOTIFY_BAD : NOTIFY_OK ;
2005-04-17 02:20:36 +04:00
}
2008-02-01 19:49:42 +03:00
static struct notifier_block __refdata cpuid_class_cpu_notifier =
2005-04-17 02:20:36 +04:00
{
. notifier_call = cpuid_class_cpu_callback ,
} ;
static int __init cpuid_init ( void )
{
int i , err = 0 ;
i = 0 ;
if ( register_chrdev ( CPUID_MAJOR , " cpu/cpuid " , & cpuid_fops ) ) {
printk ( KERN_ERR " cpuid: unable to get major %d for cpuid \n " ,
CPUID_MAJOR ) ;
err = - EBUSY ;
goto out ;
}
2005-03-23 20:56:34 +03:00
cpuid_class = class_create ( THIS_MODULE , " cpuid " ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( cpuid_class ) ) {
err = PTR_ERR ( cpuid_class ) ;
goto out_chrdev ;
}
for_each_online_cpu ( i ) {
2006-08-08 09:19:37 +04:00
err = cpuid_device_create ( i ) ;
2008-02-04 18:47:59 +03:00
if ( err ! = 0 )
2005-04-17 02:20:36 +04:00
goto out_class ;
}
2006-07-04 04:32:20 +04:00
register_hotcpu_notifier ( & cpuid_class_cpu_notifier ) ;
2005-04-17 02:20:36 +04:00
err = 0 ;
goto out ;
out_class :
i = 0 ;
for_each_online_cpu ( i ) {
2007-10-19 22:35:03 +04:00
cpuid_device_destroy ( i ) ;
2005-04-17 02:20:36 +04:00
}
2005-03-23 20:56:34 +03:00
class_destroy ( cpuid_class ) ;
2005-04-17 02:20:36 +04:00
out_chrdev :
2008-02-04 18:47:59 +03:00
unregister_chrdev ( CPUID_MAJOR , " cpu/cpuid " ) ;
2005-04-17 02:20:36 +04:00
out :
return err ;
}
static void __exit cpuid_exit ( void )
{
int cpu = 0 ;
for_each_online_cpu ( cpu )
2007-10-19 22:35:03 +04:00
cpuid_device_destroy ( cpu ) ;
2005-03-23 20:56:34 +03:00
class_destroy ( cpuid_class ) ;
2005-04-17 02:20:36 +04:00
unregister_chrdev ( CPUID_MAJOR , " cpu/cpuid " ) ;
2006-07-04 04:32:20 +04:00
unregister_hotcpu_notifier ( & cpuid_class_cpu_notifier ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( cpuid_init ) ;
module_exit ( cpuid_exit ) ;
MODULE_AUTHOR ( " H. Peter Anvin <hpa@zytor.com> " ) ;
MODULE_DESCRIPTION ( " x86 generic CPUID driver " ) ;
MODULE_LICENSE ( " GPL " ) ;