Commit d2c6181d2d2afe00399cf0c8d9deafcb66b77330
1 parent
255fd5caa5
Exists in
v2017.01-smarct4x
and in
37 other branches
x86: Add a script to process Intel microcode files
Intel delivers microcode updates in a microcode.dat file which must be split up into individual files for each CPU. Add a tool which performs this task. It can list available microcode updates for each model and produce a new microcode update in U-Boot's .dtsi format. Signed-off-by: Simon Glass <sjg@chromium.org> Tested-by: Bin Meng <bmeng.cn@gmail.com>
Showing 2 changed files with 254 additions and 0 deletions Side-by-side Diff
tools/microcode-tool
1 | +microcode-tool.py |
tools/microcode-tool.py
1 | +#!/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 | + | |
9 | +from optparse import OptionParser | |
10 | +import os | |
11 | +import re | |
12 | +import struct | |
13 | +import sys | |
14 | + | |
15 | +MICROCODE_DIR = 'arch/x86/dts/microcode' | |
16 | + | |
17 | +class 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 | + | |
39 | +def 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 | + | |
79 | +def List(date, microcodes, model): | |
80 | + """List the available microcode chunks | |
81 | + | |
82 | + Args: | |
83 | + date: Date of the microcode file | |
84 | + microcodes: Dict of Microcode objects indexed by name | |
85 | + model: Model string to search for, or None | |
86 | + """ | |
87 | + print 'Date: %s' % date | |
88 | + if model: | |
89 | + mcode_list, tried = FindMicrocode(microcodes, model.lower()) | |
90 | + print 'Matching models %s:' % (', '.join(tried)) | |
91 | + else: | |
92 | + print 'All models:' | |
93 | + mcode_list = [microcodes[m] for m in microcodes.keys()] | |
94 | + for mcode in mcode_list: | |
95 | + print '%-20s: model %s' % (mcode.name, mcode.model) | |
96 | + | |
97 | +def FindMicrocode(microcodes, model): | |
98 | + """Find all the microcode chunks which match the given model. | |
99 | + | |
100 | + This model is something like 306a9 (the value returned in eax from | |
101 | + cpuid(1) when running on Intel CPUs). But we allow a partial match, | |
102 | + omitting the last 1 or two characters to allow many families to have the | |
103 | + same microcode. | |
104 | + | |
105 | + If the model name is ambiguous we return a list of matches. | |
106 | + | |
107 | + Args: | |
108 | + microcodes: Dict of Microcode objects indexed by name | |
109 | + model: String containing model name to find | |
110 | + Returns: | |
111 | + Tuple: | |
112 | + List of matching Microcode objects | |
113 | + List of abbreviations we tried | |
114 | + """ | |
115 | + # Allow a full name to be used | |
116 | + mcode = microcodes.get(model) | |
117 | + if mcode: | |
118 | + return [mcode], [] | |
119 | + | |
120 | + tried = [] | |
121 | + found = [] | |
122 | + for i in range(3): | |
123 | + abbrev = model[:-i] if i else model | |
124 | + tried.append(abbrev) | |
125 | + for mcode in microcodes.values(): | |
126 | + if mcode.model.startswith(abbrev): | |
127 | + found.append(mcode) | |
128 | + if found: | |
129 | + break | |
130 | + return found, tried | |
131 | + | |
132 | +def CreateFile(date, license_text, mcode, outfile): | |
133 | + """Create a microcode file in U-Boot's .dtsi format | |
134 | + | |
135 | + Args: | |
136 | + date: String containing date of original microcode file | |
137 | + license: List of text lines for the license file | |
138 | + mcode: Microcode object to write | |
139 | + outfile: Filename to write to ('-' for stdout) | |
140 | + """ | |
141 | + out = '''/*%s | |
142 | + * --- | |
143 | + * This is a device tree fragment. Use #include to add these properties to a | |
144 | + * node. | |
145 | + * | |
146 | + * Date: %s | |
147 | + */ | |
148 | + | |
149 | +compatible = "intel,microcode"; | |
150 | +intel,header-version = <%d>; | |
151 | +intel,update-revision = <%#x>; | |
152 | +intel,date-code = <%#x>; | |
153 | +intel,processor-signature = <%#x>; | |
154 | +intel,checksum = <%#x>; | |
155 | +intel,loader-revision = <%d>; | |
156 | +intel,processor-flags = <%#x>; | |
157 | + | |
158 | +/* The first 48-bytes are the public header which repeats the above data */ | |
159 | +data = <%s | |
160 | +\t>;''' | |
161 | + words = '' | |
162 | + for i in range(len(mcode.words)): | |
163 | + if not (i & 3): | |
164 | + words += '\n' | |
165 | + val = mcode.words[i] | |
166 | + # Change each word so it will be little-endian in the FDT | |
167 | + # This data is needed before RAM is available on some platforms so we | |
168 | + # cannot do an endianness swap on boot. | |
169 | + val = struct.unpack("<I", struct.pack(">I", val))[0] | |
170 | + words += '\t%#010x' % val | |
171 | + | |
172 | + # Take care to avoid adding a space before a tab | |
173 | + text = '' | |
174 | + for line in license_text: | |
175 | + if line[0] == '\t': | |
176 | + text += '\n *' + line | |
177 | + else: | |
178 | + text += '\n * ' + line | |
179 | + args = [text, date] | |
180 | + args += [mcode.words[i] for i in range(7)] | |
181 | + args.append(words) | |
182 | + if outfile == '-': | |
183 | + print out % tuple(args) | |
184 | + else: | |
185 | + if not outfile: | |
186 | + if not os.path.exists(MICROCODE_DIR): | |
187 | + print >> sys.stderr, "Creating directory '%s'" % MICROCODE_DIR | |
188 | + os.makedirs(MICROCODE_DIR) | |
189 | + outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi') | |
190 | + print >> sys.stderr, "Writing microcode for '%s' to '%s'" % ( | |
191 | + mcode.name, outfile) | |
192 | + with open(outfile, 'w') as fd: | |
193 | + print >> fd, out % tuple(args) | |
194 | + | |
195 | +def MicrocodeTool(): | |
196 | + """Run the microcode tool""" | |
197 | + commands = 'create,license,list'.split(',') | |
198 | + parser = OptionParser() | |
199 | + parser.add_option('-d', '--mcfile', type='string', action='store', | |
200 | + help='Name of microcode.dat file') | |
201 | + parser.add_option('-m', '--model', type='string', action='store', | |
202 | + help='Model name to extract') | |
203 | + parser.add_option('-o', '--outfile', type='string', action='store', | |
204 | + help='Filename to use for output (- for stdout), default is' | |
205 | + ' %s/<name>.dtsi' % MICROCODE_DIR) | |
206 | + parser.usage += """ command | |
207 | + | |
208 | + Process an Intel microcode file (use -h for help). Commands: | |
209 | + | |
210 | + create Create microcode .dtsi file for a model | |
211 | + list List available models in microcode file | |
212 | + license Print the license | |
213 | + | |
214 | + Typical usage: | |
215 | + | |
216 | + ./tools/microcode-tool -d microcode.dat -m 306a create | |
217 | + | |
218 | + This will find the appropriate file and write it to %s.""" % MICROCODE_DIR | |
219 | + | |
220 | + (options, args) = parser.parse_args() | |
221 | + if not args: | |
222 | + parser.error('Please specify a command') | |
223 | + cmd = args[0] | |
224 | + if cmd not in commands: | |
225 | + parser.error("Unknown command '%s'" % cmd) | |
226 | + | |
227 | + if not options.mcfile: | |
228 | + parser.error('You must specify a microcode file') | |
229 | + date, license_text, microcodes = ParseFile(options.mcfile) | |
230 | + | |
231 | + if cmd == 'list': | |
232 | + List(date, microcodes, options.model) | |
233 | + elif cmd == 'license': | |
234 | + print '\n'.join(license_text) | |
235 | + elif cmd == 'create': | |
236 | + if not options.model: | |
237 | + parser.error('You must specify a model to create') | |
238 | + model = options.model.lower() | |
239 | + mcode_list, tried = FindMicrocode(microcodes, model) | |
240 | + if not mcode_list: | |
241 | + parser.error("Unknown model '%s' (%s) - try 'list' to list" % | |
242 | + (model, ', '.join(tried))) | |
243 | + if len(mcode_list) > 1: | |
244 | + parser.error("Ambiguous model '%s' (%s) matched %s - try 'list' " | |
245 | + "to list or specify a particular file" % | |
246 | + (model, ', '.join(tried), | |
247 | + ', '.join([m.name for m in mcode_list]))) | |
248 | + CreateFile(date, license_text, mcode_list[0], options.outfile) | |
249 | + else: | |
250 | + parser.error("Unknown command '%s'" % cmd) | |
251 | + | |
252 | +if __name__ == "__main__": | |
253 | + MicrocodeTool() |