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