blob: 71c2e91566b54605282a35d4394fc5d5a3c204cd [file] [log] [blame]
Simon Glass750deb92014-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 Glassa1616a82015-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:
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
Simon Glass750deb92014-12-16 20:21:02 -0700108def 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
Simon Glassa1616a82015-01-27 22:13:26 -0700161def CreateFile(date, license_text, mcodes, outfile):
Simon Glass750deb92014-12-16 20:21:02 -0700162 """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
Simon Glassa1616a82015-01-27 22:13:26 -0700167 mcodes: Microcode objects to write (normally only 1)
Simon Glass750deb92014-12-16 20:21:02 -0700168 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 = ''
Simon Glassa1616a82015-01-27 22:13:26 -0700191 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]
Simon Glass750deb92014-12-16 20:21:02 -0700207
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')
Simon Glassa1616a82015-01-27 22:13:26 -0700226 print >> sys.stderr, "Writing microcode for '%s' to '%s'" % (
227 ', '.join([mcode.name for mcode in mcodes]), outfile)
Simon Glass750deb92014-12-16 20:21:02 -0700228 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')
Simon Glassa1616a82015-01-27 22:13:26 -0700237 parser.add_option('-H', '--headerfile', type='string', action='append',
238 help='Name of .h file containing microcode')
Simon Glass750deb92014-12-16 20:21:02 -0700239 parser.add_option('-m', '--model', type='string', action='store',
Simon Glassa1616a82015-01-27 22:13:26 -0700240 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")
Simon Glass750deb92014-12-16 20:21:02 -0700243 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
Simon Glassa1616a82015-01-27 22:13:26 -0700267 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)')
Simon Glass750deb92014-12-16 20:21:02 -0700275
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()
Simon Glassa1616a82015-01-27 22:13:26 -0700284 if options.model == 'all':
285 options.multiple = True
286 mcode_list = microcodes.values()
287 tried = []
288 else:
289 mcode_list, tried = FindMicrocode(microcodes, model)
Simon Glass750deb92014-12-16 20:21:02 -0700290 if not mcode_list:
291 parser.error("Unknown model '%s' (%s) - try 'list' to list" %
292 (model, ', '.join(tried)))
Simon Glassa1616a82015-01-27 22:13:26 -0700293 if not options.multiple and len(mcode_list) > 1:
Simon Glass750deb92014-12-16 20:21:02 -0700294 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])))
Simon Glassa1616a82015-01-27 22:13:26 -0700298 CreateFile(date, license_text, mcode_list, options.outfile)
Simon Glass750deb92014-12-16 20:21:02 -0700299 else:
300 parser.error("Unknown command '%s'" % cmd)
301
302if __name__ == "__main__":
303 MicrocodeTool()