2008-07-28 18:44:22 +02:00
/*
* 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 <linux/capability.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/sched.h>
# include <linux/cpumask.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/vmalloc.h>
# include <linux/miscdevice.h>
# include <linux/spinlock.h>
# include <linux/mm.h>
# include <linux/fs.h>
# include <linux/mutex.h>
# include <linux/cpu.h>
# include <linux/firmware.h>
# include <linux/platform_device.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
# include <asm/msr.h>
# include <asm/uaccess.h>
# include <asm/processor.h>
# include <asm/microcode.h>
MODULE_DESCRIPTION ( " AMD Microcode Update Driver " ) ;
2008-10-17 15:30:38 +02:00
MODULE_AUTHOR ( " Peter Oruba " ) ;
2008-07-29 10:07:36 +02:00
MODULE_LICENSE ( " GPL v2 " ) ;
2008-07-28 18:44:22 +02:00
# define UCODE_MAGIC 0x00414d44
# define UCODE_EQUIV_CPU_TABLE_TYPE 0x00000000
# define UCODE_UCODE_TYPE 0x00000001
2008-09-23 12:08:44 +02:00
struct equiv_cpu_entry {
unsigned int installed_cpu ;
unsigned int fixed_errata_mask ;
unsigned int fixed_errata_compare ;
unsigned int equiv_cpu ;
} ;
struct microcode_header_amd {
unsigned int data_code ;
unsigned int patch_id ;
unsigned char mc_patch_data_id [ 2 ] ;
unsigned char mc_patch_data_len ;
unsigned char init_flag ;
unsigned int mc_patch_data_checksum ;
unsigned int nb_dev_id ;
unsigned int sb_dev_id ;
2008-12-16 19:07:47 +01:00
u16 processor_rev_id ;
2008-09-23 12:08:44 +02:00
unsigned char nb_rev_id ;
unsigned char sb_rev_id ;
unsigned char bios_api_rev ;
unsigned char reserved1 [ 3 ] ;
unsigned int match_reg [ 8 ] ;
} ;
struct microcode_amd {
struct microcode_header_amd hdr ;
unsigned int mpb [ 0 ] ;
} ;
2008-07-28 18:44:22 +02:00
# define UCODE_MAX_SIZE (2048)
2008-07-29 17:41:04 +02:00
# define DEFAULT_UCODE_DATASIZE (896)
# define MC_HEADER_SIZE (sizeof(struct microcode_header_amd))
# define DEFAULT_UCODE_TOTALSIZE (DEFAULT_UCODE_DATASIZE + MC_HEADER_SIZE)
2008-07-28 18:44:22 +02:00
# define DWSIZE (sizeof(u32))
/* 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 ) ;
2008-09-11 23:27:52 +02:00
static struct equiv_cpu_entry * equiv_cpu_table ;
2008-07-28 18:44:22 +02:00
2008-08-20 00:22:26 +02:00
static int collect_cpu_info_amd ( int cpu , struct cpu_signature * csig )
2008-07-28 18:44:22 +02:00
{
struct cpuinfo_x86 * c = & cpu_data ( cpu ) ;
2008-08-20 00:22:26 +02:00
memset ( csig , 0 , sizeof ( * csig ) ) ;
2008-07-28 18:44:22 +02:00
if ( c - > x86_vendor ! = X86_VENDOR_AMD | | c - > x86 < 0x10 ) {
printk ( KERN_ERR " microcode: CPU%d not a capable AMD processor \n " ,
cpu ) ;
2008-08-20 00:22:26 +02:00
return - 1 ;
2008-07-28 18:44:22 +02:00
}
asm volatile ( " movl %1, %%ecx; rdmsr "
2008-08-20 00:22:26 +02:00
: " =a " ( csig - > rev )
2008-07-28 18:44:22 +02:00
: " i " ( 0x0000008B ) : " ecx " ) ;
printk ( KERN_INFO " microcode: collect_cpu_info_amd : patch_id=0x%x \n " ,
2008-08-20 00:22:26 +02:00
csig - > rev ) ;
return 0 ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
static int get_matching_microcode ( int cpu , void * mc , int rev )
2008-07-28 18:44:22 +02:00
{
struct microcode_header_amd * mc_header = mc ;
struct pci_dev * nb_pci_dev , * sb_pci_dev ;
unsigned int current_cpu_id ;
unsigned int equiv_cpu_id = 0x00 ;
unsigned int i = 0 ;
2008-09-11 23:27:52 +02:00
BUG_ON ( equiv_cpu_table = = NULL ) ;
2008-07-28 18:44:22 +02:00
current_cpu_id = cpuid_eax ( 0x00000001 ) ;
while ( equiv_cpu_table [ i ] . installed_cpu ! = 0 ) {
if ( current_cpu_id = = equiv_cpu_table [ i ] . installed_cpu ) {
2008-12-16 19:07:47 +01:00
equiv_cpu_id = equiv_cpu_table [ i ] . equiv_cpu & 0xffff ;
2008-07-28 18:44:22 +02:00
break ;
}
i + + ;
}
if ( ! equiv_cpu_id ) {
printk ( KERN_ERR " microcode: CPU%d cpu_id "
" not found in equivalent cpu table \n " , cpu ) ;
return 0 ;
}
2008-12-16 19:07:47 +01:00
if ( mc_header - > processor_rev_id ! = equiv_cpu_id ) {
printk ( KERN_ERR " microcode: CPU%d patch does not match "
" (processor_rev_id: %x, eqiv_cpu_id: %x) \n " ,
cpu , mc_header - > processor_rev_id , equiv_cpu_id ) ;
2008-07-28 18:44:22 +02:00
return 0 ;
}
/* ucode may be northbridge specific */
if ( mc_header - > nb_dev_id ) {
nb_pci_dev = pci_get_device ( PCI_VENDOR_ID_AMD ,
( mc_header - > nb_dev_id & 0xff ) ,
NULL ) ;
if ( ( ! nb_pci_dev ) | |
( mc_header - > nb_rev_id ! = nb_pci_dev - > revision ) ) {
printk ( KERN_ERR " microcode: CPU%d NB mismatch \n " , cpu ) ;
pci_dev_put ( nb_pci_dev ) ;
return 0 ;
}
pci_dev_put ( nb_pci_dev ) ;
}
/* ucode may be southbridge specific */
if ( mc_header - > sb_dev_id ) {
sb_pci_dev = pci_get_device ( PCI_VENDOR_ID_AMD ,
( mc_header - > sb_dev_id & 0xff ) ,
NULL ) ;
if ( ( ! sb_pci_dev ) | |
( mc_header - > sb_rev_id ! = sb_pci_dev - > revision ) ) {
printk ( KERN_ERR " microcode: CPU%d SB mismatch \n " , cpu ) ;
pci_dev_put ( sb_pci_dev ) ;
return 0 ;
}
pci_dev_put ( sb_pci_dev ) ;
}
2008-09-11 23:27:52 +02:00
if ( mc_header - > patch_id < = rev )
2008-07-28 18:44:22 +02:00
return 0 ;
return 1 ;
}
static void 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 ;
2008-09-23 12:08:44 +02:00
struct microcode_amd * mc_amd = uci - > mc ;
2008-08-21 13:43:51 -07:00
unsigned long addr ;
2008-07-28 18:44:22 +02:00
/* We should bind the task to the CPU */
BUG_ON ( cpu_num ! = cpu ) ;
2008-09-23 12:08:44 +02:00
if ( mc_amd = = NULL )
2008-07-28 18:44:22 +02:00
return ;
spin_lock_irqsave ( & microcode_update_lock , flags ) ;
2008-09-23 12:08:44 +02:00
addr = ( unsigned long ) & mc_amd - > hdr . data_code ;
2008-08-21 13:43:51 -07:00
edx = ( unsigned int ) ( ( ( unsigned long ) upper_32_bits ( addr ) ) ) ;
eax = ( unsigned int ) ( ( ( unsigned long ) lower_32_bits ( addr ) ) ) ;
2008-07-28 18:44:22 +02:00
asm volatile ( " movl %0, %%ecx; wrmsr " :
: " i " ( 0xc0010020 ) , " a " ( eax ) , " d " ( edx ) : " ecx " ) ;
/* get patch id after patching */
asm volatile ( " movl %1, %%ecx; rdmsr "
: " =a " ( rev )
: " i " ( 0x0000008B ) : " ecx " ) ;
spin_unlock_irqrestore ( & microcode_update_lock , flags ) ;
/* check current patch id and patch's id for match */
2008-09-23 12:08:44 +02:00
if ( rev ! = mc_amd - > hdr . patch_id ) {
2008-07-28 18:44:22 +02:00
printk ( KERN_ERR " microcode: CPU%d update from revision "
" 0x%x to 0x%x failed \n " , cpu_num ,
2008-09-23 12:08:44 +02:00
mc_amd - > hdr . patch_id , rev ) ;
2008-07-28 18:44:22 +02:00
return ;
}
printk ( KERN_INFO " microcode: CPU%d updated from revision "
" 0x%x to 0x%x \n " ,
2008-09-23 12:08:44 +02:00
cpu_num , uci - > cpu_sig . rev , mc_amd - > hdr . patch_id ) ;
2008-07-28 18:44:22 +02:00
2008-08-20 00:22:26 +02:00
uci - > cpu_sig . rev = rev ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
static void * get_next_ucode ( u8 * buf , unsigned int size ,
int ( * get_ucode_data ) ( void * , const void * , size_t ) ,
unsigned int * mc_size )
2008-07-28 18:44:22 +02:00
{
2008-09-11 23:27:52 +02:00
unsigned int total_size ;
2008-09-17 15:39:18 +02:00
# define UCODE_CONTAINER_SECTION_HDR 8
u8 section_hdr [ UCODE_CONTAINER_SECTION_HDR ] ;
2008-09-11 23:27:52 +02:00
void * mc ;
2008-07-28 18:44:22 +02:00
2008-09-17 15:39:18 +02:00
if ( get_ucode_data ( section_hdr , buf , UCODE_CONTAINER_SECTION_HDR ) )
2008-09-11 23:27:52 +02:00
return NULL ;
2008-07-28 18:44:22 +02:00
2008-09-17 15:39:18 +02:00
if ( section_hdr [ 0 ] ! = UCODE_UCODE_TYPE ) {
2008-07-28 18:44:22 +02:00
printk ( KERN_ERR " microcode: error! "
" Wrong microcode payload type field \n " ) ;
2008-09-11 23:27:52 +02:00
return NULL ;
2008-07-28 18:44:22 +02:00
}
2008-09-17 15:39:18 +02:00
total_size = ( unsigned long ) ( section_hdr [ 4 ] + ( section_hdr [ 5 ] < < 8 ) ) ;
2008-07-28 18:44:22 +02:00
2008-09-11 23:27:52 +02:00
printk ( KERN_INFO " microcode: size %u, total_size %u \n " ,
size , total_size ) ;
2008-07-28 18:44:22 +02:00
2008-09-11 23:27:52 +02:00
if ( total_size > size | | total_size > UCODE_MAX_SIZE ) {
2008-07-28 18:44:22 +02:00
printk ( KERN_ERR " microcode: error! Bad data in microcode data file \n " ) ;
2008-09-11 23:27:52 +02:00
return NULL ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
mc = vmalloc ( UCODE_MAX_SIZE ) ;
if ( mc ) {
memset ( mc , 0 , UCODE_MAX_SIZE ) ;
2008-09-17 15:39:18 +02:00
if ( get_ucode_data ( mc , buf + UCODE_CONTAINER_SECTION_HDR , total_size ) ) {
2008-09-11 23:27:52 +02:00
vfree ( mc ) ;
mc = NULL ;
} else
2008-09-17 15:39:18 +02:00
* mc_size = total_size + UCODE_CONTAINER_SECTION_HDR ;
2008-07-28 18:44:22 +02:00
}
2008-09-17 15:39:18 +02:00
# undef UCODE_CONTAINER_SECTION_HDR
2008-09-11 23:27:52 +02:00
return mc ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
static int install_equiv_cpu_table ( u8 * buf ,
int ( * get_ucode_data ) ( void * , const void * , size_t ) )
2008-07-28 18:44:22 +02:00
{
2008-09-17 15:05:52 +02:00
# define UCODE_CONTAINER_HEADER_SIZE 12
u8 * container_hdr [ UCODE_CONTAINER_HEADER_SIZE ] ;
unsigned int * buf_pos = ( unsigned int * ) container_hdr ;
2008-09-11 23:27:52 +02:00
unsigned long size ;
2008-07-28 18:44:22 +02:00
2008-09-17 15:05:52 +02:00
if ( get_ucode_data ( & container_hdr , buf , UCODE_CONTAINER_HEADER_SIZE ) )
2008-07-28 18:44:22 +02:00
return 0 ;
2008-09-11 23:27:52 +02:00
size = buf_pos [ 2 ] ;
2008-07-28 18:44:22 +02:00
2008-09-11 23:27:52 +02:00
if ( buf_pos [ 1 ] ! = UCODE_EQUIV_CPU_TABLE_TYPE | | ! size ) {
2008-07-28 18:44:22 +02:00
printk ( KERN_ERR " microcode: error! "
2008-09-11 23:27:52 +02:00
" Wrong microcode equivalnet cpu table \n " ) ;
2008-07-28 18:44:22 +02:00
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 ;
}
2008-09-17 15:05:52 +02:00
buf + = UCODE_CONTAINER_HEADER_SIZE ;
2008-09-11 23:27:52 +02:00
if ( get_ucode_data ( equiv_cpu_table , buf , size ) ) {
vfree ( equiv_cpu_table ) ;
return 0 ;
}
2008-07-28 18:44:22 +02:00
2008-09-17 15:05:52 +02:00
return size + UCODE_CONTAINER_HEADER_SIZE ; /* add header length */
# undef UCODE_CONTAINER_HEADER_SIZE
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
static void free_equiv_cpu_table ( void )
2008-07-28 18:44:22 +02:00
{
2008-09-11 23:27:52 +02:00
if ( equiv_cpu_table ) {
vfree ( equiv_cpu_table ) ;
equiv_cpu_table = NULL ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
}
2008-07-28 18:44:22 +02:00
2008-09-11 23:27:52 +02:00
static int generic_load_microcode ( int cpu , void * data , size_t size ,
int ( * get_ucode_data ) ( void * , const void * , size_t ) )
{
struct ucode_cpu_info * uci = ucode_cpu_info + cpu ;
u8 * ucode_ptr = data , * new_mc = NULL , * mc ;
int new_rev = uci - > cpu_sig . rev ;
unsigned int leftover ;
unsigned long offset ;
2008-07-28 18:44:22 +02:00
2008-09-11 23:27:52 +02:00
offset = install_equiv_cpu_table ( ucode_ptr , get_ucode_data ) ;
2008-07-28 18:44:22 +02:00
if ( ! offset ) {
printk ( KERN_ERR " microcode: installing equivalent cpu table failed \n " ) ;
return - EINVAL ;
}
2008-09-11 23:27:52 +02:00
ucode_ptr + = offset ;
leftover = size - offset ;
while ( leftover ) {
2008-09-23 22:56:35 +02:00
unsigned int uninitialized_var ( mc_size ) ;
2008-09-11 23:27:52 +02:00
struct microcode_header_amd * mc_header ;
mc = get_next_ucode ( ucode_ptr , leftover , get_ucode_data , & mc_size ) ;
if ( ! mc )
2008-07-28 18:44:22 +02:00
break ;
2008-09-11 23:27:52 +02:00
mc_header = ( struct microcode_header_amd * ) mc ;
if ( get_matching_microcode ( cpu , mc , new_rev ) ) {
2008-09-14 14:50:26 +02:00
if ( new_mc )
vfree ( new_mc ) ;
2008-09-11 23:27:52 +02:00
new_rev = mc_header - > patch_id ;
new_mc = mc ;
} else
vfree ( mc ) ;
ucode_ptr + = mc_size ;
leftover - = mc_size ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
if ( new_mc ) {
if ( ! leftover ) {
2008-09-23 12:08:44 +02:00
if ( uci - > mc )
vfree ( uci - > mc ) ;
uci - > mc = new_mc ;
2008-09-11 23:27:52 +02:00
pr_debug ( " microcode: CPU%d found a matching microcode update with "
" version 0x%x (current=0x%x) \n " ,
2008-09-23 12:08:44 +02:00
cpu , new_rev , uci - > cpu_sig . rev ) ;
2008-09-11 23:27:52 +02:00
} else
vfree ( new_mc ) ;
2008-07-28 18:44:22 +02:00
}
2008-09-11 23:27:52 +02:00
free_equiv_cpu_table ( ) ;
return ( int ) leftover ;
}
static int get_ucode_fw ( void * to , const void * from , size_t n )
{
memcpy ( to , from , n ) ;
return 0 ;
}
static int request_microcode_fw ( int cpu , struct device * device )
{
const char * fw_name = " amd-ucode/microcode_amd.bin " ;
const struct firmware * firmware ;
int ret ;
/* We should bind the task to the CPU */
BUG_ON ( cpu ! = raw_smp_processor_id ( ) ) ;
ret = request_firmware ( & firmware , fw_name , device ) ;
if ( ret ) {
printk ( KERN_ERR " microcode: ucode data file %s load failed \n " , fw_name ) ;
return ret ;
}
ret = generic_load_microcode ( cpu , ( void * ) firmware - > data , firmware - > size ,
& get_ucode_fw ) ;
2008-07-28 18:44:22 +02:00
release_firmware ( firmware ) ;
2008-09-11 23:27:52 +02:00
return ret ;
}
static int request_microcode_user ( int cpu , const void __user * buf , size_t size )
{
2008-09-23 22:56:35 +02:00
printk ( KERN_WARNING " microcode: AMD microcode update via /dev/cpu/microcode "
" is not supported \n " ) ;
return - 1 ;
2008-07-28 18:44:22 +02:00
}
static void microcode_fini_cpu_amd ( int cpu )
{
struct ucode_cpu_info * uci = ucode_cpu_info + cpu ;
2008-09-23 12:08:44 +02:00
vfree ( uci - > mc ) ;
uci - > mc = NULL ;
2008-07-28 18:44:22 +02:00
}
static struct microcode_ops microcode_amd_ops = {
2008-09-11 23:27:52 +02:00
. request_microcode_user = request_microcode_user ,
. request_microcode_fw = request_microcode_fw ,
2008-07-28 18:44:22 +02:00
. collect_cpu_info = collect_cpu_info_amd ,
. apply_microcode = apply_microcode_amd ,
. microcode_fini_cpu = microcode_fini_cpu_amd ,
} ;
2008-09-23 12:08:44 +02:00
struct microcode_ops * __init init_amd_microcode ( void )
2008-07-28 18:44:22 +02:00
{
2008-09-23 12:08:44 +02:00
return & microcode_amd_ops ;
2008-07-28 18:44:22 +02:00
}