ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / arch / x86_64 / kernel / msr.c
1 #ident "$Id$"
2 /* ----------------------------------------------------------------------- *
3  *   
4  *   Copyright 2000 H. Peter Anvin - All Rights Reserved
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
9  *   USA; either version 2 of the License, or (at your option) any later
10  *   version; incorporated herein by reference.
11  *
12  * ----------------------------------------------------------------------- */
13
14 /*
15  * msr.c
16  *
17  * x86 MSR access device
18  *
19  * This device is accessed by lseek() to the appropriate register number
20  * and then read/write in chunks of 8 bytes.  A larger size means multiple
21  * reads or writes of the same register.
22  *
23  * This driver uses /dev/cpu/%d/msr where %d is the minor number, and on
24  * an SMP box will direct the access to CPU %d.
25  */
26
27 #include <linux/module.h>
28 #include <linux/config.h>
29
30 #include <linux/types.h>
31 #include <linux/errno.h>
32 #include <linux/fcntl.h>
33 #include <linux/init.h>
34 #include <linux/poll.h>
35 #include <linux/smp.h>
36 #include <linux/smp_lock.h>
37 #include <linux/major.h>
38 #include <linux/fs.h>
39
40 #include <asm/processor.h>
41 #include <asm/msr.h>
42 #include <asm/uaccess.h>
43 #include <asm/system.h>
44
45 /* Note: "err" is handled in a funny way below.  Otherwise one version
46    of gcc or another breaks. */
47
48 static inline int wrmsr_eio(u32 reg, u32 eax, u32 edx)
49 {
50   int err;
51
52   asm volatile(
53                "1:      wrmsr\n"
54                "2:\n"
55                ".section .fixup,\"ax\"\n"
56                "3:      movl %4,%0\n"
57                "        jmp 2b\n"
58                ".previous\n"
59                ".section __ex_table,\"a\"\n"
60                "        .align 8\n"
61                "        .quad 1b,3b\n"
62                ".previous"
63                : "=&bDS" (err)
64                : "a" (eax), "d" (edx), "c" (reg), "i" (-EIO), "0" (0));
65
66   return err;
67 }
68
69 static inline int rdmsr_eio(u32 reg, u32 *eax, u32 *edx)
70 {
71   int err;
72
73   asm volatile(
74                "1:      rdmsr\n"
75                "2:\n"
76                ".section .fixup,\"ax\"\n"
77                "3:      movl %4,%0\n"
78                "        jmp 2b\n"
79                ".previous\n"
80                ".section __ex_table,\"a\"\n"
81                "        .align 8\n"
82                "        .quad 1b,3b\n"
83                ".previous"
84                : "=&bDS" (err), "=a" (*eax), "=d" (*edx)
85                : "c" (reg), "i" (-EIO), "0" (0));
86
87   return err;
88 }
89
90 #ifdef CONFIG_SMP
91
92 struct msr_command {
93   int cpu;
94   int err;
95   u32 reg;
96   u32 data[2];
97 };
98
99 static void msr_smp_wrmsr(void *cmd_block)
100 {
101   struct msr_command *cmd = (struct msr_command *) cmd_block;
102   
103   if ( cmd->cpu == smp_processor_id() )
104     cmd->err = wrmsr_eio(cmd->reg, cmd->data[0], cmd->data[1]);
105 }
106
107 static void msr_smp_rdmsr(void *cmd_block)
108 {
109   struct msr_command *cmd = (struct msr_command *) cmd_block;
110   
111   if ( cmd->cpu == smp_processor_id() )
112     cmd->err = rdmsr_eio(cmd->reg, &cmd->data[0], &cmd->data[1]);
113 }
114
115 static inline int do_wrmsr(int cpu, u32 reg, u32 eax, u32 edx)
116 {
117   struct msr_command cmd;
118   int ret;
119
120   preempt_disable();
121   if ( cpu == smp_processor_id() ) {
122     ret = wrmsr_eio(reg, eax, edx);
123   } else {
124     cmd.cpu = cpu;
125     cmd.reg = reg;
126     cmd.data[0] = eax;
127     cmd.data[1] = edx;
128     
129     smp_call_function(msr_smp_wrmsr, &cmd, 1, 1);
130     ret = cmd.err;
131   }
132   preempt_enable();
133   return ret;
134 }
135
136 static inline int do_rdmsr(int cpu, u32 reg, u32 *eax, u32 *edx)
137 {
138   struct msr_command cmd;
139   int ret;
140
141   preempt_disable();
142   if ( cpu == smp_processor_id() ) {
143     ret = rdmsr_eio(reg, eax, edx);
144   } else {
145     cmd.cpu = cpu;
146     cmd.reg = reg;
147
148     smp_call_function(msr_smp_rdmsr, &cmd, 1, 1);
149     
150     *eax = cmd.data[0];
151     *edx = cmd.data[1];
152
153     ret = cmd.err;
154   }
155   preempt_enable();
156   return ret;
157 }
158
159 #else /* ! CONFIG_SMP */
160
161 static inline int do_wrmsr(int cpu, u32 reg, u32 eax, u32 edx)
162 {
163   return wrmsr_eio(reg, eax, edx);
164 }
165
166 static inline int do_rdmsr(int cpu, u32 reg, u32 *eax, u32 *edx)
167 {
168   return rdmsr_eio(reg, eax, edx);
169 }
170
171 #endif /* ! CONFIG_SMP */
172
173 static loff_t msr_seek(struct file *file, loff_t offset, int orig)
174 {
175   loff_t ret = -EINVAL;
176   lock_kernel();
177   switch (orig) {
178   case 0:
179     file->f_pos = offset;
180     ret = file->f_pos;
181     break;
182   case 1:
183     file->f_pos += offset;
184     ret = file->f_pos;
185   }
186   unlock_kernel();
187   return ret;
188 }
189
190 static ssize_t msr_read(struct file * file, char * buf,
191                         size_t count, loff_t *ppos)
192 {
193   u32 *tmp = (u32 *)buf;
194   u32 data[2];
195   size_t rv;
196   u32 reg = *ppos;
197   int cpu = iminor(file->f_dentry->d_inode);
198   int err;
199
200   if ( count % 8 )
201     return -EINVAL; /* Invalid chunk size */
202   
203   for ( rv = 0 ; count ; count -= 8 ) {
204     err = do_rdmsr(cpu, reg, &data[0], &data[1]);
205     if ( err )
206       return err;
207     if ( copy_to_user(tmp,&data,8) )
208       return -EFAULT;
209     tmp += 2;
210   }
211
212   return ((char *)tmp) - buf;
213 }
214
215 static ssize_t msr_write(struct file * file, const char * buf,
216                          size_t count, loff_t *ppos)
217 {
218   const u32 *tmp = (const u32 *)buf;
219   u32 data[2];
220   size_t rv;
221   u32 reg = *ppos;
222   int cpu = iminor(file->f_dentry->d_inode);
223   int err;
224
225   if ( count % 8 )
226     return -EINVAL; /* Invalid chunk size */
227   
228   for ( rv = 0 ; count ; count -= 8 ) {
229     if ( copy_from_user(&data,tmp,8) )
230       return -EFAULT;
231     err = do_wrmsr(cpu, reg, data[0], data[1]);
232     if ( err )
233       return err;
234     tmp += 2;
235   }
236
237   return ((char *)tmp) - buf;
238 }
239
240 static int msr_open(struct inode *inode, struct file *file)
241 {
242   int cpu = iminor(file->f_dentry->d_inode);
243   struct cpuinfo_x86 *c = &(cpu_data)[cpu];
244   
245   if (!cpu_online(cpu))
246     return -ENXIO;              /* No such CPU */
247   if ( !cpu_has(c, X86_FEATURE_MSR) )
248     return -EIO;                /* MSR not supported */
249   
250   return 0;
251 }
252
253 /*
254  * File operations we support
255  */
256 static struct file_operations msr_fops = {
257   .owner =      THIS_MODULE,
258   .llseek =     msr_seek,
259   .read =       msr_read,
260   .write =      msr_write,
261   .open =       msr_open,
262 };
263
264 int __init msr_init(void)
265 {
266   if (register_chrdev(MSR_MAJOR, "cpu/msr", &msr_fops)) {
267     printk(KERN_ERR "msr: unable to get major %d for msr\n",
268            MSR_MAJOR);
269     return -EBUSY;
270   }
271   
272   return 0;
273 }
274
275 void __exit msr_exit(void)
276 {
277   unregister_chrdev(MSR_MAJOR, "cpu/msr");
278 }
279
280 module_init(msr_init);
281 module_exit(msr_exit)
282
283 MODULE_AUTHOR("H. Peter Anvin <hpa@zytor.com>");
284 MODULE_DESCRIPTION("x86 generic MSR driver");
285 MODULE_LICENSE("GPL");