#!/usr/bin/env python2 
 | 
# 
 | 
# Copyright (c) 2014 Google, Inc 
 | 
# 
 | 
# SPDX-License-Identifier:      GPL-2.0+ 
 | 
# 
 | 
# Intel microcode update tool 
 | 
  
 | 
from optparse import OptionParser 
 | 
import os 
 | 
import re 
 | 
import struct 
 | 
import sys 
 | 
  
 | 
MICROCODE_DIR = 'arch/x86/dts/microcode' 
 | 
  
 | 
class Microcode: 
 | 
    """Holds information about the microcode for a particular model of CPU. 
 | 
  
 | 
    Attributes: 
 | 
        name:  Name of the CPU this microcode is for, including any version 
 | 
                   information (e.g. 'm12206a7_00000029') 
 | 
        model: Model code string (this is cpuid(1).eax, e.g. '206a7') 
 | 
        words: List of hex words containing the microcode. The first 16 words 
 | 
                   are the public header. 
 | 
    """ 
 | 
    def __init__(self, name, data): 
 | 
        self.name = name 
 | 
        # Convert data into a list of hex words 
 | 
        self.words = [] 
 | 
        for value in ''.join(data).split(','): 
 | 
            hexval = value.strip() 
 | 
            if hexval: 
 | 
                self.words.append(int(hexval, 0)) 
 | 
  
 | 
        # The model is in the 4rd hex word 
 | 
        self.model = '%x' % self.words[3] 
 | 
  
 | 
def ParseFile(fname): 
 | 
    """Parse a micrcode.dat file and return the component parts 
 | 
  
 | 
    Args: 
 | 
        fname: Filename to parse 
 | 
    Returns: 
 | 
        3-Tuple: 
 | 
            date:         String containing date from the file's header 
 | 
            license_text: List of text lines for the license file 
 | 
            microcodes:   List of Microcode objects from the file 
 | 
    """ 
 | 
    re_date = re.compile('/\* *(.* [0-9]{4}) *\*/$') 
 | 
    re_license = re.compile('/[^-*+] *(.*)$') 
 | 
    re_name = re.compile('/\* *(.*)\.inc *\*/', re.IGNORECASE) 
 | 
    microcodes = {} 
 | 
    license_text = [] 
 | 
    date = '' 
 | 
    data = [] 
 | 
    name = None 
 | 
    with open(fname) as fd: 
 | 
        for line in fd: 
 | 
            line = line.rstrip() 
 | 
            m_date = re_date.match(line) 
 | 
            m_license = re_license.match(line) 
 | 
            m_name = re_name.match(line) 
 | 
            if m_name: 
 | 
                if name: 
 | 
                    microcodes[name] = Microcode(name, data) 
 | 
                name = m_name.group(1).lower() 
 | 
                data = [] 
 | 
            elif m_license: 
 | 
                license_text.append(m_license.group(1)) 
 | 
            elif m_date: 
 | 
                date = m_date.group(1) 
 | 
            else: 
 | 
                data.append(line) 
 | 
    if name: 
 | 
        microcodes[name] = Microcode(name, data) 
 | 
    return date, license_text, microcodes 
 | 
  
 | 
def ParseHeaderFiles(fname_list): 
 | 
    """Parse a list of header files and return the component parts 
 | 
  
 | 
    Args: 
 | 
        fname_list: List of files to parse 
 | 
    Returns: 
 | 
            date:         String containing date from the file's header 
 | 
            license_text: List of text lines for the license file 
 | 
            microcodes:   List of Microcode objects from the file 
 | 
    """ 
 | 
    microcodes = {} 
 | 
    license_text = [] 
 | 
    date = '' 
 | 
    name = None 
 | 
    for fname in fname_list: 
 | 
        name = os.path.basename(fname).lower() 
 | 
        name = os.path.splitext(name)[0] 
 | 
        data = [] 
 | 
        with open(fname) as fd: 
 | 
            license_start = False 
 | 
            license_end = False 
 | 
            for line in fd: 
 | 
                line = line.rstrip() 
 | 
  
 | 
                if len(line) >= 2: 
 | 
                    if line[0] == '/' and line[1] == '*': 
 | 
                        license_start = True 
 | 
                        continue 
 | 
                    if line[0] == '*' and line[1] == '/': 
 | 
                        license_end = True 
 | 
                        continue 
 | 
                if license_start and not license_end: 
 | 
                    # Ignore blank line 
 | 
                    if len(line) > 0: 
 | 
                        license_text.append(line) 
 | 
                    continue 
 | 
                # Omit anything after the last comma 
 | 
                words = line.split(',')[:-1] 
 | 
                data += [word + ',' for word in words] 
 | 
        microcodes[name] = Microcode(name, data) 
 | 
    return date, license_text, microcodes 
 | 
  
 | 
  
 | 
