Skip to content

Commit

Permalink
UBootEnvContent: add content module for creating a U-Boot environment
Browse files Browse the repository at this point in the history
This allows creating a U-Boot environment in the image, if U-Boot is configured
to load its environment from a region on the sd card/emmc.
  • Loading branch information
MofX committed Jun 6, 2024
1 parent 7cfe204 commit 4859bfa
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 1 deletion.
4 changes: 4 additions & 0 deletions docs/core/plugins/content/UBootEnvContent.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
embdgen.plugins.content.UBootEnvContent
=======================================

.. automodule:: embdgen.plugins.content.UBootEnvContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-3.0-only

from typing import Dict
import strictyaml as y
from strictyaml.parser import YAMLChunk

class StringDict(y.MapPattern):
RESULT_TYPE = Dict[str, str]

def __init__(self):
super().__init__(y.Str(), y.Str())

def __call__(self, chunk: YAMLChunk) -> y.YAML:
self.validate(chunk)
v = y.YAML(chunk, validator=self)
v._value = {k._value: v._value for k, v in v._value.items()}
return v
2 changes: 1 addition & 1 deletion embdgen-core/src/embdgen/core/utils/class_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def by_type(cls, type_any: Any) -> Optional[Type[T]]:

if isclass(type_any):
for cur_type_class, impl_class in cls.class_map().items():
if get_origin(cur_type_class) is list:
if get_origin(cur_type_class) in [list, dict]:
continue
if issubclass(type_any, cur_type_class):
return impl_class
Expand Down
89 changes: 89 additions & 0 deletions embdgen-core/src/embdgen/plugins/content/UBootEnvContent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# SPDX-License-Identifier: GPL-3.0-only

from io import BufferedIOBase
from pathlib import Path
import struct
from typing import Dict, Optional
import zlib

from embdgen.core.content import BinaryContent
from embdgen.core.utils.class_factory import Config


@Config("vars")
@Config("file")
class UBootEnvContent(BinaryContent):
"""
U-Boot Environment region
The variables passed in using file and vars is merged
with variables defined in vars overwriting variables
defined in the variables file.
"""
CONTENT_TYPE ="uboot_env"

vars: Optional[Dict[str, str]] = None
"""Variables placed in the environment"""

_file: Optional[Path] = None

_data: bytes

@property
def file(self) -> Optional[Path]:
"""Variable file with key=value pairs"""
return self._file

@file.setter
def file(self, value: Optional[Path]):
if value and not value.exists():
raise Exception(f"File {value} does not exist")
self._file = value

def _parse_file(self) -> Dict[str, str]:
if not self.file:
return {}
out = {}
with self.file.open() as f:
for i, line in enumerate(f):
line = line.strip()
if line.startswith("#"):
continue
parts = line.split("=", 1)
if len(parts) != 2:
raise Exception(f"Invalid entry in U-Boot environment file (line {i + 1})")
out[parts[0].strip()] = parts[1].strip()
return out

def _merged_vars(self) -> Dict[str, str]:
out: Dict[str, str] = self._parse_file()

if self.vars:
out.update(self.vars)

return out

def prepare(self) -> None:
if self.size.is_undefined:
raise Exception("Size for U-Boot environment must be defined")

self._data = b""

res_vars = self._merged_vars()
for key in sorted(res_vars.keys()):
self._data += f"{key}={res_vars[key]}\0".encode()

self._data += b"\0"

if len(self._data) + 4 > self.size.bytes:
overflow = 4 + len(self._data) - self.size.bytes
raise Exception(f"U-Boot environment variables overflow storage area by {overflow} bytes")

self._data += b"\xFF" * (self.size.bytes - len(self._data) - 4)
self._data = struct.pack("<I", zlib.crc32(self._data)) + self._data

def do_write(self, file: BufferedIOBase) -> None:
file.write(self._data)

