blob: 24c02c4fca1400de4cca31630161204170a1fe33 [file] [log] [blame]
Simon Glassdb51b512019-10-31 07:42:56 -06001#!/usr/bin/env python3
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glass750deb92014-12-16 20:21:02 -07003#
4# Copyright (c) 2014 Google, Inc
5#
Simon Glass750deb92014-12-16 20:21:02 -07006# Intel microcode update tool
7
8from optparse import OptionParser
9import os
10import re
11import struct
12import sys
13
14MICROCODE_DIR = 'arch/x86/dts/microcode'
15
16class 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
38def 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
Simon Glassa1616a82015-01-27 22:13:26 -070078def 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:
Bin Meng580ea682015-12-11 02:55:46 -080097 license_start = False
98 license_end = False
Simon Glassa1616a82015-01-27 22:13:26 -070099 for line in fd:
100 line = line.rstrip()
101
Bin Meng580ea682015-12-11 02:55:46 -0800102 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
Simon Glassa1616a82015-01-27 22:13:26 -0700114 # 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
Simon Glass750deb92014-12-16 20:21:02 -0700121def 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 """
Simon Glassdb51b512019-10-31 07:42:56 -0600129 print('Date: %s' % date)
Simon Glass750deb92014-12-16 20:21:02 -0700130 if model:
131 mcode_list, tried = FindMicrocode(microcodes, model.lower())
Simon Glassdb51b512019-10-31 07:42:56 -0600132 print('Matching models %s:' % (', '.join(tried)))
Simon Glass750deb92014-12-16 20:21:02 -0700133 else:
Simon Glassdb51b512019-10-31 07:42:56 -0600134 print('All models:')
135 mcode_list = [microcodes[m] for m in list(microcodes.keys())]
Simon Glass750deb92014-12-16 20:21:02 -0700136 for mcode in mcode_list:
Simon Glassdb51b512019-10-31 07:42:56 -0600137 print('%-20s: model %s' % (mcode.name, mcode.model))
Simon Glass750deb92014-12-16 20:21:02 -0700138
139def 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)
Simon Glassdb51b512019-10-31 07:42:56 -0600167 for mcode in list(microcodes.values()):
Simon Glass750deb92014-12-16 20:21:02 -0700168 if mcode.model.startswith(abbrev):
169 found.append(mcode)
170 if found:
171 break
172 return found, tried
173
Simon Glassa1616a82015-01-27 22:13:26 -0700174def CreateFile(date, license_text, mcodes, outfile):
Simon Glass750deb92014-12-16 20:21:02 -0700175 """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
Simon Glassa1616a82015-01-27 22:13:26 -0700180 mcodes: Microcode objects to write (normally only 1)
Simon Glass750deb92014-12-16 20:21:02 -0700181 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
191compatible = "intel,microcode";
192intel,header-version = <%d>;
193intel,update-revision = <%#x>;
194intel,date-code = <%#x>;
195intel,processor-signature = <%#x>;
196intel,checksum = <%#x>;
197intel,loader-revision = <%d>;
198intel,processor-flags = <%#x>;
199
200/* The first 48-bytes are the public header which repeats the above data */
201data = <%s
202\t>;'''
203 words = ''
Simon Glassa1616a82015-01-27 22:13:26 -0700204 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]
Simon Glass750deb92014-12-16 20:21:02 -0700220
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 == '-':
Simon Glassdb51b512019-10-31 07:42:56 -0600232 print(out % tuple(args))
Simon Glass750deb92014-12-16 20:21:02 -0700233 else:
234 if not outfile:
235 if not os.path.exists(MICROCODE_DIR):
Simon Glassdb51b512019-10-31 07:42:56 -0600236 print("Creating directory '%s'" % MICROCODE_DIR, file=sys.stderr)
Simon Glass750deb92014-12-16 20:21:02 -0700237 os.makedirs(MICROCODE_DIR)
238 outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi')
Simon Glassdb51b512019-10-31 07:42:56 -0600239 print("Writing microcode for '%s' to '%s'" % (
240 ', '.join([mcode.name for mcode in mcodes]), outfile), file=sys.stderr)
Simon Glass750deb92014-12-16 20:21:02 -0700241 with open(outfile, 'w') as fd:
Simon Glassdb51b512019-10-31 07:42:56 -0600242 print(out % tuple(args), file=fd)
Simon Glass750deb92014-12-16 20:21:02 -0700243
244def 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')
Simon Glassa1616a82015-01-27 22:13:26 -0700250 parser.add_option('-H', '--headerfile', type='string', action='append',
251 help='Name of .h file containing microcode')
Simon Glass750deb92014-12-16 20:21:02 -0700252 parser.add_option('-m', '--model', type='string', action='store',
Simon Glassa1616a82015-01-27 22:13:26 -0700253 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")
Simon Glass750deb92014-12-16 20:21:02 -0700256 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
Simon Glassa1616a82015-01-27 22:13:26 -0700280 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)')
Simon Glass750deb92014-12-16 20:21:02 -0700288
289 if cmd == 'list':
290 List(date, microcodes, options.model)
291 elif cmd == 'license':
Simon Glassdb51b512019-10-31 07:42:56 -0600292 print('\n'.join(license_text))
Simon Glass750deb92014-12-16 20:21:02 -0700293 elif cmd == 'create':
294 if not options.model:
295 parser.error('You must specify a model to create')
296 model = options.model.lower()
Simon Glassa1616a82015-01-27 22:13:26 -0700297 if options.model == 'all':
298 options.multiple = True
Simon Glassdb51b512019-10-31 07:42:56 -0600299 mcode_list = list(microcodes.values())
Simon Glassa1616a82015-01-27 22:13:26 -0700300 tried = []
301 else:
302 mcode_list, tried = FindMicrocode(microcodes, model)
Simon Glass750deb92014-12-16 20:21:02 -0700303 if not mcode_list:
304 parser.error("Unknown model '%s' (%s) - try 'list' to list" %
305 (model, ', '.join(tried)))
Simon Glassa1616a82015-01-27 22:13:26 -0700306 if not options.multiple and len(mcode_list) > 1:
Simon Glass750deb92014-12-16 20:21:02 -0700307 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])))
Simon Glassa1616a82015-01-27 22:13:26 -0700311 CreateFile(date, license_text, mcode_list, options.outfile)
Simon Glass750deb92014-12-16 20:21:02 -0700312 else:
313 parser.error("Unknown command '%s'" % cmd)
314
315if __name__ == "__main__":
316 MicrocodeTool()