def List(date, microcodes, model): 
 | 
    """List the available microcode chunks 
 | 
  
 | 
    Args: 
 | 
        date:           Date of the microcode file 
 | 
        microcodes:     Dict of Microcode objects indexed by name 
 | 
        model:          Model string to search for, or None 
 | 
    """ 
 | 
    print 'Date: %s' % date 
 | 
    if model: 
 | 
        mcode_list, tried = FindMicrocode(microcodes, model.lower()) 
 | 
        print 'Matching models %s:' % (', '.join(tried)) 
 | 
    else: 
 | 
        print 'All models:' 
 | 
        mcode_list = [microcodes[m] for m in microcodes.keys()] 
 | 
    for mcode in mcode_list: 
 | 
        print '%-20s: model %s' % (mcode.name, mcode.model) 
 | 
  
 | 
def FindMicrocode(microcodes, model): 
 | 
    """Find all the microcode chunks which match the given model. 
 | 
  
 | 
    This model is something like 306a9 (the value returned in eax from 
 | 
    cpuid(1) when running on Intel CPUs). But we allow a partial match, 
 | 
    omitting the last 1 or two characters to allow many families to have the 
 | 
    same microcode. 
 | 
  
 | 
    If the model name is ambiguous we return a list of matches. 
 | 
  
 | 
    Args: 
 | 
        microcodes: Dict of Microcode objects indexed by name 
 | 
        model:      String containing model name to find 
 | 
    Returns: 
 | 
        Tuple: 
 | 
            List of matching Microcode objects 
 | 
            List of abbreviations we tried 
 | 
    """ 
 | 
    # Allow a full name to be used 
 | 
    mcode = microcodes.get(model) 
 | 
    if mcode: 
 | 
        return [mcode], [] 
 | 
  
 | 
    tried = [] 
 | 
    found = [] 
 | 
    for i in range(3): 
 | 
        abbrev = model[:-i] if i else model 
 | 
        tried.append(abbrev) 
 | 
        for mcode in microcodes.values(): 
 | 
            if mcode.model.startswith(abbrev): 
 | 
                found.append(mcode) 
 | 
        if found: 
 | 
            break 
 | 
    return found, tried 
 | 
  
 | 
def CreateFile(date, license_text, mcodes, outfile): 
 | 
    """Create a microcode file in U-Boot's .dtsi format 
 | 
  
 | 
    Args: 
 | 
        date:       String containing date of original microcode file 
 | 
        license:    List of text lines for the license file 
 | 
        mcodes:      Microcode objects to write (normally only 1) 
 | 
        outfile:    Filename to write to ('-' for stdout) 
 | 
    """ 
 | 
    out = '''/*%s 
 | 
 * --- 
 | 
 * This is a device tree fragment. Use #include to add these properties to a 
 | 
 * node. 
 | 
 * 
 | 
 * Date: %s 
 | 
 */ 
 | 
  
 | 
compatible = "intel,microcode"; 
 | 
intel,header-version = <%d>; 
 | 
intel,update-revision = <%#x>; 
 | 
intel,date-code = <%#x>; 
 | 
intel,processor-signature = <%#x>; 
 | 
intel,checksum = <%#x>; 
 | 
intel,loader-revision = <%d>; 
 | 
intel,processor-flags = <%#x>; 
 | 
  
 | 
/* The first 48-bytes are the public header which repeats the above data */ 
 | 
data = <%s 
 | 
\t>;''' 
 | 
    words = '' 
 | 
    add_comments = len(mcodes) > 1 
 | 
    for mcode in mcodes: 
 | 
        if add_comments: 
 | 
            words += '\n/* %s */' % mcode.name 
 | 
        for i in range(len(mcode.words)): 
 | 
            if not (i & 3): 
 | 
                words += '\n' 
 | 
            val = mcode.words[i] 
 | 
            # Change each word so it will be little-endian in the FDT 
 | 
            # This data is needed before RAM is available on some platforms so 
 | 
            # we cannot do an endianness swap on boot. 
 | 
            val = struct.unpack("<I", struct.pack(">I", val))[0] 
 | 
            words += '\t%#010x' % val 
 | 
  
 | 
    # Use the first microcode for the headers 
 | 
    mcode = mcodes[0] 
 | 
  
 | 
    # Take care to avoid adding a space before a tab 
 | 
    text = '' 
 | 
    for line in license_text: 
 | 
        if line[0] == '\t': 
 | 
            text += '\n *' + line 
 | 
        else: 
 | 
            text += '\n * ' + line 
 | 
    args = [text, date] 
 | 
    args += [mcode.words[i] for i in range(7)] 
 | 
    args.append(words) 
 | 
    if outfile == '-': 
 | 
        print out % tuple(args) 
 | 
    else: 
 | 
        if not outfile: 
 | 
            if not os.path.exists(MICROCODE_DIR): 
 | 
                print >> sys.stderr, "Creating directory '%s'" % MICROCODE_DIR 
 | 
                os.makedirs(MICROCODE_DIR) 
 | 
            outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi') 
 | 
        print >> sys.stderr, "Writing microcode for '%s' to '%s'" % ( 
 | 
                ', '.join([mcode.name for mcode in mcodes]), outfile) 
 | 
        with open(outfile, 'w') as fd: 
 | 
            print >> fd, out % tuple(args) 
 | 
  
 | 
