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