1*4882a593Smuzhiyun#!/usr/bin/env python2 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (c) 2014 Google, Inc 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0+ 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# Intel microcode update tool 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunfrom optparse import OptionParser 10*4882a593Smuzhiyunimport os 11*4882a593Smuzhiyunimport re 12*4882a593Smuzhiyunimport struct 13*4882a593Smuzhiyunimport sys 14*4882a593Smuzhiyun 15*4882a593SmuzhiyunMICROCODE_DIR = 'arch/x86/dts/microcode' 16*4882a593Smuzhiyun 17*4882a593Smuzhiyunclass Microcode: 18*4882a593Smuzhiyun """Holds information about the microcode for a particular model of CPU. 19*4882a593Smuzhiyun 20*4882a593Smuzhiyun Attributes: 21*4882a593Smuzhiyun name: Name of the CPU this microcode is for, including any version 22*4882a593Smuzhiyun information (e.g. 'm12206a7_00000029') 23*4882a593Smuzhiyun model: Model code string (this is cpuid(1).eax, e.g. '206a7') 24*4882a593Smuzhiyun words: List of hex words containing the microcode. The first 16 words 25*4882a593Smuzhiyun are the public header. 26*4882a593Smuzhiyun """ 27*4882a593Smuzhiyun def __init__(self, name, data): 28*4882a593Smuzhiyun self.name = name 29*4882a593Smuzhiyun # Convert data into a list of hex words 30*4882a593Smuzhiyun self.words = [] 31*4882a593Smuzhiyun for value in ''.join(data).split(','): 32*4882a593Smuzhiyun hexval = value.strip() 33*4882a593Smuzhiyun if hexval: 34*4882a593Smuzhiyun self.words.append(int(hexval, 0)) 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun # The model is in the 4rd hex word 37*4882a593Smuzhiyun self.model = '%x' % self.words[3] 38*4882a593Smuzhiyun 39*4882a593Smuzhiyundef ParseFile(fname): 40*4882a593Smuzhiyun """Parse a micrcode.dat file and return the component parts 41*4882a593Smuzhiyun 42*4882a593Smuzhiyun Args: 43*4882a593Smuzhiyun fname: Filename to parse 44*4882a593Smuzhiyun Returns: 45*4882a593Smuzhiyun 3-Tuple: 46*4882a593Smuzhiyun date: String containing date from the file's header 47*4882a593Smuzhiyun license_text: List of text lines for the license file 48*4882a593Smuzhiyun microcodes: List of Microcode objects from the file 49*4882a593Smuzhiyun """ 50*4882a593Smuzhiyun re_date = re.compile('/\* *(.* [0-9]{4}) *\*/$') 51*4882a593Smuzhiyun re_license = re.compile('/[^-*+] *(.*)$') 52*4882a593Smuzhiyun re_name = re.compile('/\* *(.*)\.inc *\*/', re.IGNORECASE) 53*4882a593Smuzhiyun microcodes = {} 54*4882a593Smuzhiyun license_text = [] 55*4882a593Smuzhiyun date = '' 56*4882a593Smuzhiyun data = [] 57*4882a593Smuzhiyun name = None 58*4882a593Smuzhiyun with open(fname) as fd: 59*4882a593Smuzhiyun for line in fd: 60*4882a593Smuzhiyun line = line.rstrip() 61*4882a593Smuzhiyun m_date = re_date.match(line) 62*4882a593Smuzhiyun m_license = re_license.match(line) 63*4882a593Smuzhiyun m_name = re_name.match(line) 64*4882a593Smuzhiyun if m_name: 65*4882a593Smuzhiyun if name: 66*4882a593Smuzhiyun microcodes[name] = Microcode(name, data) 67*4882a593Smuzhiyun name = m_name.group(1).lower() 68*4882a593Smuzhiyun data = [] 69*4882a593Smuzhiyun elif m_license: 70*4882a593Smuzhiyun license_text.append(m_license.group(1)) 71*4882a593Smuzhiyun elif m_date: 72*4882a593Smuzhiyun date = m_date.group(1) 73*4882a593Smuzhiyun else: 74*4882a593Smuzhiyun data.append(line) 75*4882a593Smuzhiyun if name: 76*4882a593Smuzhiyun microcodes[name] = Microcode(name, data) 77*4882a593Smuzhiyun return date, license_text, microcodes 78*4882a593Smuzhiyun 79*4882a593Smuzhiyundef ParseHeaderFiles(fname_list): 80*4882a593Smuzhiyun """Parse a list of header files and return the component parts 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun Args: 83*4882a593Smuzhiyun fname_list: List of files to parse 84*4882a593Smuzhiyun Returns: 85*4882a593Smuzhiyun date: String containing date from the file's header 86*4882a593Smuzhiyun license_text: List of text lines for the license file 87*4882a593Smuzhiyun microcodes: List of Microcode objects from the file 88*4882a593Smuzhiyun """ 89*4882a593Smuzhiyun microcodes = {} 90*4882a593Smuzhiyun license_text = [] 91*4882a593Smuzhiyun date = '' 92*4882a593Smuzhiyun name = None 93*4882a593Smuzhiyun for fname in fname_list: 94*4882a593Smuzhiyun name = os.path.basename(fname).lower() 95*4882a593Smuzhiyun name = os.path.splitext(name)[0] 96*4882a593Smuzhiyun data = [] 97*4882a593Smuzhiyun with open(fname) as fd: 98*4882a593Smuzhiyun license_start = False 99*4882a593Smuzhiyun license_end = False 100*4882a593Smuzhiyun for line in fd: 101*4882a593Smuzhiyun line = line.rstrip() 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun if len(line) >= 2: 104*4882a593Smuzhiyun if line[0] == '/' and line[1] == '*': 105*4882a593Smuzhiyun license_start = True 106*4882a593Smuzhiyun continue 107*4882a593Smuzhiyun if line[0] == '*' and line[1] == '/': 108*4882a593Smuzhiyun license_end = True 109*4882a593Smuzhiyun continue 110*4882a593Smuzhiyun if license_start and not license_end: 111*4882a593Smuzhiyun # Ignore blank line 112*4882a593Smuzhiyun if len(line) > 0: 113*4882a593Smuzhiyun license_text.append(line) 114*4882a593Smuzhiyun continue 115*4882a593Smuzhiyun # Omit anything after the last comma 116*4882a593Smuzhiyun words = line.split(',')[:-1] 117*4882a593Smuzhiyun data += [word + ',' for word in words] 118*4882a593Smuzhiyun microcodes[name] = Microcode(name, data) 119*4882a593Smuzhiyun return date, license_text, microcodes 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun 122*4882a593Smuzhiyundef List(date, microcodes, model): 123*4882a593Smuzhiyun """List the available microcode chunks 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun Args: 126*4882a593Smuzhiyun date: Date of the microcode file 127*4882a593Smuzhiyun microcodes: Dict of Microcode objects indexed by name 128*4882a593Smuzhiyun model: Model string to search for, or None 129*4882a593Smuzhiyun """ 130*4882a593Smuzhiyun print 'Date: %s' % date 131*4882a593Smuzhiyun if model: 132*4882a593Smuzhiyun mcode_list, tried = FindMicrocode(microcodes, model.lower()) 133*4882a593Smuzhiyun print 'Matching models %s:' % (', '.join(tried)) 134*4882a593Smuzhiyun else: 135*4882a593Smuzhiyun print 'All models:' 136*4882a593Smuzhiyun mcode_list = [microcodes[m] for m in microcodes.keys()] 137*4882a593Smuzhiyun for mcode in mcode_list: 138*4882a593Smuzhiyun print '%-20s: model %s' % (mcode.name, mcode.model) 139*4882a593Smuzhiyun 140*4882a593Smuzhiyundef FindMicrocode(microcodes, model): 141*4882a593Smuzhiyun """Find all the microcode chunks which match the given model. 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun This model is something like 306a9 (the value returned in eax from 144*4882a593Smuzhiyun cpuid(1) when running on Intel CPUs). But we allow a partial match, 145*4882a593Smuzhiyun omitting the last 1 or two characters to allow many families to have the 146*4882a593Smuzhiyun same microcode. 147*4882a593Smuzhiyun 148*4882a593Smuzhiyun If the model name is ambiguous we return a list of matches. 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun Args: 151*4882a593Smuzhiyun microcodes: Dict of Microcode objects indexed by name 152*4882a593Smuzhiyun model: String containing model name to find 153*4882a593Smuzhiyun Returns: 154*4882a593Smuzhiyun Tuple: 155*4882a593Smuzhiyun List of matching Microcode objects 156*4882a593Smuzhiyun List of abbreviations we tried 157*4882a593Smuzhiyun """ 158*4882a593Smuzhiyun # Allow a full name to be used 159*4882a593Smuzhiyun mcode = microcodes.get(model) 160*4882a593Smuzhiyun if mcode: 161*4882a593Smuzhiyun return [mcode], [] 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun tried = [] 164*4882a593Smuzhiyun found = [] 165*4882a593Smuzhiyun for i in range(3): 166*4882a593Smuzhiyun abbrev = model[:-i] if i else model 167*4882a593Smuzhiyun tried.append(abbrev) 168*4882a593Smuzhiyun for mcode in microcodes.values(): 169*4882a593Smuzhiyun if mcode.model.startswith(abbrev): 170*4882a593Smuzhiyun found.append(mcode) 171*4882a593Smuzhiyun if found: 172*4882a593Smuzhiyun break 173*4882a593Smuzhiyun return found, tried 174*4882a593Smuzhiyun 175*4882a593Smuzhiyundef CreateFile(date, license_text, mcodes, outfile): 176*4882a593Smuzhiyun """Create a microcode file in U-Boot's .dtsi format 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun Args: 179*4882a593Smuzhiyun date: String containing date of original microcode file 180*4882a593Smuzhiyun license: List of text lines for the license file 181*4882a593Smuzhiyun mcodes: Microcode objects to write (normally only 1) 182*4882a593Smuzhiyun outfile: Filename to write to ('-' for stdout) 183*4882a593Smuzhiyun """ 184*4882a593Smuzhiyun out = '''/*%s 185*4882a593Smuzhiyun * --- 186*4882a593Smuzhiyun * This is a device tree fragment. Use #include to add these properties to a 187*4882a593Smuzhiyun * node. 188*4882a593Smuzhiyun * 189*4882a593Smuzhiyun * Date: %s 190*4882a593Smuzhiyun */ 191*4882a593Smuzhiyun 192*4882a593Smuzhiyuncompatible = "intel,microcode"; 193*4882a593Smuzhiyunintel,header-version = <%d>; 194*4882a593Smuzhiyunintel,update-revision = <%#x>; 195*4882a593Smuzhiyunintel,date-code = <%#x>; 196*4882a593Smuzhiyunintel,processor-signature = <%#x>; 197*4882a593Smuzhiyunintel,checksum = <%#x>; 198*4882a593Smuzhiyunintel,loader-revision = <%d>; 199*4882a593Smuzhiyunintel,processor-flags = <%#x>; 200*4882a593Smuzhiyun 201*4882a593Smuzhiyun/* The first 48-bytes are the public header which repeats the above data */ 202*4882a593Smuzhiyundata = <%s 203*4882a593Smuzhiyun\t>;''' 204*4882a593Smuzhiyun words = '' 205*4882a593Smuzhiyun add_comments = len(mcodes) > 1 206*4882a593Smuzhiyun for mcode in mcodes: 207*4882a593Smuzhiyun if add_comments: 208*4882a593Smuzhiyun words += '\n/* %s */' % mcode.name 209*4882a593Smuzhiyun for i in range(len(mcode.words)): 210*4882a593Smuzhiyun if not (i & 3): 211*4882a593Smuzhiyun words += '\n' 212*4882a593Smuzhiyun val = mcode.words[i] 213*4882a593Smuzhiyun # Change each word so it will be little-endian in the FDT 214*4882a593Smuzhiyun # This data is needed before RAM is available on some platforms so 215*4882a593Smuzhiyun # we cannot do an endianness swap on boot. 216*4882a593Smuzhiyun val = struct.unpack("<I", struct.pack(">I", val))[0] 217*4882a593Smuzhiyun words += '\t%#010x' % val 218*4882a593Smuzhiyun 219*4882a593Smuzhiyun # Use the first microcode for the headers 220*4882a593Smuzhiyun mcode = mcodes[0] 221*4882a593Smuzhiyun 222*4882a593Smuzhiyun # Take care to avoid adding a space before a tab 223*4882a593Smuzhiyun text = '' 224*4882a593Smuzhiyun for line in license_text: 225*4882a593Smuzhiyun if line[0] == '\t': 226*4882a593Smuzhiyun text += '\n *' + line 227*4882a593Smuzhiyun else: 228*4882a593Smuzhiyun text += '\n * ' + line 229*4882a593Smuzhiyun args = [text, date] 230*4882a593Smuzhiyun args += [mcode.words[i] for i in range(7)] 231*4882a593Smuzhiyun args.append(words) 232*4882a593Smuzhiyun if outfile == '-': 233*4882a593Smuzhiyun print out % tuple(args) 234*4882a593Smuzhiyun else: 235*4882a593Smuzhiyun if not outfile: 236*4882a593Smuzhiyun if not os.path.exists(MICROCODE_DIR): 237*4882a593Smuzhiyun print >> sys.stderr, "Creating directory '%s'" % MICROCODE_DIR 238*4882a593Smuzhiyun os.makedirs(MICROCODE_DIR) 239*4882a593Smuzhiyun outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi') 240*4882a593Smuzhiyun print >> sys.stderr, "Writing microcode for '%s' to '%s'" % ( 241*4882a593Smuzhiyun ', '.join([mcode.name for mcode in mcodes]), outfile) 242*4882a593Smuzhiyun with open(outfile, 'w') as fd: 243*4882a593Smuzhiyun print >> fd, out % tuple(args) 244*4882a593Smuzhiyun 245*4882a593Smuzhiyundef MicrocodeTool(): 246*4882a593Smuzhiyun """Run the microcode tool""" 247*4882a593Smuzhiyun commands = 'create,license,list'.split(',') 248*4882a593Smuzhiyun parser = OptionParser() 249*4882a593Smuzhiyun parser.add_option('-d', '--mcfile', type='string', action='store', 250*4882a593Smuzhiyun help='Name of microcode.dat file') 251*4882a593Smuzhiyun parser.add_option('-H', '--headerfile', type='string', action='append', 252*4882a593Smuzhiyun help='Name of .h file containing microcode') 253*4882a593Smuzhiyun parser.add_option('-m', '--model', type='string', action='store', 254*4882a593Smuzhiyun help="Model name to extract ('all' for all)") 255*4882a593Smuzhiyun parser.add_option('-M', '--multiple', type='string', action='store', 256*4882a593Smuzhiyun help="Allow output of multiple models") 257*4882a593Smuzhiyun parser.add_option('-o', '--outfile', type='string', action='store', 258*4882a593Smuzhiyun help='Filename to use for output (- for stdout), default is' 259*4882a593Smuzhiyun ' %s/<name>.dtsi' % MICROCODE_DIR) 260*4882a593Smuzhiyun parser.usage += """ command 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun Process an Intel microcode file (use -h for help). Commands: 263*4882a593Smuzhiyun 264*4882a593Smuzhiyun create Create microcode .dtsi file for a model 265*4882a593Smuzhiyun list List available models in microcode file 266*4882a593Smuzhiyun license Print the license 267*4882a593Smuzhiyun 268*4882a593Smuzhiyun Typical usage: 269*4882a593Smuzhiyun 270*4882a593Smuzhiyun ./tools/microcode-tool -d microcode.dat -m 306a create 271*4882a593Smuzhiyun 272*4882a593Smuzhiyun This will find the appropriate file and write it to %s.""" % MICROCODE_DIR 273*4882a593Smuzhiyun 274*4882a593Smuzhiyun (options, args) = parser.parse_args() 275*4882a593Smuzhiyun if not args: 276*4882a593Smuzhiyun parser.error('Please specify a command') 277*4882a593Smuzhiyun cmd = args[0] 278*4882a593Smuzhiyun if cmd not in commands: 279*4882a593Smuzhiyun parser.error("Unknown command '%s'" % cmd) 280*4882a593Smuzhiyun 281*4882a593Smuzhiyun if (not not options.mcfile) != (not not options.mcfile): 282*4882a593Smuzhiyun parser.error("You must specify either header files or a microcode file, not both") 283*4882a593Smuzhiyun if options.headerfile: 284*4882a593Smuzhiyun date, license_text, microcodes = ParseHeaderFiles(options.headerfile) 285*4882a593Smuzhiyun elif options.mcfile: 286*4882a593Smuzhiyun date, license_text, microcodes = ParseFile(options.mcfile) 287*4882a593Smuzhiyun else: 288*4882a593Smuzhiyun parser.error('You must specify a microcode file (or header files)') 289*4882a593Smuzhiyun 290*4882a593Smuzhiyun if cmd == 'list': 291*4882a593Smuzhiyun List(date, microcodes, options.model) 292*4882a593Smuzhiyun elif cmd == 'license': 293*4882a593Smuzhiyun print '\n'.join(license_text) 294*4882a593Smuzhiyun elif cmd == 'create': 295*4882a593Smuzhiyun if not options.model: 296*4882a593Smuzhiyun parser.error('You must specify a model to create') 297*4882a593Smuzhiyun model = options.model.lower() 298*4882a593Smuzhiyun if options.model == 'all': 299*4882a593Smuzhiyun options.multiple = True 300*4882a593Smuzhiyun mcode_list = microcodes.values() 301*4882a593Smuzhiyun tried = [] 302*4882a593Smuzhiyun else: 303*4882a593Smuzhiyun mcode_list, tried = FindMicrocode(microcodes, model) 304*4882a593Smuzhiyun if not mcode_list: 305*4882a593Smuzhiyun parser.error("Unknown model '%s' (%s) - try 'list' to list" % 306*4882a593Smuzhiyun (model, ', '.join(tried))) 307*4882a593Smuzhiyun if not options.multiple and len(mcode_list) > 1: 308*4882a593Smuzhiyun parser.error("Ambiguous model '%s' (%s) matched %s - try 'list' " 309*4882a593Smuzhiyun "to list or specify a particular file" % 310*4882a593Smuzhiyun (model, ', '.join(tried), 311*4882a593Smuzhiyun ', '.join([m.name for m in mcode_list]))) 312*4882a593Smuzhiyun CreateFile(date, license_text, mcode_list, options.outfile) 313*4882a593Smuzhiyun else: 314*4882a593Smuzhiyun parser.error("Unknown command '%s'" % cmd) 315*4882a593Smuzhiyun 316*4882a593Smuzhiyunif __name__ == "__main__": 317*4882a593Smuzhiyun MicrocodeTool() 318