Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python2
      2 # SPDX-License-Identifier: GPL-2.0+
      3 #
      4 # Copyright (c) 2014 Google, Inc
      5 #
      6 # Intel microcode update tool
      7 
      8 from optparse import OptionParser
      9 import os
     10 import re
     11 import struct
     12 import sys
     13 
     14 MICROCODE_DIR = 'arch/x86/dts/microcode'
     15 
     16 class 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 
     38 def 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 
     78 def 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 
    121 def 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 microcodes.keys()]
    136     for mcode in mcode_list:
    137         print '%-20s: model %s' % (mcode.name, mcode.model)
    138 
    139 def 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 microcodes.values():
    168             if mcode.model.startswith(abbrev):
    169                 found.append(mcode)
    170         if found:
    171             break
    172     return found, tried
    173 
    174 def 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 
    191 compatible = "intel,microcode";
    192 intel,header-version = <%d>;
    193 intel,update-revision = <%#x>;
    194 intel,date-code = <%#x>;
    195 intel,processor-signature = <%#x>;
    196 intel,checksum = <%#x>;
    197 intel,loader-revision = <%d>;
    198 intel,processor-flags = <%#x>;
    199 
    200 /* The first 48-bytes are the public header which repeats the above data */
    201 data = <%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 >> sys.stderr, "Creating directory '%s'" % MICROCODE_DIR
    237                 os.makedirs(MICROCODE_DIR)
    238             outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi')
    239         print >> sys.stderr, "Writing microcode for '%s' to '%s'" % (
    240                 ', '.join([mcode.name for mcode in mcodes]), outfile)
    241         with open(outfile, 'w') as fd:
    242             print >> fd, out % tuple(args)
    243 
    244 def 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 = 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 
    315 if __name__ == "__main__":
    316     MicrocodeTool()
    317