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