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