def __repr__(self) -> str:
return f"{self.__class__.__name__}(...)"
120 changes: 120 additions & 0 deletions embdgen-core/tests/content/test_UBootEnvContent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from io import BytesIO
import pytest
from pathlib import Path

from embdgen.plugins.content.UBootEnvContent import UBootEnvContent
from embdgen.core.utils.SizeType import SizeType

class TestUBootEnvContent:
def test_assign_invalid_file(self):
with pytest.raises(Exception, match="File /path/i-do-not-exist does not exist"):
UBootEnvContent().file = Path("/path/i-do-not-exist")

def test_no_size(self):
obj = UBootEnvContent()
with pytest.raises(Exception, match="Size for U-Boot environment must be defined"):
obj.prepare()

def test_empty(self):
obj = UBootEnvContent()
obj.size = SizeType(1024)
obj.prepare()

img = BytesIO()
obj.write(img)
buf = img.getbuffer()
assert buf == b"\x97\xdb\x74\x10" + b"\0" + b"\xff" * 1019

def test_file(self, tmp_path: Path):
uboot_env = tmp_path / "uboot.env"
uboot_env.write_text(
"""
C=D
A=B
#D=E
Empty=
A=Some other string with spaces
""".strip()
)
obj = UBootEnvContent()
obj.file = uboot_env
obj.size = SizeType(1024)
obj.prepare()
img = BytesIO()
obj.write(img)
buf = img.getbuffer()
data = b"A=Some other string with spaces\0C=D\0Empty=\0\0"
assert buf == b"\x08\x88\x28\xfa" + data + b"\xff" * (1024 - 4 - len(data))

def test_file_invalid_line(self, tmp_path: Path):
uboot_env = tmp_path / "uboot.env"
uboot_env.write_text(
"""
C=D
A
#D=E
A=Some other string with spaces
""".strip()
)
obj = UBootEnvContent()
obj.file = uboot_env
obj.size = SizeType(1024)
with pytest.raises(Exception, match=r"Invalid entry in U-Boot environment file \(line 2\)"):
obj.prepare()

def test_vars(self):
obj = UBootEnvContent()
obj.vars = {
"C": "D",
"A": "Some other string with spaces",
"Empty": ""
}
obj.size = SizeType(1024)
obj.prepare()
img = BytesIO()
obj.write(img)
buf = img.getbuffer()
data = b"A=Some other string with spaces\0C=D\0Empty=\0\0"
assert buf == b"\x08\x88\x28\xfa" + data + b"\xff" * (1024 - 4 - len(data))

def test_file_and_vars(self, tmp_path: Path):
uboot_env = tmp_path / "uboot.env"
uboot_env.write_text(
"""
C=D
Some Var = I will be overwritten by vars
""".strip()
)
obj = UBootEnvContent()
obj.file = uboot_env
obj.vars = {
"Some Var": "I overwrite var A from file",
"NewVar": "Additional var in vars"
}
obj.size = SizeType(1024)
obj.prepare()
img = BytesIO()
obj.write(img)
buf: memoryview = img.getbuffer()
data = b"C=D\0NewVar=Additional var in vars\0Some Var=I overwrite var A from file\0\0"
assert buf == b"\x9f\xac\xf1\xb0" + data + b"\xff" * (1024 - 4 - len(data))

def test_overflow(self):
obj = UBootEnvContent()
obj.size = SizeType(100)
obj.vars = {
"A": "x" * (100 - 4 - len(b"A=\0\0"))
}
obj.prepare()

obj.vars = {
"A": "x" * (100 - 4 - len(b"A=\0\0") + 1)
}
with pytest.raises(Exception, match=r"U-Boot environment variables overflow storage area by 1 byte"):
obj.prepare()

obj.vars = {
"A": "x" * (100 - 4 - len(b"A=\0\0") + 548)
}
with pytest.raises(Exception, match=r"U-Boot environment variables overflow storage area by 548 byte"):
obj.prepare()

0 comments on commit 4859bfa

Please sign in to comment.