/tools/microcode-tool.py
Python | 317 lines | 260 code | 12 blank | 45 comment | 29 complexity | 92778a74bc4dd53fce0cfcce6302670f MD5 | raw file
- #!/usr/bin/env python
- #
- # 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()