Commit d2c6181d2d2afe00399cf0c8d9deafcb66b77330

Authored by Simon Glass
1 parent 255fd5caa5

x86: Add a script to process Intel microcode files

Intel delivers microcode updates in a microcode.dat file which must be
split up into individual files for each CPU. Add a tool which performs
this task. It can list available microcode updates for each model and
produce a new microcode update in U-Boot's .dtsi format.

Signed-off-by: Simon Glass <sjg@chromium.org>
Tested-by: Bin Meng <bmeng.cn@gmail.com>

Showing 2 changed files with 254 additions and 0 deletions Side-by-side Diff

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