2005-04-16 15:20:36 -07:00
/* ----------------------------------------------------------------------- *
2008-02-04 16:47:59 +01:00
*
* Copyright 2000 - 2008 H . Peter Anvin - All Rights Reserved
2005-04-16 15:20:36 -07: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 MSR access device
*
* This device is accessed by lseek ( ) to the appropriate register number
* and then read / write in chunks of 8 bytes . A larger size means multiple
* reads or writes of the same register .
*
* This driver uses / dev / cpu / % d / msr 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/smp_lock.h>
# include <linux/major.h>
# include <linux/fs.h>
# include <linux/device.h>
# include <linux/cpu.h>
# include <linux/notifier.h>
2009-01-12 14:45:14 +05:30
# include <linux/uaccess.h>
2005-04-16 15:20:36 -07:00
# include <asm/processor.h>
# include <asm/msr.h>
# include <asm/system.h>
2005-03-23 09:56:34 -08:00
static struct class * msr_class ;
2005-04-16 15:20:36 -07:00
static loff_t msr_seek ( struct file * file , loff_t offset , int orig )
{
2008-02-04 16:47:59 +01:00
loff_t ret ;
struct inode * inode = file - > f_mapping - > host ;
2005-04-16 15:20:36 -07:00
2008-02-04 16:47:59 +01:00
mutex_lock ( & inode - > i_mutex ) ;
2005-04-16 15:20:36 -07:00
switch ( orig ) {
case 0 :
file - > f_pos = offset ;
ret = file - > f_pos ;
break ;
case 1 :
file - > f_pos + = offset ;
ret = file - > f_pos ;
2008-02-04 16:47:59 +01:00
break ;
default :
ret = - EINVAL ;
2005-04-16 15:20:36 -07:00
}
2008-02-04 16:47:59 +01:00
mutex_unlock ( & inode - > i_mutex ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
2008-02-22 23:11:52 +01:00
static ssize_t msr_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
2005-04-16 15:20:36 -07:00
{
u32 __user * tmp = ( u32 __user * ) buf ;
u32 data [ 2 ] ;
u32 reg = * ppos ;
2006-12-08 02:36:42 -08:00
int cpu = iminor ( file - > f_path . dentry - > d_inode ) ;
2008-08-25 17:34:27 -07:00
int err = 0 ;
ssize_t bytes = 0 ;
2005-04-16 15:20:36 -07:00
if ( count % 8 )
return - EINVAL ; /* Invalid chunk size */
2006-01-06 00:12:12 -08:00
for ( ; count ; count - = 8 ) {
2007-05-08 17:22:01 +02:00
err = rdmsr_safe_on_cpu ( cpu , reg , & data [ 0 ] , & data [ 1 ] ) ;
2009-08-31 14:23:29 -07:00
if ( err )
2008-08-25 17:34:27 -07:00
break ;
if ( copy_to_user ( tmp , & data , 8 ) ) {
err = - EFAULT ;
break ;
2008-08-25 17:27:21 -07:00
}
2005-04-16 15:20:36 -07:00
tmp + = 2 ;
2008-08-25 17:34:27 -07:00
bytes + = 8 ;
2005-04-16 15:20:36 -07:00
}
2008-08-25 17:34:27 -07:00
return bytes ? bytes : err ;
2005-04-16 15:20:36 -07:00
}
static ssize_t msr_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
const u32 __user * tmp = ( const u32 __user * ) buf ;
u32 data [ 2 ] ;
u32 reg = * ppos ;
2006-12-08 02:36:42 -08:00
int cpu = iminor ( file - > f_path . dentry - > d_inode ) ;
2008-08-25 17:34:27 -07:00
int err = 0 ;
ssize_t bytes = 0 ;
2005-04-16 15:20:36 -07:00
if ( count % 8 )
return - EINVAL ; /* Invalid chunk size */
2006-12-07 02:14:13 +01:00
for ( ; count ; count - = 8 ) {
2008-08-25 17:34:27 -07:00
if ( copy_from_user ( & data , tmp , 8 ) ) {
err = - EFAULT ;
break ;
}
2007-05-08 17:22:01 +02:00
err = wrmsr_safe_on_cpu ( cpu , reg , data [ 0 ] , data [ 1 ] ) ;
2009-08-31 14:23:29 -07:00
if ( err )
2008-08-25 17:34:27 -07:00
break ;
2005-04-16 15:20:36 -07:00
tmp + = 2 ;
2008-08-25 17:34:27 -07:00
bytes + = 8 ;
2005-04-16 15:20:36 -07:00
}
2008-08-25 17:34:27 -07:00
return bytes ? bytes : err ;
2005-04-16 15:20:36 -07:00
}
static int msr_open ( struct inode * inode , struct file * file )
{
2006-12-08 02:36:42 -08:00
unsigned int cpu = iminor ( file - > f_path . dentry - > d_inode ) ;
2007-10-19 20:35:04 +02:00
struct cpuinfo_x86 * c = & cpu_data ( cpu ) ;
2008-05-15 09:12:01 -06:00
int ret = 0 ;
2005-04-16 15:20:36 -07:00
2008-05-15 09:12:01 -06:00
lock_kernel ( ) ;
cpu = iminor ( file - > f_path . dentry - > d_inode ) ;
2005-04-16 15:20:36 -07:00
2008-12-31 18:08:46 -08:00
if ( cpu > = nr_cpu_ids | | ! cpu_online ( cpu ) ) {
2008-05-15 09:12:01 -06:00
ret = - ENXIO ; /* No such CPU */
goto out ;
}
c = & cpu_data ( cpu ) ;
if ( ! cpu_has ( c , X86_FEATURE_MSR ) )
ret = - EIO ; /* MSR not supported */
out :
unlock_kernel ( ) ;
2008-08-14 15:43:33 -07:00
return ret ;
2005-04-16 15:20:36 -07:00
}
/*
* File operations we support
*/
2007-02-12 00:55:31 -08:00
static const struct file_operations msr_fops = {
2005-04-16 15:20:36 -07:00
. owner = THIS_MODULE ,
. llseek = msr_seek ,
. read = msr_read ,
. write = msr_write ,
. open = msr_open ,
} ;
2007-10-18 03:06:38 -07:00
static int __cpuinit msr_device_create ( int cpu )
2005-04-16 15:20:36 -07:00
{
2006-08-07 22:19:37 -07:00
struct device * dev ;
2005-04-16 15:20:36 -07:00
2008-07-21 20:03:34 -07:00
dev = device_create ( msr_class , NULL , MKDEV ( MSR_MAJOR , cpu ) , NULL ,
" msr%d " , cpu ) ;
2007-10-18 03:05:14 -07:00
return IS_ERR ( dev ) ? PTR_ERR ( dev ) : 0 ;
}
static void msr_device_destroy ( int cpu )
{
device_destroy ( msr_class , MKDEV ( MSR_MAJOR , cpu ) ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-17 18:04:36 +02:00
static int __cpuinit msr_class_cpu_callback ( struct notifier_block * nfb ,
2006-06-30 01:55:29 -07:00
unsigned long action , void * hcpu )
2005-04-16 15:20:36 -07:00
{
unsigned int cpu = ( unsigned long ) hcpu ;
2007-10-18 03:05:14 -07:00
int err = 0 ;
2005-04-16 15:20:36 -07:00
switch ( action ) {
2007-10-18 03:05:14 -07:00
case CPU_UP_PREPARE :
err = msr_device_create ( cpu ) ;
2005-04-16 15:20:36 -07:00
break ;
2007-10-18 03:05:14 -07:00
case CPU_UP_CANCELED :
2008-03-23 20:28:24 +01:00
case CPU_UP_CANCELED_FROZEN :
2005-04-16 15:20:36 -07:00
case CPU_DEAD :
2007-10-18 03:05:14 -07:00
msr_device_destroy ( cpu ) ;
2005-04-16 15:20:36 -07:00
break ;
}
2007-10-18 03:05:14 -07:00
return err ? NOTIFY_BAD : NOTIFY_OK ;
2005-04-16 15:20:36 -07:00
}
2008-02-01 17:49:42 +01:00
static struct notifier_block __refdata msr_class_cpu_notifier = {
2005-04-16 15:20:36 -07:00
. notifier_call = msr_class_cpu_callback ,
} ;
2009-04-30 15:23:42 +02:00
static char * msr_nodename ( struct device * dev )
{
return kasprintf ( GFP_KERNEL , " cpu/%u/msr " , MINOR ( dev - > devt ) ) ;
}
2005-04-16 15:20:36 -07:00
static int __init msr_init ( void )
{
int i , err = 0 ;
i = 0 ;
if ( register_chrdev ( MSR_MAJOR , " cpu/msr " , & msr_fops ) ) {
printk ( KERN_ERR " msr: unable to get major %d for msr \n " ,
MSR_MAJOR ) ;
err = - EBUSY ;
goto out ;
}
2005-03-23 09:56:34 -08:00
msr_class = class_create ( THIS_MODULE , " msr " ) ;
2005-04-16 15:20:36 -07:00
if ( IS_ERR ( msr_class ) ) {
err = PTR_ERR ( msr_class ) ;
goto out_chrdev ;
}
2009-04-30 15:23:42 +02:00
msr_class - > nodename = msr_nodename ;
2005-04-16 15:20:36 -07:00
for_each_online_cpu ( i ) {
2006-08-07 22:19:37 -07:00
err = msr_device_create ( i ) ;
2005-04-16 15:20:36 -07:00
if ( err ! = 0 )
goto out_class ;
}
2006-06-30 01:55:29 -07:00
register_hotcpu_notifier ( & msr_class_cpu_notifier ) ;
2005-04-16 15:20:36 -07:00
err = 0 ;
goto out ;
out_class :
i = 0 ;
for_each_online_cpu ( i )
2007-10-18 03:05:14 -07:00
msr_device_destroy ( i ) ;
2005-03-23 09:56:34 -08:00
class_destroy ( msr_class ) ;
2005-04-16 15:20:36 -07:00
out_chrdev :
unregister_chrdev ( MSR_MAJOR , " cpu/msr " ) ;
out :
return err ;
}
static void __exit msr_exit ( void )
{
int cpu = 0 ;
for_each_online_cpu ( cpu )
2007-10-18 03:05:14 -07:00
msr_device_destroy ( cpu ) ;
2005-03-23 09:56:34 -08:00
class_destroy ( msr_class ) ;
2005-04-16 15:20:36 -07:00
unregister_chrdev ( MSR_MAJOR , " cpu/msr " ) ;
2006-06-30 01:55:29 -07:00
unregister_hotcpu_notifier ( & msr_class_cpu_notifier ) ;
2005-04-16 15:20:36 -07:00
}
module_init ( msr_init ) ;
module_exit ( msr_exit )
MODULE_AUTHOR ( " H. Peter Anvin <hpa@zytor.com> " ) ;
MODULE_DESCRIPTION ( " x86 generic MSR driver " ) ;
MODULE_LICENSE ( " GPL " ) ;