]>
Commit | Line | Data |
---|---|---|
879ca276 | 1 | #!/usr/bin/env python3 |
83d290c5 | 2 | # SPDX-License-Identifier: GPL-2.0+ |
d2c6181d SG |
3 | # |
4 | # Copyright (c) 2014 Google, Inc | |
5 | # | |
d2c6181d SG |
6 | # Intel microcode update tool |
7 | ||
8 | from optparse import OptionParser | |
9 | import os | |
10 | import re | |
11 | import struct | |
12 | import sys | |
13 | ||
14 | MICROCODE_DIR = 'arch/x86/dts/microcode' | |
15 | ||
16 | class 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 | ||
38 | def 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 | ||
cfcf8ea2 SG |
78 | def 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: | |
aefba6f1 BM |
97 | license_start = False |
98 | license_end = False | |
cfcf8ea2 SG |
99 | for line in fd: |
100 | line = line.rstrip() | |
101 | ||
aefba6f1 BM |
102 | 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 | |
cfcf8ea2 SG |
114 | # 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 | ||
d2c6181d SG |
121 | def 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 | """ | |
879ca276 | 129 | print('Date: %s' % date) |
d2c6181d SG |
130 | if model: |
131 | mcode_list, tried = FindMicrocode(microcodes, model.lower()) | |
879ca276 | 132 | print('Matching models %s:' % (', '.join(tried))) |
d2c6181d | 133 | else: |
879ca276 SG |
134 | print('All models:') |
135 | mcode_list = [microcodes[m] for m in list(microcodes.keys())] | |
d2c6181d | 136 | for mcode in mcode_list: |
879ca276 | 137 | print('%-20s: model %s' % (mcode.name, mcode.model)) |
d2c6181d SG |
138 | |
139 | def 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) | |
879ca276 | 167 | for mcode in list(microcodes.values()): |
d2c6181d SG |
168 | if mcode.model.startswith(abbrev): |
169 | found.append(mcode) | |
170 | if found: | |
171 | break | |
172 | return found, tried | |
173 | ||
cfcf8ea2 | 174 | def CreateFile(date, license_text, mcodes, outfile): |
d2c6181d SG |
175 | """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 | |
cfcf8ea2 | 180 | mcodes: Microcode objects to write (normally only 1) |
d2c6181d SG |
181 | 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 | ||
191 | compatible = "intel,microcode"; | |
192 | intel,header-version = <%d>; | |
193 | intel,update-revision = <%#x>; | |
194 | intel,date-code = <%#x>; | |
195 | intel,processor-signature = <%#x>; | |
196 | intel,checksum = <%#x>; | |
197 | intel,loader-revision = <%d>; | |
198 | intel,processor-flags = <%#x>; | |
199 | ||
200 | /* The first 48-bytes are the public header which repeats the above data */ | |
201 | data = <%s | |
202 | \t>;''' | |
203 | words = '' | |
cfcf8ea2 SG |
204 | 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] | |
d2c6181d SG |
220 | |
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 == '-': | |
879ca276 | 232 | print(out % tuple(args)) |
d2c6181d SG |
233 | else: |
234 | if not outfile: | |
235 | if not os.path.exists(MICROCODE_DIR): | |
879ca276 | 236 | print("Creating directory '%s'" % MICROCODE_DIR, file=sys.stderr) |
d2c6181d SG |
237 | os.makedirs(MICROCODE_DIR) |
238 | outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi') | |
879ca276 SG |
239 | print("Writing microcode for '%s' to '%s'" % ( |
240 | ', '.join([mcode.name for mcode in mcodes]), outfile), file=sys.stderr) | |
d2c6181d | 241 | with open(outfile, 'w') as fd: |
879ca276 | 242 | print(out % tuple(args), file=fd) |
d2c6181d SG |
243 | |
244 | def 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') | |
cfcf8ea2 SG |
250 | parser.add_option('-H', '--headerfile', type='string', action='append', |
251 | help='Name of .h file containing microcode') | |
d2c6181d | 252 | parser.add_option('-m', '--model', type='string', action='store', |
cfcf8ea2 SG |
253 | 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") | |
d2c6181d SG |
256 | 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 | ||
cfcf8ea2 SG |
280 | 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)') | |
d2c6181d SG |
288 | |
289 | if cmd == 'list': | |
290 | List(date, microcodes, options.model) | |
291 | elif cmd == 'license': | |
879ca276 | 292 | print('\n'.join(license_text)) |
d2c6181d SG |
293 | elif cmd == 'create': |
294 | if not options.model: | |
295 | parser.error('You must specify a model to create') | |
296 | model = options.model.lower() | |
cfcf8ea2 SG |
297 | if options.model == 'all': |
298 | options.multiple = True | |
879ca276 | 299 | mcode_list = list(microcodes.values()) |
cfcf8ea2 SG |
300 | tried = [] |
301 | else: | |
302 | mcode_list, tried = FindMicrocode(microcodes, model) | |
d2c6181d SG |
303 | if not mcode_list: |
304 | parser.error("Unknown model '%s' (%s) - try 'list' to list" % | |
305 | (model, ', '.join(tried))) | |
cfcf8ea2 | 306 | if not options.multiple and len(mcode_list) > 1: |
d2c6181d SG |
307 | 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]))) | |
cfcf8ea2 | 311 | CreateFile(date, license_text, mcode_list, options.outfile) |
d2c6181d SG |
312 | else: |
313 | parser.error("Unknown command '%s'" % cmd) | |
314 | ||
315 | if __name__ == "__main__": | |
316 | MicrocodeTool() |