mkimage: clarify error message for empty input files
[platform/kernel/u-boot.git] / tools / microcode-tool.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0+
3 #
4 # Copyright (c) 2014 Google, Inc
5 #
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
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:
97             license_start = False
98             license_end = False
99             for line in fd:
100                 line = line.rstrip()
101
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
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
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     """
129     print('Date: %s' % date)
130     if model:
131         mcode_list, tried = FindMicrocode(microcodes, model.lower())
132         print('Matching models %s:' % (', '.join(tried)))
133     else:
134         print('All models:')
135         mcode_list = [microcodes[m] for m in list(microcodes.keys())]
136     for mcode in mcode_list:
137         print('%-20s: model %s' % (mcode.name, mcode.model))
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)
167         for mcode in list(microcodes.values()):
168             if mcode.model.startswith(abbrev):
169                 found.append(mcode)
170         if found:
171             break
172     return found, tried
173
174 def CreateFile(date, license_text, mcodes, outfile):
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
180         mcodes:      Microcode objects to write (normally only 1)
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 = ''
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]
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 == '-':
232         print(out % tuple(args))
233     else:
234         if not outfile:
235             if not os.path.exists(MICROCODE_DIR):
236                 print("Creating directory '%s'" % MICROCODE_DIR, file=sys.stderr)
237                 os.makedirs(MICROCODE_DIR)
238             outfile = os.path.join(MICROCODE_DIR, mcode.name + '.dtsi')
239         print("Writing microcode for '%s' to '%s'" % (
240                 ', '.join([mcode.name for mcode in mcodes]), outfile), file=sys.stderr)
241         with open(outfile, 'w') as fd:
242             print(out % tuple(args), file=fd)
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')
250     parser.add_option('-H', '--headerfile', type='string', action='append',
251                     help='Name of .h file containing microcode')
252     parser.add_option('-m', '--model', type='string', action='store',
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")
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
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)')
288
289     if cmd == 'list':
290         List(date, microcodes, options.model)
291     elif cmd == 'license':
292         print('\n'.join(license_text))
293     elif cmd == 'create':
294         if not options.model:
295             parser.error('You must specify a model to create')
296         model = options.model.lower()
297         if options.model == 'all':
298             options.multiple = True
299             mcode_list = list(microcodes.values())
300             tried = []
301         else:
302             mcode_list, tried = FindMicrocode(microcodes, model)
303         if not mcode_list:
304             parser.error("Unknown model '%s' (%s) - try 'list' to list" %
305                         (model, ', '.join(tried)))
306         if not options.multiple and len(mcode_list) > 1:
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])))
311         CreateFile(date, license_text, mcode_list, options.outfile)
312     else:
313         parser.error("Unknown command '%s'" % cmd)
314
315 if __name__ == "__main__":
316     MicrocodeTool()