aboutsummaryrefslogtreecommitdiffstats
path: root/xen/arch/x86/microcode_amd.c
diff options
context:
space:
mode:
authorKeir Fraser <keir.fraser@citrix.com>2008-09-12 14:18:13 +0100
committerKeir Fraser <keir.fraser@citrix.com>2008-09-12 14:18:13 +0100
commitbd7c09c0f8c06aad9c7c1695b9ab17f187126578 (patch)
tree7986d3a4ed9c4e01f28aefb6f2f9c1fe3e020459 /xen/arch/x86/microcode_amd.c
parent30c988711cbbd177c8f865347948565583de56db (diff)
downloadxen-bd7c09c0f8c06aad9c7c1695b9ab17f187126578.tar.gz
xen-bd7c09c0f8c06aad9c7c1695b9ab17f187126578.tar.bz2
xen-bd7c09c0f8c06aad9c7c1695b9ab17f187126578.zip
x86: microcode update support for AMD CPUs
Microcode update support for AMD CPUs Family10h and Family11h. It is based on a patch for Linux which is on its way for 2.6.28. Signed-off-by: Christoph Egger <Christoph.Egger@amd.com>
Diffstat (limited to 'xen/arch/x86/microcode_amd.c')
-rw-r--r--xen/arch/x86/microcode_amd.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/xen/arch/x86/microcode_amd.c b/xen/arch/x86/microcode_amd.c
new file mode 100644
index 0000000000..d2198d5838
--- /dev/null
+++ b/xen/arch/x86/microcode_amd.c
@@ -0,0 +1,366 @@
+/*
+ * AMD CPU Microcode Update Driver for Linux
+ * Copyright (C) 2008 Advanced Micro Devices Inc.
+ *
+ * Author: Peter Oruba <peter.oruba@amd.com>
+ *
+ * Based on work by:
+ * Tigran Aivazian <tigran@aivazian.fsnet.co.uk>
+ *
+ * This driver allows to upgrade microcode on AMD
+ * family 0x10 and 0x11 processors.
+ *
+ * Licensed unter the terms of the GNU General Public
+ * License version 2. See file COPYING for details.
+*/
+
+#include <xen/config.h>
+#include <xen/lib.h>
+#include <xen/kernel.h>
+#include <xen/init.h>
+#include <xen/sched.h>
+#include <xen/smp.h>
+#include <xen/spinlock.h>
+
+#include <asm/msr.h>
+#include <asm/uaccess.h>
+#include <asm/processor.h>
+#include <asm/microcode.h>
+
+
+#define pr_debug(x...) ((void)0)
+#define DEFINE_MUTEX(_m) DEFINE_SPINLOCK(_m)
+#define mutex_lock(_m) spin_lock(_m)
+#define mutex_unlock(_m) spin_unlock(_m)
+#define vmalloc(_s) xmalloc_bytes(_s)
+#define vfree(_p) xfree(_p)
+
+
+#define UCODE_MAGIC 0x00414d44
+#define UCODE_EQUIV_CPU_TABLE_TYPE 0x00000000
+#define UCODE_UCODE_TYPE 0x00000001
+
+#define UCODE_MAX_SIZE (2048)
+#define DEFAULT_UCODE_DATASIZE (896)
+#define MC_HEADER_SIZE (sizeof(struct microcode_header_amd))
+#define DEFAULT_UCODE_TOTALSIZE (DEFAULT_UCODE_DATASIZE + MC_HEADER_SIZE)
+#define DWSIZE (sizeof(uint32_t))
+/* For now we support a fixed ucode total size only */
+#define get_totalsize(mc) \
+ ((((struct microcode_amd *)mc)->hdr.mc_patch_data_len * 28) \
+ + MC_HEADER_SIZE)
+
+/* serialize access to the physical write */
+static DEFINE_SPINLOCK(microcode_update_lock);
+
+struct equiv_cpu_entry *equiv_cpu_table;
+
+static long install_equiv_cpu_table(const void *, uint32_t, long);
+
+static int collect_cpu_info_amd(int cpu, struct cpu_signature *csig)
+{
+ struct cpuinfo_x86 *c = &cpu_data[cpu];
+
+ memset(csig, 0, sizeof(*csig));
+
+ if (c->x86_vendor != X86_VENDOR_AMD || c->x86 < 0x10) {
+ printk(KERN_ERR "microcode: CPU%d not a capable AMD processor\n",
+ cpu);
+ return -1;
+ }
+
+ asm volatile("movl %1, %%ecx; rdmsr"
+ : "=a" (csig->rev)
+ : "i" (MSR_AMD_PATCHLEVEL) : "ecx");
+
+ printk(KERN_INFO "microcode: collect_cpu_info_amd : patch_id=0x%x\n",
+ csig->rev);
+
+ return 0;
+}
+
+static int get_matching_microcode_amd(void *mc, int cpu)
+{
+ struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+ struct microcode_header_amd *mc_header = mc;
+ unsigned long total_size = get_totalsize(mc_header);
+ void *new_mc;
+ unsigned int current_cpu_id;
+ unsigned int equiv_cpu_id = 0x00;
+ unsigned int i = 0;
+
+ /* We should bind the task to the CPU */
+ BUG_ON(cpu != raw_smp_processor_id());
+
+ /* This is a tricky part. We might be called from a write operation
+ * to the device file instead of the usual process of firmware
+ * loading. This routine needs to be able to distinguish both
+ * cases. This is done by checking if there already is a equivalent
+ * CPU table installed. If not, we're written through
+ * /dev/cpu/microcode.
+ * Since we ignore all checks. The error case in which going through
+ * firmware loading and that table is not loaded has already been
+ * checked earlier.
+ */
+ if (equiv_cpu_table == NULL) {
+ printk(KERN_INFO "microcode: CPU%d microcode update with "
+ "version 0x%x (current=0x%x)\n",
+ cpu, mc_header->patch_id, uci->cpu_sig.rev);
+ goto out;
+ }
+
+ current_cpu_id = cpuid_eax(0x00000001);
+
+ while (equiv_cpu_table[i].installed_cpu != 0) {
+ if (current_cpu_id == equiv_cpu_table[i].installed_cpu) {
+ equiv_cpu_id = equiv_cpu_table[i].equiv_cpu;
+ break;
+ }
+ i++;
+ }
+
+ if (!equiv_cpu_id) {
+ printk(KERN_ERR "microcode: CPU%d cpu_id "
+ "not found in equivalent cpu table \n", cpu);
+ return 0;
+ }
+
+ if ((mc_header->processor_rev_id[0]) != (equiv_cpu_id & 0xff)) {
+ printk(KERN_INFO
+ "microcode: CPU%d patch does not match "
+ "(patch is %x, cpu extended is %x) \n",
+ cpu, mc_header->processor_rev_id[0],
+ (equiv_cpu_id & 0xff));
+ return 0;
+ }
+
+ if ((mc_header->processor_rev_id[1]) != ((equiv_cpu_id >> 16) & 0xff)) {
+ printk(KERN_INFO "microcode: CPU%d patch does not match "
+ "(patch is %x, cpu base id is %x) \n",
+ cpu, mc_header->processor_rev_id[1],
+ ((equiv_cpu_id >> 16) & 0xff));
+
+ return 0;
+ }
+
+ if (mc_header->patch_id <= uci->cpu_sig.rev)
+ return 0;
+
+ printk(KERN_INFO "microcode: CPU%d found a matching microcode "
+ "update with version 0x%x (current=0x%x)\n",
+ cpu, mc_header->patch_id, uci->cpu_sig.rev);
+
+out:
+ new_mc = vmalloc(UCODE_MAX_SIZE);
+ if (!new_mc) {
+ printk(KERN_ERR "microcode: error, can't allocate memory\n");
+ return -ENOMEM;
+ }
+ memset(new_mc, 0, UCODE_MAX_SIZE);
+
+ /* free previous update file */
+ vfree(uci->mc.mc_amd);
+
+ memcpy(new_mc, mc, total_size);
+
+ uci->mc.mc_amd = new_mc;
+ return 1;
+}
+
+static int apply_microcode_amd(int cpu)
+{
+ unsigned long flags;
+ unsigned int eax, edx;
+ unsigned int rev;
+ int cpu_num = raw_smp_processor_id();
+ struct ucode_cpu_info *uci = ucode_cpu_info + cpu_num;
+ unsigned long addr;
+
+ /* We should bind the task to the CPU */
+ BUG_ON(cpu_num != cpu);
+
+ if (uci->mc.mc_amd == NULL)
+ return -EINVAL;
+
+ spin_lock_irqsave(&microcode_update_lock, flags);
+
+ addr = (unsigned long)&uci->mc.mc_amd->hdr.data_code;
+ edx = (unsigned int)((unsigned long)(addr >> 32));
+ eax = (unsigned int)((unsigned long)(addr & 0xffffffff));
+
+ asm volatile("movl %0, %%ecx; wrmsr" :
+ : "i" (MSR_AMD_PATCHLOADER), "a" (eax), "d" (edx) : "ecx");
+
+ /* get patch id after patching */
+ asm volatile("movl %1, %%ecx; rdmsr"
+ : "=a" (rev)
+ : "i" (MSR_AMD_PATCHLEVEL) : "ecx");
+
+ spin_unlock_irqrestore(&microcode_update_lock, flags);
+
+ /* check current patch id and patch's id for match */
+ if (rev != uci->mc.mc_amd->hdr.patch_id) {
+ printk(KERN_ERR "microcode: CPU%d update from revision "
+ "0x%x to 0x%x failed\n", cpu_num,
+ uci->mc.mc_amd->hdr.patch_id, rev);
+ return -EIO;
+ }
+
+ printk("microcode: CPU%d updated from revision "
+ "0x%x to 0x%x \n",
+ cpu_num, uci->cpu_sig.rev, uci->mc.mc_amd->hdr.patch_id);
+
+ uci->cpu_sig.rev = rev;
+
+ return 0;
+}
+
+static long get_next_ucode_from_buffer_amd(void **mc, const void *buf,
+ unsigned long size, long offset)
+{
+ struct microcode_header_amd *mc_header;
+ unsigned long total_size;
+ const uint8_t *buf_pos = buf;
+
+ /* No more data */
+ if (offset >= size)
+ return 0;
+
+ if (buf_pos[offset] != UCODE_UCODE_TYPE) {
+ printk(KERN_ERR "microcode: error! "
+ "Wrong microcode payload type field\n");
+ return -EINVAL;
+ }
+
+ mc_header = (struct microcode_header_amd *)(&buf_pos[offset+8]);
+
+ total_size = (unsigned long) (buf_pos[offset+4] +
+ (buf_pos[offset+5] << 8));
+
+ printk(KERN_INFO "microcode: size %lu, total_size %lu, offset %ld\n",
+ size, total_size, offset);
+
+ if (offset + total_size > size) {
+ printk(KERN_ERR "microcode: error! Bad data in microcode data file\n");
+ return -EINVAL;
+ }
+
+ *mc = vmalloc(UCODE_MAX_SIZE);
+ if (!*mc) {
+ printk(KERN_ERR "microcode: error! "
+ "Can not allocate memory for microcode patch\n");
+ return -ENOMEM;
+ }
+
+ memset(*mc, 0, UCODE_MAX_SIZE);
+ memcpy(*mc, (const void *)(buf + offset + 8), total_size);
+
+ return offset + total_size + 8;
+}
+
+static long install_equiv_cpu_table(const void *buf,
+ uint32_t size, long offset)
+{
+ const uint32_t *buf_pos = buf;
+
+ /* No more data */
+ if (offset >= size)
+ return 0;
+
+ if (buf_pos[1] != UCODE_EQUIV_CPU_TABLE_TYPE) {
+ printk(KERN_ERR "microcode: error! "
+ "Wrong microcode equivalnet cpu table type field\n");
+ return 0;
+ }
+
+ if (size == 0) {
+ printk(KERN_ERR "microcode: error! "
+ "Wrong microcode equivalnet cpu table length\n");
+ return 0;
+ }
+
+ equiv_cpu_table = (struct equiv_cpu_entry *) vmalloc(size);
+ if (!equiv_cpu_table) {
+ printk(KERN_ERR "microcode: error, can't allocate memory for equiv CPU table\n");
+ return 0;
+ }
+
+ memset(equiv_cpu_table, 0, size);
+ memcpy(equiv_cpu_table, (const void *)&buf_pos[3], size);
+
+ return size + 12; /* add header length */
+}
+
+static int cpu_request_microcode_amd(int cpu, const void *buf,
+ size_t size)
+{
+ const uint32_t *buf_pos;
+ long offset = 0;
+ int error = 0;
+ void *mc;
+
+ /* We should bind the task to the CPU */
+ BUG_ON(cpu != raw_smp_processor_id());
+
+ buf_pos = (const uint32_t *)buf;
+
+ if (buf_pos[0] != UCODE_MAGIC) {
+ printk(KERN_ERR "microcode: error! Wrong microcode patch file magic\n");
+ return -EINVAL;
+ }
+
+ offset = install_equiv_cpu_table(buf, (uint32_t)(buf_pos[2]), offset);
+ if (!offset) {
+ printk(KERN_ERR "microcode: installing equivalent cpu table failed\n");
+ return -EINVAL;
+ }
+
+ while ((offset =
+ get_next_ucode_from_buffer_amd(&mc, buf, size, offset)) > 0) {
+ error = get_matching_microcode_amd(mc, cpu);
+ if (error < 0)
+ break;
+ /*
+ * It's possible the data file has multiple matching ucode,
+ * lets keep searching till the latest version
+ */
+ if (error == 1) {
+ apply_microcode_amd(cpu);
+ error = 0;
+ }
+ vfree(mc);
+ }
+ if (offset > 0) {
+ vfree(mc);
+ vfree(equiv_cpu_table);
+ equiv_cpu_table = NULL;
+ }
+ if (offset < 0)
+ error = offset;
+
+ return error;
+}
+
+static void microcode_fini_cpu_amd(int cpu)
+{
+ struct ucode_cpu_info *uci = ucode_cpu_info + cpu;
+
+ vfree(uci->mc.mc_amd);
+ uci->mc.mc_amd = NULL;
+}
+
+static struct microcode_ops microcode_amd_ops = {
+ .get_matching_microcode = get_matching_microcode_amd,
+ .microcode_sanity_check = NULL,
+ .cpu_request_microcode = cpu_request_microcode_amd,
+ .collect_cpu_info = collect_cpu_info_amd,
+ .apply_microcode = apply_microcode_amd,
+ .microcode_fini_cpu = microcode_fini_cpu_amd,
+};
+
+int microcode_init_amd(struct cpuinfo_x86 *c)
+{
+ microcode_ops = &microcode_amd_ops;
+ return 0;
+}
+