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
*/
# include <linux/platform_device.h>
2008-07-28 18:44:22 +02:00
# include <linux/capability.h>
# include <linux/miscdevice.h>
2009-03-11 11:19:46 +01:00
# include <linux/firmware.h>
2008-07-28 18:44:22 +02:00
# include <linux/spinlock.h>
2009-03-11 11:19:46 +01:00
# include <linux/cpumask.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/mutex.h>
2009-03-11 11:19:46 +01:00
# include <linux/sched.h>
# include <linux/init.h>
# include <linux/slab.h>
2008-07-28 18:44:22 +02:00
# include <linux/cpu.h>
# include <linux/pci.h>
2009-03-11 11:19:46 +01:00
# include <linux/fs.h>
# include <linux/mm.h>
2008-07-28 18:44:22 +02:00
# 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
/* 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-12-16 19:16:34 +01:00
u32 dummy ;
2008-07-28 18:44:22 +02:00
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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_WARNING " microcode: CPU%d: AMD CPU family 0x%x not "
" supported \n " , cpu , c - > x86 ) ;
2008-08-20 00:22:26 +02:00
return - 1 ;
2008-07-28 18:44:22 +02:00
}
2008-12-16 19:16:34 +01:00
rdmsr ( MSR_AMD64_PATCH_LEVEL , csig - > rev , dummy ) ;
2008-12-16 19:22:36 +01:00
printk ( KERN_INFO " microcode: 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 + + ;
}
if ( ! equiv_cpu_id ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_WARNING " microcode: CPU%d: cpu revision "
" not listed in equivalent cpu table \n " , cpu ) ;
2008-07-28 18:44:22 +02:00
return 0 ;
}
2008-12-16 19:07:47 +01:00
if ( mc_header - > processor_rev_id ! = equiv_cpu_id ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: CPU%d: patch mismatch "
" (processor_rev_id: %x, equiv_cpu_id: %x) \n " ,
2008-12-16 19:07:47 +01:00
cpu , 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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: CPU%d: loading of chipset "
2008-12-16 19:20:21 +01:00
" specific code not yet supported \n " , cpu ) ;
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 ;
}
static void apply_microcode_amd ( int cpu )
{
unsigned long flags ;
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 )
2008-07-28 18:44:22 +02:00
return ;
spin_lock_irqsave ( & microcode_update_lock , flags ) ;
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
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-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: CPU%d: update failed "
" (for patch_level=0x%x) \n " , cpu , mc_amd - > hdr . patch_id ) ;
2008-07-28 18:44:22 +02:00
return ;
}
2008-12-16 19:22:36 +01:00
printk ( KERN_INFO " microcode: CPU%d: updated (new patch_level=0x%x) \n " ,
cpu , rev ) ;
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-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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: 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-12-16 19:22:36 +01:00
printk ( KERN_DEBUG " 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-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: 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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: 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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: 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
{
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-12-16 19:14:05 +01:00
static int 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 ;
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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: failed to create "
" equivalent cpu table \n " ) ;
2008-07-28 18:44:22 +02:00
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 ;
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 ) ) {
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 ;
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 ) {
2008-09-23 12:08:44 +02:00
if ( uci - > mc )
vfree ( uci - > mc ) ;
uci - > mc = new_mc ;
2008-12-16 19:11:23 +01:00
pr_debug ( " microcode: CPU%d found a matching microcode "
" update with version 0x%x (current=0x%x) \n " ,
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 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 ) {
2008-12-16 19:22:36 +01:00
printk ( KERN_ERR " microcode: failed to load file %s \n " , fw_name ) ;
2008-09-11 23:27:52 +02:00
return ret ;
}
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
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-12-16 19:22:36 +01:00
printk ( KERN_INFO " microcode: AMD microcode update via "
" /dev/cpu/microcode not supported \n " ) ;
2008-09-23 22:56:35 +02:00
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
}