├── requirements.txt
├── tests
├── data
│ ├── u-boot.bin
│ ├── imx7d-sdb.dtb
│ ├── env.txt
│ ├── u-boot.its
│ └── script.txt
├── test_fdt_image.py
├── test_env_image.py
├── test_cli_mkenv.py
├── test_env_blob.py
├── test_cli_envimg.py
├── test_cli_mkimg.py
└── test_old_image.py
├── .travis.yml
├── uboot
├── __init__.py
├── cli_envimg.py
├── cli_mkenv.py
├── env_image.py
├── env_blob.py
├── common.py
├── cli_mkimg.py
├── fdt_image.py
└── old_image.py
├── .gitignore
├── setup.py
├── docs
├── mkenv.md
├── envimg.md
└── mkimg.md
├── README.md
└── LICENSE
/requirements.txt:
--------------------------------------------------------------------------------
1 | fdt==0.1.2
2 | click==7.0
3 | easy_enum==0.2.0
4 |
--------------------------------------------------------------------------------
/tests/data/u-boot.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molejar/pyUBoot/HEAD/tests/data/u-boot.bin
--------------------------------------------------------------------------------
/tests/data/imx7d-sdb.dtb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/molejar/pyUBoot/HEAD/tests/data/imx7d-sdb.dtb
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.6"
4 | before_install:
5 | - pip install pytest pytest-console-scripts
6 | - pip install pytest-cov
7 | - pip install coveralls
8 | - pip install -r requirements.txt
9 | install:
10 | - pip install -e .
11 | script:
12 | - py.test --cov=uboot tests/*
13 | after_success:
14 | - coveralls
--------------------------------------------------------------------------------
/tests/data/env.txt:
--------------------------------------------------------------------------------
1 | bootdelay=3
2 | stdin=serial
3 | stdout=serial
4 | stderr=serial
5 | baudrate=115200
6 | console=ttymxc3
7 | ethaddr=12:34:56:78:90:AB
8 | ethact=FEC
9 | mmcdev=0
10 | mmcpart=1
11 | rootdev=mmcblk2p2
12 | fdtaddr=0x18000000
13 | fdtfile=imx6q-pop-arm2.dtb
14 | loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdtaddr} ${fdtfile}
15 | imgaddr=0x12000000
16 | imgfile=zImage
17 | loadimg=fatload mmc ${mmcdev}:${mmcpart} ${imgaddr} ${imgfile}
18 | bootargs=console=${console},${baudrate} root=/dev/${rootdev} rootwait rw
19 | bootcmd=run loadfdt; run loadimg; bootz ${imgaddr} - ${fdtaddr};
20 |
--------------------------------------------------------------------------------
/tests/data/u-boot.its:
--------------------------------------------------------------------------------
1 | /dts-v1/;
2 |
3 | / {
4 | description = "i.MX7D U-Boot Image";
5 |
6 | images {
7 | uboot@1 {
8 | description = "U-Boot (32-bit)";
9 | data = /incbin/("u-boot.bin");
10 | type = "standalone";
11 | arch = "arm";
12 | compression = "none";
13 | load = <0x40200000>;
14 | };
15 |
16 | fdt@1 {
17 | description = "FDT i.MX7D-SDB";
18 | data = /incbin/("imx7d-sdb.dtb");
19 | type = "flat_dt";
20 | compression = "none";
21 | };
22 | };
23 |
24 | configurations {
25 | default = "config@1";
26 |
27 | config@1 {
28 | description = "fsl-imx7d-sdb";
29 | firmware = "uboot@1";
30 | fdt = "fdt@1";
31 | };
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/tests/data/script.txt:
--------------------------------------------------------------------------------
1 | # U-Boot Script
2 |
3 | echo '>> Run Script ...'
4 | setenv autoload 'no'
5 | dhcp
6 | setenv serverip 192.168.1.162
7 | setenv hostname 'imx7dsb'
8 | setenv netdev 'eth0'
9 | setenv nfsroot '/srv/nfs/imx7d'
10 | setenv imgfile '/imx7d/zImage'
11 | setenv fdtfile '/imx7d/imx7d-sdb.dtb'
12 | setenv fdtaddr 0x83000000
13 | setenv imgaddr 0x80800000
14 | setenv imgload 'tftp ${imgaddr} ${imgfile}'
15 | setenv fdtload 'tftp ${fdtaddr} ${fdtfile}'
16 | setenv netargs 'setenv bootargs console=${console},${baudrate} root=/dev/nfs rw nfsroot=${serverip}:${nfsroot},v3,tcp ip=dhcp'
17 | setenv netboot 'echo Booting from net ...; run netargs; run imgload; run fdtload; bootz ${imgaddr} - ${fdtaddr};'
18 | run netboot
19 |
--------------------------------------------------------------------------------
/tests/test_fdt_image.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import pytest
17 | from uboot import parse_itb, parse_its, FdtImage
18 |
19 |
20 | # Used Directories
21 | DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
22 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp')
23 |
24 | # Test Files
25 | UBOOT_ITS = os.path.join(DATA_DIR, 'u-boot.its')
26 | UBOOT_ITB_TEMP = os.path.join(TEMP_DIR, 'u-boot.itb')
27 |
28 |
29 | def setup_module(module):
30 | # Create temp directory
31 | os.makedirs(TEMP_DIR, exist_ok=True)
32 |
33 |
34 | def teardown_module(module):
35 | # Delete created files
36 | #os.remove(UBOOT_ITB_TEMP)
37 | pass
38 |
39 |
40 | def test_01():
41 | pass
--------------------------------------------------------------------------------
/tests/test_env_image.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import shutil
17 | import pytest
18 | from uboot import EnvImgOld
19 |
20 | # Used Directories
21 | DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
22 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp')
23 |
24 | # Test Files
25 | UBOOT_BIN = os.path.join(DATA_DIR, 'u-boot.bin')
26 | ENV_TXT_TEMP = os.path.join(TEMP_DIR, 'u-boot_env.txt')
27 | UBOOT_BIN_TEMP = os.path.join(TEMP_DIR, 'u-boot.bin')
28 |
29 |
30 | def setup_module(module):
31 | # Create temp directory
32 | os.makedirs(TEMP_DIR, exist_ok=True)
33 | shutil.copyfile(UBOOT_BIN, UBOOT_BIN_TEMP)
34 |
35 |
36 | def teardown_module(module):
37 | # Delete created files
38 | #os.remove(ENV_TXT_TEMP)
39 | #os.remove(UBOOT_BIN_TEMP)
40 | pass
41 |
42 |
43 | def test_01():
44 | pass
45 |
46 |
47 | def test_02():
48 | pass
49 |
50 |
51 | def test_03():
52 | pass
--------------------------------------------------------------------------------
/uboot/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .common import EnumArchType, EnumOsType, EnumImageType, EnumCompressionType
16 | from .old_image import StdImage, FwImage, ScriptImage, MultiImage, get_img_type, new_img, parse_img
17 | from .fdt_image import FdtImage, parse_its, parse_itb
18 | from .env_image import EnvImgOld
19 | from .env_blob import EnvBlob
20 |
21 |
22 | __author__ = "Martin Olejar"
23 | __contact__ = "martin.olejar@gmail.com"
24 | __version__ = "0.1.1"
25 | __license__ = "Apache 2.0"
26 | __status__ = "Development"
27 | __all__ = [
28 | # Classes
29 | 'EnvBlob',
30 | 'EnvImgOld',
31 | 'FdtImage',
32 | 'StdImage',
33 | 'FwImage',
34 | 'ScriptImage',
35 | 'MultiImage',
36 | # Enums
37 | 'EnumOsType',
38 | 'EnumArchType',
39 | 'EnumImageType',
40 | 'EnumCompressionType',
41 | # Methods
42 | 'get_img_type',
43 | 'new_img',
44 | 'parse_img',
45 | 'parse_its',
46 | 'parse_itb'
47 | ]
48 |
49 |
50 | def parse_blob(data, offset=0):
51 | """ Universal parser for binary blob
52 |
53 | :param data:
54 | :param offset:
55 | :return:
56 | """
57 | raise NotImplementedError()
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | # IDE
107 | .idea
--------------------------------------------------------------------------------
/tests/test_cli_mkenv.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import pytest
17 |
18 | # Used Directories
19 | DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
20 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp')
21 |
22 | # Test Files
23 | ENV_TXT = os.path.join(DATA_DIR, 'env.txt')
24 | ENV_TXT_TEMP = os.path.join(TEMP_DIR, 'env.txt')
25 | ENV_BIN_TEMP = os.path.join(TEMP_DIR, 'env.bin')
26 |
27 |
28 | def setup_module(module):
29 | # Create temp directory
30 | os.makedirs(TEMP_DIR, exist_ok=True)
31 |
32 |
33 | def teardown_module(module):
34 | # Delete created files
35 | os.remove(ENV_TXT_TEMP)
36 | os.remove(ENV_BIN_TEMP)
37 |
38 |
39 | @pytest.mark.script_launch_mode('subprocess')
40 | def test_mkenv_create(script_runner):
41 | ret = script_runner.run('mkenv', 'create', ENV_TXT, ENV_BIN_TEMP)
42 | assert ret.success
43 |
44 |
45 | @pytest.mark.script_launch_mode('subprocess')
46 | def test_mkenv_info(script_runner):
47 | ret = script_runner.run('mkenv', 'info', ENV_BIN_TEMP)
48 | assert ret.success
49 |
50 |
51 | @pytest.mark.script_launch_mode('subprocess')
52 | def test_mkenv_extract(script_runner):
53 | ret = script_runner.run('mkenv', 'extract', ENV_BIN_TEMP)
54 | assert ret.success
55 |
--------------------------------------------------------------------------------
/tests/test_env_blob.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import pytest
17 | from uboot import EnvBlob
18 |
19 | # Used Directories
20 | DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
21 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp')
22 |
23 | # Test Files
24 | ENV_TXT = os.path.join(DATA_DIR, 'env.txt')
25 | ENV_TXT_TEMP = os.path.join(TEMP_DIR, 'env.txt')
26 | ENV_BIN_TEMP = os.path.join(TEMP_DIR, 'env.bin')
27 |
28 |
29 | def setup_module(module):
30 | # Create temp directory
31 | os.makedirs(TEMP_DIR, exist_ok=True)
32 |
33 |
34 | def teardown_module(module):
35 | # Delete created files
36 | os.remove(ENV_TXT_TEMP)
37 | os.remove(ENV_BIN_TEMP)
38 |
39 |
40 | def test_01():
41 | env = EnvBlob("Test")
42 | env.set("int_variable", 100)
43 | env.set("str_variable", "value")
44 |
45 | tmp = env.get("str_variable")
46 | assert tmp == "value"
47 |
48 | with pytest.raises(Exception):
49 | tmp = env.get("test")
50 |
51 |
52 | def test_02():
53 | env = EnvBlob("Test")
54 |
55 | with open(ENV_TXT, 'r') as f:
56 | env.load(f.read())
57 |
58 | with open(ENV_TXT_TEMP, 'w') as f:
59 | f.write(env.store())
60 |
61 | with open(ENV_BIN_TEMP, 'wb') as f:
62 | f.write(env.export())
63 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2018 Martin Olejar
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 |
18 | from os import path
19 | from setuptools import setup
20 | from uboot import __version__, __license__, __author__, __contact__
21 |
22 |
23 | def long_description():
24 | try:
25 | import pypandoc
26 |
27 | readme_path = path.join(path.dirname(__file__), 'README.md')
28 | return pypandoc.convert(readme_path, 'rst')
29 | except (IOError, ImportError):
30 | return (
31 | "More on: https://github.com/molejar/pyUBoot"
32 | )
33 |
34 | setup(
35 | name='uboot',
36 | author=__author__,
37 | version=__version__,
38 | license=__license__,
39 | author_email=__contact__,
40 | url='https://github.com/molejar/pyUBoot',
41 | description='Open Source library for manipulating with U-Boot images and environment variables',
42 | long_description=long_description(),
43 | python_requires='>=3.6',
44 | install_requires=[
45 | 'fdt==0.1.2',
46 | 'click==7.0',
47 | 'easy_enum==0.2.0'
48 | ],
49 | packages=['uboot'],
50 | classifiers=[
51 | 'Programming Language :: Python :: 3',
52 | 'Operating System :: OS Independent',
53 | 'License :: OSI Approved :: Apache Software License',
54 | 'Topic :: Scientific/Engineering',
55 | 'Topic :: Software Development :: Embedded Systems',
56 | 'Topic :: Utilities'
57 | ],
58 | entry_points={
59 | 'console_scripts': [
60 | 'envimg = uboot.cli_envimg:main',
61 | 'mkenv = uboot.cli_mkenv:main',
62 | 'mkimg = uboot.cli_mkimg:main'
63 | ],
64 | }
65 | )
66 |
--------------------------------------------------------------------------------
/tests/test_cli_envimg.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import shutil
17 | import pytest
18 |
19 | # Used Directories
20 | DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
21 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp')
22 |
23 | # Test Files
24 | UBOOT_BIN = os.path.join(DATA_DIR, 'u-boot.bin')
25 | ENV_TXT_TEMP = os.path.join(TEMP_DIR, 'u-boot_env.txt')
26 | UBOOT_BIN_TEMP = os.path.join(TEMP_DIR, 'u-boot.bin')
27 |
28 |
29 | def setup_module(module):
30 | # Create temp directory and copy u-boot.bin into this directory
31 | os.makedirs(TEMP_DIR, exist_ok=True)
32 | shutil.copyfile(UBOOT_BIN, UBOOT_BIN_TEMP)
33 |
34 |
35 | def teardown_module(module):
36 | # Delete created files
37 | os.remove(ENV_TXT_TEMP)
38 | os.remove(UBOOT_BIN_TEMP)
39 |
40 |
41 | @pytest.mark.script_launch_mode('subprocess')
42 | def test_envimg_info(script_runner):
43 | ret = script_runner.run('envimg', 'info', 'bootcmd=', UBOOT_BIN)
44 | assert ret.success
45 |
46 |
47 | @pytest.mark.script_launch_mode('subprocess')
48 | def test_envimg_export(script_runner):
49 | ret = script_runner.run('envimg', 'export', 'bootcmd=', UBOOT_BIN, ENV_TXT_TEMP)
50 | assert ret.success
51 |
52 |
53 | @pytest.mark.script_launch_mode('subprocess')
54 | def test_envimg_update(script_runner):
55 | ret = script_runner.run('envimg', 'update', 'bootcmd=', UBOOT_BIN_TEMP, '-e bootdelay=0')
56 | assert ret.success
57 |
58 |
59 | @pytest.mark.script_launch_mode('subprocess')
60 | def test_envimg_replace(script_runner):
61 | ret = script_runner.run('envimg', 'replace', 'bootcmd=', UBOOT_BIN_TEMP, ENV_TXT_TEMP)
62 | assert ret.success
63 |
--------------------------------------------------------------------------------
/tests/test_cli_mkimg.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import shutil
17 | import pytest
18 |
19 | # Used Directories
20 | DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
21 | TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp')
22 |
23 | # Test Files
24 | UBOOT_ITS = os.path.join(DATA_DIR, 'u-boot.its')
25 | SCRIPT_TXT = os.path.join(DATA_DIR, 'script.txt')
26 | UBOOT_ITB_TEMP = os.path.join(TEMP_DIR, 'u-boot.itb')
27 | SCRIPT_BIN_TEMP = os.path.join(TEMP_DIR, 'script.bin')
28 |
29 |
30 | def setup_module(module):
31 | # Create temp directory
32 | os.makedirs(TEMP_DIR, exist_ok=True)
33 |
34 |
35 | def teardown_module(module):
36 | # Delete temp directory
37 | shutil.rmtree(TEMP_DIR)
38 |
39 |
40 | @pytest.mark.script_launch_mode('subprocess')
41 | def test_mkimg_create(script_runner):
42 | ret = script_runner.run('mkimg', 'create', '-i', 'script', SCRIPT_BIN_TEMP, SCRIPT_TXT)
43 | assert ret.success
44 |
45 |
46 | @pytest.mark.script_launch_mode('subprocess')
47 | def test_mkimg_info(script_runner):
48 | ret = script_runner.run('mkimg', 'info', SCRIPT_BIN_TEMP)
49 | assert ret.success
50 |
51 |
52 | @pytest.mark.script_launch_mode('subprocess')
53 | def test_mkimg_extract(script_runner):
54 | ret = script_runner.run('mkimg', 'extract', SCRIPT_BIN_TEMP)
55 | assert ret.success
56 |
57 |
58 | @pytest.mark.script_launch_mode('subprocess')
59 | def test_mkimg_create_itb(script_runner):
60 | ret = script_runner.run('mkimg', 'createitb', '-o', UBOOT_ITB_TEMP, UBOOT_ITS)
61 | assert ret.success
62 |
63 |
64 | @pytest.mark.script_launch_mode('subprocess')
65 | def test_mkimg_info_itb(script_runner):
66 | ret = script_runner.run('mkimg', 'infoitb', UBOOT_ITB_TEMP)
67 | assert ret.success
68 |
69 |
70 | @pytest.mark.script_launch_mode('subprocess')
71 | def test_mkimg_extract_itb(script_runner):
72 | ret = script_runner.run('mkimg', 'extractitb', UBOOT_ITB_TEMP)
73 | assert ret.success
74 |
--------------------------------------------------------------------------------
/docs/mkenv.md:
--------------------------------------------------------------------------------
1 | U-Boot Environment Blob Tool
2 | ============================
3 |
4 | The `mkenv` is a tool to generate a U-Boot environment binary blob from a text file or extract U-Boot environment
5 | variables from a binary blob. It's implementing similar functionality as `mkenvimage` tool natively used in Linux.
6 |
7 | Usage
8 | -----
9 |
10 | For printing a general info of usage this tool execute `mkenv -?`.
11 |
12 | ```sh
13 | $ Usage: mkenv [OPTIONS] COMMAND [ARGS]...
14 |
15 | The U-Boot Make Enviroment Blob Tool
16 |
17 | Options:
18 | -v, --version Show the version and exit.
19 | -?, --help Show this message and exit.
20 |
21 | Commands:
22 | create Create new image from attached file
23 | extract Extract image content
24 | info List image content
25 | ```
26 |
27 | ## Commands
28 |
29 | #### $ mkenv info FILE
30 |
31 | Print the content of U-Boot environment blob in readable format
32 |
33 | ##### options:
34 | * **-b, --bigendian** - The target is big endian (default is little endian)
35 | * **-o, --offset** - The offset of input file (default: 0)
36 | * **-s, --size** - The environment blob size (default: 8192)
37 | * **-?, --help** - Show help message and exit
38 |
39 | ##### Example:
40 |
41 | ```sh
42 | $ mkenv info env.bin
43 |
44 | Name: None
45 | Redundant: True
46 | Endian: Little
47 | Size: 8192 Bytes
48 | EmptyValue: 0x00
49 | Variables:
50 | - bootdelay = 3
51 | - stdin = serial
52 | - stdout = serial
53 | - stderr = serial
54 | - baudrate = 115200
55 | - console = ttymxc3
56 | ...
57 | ```
58 |
59 |
60 |
61 | #### $ mkenv extract FILE
62 |
63 | Extract the content of U-Boot environment blob and save it as readable text file. That way you can extract the U-Boot environment blob from SD card image.
64 |
65 | ##### options:
66 | * **-b, --bigendian** - The target is big endian (default is little endian)
67 | * **-o, --offset** - The offset of input file (default: 0)
68 | * **-s, --size** - The environment blob size (default: 8192)
69 | * **-?, --help** - Show help message and exit
70 |
71 | ##### Example:
72 |
73 | ```sh
74 | $ mkenv extract env.bin
75 |
76 | Successfully extracted: env.txt
77 | ```
78 |
79 |
80 |
81 | #### $ mkenv create [OPTIONS] INFILE OUTFILE
82 |
83 | Create U-Boot environment blob from input text file and save it as binary file.
84 |
85 | ##### options:
86 | * **-r, --redundant** - The environment has multiple copies in flash (default: False)
87 | * **-b, --bigendian** - The target is big endian (default is little endian)
88 | * **-s, --size** - The environment blob size (default: 8192)
89 | * **-?, --help** - Show help message and exit
90 |
91 | ##### Example:
92 |
93 | ```sh
94 | $ mkenv create env.txt env.bin
95 |
96 | Successfully created: env.bin
97 | ```
--------------------------------------------------------------------------------
/docs/envimg.md:
--------------------------------------------------------------------------------
1 | Tool for editing environment variables inside U-Boot image
2 | ==========================================================
3 |
4 | The `envimg` is a tool for editing environment variables inside U-Boot image.
5 |
6 | Usage
7 | -----
8 |
9 | For printing a general info of usage this tool execute `envimg -?`.
10 |
11 | ```sh
12 | Usage: envimg.py [OPTIONS] COMMAND [ARGS]...
13 |
14 | Tool for editing environment variables inside U-Boot image
15 |
16 | Options:
17 | -v, --version Show the version and exit.
18 | -?, --help Show this message and exit.
19 |
20 | Commands:
21 | info List U-Boot environment variables
22 | export Export U-Boot environment variables
23 | replace Replace U-Boot environment variables
24 | update Update U-Boot environment variables
25 | ```
26 |
27 | ## Commands
28 |
29 | #### $ envimg info [OPTIONS] MARK FILE
30 |
31 | Print the content of U-Boot environment variables located inside image.
32 |
33 | ##### options:
34 | * **-?, --help** - Show help message and exit
35 |
36 | ##### Example:
37 |
38 | ```sh
39 | $ envimg info bootcmd= u-boot.imx
40 |
41 | Image Name: u-boot.imx
42 | Image Size: 478208 bytes
43 | EnVar Size: 2470 bytes
44 | EnVar Offset: 312273
45 | EnVar Mark: bootcmd=
46 | EnVar Content:
47 | - bootcmd = mmc dev ${mmcdev}; mmc dev ${mmcdev}; if mmc rescan; then run mmcboot; else run netboot; fi;
48 | - bootdelay = 3
49 | - ...
50 | ```
51 |
52 |
53 |
54 | #### $ envimg export [OPTIONS] MARK FILE FENV
55 |
56 | Export environment variables from U-Boot image.
57 |
58 | ##### options:
59 | * **-?, --help** - Show help message and exit
60 |
61 | ##### Example:
62 |
63 | ```sh
64 | $ envimg export bootcmd= u-boot.imx env.txt
65 |
66 | Environment variables saved into: env.txt
67 | ```
68 |
69 |
70 |
71 | #### $ envimg update [OPTIONS] MARK FILE
72 |
73 | Update environment variables inside U-Boot image.
74 |
75 | ##### options:
76 | * **-f, --fenv** - The file with environment variables
77 | * **-e, --env** - Environment variables
78 | * **-?, --help** - Show help message and exit
79 |
80 | ##### Example:
81 |
82 | ```sh
83 | $ envimg update bootcmd= u-boot.imx -e "bootdelay = 6"
84 |
85 | Image Name: u-boot.imx
86 | Image Size: 478208 bytes
87 | EnVar Size: 2470 bytes
88 | EnVar Offset: 312273
89 | EnVar Mark: bootcmd=
90 | EnVar Content:
91 | - bootcmd = mmc dev ${mmcdev}; mmc dev ${mmcdev}; if mmc rescan; then run mmcboot; else run netboot; fi;
92 | - bootdelay = 6
93 | - ...
94 | ```
95 |
96 |
97 |
98 | #### $ envimg replace [OPTIONS] MARK FILE FENV
99 |
100 | Replace environment variables inside U-Boot image.
101 |
102 | ##### options:
103 | * **-?, --help** - Show help message and exit
104 |
105 | ##### Example:
106 |
107 | ```sh
108 | $ envimg update bootcmd= u-boot.imx env.txt
109 |
110 | ...
111 | ```
--------------------------------------------------------------------------------
/uboot/cli_envimg.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import sys
17 | import click
18 | import uboot
19 |
20 | # Application error code
21 | ERROR_CODE = 1
22 |
23 | # The version of u-boot tools
24 | VERSION = uboot.__version__
25 |
26 | # Short description of U-Boot envimg tool
27 | DESCRIP = (
28 | "Tool for editing environment variables inside U-Boot image"
29 | )
30 |
31 |
32 | # U-Boot envimg: Base options
33 | @click.group(context_settings=dict(help_option_names=['-?', '--help']), help=DESCRIP)
34 | @click.version_option(VERSION, '-v', '--version')
35 | def cli():
36 | click.echo()
37 |
38 |
39 | # U-Boot envimg: List U-Boot environment variables
40 | @cli.command(short_help="List U-Boot environment variables")
41 | @click.argument('mark', nargs=1, type=click.STRING)
42 | @click.argument('file', nargs=1, type=click.Path(exists=True))
43 | def info(mark, file):
44 | """ List U-Boot environment variables """
45 | try:
46 | envimg = uboot.EnvImgOld(start_string=mark)
47 | envimg.open_img(file)
48 |
49 | except Exception as e:
50 | click.echo(str(e) if str(e) else "Unknown Error !")
51 | sys.exit(ERROR_CODE)
52 |
53 | click.echo(str(envimg))
54 |
55 |
56 | # U-Boot envimg: Export U-Boot environment variables
57 | @cli.command(short_help="Export U-Boot environment variables")
58 | @click.argument('mark', nargs=1, type=click.STRING)
59 | @click.argument('file', nargs=1, type=click.Path(exists=True))
60 | @click.argument('fenv', nargs=1, type=click.Path())
61 | def export(mark, file, fenv):
62 | """ Export U-Boot environment variables """
63 | try:
64 | envimg = uboot.EnvImgOld(start_string=mark)
65 | envimg.open_img(file)
66 |
67 | with open(fenv, 'w') as f:
68 | f.write(envimg.store())
69 |
70 | except Exception as e:
71 | click.echo(str(e) if str(e) else "Unknown Error !")
72 | sys.exit(ERROR_CODE)
73 |
74 | click.secho("Environment variables saved into: %s" % fenv)
75 |
76 |
77 | # U-Boot envimg: Update U-Boot environment variables
78 | @cli.command(short_help="Update U-Boot environment variables")
79 | @click.option('-f', '--fenv', type=click.Path(exists=True), help="The file with environment variables")
80 | @click.option('-e', '--env', type=click.STRING, multiple=True, help="Environment variables")
81 | @click.argument('mark', nargs=1, type=click.STRING)
82 | @click.argument('file', nargs=1, type=click.Path(exists=True))
83 | def update(env, mark, file, fenv=None):
84 | """ Update U-Boot environment variables """
85 | changed = False
86 |
87 | try:
88 | envimg = uboot.EnvImgOld(start_string=mark)
89 | envimg.open_img(file)
90 |
91 | if fenv is not None:
92 | changed = True
93 | with open(fenv, 'r') as f:
94 | envimg.load(f.read())
95 |
96 | if env:
97 | changed = True
98 | for var in env:
99 | name, value = var.split('=', 1)
100 | envimg.set(name.strip(), value.strip())
101 |
102 | if changed:
103 | envimg.save_img(file)
104 |
105 | except Exception as e:
106 | click.echo(str(e) if str(e) else "Unknown Error !")
107 | sys.exit(ERROR_CODE)
108 |
109 | click.echo(str(envimg))
110 |
111 |
112 | # U-Boot envimg: Replace U-Boot environment variables
113 | @cli.command(short_help="Replace U-Boot environment variables")
114 | @click.argument('mark', nargs=1, type=click.STRING)
115 | @click.argument('file', nargs=1, type=click.Path(exists=True))
116 | @click.argument('fenv', nargs=1, type=click.Path(exists=True))
117 | def replace(mark, file, fenv):
118 | """ Replace U-Boot environment variables """
119 | try:
120 | envimg = uboot.EnvImgOld(start_string=mark)
121 | envimg.open_img(file)
122 | envimg.clear()
123 |
124 | with open(fenv, 'r') as f:
125 | envimg.load(f.read())
126 |
127 | envimg.save_img(file)
128 |
129 | except Exception as e:
130 | click.echo(str(e) if str(e) else "Unknown Error !")
131 | sys.exit(ERROR_CODE)
132 |
133 | click.echo(str(envimg))
134 |
135 |
136 | def main():
137 | cli(obj={})
138 |
139 |
140 | if __name__ == '__main__':
141 | main()
142 |
--------------------------------------------------------------------------------
/docs/mkimg.md:
--------------------------------------------------------------------------------
1 | U-Boot Image Tool
2 | =================
3 |
4 | The `mkimg` is a tool to create images for use with the U-Boot boot loader. These images can contain the linux kernel, device tree blob, root file system image, firmware images etc., either separate or combined.It's implementing similar functionality as `mkimage` tool used in Linux.
5 |
6 | Usage
7 | -----
8 |
9 | For printing a general info of usage this tool execute `mkimg -?`.
10 |
11 | ```sh
12 | $ Usage: mkimg [OPTIONS] COMMAND [ARGS]...
13 |
14 | The U-Boot Image Tool
15 |
16 | Options:
17 | -v, --version Show the version and exit.
18 | -?, --help Show this message and exit.
19 |
20 | Commands:
21 | create Create old U-Boot image from attached files
22 | createitb Create new U-Boot image from *.its file
23 | extract Extract content from old U-Boot image
24 | extractitb Extract content from new U-Boot image
25 | info Show old image content
26 | infoitb Show new image content
27 | ```
28 |
29 | ## Commands for old U-Boot images
30 |
31 | #### $ mkimg info FILE
32 |
33 | Print the U-Boot executable image content in readable format
34 |
35 | ##### Example:
36 |
37 | ```sh
38 | $ mkimg info script.bin
39 |
40 | Image Name: iMX7D NetBoot Script
41 | Created: Tue Apr 04 17:15:01 2017
42 | Image Type: ARM Linux Script (uncompressed)
43 | Data Size: 0.62 kB
44 | Load Address: 0x00000000
45 | Entry Address: 0x00000000
46 | Content: 16 Commands
47 | 0) echo '>> Run Script ...'
48 | 1) setenv autoload 'no'
49 | 2) dhcp
50 | 3) setenv serverip 192.168.1.162
51 | 4) setenv hostname 'imx7dsb'
52 | 5) setenv netdev 'eth0'
53 | 6) setenv nfsroot '/srv/nfs/imx7d'
54 | 7) setenv imgfile '/imx7d/zImage'
55 | 8) setenv fdtfile '/imx7d/imx7d-sdb.dtb'
56 | 9) setenv fdtaddr 0x83000000
57 | 10) setenv imgaddr 0x80800000
58 | 11) setenv imgload 'tftp ${imgaddr} ${imgfile}'
59 | 12) setenv fdtload 'tftp ${fdtaddr} ${fdtfile}'
60 | 13) setenv netargs 'setenv bootargs console=${console},${baudrate} root=/dev/nfs rw nfsroot=${serverip}:${nfsroot},v3,tcp ip=dhcp'
61 | 14) setenv netboot 'echo Booting from net ...; run netargs; run imgload; run fdtload; bootz ${imgaddr} - ${fdtaddr};'
62 | 15) run netboot
63 | ```
64 |
65 |
66 |
67 | #### $ mkimg extract FILE
68 |
69 | Extract U-Boot executable image
70 |
71 | ##### Example:
72 |
73 | ```sh
74 | $ mkimg extract script.bin
75 |
76 | Image extracted into dir: script.bin.ex
77 | ```
78 |
79 |
80 |
81 | #### $ mkimg create [OPTIONS] OUTFILE [INFILES]
82 |
83 | Create U-Boot executable image (uImage, Script, ...)
84 |
85 | ##### options:
86 | * **-a, --arch** - Architecture (default: arm)
87 | * **-o, --ostype** - Operating system (default: linux)
88 | * **-i, --imgtype** - Image type (default: firmware)
89 | * **-c, --compress** - Image compression (default: none)
90 | * **-l, --laddr** - Load address (default: 0)
91 | * **-e, --epaddr** - Entry point address (default: 0)
92 | * **-n, --name** - Image name (max: 32 chars)
93 | * **-?, --help** - Show help message and exit
94 |
95 | ##### Example:
96 |
97 | ```sh
98 | $ mkimg create -a arm -o linux -i script -c none script.bin script.txt
99 |
100 | Created Image: script.bin
101 | ```
102 |
103 | ## Commands for new FDT U-Boot images
104 |
105 | #### $ mkimg infoitb FILE
106 |
107 | List new U-Boot image content in readable format
108 |
109 | ##### Example:
110 |
111 | ```sh
112 | $ mkimg infoitb image.itb
113 |
114 | FIT description: i.MX7D U-Boot Image
115 | Created: Fri May 11 21:51:14 2018
116 | Default config: config@1
117 |
118 | IMG[0] uboot@1
119 | size: 472.00 kB
120 | description: U-Boot (32-bit)
121 | type: standalone
122 | arch: arm
123 | compression: none
124 | load: 0x40200000
125 |
126 | IMG[1] fdt@1
127 | size: 46.00 kB
128 | description: FDT i.MX7D-SDB
129 | type: flat_dt
130 | compression: none
131 |
132 | CFG[0] config@1
133 | description: fsl-imx7d-sdb
134 | firmware: uboot@1
135 | fdt: fdt@1
136 | ```
137 |
138 |
139 |
140 | #### $ mkimg createitb [OPTIONS] FILE
141 |
142 | Create new U-Boot image from *.its file
143 |
144 | ##### options:
145 | * **-o, --outfile** - Output path/file name
146 | * **-p, --padding** - Add padding to the blob of long (default: 0)
147 | * **-a, --align** - Make the blob align to the (default: 0)
148 | * **-s, --size** - Make the blob at least long (default: none)
149 | * **-?, --help** - Show help message and exit
150 |
151 | ##### Example:
152 |
153 | ```sh
154 | $ mkimg createitb image.its
155 |
156 | Created Image: image.itb
157 | ```
158 |
159 |
160 |
161 | #### $ mkimg extractitb FILE
162 |
163 | Extract content from new U-Boot image (*.itb)
164 |
165 | ##### Example:
166 |
167 | ```sh
168 | $ mkimg extractitb image.itb
169 |
170 | Image extracted into dir: image.itb.ex
171 | ```
172 |
173 |
174 |
--------------------------------------------------------------------------------
/uboot/cli_mkenv.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import sys
17 | import click
18 | import uboot
19 |
20 | # Application error code
21 | ERROR_CODE = 1
22 |
23 | # The version of u-boot tools
24 | VERSION = uboot.__version__
25 |
26 | # Short description of U-Boot mkenv tool
27 | DESCRIP = (
28 | "The U-Boot Make Environment Blob Tool"
29 | )
30 |
31 |
32 | # User defined class
33 | class UInt(click.ParamType):
34 | """ Custom argument type for UINT """
35 | name = 'unsigned int'
36 |
37 | def __repr__(self):
38 | return 'UINT'
39 |
40 | def convert(self, value, param, ctx):
41 | try:
42 | if isinstance(value, (int,)):
43 | return value
44 | else:
45 | return int(value, 0)
46 | except:
47 | self.fail('%s is not a valid value' % value, param, ctx)
48 |
49 |
50 | # Create instances of custom argument types
51 | UINT = UInt()
52 |
53 |
54 | # U-Boot mkenv: Base options
55 | @click.group(context_settings=dict(help_option_names=['-?', '--help']), help=DESCRIP)
56 | @click.version_option(VERSION, '-v', '--version')
57 | def cli():
58 | click.echo()
59 |
60 |
61 | # U-Boot mkenv: List image content
62 | @cli.command(short_help="List image content")
63 | @click.option('-b', '--bigendian', is_flag=True, help="The target is big endian (default is little endian)")
64 | @click.option('-o', '--offset', type=UINT, default=0, show_default=True, help="The offset of input file")
65 | @click.option('-s', '--size', type=UINT, default=8192, show_default=True, help="The environment blob size")
66 | @click.argument('file', nargs=1, type=click.Path(exists=True))
67 | def info(offset, size, bigendian, file):
68 | """ List image content """
69 | try:
70 | with open(file, "rb") as f:
71 | f.seek(offset)
72 | data = f.read(size)
73 |
74 | env = uboot.EnvBlob.parse(data, 0, bigendian)
75 | env.size = size
76 |
77 | except Exception as e:
78 | click.echo(str(e) if str(e) else "Unknown Error !")
79 | sys.exit(ERROR_CODE)
80 |
81 | click.echo(str(env))
82 |
83 |
84 | # U-Boot mkenv: Create new image from attached file
85 | @cli.command(short_help="Create new image from attached file")
86 | @click.argument('infile', nargs=1, type=click.Path(exists=True))
87 | @click.argument('outfile', nargs=1, type=click.Path(readable=False))
88 | @click.option('-b', '--bigendian', is_flag=True, help="The target is big endian (default is little endian)")
89 | @click.option('-r', '--redundant', is_flag=True, show_default=True, help="The environment has multiple copies in flash")
90 | @click.option('-s', '--size', type=UINT, default=8192, show_default=True, help="The environment blob size")
91 | def create(size, redundant, bigendian, infile, outfile):
92 | """ Create new image from attached file """
93 | try:
94 | env = uboot.EnvBlob(size=size, redundant=redundant, bigendian=bigendian)
95 |
96 | with open(infile, 'r') as f:
97 | env.load(f.read())
98 |
99 | with open(outfile, 'wb') as f:
100 | f.write(env.export())
101 |
102 | except Exception as e:
103 | click.echo(str(e) if str(e) else "Unknown Error !")
104 | sys.exit(ERROR_CODE)
105 |
106 | click.secho(" Successfully created: %s" % outfile)
107 |
108 |
109 | # U-Boot mkenv: Extract image content
110 | @cli.command(short_help="Extract image content")
111 | @click.argument('file', nargs=1, type=click.Path(exists=True))
112 | @click.option('-b', '--bigendian', is_flag=True, help="The target is big endian (default is little endian)")
113 | @click.option('-o', '--offset', type=UINT, default=0, show_default=True, help="The offset of input file")
114 | @click.option('-s', '--size', type=UINT, default=8192, show_default=True, help="The environment blob size")
115 | def extract(offset, size, bigendian, file):
116 | """ Extract image content """
117 | try:
118 | fileName, _ = os.path.splitext(file)
119 |
120 | with open(file, "rb") as f:
121 | f.seek(offset)
122 | data = f.read(size)
123 |
124 | env = uboot.EnvBlob.parse(data, 0, bigendian)
125 | env.size = size
126 |
127 | with open(fileName + '.txt', 'w') as f:
128 | f.write(env.store())
129 |
130 | except Exception as e:
131 | click.echo(str(e) if str(e) else "Unknown Error !")
132 | sys.exit(ERROR_CODE)
133 |
134 | click.secho(" Successfully extracted: %s.txt" % fileName)
135 |
136 |
137 | def main():
138 | cli(obj={})
139 |
140 |
141 | if __name__ == '__main__':
142 | main()
143 |
--------------------------------------------------------------------------------
/uboot/env_image.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import collections
17 |
18 |
19 | class EnvImgOld(object):
20 |
21 | @property
22 | def start_string(self):
23 | return self._env_mark
24 |
25 | @property
26 | def max_size(self):
27 | return self._env_max_size
28 |
29 | @property
30 | def size(self):
31 | size = 2 * len(self._env)
32 | for key, value in self._env:
33 | size += len(key) + len(value)
34 | return size
35 |
36 | def __init__(self, start_string):
37 | self._env_mark = start_string
38 | self._env_offset = -1
39 | self._env_max_size = 0
40 | self._env = collections.OrderedDict()
41 | self._img = bytearray()
42 | self._file = ''
43 |
44 | def __str__(self):
45 | return self.info()
46 |
47 | def __repr__(self):
48 | return self.info()
49 |
50 | def _parse(self):
51 | """ Parse environment variables from image """
52 | self._env_offset = self._img.find(self._env_mark.encode())
53 | if self._env_offset == -1:
54 | raise Exception("Searched string \"%s\" doesnt exist in image" % self._env_mark)
55 |
56 | eof = 0
57 | index = self._env_offset
58 | while index < len(self._img):
59 | if self._img[index]:
60 | eof = 0
61 | else:
62 | eof += 1
63 |
64 | if eof == 2:
65 | self._env_max_size = index - self._env_offset - 1
66 | break
67 |
68 | index += 1
69 |
70 | data = self._img[self._env_offset : self._env_offset + self._env_max_size].decode()
71 | for s in data.split('\0'):
72 | key, value = s.split('=', 1)
73 | self._env[key] = value
74 |
75 | def _update(self):
76 | """ Update environment variables inside image """
77 | data = str()
78 | for key, val in self._env.items():
79 | data += "{0:s}={1:s}\0".format(key, val)
80 |
81 | if len(data) - 1 > self._env_max_size:
82 | raise Exception("EnVar blob size is out of range: %d instead %d bytes" % (len(data), self._env_max_size))
83 |
84 | if len(data) < self._env_max_size:
85 | data += "\0" * (self._env_max_size - len(data))
86 |
87 | index = self._env_offset
88 | for c in data:
89 | self._img[index] = ord(c)
90 | index += 1
91 |
92 | def info(self):
93 | """ Get info message
94 | :return string
95 | """
96 | msg = ""
97 | if self._file:
98 | msg += "Image Name: {}\n".format(self._file)
99 | msg += "Image Size: {} bytes\n".format(len(self._img))
100 | msg += "EnVar Size: {} bytes\n".format(self._env_max_size)
101 | msg += "EnVar Offset: {} \n".format(self._env_offset)
102 | msg += "EnVar Mark: {}\n".format(self._env_mark)
103 | msg += "EnVar Content:\n"
104 | for key, val in self._env.items():
105 | msg += "- {0:s} = {1:s}\n".format(key, val)
106 |
107 | return msg
108 |
109 | def get(self, name=None):
110 | """ Get the value of u-boot environment variable. If name is None, get list of all variables
111 | :param name: The variable name
112 | :return The variable value
113 | """
114 | if name:
115 | assert isinstance(name, str), "Env name is not a string: %r" % name
116 | if not name in self._env:
117 | raise Exception("EnVar name %s doesnt exist !" % name)
118 | return self._env[name]
119 | else:
120 | return self._env.keys()
121 |
122 | def set(self, name, value):
123 | """ Set the u-boot environment variable.
124 | :param name: The variable name
125 | :param value: The variable value
126 | """
127 | assert isinstance(name, str), "EnVar name is not a string: %r" % name
128 | if not isinstance(value, str):
129 | value = str(value)
130 | self._env[name] = value
131 |
132 | def clear(self):
133 | self._env.clear()
134 |
135 | def load(self, txt_data):
136 | """ Load the u-boot environment variables from readable string.
137 | :param txt_data: environment variables as string
138 | """
139 | for line in txt_data.split('\n'):
140 | line = line.rstrip('\0')
141 | if not line:
142 | continue
143 | if line.startswith('#'):
144 | continue
145 | env_name, env_value = line.split('=', 1)
146 | self._env[env_name.strip()] = env_value.strip()
147 |
148 | def store(self):
149 | """ Store the u-boot environment variables into readable string.
150 | :return environment variables as string
151 | """
152 | txt_data = ""
153 |
154 | for key, val in self._env.items():
155 | txt_data += "{0:s} = {1:s}\n".format(key, val)
156 |
157 | return txt_data
158 |
159 | def import_img(self, data):
160 | """ Import the u-boot image and parse environment variables from it.
161 | :param data: Image data in bytes
162 | """
163 | self._img = data if isinstance(data, bytearray) else bytearray(data)
164 | self._parse()
165 |
166 | def export_img(self):
167 | """ Export the u-boot image with updated environment variables.
168 | :return environment variables as string
169 | """
170 | self._update()
171 | return self._img
172 |
173 | def open_img(self, file):
174 | """ Open the u-boot image and parse environment variables from it.
175 | :param file: Path to image file
176 | """
177 | with open(file, 'rb') as f:
178 | self._img = bytearray(os.path.getsize(file))
179 | f.readinto(self._img)
180 |
181 | self._parse()
182 | self._file = file
183 |
184 | def save_img(self, file):
185 | """ Save the u-boot image with updated environment variables.
186 | :param file: Path to image file
187 | """
188 | self._update()
189 | with open(file, 'wb') as f:
190 | f.write(self._img)
--------------------------------------------------------------------------------
/uboot/env_blob.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import struct
16 | import binascii
17 | import collections
18 |
19 |
20 | class EnvBlob(object):
21 |
22 | @property
23 | def name(self):
24 | return self._name
25 |
26 | @name.setter
27 | def name(self, value):
28 | assert type(value) is str, "name is not a string: %r" % value
29 | self._name = value
30 |
31 | @property
32 | def size(self):
33 | return self._size
34 |
35 | @size.setter
36 | def size(self, value):
37 | self._size = value
38 |
39 | @property
40 | def redundant(self):
41 | return self._redundant
42 |
43 | @redundant.setter
44 | def redundant(self, value):
45 | self._redundant = value
46 |
47 | @property
48 | def bigendian(self):
49 | return self._bigendian
50 |
51 | @bigendian.setter
52 | def bigendian(self, value):
53 | self._bigendian = value
54 |
55 | def __init__(self, name=None, size=8192, redundant=False, bigendian=False, empty_value=0x00):
56 | self._name = name
57 | self._size = size
58 | self._redundant = redundant
59 | self._bigendian = bigendian
60 | self._empty_value = empty_value
61 | self._env = collections.OrderedDict()
62 |
63 | def __str__(self):
64 | return self.info()
65 |
66 | def __repr__(self):
67 | return self.info()
68 |
69 | def __len__(self):
70 | return self._size
71 |
72 | def info(self):
73 | msg = str()
74 | msg += "Name: {}\n".format(self._name)
75 | msg += "Redundant: {}\n".format(self._redundant)
76 | msg += "Endian: {}\n".format("Big" if self._bigendian else "Little")
77 | msg += "Size: {} Bytes\n".format(self._size)
78 | msg += "EmptyValue: 0x{:02X}\n".format(self._empty_value)
79 | msg += "Variables:\n"
80 | for key, val in self._env.items():
81 | msg += "- {0:s} = {1:s}\n".format(key, val)
82 | return msg
83 |
84 | def get(self, name=None):
85 | """ Get the value of u-boot environment variable. If name is None, get list of all variables
86 | :param name: The variable name
87 | :return The variable value
88 | """
89 | if name:
90 | assert isinstance(name, str), "name is not a string: %r" % name
91 | if name not in self._env:
92 | raise Exception("ERROR: Env %s doesnt exist !" % name)
93 | return self._env[name]
94 | else:
95 | return self._env.keys()
96 |
97 | def set(self, name, value):
98 | """ Set the u-boot environment variable.
99 | :param name: The variable name
100 | :param value: The variable value
101 | """
102 | assert isinstance(name, str), "Error "
103 | if not isinstance(value, str):
104 | value = str(value)
105 | self._env[name] = value
106 |
107 | def clear(self):
108 | self._env.clear()
109 |
110 | def load(self, txt_data):
111 | """ Load variables from text file
112 | :param txt_data:
113 | """
114 | for line in txt_data.split('\n'):
115 | line = line.rstrip('\0')
116 | if not line: continue
117 |
118 | if line.startswith('#'):
119 | pass # TODO: Parse init values
120 | else:
121 | name, value = line.split('=', 1)
122 | self._env[name.strip()] = value.strip()
123 |
124 | def store(self, txt_data=None):
125 | """ Store variables into text file
126 | :param txt_data:
127 | :return: txt_data
128 | """
129 | if txt_data is None:
130 | txt_data = ""
131 |
132 | txt_data += "# Name: {0:s}\n".format(self.name if self.name else "")
133 | txt_data += "# Size: {0:d}\n".format(self.size)
134 | txt_data += "# Redundant: {0:s}\n".format("Yes" if self.redundant else "No")
135 | txt_data += "\n"
136 |
137 | for key, val in self._env.items():
138 | txt_data += "{0:s}={1:s}\n".format(key, val)
139 |
140 | return txt_data
141 |
142 | @classmethod
143 | def parse(cls, data, offset=0, bigendian=False):
144 | """ Parse the u-boot environment variables from bytearray.
145 | :param data: The data in bytes array
146 | :param offset: The offset of input data
147 | :param bigendian: The endian type
148 | """
149 | env = cls(bigendian=bigendian)
150 |
151 | fmt = ">IB" if env.bigendian else " env_size:
194 | raise Exception("ERROR: ENV size out of range, extend required size !")
195 | env_blob = data.encode('utf-8')
196 | env_blob += self._empty_value.to_bytes(1,'little') * (env_size - len(data))
197 | crc = binascii.crc32(env_blob) & 0xffffffff
198 |
199 | fmt = ">I" if self._bigendian else " Shell Scripts).
52 |
53 | # The following are exposed to uImage header.
54 | # Do not change values for backward compatibility.
55 |
56 |
57 | class EnumImageType(Enum):
58 | """ Supported UBoot Image Types """
59 |
60 | STANDALONE = (1, "standalone", "Standalone Program")
61 | KERNEL = (2, "kernel", "Kernel Image")
62 | RAMDISK = (3, "ramdisk", "RAMDisk Image")
63 | MULTI = (4, "multi", "Multi-File Image")
64 | FIRMWARE = (5, "firmware", "Firmware Image")
65 | SCRIPT = (6, "script", "U-Boot Script Image")
66 | FILESYSTEM = (7, "filesystem", "Filesystem Image")
67 | FLATDT = (8, "flat_dt", "Flat Device Tree Image")
68 | KWB = (9, "kwbimage", "Kirkwood Boot Image")
69 | IMX = (10, "imximage", "Freescale i.MX Boot Image")
70 | UBL = (11, "ublimage", "Davinci UBL image")
71 | OMAP = (12, "omapimage", "TI OMAP SPL With GP CH")
72 | AIS = (13, "aisimage", "Davinci AIS image")
73 |
74 | KNOLOAD = (14, "kernel_noload", "Kernel Image (no loading done)")
75 | PBL = (15, "pblimage", "Freescale PBL Boot Image")
76 | MXS = (16, "mxsimage", "Freescale MXS Boot Image")
77 | GP = (17, "gpimage", "TI Keystone SPL Image")
78 | ATMEL = (18, "atmelimage", "ATMEL ROM-Boot Image")
79 | SOCFPGA = (19, "socfpgaimage", "Altera SOCFPGA preloader")
80 | X86SETUP = (20, "x86_setup", "x86 setup.bin")
81 | LPC32XX = (21, "lpc32xximage", "LPC32XX Boot Image")
82 | LOADABLE = (22, "loadable", "A list of typeless images")
83 | RKBOOT = (23, "rkimage", "Rockchip Boot Image")
84 | RKSD = (24, "rksd", "Rockchip SD Boot Image")
85 | RKSPI = (25, "rkspi", "Rockchip SPI Boot Image")
86 | ZYNQ = (26, "zynqimage", "Xilinx Zynq Boot Image")
87 | ZYNQMP = (27, "zynqmpimage", "Xilinx ZynqMP Boot Image")
88 | FPGA = (28, "fpga", "FPGA Image")
89 | VYBRID = (29, "vybridimage", "Vybrid Boot Image")
90 | TEE = (30, "tee", "Trusted Execution Environment Image")
91 | FW_IVT = (31, "firmware_ivt", "Firmware with HABv4 IVT")
92 | PMMC = (32, "pmmc", "TI Power Management Micro-Controller Firmware")
93 |
94 |
95 | # ----------------------------------------------------------------------------------------------------------------------
96 | # Operating System Codes
97 | # ----------------------------------------------------------------------------------------------------------------------
98 |
99 | # The following are exposed to uImage header.
100 | # Do not change values for backward compatibility.
101 |
102 | class EnumOsType(Enum):
103 | """ Supported Operating Systems """
104 |
105 | OPENBSD = (1, "openbsd", "OpenBSD")
106 | NETBSD = (2, "netbsd", "NetBSD")
107 | FREEBSD = (3, "freebsd", "FreeBSD")
108 | BSD4 = (4, "4-4bsd", "4-4BSD")
109 | LINUX = (5, "linux", "Linux")
110 | SVR4 = (6, "svr4", "SVR4")
111 | ESIX = (7, "esix", "Esix")
112 | SOLARIS = (8, "solaris", "Solaris")
113 | IRIX = (9, "irix", "Irix")
114 | SCO = (10, "sco", "SCO")
115 | DELL = (11, "dell", "Dell")
116 | NCR = (12, "ncr", "NCR")
117 | LYNXOS = (13, "lynxos", "LynxOS")
118 | VXWORKS = (14, "vxworks", "VxWorks")
119 | PSOS = (15, "psos", "pSOS")
120 | QNX = (16, "qnx", "QNX")
121 | UBOOT = (17, "u-boot", "U-Boot Firmware")
122 | RTEMS = (18, "rtems", "RTEMS")
123 | ARTOS = (19, "artos", "ARTOS")
124 | UNITY = (20, "unity", "Unity OS")
125 | INTEGRITY = (21, "integrity", "INTEGRITY")
126 | OSE = (22, "ose", "Enea OSE")
127 | PLAN9 = (23, "plan9", "Plan 9")
128 | OPENRTOS = (24, "openrtos", "OpenRTOS")
129 |
130 |
131 | # ----------------------------------------------------------------------------------------------------------------------
132 | # CPU Architecture Codes (supported by Linux)
133 | # ----------------------------------------------------------------------------------------------------------------------
134 |
135 | # The following are exposed to uImage header.
136 | # Do not change values for backward compatibility.
137 |
138 | class EnumArchType(Enum):
139 | """ Supported CPU Architectures """
140 |
141 | ALPHA = (1, "alpha", "Alpha")
142 | ARM = (2, "arm", "ARM")
143 | I386 = (3, "x86", "Intel x86")
144 | IA64 = (4, "ia64", "IA64")
145 | MIPS = (5, "mips", "MIPS")
146 | MIPS64 = (6, "mips64", "MIPS 64-Bit")
147 | PPC = (7, "powerpc", "PowerPC")
148 | S390 = (8, "s390", "IBM S390")
149 | SH = (9, "sh", "SuperH")
150 | SPARC = (10, "sparc", "SPARC")
151 | SPARC64 = (11, "sparc64", "SPARC 64 Bit")
152 | M68K = (12, "m68k", "M68K")
153 | NIOS = (13, "nios", "Nios 32")
154 | MICROBLAZE = (14, "microblaze", "MicroBlaze")
155 | NIOS2 = (15, "nios2", "NIOS II")
156 | BLACKFIN = (16, "blackfin", "Blackfin")
157 | AVR32 = (17, "avr32", "AVR32")
158 | ST200 = (18, "ST200", "STMicroelectronics ST200")
159 | SANDBOX = (19, "sandbox", "Sandbox architecture (test only)")
160 | NDS32 = (20, "nds32", "ANDES Technology - NDS32")
161 | OPENRISC = (21, "or1k", "OpenRISC 1000")
162 | ARM64 = (22, "arm64", "AArch64")
163 | ARC = (23, "arc", "Synopsis DesignWare ARC")
164 | X86_64 = (24, "x86_64", "AMD x86_64, Intel and Via")
165 | XTENSA = (25, "xtensa", "Xtensa")
166 |
167 |
168 | # ----------------------------------------------------------------------------------------------------------------------
169 | # Compression Types
170 | # ----------------------------------------------------------------------------------------------------------------------
171 |
172 | # The following are exposed to uImage header.
173 | # Do not change values for backward compatibility.
174 |
175 | class EnumCompressionType(Enum):
176 | """ Supported Data Compression """
177 |
178 | NONE = (0, 'none', 'Uncompressed')
179 | GZIP = (1, 'gzip', 'Compressed with GZIP')
180 | BZIP2 = (2, 'bzip2', 'Compressed with BZIP2')
181 | LZMA = (3, 'lzma', 'Compressed with LZMA')
182 | LZO = (4, 'lzo', 'Compressed with LZO')
183 | LZ4 = (5, 'lz4', 'Compressed with LZ4')
184 |
--------------------------------------------------------------------------------
/uboot/cli_mkimg.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import sys
17 | import click
18 | import uboot
19 |
20 | # Application error code
21 | ERROR_CODE = 1
22 |
23 | # The version of u-boot tools
24 | VERSION = uboot.__version__
25 |
26 | # Short description of U-Boot mkimg tool
27 | DESCRIP = (
28 | "The U-Boot Image Tool"
29 | )
30 |
31 |
32 | # User defined class
33 | class UInt(click.ParamType):
34 | """ Custom argument type for UINT """
35 | name = 'unsigned int'
36 |
37 | def __repr__(self):
38 | return 'UINT'
39 |
40 | def convert(self, value, param, ctx):
41 | try:
42 | if isinstance(value, (int,)):
43 | return value
44 | else:
45 | return int(value, 0)
46 | except:
47 | self.fail('{} is not a valid value'.format(value), param, ctx)
48 |
49 |
50 | # Create instances of custom argument types
51 | UINT = UInt()
52 |
53 | # --
54 | ARCT = [item[0] for item in uboot.EnumArchType]
55 | OST = [item[0] for item in uboot.EnumOsType]
56 | IMGT = [item[0] for item in uboot.EnumImageType]
57 | COMT = [item[0] for item in uboot.EnumCompressionType]
58 |
59 |
60 | # U-Boot mkimg: Base options
61 | @click.group(context_settings=dict(help_option_names=['-?', '--help']), help=DESCRIP)
62 | @click.version_option(VERSION, '-v', '--version')
63 | def cli():
64 | click.echo()
65 |
66 |
67 | @cli.command(short_help="Show old image content")
68 | @click.argument('file', nargs=1, type=click.Path(exists=True))
69 | def info(file):
70 | """ List old image content in readable format """
71 | try:
72 | with open(file, 'rb') as f:
73 | img = uboot.parse_img(f.read())
74 | click.echo(img.info())
75 |
76 | except Exception as e:
77 | click.echo(str(e) if str(e) else "Unknown Error !")
78 | sys.exit(ERROR_CODE)
79 |
80 |
81 | @cli.command(short_help="Show new image content")
82 | @click.argument('file', nargs=1, type=click.Path(exists=True))
83 | def infoitb(file):
84 | """ List new image content in readable format """
85 | try:
86 | with open(file, 'rb') as f:
87 | img = uboot.parse_itb(f.read())
88 | click.echo(img.info())
89 |
90 | except Exception as e:
91 | click.echo(str(e) if str(e) else "Unknown Error !")
92 | sys.exit(ERROR_CODE)
93 |
94 |
95 | @cli.command(short_help="Create old U-Boot image from attached files")
96 | @click.option('-a', '--arch', type=click.Choice(ARCT), default='arm', show_default=True, help='Architecture')
97 | @click.option('-o', '--ostype', type=click.Choice(OST), default='linux', show_default=True, help='Operating system')
98 | @click.option('-i', '--imgtype', type=click.Choice(IMGT), default='firmware', show_default=True, help='Image type')
99 | @click.option('-c', '--compress', type=click.Choice(COMT), default='none', show_default=True, help='Image compression')
100 | @click.option('-l', '--laddr', type=UINT, default=0, show_default=True, help="Load address")
101 | @click.option('-e', '--epaddr', type=UINT, default=0, show_default=True, help="Entry point address")
102 | @click.option('-n', '--name', type=click.STRING, default="", help="Image name (max: 32 chars)")
103 | @click.argument('outfile', nargs=1, type=click.Path(readable=False))
104 | @click.argument('infiles', nargs=-1, type=click.Path(exists=True))
105 | def create(arch, ostype, imgtype, compress, laddr, epaddr, name, outfile, infiles):
106 | """ Create old U-Boot image from attached files """
107 | try:
108 | img_type = uboot.EnumImageType[imgtype]
109 |
110 | if img_type == uboot.EnumImageType.MULTI:
111 | img = uboot.MultiImage
112 | for file in infiles:
113 | with open(file, 'rb') as f:
114 | simg = uboot.parse_img(f.read())
115 | img.append(simg)
116 |
117 | elif img_type == uboot.EnumImageType.MULTI:
118 | img = uboot.ScriptImage()
119 | with open(infiles[0], 'r') as f:
120 | img.load(f.read())
121 |
122 | else:
123 | img = uboot.StdImage(image=img_type)
124 | with open(infiles[0], 'rb') as f:
125 | img.data = bytearray(f.read())
126 |
127 | img.header.arch_type = uboot.EnumArchType[arch]
128 | img.header.os_type = uboot.EnumOsType[ostype]
129 | img.header.compression = uboot.EnumCompressionType[compress]
130 | img.header.load_address = laddr
131 | img.header.entry_point = epaddr
132 | img.header.name = name
133 |
134 | click.echo(img.info())
135 | with open(outfile, 'wb') as f:
136 | f.write(img.export())
137 |
138 | except Exception as e:
139 | click.echo(str(e) if str(e) else "Unknown Error !")
140 | sys.exit(ERROR_CODE)
141 |
142 | click.secho("\n Created Image: %s" % outfile)
143 |
144 |
145 | @cli.command(short_help="Create new U-Boot image from *.its file")
146 | @click.option('-o', '--outfile', type=click.Path(readable=False), default=None, help="Output file")
147 | @click.option('-p', '--padding', type=UINT, default=0, help="Add padding to the blob of long")
148 | @click.option('-a', '--align', type=UINT, default=None, help="Make the blob align to the ")
149 | @click.option('-s', '--size', type=UINT, default=None, help="Make the blob at least long")
150 | @click.argument('itsfile', nargs=1, type=click.Path(exists=True))
151 | def createitb(outfile, padding, align, size, itsfile):
152 | """ Create new U-Boot image from *.its file """
153 |
154 | try:
155 | if outfile is None:
156 | outfile = os.path.splitext(itsfile)[0] + ".itb"
157 |
158 | with open(itsfile, 'r') as f:
159 | img = uboot.parse_its(f.read(), os.path.dirname(itsfile))
160 |
161 | with open(outfile, 'wb') as f:
162 | f.write(img.to_itb(padding, align, size))
163 |
164 | except Exception as e:
165 | click.echo(str(e) if str(e) else "Unknown Error !")
166 | sys.exit(ERROR_CODE)
167 |
168 | click.secho("\n Created Image: %s" % outfile)
169 |
170 |
171 | @cli.command(short_help="Extract content from old U-Boot image")
172 | @click.argument('file', nargs=1, type=click.Path(exists=True))
173 | def extract(file):
174 | """ Extract content from old U-Boot image """
175 |
176 | def get_file_ext(img):
177 | ext = ('bin', 'gz', 'bz2', 'lzma', 'lzo', 'lz4')
178 | return ext[img.compression]
179 |
180 | try:
181 | with open(file, 'rb') as f:
182 | raw_data = f.read()
183 |
184 | img = uboot.parse_img(raw_data)
185 |
186 | file_path, file_name = os.path.split(file)
187 | dest_dir = os.path.normpath(os.path.join(file_path, file_name + ".ex"))
188 | os.makedirs(dest_dir, exist_ok=True)
189 |
190 | if img.header.image_type == uboot.EnumImageType.MULTI:
191 | n = 0
192 | for simg in img:
193 | with open(os.path.join(dest_dir, 'image_{0:02d}.bin'.format(n)), 'wb') as f:
194 | f.write(simg.export())
195 | n += 1
196 | elif img.header.image_type == uboot.EnumImageType.SCRIPT:
197 | with open(os.path.join(dest_dir, 'script.txt'), 'w') as f:
198 | f.write(img.store())
199 |
200 | else:
201 | with open(os.path.join(dest_dir, 'image.' + get_file_ext(img)), 'wb') as f:
202 | f.write(img.data)
203 |
204 | with open(os.path.join(dest_dir, 'info.txt'), 'w') as f:
205 | f.write(img.info())
206 |
207 | except Exception as e:
208 | click.echo(str(e) if str(e) else "Unknown Error !")
209 | sys.exit(ERROR_CODE)
210 |
211 | click.secho("\n Image extracted into dir: %s" % dest_dir)
212 |
213 |
214 | @cli.command(short_help="Extract content from new U-Boot image")
215 | @click.argument('file', nargs=1, type=click.Path(exists=True))
216 | def extractitb(file):
217 | """ Extract content from new U-Boot image """
218 |
219 | try:
220 | with open(file, 'rb') as f:
221 | img = uboot.parse_itb(f.read())
222 |
223 | file_path, file_name = os.path.split(file)
224 | dest_dir = os.path.normpath(os.path.join(file_path, file_name + ".ex"))
225 | os.makedirs(dest_dir, exist_ok=True)
226 |
227 | its, images = img.to_its()
228 |
229 | with open(os.path.join(dest_dir, file_name.split('.')[0] + '.its'), 'w') as f:
230 | f.write(its)
231 |
232 | for name, data in images.items():
233 | with open(os.path.join(dest_dir, name), 'wb') as f:
234 | f.write(data)
235 |
236 | except Exception as e:
237 | click.echo(str(e) if str(e) else "Unknown Error !")
238 | sys.exit(ERROR_CODE)
239 |
240 | click.secho("\n Image extracted into dir: %s" % dest_dir)
241 |
242 |
243 | def main():
244 | cli(obj={})
245 |
246 |
247 | if __name__ == '__main__':
248 | main()
249 |
--------------------------------------------------------------------------------
/uboot/fdt_image.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | import fdt
17 | import time
18 | import struct
19 |
20 | from .common import EnumOsType, EnumArchType, EnumImageType, EnumCompressionType
21 |
22 |
23 | # ----------------------------------------------------------------------------------------------------------------------
24 | # Helper methods
25 | # ----------------------------------------------------------------------------------------------------------------------
26 | def get_value(obj, name, default=None):
27 | prop = obj.get_property(name)
28 | return default if prop is None else prop[0]
29 |
30 |
31 | def get_data(obj):
32 | prop = obj.get_property("data")
33 | if isinstance(prop, fdt.PropBytes):
34 | return prop.data
35 | if isinstance(prop, fdt.PropWords):
36 | data = bytearray()
37 | for val in prop.data:
38 | data += struct.pack(">I", val)
39 | return data
40 |
41 | raise Exception("Image data error")
42 |
43 |
44 | # ----------------------------------------------------------------------------------------------------------------------
45 | # FDT Image Class
46 | # ----------------------------------------------------------------------------------------------------------------------
47 | class FdtImage(object):
48 |
49 | def __init__(self):
50 | self.description = ""
51 | self.time_stamp = int(time.time())
52 | self.def_config = None
53 | self.configs = []
54 | self.img_info = []
55 | self.img_data = {}
56 |
57 | def __str__(self):
58 | return self.info()
59 |
60 | def __repr__(self):
61 | return self.info()
62 |
63 | def info(self):
64 | msg = "FIT description: {}\n".format(self.description)
65 | msg += "Created: {}\n".format(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime(self.time_stamp)))
66 | msg += "Default config: {}\n".format(self.def_config)
67 | for n, img in enumerate(self.img_info):
68 | msg += "\n"
69 | msg += " IMG[{}] {}\n".format(n, img.name)
70 | msg += " size: {0:.02f} kB\n".format(len(self.img_data[img.name]) / 1024)
71 | for p in img.props:
72 | if isinstance(p, fdt.PropWords):
73 | msg += " {}: 0x{:X}\n".format(p.name, p[0])
74 | else:
75 | msg += " {}: {}\n".format(p.name, p[0])
76 | for n, cfg in enumerate(self.configs):
77 | msg += "\n"
78 | msg += " CFG[{}] {}\n".format(n, cfg.name)
79 | for p in cfg.props:
80 | msg += " {}: {}\n".format(p.name, p[0])
81 | return msg
82 |
83 | def add_img(self, nfo, data):
84 | """
85 | :param nfo:
86 | :param data:
87 | :return:
88 | """
89 | assert isinstance(nfo, fdt.Node), "nfo type must be a fdt.Node"
90 | assert isinstance(data, (bytes, bytearray)), "data type must be a bytes or bytearray"
91 |
92 | if not nfo.exist_property("type"):
93 | raise Exception("Image type must be defined")
94 |
95 | for p in nfo.props:
96 | name, value = p.name, p[0]
97 | if name == "type" and value != "flat_dt" and value not in EnumImageType:
98 | raise Exception("Unknown IMAGE type")
99 | elif name == "os" and value not in EnumOsType:
100 | raise Exception("Unknown OS type")
101 | elif name == "arch" and value not in EnumArchType:
102 | raise Exception("Unknown ARCH type")
103 | elif name == "compression" and value not in EnumCompressionType:
104 | raise Exception("Unknown Compression type")
105 |
106 | self.img_info.append(nfo)
107 | self.img_data[nfo.name] = data
108 |
109 | def add_cfg(self, item, validate=False):
110 | """
111 | :param item:
112 | :param validate:
113 | :return:
114 | """
115 | assert isinstance(item, fdt.Node), "validate_cfg: item type must be a fdt.Node"
116 | for cfg in item.props:
117 | if cfg.name == "description":
118 | continue
119 | if validate and cfg[0] not in self.img_data:
120 | raise Exception("add_cfg: Config Validation Error")
121 | self.configs.append(item)
122 |
123 | def to_its(self, rpath=None, tabsize=4):
124 | """ Export to ITS format
125 |
126 | :param rpath:
127 | :param tabsize:
128 | :return:
129 | """
130 | data = {}
131 | images = fdt.Node("images")
132 | for img in self.img_info:
133 | img_name = img.name.replace('@', '_') + '.bin'
134 | img_clone = img.copy()
135 | img_clone.append(fdt.PropIncBin("data", None, img_name, rpath))
136 | images.append(img_clone)
137 | data[img_name] = self.img_data[img.name]
138 |
139 |
140 | # Add images and configs
141 | root_node = fdt.Node('/')
142 | root_node.append(fdt.PropStrings("description", self.description))
143 | root_node.append(images)
144 | if len(self.configs) != 0:
145 | configs = fdt.Node("configurations", nodes=self.configs)
146 | if self.def_config is not None and self.def_config in [cnf.name for cnf in self.configs]:
147 | configs.append(fdt.PropStrings("default", self.def_config))
148 | root_node.append(configs)
149 |
150 | # Crete ITS
151 | its = "/dts-v1/;\n"
152 | its += '\n'
153 | its += root_node.to_dts(tabsize)
154 | return its, data
155 |
156 | def to_itb(self, padding=0, align=None, size=None):
157 | """ Export to ITB format
158 |
159 | :param padding:
160 | :param align:
161 | :param size:
162 | :return:
163 | """
164 | img_blob = bytes()
165 | img_offset = padding
166 |
167 | fdt_obj = fdt.FDT()
168 | fdt_obj.add_item(fdt.PropWords("timestamp", int(time.time()) if self.time_stamp is None else self.time_stamp))
169 | fdt_obj.add_item(fdt.PropStrings("description", self.description))
170 |
171 | # Add images
172 | node = fdt.Node("images")
173 | for image in self.img_info:
174 | if image.name not in self.img_data:
175 | raise Exception("export: data is None")
176 | cimg = image.copy()
177 | data = self.img_data[image.name]
178 | if padding:
179 | img_blob += data
180 | img_offset += len(data)
181 | cimg.append(fdt.PropWords("data-size", len(data)))
182 | cimg.append(fdt.PropWords("data-position", img_offset))
183 | else:
184 | cimg.append(fdt.PropBytes("data", data=data))
185 | node.append(cimg)
186 | fdt_obj.add_item(node)
187 |
188 | # Add configs
189 | if len(self.configs) != 0:
190 | node = fdt.Node("configurations")
191 | if self.def_config is not None and self.def_config in [cnf.name for cnf in self.configs]:
192 | node.append(fdt.PropStrings("default", self.def_config))
193 | for cfg in self.configs:
194 | node.append(cfg)
195 | fdt_obj.add_item(node)
196 |
197 | # Generate FDT blob
198 | itb = fdt_obj.to_dtb(17)
199 |
200 | # ...
201 | if padding:
202 | itb_align = padding - len(itb)
203 | if itb_align < 0:
204 | raise Exception()
205 | if itb_align > 0:
206 | itb += bytes([0] * itb_align)
207 | itb += img_blob
208 |
209 | return itb
210 |
211 |
212 | def parse_its(text, root_dir=''):
213 | """ Parse ITS file
214 |
215 | :param text:
216 | :param root_dir:
217 | :return:
218 | """
219 | its_obj = fdt.parse_dts(text, root_dir)
220 | # ...
221 | fim_obj = FdtImage()
222 | prop = its_obj.get_property("description")
223 | if prop is not None:
224 | fim_obj.description = prop[0]
225 |
226 | # Parse images
227 | node = its_obj.get_node("images")
228 | if node is None:
229 | raise Exception("parse_its: images not defined")
230 | for img in node.nodes:
231 | img_data = get_data(img)
232 | img.remove_property("data")
233 | fim_obj.add_img(img, img_data)
234 |
235 | # Parse configs if exists
236 | try:
237 | node = its_obj.get_node("configurations")
238 | for cfg in node.nodes:
239 | fim_obj.add_cfg(cfg, True)
240 | fim_obj.def_config = get_value(node, "default")
241 | except ValueError:
242 | pass
243 |
244 | return fim_obj
245 |
246 |
247 | def parse_itb(data, offset=0):
248 | """ Parse ITB data-blob
249 |
250 | :param data:
251 | :param offset:
252 | :return:
253 | """
254 | fdt_obj = fdt.parse_dtb(data, offset)
255 | # ...
256 | fim_obj = FdtImage()
257 | fim_obj.time_stamp = get_value(fdt_obj, "timestamp")
258 | fim_obj.description = get_value(fdt_obj, "description", "")
259 |
260 | # Parse images
261 | node = fdt_obj.get_node("images")
262 | if node is None or not node.nodes:
263 | raise Exception("parse_itb: images not defined")
264 | for img in node.nodes:
265 | if img.exist_property("data"):
266 | img_data = get_data(img)
267 | img.remove_property("data")
268 | elif img.exist_property("data-size") and img.exist_property("data-position"):
269 | data_size = get_value(img, "data-size")
270 | data_offset = get_value(img, "data-position")
271 | img_data = data[offset + data_offset: offset + data_offset + data_size]
272 | img.remove_property("data-size")
273 | img.remove_property("data-position")
274 | else:
275 | raise Exception()
276 | fim_obj.add_img(img, img_data)
277 |
278 | # Parse configs if exists
279 | try:
280 | node = fdt_obj.get_node("configurations")
281 | for cfg in node.nodes:
282 | fim_obj.add_cfg(cfg, True)
283 | fim_obj.def_config = get_value(node, "default")
284 | except ValueError:
285 | pass
286 |
287 | return fim_obj
288 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/uboot/old_image.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Martin Olejar
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import time
16 | import binascii
17 | from struct import pack, unpack_from, calcsize
18 |
19 | from .common import EnumArchType, EnumOsType, EnumImageType, EnumCompressionType
20 |
21 |
22 | # ----------------------------------------------------------------------------------------------------------------------
23 | # Helper methods
24 | # ----------------------------------------------------------------------------------------------------------------------
25 | def CRC32(data):
26 | """ Help function for 32bit CRC calculation
27 | :param data: Tha data blob as byte array
28 | :return: CRC Value
29 | """
30 | return binascii.crc32(data) & 0xFFFFFFFF
31 | # ----------------------------------------------------------------------------------------------------------------------
32 |
33 |
34 | # ----------------------------------------------------------------------------------------------------------------------
35 | # UBoot Image Header Class
36 | # ----------------------------------------------------------------------------------------------------------------------
37 | class Header(object):
38 | MAGIC_NUMBER = 0x27051956
39 | FORMAT = '!7L4B32s' # (Big-endian, 7 ULONGS, 4 UCHARs, 32-byte string)
40 | SIZE = calcsize(FORMAT) # Should be 64-bytes
41 |
42 | @property
43 | def header_crc(self):
44 | return CRC32(pack(self.FORMAT,
45 | self.magic_number,
46 | 0,
47 | self.time_stamp,
48 | self.data_size,
49 | self.load_address,
50 | self.entry_address,
51 | self.data_crc,
52 | self.os_type,
53 | self.arch_type,
54 | self.image_type,
55 | self.compression,
56 | self.name.encode('utf-8')))
57 |
58 | @property
59 | def os_type(self):
60 | return self._os_type
61 |
62 | @os_type.setter
63 | def os_type(self, value):
64 | assert value in EnumOsType, "HEADER: Unknown Value of OS Type: %d" % value
65 | self._os_type = value
66 |
67 | @property
68 | def arch_type(self):
69 | return self._arch_type
70 |
71 | @arch_type.setter
72 | def arch_type(self, value):
73 | assert value in EnumArchType, "HEADER: Unknown Value of Arch Type: %d" % value
74 | self._arch_type = value
75 |
76 | @property
77 | def image_type(self):
78 | return self._image_type
79 |
80 | @image_type.setter
81 | def image_type(self, value):
82 | assert value in EnumImageType, "HEADER: Unknown Value of Image Type: %d" % value
83 | self._image_type = value
84 |
85 | @property
86 | def compression(self):
87 | return self._compression
88 |
89 | @compression.setter
90 | def compression(self, value):
91 | assert value in EnumCompressionType, "HEADER: Unknown Value of Compression Type: %d" % value
92 | self._compression = value
93 |
94 | @property
95 | def name(self):
96 | return self._name
97 |
98 | @name.setter
99 | def name(self, value):
100 | assert isinstance(value, str), "HEADER: Name must be a string"
101 | assert len(value) <= 32, "HEADER: Name to long: %d char instead 32" % len(value)
102 | self._name = value
103 |
104 | @property
105 | def size(self):
106 | return self.SIZE
107 |
108 | def __init__(self, **kwargs):
109 | """ U-Boot Image Header Constructor
110 | :param laddr: Load address
111 | :param eaddr: Entry point address
112 | :param arch: Architecture (ARCHType Enum)
113 | :param os: Operating system (OSType Enum)
114 | :param image: Image type (IMGType Enum)
115 | :param compress: Image compression (COMPRESSType Enum)
116 | :param name: Image name (max: 32 chars)
117 | """
118 | self.magic_number = self.MAGIC_NUMBER # U-Boot Default Value is 0x27051956
119 | self.time_stamp = int(time.time())
120 | self.data_size = 0
121 | self.data_crc = 0
122 | self.load_address = 0
123 | self.entry_address = 0
124 | self._os_type = EnumOsType.LINUX
125 | self._arch_type = EnumArchType.ARM
126 | self._image_type = EnumImageType.STANDALONE
127 | self._compression = EnumCompressionType.NONE
128 | self._name = ''
129 |
130 | if 'laddr' in kwargs:
131 | try:
132 | self.load_address = int(kwargs['laddr'], 0) if isinstance(kwargs['laddr'], str) else int(kwargs['laddr'])
133 | except ValueError:
134 | raise Exception("The value of \"laddr\" is not correct !")
135 | if 'eaddr' in kwargs:
136 | try:
137 | self.entry_address = int(kwargs['eaddr'], 0) if isinstance(kwargs['eaddr'], str) else int(kwargs['eaddr'])
138 | except ValueError:
139 | raise Exception("The value of \"eaddr\" is not correct !")
140 | if 'os' in kwargs and kwargs['os'] is not None:
141 | val = kwargs['os']
142 | self.os_type = val if isinstance(val, int) else EnumOsType[val]
143 | if 'arch' in kwargs and kwargs['arch'] is not None:
144 | val = kwargs['arch']
145 | self.arch_type = val if isinstance(val, int) else EnumArchType[val]
146 | if 'image' in kwargs and kwargs['image'] is not None:
147 | val = kwargs['image']
148 | self.image_type = val if isinstance(val, int) else EnumImageType[val]
149 | if 'compress' in kwargs and kwargs['compress'] is not None:
150 | val = kwargs['compress']
151 | self.compression = val if isinstance(val, int) else EnumCompressionType[val]
152 | if 'name' in kwargs and kwargs['name'] is not None:
153 | self.name = kwargs['name']
154 |
155 | def __len__(self):
156 | return self.size
157 |
158 | def __str__(self):
159 | return self.info()
160 |
161 | def __repr__(self):
162 | return self.info()
163 |
164 | def __ne__(self, obj):
165 | return not self.__eq__(obj)
166 |
167 | def __eq__(self, obj):
168 | if not isinstance(obj, Header):
169 | return False
170 | if self.header_crc != obj.header_crc:
171 | return False
172 | return True
173 |
174 | def info(self):
175 | msg = "Image Name: {0:s}\n".format(self.name)
176 | msg += "Created: {0:s}\n".format(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime(self.time_stamp)))
177 | msg += "Image Type: {0:s} {1:s} {2:s} ({3:s})\n".format(EnumArchType[self.arch_type],
178 | EnumOsType[self.os_type],
179 | EnumImageType[self.image_type],
180 | EnumCompressionType[self.compression])
181 | msg += "Data Size: {0:.02f} kB\n".format(self.data_size / 1024)
182 | msg += "Load Address: 0x{0:08X}\n".format(self.load_address)
183 | msg += "Entry Address: 0x{0:08X}\n".format(self.entry_address)
184 | return msg
185 |
186 | def export(self):
187 | return pack(self.FORMAT,
188 | self.magic_number,
189 | self.header_crc,
190 | self.time_stamp,
191 | self.data_size,
192 | self.load_address,
193 | self.entry_address,
194 | self.data_crc,
195 | self.os_type,
196 | self.arch_type,
197 | self.image_type,
198 | self.compression,
199 | self.name.encode('utf-8'))
200 |
201 | @classmethod
202 | def parse(cls, data, offset=0, ignore_crc=False):
203 | if len(data) < cls.SIZE:
204 | raise Exception("Header: Too small size of input data !")
205 |
206 | val = unpack_from(cls.FORMAT, data, offset)
207 | header = cls()
208 |
209 | header.magic_number = val[0]
210 | header_crc = val[1]
211 | header.time_stamp = val[2]
212 | header.data_size = val[3]
213 | header.load_address = val[4]
214 | header.entry_address = val[5]
215 | header.data_crc = val[6]
216 | header.os_type = val[7]
217 | header.arch_type = val[8]
218 | header.image_type = val[9]
219 | header.compression = val[10]
220 | header.name = val[11].decode('utf-8', errors='ignore').strip('\0')
221 |
222 | if header.magic_number != header.MAGIC_NUMBER:
223 | raise Exception("Header: Magic number not valid !")
224 |
225 | if not ignore_crc:
226 | if header_crc != header.header_crc:
227 | raise Exception(f"Header: Uncorrect CRC of input data ! {hex(header_crc)} != {hex(header.header_crc)}")
228 |
229 | return header
230 | # ----------------------------------------------------------------------------------------------------------------------
231 |
232 |
233 | # ----------------------------------------------------------------------------------------------------------------------
234 | # UBoot Image Classes
235 | # ----------------------------------------------------------------------------------------------------------------------
236 | class BaseImage(object):
237 | def __init__(self, **kwargs):
238 | self.header = Header(**kwargs)
239 |
240 | def __str__(self):
241 | return self.info()
242 |
243 | def __repr__(self):
244 | return self.info()
245 |
246 | def __ne__(self, obj):
247 | return not self.__eq__(obj)
248 |
249 | def info(self):
250 | self.export()
251 | return self.header.info()
252 |
253 | def parse(self, data, offset=0):
254 | raise NotImplementedError()
255 |
256 | def export(self):
257 | raise NotImplementedError()
258 |
259 |
260 | class StdImage(BaseImage):
261 | def __init__(self, data=None, **kwargs):
262 | """ Image Constructor
263 | :param data: Image content as byte array
264 | :param laddr: Load address
265 | :param eaddr: Entry point address
266 | :param arch: Architecture (ARCHType Enum)
267 | :param os: Operating system (OSType Enum)
268 | :param image: Image type (IMGType Enum)
269 | :param compress: Image compression (COMPRESSType Enum)
270 | :param name: Image name (max: 32 chars)
271 | """
272 | super().__init__(**kwargs)
273 | self.data = data if data else bytearray()
274 |
275 | def __eq__(self, obj):
276 | if not isinstance(obj, StdImage):
277 | return False
278 | if self.__len__() != len(obj):
279 | return False
280 | if self.data != obj.data:
281 | return False
282 | return True
283 |
284 | def __len__(self):
285 | return len(self.data)
286 |
287 | def __iter__(self):
288 | return iter(self.data)
289 |
290 | def __getitem__(self, key):
291 | return self.data[key]
292 |
293 | def __setitem__(self, key, value):
294 | self.data[key] = value
295 |
296 | def info(self):
297 | msg = super().info()
298 | msg += "Content: Binary Blob ({0:d} Bytes)\n".format(len(self.data))
299 | return msg
300 |
301 | def export(self):
302 | """ Export the image into byte array. """
303 | if len(self.data) == 0:
304 | raise Exception("Image: No data to export !")
305 |
306 | self.header.data_size = len(self.data)
307 | self.header.data_crc = CRC32(self.data)
308 |
309 | return self.header.export() + self.data
310 |
311 | @classmethod
312 | def parse(cls, data, offset=0, ignore_crc=False):
313 | """ Load the image from byte array.
314 | :param data: The raw image as byte array
315 | :param offset: The offset of input data
316 | :param ignore_crc: ignore crc errors
317 | """
318 | img = cls()
319 | img.header = Header.parse(data, offset, ignore_crc)
320 | offset += img.header.size
321 | if len(data[offset:]) < img.header.data_size:
322 | raise Exception("Image: Too small size of input data !")
323 |
324 | img.data = data[offset:offset + img.header.data_size]
325 | if CRC32(img.data) != img.header.data_crc:
326 | if not ignore_crc:
327 | raise Exception("Image: Uncorrect CRC of input data ")
328 |
329 | return img
330 |
331 |
332 | class FwImage(StdImage):
333 | def __init__(self, data=None, **kwargs):
334 | """ Image Constructor
335 | :param data: Image content as byte array
336 | :param laddr: Load address
337 | :param eaddr: Entry point address
338 | :param arch: Architecture (ARCHType Enum)
339 | :param os: Operating system (OSType Enum)
340 | :param compress: Image compression (COMPRESSType Enum)
341 | :param name: Image name (max: 32 chars)
342 | """
343 | super().__init__(data, **kwargs)
344 | # Set The Image Type to Firmware
345 | self.header.image_type = EnumImageType.FIRMWARE
346 |
347 | def __eq__(self, obj):
348 | if not isinstance(obj, FwImage):
349 | return False
350 | if self.__len__() != len(obj):
351 | return False
352 | if self.data != obj.data:
353 | return False
354 | return True
355 |
356 |
357 | class ScriptImage(BaseImage):
358 |
359 | @property
360 | def cmds(self):
361 | return self._cmds
362 |
363 | @cmds.setter
364 | def cmds(self, value):
365 | self._cmds = value
366 |
367 | def __init__(self, cmds=None, **kwargs):
368 | """ Script Image Constructor
369 | :param cmds: List of commands, the item is in format [, ]
370 | :param laddr: Load address
371 | :param eaddr: Entry point address
372 | :param arch: Architecture (ARCHType Enum)
373 | :param os: Operating system (OSType Enum)
374 | :param compress: Image compression (COMPRESSType Enum)
375 | :param name: Image name (max: 32 chars)
376 | """
377 | super().__init__(**kwargs)
378 | # Set The Image Type to Script
379 | self.header.image_type = EnumImageType.SCRIPT
380 | self._cmds = cmds if cmds else []
381 |
382 | def __eq__(self, obj):
383 | if not isinstance(obj, ScriptImage):
384 | return False
385 | if self.__len__() != len(obj):
386 | return False
387 | if self.cmds != obj.cmds:
388 | return False
389 | return True
390 |
391 | def __len__(self):
392 | return len(self._cmds)
393 |
394 | def __iter__(self):
395 | return iter(self._cmds)
396 |
397 | def __getitem__(self, key):
398 | return self._cmds[key]
399 |
400 | def __setitem__(self, key, value):
401 | self._cmds[key] = value
402 |
403 | def info(self):
404 | i = 0
405 | msg = super().info()
406 | msg += 'Content: {0:d} Commands\n'.format(len(self._cmds))
407 | for cmd in self._cmds:
408 | msg += "{0:3d}) {1:s} {2:s}\n".format(i, cmd[0], cmd[1])
409 | i += 1
410 | return msg
411 |
412 | def append(self, cmd_name, cmd_value):
413 | assert isinstance(cmd_name, str), "ScriptImage: Command name must be a string"
414 | assert isinstance(cmd_value, str), "ScriptImage: Command value must be a string"
415 | self._cmds.append([cmd_name, cmd_value])
416 |
417 | def pop(self, index):
418 | assert 0 <= index < len(self._cmds)
419 | return self._cmds.pop(index)
420 |
421 | def clear(self):
422 | self._cmds.clear()
423 |
424 | def load(self, txt_data):
425 |
426 | for line in txt_data.split('\n'):
427 | line = line.rstrip('\0')
428 | if not line:
429 | continue
430 | if line.startswith('#'):
431 | continue
432 | cmd = line.split(' ', 1)
433 | if len(cmd) == 1:
434 | cmd.append('')
435 | self._cmds.append([cmd[0], cmd[1]])
436 |
437 | def store(self, txt_data=None):
438 | """ Store variables into text file
439 | :param txt_data:
440 | :return: txt_data
441 | """
442 | if txt_data is None:
443 | txt_data = ""
444 |
445 | txt_data += '# U-Boot Script\n\n'
446 | for cmd in self._cmds:
447 | txt_data += "{0:s} {1:s}\n".format(cmd[0], cmd[1])
448 |
449 | return txt_data
450 |
451 | def export(self):
452 | """ Export the image into byte array. """
453 | if len(self._cmds) == 0:
454 | raise Exception("Image: No data to export !")
455 |
456 | data = b''
457 | for cmd in self._cmds:
458 | data += "{0:s} {1:s}\n".format(cmd[0], cmd[1]).encode('utf-8')
459 | data = pack('!2L', len(data), 0) + data
460 |
461 | self.header.data_size = len(data)
462 | self.header.data_crc = CRC32(data)
463 |
464 | return self.header.export() + data
465 |
466 | @classmethod
467 | def parse(cls, data, offset=0, ignore_crc=False):
468 | """ Load the image from byte array.
469 | :param data: The raw image as byte array
470 | :param offset: The offset of input data
471 | :param ignore_crc: ignore crc errors
472 | """
473 | img = cls()
474 | img.header = Header.parse(data, offset, ignore_crc)
475 | offset += img.header.size
476 | if len(data[offset:]) < img.header.data_size:
477 | raise Exception("Image: Too small size of input data !")
478 |
479 | data = data[offset:offset + img.header.data_size]
480 | if CRC32(data) != img.header.data_crc:
481 | if not ignore_crc:
482 | raise Exception("Image: Uncorrect CRC of input data ")
483 |
484 | # TODO: Check image format for script type
485 | size = unpack_from('!2L', data)[0]
486 | offset = 8
487 |
488 | data = data[offset:offset+size].decode('utf-8')
489 | for line in data.split('\n'):
490 | line = line.rstrip('\0')
491 | if not line:
492 | continue
493 | if line.startswith('#'):
494 | continue
495 | cmd = line.split(' ', 1)
496 | if len(cmd) == 1:
497 | cmd.append('')
498 | img.append(cmd[0], cmd[1])
499 |
500 | return img
501 |
502 |
503 | class MultiImage(BaseImage):
504 | def __init__(self, imgs=None, **kwargs):
505 | """ Multi Image Constructor
506 | :param imgs: The list of all images
507 | :param laddr: Load address
508 | :param eaddr: Entry point address
509 | :param arch: Architecture (ARCHType Enum)
510 | :param os: Operating system (OSType Enum)
511 | :param compress: Image compression (COMPRESSType Enum)
512 | :param name: Image name (max: 32 chars)
513 | """
514 | super().__init__(**kwargs)
515 | # Set Image Type as Multi in default
516 | self.header.image_type = EnumImageType.MULTI
517 | self._imgs = imgs if imgs else []
518 |
519 | def __eq__(self, obj):
520 | if not isinstance(obj, MultiImage):
521 | return False
522 | if self.__len__() != len(obj):
523 | return False
524 | for img in self._imgs:
525 | if img not in obj:
526 | return False
527 | return True
528 |
529 | def __len__(self):
530 | return len(self._imgs)
531 |
532 | def __iadd__(self, value):
533 | self._imgs += value
534 |
535 | def __iter__(self):
536 | return iter(self._imgs)
537 |
538 | def __getitem__(self, key):
539 | return self._imgs[key]
540 |
541 | def __setitem__(self, key, value):
542 | self._imgs[key] = value
543 |
544 | def __delitem__(self, key):
545 | self._imgs.remove(key)
546 |
547 | def info(self):
548 | msg = super().info()
549 | msg += 'Content: {0:d} Images\n'.format(len(self._imgs))
550 | n = 0
551 | for img in self._imgs:
552 | msg += '#IMAGE[' + str(n) + ']\n'
553 | msg += img.info()
554 | n += 1
555 | return msg
556 |
557 | def append(self, image):
558 | assert isinstance(image, BaseImage), "MultiImage: Unsupported value"
559 | self._imgs.append(image)
560 |
561 | def pop(self, index):
562 | assert 0 <= index < len(self._imgs)
563 | return self._imgs.pop(index)
564 |
565 | def cear(self):
566 | self._imgs.clear()
567 |
568 | def export(self):
569 | """ Export the image into byte array.
570 | :return
571 | """
572 | data = bytes()
573 | dlen = []
574 |
575 | if len(self._imgs) == 0:
576 | raise Exception("MultiImage: No data to export !")
577 |
578 | for img in self._imgs:
579 | dimg = img.export()
580 | # images must be aligned
581 | if len(dimg) % 4:
582 | dimg += b'\0' * (len(dimg) % 4)
583 | dlen.append(len(dimg))
584 | data += dimg
585 |
586 | dlen.append(0)
587 | fmt = "!{0:d}L".format(len(dlen))
588 | data = pack(fmt, *dlen) + data
589 |
590 | self.header.data_size = len(data)
591 | self.header.data_crc = CRC32(data)
592 |
593 | return self.header.export() + data
594 |
595 | @classmethod
596 | def parse(cls, data, offset=0, ignore_crc=False):
597 | """ Load the image from byte array.
598 | :param data: The raw image as byte array
599 | :param offset: The offset of input data
600 | :param ignore_crc: ignore crc errors
601 | """
602 | img = cls()
603 | img.header = Header.parse(data, offset, ignore_crc)
604 | offset += img.header.size
605 |
606 | if len(data[offset:]) < img.header.data_size:
607 | raise Exception("MultiImage: Too small size of input data !")
608 |
609 | if CRC32(data[offset:offset + img.header.data_size]) != img.header.data_crc:
610 | if not ignore_crc:
611 | raise Exception("MultiImage: Uncorrect CRC of input data !")
612 |
613 | if img.header.image_type != EnumImageType.MULTI:
614 | raise Exception("MultiImage: Not a Multi Image Type !")
615 |
616 | # Parse images lengths
617 | sList = []
618 | while True:
619 | (length,) = unpack_from('!L', data, offset)
620 | offset += 4
621 | if length == 0: break
622 | sList.append(length)
623 |
624 | # Parse images itself
625 | for size in sList:
626 | img.append(parse_img(data, offset))
627 | offset += size
628 |
629 | return img
630 | # ----------------------------------------------------------------------------------------------------------------------
631 |
632 |
633 | def get_img_type(data, offset=0, ignore_crc=False):
634 | """ Help function for extracting image fom raw data
635 | :param data: The raw data as byte array
636 | :param offset: The offset
637 | :return: Image type and offset where image start
638 | """
639 | while True:
640 | if (offset + Header.SIZE) > len(data):
641 | raise Exception(f"Not an U-Boot image ! {(offset + Header.SIZE)} > {len(data)}")
642 |
643 | (header_mn, header_crc,) = unpack_from('!2L', data, offset)
644 | # Check the magic number if is U-Boot image
645 | if header_mn == Header.MAGIC_NUMBER:
646 | header = bytearray(data[offset:offset+Header.SIZE])
647 | header[4:8] = [0]*4
648 | if not ignore_crc:
649 | if header_crc == CRC32(header):
650 | break
651 | else:
652 | break
653 | offset += 4
654 |
655 | (image_type,) = unpack_from('B', data, offset + 30)
656 |
657 | return image_type, offset
658 |
659 |
660 | def new_img(**kwargs):
661 | """ Help function for creating image
662 | :param img_type:
663 | :return: Image object
664 | """
665 | if not 'image' in kwargs:
666 | kwargs['image'] = EnumImageType.FIRMWARE
667 |
668 | if isinstance(kwargs['image'], str):
669 | img_type = EnumImageType[kwargs['image']]
670 | else:
671 | img_type = kwargs['image']
672 |
673 | if img_type not in EnumImageType:
674 | raise Exception("Not a valid image type")
675 |
676 | if img_type == EnumImageType.MULTI:
677 | img_obj = MultiImage(**kwargs)
678 | elif img_type == EnumImageType.FIRMWARE:
679 | img_obj = FwImage(**kwargs)
680 | elif img_type == EnumImageType.SCRIPT:
681 | img_obj = ScriptImage(**kwargs)
682 | else:
683 | img_obj = StdImage(**kwargs)
684 |
685 | return img_obj
686 |
687 |
688 | def parse_img(data, offset=0, ignore_crc=False):
689 | """ Help function for extracting image fom raw data
690 | :param data: The raw data as byte array
691 | :param offset: The offset
692 | :param ignore_crc: Ignore CRC mismatches
693 | :return: Image object
694 | """
695 | (img_type, offset) = get_img_type(bytearray(data), offset, ignore_crc)
696 |
697 | if img_type not in EnumImageType:
698 | raise Exception("Not a valid image type")
699 |
700 | if img_type == EnumImageType.MULTI:
701 | img = MultiImage.parse(data, offset, ignore_crc)
702 | elif img_type == EnumImageType.FIRMWARE:
703 | img = FwImage.parse(data, offset, ignore_crc)
704 | elif img_type == EnumImageType.SCRIPT:
705 | img = ScriptImage.parse(data, offset, ignore_crc)
706 | else:
707 | img = StdImage.parse(data, offset, ignore_crc)
708 | img.header.image_type = img_type
709 |
710 | return img
711 |
--------------------------------------------------------------------------------