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 .
*
2008-12-16 19:08:53 +01:00
* Licensed under the terms of the GNU General Public
2008-07-28 18:44:22 +02:00
* License version 2. See file COPYING for details .
2009-03-11 11:19:46 +01:00
*/
2009-12-08 22:30:50 -08:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2009-03-11 11:19:46 +01:00
# include <linux/firmware.h>
# include <linux/pci_ids.h>
# include <linux/uaccess.h>
# include <linux/vmalloc.h>
# include <linux/kernel.h>
# include <linux/module.h>
2008-07-28 18:44:22 +02:00
# include <linux/pci.h>
# include <asm/microcode.h>
2009-03-11 11:19:46 +01:00
# include <asm/processor.h>
# include <asm/msr.h>
2008-07-28 18:44:22 +02:00
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 {
2008-12-16 19:21:30 +01:00
u32 installed_cpu ;
u32 fixed_errata_mask ;
u32 fixed_errata_compare ;
u16 equiv_cpu ;
u16 res ;
} __attribute__ ( ( packed ) ) ;
2008-09-23 12:08:44 +02:00
struct microcode_header_amd {
2008-12-16 19:21:30 +01:00
u32 data_code ;
u32 patch_id ;
u16 mc_patch_data_id ;
u8 mc_patch_data_len ;
u8 init_flag ;
u32 mc_patch_data_checksum ;
u32 nb_dev_id ;
u32 sb_dev_id ;
u16 processor_rev_id ;
u8 nb_rev_id ;
u8 sb_rev_id ;
u8 bios_api_rev ;
u8 reserved1 [ 3 ] ;
u32 match_reg [ 8 ] ;
} __attribute__ ( ( packed ) ) ;
2008-09-23 12:08:44 +02:00
struct microcode_amd {
2009-03-11 11:19:46 +01:00
struct microcode_header_amd hdr ;
unsigned int mpb [ 0 ] ;
2008-09-23 12:08:44 +02:00
} ;
2008-12-16 19:17:45 +01:00
# define UCODE_MAX_SIZE 2048
# define UCODE_CONTAINER_SECTION_HDR 8
# define UCODE_CONTAINER_HEADER_SIZE 12
2008-07-28 18:44:22 +02:00
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
{
2010-01-22 21:34:56 +01:00
struct cpuinfo_x86 * c = & cpu_data ( cpu ) ;
2008-12-16 19:16:34 +01:00
u32 dummy ;
2008-07-28 18:44:22 +02:00
2009-11-17 08:06:38 +01:00
memset ( csig , 0 , sizeof ( * csig ) ) ;
2010-01-22 21:34:56 +01:00
if ( c - > x86_vendor ! = X86_VENDOR_AMD | | c - > x86 < 0x10 ) {
pr_warning ( " microcode: CPU%d: AMD CPU family 0x%x not "
" supported \n " , cpu , c - > x86 ) ;
return - 1 ;
}
2008-12-16 19:16:34 +01:00
rdmsr ( MSR_AMD64_PATCH_LEVEL , csig - > rev , dummy ) ;
2009-12-08 22:30:50 -08:00
pr_info ( " CPU%d: patch_level=0x%x \n " , cpu , csig - > rev ) ;
2008-08-20 00:22:26 +02:00
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 ;
unsigned int current_cpu_id ;
2008-12-16 19:21:30 +01:00
u16 equiv_cpu_id = 0 ;
2008-07-28 18:44:22 +02:00
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:21:30 +01:00
equiv_cpu_id = equiv_cpu_table [ i ] . equiv_cpu ;
2008-07-28 18:44:22 +02:00
break ;
}
i + + ;
}
2009-11-10 12:08:25 +01:00
if ( ! equiv_cpu_id )
2008-07-28 18:44:22 +02:00
return 0 ;
2009-10-29 14:47:42 +01:00
if ( mc_header - > processor_rev_id ! = equiv_cpu_id )
2008-07-28 18:44:22 +02:00
return 0 ;
2008-12-16 19:20:21 +01:00
/* ucode might be chipset specific -- currently we don't support this */
if ( mc_header - > nb_dev_id | | mc_header - > sb_dev_id ) {
2009-12-08 22:30:50 -08:00
pr_err ( " CPU%d: loading of chipset specific code not yet supported \n " ,
cpu ) ;
2008-12-16 19:20:21 +01:00
return 0 ;
2008-07-28 18:44:22 +02:00
}
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 ;
}
2009-05-11 23:48:27 +02:00
static int apply_microcode_amd ( int cpu )
2008-07-28 18:44:22 +02:00
{
2008-12-16 19:16:34 +01:00
u32 rev , dummy ;
2008-07-28 18:44:22 +02:00
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-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 )
2009-05-11 23:48:27 +02:00
return 0 ;
2008-07-28 18:44:22 +02:00
2008-12-19 01:36:14 +01:00
wrmsrl ( MSR_AMD64_PATCH_LOADER , ( u64 ) ( long ) & mc_amd - > hdr . data_code ) ;
2008-07-28 18:44:22 +02:00
/* get patch id after patching */
2008-12-16 19:16:34 +01:00
rdmsr ( MSR_AMD64_PATCH_LEVEL , rev , dummy ) ;
2008-07-28 18:44:22 +02:00
/* 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 ) {
2009-12-08 22:30:50 -08:00
pr_err ( " CPU%d: update failed (for patch_level=0x%x) \n " ,
cpu , mc_amd - > hdr . patch_id ) ;
2009-05-11 23:48:27 +02:00
return - 1 ;
2008-07-28 18:44:22 +02:00
}
2009-12-08 22:30:50 -08:00
pr_info ( " CPU%d: updated (new patch_level=0x%x) \n " , cpu , rev ) ;
2008-08-20 00:22:26 +02:00
uci - > cpu_sig . rev = rev ;
2009-05-11 23:48:27 +02:00
return 0 ;
2008-07-28 18:44:22 +02:00
}
2008-12-16 19:14:05 +01:00
static int get_ucode_data ( void * to , const u8 * from , size_t n )
{
memcpy ( to , from , n ) ;
return 0 ;
}
2009-03-11 11:19:46 +01:00
static void *
get_next_ucode ( const u8 * buf , unsigned int size , 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
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 ) {
2009-12-08 22:30:50 -08:00
pr_err ( " error: invalid type field in container file section header \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
if ( total_size > size | | total_size > UCODE_MAX_SIZE ) {
2009-12-08 22:30:50 -08:00
pr_err ( " error: size mismatch \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-12-16 19:11:23 +01: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-11 23:27:52 +02:00
return mc ;
2008-07-28 18:44:22 +02:00
}
2008-12-16 19:14:05 +01:00
static int install_equiv_cpu_table ( const u8 * buf )
2008-07-28 18:44:22 +02:00
{
2008-09-17 15:05:52 +02:00
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 ) {
2009-12-08 22:30:50 -08:00
pr_err ( " error: invalid type field in container file section header \n " ) ;
2008-07-28 18:44:22 +02:00
return 0 ;
}
equiv_cpu_table = ( struct equiv_cpu_entry * ) vmalloc ( size ) ;
if ( ! equiv_cpu_table ) {
2009-12-08 22:30:50 -08:00
pr_err ( " failed to allocate equivalent CPU table \n " ) ;
2008-07-28 18:44:22 +02:00
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 */
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
{
2009-06-07 22:30:36 +08:00
vfree ( equiv_cpu_table ) ;
equiv_cpu_table = NULL ;
2008-09-11 23:27:52 +02:00
}
2008-07-28 18:44:22 +02:00
2009-05-11 23:48:27 +02:00
static enum ucode_state
generic_load_microcode ( int cpu , const u8 * data , size_t size )
2008-09-11 23:27:52 +02:00
{
struct ucode_cpu_info * uci = ucode_cpu_info + cpu ;
2008-12-16 19:13:00 +01:00
const u8 * ucode_ptr = data ;
void * new_mc = NULL ;
void * mc ;
2008-09-11 23:27:52 +02:00
int new_rev = uci - > cpu_sig . rev ;
unsigned int leftover ;
unsigned long offset ;
2009-05-11 23:48:27 +02:00
enum ucode_state state = UCODE_OK ;
2008-07-28 18:44:22 +02:00
2008-12-16 19:14:05 +01:00
offset = install_equiv_cpu_table ( ucode_ptr ) ;
2008-07-28 18:44:22 +02:00
if ( ! offset ) {
2009-12-08 22:30:50 -08:00
pr_err ( " failed to create equivalent cpu table \n " ) ;
2009-05-11 23:48:27 +02:00
return UCODE_ERROR ;
2008-07-28 18:44:22 +02:00
}
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 ;
2008-12-16 19:14:05 +01:00
mc = get_next_ucode ( ucode_ptr , leftover , & mc_size ) ;
2008-09-11 23:27:52 +02:00
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 ) ) {
2009-06-07 22:30:36 +08:00
vfree ( new_mc ) ;
2008-09-11 23:27:52 +02:00
new_rev = mc_header - > patch_id ;
new_mc = mc ;
2008-12-16 19:11:23 +01:00
} else
2008-09-11 23:27:52 +02:00
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 ) {
2009-06-07 22:30:36 +08:00
vfree ( uci - > mc ) ;
2008-09-23 12:08:44 +02:00
uci - > mc = new_mc ;
2009-12-08 22:30:50 -08:00
pr_debug ( " CPU%d found a matching microcode update with version 0x%x (current=0x%x) \n " ,
2008-12-16 19:11:23 +01:00
cpu , new_rev , uci - > cpu_sig . rev ) ;
2009-05-11 23:48:27 +02:00
} else {
2008-09-11 23:27:52 +02:00
vfree ( new_mc ) ;
2009-05-11 23:48:27 +02:00
state = UCODE_ERROR ;
}
} else
state = UCODE_NFOUND ;
2008-09-11 23:27:52 +02:00
free_equiv_cpu_table ( ) ;
2009-05-11 23:48:27 +02:00
return state ;
2008-09-11 23:27:52 +02:00
}
2009-05-11 23:48:27 +02:00
static enum ucode_state request_microcode_fw ( int cpu , struct device * device )
2008-09-11 23:27:52 +02:00
{
2010-01-22 21:34:56 +01:00
const char * fw_name = " amd-ucode/microcode_amd.bin " ;
const struct firmware * firmware ;
2009-05-11 23:48:27 +02:00
enum ucode_state ret ;
2008-09-11 23:27:52 +02:00
2010-01-22 21:34:56 +01:00
if ( request_firmware ( & firmware , fw_name , device ) ) {
printk ( KERN_ERR " microcode: failed to load file %s \n " , fw_name ) ;
2009-05-11 23:48:27 +02:00
return UCODE_NFOUND ;
2010-01-22 21:34:56 +01:00
}
2008-09-11 23:27:52 +02:00
2009-10-29 14:45:52 +01:00
if ( * ( u32 * ) firmware - > data ! = UCODE_MAGIC ) {
2009-12-08 22:30:50 -08:00
pr_err ( " invalid UCODE_MAGIC (0x%08x) \n " ,
2009-10-29 14:45:52 +01:00
* ( u32 * ) firmware - > data ) ;
return UCODE_ERROR ;
}
2008-12-16 19:14:05 +01:00
ret = generic_load_microcode ( cpu , firmware - > data , firmware - > size ) ;
2008-09-11 23:27:52 +02:00
2010-01-22 21:34:56 +01:00
release_firmware ( firmware ) ;
2008-09-11 23:27:52 +02:00
return ret ;
}
2009-05-11 23:48:27 +02:00
static enum ucode_state
request_microcode_user ( int cpu , const void __user * buf , size_t size )
2008-09-11 23:27:52 +02:00
{
2009-12-08 22:30:50 -08:00
pr_info ( " AMD microcode update via /dev/cpu/microcode not supported \n " ) ;
2009-05-11 23:48:27 +02:00
return UCODE_ERROR ;
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
}