Commit | Line | Data |
---|---|---|
80cc9f10 PO |
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 | ||
41 | MODULE_DESCRIPTION("AMD Microcode Update Driver"); | |
3c52204b | 42 | MODULE_AUTHOR("Peter Oruba"); |
5d7b6052 | 43 | MODULE_LICENSE("GPL v2"); |
80cc9f10 PO |
44 | |
45 | #define UCODE_MAGIC 0x00414d44 | |
46 | #define UCODE_EQUIV_CPU_TABLE_TYPE 0x00000000 | |
47 | #define UCODE_UCODE_TYPE 0x00000001 | |
48 | ||
18dbc916 DA |
49 | struct equiv_cpu_entry { |
50 | unsigned int installed_cpu; | |
51 | unsigned int fixed_errata_mask; | |
52 | unsigned int fixed_errata_compare; | |
53 | unsigned int equiv_cpu; | |
54 | }; | |
55 | ||
56 | struct microcode_header_amd { | |
57 | unsigned int data_code; | |
58 | unsigned int patch_id; | |
59 | unsigned char mc_patch_data_id[2]; | |
60 | unsigned char mc_patch_data_len; | |
61 | unsigned char init_flag; | |
62 | unsigned int mc_patch_data_checksum; | |
63 | unsigned int nb_dev_id; | |
64 | unsigned int sb_dev_id; | |
3c763fd7 | 65 | u16 processor_rev_id; |
18dbc916 DA |
66 | unsigned char nb_rev_id; |
67 | unsigned char sb_rev_id; | |
68 | unsigned char bios_api_rev; | |
69 | unsigned char reserved1[3]; | |
70 | unsigned int match_reg[8]; | |
71 | }; | |
72 | ||
73 | struct microcode_amd { | |
74 | struct microcode_header_amd hdr; | |
75 | unsigned int mpb[0]; | |
76 | }; | |
77 | ||
80cc9f10 | 78 | #define UCODE_MAX_SIZE (2048) |
a0ac87d6 PO |
79 | #define DEFAULT_UCODE_DATASIZE (896) |
80 | #define MC_HEADER_SIZE (sizeof(struct microcode_header_amd)) | |
81 | #define DEFAULT_UCODE_TOTALSIZE (DEFAULT_UCODE_DATASIZE + MC_HEADER_SIZE) | |
80cc9f10 PO |
82 | #define DWSIZE (sizeof(u32)) |
83 | /* For now we support a fixed ucode total size only */ | |
84 | #define get_totalsize(mc) \ | |
85 | ((((struct microcode_amd *)mc)->hdr.mc_patch_data_len * 28) \ | |
86 | + MC_HEADER_SIZE) | |
87 | ||
80cc9f10 PO |
88 | /* serialize access to the physical write */ |
89 | static DEFINE_SPINLOCK(microcode_update_lock); | |
90 | ||
a0a29b62 | 91 | static struct equiv_cpu_entry *equiv_cpu_table; |
80cc9f10 | 92 | |
d45de409 | 93 | static int collect_cpu_info_amd(int cpu, struct cpu_signature *csig) |
80cc9f10 PO |
94 | { |
95 | struct cpuinfo_x86 *c = &cpu_data(cpu); | |
80cc9f10 | 96 | |
d45de409 | 97 | memset(csig, 0, sizeof(*csig)); |
80cc9f10 PO |
98 | |
99 | if (c->x86_vendor != X86_VENDOR_AMD || c->x86 < 0x10) { | |
100 | printk(KERN_ERR "microcode: CPU%d not a capable AMD processor\n", | |
101 | cpu); | |
d45de409 | 102 | return -1; |
80cc9f10 PO |
103 | } |
104 | ||
105 | asm volatile("movl %1, %%ecx; rdmsr" | |
d45de409 | 106 | : "=a" (csig->rev) |
80cc9f10 PO |
107 | : "i" (0x0000008B) : "ecx"); |
108 | ||
109 | printk(KERN_INFO "microcode: collect_cpu_info_amd : patch_id=0x%x\n", | |
d45de409 DA |
110 | csig->rev); |
111 | ||
112 | return 0; | |
80cc9f10 PO |
113 | } |
114 | ||
a0a29b62 | 115 | static int get_matching_microcode(int cpu, void *mc, int rev) |
80cc9f10 | 116 | { |
80cc9f10 | 117 | struct microcode_header_amd *mc_header = mc; |
80cc9f10 PO |
118 | struct pci_dev *nb_pci_dev, *sb_pci_dev; |
119 | unsigned int current_cpu_id; | |
120 | unsigned int equiv_cpu_id = 0x00; | |
121 | unsigned int i = 0; | |
122 | ||
a0a29b62 | 123 | BUG_ON(equiv_cpu_table == NULL); |
80cc9f10 PO |
124 | current_cpu_id = cpuid_eax(0x00000001); |
125 | ||
126 | while (equiv_cpu_table[i].installed_cpu != 0) { | |
127 | if (current_cpu_id == equiv_cpu_table[i].installed_cpu) { | |
3c763fd7 | 128 | equiv_cpu_id = equiv_cpu_table[i].equiv_cpu & 0xffff; |
80cc9f10 PO |
129 | break; |
130 | } | |
131 | i++; | |
132 | } | |
133 | ||
134 | if (!equiv_cpu_id) { | |
135 | printk(KERN_ERR "microcode: CPU%d cpu_id " | |
136 | "not found in equivalent cpu table \n", cpu); | |
137 | return 0; | |
138 | } | |
139 | ||
3c763fd7 AH |
140 | if (mc_header->processor_rev_id != equiv_cpu_id) { |
141 | printk(KERN_ERR "microcode: CPU%d patch does not match " | |
142 | "(processor_rev_id: %x, eqiv_cpu_id: %x)\n", | |
143 | cpu, mc_header->processor_rev_id, equiv_cpu_id); | |
80cc9f10 PO |
144 | return 0; |
145 | } | |
146 | ||
147 | /* ucode may be northbridge specific */ | |
148 | if (mc_header->nb_dev_id) { | |
149 | nb_pci_dev = pci_get_device(PCI_VENDOR_ID_AMD, | |
150 | (mc_header->nb_dev_id & 0xff), | |
151 | NULL); | |
152 | if ((!nb_pci_dev) || | |
153 | (mc_header->nb_rev_id != nb_pci_dev->revision)) { | |
154 | printk(KERN_ERR "microcode: CPU%d NB mismatch \n", cpu); | |
155 | pci_dev_put(nb_pci_dev); | |
156 | return 0; | |
157 | } | |
158 | pci_dev_put(nb_pci_dev); | |
159 | } | |
160 | ||
161 | /* ucode may be southbridge specific */ | |
162 | if (mc_header->sb_dev_id) { | |
163 | sb_pci_dev = pci_get_device(PCI_VENDOR_ID_AMD, | |
164 | (mc_header->sb_dev_id & 0xff), | |
165 | NULL); | |
166 | if ((!sb_pci_dev) || | |
167 | (mc_header->sb_rev_id != sb_pci_dev->revision)) { | |
168 | printk(KERN_ERR "microcode: CPU%d SB mismatch \n", cpu); | |
169 | pci_dev_put(sb_pci_dev); | |
170 | return 0; | |
171 | } | |
172 | pci_dev_put(sb_pci_dev); | |
173 | } | |
174 | ||
a0a29b62 | 175 | if (mc_header->patch_id <= rev) |
80cc9f10 PO |
176 | return 0; |
177 | ||
80cc9f10 PO |
178 | return 1; |
179 | } | |
180 | ||
181 | static void apply_microcode_amd(int cpu) | |
182 | { | |
183 | unsigned long flags; | |
184 | unsigned int eax, edx; | |
185 | unsigned int rev; | |
186 | int cpu_num = raw_smp_processor_id(); | |
187 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num; | |
18dbc916 | 188 | struct microcode_amd *mc_amd = uci->mc; |
5b792d32 | 189 | unsigned long addr; |
80cc9f10 PO |
190 | |
191 | /* We should bind the task to the CPU */ | |
192 | BUG_ON(cpu_num != cpu); | |
193 | ||
18dbc916 | 194 | if (mc_amd == NULL) |
80cc9f10 PO |
195 | return; |
196 | ||
197 | spin_lock_irqsave(µcode_update_lock, flags); | |
198 | ||
18dbc916 | 199 | addr = (unsigned long)&mc_amd->hdr.data_code; |
5b792d32 RD |
200 | edx = (unsigned int)(((unsigned long)upper_32_bits(addr))); |
201 | eax = (unsigned int)(((unsigned long)lower_32_bits(addr))); | |
80cc9f10 PO |
202 | |
203 | asm volatile("movl %0, %%ecx; wrmsr" : | |
204 | : "i" (0xc0010020), "a" (eax), "d" (edx) : "ecx"); | |
205 | ||
206 | /* get patch id after patching */ | |
207 | asm volatile("movl %1, %%ecx; rdmsr" | |
208 | : "=a" (rev) | |
209 | : "i" (0x0000008B) : "ecx"); | |
210 | ||
211 | spin_unlock_irqrestore(µcode_update_lock, flags); | |
212 | ||
213 | /* check current patch id and patch's id for match */ | |
18dbc916 | 214 | if (rev != mc_amd->hdr.patch_id) { |
80cc9f10 PO |
215 | printk(KERN_ERR "microcode: CPU%d update from revision " |
216 | "0x%x to 0x%x failed\n", cpu_num, | |
18dbc916 | 217 | mc_amd->hdr.patch_id, rev); |
80cc9f10 PO |
218 | return; |
219 | } | |
220 | ||
221 | printk(KERN_INFO "microcode: CPU%d updated from revision " | |
222 | "0x%x to 0x%x \n", | |
18dbc916 | 223 | cpu_num, uci->cpu_sig.rev, mc_amd->hdr.patch_id); |
80cc9f10 | 224 | |
d45de409 | 225 | uci->cpu_sig.rev = rev; |
80cc9f10 PO |
226 | } |
227 | ||
a0a29b62 DA |
228 | static void * get_next_ucode(u8 *buf, unsigned int size, |
229 | int (*get_ucode_data)(void *, const void *, size_t), | |
230 | unsigned int *mc_size) | |
80cc9f10 | 231 | { |
a0a29b62 | 232 | unsigned int total_size; |
d4738792 PO |
233 | #define UCODE_CONTAINER_SECTION_HDR 8 |
234 | u8 section_hdr[UCODE_CONTAINER_SECTION_HDR]; | |
a0a29b62 | 235 | void *mc; |
80cc9f10 | 236 | |
d4738792 | 237 | if (get_ucode_data(section_hdr, buf, UCODE_CONTAINER_SECTION_HDR)) |
a0a29b62 | 238 | return NULL; |
80cc9f10 | 239 | |
d4738792 | 240 | if (section_hdr[0] != UCODE_UCODE_TYPE) { |
80cc9f10 PO |
241 | printk(KERN_ERR "microcode: error! " |
242 | "Wrong microcode payload type field\n"); | |
a0a29b62 | 243 | return NULL; |
80cc9f10 PO |
244 | } |
245 | ||
d4738792 | 246 | total_size = (unsigned long) (section_hdr[4] + (section_hdr[5] << 8)); |
80cc9f10 | 247 | |
a0a29b62 DA |
248 | printk(KERN_INFO "microcode: size %u, total_size %u\n", |
249 | size, total_size); | |
80cc9f10 | 250 | |
a0a29b62 | 251 | if (total_size > size || total_size > UCODE_MAX_SIZE) { |
80cc9f10 | 252 | printk(KERN_ERR "microcode: error! Bad data in microcode data file\n"); |
a0a29b62 | 253 | return NULL; |
80cc9f10 PO |
254 | } |
255 | ||
a0a29b62 DA |
256 | mc = vmalloc(UCODE_MAX_SIZE); |
257 | if (mc) { | |
258 | memset(mc, 0, UCODE_MAX_SIZE); | |
d4738792 | 259 | if (get_ucode_data(mc, buf + UCODE_CONTAINER_SECTION_HDR, total_size)) { |
a0a29b62 DA |
260 | vfree(mc); |
261 | mc = NULL; | |
262 | } else | |
d4738792 | 263 | *mc_size = total_size + UCODE_CONTAINER_SECTION_HDR; |
80cc9f10 | 264 | } |
d4738792 | 265 | #undef UCODE_CONTAINER_SECTION_HDR |
a0a29b62 | 266 | return mc; |
80cc9f10 PO |
267 | } |
268 | ||
a0a29b62 DA |
269 | |
270 | static int install_equiv_cpu_table(u8 *buf, | |
271 | int (*get_ucode_data)(void *, const void *, size_t)) | |
80cc9f10 | 272 | { |
b6cffde1 PO |
273 | #define UCODE_CONTAINER_HEADER_SIZE 12 |
274 | u8 *container_hdr[UCODE_CONTAINER_HEADER_SIZE]; | |
275 | unsigned int *buf_pos = (unsigned int *)container_hdr; | |
a0a29b62 | 276 | unsigned long size; |
80cc9f10 | 277 | |
b6cffde1 | 278 | if (get_ucode_data(&container_hdr, buf, UCODE_CONTAINER_HEADER_SIZE)) |
80cc9f10 PO |
279 | return 0; |
280 | ||
a0a29b62 | 281 | size = buf_pos[2]; |
80cc9f10 | 282 | |
a0a29b62 | 283 | if (buf_pos[1] != UCODE_EQUIV_CPU_TABLE_TYPE || !size) { |
80cc9f10 | 284 | printk(KERN_ERR "microcode: error! " |
a0a29b62 | 285 | "Wrong microcode equivalnet cpu table\n"); |
80cc9f10 PO |
286 | return 0; |
287 | } | |
288 | ||
289 | equiv_cpu_table = (struct equiv_cpu_entry *) vmalloc(size); | |
290 | if (!equiv_cpu_table) { | |
291 | printk(KERN_ERR "microcode: error, can't allocate memory for equiv CPU table\n"); | |
292 | return 0; | |
293 | } | |
294 | ||
b6cffde1 | 295 | buf += UCODE_CONTAINER_HEADER_SIZE; |
a0a29b62 DA |
296 | if (get_ucode_data(equiv_cpu_table, buf, size)) { |
297 | vfree(equiv_cpu_table); | |
298 | return 0; | |
299 | } | |
80cc9f10 | 300 | |
b6cffde1 PO |
301 | return size + UCODE_CONTAINER_HEADER_SIZE; /* add header length */ |
302 | #undef UCODE_CONTAINER_HEADER_SIZE | |
80cc9f10 PO |
303 | } |
304 | ||
a0a29b62 | 305 | static void free_equiv_cpu_table(void) |
80cc9f10 | 306 | { |
a0a29b62 DA |
307 | if (equiv_cpu_table) { |
308 | vfree(equiv_cpu_table); | |
309 | equiv_cpu_table = NULL; | |
80cc9f10 | 310 | } |
a0a29b62 | 311 | } |
80cc9f10 | 312 | |
a0a29b62 DA |
313 | static int generic_load_microcode(int cpu, void *data, size_t size, |
314 | int (*get_ucode_data)(void *, const void *, size_t)) | |
315 | { | |
316 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | |
317 | u8 *ucode_ptr = data, *new_mc = NULL, *mc; | |
318 | int new_rev = uci->cpu_sig.rev; | |
319 | unsigned int leftover; | |
320 | unsigned long offset; | |
80cc9f10 | 321 | |
a0a29b62 | 322 | offset = install_equiv_cpu_table(ucode_ptr, get_ucode_data); |
80cc9f10 PO |
323 | if (!offset) { |
324 | printk(KERN_ERR "microcode: installing equivalent cpu table failed\n"); | |
325 | return -EINVAL; | |
326 | } | |
327 | ||
a0a29b62 DA |
328 | ucode_ptr += offset; |
329 | leftover = size - offset; | |
330 | ||
331 | while (leftover) { | |
2f9284e4 | 332 | unsigned int uninitialized_var(mc_size); |
a0a29b62 DA |
333 | struct microcode_header_amd *mc_header; |
334 | ||
335 | mc = get_next_ucode(ucode_ptr, leftover, get_ucode_data, &mc_size); | |
336 | if (!mc) | |
80cc9f10 | 337 | break; |
a0a29b62 DA |
338 | |
339 | mc_header = (struct microcode_header_amd *)mc; | |
340 | if (get_matching_microcode(cpu, mc, new_rev)) { | |
a1c75cc5 IM |
341 | if (new_mc) |
342 | vfree(new_mc); | |
a0a29b62 DA |
343 | new_rev = mc_header->patch_id; |
344 | new_mc = mc; | |
345 | } else | |
346 | vfree(mc); | |
347 | ||
348 | ucode_ptr += mc_size; | |
349 | leftover -= mc_size; | |
80cc9f10 | 350 | } |
a0a29b62 DA |
351 | |
352 | if (new_mc) { | |
353 | if (!leftover) { | |
18dbc916 DA |
354 | if (uci->mc) |
355 | vfree(uci->mc); | |
356 | uci->mc = new_mc; | |
a0a29b62 DA |
357 | pr_debug("microcode: CPU%d found a matching microcode update with" |
358 | " version 0x%x (current=0x%x)\n", | |
18dbc916 | 359 | cpu, new_rev, uci->cpu_sig.rev); |
a0a29b62 DA |
360 | } else |
361 | vfree(new_mc); | |
80cc9f10 | 362 | } |
a0a29b62 DA |
363 | |
364 | free_equiv_cpu_table(); | |
365 | ||
366 | return (int)leftover; | |
367 | } | |
368 | ||
369 | static int get_ucode_fw(void *to, const void *from, size_t n) | |
370 | { | |
371 | memcpy(to, from, n); | |
372 | return 0; | |
373 | } | |
374 | ||
375 | static int request_microcode_fw(int cpu, struct device *device) | |
376 | { | |
377 | const char *fw_name = "amd-ucode/microcode_amd.bin"; | |
378 | const struct firmware *firmware; | |
379 | int ret; | |
380 | ||
381 | /* We should bind the task to the CPU */ | |
382 | BUG_ON(cpu != raw_smp_processor_id()); | |
383 | ||
384 | ret = request_firmware(&firmware, fw_name, device); | |
385 | if (ret) { | |
386 | printk(KERN_ERR "microcode: ucode data file %s load failed\n", fw_name); | |
387 | return ret; | |
388 | } | |
389 | ||
390 | ret = generic_load_microcode(cpu, (void*)firmware->data, firmware->size, | |
391 | &get_ucode_fw); | |
392 | ||
80cc9f10 PO |
393 | release_firmware(firmware); |
394 | ||
a0a29b62 DA |
395 | return ret; |
396 | } | |
397 | ||
a0a29b62 DA |
398 | static int request_microcode_user(int cpu, const void __user *buf, size_t size) |
399 | { | |
2f9284e4 DA |
400 | printk(KERN_WARNING "microcode: AMD microcode update via /dev/cpu/microcode" |
401 | "is not supported\n"); | |
402 | return -1; | |
80cc9f10 PO |
403 | } |
404 | ||
80cc9f10 PO |
405 | static void microcode_fini_cpu_amd(int cpu) |
406 | { | |
407 | struct ucode_cpu_info *uci = ucode_cpu_info + cpu; | |
408 | ||
18dbc916 DA |
409 | vfree(uci->mc); |
410 | uci->mc = NULL; | |
80cc9f10 PO |
411 | } |
412 | ||
413 | static struct microcode_ops microcode_amd_ops = { | |
a0a29b62 DA |
414 | .request_microcode_user = request_microcode_user, |
415 | .request_microcode_fw = request_microcode_fw, | |
80cc9f10 PO |
416 | .collect_cpu_info = collect_cpu_info_amd, |
417 | .apply_microcode = apply_microcode_amd, | |
418 | .microcode_fini_cpu = microcode_fini_cpu_amd, | |
419 | }; | |
420 | ||
18dbc916 | 421 | struct microcode_ops * __init init_amd_microcode(void) |
80cc9f10 | 422 | { |
18dbc916 | 423 | return µcode_amd_ops; |
80cc9f10 | 424 | } |