diff options
Diffstat (limited to 'meta/lib/oe/fitimage.py')
-rw-r--r-- | meta/lib/oe/fitimage.py | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py new file mode 100644 index 0000000000..f303799155 --- /dev/null +++ b/meta/lib/oe/fitimage.py | |||
@@ -0,0 +1,547 @@ | |||
1 | # | ||
2 | # Copyright OpenEmbedded Contributors | ||
3 | # | ||
4 | # SPDX-License-Identifier: GPL-2.0-only | ||
5 | # | ||
6 | # This file contains common functions for the fitimage generation | ||
7 | |||
8 | import os | ||
9 | import shlex | ||
10 | import subprocess | ||
11 | import bb | ||
12 | |||
13 | from oeqa.utils.commands import runCmd | ||
14 | |||
15 | class ItsNode: | ||
16 | INDENT_SIZE = 8 | ||
17 | |||
18 | def __init__(self, name, parent_node, sub_nodes=None, properties=None): | ||
19 | self.name = name | ||
20 | self.parent_node = parent_node | ||
21 | |||
22 | self.sub_nodes = [] | ||
23 | if sub_nodes: | ||
24 | self.sub_nodes = sub_nodes | ||
25 | |||
26 | self.properties = {} | ||
27 | if properties: | ||
28 | self.properties = properties | ||
29 | |||
30 | if parent_node: | ||
31 | parent_node.add_sub_node(self) | ||
32 | |||
33 | def add_sub_node(self, sub_node): | ||
34 | self.sub_nodes.append(sub_node) | ||
35 | |||
36 | def add_property(self, key, value): | ||
37 | self.properties[key] = value | ||
38 | |||
39 | def emit(self, f, indent): | ||
40 | indent_str_name = " " * indent | ||
41 | indent_str_props = " " * (indent + self.INDENT_SIZE) | ||
42 | f.write("%s%s {\n" % (indent_str_name, self.name)) | ||
43 | for key, value in self.properties.items(): | ||
44 | bb.debug(1, "key: %s, value: %s" % (key, str(value))) | ||
45 | # Single integer: <0x12ab> | ||
46 | if isinstance(value, int): | ||
47 | f.write(indent_str_props + key + ' = <0x%x>;\n' % value) | ||
48 | # list of strings: "string1", "string2" or integers: <0x12ab 0x34cd> | ||
49 | elif isinstance(value, list): | ||
50 | if len(value) == 0: | ||
51 | f.write(indent_str_props + key + ' = "";\n') | ||
52 | elif isinstance(value[0], int): | ||
53 | list_entries = ' '.join('0x%x' % entry for entry in value) | ||
54 | f.write(indent_str_props + key + ' = <%s>;\n' % list_entries) | ||
55 | else: | ||
56 | list_entries = ', '.join('"%s"' % entry for entry in value) | ||
57 | f.write(indent_str_props + key + ' = %s;\n' % list_entries) | ||
58 | elif isinstance(value, str): | ||
59 | # path: /incbin/("path/to/file") | ||
60 | if key in ["data"] and value.startswith('/incbin/('): | ||
61 | f.write(indent_str_props + key + ' = %s;\n' % value) | ||
62 | # Integers which are already string formatted | ||
63 | elif value.startswith("<") and value.endswith(">"): | ||
64 | f.write(indent_str_props + key + ' = %s;\n' % value) | ||
65 | else: | ||
66 | f.write(indent_str_props + key + ' = "%s";\n' % value) | ||
67 | else: | ||
68 | bb.fatal("%s has unexpexted data type." % str(value)) | ||
69 | for sub_node in self.sub_nodes: | ||
70 | sub_node.emit(f, indent + self.INDENT_SIZE) | ||
71 | f.write(indent_str_name + '};\n') | ||
72 | |||
73 | class ItsNodeImages(ItsNode): | ||
74 | def __init__(self, parent_node): | ||
75 | super().__init__("images", parent_node) | ||
76 | |||
77 | class ItsNodeConfigurations(ItsNode): | ||
78 | def __init__(self, parent_node): | ||
79 | super().__init__("configurations", parent_node) | ||
80 | |||
81 | class ItsNodeHash(ItsNode): | ||
82 | def __init__(self, name, parent_node, algo, opt_props=None): | ||
83 | properties = { | ||
84 | "algo": algo | ||
85 | } | ||
86 | if opt_props: | ||
87 | properties.update(opt_props) | ||
88 | super().__init__(name, parent_node, None, properties) | ||
89 | |||
90 | class ItsImageSignature(ItsNode): | ||
91 | def __init__(self, name, parent_node, algo, keyname, opt_props=None): | ||
92 | properties = { | ||
93 | "algo": algo, | ||
94 | "key-name-hint": keyname | ||
95 | } | ||
96 | if opt_props: | ||
97 | properties.update(opt_props) | ||
98 | super().__init__(name, parent_node, None, properties) | ||
99 | |||
100 | class ItsNodeImage(ItsNode): | ||
101 | def __init__(self, name, parent_node, description, type, compression, sub_nodes=None, opt_props=None): | ||
102 | properties = { | ||
103 | "description": description, | ||
104 | "type": type, | ||
105 | "compression": compression, | ||
106 | } | ||
107 | if opt_props: | ||
108 | properties.update(opt_props) | ||
109 | super().__init__(name, parent_node, sub_nodes, properties) | ||
110 | |||
111 | class ItsNodeDtb(ItsNodeImage): | ||
112 | def __init__(self, name, parent_node, description, type, compression, | ||
113 | sub_nodes=None, opt_props=None, compatible=None): | ||
114 | super().__init__(name, parent_node, description, type, compression, sub_nodes, opt_props) | ||
115 | self.compatible = compatible | ||
116 | |||
117 | class ItsNodeDtbAlias(ItsNode): | ||
118 | """Additional Configuration Node for a DTB | ||
119 | |||
120 | Symlinks pointing to a DTB file are handled by an addtitional | ||
121 | configuration node referring to another DTB image node. | ||
122 | """ | ||
123 | def __init__(self, name, alias_name, compatible=None): | ||
124 | super().__init__(name, parent_node=None, sub_nodes=None, properties=None) | ||
125 | self.alias_name = alias_name | ||
126 | self.compatible = compatible | ||
127 | |||
128 | class ItsNodeConfigurationSignature(ItsNode): | ||
129 | def __init__(self, name, parent_node, algo, keyname, opt_props=None): | ||
130 | properties = { | ||
131 | "algo": algo, | ||
132 | "key-name-hint": keyname | ||
133 | } | ||
134 | if opt_props: | ||
135 | properties.update(opt_props) | ||
136 | super().__init__(name, parent_node, None, properties) | ||
137 | |||
138 | class ItsNodeConfiguration(ItsNode): | ||
139 | def __init__(self, name, parent_node, description, sub_nodes=None, opt_props=None): | ||
140 | properties = { | ||
141 | "description": description, | ||
142 | } | ||
143 | if opt_props: | ||
144 | properties.update(opt_props) | ||
145 | super().__init__(name, parent_node, sub_nodes, properties) | ||
146 | |||
147 | class ItsNodeRootKernel(ItsNode): | ||
148 | """Create FIT images for the kernel | ||
149 | |||
150 | Currently only a single kernel (no less or more) can be added to the FIT | ||
151 | image along with 0 or more device trees and 0 or 1 ramdisk. | ||
152 | |||
153 | If a device tree included in the FIT image, the default configuration is the | ||
154 | firt DTB. If there is no dtb present than the default configuation the kernel. | ||
155 | """ | ||
156 | def __init__(self, description, address_cells, host_prefix, arch, conf_prefix, | ||
157 | sign_enable=False, sign_keydir=None, | ||
158 | mkimage=None, mkimage_dtcopts=None, | ||
159 | mkimage_sign=None, mkimage_sign_args=None, | ||
160 | hash_algo=None, sign_algo=None, pad_algo=None, | ||
161 | sign_keyname_conf=None, | ||
162 | sign_individual=False, sign_keyname_img=None): | ||
163 | props = { | ||
164 | "description": description, | ||
165 | "#address-cells": f"<{address_cells}>" | ||
166 | } | ||
167 | super().__init__("/", None, None, props) | ||
168 | self.images = ItsNodeImages(self) | ||
169 | self.configurations = ItsNodeConfigurations(self) | ||
170 | |||
171 | self._host_prefix = host_prefix | ||
172 | self._arch = arch | ||
173 | self._conf_prefix = conf_prefix | ||
174 | |||
175 | # Signature related properties | ||
176 | self._sign_enable = sign_enable | ||
177 | self._sign_keydir = sign_keydir | ||
178 | self._mkimage = mkimage | ||
179 | self._mkimage_dtcopts = mkimage_dtcopts | ||
180 | self._mkimage_sign = mkimage_sign | ||
181 | self._mkimage_sign_args = mkimage_sign_args | ||
182 | self._hash_algo = hash_algo | ||
183 | self._sign_algo = sign_algo | ||
184 | self._pad_algo = pad_algo | ||
185 | self._sign_keyname_conf = sign_keyname_conf | ||
186 | self._sign_individual = sign_individual | ||
187 | self._sign_keyname_img = sign_keyname_img | ||
188 | self._sanitize_sign_config() | ||
189 | |||
190 | self._dtbs = [] | ||
191 | self._dtb_alias = [] | ||
192 | self._kernel = None | ||
193 | self._ramdisk = None | ||
194 | self._bootscr = None | ||
195 | self._setup = None | ||
196 | |||
197 | def _sanitize_sign_config(self): | ||
198 | if self._sign_enable: | ||
199 | if not self._hash_algo: | ||
200 | bb.fatal("FIT image signing is enabled but no hash algorithm is provided.") | ||
201 | if not self._sign_algo: | ||
202 | bb.fatal("FIT image signing is enabled but no signature algorithm is provided.") | ||
203 | if not self._pad_algo: | ||
204 | bb.fatal("FIT image signing is enabled but no padding algorithm is provided.") | ||
205 | if not self._sign_keyname_conf: | ||
206 | bb.fatal("FIT image signing is enabled but no configuration key name is provided.") | ||
207 | if self._sign_individual and not self._sign_keyname_img: | ||
208 | bb.fatal("FIT image signing is enabled for individual images but no image key name is provided.") | ||
209 | |||
210 | def write_its_file(self, itsfile): | ||
211 | with open(itsfile, 'w') as f: | ||
212 | f.write("/dts-v1/;\n\n") | ||
213 | self.emit(f, 0) | ||
214 | |||
215 | def its_add_node_image(self, image_id, description, image_type, compression, opt_props): | ||
216 | image_node = ItsNodeImage( | ||
217 | image_id, | ||
218 | self.images, | ||
219 | description, | ||
220 | image_type, | ||
221 | compression, | ||
222 | opt_props=opt_props | ||
223 | ) | ||
224 | if self._hash_algo: | ||
225 | ItsNodeHash( | ||
226 | "hash-1", | ||
227 | image_node, | ||
228 | self._hash_algo | ||
229 | ) | ||
230 | if self._sign_individual: | ||
231 | ItsImageSignature( | ||
232 | "signature-1", | ||
233 | image_node, | ||
234 | f"{self._hash_algo},{self._sign_algo}", | ||
235 | self._sign_keyname_img | ||
236 | ) | ||
237 | return image_node | ||
238 | |||
239 | def its_add_node_dtb(self, image_id, description, image_type, compression, opt_props, compatible): | ||
240 | dtb_node = ItsNodeDtb( | ||
241 | image_id, | ||
242 | self.images, | ||
243 | description, | ||
244 | image_type, | ||
245 | compression, | ||
246 | opt_props=opt_props, | ||
247 | compatible=compatible | ||
248 | ) | ||
249 | if self._hash_algo: | ||
250 | ItsNodeHash( | ||
251 | "hash-1", | ||
252 | dtb_node, | ||
253 | self._hash_algo | ||
254 | ) | ||
255 | if self._sign_individual: | ||
256 | ItsImageSignature( | ||
257 | "signature-1", | ||
258 | dtb_node, | ||
259 | f"{self._hash_algo},{self._sign_algo}", | ||
260 | self._sign_keyname_img | ||
261 | ) | ||
262 | return dtb_node | ||
263 | |||
264 | def fitimage_emit_section_kernel(self, kernel_id, kernel_path, compression, | ||
265 | load, entrypoint, mkimage_kernel_type, entrysymbol=None): | ||
266 | """Emit the fitImage ITS kernel section""" | ||
267 | if self._kernel: | ||
268 | bb.fatal("Kernel section already exists in the ITS file.") | ||
269 | if entrysymbol: | ||
270 | result = subprocess.run([self._host_prefix + "nm", "vmlinux"], capture_output=True, text=True) | ||
271 | for line in result.stdout.splitlines(): | ||
272 | parts = line.split() | ||
273 | if len(parts) == 3 and parts[2] == entrysymbol: | ||
274 | entrypoint = "<0x%s>" % parts[0] | ||
275 | break | ||
276 | kernel_node = self.its_add_node_image( | ||
277 | kernel_id, | ||
278 | "Linux kernel", | ||
279 | mkimage_kernel_type, | ||
280 | compression, | ||
281 | { | ||
282 | "data": '/incbin/("' + kernel_path + '")', | ||
283 | "arch": self._arch, | ||
284 | "os": "linux", | ||
285 | "load": f"<{load}>", | ||
286 | "entry": f"<{entrypoint}>" | ||
287 | } | ||
288 | ) | ||
289 | self._kernel = kernel_node | ||
290 | |||
291 | def fitimage_emit_section_dtb(self, dtb_id, dtb_path, dtb_loadaddress=None, | ||
292 | dtbo_loadaddress=None, add_compatible=False): | ||
293 | """Emit the fitImage ITS DTB section""" | ||
294 | load=None | ||
295 | dtb_ext = os.path.splitext(dtb_path)[1] | ||
296 | if dtb_ext == ".dtbo": | ||
297 | if dtbo_loadaddress: | ||
298 | load = dtbo_loadaddress | ||
299 | elif dtb_loadaddress: | ||
300 | load = dtb_loadaddress | ||
301 | |||
302 | opt_props = { | ||
303 | "data": '/incbin/("' + dtb_path + '")', | ||
304 | "arch": self._arch | ||
305 | } | ||
306 | if load: | ||
307 | opt_props["load"] = f"<{load}>" | ||
308 | |||
309 | # Preserve the DTB's compatible string to be added to the configuration node | ||
310 | compatible = None | ||
311 | if add_compatible: | ||
312 | compatible = get_compatible_from_dtb(dtb_path) | ||
313 | |||
314 | dtb_node = self.its_add_node_dtb( | ||
315 | "fdt-" + dtb_id, | ||
316 | "Flattened Device Tree blob", | ||
317 | "flat_dt", | ||
318 | "none", | ||
319 | opt_props, | ||
320 | compatible | ||
321 | ) | ||
322 | self._dtbs.append(dtb_node) | ||
323 | |||
324 | def fitimage_emit_section_dtb_alias(self, dtb_alias_id, dtb_path, add_compatible=False): | ||
325 | """Add a configuration node referring to another DTB""" | ||
326 | # Preserve the DTB's compatible string to be added to the configuration node | ||
327 | compatible = None | ||
328 | if add_compatible: | ||
329 | compatible = get_compatible_from_dtb(dtb_path) | ||
330 | |||
331 | dtb_id = os.path.basename(dtb_path) | ||
332 | dtb_alias_node = ItsNodeDtbAlias("fdt-" + dtb_id, dtb_alias_id, compatible) | ||
333 | self._dtb_alias.append(dtb_alias_node) | ||
334 | bb.warn(f"compatible: {compatible}, dtb_alias_id: {dtb_alias_id}, dtb_id: {dtb_id}, dtb_path: {dtb_path}") | ||
335 | |||
336 | def fitimage_emit_section_boot_script(self, bootscr_id, bootscr_path): | ||
337 | """Emit the fitImage ITS u-boot script section""" | ||
338 | if self._bootscr: | ||
339 | bb.fatal("U-boot script section already exists in the ITS file.") | ||
340 | bootscr_node = self.its_add_node_image( | ||
341 | bootscr_id, | ||
342 | "U-boot script", | ||
343 | "script", | ||
344 | "none", | ||
345 | { | ||
346 | "data": '/incbin/("' + bootscr_path + '")', | ||
347 | "arch": self._arch, | ||
348 | "type": "script" | ||
349 | } | ||
350 | ) | ||
351 | self._bootscr = bootscr_node | ||
352 | |||
353 | def fitimage_emit_section_setup(self, setup_id, setup_path): | ||
354 | """Emit the fitImage ITS setup section""" | ||
355 | if self._setup: | ||
356 | bb.fatal("Setup section already exists in the ITS file.") | ||
357 | load = "<0x00090000>" | ||
358 | entry = "<0x00090000>" | ||
359 | setup_node = self.its_add_node_image( | ||
360 | setup_id, | ||
361 | "Linux setup.bin", | ||
362 | "x86_setup", | ||
363 | "none", | ||
364 | { | ||
365 | "data": '/incbin/("' + setup_path + '")', | ||
366 | "arch": self._arch, | ||
367 | "os": "linux", | ||
368 | "load": load, | ||
369 | "entry": entry | ||
370 | } | ||
371 | ) | ||
372 | self._setup = setup_node | ||
373 | |||
374 | def fitimage_emit_section_ramdisk(self, ramdisk_id, ramdisk_path, description="ramdisk", load=None, entry=None): | ||
375 | """Emit the fitImage ITS ramdisk section""" | ||
376 | if self._ramdisk: | ||
377 | bb.fatal("Ramdisk section already exists in the ITS file.") | ||
378 | opt_props = { | ||
379 | "data": '/incbin/("' + ramdisk_path + '")', | ||
380 | "type": "ramdisk", | ||
381 | "arch": self._arch, | ||
382 | "os": "linux" | ||
383 | } | ||
384 | if load: | ||
385 | opt_props["load"] = f"<{load}>" | ||
386 | if entry: | ||
387 | opt_props["entry"] = f"<{entry}>" | ||
388 | |||
389 | ramdisk_node = self.its_add_node_image( | ||
390 | ramdisk_id, | ||
391 | description, | ||
392 | "ramdisk", | ||
393 | "none", | ||
394 | opt_props | ||
395 | ) | ||
396 | self._ramdisk = ramdisk_node | ||
397 | |||
398 | def _fitimage_emit_one_section_config(self, conf_node_name, dtb=None): | ||
399 | """Emit the fitImage ITS configuration section""" | ||
400 | opt_props = {} | ||
401 | conf_desc = [] | ||
402 | sign_entries = [] | ||
403 | |||
404 | if self._kernel: | ||
405 | conf_desc.append("Linux kernel") | ||
406 | opt_props["kernel"] = self._kernel.name | ||
407 | if self._sign_enable: | ||
408 | sign_entries.append("kernel") | ||
409 | |||
410 | if dtb: | ||
411 | conf_desc.append("FDT blob") | ||
412 | opt_props["fdt"] = dtb.name | ||
413 | if dtb.compatible: | ||
414 | opt_props["compatible"] = dtb.compatible | ||
415 | if self._sign_enable: | ||
416 | sign_entries.append("fdt") | ||
417 | |||
418 | if self._ramdisk: | ||
419 | conf_desc.append("ramdisk") | ||
420 | opt_props["ramdisk"] = self._ramdisk.name | ||
421 | if self._sign_enable: | ||
422 | sign_entries.append("ramdisk") | ||
423 | |||
424 | if self._bootscr: | ||
425 | conf_desc.append("u-boot script") | ||
426 | opt_props["bootscr"] = self._bootscr.name | ||
427 | if self._sign_enable: | ||
428 | sign_entries.append("bootscr") | ||
429 | |||
430 | if self._setup: | ||
431 | conf_desc.append("setup") | ||
432 | opt_props["setup"] = self._setup.name | ||
433 | if self._sign_enable: | ||
434 | sign_entries.append("setup") | ||
435 | |||
436 | # First added configuration is the default configuration | ||
437 | default_flag = "0" | ||
438 | if len(self.configurations.sub_nodes) == 0: | ||
439 | default_flag = "1" | ||
440 | |||
441 | conf_node = ItsNodeConfiguration( | ||
442 | conf_node_name, | ||
443 | self.configurations, | ||
444 | f"{default_flag} {', '.join(conf_desc)}", | ||
445 | opt_props=opt_props | ||
446 | ) | ||
447 | if self._hash_algo: | ||
448 | ItsNodeHash( | ||
449 | "hash-1", | ||
450 | conf_node, | ||
451 | self._hash_algo | ||
452 | ) | ||
453 | if self._sign_enable: | ||
454 | ItsNodeConfigurationSignature( | ||
455 | "signature-1", | ||
456 | conf_node, | ||
457 | f"{self._hash_algo},{self._sign_algo}", | ||
458 | self._sign_keyname_conf, | ||
459 | opt_props={ | ||
460 | "padding": self._pad_algo, | ||
461 | "sign-images": sign_entries | ||
462 | } | ||
463 | ) | ||
464 | |||
465 | def fitimage_emit_section_config(self, default_dtb_image=None): | ||
466 | if self._dtbs: | ||
467 | for dtb in self._dtbs: | ||
468 | dtb_name = dtb.name | ||
469 | if dtb.name.startswith("fdt-"): | ||
470 | dtb_name = dtb.name[len("fdt-"):] | ||
471 | self._fitimage_emit_one_section_config(self._conf_prefix + dtb_name, dtb) | ||
472 | for dtb in self._dtb_alias: | ||
473 | self._fitimage_emit_one_section_config(self._conf_prefix + dtb.alias_name, dtb) | ||
474 | else: | ||
475 | # Currently exactly one kernel is supported. | ||
476 | self._fitimage_emit_one_section_config(self._conf_prefix + "1") | ||
477 | |||
478 | default_conf = self.configurations.sub_nodes[0].name | ||
479 | if default_dtb_image and self._dtbs: | ||
480 | default_conf = self._conf_prefix + default_dtb_image | ||
481 | self.configurations.add_property('default', default_conf) | ||
482 | |||
483 | def run_mkimage_assemble(self, itsfile, fitfile): | ||
484 | cmd = [ | ||
485 | self._mkimage, | ||
486 | '-f', itsfile, | ||
487 | fitfile | ||
488 | ] | ||
489 | if self._mkimage_dtcopts: | ||
490 | cmd.insert(1, '-D') | ||
491 | cmd.insert(2, self._mkimage_dtcopts) | ||
492 | try: | ||
493 | subprocess.run(cmd, check=True, capture_output=True) | ||
494 | except subprocess.CalledProcessError as e: | ||
495 | bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}\nitsflile: {os.path.abspath(itsfile)}") | ||
496 | |||
497 | def run_mkimage_sign(self, fitfile): | ||
498 | if not self._sign_enable: | ||
499 | bb.debug(1, "FIT image signing is disabled. Skipping signing.") | ||
500 | return | ||
501 | |||
502 | # Some sanity checks because mkimage exits with 0 also without needed keys | ||
503 | sign_key_path = os.path.join(self._sign_keydir, self._sign_keyname_conf) | ||
504 | if not os.path.exists(sign_key_path + '.key') or not os.path.exists(sign_key_path + '.crt'): | ||
505 | bb.fatal("%s.key or .crt does not exist" % sign_key_path) | ||
506 | if self._sign_individual: | ||
507 | sign_key_img_path = os.path.join(self._sign_keydir, self._sign_keyname_img) | ||
508 | if not os.path.exists(sign_key_img_path + '.key') or not os.path.exists(sign_key_img_path + '.crt'): | ||
509 | bb.fatal("%s.key or .crt does not exist" % sign_key_img_path) | ||
510 | |||
511 | cmd = [ | ||
512 | self._mkimage_sign, | ||
513 | '-F', | ||
514 | '-k', self._sign_keydir, | ||
515 | '-r', fitfile | ||
516 | ] | ||
517 | if self._mkimage_dtcopts: | ||
518 | cmd.extend(['-D', self._mkimage_dtcopts]) | ||
519 | if self._mkimage_sign_args: | ||
520 | cmd.extend(shlex.split(self._mkimage_sign_args)) | ||
521 | try: | ||
522 | subprocess.run(cmd, check=True, capture_output=True) | ||
523 | except subprocess.CalledProcessError as e: | ||
524 | bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}") | ||
525 | |||
526 | |||
527 | def symlink_points_below(file_or_symlink, expected_parent_dir): | ||
528 | """returns symlink destination if it points below directory""" | ||
529 | file_path = os.path.join(expected_parent_dir, file_or_symlink) | ||
530 | if not os.path.islink(file_path): | ||
531 | return None | ||
532 | |||
533 | realpath = os.path.relpath(os.path.realpath(file_path), expected_parent_dir) | ||
534 | if realpath.startswith(".."): | ||
535 | return None | ||
536 | |||
537 | return realpath | ||
538 | |||
539 | def get_compatible_from_dtb(dtb_path, fdtget_path="fdtget"): | ||
540 | compatible = None | ||
541 | cmd = [fdtget_path, "-t", "s", dtb_path, "/", "compatible"] | ||
542 | try: | ||
543 | ret = subprocess.run(cmd, check=True, capture_output=True, text=True) | ||
544 | compatible = ret.stdout.strip().split() | ||
545 | except subprocess.CalledProcessError: | ||
546 | compatible = None | ||
547 | return compatible | ||