def MicrocodeTool(): 
 | 
    """Run the microcode tool""" 
 | 
    commands = 'create,license,list'.split(',') 
 | 
    parser = OptionParser() 
 | 
    parser.add_option('-d', '--mcfile', type='string', action='store', 
 | 
                    help='Name of microcode.dat file') 
 | 
    parser.add_option('-H', '--headerfile', type='string', action='append', 
 | 
                    help='Name of .h file containing microcode') 
 | 
    parser.add_option('-m', '--model', type='string', action='store', 
 | 
                    help="Model name to extract ('all' for all)") 
 | 
    parser.add_option('-M', '--multiple', type='string', action='store', 
 | 
                    help="Allow output of multiple models") 
 | 
    parser.add_option('-o', '--outfile', type='string', action='store', 
 | 
                    help='Filename to use for output (- for stdout), default is' 
 | 
                    ' %s/<name>.dtsi' % MICROCODE_DIR) 
 | 
    parser.usage += """ command 
 | 
  
 | 
    Process an Intel microcode file (use -h for help). Commands: 
 | 
  
 | 
       create     Create microcode .dtsi file for a model 
 | 
       list       List available models in microcode file 
 | 
       license    Print the license 
 | 
  
 | 
    Typical usage: 
 | 
  
 | 
       ./tools/microcode-tool -d microcode.dat -m 306a create 
 | 
  
 | 
    This will find the appropriate file and write it to %s.""" % MICROCODE_DIR 
 | 
  
 | 
    (options, args) = parser.parse_args() 
 | 
    if not args: 
 | 
        parser.error('Please specify a command') 
 | 
    cmd = args[0] 
 | 
    if cmd not in commands: 
 | 
        parser.error("Unknown command '%s'" % cmd) 
 | 
  
 | 
    if (not not options.mcfile) != (not not options.mcfile): 
 | 
        parser.error("You must specify either header files or a microcode file, not both") 
 | 
    if options.headerfile: 
 | 
        date, license_text, microcodes = ParseHeaderFiles(options.headerfile) 
 | 
    elif options.mcfile: 
 | 
        date, license_text, microcodes = ParseFile(options.mcfile) 
 | 
    else: 
 | 
        parser.error('You must specify a microcode file (or header files)') 
 | 
  
 | 
    if cmd == 'list': 
 | 
        List(date, microcodes, options.model) 
 | 
    elif cmd == 'license': 
 | 
        print '\n'.join(license_text) 
 | 
    elif cmd == 'create': 
 | 
        if not options.model: 
 | 
            parser.error('You must specify a model to create') 
 | 
        model = options.model.lower() 
 | 
        if options.model == 'all': 
 | 
            options.multiple = True 
 | 
            mcode_list = microcodes.values() 
 | 
            tried = [] 
 | 
        else: 
 | 
            mcode_list, tried = FindMicrocode(microcodes, model) 
 | 
        if not mcode_list: 
 | 
            parser.error("Unknown model '%s' (%s) - try 'list' to list" % 
 | 
                        (model, ', '.join(tried))) 
 | 
        if not options.multiple and len(mcode_list) > 1: 
 | 
            parser.error("Ambiguous model '%s' (%s) matched %s - try 'list' " 
 | 
                        "to list or specify a particular file" % 
 | 
                        (model, ', '.join(tried), 
 | 
                        ', '.join([m.name for m in mcode_list]))) 
 | 
        CreateFile(date, license_text, mcode_list, options.outfile) 
 | 
    else: 
 | 
        parser.error("Unknown command '%s'" % cmd) 
 | 
  
 | 
if __name__ == "__main__": 
 | 
    MicrocodeTool() 
 |