A bare-metal first-stage bootloader (preloader) for the Tecno KD7, built
with Meson and the arm-none-eabi toolchain targeting the
arm-eabihf ABI (Cortex-A55, NEON/FP, hard-float).
BROM (ROM)
└─► loads bootloader-kd7.bin to SRAM @ 0x00100000
└─► _reset (startup.S)
├── disable IRQ/FIQ, enter SVC mode
├── invalidate I-cache, TLBs, branch predictor
├── disable MMU + D-cache, enable I-cache
├── set SP = top of SRAM (0x00180000)
├── zero BSS, copy .data
└─► bootloader_main() (src/main.c)
├── wdt_disable()
├── uart_init() → 115200 8N1 on UART0
├── emi_init() → configure LPDDR4X via EMI
├── dram_test() → 1 KiB march pattern
├── verify next-stage image at 0x40080000
└─► jump to LK / U-Boot
| Region | Base address | Size | Purpose |
|---|---|---|---|
| Internal SRAM | 0x00100000 |
512 KiB | Bootloader code + stack + BSS |
| DRAM (LPDDR4X) | 0x40000000 |
up to 2 GiB | Mapped by EMI after init |
| Next-stage LZ | 0x40080000 |
— | Where LK / U-Boot image is expected |
bootloader-kd7/
├── arch/arm/
│ └── startup.S ARM Cortex-A55 reset handler, vector table
├── include/
│ ├── platform.h MT6769 register map, I/O helpers
│ ├── uart.h
│ ├── emi.h
│ └── wdt.h
├── src/
│ ├── main.c bootloader_main() — top-level flow
│ ├── uart.c 16550-compatible UART driver (polling)
│ ├── emi.c EMI / DRAM controller init
│ └── wdt.c Watchdog timer disable / reset
├── linker/
│ └── bootloader.ld Linker script (SRAM + DRAM regions)
├── cross/
│ └── arm-eabihf.ini Meson cross-compilation definition
└── meson.build Build system root
# Debian/Ubuntu
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi meson ninja-build
# macOS (Homebrew)
brew install arm-none-eabi-gcc meson ninja
# Arch Linux
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils meson ninjaMinimum versions: arm-none-eabi-gcc ≥ 10, meson ≥ 1.0, ninja ≥ 1.11.
# Configure (cross-compile for arm-eabihf)
meson setup build \
--cross-file cross/arm-eabihf.ini \
--buildtype release
# Compile → produces build/bootloader-kd7.bin
meson compile -C build
# Show section sizes
meson compile -C build size
# View disassembly listing
cat build/bootloader-kd7.lst
# View linker memory usage report
cat build/bootloader.map | grep -A5 "Memory Configuration"| File | Description |
|---|---|
build/bootloader-kd7.elf |
ELF with debug symbols (objdump-friendly) |
build/bootloader-kd7.bin |
Flat binary loaded by BROM |
build/bootloader-kd7.lst |
Full annotated disassembly |
build/bootloader.map |
Linker map (section layout + sizes) |
Warning: Flashing custom preloaders can brick the device if done incorrectly. Always keep a backup of the stock preloader.
The BROM exposes a USB download mode (hold Vol-Down + Power at boot). Use mtkclient or SP Flash Tool:
# mtkclient (recommended — open source)
pip install mtkclient
python mtk w preloader bootloader-kd7.bin
# SP Flash Tool
# Select scatter file → set "preloader" partition → click Download| Area | Status | Notes |
|---|---|---|
| UART driver | ✅ Functional | Polling, 115200 8N1 |
| WDT disable | ✅ Functional | Prevents spurious resets |
| EMI / DRAM | Real init requires MediaTek NDA DDR calibration blob | |
| MSDC (eMMC) | ❌ Not implemented | Needed to load next-stage from flash |
| Image verify | ❌ Stub | RSA-2048 + SHA-256 signature check |
| Secure Boot | ❌ Not implemented | Requires DA (Download Agent) key |
| MMU / cache | ❌ Disabled | Enable after DRAM init for performance |
EMI note: Full DRAM bring-up (emi_phy_init()) requires MediaTek's
closed-source calibration library, available only under NDA from MediaTek.
The emi_init() stub in src/emi.c shows exactly where to integrate it.
MIT — see LICENSE.
Hardware register definitions are derived from public MediaTek BSP fragments
and Linux kernel DTS files (GPL-2.0).