aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/microcode_amd.c
diff options
context:
space:
mode:
authorPeter Oruba <peter.oruba@amd.com>2008-07-28 12:44:22 -0400
committerIngo Molnar <mingo@elte.hu>2008-07-28 13:57:58 -0400
commit80cc9f1020f49c9e5b50898c102fd444de70a0a3 (patch)
tree6e8cfe33c4f09a129b168a7f831c6dcc81f6c4ea /arch/x86/kernel/microcode_amd.c
parent8d86f390d9bb5b39f0a315838d1616de6363e1b9 (diff)
x86: AMD microcode patch loading support
This patch introduces microcode patch loading for AMD processors. It is based on previous corresponding work for Intel processors. It hooks into the general patch loading module. Main difference is that a container file format is used to hold all patch data for multiple processors as well as an equivalent CPU table, which comes seperately, as opposed to Intel's microcode patching solution. Kconfig and Makefile have been changed provice config and build option for new source file. Signed-off-by: Peter Oruba <peter.oruba@amd.com> Cc: Tigran Aivazian <tigran@aivazian.fsnet.co.uk> Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch/x86/kernel/microcode_amd.c')
-rw-r--r--arch/x86/kernel/microcode_amd.c521
1 files changed, 521 insertions, 0 deletions
diff --git a/arch/x86/kernel/microcode_amd.c b/arch/x86/kernel/microcode_amd.c
new file mode 100644
index 000000000000..db199e3fdd1f
--- /dev/null
+++ b/arch/x86/kernel/microcode_amd.c
@@ -0,0 +1,521 @@
1/*
2 * AMD CPU Microcode Update Driver for Linux
3 * Copyright (C) 2008 Advanced Micro Devices Inc.
4 *
5 * Author: Peter Oruba <peter.oruba@amd.com>
6 *
7 * Based on work by:
8 * Tigran Aivazian <tigran@aivazian.fsnet.co.uk>
9 *
10 * This driver allows to upgrade microcode on AMD
11 * family 0x10 and 0x11 processors.
12 *
13 * Licensed unter the terms of the GNU General Public
14 * License version 2. See file COPYING for details.
15*/
16
17#include <linux/capability.h>
18#include <linux/kernel.h>
19#include <linux/init.h>
20#include <linux/sched.h>
21#include <linux/cpumask.h>
22#include <linux/module.h>
23#include <linux/slab.h>
24#include <linux/vmalloc.h>
25#include <linux/miscdevice.h>
26#include <linux/spinlock.h>
27#include <linux/mm.h>
28#include <linux/fs.h>
29#include <linux/mutex.h>
30#include <linux/cpu.h>
31#include <linux/firmware.h>
32#include <linux/platform_device.h>
33#include <linux/pci.h>
34#include <linux/pci_ids.h>
35
36#include <asm/msr.h>
37#include <asm/uaccess.h>
38#include <asm/processor.h>
39#include <asm/microcode.h>
40
41MODULE_DESCRIPTION("AMD Microcode Update Driver");
42MODULE_AUTHOR("Peter Oruba <peter.oruba@amd.com>");
43MODULE_LICENSE("GPLv2");
44
45#define UCODE_MAGIC 0x00414d44
46#define UCODE_EQUIV_CPU_TABLE_TYPE 0x00000000
47#define UCODE_UCODE_TYPE 0x00000001
48
49#define UCODE_MAX_SIZE (2048)
50#define DEFAULT_UCODE_DATASIZE (896) /* 896 bytes */
51#define MC_HEADER_SIZE (sizeof(struct microcode_header_amd)) /* 64 bytes */
52#define DEFAULT_UCODE_TOTALSIZE (DEFAULT_UCODE_DATASIZE + MC_HEADER_SIZE) /* 960 bytes */
53#define DWSIZE (sizeof(u32))
54/* For now we support a fixed ucode total size only */
55#define get_totalsize(mc) \
56 ((((struct microcode_amd *)mc)->hdr.mc_patch_data_len * 28) \
57 + MC_HEADER_SIZE)
58
59extern int microcode_init(void *opaque, struct module *module);
60extern void microcode_exit(void);
61
62/* serialize access to the physical write */
63static DEFINE_SPINLOCK(microcode_update_lock);
64
65/* no concurrent ->write()s are allowed on /dev/cpu/microcode */
66extern struct mutex (microcode_mutex);
67
68struct equiv_cpu_entry *equiv_cpu_table;
69
70extern struct ucode_cpu_info ucode_cpu_info[NR_CPUS];
71
72static void collect_cpu_info_amd(int cpu)
73{
74 struct cpuinfo_x86 *c = &cpu_data(cpu);
75 struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
76
77 /* We should bind the task to the CPU */
78 BUG_ON(raw_smp_processor_id() != cpu);
79 uci->rev = 0;
80 uci->pf = 0;
81 uci->mc.mc_amd = NULL;
82 uci->valid = 1;
83
84 if (c->x86_vendor != X86_VENDOR_AMD || c->x86 < 0x10) {
85 printk(KERN_ERR "microcode: CPU%d not a capable AMD processor\n",
86 cpu);
87 uci->valid = 0;
88 return;
89 }
90
91 asm volatile("movl %1, %%ecx; rdmsr"
92 : "=a" (uci->rev)
93 : "i" (0x0000008B) : "ecx");
94
95 printk(KERN_INFO "microcode: collect_cpu_info_amd : patch_id=0x%x\n",
96 uci->rev);
97}
98
99static int get_matching_microcode_amd(void *mc, int cpu)
100{
101 struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
102 struct microcode_header_amd *mc_header = mc;
103 unsigned long total_size = get_totalsize(mc_header);
104 void *new_mc;
105 struct pci_dev *nb_pci_dev, *sb_pci_dev;
106 unsigned int current_cpu_id;
107 unsigned int equiv_cpu_id = 0x00;
108 unsigned int i = 0;
109
110 /* We should bind the task to the CPU */
111 BUG_ON(cpu != raw_smp_processor_id());
112
113 /* This is a tricky part. We might be called from a write operation */
114 /* to the device file instead of the usual process of firmware */
115 /* loading. This routine needs to be able to distinguish both */
116/* cases. This is done by checking if there alread is a equivalent */
117 /* CPU table installed. If not, we're written through */
118 /* /dev/cpu/microcode. */
119/* Since we ignore all checks. The error case in which going through */
120/* firmware loading and that table is not loaded has already been */
121 /* checked earlier. */
122 if (equiv_cpu_table == NULL) {
123 printk(KERN_INFO "microcode: CPU%d microcode update with "
124 "version 0x%x (current=0x%x)\n",
125 cpu, mc_header->patch_id, uci->rev);
126 goto out;
127 }
128
129 current_cpu_id = cpuid_eax(0x00000001);
130
131 while (equiv_cpu_table[i].installed_cpu != 0) {
132 if (current_cpu_id == equiv_cpu_table[i].installed_cpu) {
133 equiv_cpu_id = equiv_cpu_table[i].equiv_cpu;
134 break;
135 }
136 i++;
137 }
138
139 if (!equiv_cpu_id) {
140 printk(KERN_ERR "microcode: CPU%d cpu_id "
141 "not found in equivalent cpu table \n", cpu);
142 return 0;
143 }
144
145 if ((mc_header->processor_rev_id[0]) != (equiv_cpu_id & 0xff)) {
146 printk(KERN_ERR
147 "microcode: CPU%d patch does not match "
148 "(patch is %x, cpu extended is %x) \n",
149 cpu, mc_header->processor_rev_id[0],
150 (equiv_cpu_id & 0xff));
151 return 0;
152 }
153
154 if ((mc_header->processor_rev_id[1]) != ((equiv_cpu_id >> 16) & 0xff)) {
155 printk(KERN_ERR "microcode: CPU%d patch does not match "
156 "(patch is %x, cpu base id is %x) \n",
157 cpu, mc_header->processor_rev_id[1],
158 ((equiv_cpu_id >> 16) & 0xff));
159
160 return 0;
161 }
162
163 /* ucode may be northbridge specific */
164 if (mc_header->nb_dev_id) {
165 nb_pci_dev = pci_get_device(PCI_VENDOR_ID_AMD,
166 (mc_header->nb_dev_id & 0xff),
167 NULL);
168 if ((!nb_pci_dev) ||
169 (mc_header->nb_rev_id != nb_pci_dev->revision)) {
170 printk(KERN_ERR "microcode: CPU%d NB mismatch \n", cpu);
171 pci_dev_put(nb_pci_dev);
172 return 0;
173 }
174 pci_dev_put(nb_pci_dev);
175 }
176
177 /* ucode may be southbridge specific */
178 if (mc_header->sb_dev_id) {
179 sb_pci_dev = pci_get_device(PCI_VENDOR_ID_AMD,
180 (mc_header->sb_dev_id & 0xff),
181 NULL);
182 if ((!sb_pci_dev) ||
183 (mc_header->sb_rev_id != sb_pci_dev->revision)) {
184 printk(KERN_ERR "microcode: CPU%d SB mismatch \n", cpu);
185 pci_dev_put(sb_pci_dev);
186 return 0;
187 }
188 pci_dev_put(sb_pci_dev);
189 }
190
191 if (mc_header->patch_id <= uci->rev)
192 return 0;
193
194 printk(KERN_INFO "microcode: CPU%d found a matching microcode "
195 "update with version 0x%x (current=0x%x)\n",
196 cpu, mc_header->patch_id, uci->rev);
197
198out:
199 new_mc = vmalloc(UCODE_MAX_SIZE);
200 if (!new_mc) {
201 printk(KERN_ERR "microcode: error, can't allocate memory\n");
202 return -ENOMEM;
203 }
204 memset(new_mc, 0, UCODE_MAX_SIZE);
205
206 /* free previous update file */
207 vfree(uci->mc.mc_amd);
208
209 memcpy(new_mc, mc, total_size);
210
211 uci->mc.mc_amd = new_mc;
212 return 1;
213}
214
215static void apply_microcode_amd(int cpu)
216{
217 unsigned long flags;
218 unsigned int eax, edx;
219 unsigned int rev;
220 int cpu_num = raw_smp_processor_id();
221 struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
222
223 /* We should bind the task to the CPU */
224 BUG_ON(cpu_num != cpu);
225
226 if (uci->mc.mc_amd == NULL)
227 return;
228
229 spin_lock_irqsave(&microcode_update_lock, flags);
230
231 edx = (unsigned int)(((unsigned long)
232 &(uci->mc.mc_amd->hdr.data_code)) >> 32);
233 eax = (unsigned int)(((unsigned long)
234 &(uci->mc.mc_amd->hdr.data_code)) & 0xffffffffL);
235
236 asm volatile("movl %0, %%ecx; wrmsr" :
237 : "i" (0xc0010020), "a" (eax), "d" (edx) : "ecx");
238
239 /* get patch id after patching */
240 asm volatile("movl %1, %%ecx; rdmsr"
241 : "=a" (rev)
242 : "i" (0x0000008B) : "ecx");
243
244 spin_unlock_irqrestore(&microcode_update_lock, flags);
245
246 /* check current patch id and patch's id for match */
247 if (rev != uci->mc.mc_amd->hdr.patch_id) {
248 printk(KERN_ERR "microcode: CPU%d update from revision "
249 "0x%x to 0x%x failed\n", cpu_num,
250 uci->mc.mc_amd->hdr.patch_id, rev);
251 return;
252 }
253
254 printk(KERN_INFO "microcode: CPU%d updated from revision "
255 "0x%x to 0x%x \n",
256 cpu_num, uci->rev, uci->mc.mc_amd->hdr.patch_id);
257
258 uci->rev = rev;
259}
260
261#ifdef CONFIG_MICROCODE_OLD_INTERFACE
262extern void __user *user_buffer; /* user area microcode data buffer */
263extern unsigned int user_buffer_size; /* it's size */
264
265static long get_next_ucode_amd(void **mc, long offset)
266{
267 struct microcode_header_amd mc_header;
268 unsigned long total_size;
269
270 /* No more data */
271 if (offset >= user_buffer_size)
272 return 0;
273 if (copy_from_user(&mc_header, user_buffer + offset, MC_HEADER_SIZE)) {
274 printk(KERN_ERR "microcode: error! Can not read user data\n");
275 return -EFAULT;
276 }
277 total_size = get_totalsize(&mc_header);
278 if (offset + total_size > user_buffer_size) {
279 printk(KERN_ERR "microcode: error! Bad total size in microcode "
280 "data file\n");
281 return -EINVAL;
282 }
283 *mc = vmalloc(UCODE_MAX_SIZE);
284 if (!*mc)
285 return -ENOMEM;
286 memset(*mc, 0, UCODE_MAX_SIZE);
287
288 if (copy_from_user(*mc, user_buffer + offset, total_size)) {
289 printk(KERN_ERR "microcode: error! Can not read user data\n");
290 vfree(*mc);
291 return -EFAULT;
292 }
293 return offset + total_size;
294}
295#else
296#define get_next_ucode_amd() NULL
297#endif
298
299static long get_next_ucode_from_buffer_amd(void **mc, void *buf,
300 unsigned long size, long offset)
301{
302 struct microcode_header_amd *mc_header;
303 unsigned long total_size;
304 unsigned char *buf_pos = buf;
305
306 /* No more data */
307 if (offset >= size)
308 return 0;
309
310 if (buf_pos[offset] != UCODE_UCODE_TYPE) {
311 printk(KERN_ERR "microcode: error! "
312 "Wrong microcode payload type field\n");
313 return -EINVAL;
314 }
315
316 mc_header = (struct microcode_header_amd *)(&buf_pos[offset+8]);
317
318 total_size = (unsigned long) (buf_pos[offset+4] +
319 (buf_pos[offset+5] << 8));
320
321 printk(KERN_INFO "microcode: size %lu, total_size %lu, offset %ld\n",
322 size, total_size, offset);
323
324 if (offset + total_size > size) {
325 printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
326 return -EINVAL;
327 }
328
329 *mc = vmalloc(UCODE_MAX_SIZE);
330 if (!*mc) {
331 printk(KERN_ERR "microcode: error! "
332 "Can not allocate memory for microcode patch\n");
333 return -ENOMEM;
334 }
335
336 memset(*mc, 0, UCODE_MAX_SIZE);
337 memcpy(*mc, buf + offset + 8, total_size);
338
339 return offset + total_size + 8;
340}
341
342static long install_equiv_cpu_table(void *buf, unsigned long size, long offset)
343{
344 unsigned int *buf_pos = buf;
345
346 /* No more data */
347 if (offset >= size)
348 return 0;
349
350 if (buf_pos[1] != UCODE_EQUIV_CPU_TABLE_TYPE) {
351 printk(KERN_ERR "microcode: error! "
352 "Wrong microcode equivalnet cpu table type field\n");
353 return 0;
354 }
355
356 if (size == 0) {
357 printk(KERN_ERR "microcode: error! "
358 "Wrong microcode equivalnet cpu table length\n");
359 return 0;
360 }
361
362 equiv_cpu_table = (struct equiv_cpu_entry *) vmalloc(size);
363 if (!equiv_cpu_table) {
364 printk(KERN_ERR "microcode: error, can't allocate memory for equiv CPU table\n");
365 return 0;
366 }
367
368 memset(equiv_cpu_table, 0, size);
369 memcpy(equiv_cpu_table, &buf_pos[3], size);
370
371 return size + 12; /* add header length */
372}
373
374/* fake device for request_firmware */
375extern struct platform_device *microcode_pdev;
376
377static int cpu_request_microcode_amd(int cpu)
378{
379 char name[30];
380 const struct firmware *firmware;
381 void *buf;
382 unsigned int *buf_pos;
383 unsigned long size;
384 long offset = 0;
385 int error;
386 void *mc;
387
388 /* We should bind the task to the CPU */
389 BUG_ON(cpu != raw_smp_processor_id());
390
391 sprintf(name, "amd-ucode/microcode_amd.bin");
392 error = request_firmware(&firmware, "amd-ucode/microcode_amd.bin",
393 &microcode_pdev->dev);
394 if (error) {
395 printk(KERN_ERR "microcode: ucode data file %s load failed\n",
396 name);
397 return error;
398 }
399
400 buf_pos = buf = firmware->data;
401 size = firmware->size;
402
403 if (buf_pos[0] != UCODE_MAGIC) {
404 printk(KERN_ERR "microcode: error! Wrong microcode patch file magic\n");
405 return -EINVAL;
406 }
407
408 offset = install_equiv_cpu_table(buf, buf_pos[2], offset);
409
410 if (!offset) {
411 printk(KERN_ERR "microcode: installing equivalent cpu table failed\n");
412 return -EINVAL;
413 }
414
415 while ((offset =
416 get_next_ucode_from_buffer_amd(&mc, buf, size, offset)) > 0) {
417 error = get_matching_microcode_amd(mc, cpu);
418 if (error < 0)
419 break;
420 /*
421 * It's possible the data file has multiple matching ucode,
422 * lets keep searching till the latest version
423 */
424 if (error == 1) {
425 apply_microcode_amd(cpu);
426 error = 0;
427 }
428 vfree(mc);
429 }
430 if (offset > 0) {
431 vfree(mc);
432 vfree(equiv_cpu_table);
433 equiv_cpu_table = NULL;
434 }
435 if (offset < 0)
436 error = offset;
437 release_firmware(firmware);
438
439 return error;
440}
441
442static int apply_microcode_check_cpu_amd(int cpu)
443{
444 struct cpuinfo_x86 *c = &cpu_data(cpu);
445 struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
446 unsigned int rev;
447 cpumask_t old;
448 cpumask_of_cpu_ptr(newmask, cpu);
449 int err = 0;
450
451 /* Check if the microcode is available */
452 if (!uci->mc.mc_amd)
453 return 0;
454
455 old = current->cpus_allowed;
456 set_cpus_allowed(current, newmask);
457
458 /* Check if the microcode we have in memory matches the CPU */
459 if (c->x86_vendor != X86_VENDOR_AMD || c->x86 < 16)
460 err = -EINVAL;
461
462 if (!err) {
463 asm volatile("movl %1, %%ecx; rdmsr"
464 : "=a" (rev)
465 : "i" (0x0000008B) : "ecx");
466
467 if (uci->rev != rev)
468 err = -EINVAL;
469 }
470
471 if (!err)
472 apply_microcode_amd(cpu);
473 else
474 printk(KERN_ERR "microcode: Could not apply microcode to CPU%d:"
475 " rev=0x%x\n",
476 cpu, uci->rev);
477
478 set_cpus_allowed(current, old);
479 return err;
480}
481
482static void microcode_fini_cpu_amd(int cpu)
483{
484 struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
485
486 mutex_lock(&microcode_mutex);
487 uci->valid = 0;
488 vfree(uci->mc.mc_amd);
489 uci->mc.mc_amd = NULL;
490 mutex_unlock(&microcode_mutex);
491}
492
493static struct microcode_ops microcode_amd_ops = {
494 .get_next_ucode = get_next_ucode_amd,
495 .get_matching_microcode = get_matching_microcode_amd,
496 .microcode_sanity_check = NULL,
497 .apply_microcode_check_cpu = apply_microcode_check_cpu_amd,
498 .cpu_request_microcode = cpu_request_microcode_amd,
499 .collect_cpu_info = collect_cpu_info_amd,
500 .apply_microcode = apply_microcode_amd,
501 .microcode_fini_cpu = microcode_fini_cpu_amd,
502};
503
504static int __init microcode_amd_module_init(void)
505{
506 struct cpuinfo_x86 *c = &cpu_data(get_cpu());
507
508 equiv_cpu_table = NULL;
509 if (c->x86_vendor == X86_VENDOR_AMD)
510 return microcode_init(&microcode_amd_ops, THIS_MODULE);
511 else
512 return -ENODEV;
513}
514
515static void __exit microcode_amd_module_exit(void)
516{
517 microcode_exit();
518}
519
520module_init(microcode_amd_module_init)
521module_exit(microcode_amd_module_exit)