1 | # |
---|
2 | # RTEMS Tools Project (http://www.rtems.org/) |
---|
3 | # Copyright 2019 Chris Johns (chrisj@rtems.org) |
---|
4 | # All rights reserved. |
---|
5 | # |
---|
6 | # This file is part of the RTEMS Tools package in 'rtems-tools'. |
---|
7 | # |
---|
8 | # Permission to use, copy, modify, and/or distribute this software for any |
---|
9 | # purpose with or without fee is hereby granted, provided that the above |
---|
10 | # copyright notice and this permission notice appear in all copies. |
---|
11 | # |
---|
12 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
---|
13 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
---|
14 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
---|
15 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
---|
16 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
---|
17 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
---|
18 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
---|
19 | |
---|
20 | # |
---|
21 | # This code builds a bootloader image for an SD card for a range of |
---|
22 | # boards on a range of hosts. |
---|
23 | # |
---|
24 | |
---|
25 | from __future__ import print_function |
---|
26 | |
---|
27 | import argparse |
---|
28 | import copy |
---|
29 | import datetime |
---|
30 | import os |
---|
31 | import sys |
---|
32 | import tempfile |
---|
33 | |
---|
34 | from rtemstoolkit import check |
---|
35 | from rtemstoolkit import configuration |
---|
36 | from rtemstoolkit import error |
---|
37 | from rtemstoolkit import execute |
---|
38 | from rtemstoolkit import host |
---|
39 | from rtemstoolkit import log |
---|
40 | from rtemstoolkit import macros |
---|
41 | from rtemstoolkit import path |
---|
42 | from rtemstoolkit import version |
---|
43 | |
---|
44 | def _check_exes(exes): |
---|
45 | ok = True |
---|
46 | first = True |
---|
47 | for exe in exes: |
---|
48 | log.output('check exe: %s' % (exe)) |
---|
49 | if not check.check_exe(None, exe): |
---|
50 | if first: |
---|
51 | log.notice('Host executable(s) not found:') |
---|
52 | first = False |
---|
53 | log.notice(' %s' % (exe)) |
---|
54 | ok = False |
---|
55 | return ok |
---|
56 | |
---|
57 | def _command(cmd, cwd): |
---|
58 | e = execute.capture_execution() |
---|
59 | cwd = path.abspath(cwd) |
---|
60 | log.output('>> cwd: %s' % (cwd)) |
---|
61 | log.output('> %s' % (cmd)) |
---|
62 | exit_code, proc, output = e.shell(cmd, cwd = path.host(cwd)) |
---|
63 | output_split = output.split(os.linesep) |
---|
64 | if len(output_split) >= 1 and len(output_split[0]) > 0: |
---|
65 | log.output(['> ' + l for l in output_split]) |
---|
66 | log.output('> exit: %d' % (exit_code)) |
---|
67 | if exit_code != 0: |
---|
68 | err = 'executing failure: (exit:%d) %s' % (exit_code, cmd) |
---|
69 | raise error.general(err) |
---|
70 | return output |
---|
71 | |
---|
72 | siunits = { 'g': 1024 * 1024 * 1024, |
---|
73 | 'm': 1024 * 1024, |
---|
74 | 'k': 1024 } |
---|
75 | |
---|
76 | def si_units(self, units): |
---|
77 | if units not in siunits: |
---|
78 | raise error.general('invalid SI unit: %s' % (units)) |
---|
79 | return siunits[units] |
---|
80 | |
---|
81 | def _si_parse_size(size): |
---|
82 | orig = size |
---|
83 | units = 1 |
---|
84 | suffix = '' |
---|
85 | if size[-1].isalpha(): |
---|
86 | suffix = size[-1] |
---|
87 | if suffix not in list(siunits.keys()): |
---|
88 | err = 'invalid SI unit (k, m, g): %s: %s' % (suffix, size) |
---|
89 | raise error.general(err) |
---|
90 | units = siunits[suffix] |
---|
91 | size = size[:-1] |
---|
92 | if not size.isdigit(): |
---|
93 | raise error.general('invalid size: %s' % (orig)) |
---|
94 | size = int(size) |
---|
95 | return size, suffix, size * units |
---|
96 | |
---|
97 | def _si_size(size): |
---|
98 | si_s, si_u, size = _si_parse_size(size) |
---|
99 | return size |
---|
100 | |
---|
101 | def _si_size_units(size): |
---|
102 | si_s, si_u, size = _si_parse_size(size) |
---|
103 | return si_s, si_u |
---|
104 | |
---|
105 | def _si_from_size(size): |
---|
106 | if isinstance(size, str): |
---|
107 | value = int(size) |
---|
108 | if str(value) != size: |
---|
109 | return size |
---|
110 | size = int(size) |
---|
111 | for u in siunits: |
---|
112 | value = int(size / siunits[u]) |
---|
113 | if value != 0: |
---|
114 | return '%d%s' % (value, u) |
---|
115 | return str(size) |
---|
116 | |
---|
117 | class bootloader(object): |
---|
118 | |
---|
119 | mandatory_configs = [ |
---|
120 | 'image_size', |
---|
121 | 'part_type', |
---|
122 | 'part_label', |
---|
123 | 'fs_format', |
---|
124 | 'fs_size', |
---|
125 | 'fs_alignment', |
---|
126 | 'tool_prefix', |
---|
127 | 'bootloaders' |
---|
128 | ] |
---|
129 | |
---|
130 | def __init__(self, command_path, bootloader = None): |
---|
131 | # |
---|
132 | # Check if there is a defaults.mc file under the command path. If so |
---|
133 | # this is the tester being run from within the git repo. If not found |
---|
134 | # assume the tools have been installed and the defaults is in the |
---|
135 | # install prefix. |
---|
136 | # |
---|
137 | boot_ini = 'tools/config/rtems-boot.ini' |
---|
138 | if path.exists(path.join(command_path, boot_ini)): |
---|
139 | rtdir = command_path |
---|
140 | else: |
---|
141 | rtdir = '%{_prefix}/share/rtems' |
---|
142 | boot_ini = '%s/%s' % (rtdir, boot_ini) |
---|
143 | self.build = 'build' |
---|
144 | self.macros = macros.macros(rtdir = rtdir, show_minimal = True) |
---|
145 | self.config = configuration.configuration(raw = False) |
---|
146 | self.load_config(bootloader, self.macros.expand(boot_ini)) |
---|
147 | self.clean = True |
---|
148 | |
---|
149 | def __getitem__(self, key): |
---|
150 | if self.macros.has_key(key) and self.macros[key] != 'None': |
---|
151 | r = self.macros.expand('%%{%s}' % (key)) |
---|
152 | if r == '1': |
---|
153 | return True |
---|
154 | elif r == '0': |
---|
155 | return False |
---|
156 | else: |
---|
157 | return r |
---|
158 | return None |
---|
159 | |
---|
160 | def __setitem__(self, key, value): |
---|
161 | if value is None: |
---|
162 | value = 'None' |
---|
163 | if isinstance(value, int): |
---|
164 | value = str(int) |
---|
165 | self.macros[key] = value |
---|
166 | |
---|
167 | def get_mandatory_configs(self): |
---|
168 | return self.mandatory_configs |
---|
169 | |
---|
170 | def check_mandatory_configs(self): |
---|
171 | for c in self.get_mandatory_configs(): |
---|
172 | if not self.macros.has_key(c): |
---|
173 | raise error.general('boot config missing: %s' % (c)) |
---|
174 | |
---|
175 | def load_config(self, bootloader, config): |
---|
176 | self.config.load(config) |
---|
177 | # |
---|
178 | # Check the config file has the basic data and structure. |
---|
179 | # |
---|
180 | bootloaders = self.config.comma_list('default', 'bootloaders') |
---|
181 | for bl in bootloaders: |
---|
182 | if not self.config.has_section(bl): |
---|
183 | raise error.general('boot config: missing bootloader section: %s' % (bl)) |
---|
184 | for b in self.config.comma_list(bl, 'boards'): |
---|
185 | if not self.config.has_section(b): |
---|
186 | raise error.general('boot config: missing board section: %s' % (b)) |
---|
187 | # |
---|
188 | # Is the bootloader valid? |
---|
189 | # |
---|
190 | if bootloader is not None and bootloader not in bootloaders: |
---|
191 | raise error.general('boot config: unknown bootloader: %s' % (bootloader)) |
---|
192 | self.macros['bootloader'] = str(bootloader) |
---|
193 | self.macros['version_str'] = version.string() |
---|
194 | self.macros['version'] = str(version.version()) |
---|
195 | self.macros['revision'] = str(version.revision()) |
---|
196 | if version.released(): |
---|
197 | self.macros['released'] = '1' |
---|
198 | # |
---|
199 | # Map the config to macros. The [default] section is global. The |
---|
200 | # remaining sections are added as macro maps so the specalised |
---|
201 | # bootloaders can enable reading from a macro map to provide specific |
---|
202 | # values for a specific config such as a board. |
---|
203 | # |
---|
204 | for s in self.config.get_sections(): |
---|
205 | if s != 'default': |
---|
206 | self.macros.set_write_map(s, add = True) |
---|
207 | for i in self.config.get_items(s): |
---|
208 | self.macros[i[0]] = i[1] |
---|
209 | self.macros.unset_write_map() |
---|
210 | self.macros.set_read_map('global') |
---|
211 | if bootloader is not None: |
---|
212 | self.macros.set_read_map(bootloader) |
---|
213 | |
---|
214 | def list_boards(self): |
---|
215 | boards = { } |
---|
216 | for bl in self.config.comma_list('default', 'bootloaders'): |
---|
217 | boards[bl] = self.config.comma_list(bl, 'boards') |
---|
218 | return boards |
---|
219 | |
---|
220 | def section_macro_map(self, section, nesting_level = 0): |
---|
221 | nesting_level += 1 |
---|
222 | if nesting_level >= 100: |
---|
223 | err = 'boot config: too many map levels (looping?): %s' % (section) |
---|
224 | raise error.general(err) |
---|
225 | if section not in self.macros.maps(): |
---|
226 | raise error.general('boot config: maps section not found: %s' % (section)) |
---|
227 | self.macros.set_read_map(section) |
---|
228 | for s in self.config.comma_list(section, 'uses', err = False): |
---|
229 | self.section_macro_map(s, nesting_level) |
---|
230 | |
---|
231 | def boards(self): |
---|
232 | return self.config.comma_list(self['bootloader'], 'boards') |
---|
233 | |
---|
234 | def log(self): |
---|
235 | log.output('Configuration:') |
---|
236 | log.output(' Bootloader: {0}'.format(self.macros['bootloader'])) |
---|
237 | log.output(' Image: {0}'.format(self.macros['output'])) |
---|
238 | log.output(' Image Size: {0}'.format(self.macros['image_size'])) |
---|
239 | log.output(' Part Type: {0}'.format(self.macros['part_type'])) |
---|
240 | log.output(' FS Format: {0}'.format(self.macros['fs_format'])) |
---|
241 | log.output(' FS Size: {0}'.format(self.macros['fs_size'])) |
---|
242 | log.output(' FS Align: {0}'.format(self.macros['fs_alignment'])) |
---|
243 | log.output(' Kernel: {0}'.format(self.macros['kernel'])) |
---|
244 | log.output(' FDT: {0}'.format(self.macros['fdt'])) |
---|
245 | log.output(' Net DHCP: {0}'.format(self.macros['net_dhcp'])) |
---|
246 | log.output(' Net IP: {0}'.format(self.macros['net_ip'])) |
---|
247 | log.output(' Net Server IP: {0}'.format(self.macros['net_server_ip'])) |
---|
248 | log.output(' Net File: {0}'.format(self.macros['net_exe'])) |
---|
249 | log.output(' Net FDT: {0}'.format(self.macros['net_fdt'])) |
---|
250 | log.output(' Files: {0}'.format(len(self.files()))) |
---|
251 | log.output('Macros:') |
---|
252 | macro_str = str(self.macros).split(os.linesep) |
---|
253 | log.output(os.linesep.join([' ' + l for l in macro_str])) |
---|
254 | |
---|
255 | def get_exes(self): |
---|
256 | return [] |
---|
257 | |
---|
258 | def check_exes(self): |
---|
259 | return _check_exes(self.get_exes()) |
---|
260 | |
---|
261 | def files(self): |
---|
262 | return [f.strip() for f in self.comma_split(self['files']) if len(f) > 0] |
---|
263 | |
---|
264 | def install_files(self, image, mountpoint): |
---|
265 | pass |
---|
266 | |
---|
267 | def install_configuration(self, image, mountpoint): |
---|
268 | pass |
---|
269 | |
---|
270 | def kernel_image(self): |
---|
271 | return self['kernel_image'].replace('@KERNEL@', path.basename(self['kernel'])) |
---|
272 | |
---|
273 | def fdt_image(self): |
---|
274 | return self['fdt_image'].replace('@FDT@', path.basename(self['fdt'])) |
---|
275 | |
---|
276 | def filter_text(self, lines): |
---|
277 | out = [] |
---|
278 | for line in lines: |
---|
279 | if '@KERNEL@' in line: |
---|
280 | line = line.replace('@KERNEL@', path.basename(self['kernel'])) |
---|
281 | if '@KERNEL_IMAGE@' in line: |
---|
282 | line = line.replace('@KERNEL_IMAGE@', self.kernel_image()) |
---|
283 | if '@FDT@' in line: |
---|
284 | line = line.replace('@FDT@', path.basename(self['fdt'])) |
---|
285 | if '@FDT_IMAGE@' in line: |
---|
286 | line = line.replace('@FDT_IMAGE@', self.fdt_image()) |
---|
287 | if '@NET_SERVER_IP@' in line and self['net_server_ip']is not None: |
---|
288 | line = line.replace('@NET_SERVER_IP@', self['net_server_ip']) |
---|
289 | if '@NET_IP@' in line and self['net_ip'] is not None: |
---|
290 | line = line.replace('@NET_IP@', self['net_ip']) |
---|
291 | if '@NET_BOOTEXE@' in line and self['net_exe'] is not None: |
---|
292 | line = line.replace('@NET_BOOTEXE@', self['net_exe']) |
---|
293 | if '@NET_BOOTFDT@' in line and self['net_fdt'] is not None: |
---|
294 | line = line.replace('@NET_BOOTFDT@', self['net_fdt']) |
---|
295 | out += [line] |
---|
296 | return out |
---|
297 | |
---|
298 | def comma_split(self, value): |
---|
299 | if value is not None: |
---|
300 | return [s.strip() for s in value.split(',')] |
---|
301 | return [] |
---|
302 | |
---|
303 | class uboot_bootloader(bootloader): |
---|
304 | |
---|
305 | def __init__(self, command_path, convert_kernel, paths, board): |
---|
306 | self.uboot = { 'paths': paths, 'board': board } |
---|
307 | self.convert_kernel = convert_kernel |
---|
308 | super(uboot_bootloader, self).__init__(command_path, 'u-boot') |
---|
309 | if self.board() not in self.boards(): |
---|
310 | raise error.general('board not found: %s' %(self.board())) |
---|
311 | log.output('Board: %s' % (self.board())) |
---|
312 | self.section_macro_map(self.board()) |
---|
313 | self.macros.set_read_map(self['bootloader'] + '-templates') |
---|
314 | self.macros.lock_read_map() |
---|
315 | self._check_frist_second_stages() |
---|
316 | |
---|
317 | def _check_frist_second_stages(self): |
---|
318 | if self['first_stage'] is not None and \ |
---|
319 | not path.exists(self['first_stage']): |
---|
320 | err = 'u-boot: first stage loader not found: %s' % \ |
---|
321 | (self['first_stage']) |
---|
322 | raise error.general(err) |
---|
323 | if self['second_stage'] is not None and \ |
---|
324 | not path.exists(self['second_stage']): |
---|
325 | err = 'u-boot: second stage loader not found: %s' % \ |
---|
326 | (self['second_stage']) |
---|
327 | raise error.general(err) |
---|
328 | |
---|
329 | def load_config(self, bootloader, config): |
---|
330 | super(uboot_bootloader, self).load_config(bootloader, config) |
---|
331 | if not self.convert_kernel: |
---|
332 | paths_count = len(self.uboot['paths']) |
---|
333 | if paths_count == 1: |
---|
334 | self.macros['ubootdir'] = path.abspath(self.uboot['paths'][0]) |
---|
335 | elif paths_count == 2: |
---|
336 | self.macros['first_stage'] = self.uboot['paths'][0] |
---|
337 | self.macros['second_stage'] = self.uboot['paths'][1] |
---|
338 | else: |
---|
339 | raise error.general('u-boot: invalid number of paths') |
---|
340 | self.macros['mkimage'] = 'mkimage' |
---|
341 | |
---|
342 | def get_mandatory_configs(self): |
---|
343 | cfgs = super(uboot_bootloader, self).get_mandatory_configs() |
---|
344 | return cfgs + ['objcopy', |
---|
345 | 'arch', |
---|
346 | 'vendor', |
---|
347 | 'board', |
---|
348 | 'config_name', |
---|
349 | 'first_stage', |
---|
350 | 'second_stage', |
---|
351 | 'kernel_converter'] |
---|
352 | |
---|
353 | def board(self): |
---|
354 | return self.uboot['board'] |
---|
355 | |
---|
356 | def get_exes(self): |
---|
357 | exes = super(uboot_bootloader, self).get_exes() |
---|
358 | if self['executables'] is not None: |
---|
359 | exes += self.comma_split(self['executables']) |
---|
360 | return exes |
---|
361 | |
---|
362 | def install_files(self, image, mountpoint): |
---|
363 | if self['kernel'] is not None: |
---|
364 | kernel_image = self.kernel_convert(image, self['kernel']) |
---|
365 | if self.convert_kernel: |
---|
366 | path.copy(kernel_image, self['output']) |
---|
367 | else: |
---|
368 | image.install(kernel_image, mountpoint) |
---|
369 | if self['fdt'] is not None: |
---|
370 | fdt_image = self.fdt_convert(image, self['fdt']) |
---|
371 | image.install(fdt_image, mountpoint) |
---|
372 | |
---|
373 | def install_configuration(self, image, mountpoint): |
---|
374 | uenv_txt = self['uenv_txt'] |
---|
375 | if uenv_txt is not None: |
---|
376 | log.output('Uenv txt: %s' % (uenv_txt)) |
---|
377 | image.install(path.abspath(uenv_txt), mountpoint) |
---|
378 | else: |
---|
379 | template = None |
---|
380 | if self['net_dhcp'] is not None or \ |
---|
381 | self['net_ip'] is not None: |
---|
382 | if self['net_dhcp'] is not None: |
---|
383 | template = 'uenv_net_dhcp' |
---|
384 | else: |
---|
385 | template = 'uenv_net_static' |
---|
386 | if self['net_server_ip']: |
---|
387 | template += '_sip' |
---|
388 | if self['net_fdt']: |
---|
389 | template += '_net_fdt' |
---|
390 | else: |
---|
391 | if self['kernel'] is not None: |
---|
392 | template = 'uenv_exe' |
---|
393 | elif self['fdt'] is not None: |
---|
394 | template = 'uenv' |
---|
395 | if self['fdt'] is not None: |
---|
396 | template += '_fdt' |
---|
397 | if template is not None: |
---|
398 | log.notice('Uenv template: %s' % (template)) |
---|
399 | uenv_start = self.comma_split(self['uenv_start']) |
---|
400 | uenv_body = self.comma_split(self[template]) |
---|
401 | uenv_end = self.comma_split(self['uenv_end']) |
---|
402 | uenv = uenv_start + uenv_body + uenv_end |
---|
403 | image.install_text(self.filter_text(uenv), |
---|
404 | path.join(mountpoint, self['boot_config'])) |
---|
405 | |
---|
406 | def kernel_convert(self, image, kernel): |
---|
407 | dst = path.join(path.abspath(self['build']), path.basename(kernel)) |
---|
408 | self['kernel_build'] = dst |
---|
409 | log.output('Copy (into build): %s -> %s' % (kernel, dst)) |
---|
410 | image.clean_path(dst) |
---|
411 | path.copy(kernel, dst) |
---|
412 | cmds = self.filter_text(self.comma_split(self['kernel_converter'])) |
---|
413 | for cmd in cmds: |
---|
414 | _command(cmd, self['build']) |
---|
415 | return self['kernel_image'].replace('@KERNEL@', dst) |
---|
416 | |
---|
417 | def fdt_convert(self, image, fdt): |
---|
418 | dst = path.join(path.abspath(self['build']), path.basename(fdt)) |
---|
419 | self['fdt_build'] = dst |
---|
420 | log.output('Copy (into build): %s -> %s' % (fdt, dst)) |
---|
421 | image.clean_path(dst) |
---|
422 | path.copy(fdt, dst) |
---|
423 | return self['fdt_image'].replace('@FDT@', dst) |
---|
424 | |
---|
425 | class image(object): |
---|
426 | |
---|
427 | def __init__(self, bootloader): |
---|
428 | self.loader = bootloader |
---|
429 | self.detach_images = [] |
---|
430 | self.unmount_paths = [] |
---|
431 | self.remove_paths = [] |
---|
432 | |
---|
433 | def build(self): |
---|
434 | # |
---|
435 | # Cleanup if any goes wrong. |
---|
436 | # |
---|
437 | try: |
---|
438 | # |
---|
439 | # Ge the absolute paths to fixed locations. |
---|
440 | # |
---|
441 | output = path.abspath(self.loader['output']) |
---|
442 | build = path.abspath(self.loader['build']) |
---|
443 | mountpoint = path.join(build, 'mnt') |
---|
444 | |
---|
445 | # |
---|
446 | # Create any paths we need. They are removed when this object is |
---|
447 | # deleted. |
---|
448 | # |
---|
449 | self.create_path(build) |
---|
450 | |
---|
451 | # |
---|
452 | # If only coverting a kernel no need to create an image. |
---|
453 | # |
---|
454 | if not self.loader.convert_kernel: |
---|
455 | self.create_path(mountpoint) |
---|
456 | |
---|
457 | # |
---|
458 | # Create the blank image file. This is attached as a device, |
---|
459 | # partitioned, formatted and the files written to it. |
---|
460 | # |
---|
461 | log.notice('Create image: %s size %s' % (self.loader['output'], |
---|
462 | self.loader['image_size'])) |
---|
463 | self.image_create(output, self.loader['image_size']) |
---|
464 | |
---|
465 | # |
---|
466 | # Attach the image so it is a device. |
---|
467 | # |
---|
468 | log.notice('Attach image to device: %s' % (self.loader['output'])) |
---|
469 | device = self.image_attach(output) |
---|
470 | |
---|
471 | # |
---|
472 | # Partition the image. The device may change. |
---|
473 | # |
---|
474 | log.notice('Partition device: %s as %s' % (device, |
---|
475 | self.loader['part_type'])) |
---|
476 | device = self.partition(output, |
---|
477 | device, |
---|
478 | self.loader['part_type'], |
---|
479 | self.loader['part_label'], |
---|
480 | self.loader['fs_format'], |
---|
481 | self.loader['fs_size'], |
---|
482 | self.loader['fs_alignment']) |
---|
483 | part = self.device_partition(device, 1) |
---|
484 | |
---|
485 | # |
---|
486 | # Format the first partition. |
---|
487 | # |
---|
488 | log.notice('Format: %s as %s' % (part, self.loader['fs_format'])) |
---|
489 | self.format_partition(part, self.loader['fs_format']) |
---|
490 | |
---|
491 | # |
---|
492 | # Mount the file system. |
---|
493 | # |
---|
494 | log.notice('Mount: %s' % (part)) |
---|
495 | self.mount(self.loader['fs_format'], part, mountpoint) |
---|
496 | |
---|
497 | # |
---|
498 | # Install the first stage and second stage boot loaders. |
---|
499 | # |
---|
500 | self.install(self.loader['first_stage'], mountpoint) |
---|
501 | self.install(self.loader['second_stage'], mountpoint) |
---|
502 | |
---|
503 | # |
---|
504 | # Install the bootload files. |
---|
505 | # |
---|
506 | self.loader.install_files(self, mountpoint) |
---|
507 | |
---|
508 | if not self.loader.convert_kernel: |
---|
509 | # |
---|
510 | # Install the bootloader configuration. |
---|
511 | # |
---|
512 | self.loader.install_configuration(self, mountpoint) |
---|
513 | |
---|
514 | # |
---|
515 | # Install any user files if present. |
---|
516 | # |
---|
517 | for f in self.loader.files(): |
---|
518 | self.install(f, mountpoint) |
---|
519 | |
---|
520 | # |
---|
521 | # Done. |
---|
522 | # |
---|
523 | log.notice('Finished') |
---|
524 | finally: |
---|
525 | self.cleanup() |
---|
526 | |
---|
527 | def install(self, src, dst): |
---|
528 | src_base = path.basename(src) |
---|
529 | log.notice('Install: %s' % (src_base)) |
---|
530 | asrc = path.abspath(src) |
---|
531 | adst = path.join(path.abspath(dst), src_base) |
---|
532 | log.output('Copy: %s -> %s' % (asrc, adst)) |
---|
533 | path.copy(asrc, adst) |
---|
534 | |
---|
535 | def install_text(self, text, dst): |
---|
536 | dst_base = path.basename(dst) |
---|
537 | log.notice('Install: %s' % (dst_base)) |
---|
538 | adst = path.abspath(dst) |
---|
539 | log.output('Copy: text[%d] -> %s' % (len(text), adst)) |
---|
540 | log.output([' ] ' + l for l in text]) |
---|
541 | with open(adst, "w") as o: |
---|
542 | o.write(os.linesep.join(text)) |
---|
543 | |
---|
544 | def image_create(self, path_, size): |
---|
545 | self.host_image_create(path_, size, path.exists(path_)) |
---|
546 | |
---|
547 | def image_attach(self, path_): |
---|
548 | device = self.host_image_attach(path_) |
---|
549 | self.detach_images += [device] |
---|
550 | return device |
---|
551 | |
---|
552 | def image_detach(self, device): |
---|
553 | if device in self.detach_images: |
---|
554 | self.detach_images.remove(device) |
---|
555 | self.host_image_detach(device) |
---|
556 | |
---|
557 | def partition(self, image_, device, ptype, plabel, pformat, psize, palign): |
---|
558 | return self.host_partition(image_, device, |
---|
559 | ptype, plabel, pformat, psize, palign) |
---|
560 | |
---|
561 | def format_partition(self, device, pformat): |
---|
562 | self.host_format_partition(device, pformat) |
---|
563 | |
---|
564 | def device_partition(self, device, pindex): |
---|
565 | return self.host_device_partition(device, pindex) |
---|
566 | |
---|
567 | def mount(self, pformat, device, path_): |
---|
568 | if path_ not in self.unmount_paths: |
---|
569 | self.host_mount(pformat, device, path_) |
---|
570 | self.unmount_paths += [path_] |
---|
571 | |
---|
572 | def unmount(self, path_): |
---|
573 | if path_ in self.unmount_paths: |
---|
574 | self.host_unmount(path_) |
---|
575 | self.unmount_paths.remove(path_) |
---|
576 | |
---|
577 | def cleanup(self): |
---|
578 | log.notice('Cleaning up') |
---|
579 | for m in self.unmount_paths: |
---|
580 | log.output('unmount: %s' % (m)) |
---|
581 | self.unmount(m) |
---|
582 | for i in self.detach_images: |
---|
583 | log.output('detach: %s' % (i)) |
---|
584 | self.image_detach(i) |
---|
585 | if self.loader.clean: |
---|
586 | for r in self.remove_paths: |
---|
587 | if path.exists(r): |
---|
588 | log.output('remove: %s' % (r)) |
---|
589 | path.removeall(r) |
---|
590 | |
---|
591 | def get_exes(self): |
---|
592 | return ['dd'] |
---|
593 | |
---|
594 | def check_exes(self): |
---|
595 | return _check_exes(self.get_exes()) |
---|
596 | |
---|
597 | def clean_path(self, name): |
---|
598 | self.remove_paths += [name] |
---|
599 | |
---|
600 | def create_path(self, where, recreate = True, cleanup = True): |
---|
601 | if path.exists(where): |
---|
602 | log.output('remove: %s' % (where)) |
---|
603 | path.removeall(where) |
---|
604 | try: |
---|
605 | log.output('make: %s' % (where)) |
---|
606 | path.mkdir(where) |
---|
607 | except: |
---|
608 | raise error.general('cannot create build path: %s' % (where)) |
---|
609 | if not path.isreadable(where): |
---|
610 | raise error.general('build path is not readable: %s' % (where)) |
---|
611 | if not path.iswritable(where): |
---|
612 | raise error.general('build path is not writeable: %s' % (where)) |
---|
613 | if cleanup: |
---|
614 | self.remove_paths += [where] |
---|
615 | |
---|
616 | def command(self, cmd): |
---|
617 | return _command(cmd, self.loader['build']) |
---|
618 | |
---|
619 | def host_image_create(self, path_, size, exists): |
---|
620 | img_size, img_units = _si_size_units(size) |
---|
621 | self.command('dd if=/dev/zero of=%s bs=1%s count=%d' % (path_, |
---|
622 | img_units, |
---|
623 | img_size)) |
---|
624 | |
---|
625 | def host_image_attach(self, path_): |
---|
626 | raise error.general('no platform support: host_image_attach') |
---|
627 | |
---|
628 | def host_image_detach(self, device): |
---|
629 | raise error.general('no platform support: host_image_detach') |
---|
630 | |
---|
631 | def host_partition(self, image_, device, ptype, plabel, pformat, psize, palign): |
---|
632 | raise error.general('no platform support: host_partition') |
---|
633 | |
---|
634 | def host_format_partition(self, device, pformat): |
---|
635 | raise error.general('no platform support: host_format_partition') |
---|
636 | |
---|
637 | def host_device_partition(self, device, pindex): |
---|
638 | raise error.general('no platform support: host_device_partition') |
---|
639 | |
---|
640 | def host_mount(self, pformat, device, path_): |
---|
641 | raise error.general('no platform support: host_mount') |
---|
642 | |
---|
643 | def host_unmount(self, path_): |
---|
644 | raise error.general('no platform support: host_unmount') |
---|
645 | |
---|
646 | class freebsd_image(image): |
---|
647 | def __init__(self, loader): |
---|
648 | super(freebsd_image, self).__init__(loader) |
---|
649 | |
---|
650 | def get_exes(self): |
---|
651 | exes = super(freebsd_image, self).get_exes() |
---|
652 | return exes + ['mdconfig', |
---|
653 | 'gpart', |
---|
654 | 'newfs_msdos', |
---|
655 | 'mount', |
---|
656 | 'umount'] |
---|
657 | |
---|
658 | def host_image_attach(self, path_): |
---|
659 | return self.command('sudo mdconfig -f %s' % (path_)) |
---|
660 | |
---|
661 | def host_image_detach(self, device): |
---|
662 | self.command('sudo mdconfig -d -u %s' % (device)) |
---|
663 | |
---|
664 | def host_partition(self, image_, device, ptype, plabel, pformat, psize, palign): |
---|
665 | types = { 'MBR': 'MBR' } |
---|
666 | formats = { 'fat16': 'fat16', |
---|
667 | 'fat32': 'fat32' } |
---|
668 | if ptype not in types: |
---|
669 | err = 'unknown type of partitioning: %s' % (ptype) |
---|
670 | raise error.general(err) |
---|
671 | if pformat not in formats: |
---|
672 | raise error.general('unknown format: %s' % (pformat)) |
---|
673 | self.command('sudo gpart create -s %s %s' % (types[ptype], device)) |
---|
674 | self.command('sudo gpart add -s %s -t %s -a %s %s' % (_si_from_size(psize), |
---|
675 | formats[pformat], |
---|
676 | palign, |
---|
677 | device)) |
---|
678 | self.command('sudo gpart set -a active -i 1 %s' % (device)) |
---|
679 | return device |
---|
680 | |
---|
681 | def host_format_partition(self, device, pformat): |
---|
682 | formats = { 'fat16': ('newfs_msdos', '16'), |
---|
683 | 'fat32': ('newfs_msdos', '32') } |
---|
684 | if pformat not in formats: |
---|
685 | raise error.general('unknown format: %s' % (pformat)) |
---|
686 | self.command('sudo %s -F %s %s' % (formats[pformat][0], |
---|
687 | formats[pformat][1], |
---|
688 | device)) |
---|
689 | |
---|
690 | def host_device_partition(self, device, pindex): |
---|
691 | return '/dev/%ss%d' % (device, pindex) |
---|
692 | |
---|
693 | def host_mount(self, pformat, device, path_): |
---|
694 | formats = { 'fat16': 'msdos', |
---|
695 | 'fat32': 'msdos' } |
---|
696 | if pformat not in formats: |
---|
697 | raise error.general('unknown format: %s' % (pformat)) |
---|
698 | self.command('sudo mount -t %s %s %s' % (formats[pformat], |
---|
699 | device, |
---|
700 | path_)) |
---|
701 | |
---|
702 | def host_unmount(self, path_): |
---|
703 | self.command('sudo umount %s' % (path_)) |
---|
704 | |
---|
705 | class linux_image(image): |
---|
706 | def __init__(self, loader): |
---|
707 | super(linux_image, self).__init__(loader) |
---|
708 | |
---|
709 | def get_exes(self): |
---|
710 | exes = super(linux_image, self).get_exes() |
---|
711 | return exes + ['losetup', |
---|
712 | 'fdisk', |
---|
713 | 'mkfs.fat', |
---|
714 | 'mount', |
---|
715 | 'umount'] |
---|
716 | |
---|
717 | def host_image_create(self, path_, size, exists): |
---|
718 | img_size, img_units = _si_size_units(size) |
---|
719 | self.command('dd if=/dev/zero of=%s bs=%s count=%d' % (path_, |
---|
720 | _si_units(img_units), |
---|
721 | img_size)) |
---|
722 | def host_image_attach(self, path_): |
---|
723 | return self.command('sudo losetup --partscan --find --show %s' % (path_)) |
---|
724 | |
---|
725 | def host_image_detach(self, device): |
---|
726 | self.command('sudo losetup --detach %s' % (device)) |
---|
727 | |
---|
728 | def host_partition(self, image_, device, ptype, pformat, psize, palign): |
---|
729 | types = { 'MBR': 'MBR' } |
---|
730 | formats = { 'fat16': '6', |
---|
731 | 'fat32': 'b' } |
---|
732 | if ptype not in types: |
---|
733 | err = 'unknown type of partitioning: %s' % (ptype) |
---|
734 | raise error.general(err) |
---|
735 | if pformat not in formats: |
---|
736 | raise error.general('unknown format: %s' % (pformat)) |
---|
737 | # |
---|
738 | # Datch the loop back device, we use fdisk on the image to avoid any |
---|
739 | # kernel errors related to re-reading the updated partition data. |
---|
740 | # |
---|
741 | self.host_image_detach(device) |
---|
742 | # |
---|
743 | # This awkward exchange is needed to script fdisk, hmmm. |
---|
744 | # |
---|
745 | with tempfile.NamedTemporaryFile() as tmp: |
---|
746 | s = os.linesep.join(['o', # create a new empty part table |
---|
747 | 'n', # add a new partition |
---|
748 | 'p', # primary |
---|
749 | '1', # partition 1 |
---|
750 | '%d' % (_si_size(palign) / 512), |
---|
751 | '%d' % (_si_size(psize) / 512), |
---|
752 | 't', # change a partition type |
---|
753 | '%s' % (formats[pformat]), # hex code |
---|
754 | 'a', # toggle a bootable flag |
---|
755 | 'p', # print |
---|
756 | 'w', # write table to disk and exit |
---|
757 | '']) |
---|
758 | log.output('fdisk script:') |
---|
759 | log.output(s) |
---|
760 | tmp.write(s.encode()) |
---|
761 | tmp.seek(0) |
---|
762 | self.command('cat %s | fdisk -t %s %s' % (tmp.name, |
---|
763 | types[ptype], |
---|
764 | image_)) |
---|
765 | return self.host_image_attach(image_) |
---|
766 | |
---|
767 | def host_format_partition(self, device, pformat): |
---|
768 | formats = { 'fat16': ('mkfs.fat', '16'), |
---|
769 | 'fat32': ('mkfs.fat', '32') } |
---|
770 | if pformat not in formats: |
---|
771 | raise error.general('unknown format: %s' % (pformat)) |
---|
772 | self.command('sudo %s -F %s %s' % (formats[pformat][0], |
---|
773 | formats[pformat][1], |
---|
774 | device)) |
---|
775 | |
---|
776 | def host_device_partition(self, device, pindex): |
---|
777 | return '%sp%d' % (device, pindex) |
---|
778 | |
---|
779 | def host_mount(self, pformat, device, path_): |
---|
780 | options = { 'fat16': '-o uid=%d' % (os.getuid()), |
---|
781 | 'fat32': '-o uid=%d' % (os.getuid()) } |
---|
782 | if pformat in options: |
---|
783 | opts = options[pformat] |
---|
784 | else: |
---|
785 | opts = '' |
---|
786 | self.command('sudo mount %s %s %s' % (opts, device, path_)) |
---|
787 | |
---|
788 | def host_unmount(self, path_): |
---|
789 | self.command('sudo umount %s' % (path_)) |
---|
790 | |
---|
791 | class darwin_image(image): |
---|
792 | def __init__(self, loader): |
---|
793 | super(darwin_image, self).__init__(loader) |
---|
794 | if not self.loader['output'].endswith('.img'): |
---|
795 | log.notice('Output file does not end with `.img`. ' + \ |
---|
796 | 'Needed on MacOS due to a bug (Id: 51283993)') |
---|
797 | raise error.general('output file does not end with `.img`') |
---|
798 | |
---|
799 | def get_exes(self): |
---|
800 | exes = super(darwin_image, self).get_exes() |
---|
801 | return exes + ['hdiutil', |
---|
802 | 'diskutil', |
---|
803 | 'fdisk'] |
---|
804 | |
---|
805 | def host_image_attach(self, path_): |
---|
806 | output = self.command('sudo hdiutil attach %s -nomount -nobrowse' % (path_)) |
---|
807 | if len(output.split(os.linesep)) != 1 or not output.startswith('/dev/'): |
---|
808 | raise error.general('invalid hdiutil attach outputl; see log') |
---|
809 | return output.strip() |
---|
810 | |
---|
811 | def host_image_detach(self, device): |
---|
812 | self.command('sudo hdiutil detach %s' % (device)) |
---|
813 | |
---|
814 | def host_partition(self, image_, device, ptype, plabel, pformat, psize, palign): |
---|
815 | types = { 'MBR': 'MBR' } |
---|
816 | formats = { 'fat16': 'MS-DOS FAT16', |
---|
817 | 'fat32': 'MS-DOS FAT32' } |
---|
818 | if ptype not in types: |
---|
819 | err = 'unknown type of partitioning: %s' % (ptype) |
---|
820 | raise error.general(err) |
---|
821 | if pformat not in formats: |
---|
822 | raise error.general('unknown format: %s' % (pformat)) |
---|
823 | # |
---|
824 | # Align the parition by adding free space before. Sign. |
---|
825 | # |
---|
826 | cmd = "sudo diskutil partitionDisk %s 2 %s " % (device, types[ptype]) |
---|
827 | cmd += "'Free Space' '%%noformat%%' %s " % (palign) |
---|
828 | cmd += "'%s' %s %s" % (formats[pformat], plabel, psize) |
---|
829 | self.command(cmd) |
---|
830 | # |
---|
831 | # MacOS mounts the filesystem once the partitioning has finished, |
---|
832 | # unmount it as we have no control over the mountpoint. |
---|
833 | # |
---|
834 | self.command('sudo diskutil unmountDisk %s' % (device)) |
---|
835 | # |
---|
836 | # This awkward exchange is needed to set the active bit. |
---|
837 | # |
---|
838 | with tempfile.NamedTemporaryFile() as tmp: |
---|
839 | s = os.linesep.join(['f 1', # flag toggle on partition 1 |
---|
840 | 'w', # write |
---|
841 | 'p', # print |
---|
842 | 'q', # quit |
---|
843 | '']) |
---|
844 | tmp.write(s.encode()) |
---|
845 | tmp.seek(0) |
---|
846 | self.command('cat %s | sudo fdisk -y -e %s' % (tmp.name, device)) |
---|
847 | return device |
---|
848 | |
---|
849 | def host_format_partition(self, device, pformat): |
---|
850 | log.output(' * No format stage; done when partitioning') |
---|
851 | |
---|
852 | def host_device_partition(self, device, pindex): |
---|
853 | return '%ss%d' % (device, pindex) |
---|
854 | |
---|
855 | def host_mount(self, pformat, device, path_): |
---|
856 | self.command('sudo diskutil mount -mountPoint %s %s' % (path_, device)) |
---|
857 | |
---|
858 | def host_unmount(self, path_): |
---|
859 | self.command('sudo diskutil unmount %s' % (path_)) |
---|
860 | |
---|
861 | builders = { |
---|
862 | 'freebsd': freebsd_image, |
---|
863 | 'linux' : linux_image, |
---|
864 | 'darwin' : darwin_image |
---|
865 | } |
---|
866 | |
---|
867 | def load_log(logfile): |
---|
868 | log.default = log.log(streams = [logfile]) |
---|
869 | |
---|
870 | def log_default(): |
---|
871 | return 'rtems-log-boot-image.txt' |
---|
872 | |
---|
873 | class valid_dir(argparse.Action): |
---|
874 | def __call__(self, parser, namespace, values, option_string = None): |
---|
875 | if type(values) is not list: |
---|
876 | values = [values] |
---|
877 | for value in values: |
---|
878 | if not path.isdir(value): |
---|
879 | raise argparse.ArgumentError(self, |
---|
880 | 'is not a valid directory: %s' % (value)) |
---|
881 | if not path.isreadable(value): |
---|
882 | raise argparse.ArgumentError(self, 'is not readable: %s' % (value)) |
---|
883 | if not path.iswritable(value): |
---|
884 | raise argparse.ArgumentError(self, 'is not writeable: %s' % (value)) |
---|
885 | setattr(namespace, self.dest, value) |
---|
886 | |
---|
887 | class valid_file(argparse.Action): |
---|
888 | def __call__(self, parser, namespace, value, option_string = None): |
---|
889 | current = getattr(namespace, self.dest) |
---|
890 | if not isinstance(current, list) and current is not None: |
---|
891 | raise argparse.ArgumentError(self, |
---|
892 | ' already provided: %s, have %s' % (value, |
---|
893 | current)) |
---|
894 | if not path.isfile(value): |
---|
895 | raise argparse.ArgumentError(self, 'is not a valid file: %s' % (value)) |
---|
896 | if not path.isreadable(value): |
---|
897 | raise argparse.ArgumentError(self, 'is not readable: %s' % (value)) |
---|
898 | if current is not None: |
---|
899 | value = current + [value] |
---|
900 | setattr(namespace, self.dest, value) |
---|
901 | |
---|
902 | class valid_file_if_exists(argparse.Action): |
---|
903 | def __call__(self, parser, namespace, value, option_string = None): |
---|
904 | current = getattr(namespace, self.dest) |
---|
905 | if not isinstance(current, list) and current is not None: |
---|
906 | raise argparse.ArgumentError(self, |
---|
907 | ' already provided: %s, have %s' % (value, |
---|
908 | current)) |
---|
909 | if path.exists(value): |
---|
910 | if not path.isfile(value): |
---|
911 | raise argparse.ArgumentError(self, 'is not a valid file: %s' % (value)) |
---|
912 | if not path.isreadable(value): |
---|
913 | raise argparse.ArgumentError(self, 'is not readable: %s' % (value)) |
---|
914 | if current is not None: |
---|
915 | value = current + [value] |
---|
916 | setattr(namespace, self.dest, value) |
---|
917 | |
---|
918 | class valid_paths(argparse.Action): |
---|
919 | def __call__(self, parser, namespace, value, option_string = None): |
---|
920 | current = getattr(namespace, self.dest) |
---|
921 | if current is None: |
---|
922 | current = [] |
---|
923 | if isinstance(value, list): |
---|
924 | values = value |
---|
925 | else: |
---|
926 | values = [values] |
---|
927 | for value in values: |
---|
928 | if not path.isfile(value) and not path.isdir(value): |
---|
929 | err = 'is not a valid file or directory: %s' % (value) |
---|
930 | raise argparse.ArgumentError(self, err) |
---|
931 | if not path.isreadable(value): |
---|
932 | raise argparse.ArgumentError(self, 'is not readable: %s' % (value)) |
---|
933 | current += [value] |
---|
934 | setattr(namespace, self.dest, current) |
---|
935 | |
---|
936 | class valid_format(argparse.Action): |
---|
937 | def __call__(self, parser, namespace, value, option_string = None): |
---|
938 | current = getattr(namespace, self.dest) |
---|
939 | if not isinstance(current, list) and current is not None: |
---|
940 | raise argparse.ArgumentError(self, |
---|
941 | ' already provided: %s, have %s' % (value, |
---|
942 | current)) |
---|
943 | if value not in ['fat16', 'fat32']: |
---|
944 | raise argparse.ArgumentError(self, ' invalid format: %s' % (value)) |
---|
945 | setattr(namespace, self.dest, value) |
---|
946 | |
---|
947 | class valid_si(argparse.Action): |
---|
948 | def __call__(self, parser, namespace, value, option_string = None): |
---|
949 | current = getattr(namespace, self.dest) |
---|
950 | units = len(value) |
---|
951 | if value[-1].isalpha(): |
---|
952 | if value[-1] not in ['k', 'm', 'g']: |
---|
953 | raise argparse.ArgumentError(self, |
---|
954 | 'invalid SI (k, m, g): %s' % (value[-1])) |
---|
955 | units = -1 |
---|
956 | if not value[:units].isdigit(): |
---|
957 | raise argparse.ArgumentError(self, 'invalid SI size: %s' % (value)) |
---|
958 | setattr(namespace, self.dest, value) |
---|
959 | |
---|
960 | class valid_ip(argparse.Action): |
---|
961 | def __call__(self, parser, namespace, value, option_string = None): |
---|
962 | current = getattr(namespace, self.dest) |
---|
963 | if current is not None: |
---|
964 | raise argparse.ArgumentError(self, |
---|
965 | ' already provided: %s, have %s' % (value, |
---|
966 | current)) |
---|
967 | setattr(namespace, self.dest, value) |
---|
968 | |
---|
969 | def run(args = sys.argv, command_path = None): |
---|
970 | ec = 0 |
---|
971 | notice = None |
---|
972 | builder = None |
---|
973 | try: |
---|
974 | description = 'Provide one path to a u-boot build or provide two ' |
---|
975 | description += 'paths to the built the first and second stage loaders, ' |
---|
976 | description += 'for example a first stage loader is \'MLO\' and a second ' |
---|
977 | description += '\'u-boot.img\'. If converting a kernel only provide the ' |
---|
978 | description += 'executable\'s path.' |
---|
979 | |
---|
980 | argsp = argparse.ArgumentParser(prog = 'rtems-boot-image', |
---|
981 | description = description) |
---|
982 | argsp.add_argument('-l', '--log', |
---|
983 | help = 'log file (default: %(default)s).', |
---|
984 | type = str, default = log_default()) |
---|
985 | argsp.add_argument('-v', '--trace', |
---|
986 | help = 'enable trace logging for debugging.', |
---|
987 | action = 'store_true') |
---|
988 | argsp.add_argument('-s', '--image-size', |
---|
989 | help = 'image size in mega-bytes (default: %(default)s).', |
---|
990 | type = str, action = valid_si, default = '64m') |
---|
991 | argsp.add_argument('-F', '--fs-format', |
---|
992 | help = 'root file system format (default: %(default)s).', |
---|
993 | type = str, action = valid_format, default = 'fat16') |
---|
994 | argsp.add_argument('-S', '--fs-size', |
---|
995 | help = 'root file system size in SI units ' + \ |
---|
996 | '(default: %(default)s).', |
---|
997 | type = str, action = valid_si, default = 'auto') |
---|
998 | argsp.add_argument('-A', '--fs-align', |
---|
999 | help = 'root file system alignment in SI units ' + \ |
---|
1000 | '(default: %(default)s).', |
---|
1001 | type = str, action = valid_si, default = '1m') |
---|
1002 | argsp.add_argument('-k', '--kernel', |
---|
1003 | help = 'install the kernel (default: %(default)r).', |
---|
1004 | type = str, action = valid_file, default = None) |
---|
1005 | argsp.add_argument('-d', '--fdt', |
---|
1006 | help = 'Flat device tree source/blob (default: %(default)r).', |
---|
1007 | type = str, action = valid_file, default = None) |
---|
1008 | argsp.add_argument('-f', '--file', |
---|
1009 | help = 'install the file (default: None).', |
---|
1010 | type = str, action = valid_file, default = []) |
---|
1011 | argsp.add_argument('--net-boot', |
---|
1012 | help = 'configure a network boot using TFTP ' + \ |
---|
1013 | '(default: %(default)r).', |
---|
1014 | action = 'store_true') |
---|
1015 | argsp.add_argument('--net-boot-dhcp', |
---|
1016 | help = 'network boot using dhcp (default: %(default)r).', |
---|
1017 | action = 'store_true', default = False) |
---|
1018 | argsp.add_argument('--net-boot-ip', |
---|
1019 | help = 'network boot IP address (default: %(default)r).', |
---|
1020 | type = str, action = valid_ip, default = None) |
---|
1021 | argsp.add_argument('--net-boot-server', |
---|
1022 | help = 'network boot server IP address ' + \ |
---|
1023 | '(default: %(default)r).', |
---|
1024 | type = str, action = valid_ip, default = None) |
---|
1025 | argsp.add_argument('--net-boot-file', |
---|
1026 | help = 'network boot file (default: %(default)r).', |
---|
1027 | type = str, default = 'rtems.img') |
---|
1028 | argsp.add_argument('--net-boot-fdt', |
---|
1029 | help = 'network boot load a fdt file (default: %(default)r).', |
---|
1030 | type = str, default = None) |
---|
1031 | argsp.add_argument('-U', '--custom-uenv', |
---|
1032 | help = 'install the custom uEnv.txt file ' + \ |
---|
1033 | '(default: %(default)r).', |
---|
1034 | type = str, action = valid_file, default = None) |
---|
1035 | argsp.add_argument('-b', '--board', |
---|
1036 | help = 'name of the board (default: %(default)r).', |
---|
1037 | type = str, default = 'list') |
---|
1038 | argsp.add_argument('--convert-kernel', |
---|
1039 | help = 'convert a kernel to a bootoader image ' + \ |
---|
1040 | '(default: %(default)r).', |
---|
1041 | action = 'store_true', default = False) |
---|
1042 | argsp.add_argument('--no-clean', |
---|
1043 | help = 'do not clean when finished (default: %(default)r).', |
---|
1044 | action = 'store_false', default = True) |
---|
1045 | argsp.add_argument('-o', '--output', |
---|
1046 | help = 'image output file name', |
---|
1047 | type = str, action = valid_file_if_exists, required = True) |
---|
1048 | argsp.add_argument('paths', |
---|
1049 | help = 'files or paths, the number and type sets the mode.', |
---|
1050 | nargs = '+', action = valid_paths) |
---|
1051 | |
---|
1052 | argopts = argsp.parse_args(args[1:]) |
---|
1053 | |
---|
1054 | load_log(argopts.log) |
---|
1055 | log.notice('RTEMS Tools - Boot Image, %s' % (version.string())) |
---|
1056 | log.output(log.info(args)) |
---|
1057 | log.tracing = argopts.trace |
---|
1058 | |
---|
1059 | if argopts.net_boot_dhcp is not None or \ |
---|
1060 | argopts.net_boot_ip is not None: |
---|
1061 | if argopts.convert_kernel: |
---|
1062 | raise error.general('net boot options not valid with kernel convert.') |
---|
1063 | if argopts.custom_uenv is not None: |
---|
1064 | raise error.general('cannot set custom uenv and net boot options.') |
---|
1065 | |
---|
1066 | host.load() |
---|
1067 | |
---|
1068 | log.output('Platform: %s' % (host.name)) |
---|
1069 | |
---|
1070 | if argopts.board == 'list': |
---|
1071 | loader = bootloader(command_path) |
---|
1072 | boards = loader.list_boards() |
---|
1073 | log.notice(' Board list: bootloaders (%d)' % (len(boards))) |
---|
1074 | for bl in sorted(boards): |
---|
1075 | log.notice(' %s: %d' % (bl, len(boards[bl]))) |
---|
1076 | for b in boards[bl]: |
---|
1077 | log.notice(' ' + b) |
---|
1078 | raise error.exit() |
---|
1079 | |
---|
1080 | loader = uboot_bootloader(command_path, |
---|
1081 | argopts.convert_kernel, |
---|
1082 | argopts.paths, |
---|
1083 | argopts.board) |
---|
1084 | |
---|
1085 | loader.check_mandatory_configs() |
---|
1086 | |
---|
1087 | if loader.convert_kernel: |
---|
1088 | if argopts.kernel is not None: |
---|
1089 | raise error.general('kernel convert does not use the kernel option.') |
---|
1090 | if len(argopts.paths) != 1: |
---|
1091 | raise error.general('kernel convert take a single path.') |
---|
1092 | argopts.kernel = argopts.paths[0] |
---|
1093 | else: |
---|
1094 | loader.clean = argopts.no_clean |
---|
1095 | |
---|
1096 | loader['board'] = argopts.board |
---|
1097 | loader['output'] = argopts.output |
---|
1098 | loader['build'] = 'build' # need an option for this at some point |
---|
1099 | loader['image_size'] = argopts.image_size |
---|
1100 | loader['fs_format'] = argopts.fs_format |
---|
1101 | loader['fs_size'] = argopts.fs_size |
---|
1102 | loader['fs_align'] = argopts.fs_align |
---|
1103 | loader['kernel'] = argopts.kernel |
---|
1104 | loader['fdt'] = argopts.fdt |
---|
1105 | loader['files'] = ','.join(argopts.file) |
---|
1106 | loader['net_dhcp'] = argopts.net_boot_dhcp |
---|
1107 | loader['net_ip'] = argopts.net_boot_ip |
---|
1108 | loader['net_server_ip'] = argopts.net_boot_server |
---|
1109 | loader['net_exe'] = argopts.net_boot_file |
---|
1110 | loader['net_fdt'] = argopts.net_boot_fdt |
---|
1111 | loader['uenv_txt'] = argopts.custom_uenv |
---|
1112 | |
---|
1113 | loader.log() |
---|
1114 | |
---|
1115 | if not loader.convert_kernel: |
---|
1116 | if loader['fs_size'] == 'auto': |
---|
1117 | loader['fs_size'] = \ |
---|
1118 | str(_si_size(loader['image_size']) - _si_size(loader['fs_align'])) |
---|
1119 | elif _si_size(loader['image_size']) < \ |
---|
1120 | _si_size(loader['fs_align']) + _si_size(loader['fs_size']): |
---|
1121 | raise error.general('filesystem partition size larger than image size.') |
---|
1122 | |
---|
1123 | if host.name not in builders: |
---|
1124 | err = 'no builder; platform not supported: %s' % (host.name) |
---|
1125 | raise error.general(err) |
---|
1126 | |
---|
1127 | builder = builders[host.name](loader) |
---|
1128 | |
---|
1129 | if not loader.check_exes() or not builder.check_exes(): |
---|
1130 | raise error.general('command(s) not found; please fix.') |
---|
1131 | |
---|
1132 | builder.build() |
---|
1133 | |
---|
1134 | except error.general as gerr: |
---|
1135 | notice = str(gerr) |
---|
1136 | ec = 1 |
---|
1137 | except error.internal as ierr: |
---|
1138 | notice = str(ierr) |
---|
1139 | ec = 1 |
---|
1140 | except error.exit as eerr: |
---|
1141 | pass |
---|
1142 | except KeyboardInterrupt: |
---|
1143 | notice = 'abort: user terminated' |
---|
1144 | ec = 1 |
---|
1145 | except: |
---|
1146 | raise |
---|
1147 | notice = 'abort: unknown error' |
---|
1148 | ec = 1 |
---|
1149 | if builder is not None: |
---|
1150 | del builder |
---|
1151 | if notice is not None: |
---|
1152 | log.stderr(notice) |
---|
1153 | sys.exit(ec) |
---|
1154 | |
---|
1155 | if __name__ == "__main__": |
---|
1156 | run() |
---|