Index: head/sys/dev/cpuctl/cpuctl.c =================================================================== --- head/sys/dev/cpuctl/cpuctl.c +++ head/sys/dev/cpuctl/cpuctl.c @@ -377,13 +377,24 @@ return (ret); } +/* + * NB: MSR 0xc0010020, MSR_K8_UCODE_UPDATE, is not documented by AMD. + * Coreboot, illumos and Linux source code was used to understand + * its workings. + */ +static void +amd_ucode_wrmsr(void *ucode_ptr) +{ + uint32_t tmp[4]; + + wrmsr_safe(MSR_K8_UCODE_UPDATE, (uintptr_t)ucode_ptr); + do_cpuid(0, tmp); +} + static int update_amd(int cpu, cpuctl_update_args_t *args, struct thread *td) { - void *ptr = NULL; - uint32_t tmp[4]; - int is_bound = 0; - int oldcpu; + void *ptr; int ret; if (args->size == 0 || args->data == NULL) { @@ -394,41 +405,23 @@ DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__); return (EINVAL); } + /* - * XXX Might not require contignous address space - needs check + * 16 byte alignment required. Rely on the fact that + * malloc(9) always returns the pointer aligned at least on + * the size of the allocation. */ - ptr = contigmalloc(args->size, M_CPUCTL, 0, 0, 0xffffffff, 16, 0); - if (ptr == NULL) { - DPRINTF("[cpuctl,%d]: cannot allocate %zd bytes of memory", - __LINE__, args->size); - return (ENOMEM); - } + ptr = malloc(args->size + 16, M_CPUCTL, M_ZERO | M_WAITOK); if (copyin(args->data, ptr, args->size) != 0) { DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed", __LINE__, args->data, ptr, args->size); ret = EFAULT; goto fail; } - oldcpu = td->td_oncpu; - is_bound = cpu_sched_is_bound(td); - set_cpu(cpu, td); - critical_enter(); - - /* - * Perform update. - */ - wrmsr_safe(MSR_K8_UCODE_UPDATE, (uintptr_t)ptr); - - /* - * Serialize instruction flow. - */ - do_cpuid(0, tmp); - critical_exit(); - restore_cpu(oldcpu, is_bound, td); + smp_rendezvous(NULL, amd_ucode_wrmsr, NULL, ptr); ret = 0; fail: - if (ptr != NULL) - contigfree(ptr, args->size, M_CPUCTL); + free(ptr, M_CPUCTL); return (ret); } Index: head/usr.sbin/cpucontrol/Makefile =================================================================== --- head/usr.sbin/cpucontrol/Makefile +++ head/usr.sbin/cpucontrol/Makefile @@ -2,7 +2,7 @@ PROG= cpucontrol MAN= cpucontrol.8 -SRCS= cpucontrol.c intel.c amd.c via.c +SRCS= cpucontrol.c intel.c amd.c amd10h.c via.c NO_WCAST_ALIGN= Index: head/usr.sbin/cpucontrol/amd.h =================================================================== --- head/usr.sbin/cpucontrol/amd.h +++ head/usr.sbin/cpucontrol/amd.h @@ -33,6 +33,8 @@ */ ucode_probe_t amd_probe; ucode_update_t amd_update; +ucode_probe_t amd10h_probe; +ucode_update_t amd10h_update; typedef struct amd_fw_header { uint32_t date; /* Update creation date. */ @@ -46,4 +48,45 @@ #define AMD_MAGIC 0xaaaaaa +/* + * AMD family 10h and later. + */ +typedef struct amd_10h_fw_header { + uint32_t data_code; + uint32_t patch_id; + uint16_t mc_patch_data_id; + uint8_t mc_patch_data_len; + uint8_t init_flag; + uint32_t mc_patch_data_checksum; + uint32_t nb_dev_id; + uint32_t sb_dev_id; + uint16_t processor_rev_id; + uint8_t nb_rev_id; + uint8_t sb_rev_id; + uint8_t bios_api_rev; + uint8_t reserved1[3]; + uint32_t match_reg[8]; +} amd_10h_fw_header_t; + +typedef struct equiv_cpu_entry { + uint32_t installed_cpu; + uint32_t fixed_errata_mask; + uint32_t fixed_errata_compare; + uint16_t equiv_cpu; + uint16_t res; +} equiv_cpu_entry_t; + +typedef struct section_header { + uint32_t type; + uint32_t size; +} section_header_t; + +typedef struct container_header { + uint32_t magic; +} container_header_t; + +#define AMD_10H_MAGIC 0x414d44 +#define AMD_10H_EQUIV_TABLE_TYPE 0 +#define AMD_10H_uCODE_TYPE 1 + #endif /* !AMD_H */ Index: head/usr.sbin/cpucontrol/amd10h.c =================================================================== --- head/usr.sbin/cpucontrol/amd10h.c +++ head/usr.sbin/cpucontrol/amd10h.c @@ -0,0 +1,307 @@ +/*- + * Copyright (c) 2012 Andriy Gapon . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "cpucontrol.h" +#include "amd.h" + +int +amd10h_probe(int fd) +{ + char vendor[13]; + cpuctl_cpuid_args_t idargs; + uint32_t family; + uint32_t signature; + int error; + + idargs.level = 0; + error = ioctl(fd, CPUCTL_CPUID, &idargs); + if (error < 0) { + WARN(0, "ioctl()"); + return (1); + } + ((uint32_t *)vendor)[0] = idargs.data[1]; + ((uint32_t *)vendor)[1] = idargs.data[3]; + ((uint32_t *)vendor)[2] = idargs.data[2]; + vendor[12] = '\0'; + if (strncmp(vendor, AMD_VENDOR_ID, sizeof(AMD_VENDOR_ID)) != 0) + return (1); + + idargs.level = 1; + error = ioctl(fd, CPUCTL_CPUID, &idargs); + if (error < 0) { + WARN(0, "ioctl()"); + return (1); + } + signature = idargs.data[0]; + family = ((signature >> 8) & 0x0f) + ((signature >> 20) & 0xff); + if (family < 0x10) + return (1); + return (0); +} + +/* + * NB: the format of microcode update files is not documented by AMD. + * It has been reverse engineered from studying Coreboot, illumos and Linux + * source code. + */ +void +amd10h_update(const char *dev, const char *path) +{ + struct stat st; + cpuctl_cpuid_args_t idargs; + cpuctl_msr_args_t msrargs; + cpuctl_update_args_t args; + const amd_10h_fw_header_t *fw_header; + const amd_10h_fw_header_t *selected_fw; + const equiv_cpu_entry_t *equiv_cpu_table; + const section_header_t *section_header; + const container_header_t *container_header; + const uint8_t *fw_data; + uint8_t *fw_image; + size_t fw_size; + size_t selected_size; + uint32_t revision; + uint32_t new_rev; + uint32_t signature; + uint16_t equiv_id; + int fd, devfd; + unsigned int i; + int error; + + assert(path); + assert(dev); + + fd = -1; + fw_image = MAP_FAILED; + devfd = open(dev, O_RDWR); + if (devfd < 0) { + WARN(0, "could not open %s for writing", dev); + return; + } + idargs.level = 1; + error = ioctl(devfd, CPUCTL_CPUID, &idargs); + if (error < 0) { + WARN(0, "ioctl()"); + goto done; + } + signature = idargs.data[0]; + + msrargs.msr = 0x0000008b; + error = ioctl(devfd, CPUCTL_RDMSR, &msrargs); + if (error < 0) { + WARN(0, "ioctl(%s)", dev); + goto done; + } + revision = (uint32_t)msrargs.data; + + WARNX(1, "found cpu family %#x model %#x " + "stepping %#x extfamily %#x extmodel %#x.", + (signature >> 8) & 0x0f, (signature >> 4) & 0x0f, + (signature >> 0) & 0x0f, (signature >> 20) & 0xff, + (signature >> 16) & 0x0f); + WARNX(1, "microcode revision %#x", revision); + + /* + * Open the firmware file. + */ + fd = open(path, O_RDONLY, 0); + if (fd < 0) { + WARN(0, "open(%s)", path); + goto done; + } + error = fstat(fd, &st); + if (error != 0) { + WARN(0, "fstat(%s)", path); + goto done; + } + if (st.st_size < 0 || (size_t)st.st_size < + (sizeof(*container_header) + sizeof(*section_header))) { + WARNX(2, "file too short: %s", path); + goto done; + } + fw_size = st.st_size; + + /* + * mmap the whole image. + */ + fw_image = (uint8_t *)mmap(NULL, st.st_size, PROT_READ, + MAP_PRIVATE, fd, 0); + if (fw_image == MAP_FAILED) { + WARN(0, "mmap(%s)", path); + goto done; + } + + fw_data = fw_image; + container_header = (const container_header_t *)fw_data; + if (container_header->magic != AMD_10H_MAGIC) { + WARNX(2, "%s is not a valid amd firmware: bad magic", path); + goto done; + } + fw_data += sizeof(*container_header); + fw_size -= sizeof(*container_header); + + section_header = (const section_header_t *)fw_data; + if (section_header->type != AMD_10H_EQUIV_TABLE_TYPE) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is not CPU equivalence table", path); + goto done; + } + if (section_header->size == 0) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is empty", path); + goto done; + } + fw_data += sizeof(*section_header); + fw_size -= sizeof(*section_header); + + if (section_header->size > fw_size) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + goto done; + } + if (section_header->size < sizeof(*equiv_cpu_table)) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is too short", path); + goto done; + } + equiv_cpu_table = (const equiv_cpu_entry_t *)fw_data; + fw_data += section_header->size; + fw_size -= section_header->size; + + equiv_id = 0; + for (i = 0; equiv_cpu_table[i].installed_cpu != 0; i++) { + if (signature == equiv_cpu_table[i].installed_cpu) { + equiv_id = equiv_cpu_table[i].equiv_cpu; + WARNX(3, "equiv_id: %x", equiv_id); + break; + } + } + if (equiv_id == 0) { + WARNX(2, "CPU is not found in the equivalence table"); + goto done; + } + + selected_fw = NULL; + selected_size = 0; + while (fw_size >= sizeof(*section_header)) { + section_header = (const section_header_t *)fw_data; + fw_data += sizeof(*section_header); + fw_size -= sizeof(*section_header); + if (section_header->type != AMD_10H_uCODE_TYPE) { + WARNX(2, "%s is not a valid amd firmware: " + "section has incorret type", path); + goto done; + } + if (section_header->size > fw_size) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + goto done; + } + if (section_header->size < sizeof(*fw_header)) { + WARNX(2, "%s is not a valid amd firmware: " + "section is too short", path); + goto done; + } + fw_header = (const amd_10h_fw_header_t *)fw_data; + fw_data += section_header->size; + fw_size -= section_header->size; + + if (fw_header->processor_rev_id != equiv_id) + continue; /* different cpu */ + if (fw_header->patch_id <= revision) + continue; /* not newer revision */ + if (fw_header->nb_dev_id != 0 || fw_header->sb_dev_id != 0) { + WARNX(2, "Chipset-specific microcode is not supported"); + } + + WARNX(3, "selecting revision: %x", fw_header->patch_id); + revision = fw_header->patch_id; + selected_fw = fw_header; + selected_size = section_header->size; + } + + if (fw_size != 0) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + goto done; + } + + if (selected_fw != NULL) { + WARNX(1, "selected ucode size is %zu", selected_size); + fprintf(stderr, "%s: updating cpu %s to revision %#x... ", + path, dev, revision); + + args.data = __DECONST(void *, selected_fw); + args.size = selected_size; + error = ioctl(devfd, CPUCTL_UPDATE, &args); + if (error < 0) { + fprintf(stderr, "failed.\n"); + warn("ioctl()"); + goto done; + } + fprintf(stderr, "done.\n"); + } + + msrargs.msr = 0x0000008b; + error = ioctl(devfd, CPUCTL_RDMSR, &msrargs); + if (error < 0) { + WARN(0, "ioctl(%s)", dev); + goto done; + } + new_rev = (uint32_t)msrargs.data; + if (new_rev != revision) + WARNX(0, "revision after update %#x", new_rev); + +done: + if (fd >= 0) + close(fd); + if (devfd >= 0) + close(devfd); + if (fw_image != MAP_FAILED) + if (munmap(fw_image, st.st_size) != 0) + warn("munmap(%s)", path); + return; +} Index: head/usr.sbin/cpucontrol/cpucontrol.c =================================================================== --- head/usr.sbin/cpucontrol/cpucontrol.c +++ head/usr.sbin/cpucontrol/cpucontrol.c @@ -91,6 +91,7 @@ ucode_update_t *update; } handlers[] = { { intel_probe, intel_update }, + { amd10h_probe, amd10h_update }, { amd_probe, amd_update }, { via_probe, via_update }, };