blob: 790c27e45987e197ed450469d6e3791ee48c8be2 [file] [log] [blame]
Simon Glassd2c61812014-12-16 20:21:02 -07001#!/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
Simon Glasscfcf8ea2015-01-27 22:13:26 -070079def 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:
Bin Mengaefba6f2015-12-11 02:55:46 -080098 license_start = False
99 license_end = False
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700100 for line in fd:
101 line = line.rstrip()
102
Bin Mengaefba6f2015-12-11 02:55:46 -0800103 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
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700115 # 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
Simon Glassd2c61812014-12-16 20:21:02 -0700122def 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
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700175def CreateFile(date, license_text, mcodes, outfile):
Simon Glassd2c61812014-12-16 20:21:02 -0700176 """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
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700181 mcodes: Microcode objects to write (normally only 1)
Simon Glassd2c61812014-12-16 20:21:02 -0700182 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 = ''
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700205 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]
Simon Glassd2c61812014-12-16 20:21:02 -0700221
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')
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700240 print >> sys.stderr, "Writing microcode for '%s' to '%s'" % (
241 ', '.join([mcode.name for mcode in mcodes]), outfile)
Simon Glassd2c61812014-12-16 20:21:02 -0700242 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')
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700251 parser.add_option('-H', '--headerfile', type='string', action='append',
252 help='Name of .h file containing microcode')
Simon Glassd2c61812014-12-16 20:21:02 -0700253 parser.add_option('-m', '--model', type='string', action='store',
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700254 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")
Simon Glassd2c61812014-12-16 20:21:02 -0700257 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
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700281 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)')
Simon Glassd2c61812014-12-16 20:21:02 -0700289
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()
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700298 if options.model == 'all':
299 options.multiple = True
300 mcode_list = microcodes.values()
301 tried = []
302 else:
303 mcode_list, tried = FindMicrocode(microcodes, model)
Simon Glassd2c61812014-12-16 20:21:02 -0700304 if not mcode_list:
305 parser.error("Unknown model '%s' (%s) - try 'list' to list" %
306 (model, ', '.join(tried)))
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700307 if not options.multiple and len(mcode_list) > 1:
Simon Glassd2c61812014-12-16 20:21:02 -0700308 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])))
Simon Glasscfcf8ea2015-01-27 22:13:26 -0700312 CreateFile(date, license_text, mcode_list, options.outfile)
Simon Glassd2c61812014-12-16 20:21:02 -0700313 else:
314 parser.error("Unknown command '%s'" % cmd)
315
316if __name__ == "__main__":
317 MicrocodeTool()