diff --git a/build_mp.py b/build_mp.py index be0d87c..5384900 100755 --- a/build_mp.py +++ b/build_mp.py @@ -1112,25 +1112,34 @@ def m4_reaches_memory(sp, pc, nmi, hf): return any(lo <= w < hi for w in ((sp - 4) & 0xFFFFFFFF, pc & ~1, hf & ~1) for lo, hi in M4_BUS_REGIONS) -#: Padding tried, in bytes, when an image's M4 vector table reaches memory. -M4_PAD_SIZES = (16, 32, 48, 64) +#: Paddings tried step by this many bytes: a multiple of every alignment in +#: .text (8 at most on the GIGA), so a pad that grows by it moves all the code +#: after it on by just as much, and the bytes it brings to the M4's address can +#: be read off the image already linked. +M4_PAD_STEP = 16 +#: The most padding tried. The windows it can bring to the M4's address lie 16 +#: bytes apart over 4 KB, so no literal pool or table of addresses (a few dozen +#: bytes) can refuse them all, however the image grows around them. +M4_PAD_MAX = 4096 +#: Relinks tried before giving up, when the bytes read off the image weren't +#: what the relink put there. +M4_PAD_TRIES = 4 M4_PAD_SYMBOL = "pydevices_m4_pad" def stm32_m4_pad_module(build, pad): - """A generated C module whose only code is *pad* bytes of nops, kept by - the linker and placed with the other modules, before the M4's address: - it moves the code there. With pad 0 it compiles nothing. Returns its - directory, for USER_C_MODULES; files are rewritten only when they change.""" + """A generated C module whose only code is a function with *pad* bytes of + nops, kept by the linker and placed with the other modules, before the M4's + address: growing it moves the code there. Returns its directory, for + USER_C_MODULES; files are rewritten only when they change.""" d = build / "m4pad-src" / "m4pad" d.mkdir(parents=True, exist_ok=True) - mk = "# Generated by build_mp.py: padding that moves the code at the M4's boot address.\n" - c = "// Generated by build_mp.py: padding that moves the code at the M4's boot address.\n" - if pad: - mk += f"SRC_USERMOD_C += $(USERMOD_DIR)/m4pad.c\nLDFLAGS_USERMOD += -Wl,-u,{M4_PAD_SYMBOL}\n" - c += (f"void {M4_PAD_SYMBOL}(void);\n" - f"void {M4_PAD_SYMBOL}(void) {{\n" - f" __asm volatile (\".rept {pad // 2}\\n nop\\n .endr\");\n}}\n") + mk = ("# Generated by build_mp.py: padding that moves the code at the M4's boot address.\n" + f"SRC_USERMOD_C += $(USERMOD_DIR)/m4pad.c\nLDFLAGS_USERMOD += -Wl,-u,{M4_PAD_SYMBOL}\n") + c = ("// Generated by build_mp.py: padding that moves the code at the M4's boot address.\n" + f"void {M4_PAD_SYMBOL}(void);\n" + f"void {M4_PAD_SYMBOL}(void) {{\n" + f" __asm volatile (\".rept {pad // 2}\\n nop\\n .endr\");\n}}\n") for name, text in (("micropython.mk", mk), ("m4pad.c", c)): f = d / name if not f.exists() or f.read_text() != text: @@ -1145,22 +1154,70 @@ def stm32_m4_pad_now(build): return int(m.group(1)) * 2 if m else 0 -def stm32_m4_settle(image, text, pad, relink): +def elf_symbol(elf, name): + """The value of symbol *name* in a 32-bit little-endian ELF file, or None.""" + data = Path(elf).read_bytes() if Path(elf).exists() else b"" + if data[:6] != b"\x7fELF\x01\x01": + return None + shoff, = struct.unpack_from(" len(data): + continue + if not m4_reaches_memory(*struct.unpack_from(" make's exit code for each. Returns (pad, words, - refused words or None), or (None, words, None) when relink failed.""" - first = None - for p in [pad] + [q for q in (0,) + M4_PAD_SIZES if q != pad]: - if p != pad: - rc = relink(p) - if rc != 0: - return None, None, first - words = stm32_m4_boot_words(image, text) + memory. Keeps the current *pad* when its table passes; otherwise picks the + smallest padding whose table would pass, read off the image + (stm32_m4_pad_candidates), and calls relink(pad) -> make's exit code, up to + M4_PAD_TRIES times. *pad_at* () returns the padding function's address in + the image linked now. Returns (pad, words, refused words or None), or + (None, None, refused words) when relink failed.""" + words = stm32_m4_boot_words(image, text) + if not (words and m4_reaches_memory(*words)): + return pad, words, None + first, tried = words, {pad} + for _ in range(M4_PAD_TRIES): + p = next((q for q in stm32_m4_pad_candidates(image, text, pad, pad_at()) or () if q not in tried), None) + if p is None: + break + tried.add(p) + if relink(p) != 0: + return None, None, first + pad, words = p, stm32_m4_boot_words(image, text) if not (words and m4_reaches_memory(*words)): - return p, words, first - first = first or words - pad = p + return pad, words, first return pad, words, first @@ -2240,15 +2297,16 @@ def relink(p): (build / "firmware.elf").unlink(missing_ok=True) return shell(make, log=True)[0] - pad, words, refused = stm32_m4_settle(image, STM32_M4_SLOT[board][1], pad_before, relink) + pad, words, refused = stm32_m4_settle(image, STM32_M4_SLOT[board][1], pad_before, + lambda: elf_symbol(build / "firmware.elf", M4_PAD_SYMBOL), relink) if pad is None: rc = 1 elif words and m4_reaches_memory(*words): die(f"{image.name} hands the M4 core a vector table that reaches memory (stack {hex(words[0])}, " f"reset {hex(words[1])}, HardFault {hex(words[3])}) at {hex(STM32_M4_BOOT_DEFAULT)}, where " "the M4 of a board as shipped starts at reset on its own (BOOT_CM4_ADD0, with BCM4 set), " - f"and none of {', '.join(str(p) for p in M4_PAD_SIZES)} bytes of padding moved it clear. " - "Change the build a little, or turn BCM4 off on your board: the flash guide says how.") + f"and no padding up to {M4_PAD_MAX:,} bytes before it moved it clear. Look at what " + f"{build / 'firmware.map'} puts there, or turn BCM4 off on your board: the flash guide says how.") else: stm32_m4_pad = pad if refused: diff --git a/modules.lock b/modules.lock index 77d07b3..ea3b014 100644 --- a/modules.lock +++ b/modules.lock @@ -19,7 +19,7 @@ # sibling checkout is linked there instead) and compiles the port for its # interpreter. Move the commit here to move every LVGL build, MicroPython's and # CircuitPython's. -audiodsp https://github.com/PyDevices/audiodsp.git 1a387e5abc19923d5dcad864e072c1799a1a8253 +audiodsp https://github.com/PyDevices/audiodsp.git 6d08b0784939fa122ccb0dabeda5816bd8f70597 omnipython-lvgl https://github.com/PyDevices/omnipython-lvgl.git c34fbeeb0440e71acb62c92ddee1c3c65d089622 pygraphics https://github.com/PyDevices/pygraphics.git f97fd2e20e73f23c2138ab52a630cf04b0a7aea1 ulab https://github.com/v923z/micropython-ulab.git 1d3ddd8f5228e2c7335dac71f4846e6abb0ec9f9 diff --git a/tests/test_build_mp.py b/tests/test_build_mp.py index 63adab0..79c21c8 100644 --- a/tests/test_build_mp.py +++ b/tests/test_build_mp.py @@ -781,46 +781,118 @@ def test_a_stack_in_memory_is_refused_whatever_the_reset_address(self): def test_the_m7s_tightly_coupled_memory_is_out_of_the_m4s_reach(self): self.assertFalse(build_mp.m4_reaches_memory(0x20010000, 0x00000101, 0, 0x20004791)) + # Where the padding function sits in the fake images below, well before the + # M4's address, as it does in a real GIGA image. + PAD_AT = 0x080405ED + + def _code(self, windows): + """An image whose code before the M4's address is *windows*, 16-byte + vector tables in order, the last one at the address.""" + off = build_mp.STM32_M4_BOOT_DEFAULT - 0x08040000 + body = b"".join(struct.pack("