├── LICENSE ├── Makefile ├── README.md ├── build.go ├── cmd └── armory-boot-usb │ ├── README.md │ └── armory-boot-usb.go ├── config ├── config.go ├── crypto.go └── minisign.go ├── console.go ├── debug.go ├── disk ├── disk.go └── ext4.go ├── docker └── Dockerfile ├── exec ├── boot_amd64.go ├── boot_amd64.s ├── boot_arm.go ├── boot_arm.s ├── boot_riscv64.go ├── elf.go ├── exec.go ├── linux_amd64.go └── linux_arm.go ├── go.mod ├── go.sum ├── main.go ├── mem.go └── sdp ├── imx.go └── sdp.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 WithSecure Corporation. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of WithSecure Corporation nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) WithSecure Corporation 2 | # 3 | # Use of this source code is governed by the license 4 | # that can be found in the LICENSE file. 5 | 6 | BUILD_USER ?= $(shell whoami) 7 | BUILD_HOST ?= $(shell hostname) 8 | BUILD_DATE ?= $(shell /bin/date -u "+%Y-%m-%d %H:%M:%S") 9 | BUILD_TAGS = linkramsize,linkramstart,linkprintk 10 | BUILD = ${BUILD_USER}@${BUILD_HOST} on ${BUILD_DATE} 11 | REV = $(shell git rev-parse --short HEAD 2> /dev/null) 12 | 13 | SHELL = /bin/bash 14 | START ?= 5242880 15 | 16 | ifeq ("${CONSOLE}","on") 17 | BUILD_TAGS := ${BUILD_TAGS},console 18 | endif 19 | 20 | APP := armory-boot 21 | GOENV := GO_EXTLINK_ENABLED=0 CGO_ENABLED=0 GOOS=tamago GOARM=7 GOARCH=arm 22 | TEXT_START := 0x90010000 # ramStart (defined in imx6/imx6ul/memory.go) + 0x10000 23 | TAMAGOFLAGS := -tags ${BUILD_TAGS} -trimpath -ldflags "-T $(TEXT_START) -R 0x1000 -X 'main.Build=${BUILD}' -X 'main.Revision=${REV}' -X 'main.Boot=${BOOT}' -X 'main.Start=${START}' -X 'main.PublicKeyStr=${PUBLIC_KEY}'" 24 | GOFLAGS := -trimpath -ldflags "-s -w" 25 | 26 | .PHONY: clean 27 | 28 | #### primary targets #### 29 | 30 | all: $(APP) 31 | 32 | imx: $(APP).imx 33 | 34 | imx_signed: $(APP)-signed.imx 35 | 36 | elf: $(APP) 37 | 38 | $(APP)-usb: 39 | @if [ "${TAMAGO}" != "" ]; then \ 40 | ${TAMAGO} build $(GOFLAGS) cmd/$(APP)-usb/*.go; \ 41 | else \ 42 | go build $(GOFLAGS) cmd/$(APP)-usb/*.go; \ 43 | fi 44 | 45 | $(APP)-usb.exe: BUILD_OPTS := GOOS=windows CGO_ENABLED=1 CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc 46 | $(APP)-usb.exe: 47 | @if [ "${TAMAGO}" != "" ]; then \ 48 | $(BUILD_OPTS) ${TAMAGO} build cmd/$(APP)-usb/*.go; \ 49 | else \ 50 | $(BUILD_OPTS) go build cmd/$(APP)-usb/*.go; \ 51 | fi 52 | 53 | #### utilities #### 54 | 55 | check_env: 56 | @if [ "${BOOT}" != "eMMC" ] && [ "${BOOT}" != "uSD" ]; then \ 57 | echo 'You need to set the BOOT variable to either eMMC or uSD to select boot media'; \ 58 | exit 1; \ 59 | fi 60 | 61 | check_tamago: 62 | @if [ "${TAMAGO}" == "" ] || [ ! -f "${TAMAGO}" ]; then \ 63 | echo 'You need to set the TAMAGO variable to a compiled version of https://github.com/usbarmory/tamago-go'; \ 64 | exit 1; \ 65 | fi 66 | 67 | check_hab_keys: 68 | @if [ "${HAB_KEYS}" == "" ]; then \ 69 | echo 'You need to set the HAB_KEYS variable to the path of secure boot keys'; \ 70 | echo 'See https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)'; \ 71 | exit 1; \ 72 | fi 73 | 74 | dcd: 75 | echo $(GOMODCACHE) 76 | echo $(TAMAGO_PKG) 77 | cp -f $(GOMODCACHE)/$(TAMAGO_PKG)/board/usbarmory/mk2/imximage.cfg $(APP).dcd 78 | 79 | clean: 80 | @rm -fr $(APP) $(APP).bin $(APP).imx $(APP)-signed.imx $(APP).csf $(APP).dcd $(APP)-usb $(APP)-usb.exe 81 | 82 | #### dependencies #### 83 | 84 | $(APP): check_tamago check_env 85 | $(GOENV) $(TAMAGO) build $(TAMAGOFLAGS) -o ${APP} 86 | 87 | $(APP).dcd: check_tamago 88 | $(APP).dcd: GOMODCACHE=$(shell ${TAMAGO} env GOMODCACHE) 89 | $(APP).dcd: TAMAGO_PKG=$(shell grep "github.com/usbarmory/tamago v" go.mod | awk '{print $$1"@"$$2}') 90 | $(APP).dcd: dcd 91 | 92 | $(APP).bin: CROSS_COMPILE=arm-none-eabi- 93 | $(APP).bin: $(APP) 94 | $(CROSS_COMPILE)objcopy --enable-deterministic-archives \ 95 | -j .text -j .rodata -j .shstrtab -j .typelink \ 96 | -j .itablink -j .gopclntab -j .go.buildinfo -j .noptrdata -j .data \ 97 | -j .bss --set-section-flags .bss=alloc,load,contents \ 98 | -j .noptrbss --set-section-flags .noptrbss=alloc,load,contents \ 99 | $(APP) -O binary $(APP).bin 100 | 101 | $(APP).imx: SOURCE_DATE_EPOCH=0 102 | $(APP).imx: $(APP).bin $(APP).dcd 103 | mkimage -n $(APP).dcd -T imximage -e $(TEXT_START) -d $(APP).bin $(APP).imx 104 | # Copy entry point from ELF file 105 | dd if=$(APP) of=$(APP).imx bs=1 count=4 skip=24 seek=4 conv=notrunc 106 | 107 | #### secure boot #### 108 | 109 | $(APP)-signed.imx: check_hab_keys $(APP).imx 110 | ${TAMAGO} install github.com/usbarmory/crucible/cmd/habtool@latest 111 | $(shell ${TAMAGO} env GOPATH)/bin/habtool \ 112 | -A ${HAB_KEYS}/CSF_1_key.pem \ 113 | -a ${HAB_KEYS}/CSF_1_crt.pem \ 114 | -B ${HAB_KEYS}/IMG_1_key.pem \ 115 | -b ${HAB_KEYS}/IMG_1_crt.pem \ 116 | -t ${HAB_KEYS}/SRK_1_2_3_4_table.bin \ 117 | -x 1 \ 118 | -i $(APP).imx \ 119 | -o $(APP).csf && \ 120 | cat $(APP).imx $(APP).csf > $(APP)-signed.imx 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This [TamaGo](https://github.com/usbarmory/tamago) based unikernel 5 | acts as a primary boot loader for the [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki), 6 | allowing boot of kernel images (e.g. Linux) from either the eMMC card or an 7 | external microSD card. 8 | 9 | This repository also provides a [command line utility](https://github.com/usbarmory/armory-boot#serial-download-protocol-utility) 10 | to load imx executables through USB using [SDP](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#serial-download-protocol-sdp). 11 | 12 | Compiling 13 | ========= 14 | 15 | Build the [TamaGo compiler](https://github.com/usbarmory/tamago-go) 16 | (or use the [latest binary release](https://github.com/usbarmory/tamago-go/releases/latest)): 17 | 18 | ``` 19 | wget https://github.com/usbarmory/tamago-go/archive/refs/tags/latest.zip 20 | unzip latest.zip 21 | cd tamago-go-latest/src && ./all.bash 22 | cd ../bin && export TAMAGO=`pwd`/go 23 | ``` 24 | 25 | The `BOOT` environment variable must be set to either `uSD` or `eMMC` to 26 | configure the bootloader media for `/boot/armory-boot.conf`, as well as kernel 27 | images, location. 28 | 29 | The `START` environment variable must be set to the offset of the first valid 30 | ext4 partition where `/boot/armory-boot.conf` is located (typically 5242880 for 31 | USB armory Mk II default pre-compiled images). 32 | 33 | The `CONSOLE` environment variable may be set to `on` to enable serial 34 | logging when a [debug accessory](https://github.com/usbarmory/usbarmory/tree/master/hardware/mark-two-debug-accessory) 35 | is connected. 36 | 37 | Build the `armory-boot.imx` application executable: 38 | 39 | ``` 40 | git clone https://github.com/usbarmory/armory-boot && cd armory-boot 41 | make imx BOOT=uSD START=5242880 42 | ``` 43 | 44 | Docker 45 | ------ 46 | 47 | For convenience a docker configuration is provided. 48 | 49 | Ensure docker daemon is running and build the `armory-boot` docker image, 50 | this needs to be done only the first time: 51 | 52 | ``` 53 | docker build --build-arg UID=$UID --build-arg GID=$UID -t armory-boot docker 54 | ``` 55 | 56 | You can enter to the container as follows: 57 | 58 | ``` 59 | docker run -it --rm -v $PWD:/build armory-boot 60 | ``` 61 | 62 | Installing 63 | ========== 64 | 65 | The `armory-boot.imx` file can be flashed on the internal eMMC card or an 66 | external micro SD card as shown in [these instructions](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#flashing-imx-native-images). 67 | 68 | Configuration 69 | ============= 70 | 71 | The bootloader expects a single configuration file to read information on the 72 | image and parameters to boot. 73 | 74 | The bootloader is configured via a single configuration file, and can boot either 75 | an ARM kernel image or an ELF unikernel (e.g. 76 | [tamago-example](https://github.com/usbarmory/tamago-example)). 77 | The required elements in the configuration file differ depending on the type of 78 | image being loaded, examples for both are given below. 79 | 80 | It is an error specify both unikernel and kernel config parameters in the same 81 | configuration file. 82 | 83 | Linux kernel boot 84 | ----------------- 85 | 86 | To load a Linux kernel, the bootloader requires that you provide the paths to 87 | the kernel image and the Device Tree Blob file, along with their respective 88 | SHA256 hashes (only used with configuration signature verification, see _Secure 89 | Boot_), as well as the kernel command line. 90 | 91 | An optional initial ramdisk can be passed with the `initrd` parameter. 92 | 93 | Example `/boot/armory-boot.conf` configuration file for loading a Linux kernel: 94 | 95 | ``` 96 | { 97 | "kernel": [ 98 | "/boot/zImage-5.4.51-0-usbarmory", 99 | "aceb3514d5ba6ac591a7d5f2cad680e83a9f848d19763563da8024f003e927c7" 100 | ], 101 | "dtb": [ 102 | "/boot/imx6ulz-usbarmory-default-5.4.51-0.dtb", 103 | "60d4fe465ef60042293f5723bf4a001d8e75f26e517af2b55e6efaef9c0db1f6" 104 | ], 105 | "initrd": [ 106 | "/boot/initrd.img-5.4.51-0-usbarmory", 107 | "64119096fd329e89f062cb5e0fc5b8e66f98081aef987e0bc7a92a05f4452540" 108 | ], 109 | "cmdline": "console=ttymxc1,115200 root=/dev/mmcblk0p1 rootwait rw" 110 | } 111 | ``` 112 | 113 | TamaGo unikernel boot 114 | --------------------- 115 | 116 | To load a TamaGo unikernel, the bootloader only needs the path to the ELF 117 | binary along with its SHA256 hash (only used with configuration signature 118 | verification, see _Secure Boot_). 119 | 120 | Example `/boot/armory-boot.conf` configuration file for loading a TamaGo 121 | unikernel: 122 | 123 | ``` 124 | { 125 | "unikernel": [ 126 | "/boot/tamago-example", 127 | "e6de9214249dd7989b4056372424e84b273ff4e5d2410fa12ac230ddaf22690a" 128 | ] 129 | } 130 | ``` 131 | 132 | Secure Boot 133 | =========== 134 | 135 | On secure booted systems the `imx_signed` target should be used instead with the relevant 136 | [`HAB_KEYS`](https://github.com/usbarmory/usbarmory/wiki/Secure-boot-(Mk-II)) set. 137 | 138 | Additionally, to maintain the chain of trust, the `PUBLIC_KEY` environment 139 | variable must be set with either a [signify](https://man.openbsd.org/signify) 140 | or [minisign](https://jedisct1.github.io/minisign/) public key to enable 141 | configuration file signature verification. 142 | 143 | Example key generation (signify): 144 | 145 | ``` 146 | signify -G -p armory-boot.pub -s armory-boot.sec 147 | ``` 148 | 149 | Example key generation (minisign): 150 | 151 | ``` 152 | minisign -G -p armory-boot.pub -s armory-boot.sec 153 | ``` 154 | 155 | Compilation with embedded key: 156 | 157 | ``` 158 | make imx_signed BOOT=uSD START=5242880 PUBLIC_KEY= HAB_KEYS= 159 | ``` 160 | 161 | When `armory-boot` is compiled with the `PUBLIC_KEY` variable, a signature for 162 | the configuration file must be created in `/boot/armory-boot.conf.sig` using 163 | with the corresponding secret key. 164 | 165 | Example signature generation (signify): 166 | 167 | ``` 168 | signify -S -s armory-boot.sec -m armory-boot.conf -x armory-boot.conf.sig 169 | ``` 170 | 171 | Example signature generation (minisign): 172 | 173 | ``` 174 | minisign -S -s armory-boot.sec -m armory-boot.conf -x armory-boot.conf.sig 175 | ``` 176 | 177 | LED status 178 | ========== 179 | 180 | The [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki) LEDs 181 | are used, in sequence, as follows: 182 | 183 | | Boot sequence | Blue | White | 184 | |---------------------------------|------|-------| 185 | | 0. initialization | off | off | 186 | | 1. boot media detected | on | off | 187 | | 2. kernel verification complete | on | on | 188 | | 3. jumping to kernel image | off | off | 189 | 190 | Serial Download Protocol utility 191 | ================================ 192 | 193 | The `armory-boot-usb` command line utility allows to load an imx executable 194 | through USB using [SDP](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#serial-download-protocol-sdp), 195 | useful for testing or initial provisioning purposes. 196 | 197 | You can automatically download, compile and install the utility, under your 198 | GOPATH, as follows: 199 | 200 | ``` 201 | go install github.com/usbarmory/armory-boot/cmd/armory-boot-usb@latest 202 | ``` 203 | 204 | Alternatively you can manually compile it from source: 205 | 206 | ``` 207 | git clone https://github.com/usbarmory/armory-boot 208 | cd armory-boot && make armory-boot-usb 209 | ``` 210 | 211 | The utility can be cross compiled for Windows as follows: 212 | 213 | ``` 214 | make armory-boot-usb.exe 215 | ``` 216 | 217 | Pre-compiled binaries for Linux and Windows are released 218 | [here](https://github.com/usbarmory/armory-boot/releases). 219 | 220 | The utility is meant to be used on devices running in 221 | [USB SDP mode](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)): 222 | 223 | ``` 224 | sudo armory-boot-usb -i armory-boot.imx 225 | found device 15a2:0080 Freescale SemiConductor Inc SE Blank 6ULL 226 | parsing armory-boot.imx 227 | loading DCD at 0x00910000 (952 bytes) 228 | loading imx to 0x9000f400 (2182144 bytes) 229 | jumping to 0x9000f400 230 | serial download complete 231 | ``` 232 | 233 | Authors 234 | ======= 235 | 236 | Andrea Barisani 237 | andrea@inversepath.com 238 | 239 | License 240 | ======= 241 | 242 | armory-boot | https://github.com/usbarmory/armory-boot 243 | Copyright (c) WithSecure Corporation 244 | 245 | These source files are distributed under the BSD-style license found in the 246 | [LICENSE](https://github.com/usbarmory/armory-boot/blob/master/LICENSE) file. 247 | -------------------------------------------------------------------------------- /build.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | // Build time variables 9 | var ( 10 | // Build information 11 | Build string 12 | Revision string 13 | 14 | // Boot device 15 | Boot string 16 | Start string 17 | 18 | // Authentication key 19 | PublicKeyStr string 20 | ) 21 | -------------------------------------------------------------------------------- /cmd/armory-boot-usb/README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This tool implements a minimal set of the Serial Download Protocol (SDP), used 5 | on NXP i.MX SoC application processors, to load an executable image over USB. 6 | 7 | The tool is aimed at [USB armory Mk II](https://github.com/usbarmory/usbarmory/wiki) users but should work 8 | with all SoCs from the i.MX series capable of USB HID based SDP (only tested 9 | devices are listed as supported, Pull Requests are welcome to expand this set). 10 | 11 | The [mfgtools](https://github.com/NXPmicro/mfgtools) and 12 | [imx_usb_loader](https://github.com/boundarydevices/imx_usb_loader) projects 13 | also implement similar functionality. 14 | 15 | Serial Download Protocol 16 | ======================== 17 | 18 | The `armory-boot-usb` command line utility allows to load an imx executable 19 | through USB using [SDP](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)#serial-download-protocol-sdp), 20 | useful for testing or initial provisioning purposes. 21 | 22 | You can automatically download, compile and install the utility, under your 23 | GOPATH, as follows: 24 | 25 | ``` 26 | go install github.com/usbarmory/armory-boot/cmd/armory-boot-usb@latest 27 | ``` 28 | 29 | Alternatively you can manually compile it from source: 30 | 31 | ``` 32 | git clone https://github.com/usbarmory/armory-boot 33 | cd armory-boot && make armory-boot-usb 34 | ``` 35 | 36 | The utility can be cross compiled for Windows as follows: 37 | 38 | ``` 39 | make armory-boot-usb.exe 40 | ``` 41 | 42 | Pre-compiled binaries for Linux and Windows are released 43 | [here](https://github.com/usbarmory/armory-boot/releases). 44 | 45 | The utility is meant to be used on devices running in 46 | [USB SDP mode](https://github.com/usbarmory/usbarmory/wiki/Boot-Modes-(Mk-II)): 47 | 48 | ``` 49 | sudo armory-boot-usb -i armory-boot.imx 50 | found device 15a2:0080 Freescale SemiConductor Inc SE Blank 6ULL 51 | parsing armory-boot.imx 52 | loading DCD at 0x00910000 (952 bytes) 53 | loading imx to 0x9000f400 (2182144 bytes) 54 | jumping to 0x9000f400 55 | serial download complete 56 | ``` 57 | 58 | Authors 59 | ======= 60 | 61 | Andrea Barisani 62 | andrea@inversepath.com 63 | 64 | License 65 | ======= 66 | 67 | armory-boot | https://github.com/usbarmory/armory-boot 68 | Copyright (c) WithSecure Corporation 69 | 70 | These source files are distributed under the BSD-style license found in the 71 | [LICENSE](https://github.com/usbarmory/armory-boot/blob/master/LICENSE) file. 72 | -------------------------------------------------------------------------------- /cmd/armory-boot-usb/armory-boot-usb.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // This tool implements a minimal set of the Serial Download Protocol (SDP), 7 | // used on NXP i.MX SoC application processors, to load an executable image 8 | // over USB. 9 | // 10 | // It implements a subset of the functionality also available in the following 11 | // tools: 12 | // * https://github.com/NXPmicro/mfgtools 13 | // * https://github.com/boundarydevices/imx_usb_loader 14 | 15 | package main 16 | 17 | import ( 18 | "errors" 19 | "flag" 20 | "log" 21 | "math/big" 22 | "os" 23 | "runtime" 24 | "time" 25 | 26 | "github.com/usbarmory/armory-boot/sdp" 27 | 28 | "github.com/usbarmory/hid" 29 | ) 30 | 31 | const ( 32 | // USB vendor ID for all supported devices 33 | FreescaleVendorID = 0x15a2 34 | 35 | // On-Chip RAM (OCRAM/iRAM) address for payload staging 36 | iramOffset = 0x00910000 37 | ) 38 | 39 | // SDP HID report IDs 40 | // (p323, 8.9.3.1 SDP commands, IMX6ULLRM). 41 | const ( 42 | H2D_COMMAND = 1 // Command - Host to Device 43 | H2D_DATA = 2 // Data - Host to Device 44 | D2H_RESPONSE = 3 // Response - Device to Host 45 | D2H_RESPONSE_LAST = 4 // Response - Device to Host 46 | ) 47 | 48 | // This tool should work with all SoCs from the i.MX series capable of USB HID 49 | // based SDP, only tested devices are listed as supported, Pull Requests are 50 | // welcome to expand this set. 51 | var supportedDevices = map[uint16]string{ 52 | 0x0054: "Freescale SemiConductor Inc SE Blank ARIK", 53 | 0x0061: "Freescale SemiConductor Inc SP Blank RIGEL", 54 | 0x007d: "Freescale SemiConductor Inc SE Blank 6UL", 55 | 0x0080: "Freescale SemiConductor Inc SE Blank 6ULL", 56 | } 57 | 58 | type Config struct { 59 | inf *hid.DeviceInfo 60 | dev hid.Device 61 | timeout int 62 | input string 63 | register string 64 | } 65 | 66 | var conf *Config 67 | 68 | func init() { 69 | log.SetFlags(0) 70 | log.SetOutput(os.Stdout) 71 | 72 | conf = &Config{} 73 | 74 | flag.IntVar(&conf.timeout, "t", 5, "timeout in seconds for command responses") 75 | flag.StringVar(&conf.input, "i", "", "imx file") 76 | flag.StringVar(&conf.register, "r", "0x021bc400", "read register") 77 | } 78 | 79 | // detect compatible devices in SDP mode 80 | func detect() (err error) { 81 | devices, err := hid.Devices() 82 | 83 | if err != nil { 84 | return 85 | } 86 | 87 | for _, d := range devices { 88 | if d.VendorID != FreescaleVendorID { 89 | continue 90 | } 91 | 92 | if product, ok := supportedDevices[d.ProductID]; ok { 93 | log.Printf("found device %04x:%04x %s", d.VendorID, d.ProductID, product) 94 | } else { 95 | continue 96 | } 97 | 98 | conf.inf = d 99 | conf.dev, err = d.Open() 100 | 101 | if err != nil { 102 | return 103 | } 104 | 105 | break 106 | } 107 | 108 | if conf.dev == nil { 109 | return errors.New("no device found, target missing or invalid permissions (forgot admin shell?)") 110 | } 111 | 112 | return 113 | } 114 | 115 | func sendHIDReport(reqID int, req []byte, resID int, n int) (res []byte, err error) { 116 | p := append([]byte{byte(reqID)}, req...) 117 | 118 | if err = conf.dev.Write(p); err != nil || resID < 0 { 119 | return 120 | } 121 | 122 | if n > 0 { 123 | conf.inf.InputReportLength = 1 + uint16(n) 124 | } 125 | 126 | timer := time.After(time.Duration(conf.timeout) * time.Second) 127 | 128 | for { 129 | select { 130 | case res, ok := <-conf.dev.ReadCh(): 131 | if !ok { 132 | return nil, errors.New("error reading response") 133 | } 134 | 135 | if len(res) > 0 && res[0] == byte(resID) { 136 | return res[1:], nil 137 | } 138 | case <-timer: 139 | return nil, errors.New("command timeout") 140 | } 141 | } 142 | } 143 | 144 | func readRegister(addr uint32, n int) { 145 | r1 := sdp.BuildReadRegisterReport(addr, uint32(n)) 146 | 147 | log.Printf("reading %d bytes at %#x", n, addr) 148 | res, err := sendHIDReport(H2D_COMMAND, r1, D2H_RESPONSE_LAST, n) 149 | 150 | if err != nil { 151 | log.Fatal(err) 152 | } 153 | 154 | log.Printf("%#.8x: %x", addr, res) 155 | } 156 | 157 | func dcdWrite(dcd []byte, addr uint32) (err error) { 158 | r1, r2 := sdp.BuildDCDWriteReport(dcd, addr) 159 | 160 | _, err = sendHIDReport(H2D_COMMAND, r1, -1, -1) 161 | 162 | if err != nil { 163 | return 164 | } 165 | 166 | _, err = sendHIDReport(H2D_DATA, r2, D2H_RESPONSE_LAST, -1) 167 | 168 | return 169 | } 170 | 171 | func fileWrite(imx []byte, addr uint32) (err error) { 172 | r1, r2 := sdp.BuildFileWriteReport(imx, addr) 173 | 174 | _, err = sendHIDReport(H2D_COMMAND, r1, -1, -1) 175 | 176 | if err != nil { 177 | return 178 | } 179 | 180 | resID := -1 181 | timer := time.After(time.Duration(conf.timeout) * time.Second) 182 | 183 | for i, r := range r2 { 184 | if i == len(r2)-1 { 185 | resID = D2H_RESPONSE_LAST 186 | } 187 | send: 188 | _, err = sendHIDReport(H2D_DATA, r, resID, -1) 189 | 190 | if err != nil && runtime.GOOS == "darwin" && err.Error() == "hid: general error" { 191 | // On macOS access contention with the OS causes 192 | // errors, as a workaround we retry from the transfer 193 | // that got caught up. 194 | select { 195 | case <-timer: 196 | return 197 | default: 198 | off := uint32(i) * 1024 199 | r1 := &sdp.SDP{ 200 | CommandType: sdp.WriteFile, 201 | Address: addr + off, 202 | DataCount: uint32(len(imx)) - off, 203 | } 204 | 205 | if _, err = sendHIDReport(H2D_COMMAND, r1.Bytes(), -1, -1); err != nil { 206 | return 207 | } 208 | 209 | goto send 210 | } 211 | } 212 | 213 | if err != nil { 214 | break 215 | } 216 | } 217 | 218 | return 219 | } 220 | 221 | func jumpAddress(addr uint32) (err error) { 222 | r1 := sdp.BuildJumpAddressReport(addr) 223 | _, err = sendHIDReport(H2D_COMMAND, r1, -1, -1) 224 | 225 | return 226 | } 227 | 228 | func writeAndJump(input string) { 229 | log.Printf("parsing %s", input) 230 | imx, err := os.ReadFile(input) 231 | 232 | if err != nil { 233 | log.Fatal(err) 234 | } 235 | 236 | ivt, err := sdp.ParseIVT(imx) 237 | 238 | if err != nil { 239 | log.Fatalf("IVT parsing error: %v", err) 240 | } 241 | 242 | dcd, err := sdp.ParseDCD(imx, ivt) 243 | 244 | if err != nil { 245 | log.Fatalf("DCD parsing error: %v", err) 246 | } 247 | 248 | log.Printf("loading DCD at %#08x (%d bytes)", iramOffset, len(dcd)) 249 | err = dcdWrite(dcd, iramOffset) 250 | 251 | if err != nil { 252 | log.Fatal(err) 253 | } 254 | 255 | log.Printf("loading imx to %#08x (%d bytes)", ivt.Self, len(imx)) 256 | err = fileWrite(imx, ivt.Self) 257 | 258 | if err != nil { 259 | log.Fatal(err) 260 | } 261 | 262 | log.Printf("jumping to %#08x", ivt.Self) 263 | err = jumpAddress(ivt.Self) 264 | 265 | if err != nil { 266 | log.Fatal(err) 267 | } 268 | 269 | log.Printf("serial download complete") 270 | } 271 | 272 | func main() { 273 | var err error 274 | 275 | flag.Parse() 276 | 277 | if err = detect(); err != nil { 278 | log.Fatal(err) 279 | } 280 | 281 | switch { 282 | case len(conf.input) > 0: 283 | writeAndJump(conf.input) 284 | case len(conf.register) > 0: 285 | addr := new(big.Int) 286 | addr.SetString(conf.register, 0) 287 | readRegister(uint32(addr.Int64()), 4) 288 | default: 289 | flag.PrintDefaults() 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package config provides parsing for the armory-boot configuration file 7 | // format. 8 | package config 9 | 10 | import ( 11 | "encoding/json" 12 | "errors" 13 | "fmt" 14 | "log" 15 | 16 | "github.com/usbarmory/armory-boot/disk" 17 | ) 18 | 19 | // DefaultConfigPath is the default armory-boot configuration file path. 20 | const DefaultConfigPath = "/boot/armory-boot.conf" 21 | 22 | // DefaultSignaturePath is the default armory-boot configuration file signature 23 | // path. 24 | const DefaultSignaturePath = "/boot/armory-boot.conf.sig" 25 | 26 | // Config represents the armory-boot configuration. 27 | type Config struct { 28 | // KernelPath is the path to a Linux kernel image. 29 | KernelPath []string `json:"kernel"` 30 | 31 | // DeviceTreeBlobPath is the path to a Linux DTB file. 32 | DeviceTreeBlobPath []string `json:"dtb"` 33 | 34 | // InitialRamDiskPath is the path to a Linux initrd file. 35 | InitialRamDiskPath []string `json:"initrd"` 36 | 37 | // CmdLine is the Linux kernel command-line parameters. 38 | CmdLine string `json:"cmdline"` 39 | 40 | // Unikernel is the path to an ELF unikernel image (e.g. TamaGo). 41 | UnikernelPath []string `json:"unikernel"` 42 | 43 | // ELF indicates whether the loaded kernel is a unikernel or not. 44 | ELF bool 45 | 46 | // JSON holds the configuration file contents 47 | JSON []byte 48 | 49 | kernel []byte 50 | dtb []byte 51 | initrd []byte 52 | 53 | kernelHash string 54 | dtbHash string 55 | initrdHash string 56 | } 57 | 58 | func (c *Config) init(part *disk.Partition) (err error) { 59 | var kernelPath string 60 | 61 | if err = json.Unmarshal(c.JSON, &c); err != nil { 62 | return 63 | } 64 | 65 | ul, kl := len(c.UnikernelPath), len(c.KernelPath) 66 | isUnikernel, isKernel := ul > 0, kl > 0 67 | 68 | if isUnikernel == isKernel { 69 | return errors.New("must specify either unikernel or kernel") 70 | } 71 | 72 | switch { 73 | case isKernel: 74 | if kl != 2 { 75 | return errors.New("invalid kernel parameter size") 76 | } 77 | 78 | if len(c.DeviceTreeBlobPath) != 2 { 79 | return errors.New("invalid dtb parameter size") 80 | } 81 | 82 | if len(c.InitialRamDiskPath) > 0 { 83 | if len(c.InitialRamDiskPath) != 2 { 84 | return errors.New("invalid initrd parameter size") 85 | } 86 | 87 | if c.initrd, err = part.ReadAll(c.InitialRamDiskPath[0]); err != nil { 88 | return 89 | } 90 | 91 | c.initrdHash = c.InitialRamDiskPath[1] 92 | } 93 | 94 | kernelPath = c.KernelPath[0] 95 | c.kernelHash = c.KernelPath[1] 96 | 97 | if c.dtb, err = part.ReadAll(c.DeviceTreeBlobPath[0]); err != nil { 98 | return 99 | } 100 | 101 | c.dtbHash = c.DeviceTreeBlobPath[1] 102 | case isUnikernel: 103 | if ul != 2 { 104 | return errors.New("invalid unikernel parameter size") 105 | } 106 | 107 | kernelPath = c.UnikernelPath[0] 108 | c.kernelHash = c.UnikernelPath[1] 109 | } 110 | 111 | if c.kernel, err = part.ReadAll(kernelPath); err != nil { 112 | return fmt.Errorf("invalid path %s, %v", kernelPath, err) 113 | } 114 | 115 | if err != nil { 116 | return fmt.Errorf("invalid path %s, %v", c.DeviceTreeBlobPath[0], err) 117 | } 118 | 119 | if isUnikernel { 120 | c.ELF = true 121 | } 122 | 123 | return 124 | } 125 | 126 | // Load reads an armory-boot configuration file, and optionally its signature, 127 | // from a disk partition. The public key argument is used for signature 128 | // authentication, a valid signature path must be present if a key is set. 129 | func Load(part *disk.Partition, configPath string, sigPath string, pubKey string) (c *Config, err error) { 130 | log.Printf("armory-boot: loading configuration at %s\n", configPath) 131 | 132 | c = &Config{} 133 | 134 | if c.JSON, err = part.ReadAll(configPath); err != nil { 135 | return 136 | } 137 | 138 | if len(pubKey) > 0 { 139 | sig, err := part.ReadAll(sigPath) 140 | 141 | if err != nil { 142 | return nil, fmt.Errorf("invalid signature path, %v", err) 143 | } 144 | 145 | if err = Verify(c.JSON, sig, pubKey); err != nil { 146 | return nil, err 147 | } 148 | } 149 | 150 | defer func() { 151 | if err != nil { 152 | c.kernel = nil 153 | c.dtb = nil 154 | c.initrd = nil 155 | } 156 | }() 157 | 158 | if err = c.init(part); err != nil { 159 | return 160 | } 161 | 162 | if len(pubKey) == 0 { 163 | return 164 | } 165 | 166 | if !CompareHash(c.kernel, c.kernelHash) { 167 | err = errors.New("invalid kernel hash") 168 | return 169 | } 170 | 171 | if len(c.dtb) > 0 && !CompareHash(c.dtb, c.dtbHash) { 172 | err = errors.New("invalid dtb hash") 173 | return 174 | } 175 | 176 | if len(c.initrd) > 0 && !CompareHash(c.initrd, c.initrdHash) { 177 | err = errors.New("invalid initrd hash") 178 | return 179 | } 180 | 181 | return 182 | } 183 | 184 | // Kernel returns the contents of the kernel image previously loaded by a 185 | // successful Load(). 186 | func (c *Config) Kernel() []byte { 187 | return c.kernel 188 | } 189 | 190 | // DeviceTreeBlob returns the contents of the dtb file previously loaded by a 191 | // successful Load(). 192 | func (c *Config) DeviceTreeBlob() []byte { 193 | return c.dtb 194 | } 195 | 196 | // InitialRamDisk returns the contents of the initrd image previously loaded by 197 | // a successful Load(). 198 | func (c *Config) InitialRamDisk() []byte { 199 | return c.initrd 200 | } 201 | -------------------------------------------------------------------------------- /config/crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package config 7 | 8 | import ( 9 | "bytes" 10 | "crypto/sha256" 11 | "encoding/hex" 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 16 | ) 17 | 18 | func init() { 19 | if imx6ul.DCP != nil { 20 | imx6ul.DCP.Init() 21 | } 22 | } 23 | 24 | // Verify authenticates an input against a signify/minisign generated 25 | // signature, pubKey must be the last line of a signify/minisign public key 26 | // (i.e. without comments). 27 | func Verify(buf []byte, sig []byte, pubKey string) (err error) { 28 | s, err := DecodeSignature(string(sig)) 29 | 30 | if err != nil { 31 | return fmt.Errorf("invalid signature, %v", err) 32 | } 33 | 34 | pub, err := NewPublicKey(pubKey) 35 | 36 | if err != nil { 37 | return fmt.Errorf("invalid public key, %v", err) 38 | } 39 | 40 | valid, err := pub.Verify(buf, s) 41 | 42 | if err != nil { 43 | return fmt.Errorf("invalid signature, %v", err) 44 | } 45 | 46 | if !valid { 47 | return errors.New("invalid signature") 48 | } 49 | 50 | return 51 | } 52 | 53 | // CompareHash computes a SHA256 checksum of the input data, using hardware 54 | // acceleration (NXP DCP), and compares the computed hash with the one passed 55 | // as a string with only hexadecimal characters and even length. 56 | // 57 | // As this function is meant for pre-boot use, the entire input buffer is 58 | // copied in a DMA region for DCP consumption in a single pass, rather than 59 | // buffering over multiple passes, to reduce DCP command overhead. When used in 60 | // other contexts callers must ensure that enough DMA space is available. 61 | // 62 | // This function is only meant to be used with `GOOS=tamago GOARCH=arm` on 63 | // i.MX6 targets. 64 | func CompareHash(buf []byte, s string) (valid bool) { 65 | var sum [32]byte 66 | var err error 67 | 68 | switch { 69 | case imx6ul.CAAM != nil: 70 | sum, err = imx6ul.CAAM.Sum256(buf) 71 | case imx6ul.DCP != nil: 72 | sum, err = imx6ul.DCP.Sum256(buf) 73 | default: 74 | sum = sha256.Sum256(buf) 75 | } 76 | 77 | if err != nil { 78 | return false 79 | } 80 | 81 | hash, err := hex.DecodeString(s) 82 | 83 | if err != nil { 84 | return false 85 | } 86 | 87 | return bytes.Equal(sum[:], hash) 88 | } 89 | -------------------------------------------------------------------------------- /config/minisign.go: -------------------------------------------------------------------------------- 1 | // https://github.com/jedisct1/go-minisign 2 | // 3 | // Copyright (c) 2018-2019 Frank Denis 4 | // 5 | // This code is a modified version of go-minisign which ignores trusted 6 | // comments to support verification of both signify as well as minisign 7 | // signatures. 8 | // 9 | // Use of this source code is governed by the license that can be found at: 10 | // https://github.com/jedisct1/go-minisign/blob/master/LICENSE 11 | 12 | package config 13 | 14 | import ( 15 | "encoding/base64" 16 | "errors" 17 | "strings" 18 | 19 | "golang.org/x/crypto/blake2b" 20 | "golang.org/x/crypto/ed25519" 21 | ) 22 | 23 | type PublicKey struct { 24 | SignatureAlgorithm [2]byte 25 | KeyId [8]byte 26 | PublicKey [32]byte 27 | } 28 | 29 | type Signature struct { 30 | UntrustedComment string 31 | SignatureAlgorithm [2]byte 32 | KeyId [8]byte 33 | Signature [64]byte 34 | TrustedComment string 35 | GlobalSignature [64]byte 36 | } 37 | 38 | func NewPublicKey(publicKeyStr string) (PublicKey, error) { 39 | var publicKey PublicKey 40 | 41 | bin, err := base64.StdEncoding.DecodeString(publicKeyStr) 42 | 43 | if err != nil || len(bin) != 42 { 44 | return publicKey, errors.New("Invalid encoded public key") 45 | } 46 | 47 | copy(publicKey.SignatureAlgorithm[:], bin[0:2]) 48 | copy(publicKey.KeyId[:], bin[2:10]) 49 | copy(publicKey.PublicKey[:], bin[10:42]) 50 | 51 | return publicKey, nil 52 | } 53 | 54 | func trimCarriageReturn(input string) string { 55 | return strings.TrimRight(input, "\r") 56 | } 57 | 58 | func DecodeSignature(in string) (Signature, error) { 59 | var signature Signature 60 | 61 | lines := strings.SplitN(in, "\n", 4) 62 | 63 | if len(lines) < 2 { 64 | return signature, errors.New("Incomplete encoded signature") 65 | } 66 | 67 | signature.UntrustedComment = trimCarriageReturn(lines[0]) 68 | 69 | bin1, err := base64.StdEncoding.DecodeString(lines[1]) 70 | 71 | if err != nil || len(bin1) != 74 { 72 | return signature, errors.New("Invalid encoded signature") 73 | } 74 | 75 | copy(signature.SignatureAlgorithm[:], bin1[0:2]) 76 | copy(signature.KeyId[:], bin1[2:10]) 77 | copy(signature.Signature[:], bin1[10:74]) 78 | 79 | if len(lines) == 4 { 80 | signature.TrustedComment = trimCarriageReturn(lines[2]) 81 | 82 | bin2, err := base64.StdEncoding.DecodeString(lines[3]) 83 | 84 | if err != nil || len(bin2) != 64 { 85 | return signature, errors.New("Invalid encoded signature") 86 | } 87 | 88 | copy(signature.GlobalSignature[:], bin2) 89 | } 90 | 91 | return signature, nil 92 | } 93 | 94 | func (publicKey *PublicKey) Verify(bin []byte, signature Signature) (bool, error) { 95 | if publicKey.SignatureAlgorithm != [2]byte{'E', 'd'} { 96 | return false, errors.New("Incompatible signature algorithm") 97 | } 98 | 99 | prehashed := false 100 | if signature.SignatureAlgorithm[0] == 0x45 && signature.SignatureAlgorithm[1] == 0x64 { 101 | prehashed = false 102 | } else if signature.SignatureAlgorithm[0] == 0x45 && signature.SignatureAlgorithm[1] == 0x44 { 103 | prehashed = true 104 | } else { 105 | return false, errors.New("Unsupported signature algorithm") 106 | } 107 | 108 | if publicKey.KeyId != signature.KeyId { 109 | return false, errors.New("Incompatible key identifiers") 110 | } 111 | 112 | if prehashed { 113 | h, _ := blake2b.New512(nil) 114 | h.Write(bin) 115 | bin = h.Sum(nil) 116 | } 117 | 118 | if !ed25519.Verify(ed25519.PublicKey(publicKey.PublicKey[:]), bin, signature.Signature[:]) { 119 | return false, errors.New("Invalid signature") 120 | } 121 | 122 | if len(signature.TrustedComment) != 0 { 123 | if !strings.HasPrefix(signature.TrustedComment, "trusted comment: ") { 124 | return false, errors.New("Unexpected format for the trusted comment") 125 | } 126 | 127 | if !ed25519.Verify(ed25519.PublicKey(publicKey.PublicKey[:]), append(signature.Signature[:], []byte(signature.TrustedComment)[17:]...), signature.GlobalSignature[:]) { 128 | return false, errors.New("Invalid global signature") 129 | } 130 | } 131 | 132 | return true, nil 133 | } 134 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | //go:build !console 7 | // +build !console 8 | 9 | package main 10 | 11 | import ( 12 | "io" 13 | "log" 14 | _ "unsafe" 15 | 16 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 17 | ) 18 | 19 | // This bootloader does not log any sensitive information to the serial 20 | // console, however it is desirable to silence any potential stack trace or 21 | // runtime errors to avoid unwanted information leaks. 22 | // 23 | // The TamaGo board support for the USB armory Mk II enables the serial console 24 | // (UART2) at runtime initialization, which therefore invokes imx6.UART2.Init() 25 | // before init(). 26 | // 27 | // To this end the runtime printk function, responsible for all console logging 28 | // operations (i.e. stdout/stderr), is overridden with a NOP. Secondarily UART2 29 | // is disabled at the first opportunity (init()). 30 | 31 | func init() { 32 | // disable console 33 | imx6ul.UART2.Disable() 34 | // silence logging 35 | log.SetOutput(io.Discard) 36 | } 37 | 38 | //go:linkname printk runtime.printk 39 | func printk(c byte) { 40 | // ensure that any serial output is supressed before UART2 disabling 41 | } 42 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | //go:build console 7 | // +build console 8 | 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "log" 14 | "runtime" 15 | "time" 16 | _ "unsafe" 17 | 18 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 19 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 20 | ) 21 | 22 | func init() { 23 | debugConsole, _ := usbarmory.DetectDebugAccessory(250 * time.Millisecond) 24 | <-debugConsole 25 | 26 | banner := fmt.Sprintf("armory-boot • %s/%s (%s) • %s %s • %s", 27 | runtime.GOOS, runtime.GOARCH, runtime.Version(), 28 | Revision, Build, 29 | imx6ul.Model()) 30 | 31 | log.SetFlags(0) 32 | log.Printf("%s", banner) 33 | } 34 | 35 | //go:linkname printk runtime.printk 36 | func printk(c byte) { 37 | usbarmory.UART2.Tx(c) 38 | } 39 | -------------------------------------------------------------------------------- /disk/disk.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package disk 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "strconv" 12 | 13 | "github.com/usbarmory/tamago/soc/nxp/usdhc" 14 | ) 15 | 16 | // DefaultBootDevice is the default boot device 17 | const DefaultBootDevice = "uSD" 18 | 19 | // DefaultOffset is the default start offset of the ext4 partition 20 | const DefaultOffset = 5242880 21 | 22 | // Detect initializes the USB armory internal flash ("eMMC") or external 23 | // microSD card ("uSD") as boot device, an ext4 partition must be present at 24 | // the passed start offset. An empty value for device or start parameter selects 25 | // its default value. 26 | func Detect(card *usdhc.USDHC, start string) (part *Partition, err error) { 27 | offset := int64(DefaultOffset) 28 | 29 | if card == nil { 30 | return nil, errors.New("invalid card") 31 | } 32 | 33 | if len(start) > 0 { 34 | if offset, err = strconv.ParseInt(start, 10, 64); err != nil { 35 | return nil, fmt.Errorf("invalid start offset, %v\n", err) 36 | } 37 | } 38 | 39 | part = &Partition{ 40 | Card: card, 41 | Offset: offset, 42 | } 43 | 44 | if err := part.Card.Detect(); err != nil { 45 | return nil, fmt.Errorf("could not detect card, %v\n", err) 46 | } 47 | 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /disk/ext4.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package disk provides support for SD/MMC card partition access, only ext4 7 | // filesystems are currently supported. 8 | // 9 | // This package is only meant to be used with `GOOS=tamago GOARCH=arm` as 10 | // supported by the TamaGo framework for bare metal Go, see 11 | // https://github.com/usbarmory/tamago. 12 | package disk 13 | 14 | import ( 15 | "errors" 16 | "fmt" 17 | "io" 18 | "strings" 19 | 20 | "github.com/dsoprea/go-ext4" 21 | 22 | "github.com/usbarmory/tamago/soc/nxp/usdhc" 23 | ) 24 | 25 | // Partition represents an SD/MMC card partition, only ext4 filesystems are 26 | // currently supported. 27 | type Partition struct { 28 | Card *usdhc.USDHC 29 | Offset int64 30 | _offset int64 31 | } 32 | 33 | func (part *Partition) getBlockGroupDescriptor(inode int) (bgd *ext4.BlockGroupDescriptor, err error) { 34 | _, err = part.Seek(ext4.Superblock0Offset, io.SeekStart) 35 | 36 | if err != nil { 37 | return 38 | } 39 | 40 | sb, err := ext4.NewSuperblockWithReader(part) 41 | 42 | if err != nil { 43 | return 44 | } 45 | 46 | bgdl, err := ext4.NewBlockGroupDescriptorListWithReadSeeker(part, sb) 47 | 48 | if err != nil { 49 | return 50 | } 51 | 52 | return bgdl.GetWithAbsoluteInode(inode) 53 | } 54 | 55 | func (part *Partition) Read(p []byte) (n int, err error) { 56 | buf, err := part.Card.Read(part._offset, int64(len(p))) 57 | 58 | if err != nil { 59 | return 60 | } 61 | 62 | n = copy(p, buf) 63 | _, err = part.Seek(int64(n), io.SeekCurrent) 64 | 65 | return 66 | } 67 | 68 | func (part *Partition) Seek(offset int64, whence int) (int64, error) { 69 | info := part.Card.Info() 70 | end := int64(info.Blocks) * int64(info.BlockSize) 71 | 72 | switch whence { 73 | case io.SeekStart: 74 | part._offset = part.Offset + offset 75 | case io.SeekCurrent: 76 | part._offset += offset 77 | case io.SeekEnd: 78 | part._offset = end + part.Offset + offset 79 | default: 80 | return 0, fmt.Errorf("invalid whence %d", whence) 81 | } 82 | 83 | if part._offset > end { 84 | return 0, fmt.Errorf("invalid offset %d (%d)", part._offset, offset) 85 | } 86 | 87 | if part._offset < part.Offset { 88 | return 0, fmt.Errorf("invalid offset %d (%d)", part._offset, offset) 89 | } 90 | 91 | return part._offset, nil 92 | } 93 | 94 | func (part *Partition) ReadAll(fullPath string) (buf []byte, err error) { 95 | fullPath = strings.TrimPrefix(fullPath, "/") 96 | path := strings.Split(fullPath, "/") 97 | 98 | bgd, err := part.getBlockGroupDescriptor(ext4.InodeRootDirectory) 99 | 100 | if err != nil { 101 | return 102 | } 103 | 104 | dw, err := ext4.NewDirectoryWalk(part, bgd, ext4.InodeRootDirectory) 105 | 106 | var i int 107 | var inodeNumber int 108 | 109 | for { 110 | if err != nil { 111 | return 112 | } 113 | 114 | p, de, err := dw.Next() 115 | 116 | if err == io.EOF { 117 | break 118 | } else if err != nil { 119 | return nil, err 120 | } 121 | 122 | deInode := int(de.Data().Inode) 123 | 124 | bgd, err = part.getBlockGroupDescriptor(deInode) 125 | 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | if p == path[i] { 131 | if i == len(path)-1 { 132 | inodeNumber = deInode 133 | break 134 | } else { 135 | dw, err = ext4.NewDirectoryWalk(part, bgd, deInode) 136 | 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | i += 1 142 | } 143 | } 144 | } 145 | 146 | if inodeNumber == 0 { 147 | return nil, errors.New("file not found") 148 | } 149 | 150 | inode, err := ext4.NewInodeWithReadSeeker(bgd, part, inodeNumber) 151 | 152 | if err != nil { 153 | return 154 | } 155 | 156 | en := ext4.NewExtentNavigatorWithReadSeeker(part, inode) 157 | r := ext4.NewInodeReader(en) 158 | 159 | return io.ReadAll(r) 160 | } 161 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update -y 4 | RUN apt-get dist-upgrade -y 5 | 6 | # create user "builder" with sudo privileges 7 | ARG GID 8 | ARG UID 9 | ARG USER=builder 10 | RUN groupadd --gid ${GID} $USER 11 | RUN useradd --uid ${UID} --gid $USER --shell /bin/bash --home-dir /home/$USER --create-home $USER 12 | RUN apt-get install -y sudo 13 | RUN echo "builder ALL=(ALL) NOPASSWD: ALL" | tee -a /etc/sudoers 14 | 15 | ENV TZ=Europe/Rome 16 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 17 | 18 | RUN apt-get install -y cmake gcc gcc-arm-none-eabi gcc-mingw-w64 git gzip \ 19 | libsodium-dev libusb-1.0-0 make pkg-config protobuf-compiler \ 20 | u-boot-tools vim wget 21 | 22 | # install tamago-go 23 | ENV TAMAGO_VERSION="1.20.4" 24 | ENV TAMAGO_CHECKSUM="b04288f094a716a9552d265028ca4a3a2c610e78d868b3fc67a7b38ffee2b30b" 25 | RUN wget -O tamago-go.tgz https://github.com/usbarmory/tamago-go/releases/download/tamago-go${TAMAGO_VERSION}/tamago-go${TAMAGO_VERSION}.linux-amd64.tar.gz 26 | RUN echo "${TAMAGO_CHECKSUM} tamago-go.tgz" | sha256sum --strict --check - 27 | RUN tar -C / -xzf tamago-go.tgz && rm tamago-go.tgz 28 | 29 | # install minisign 30 | ENV MINISIGN_VERSION="0.10" 31 | ENV MINISIGN_CHECKSUM="9fe40c2bd899a91f6b62a6ff3d469ece670f155307df50c2482ddd31337ab6da" 32 | RUN wget -O minisign.tgz https://github.com/jedisct1/minisign/archive/refs/tags/${MINISIGN_VERSION}.tar.gz 33 | RUN echo "${MINISIGN_CHECKSUM} minisign.tgz" | sha256sum --strict --check - 34 | RUN tar -xzf minisign.tgz 35 | RUN cd minisign-${MINISIGN_VERSION}; mkdir build; cd build; cmake ..; make; make install 36 | RUN rm -r minisign* 37 | 38 | # install habtool 39 | ENV HABTOOL_VERSION="v2022.05.25" 40 | RUN su - $USER -c "/usr/local/tamago-go/bin/go install github.com/usbarmory/crucible/cmd/habtool@${HABTOOL_VERSION}" 41 | 42 | ENV GOPATH "/home/${USER}/go" 43 | ENV USBARMORY_GIT "/home/${USER}/usbarmory" 44 | ENV TAMAGO "/usr/local/tamago-go/bin/go" 45 | ENV PATH "${PATH}:${GOPATH}/bin:/usr/local/tamago-go/bin" 46 | 47 | USER $USER 48 | WORKDIR /build 49 | -------------------------------------------------------------------------------- /exec/boot_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package exec 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/usbarmory/tamago/dma" 12 | ) 13 | 14 | // defined in boot_amd64.s 15 | func exec(kernel uint, params uint) 16 | 17 | func boot(kernel uint, params uint, cleanup func(), _ *dma.Region) (err error) { 18 | if cleanup != nil { 19 | cleanup() 20 | } 21 | 22 | exec(kernel, params) 23 | 24 | return errors.New("boot failure") 25 | } 26 | -------------------------------------------------------------------------------- /exec/boot_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // func exec(kernel uint, params uint) 7 | TEXT ·exec(SB),$0-16 8 | // Disable interrupts 9 | CLI 10 | 11 | // When booting Linux: 12 | // - SI must hold the base address of struct boot_params 13 | MOVQ params+8(FP), SI 14 | 15 | // Jump to kernel image 16 | MOVQ kernel+0(FP), AX 17 | JMP AX 18 | -------------------------------------------------------------------------------- /exec/boot_arm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package exec 7 | 8 | import ( 9 | "errors" 10 | 11 | "github.com/usbarmory/tamago/arm" 12 | "github.com/usbarmory/tamago/dma" 13 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 14 | ) 15 | 16 | // defined in boot_arm.s 17 | func svc() 18 | func exec() 19 | 20 | // exec() parameters are passed as pointers to limit stack allocation as it's 21 | // executed on g0 22 | var ( 23 | _kernel uint32 24 | _params uint32 25 | _mmu bool 26 | ) 27 | 28 | func boot(kernel uint, params uint, cleanup func(), region *dma.Region) (err error) { 29 | table := arm.SystemVectorTable() 30 | table.Supervisor = exec 31 | 32 | imx6ul.ARM.SetVectorTable(table) 33 | 34 | _kernel = uint32(kernel) 35 | _params = uint32(params) 36 | _mmu = (region != nil) 37 | 38 | if cleanup != nil { 39 | cleanup() 40 | } 41 | 42 | if region != nil { 43 | imx6ul.ARM.SetAttribute( 44 | uint32(region.Start()), 45 | uint32(region.End()), 46 | arm.TTE_EXECUTE_NEVER, 0) 47 | } else { 48 | imx6ul.ARM.FlushDataCache() 49 | imx6ul.ARM.DisableCache() 50 | } 51 | 52 | svc() 53 | 54 | return errors.New("supervisor failure") 55 | } 56 | -------------------------------------------------------------------------------- /exec/boot_arm.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // func svc() 7 | TEXT ·svc(SB),$0 8 | SWI $0 9 | 10 | // func exec() 11 | TEXT ·exec(SB),$0 12 | MOVW ·_kernel(SB), R3 13 | MOVW ·_params(SB), R4 14 | MOVW ·_mmu(SB), R5 15 | 16 | CMP $1, R5 17 | B.EQ mmu 18 | 19 | // Disable MMU 20 | MRC 15, 0, R0, C1, C0, 0 21 | BIC $1, R0 22 | MCR 15, 0, R0, C1, C0, 0 23 | mmu: 24 | // When booting Linux: 25 | // - CPU register 0 must be 0 26 | // - CPU register 1 not required for DTB boot 27 | // - CPU register 2 must be the parameter list address 28 | MOVW $0, R0 29 | MOVW R4, R2 30 | 31 | // Jump to kernel image 32 | B (R3) 33 | -------------------------------------------------------------------------------- /exec/boot_riscv64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | package exec 6 | 7 | import ( 8 | "github.com/usbarmory/tamago/dma" 9 | ) 10 | 11 | func boot(kernel uint, params uint, cleanup func(), _ *dma.Region) (err error) { 12 | panic("unimplemented") 13 | } 14 | -------------------------------------------------------------------------------- /exec/elf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package exec 7 | 8 | import ( 9 | "bytes" 10 | "debug/elf" 11 | "errors" 12 | "fmt" 13 | 14 | "github.com/usbarmory/tamago/dma" 15 | ) 16 | 17 | // ELFImage represents a bootable bare-metal ELF image. 18 | type ELFImage struct { 19 | // Region is the memory area for image loading. 20 | Region *dma.Region 21 | // ELF is a bootable bare-metal ELF image. 22 | ELF []byte 23 | 24 | entry uint 25 | } 26 | 27 | // Load loads a bare-metal ELF image in memory. 28 | // 29 | // The ELF loader is _very_ simple, suitable for loading unikernels like those 30 | // produced by TamaGo. 31 | func (image *ELFImage) Load() (err error) { 32 | if image.Region == nil { 33 | return errors.New("image memory Region must be assigned") 34 | } 35 | 36 | f, err := elf.NewFile(bytes.NewReader(image.ELF)) 37 | 38 | if err != nil { 39 | return 40 | } 41 | 42 | for idx, prg := range f.Progs { 43 | if prg.Type != elf.PT_LOAD { 44 | continue 45 | } 46 | 47 | b := make([]byte, prg.Memsz) 48 | 49 | if _, err := prg.ReadAt(b[0:prg.Filesz], 0); err != nil { 50 | return fmt.Errorf("failed to read LOAD section at idx %d, %q", idx, err) 51 | } 52 | 53 | if uint(prg.Paddr) < image.Region.Start() { 54 | return fmt.Errorf("incompatible memory layout (paddr:%x)", prg.Paddr) 55 | } 56 | 57 | off := uint(prg.Paddr) - image.Region.Start() 58 | 59 | if off > image.Region.Size() { 60 | return fmt.Errorf("incompatible memory layout (paddr:%x off:%x)", prg.Paddr, off) 61 | } 62 | 63 | image.Region.Write(image.Region.Start(), int(off), b) 64 | } 65 | 66 | image.entry = uint(f.Entry) 67 | 68 | return 69 | } 70 | 71 | // Entry returns the image entry address. 72 | func (image *ELFImage) Entry() uint { 73 | return image.entry 74 | } 75 | 76 | // Boot calls a loaded bare-metal ELF image. 77 | func (image *ELFImage) Boot(cleanup func()) (err error) { 78 | if image.entry == 0 { 79 | return errors.New("Load() kernel before Boot()") 80 | } 81 | 82 | return boot(image.entry, 0, cleanup, image.Region) 83 | } 84 | -------------------------------------------------------------------------------- /exec/exec.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package exec provides support for kernel image loading and booting in bare 7 | // metal Go applications. 8 | // 9 | // This package is only meant to be used with `GOOS=tamagom` 10 | // as supported by the TamaGo framework for bare metal Go, see 11 | // https://github.com/usbarmory/tamago. 12 | package exec 13 | 14 | // BootImage represents a bootable image. 15 | type BootImage interface { 16 | Load() error 17 | Entry() uint 18 | Boot(cleanup func()) error 19 | } 20 | -------------------------------------------------------------------------------- /exec/linux_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package exec 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | "fmt" 13 | 14 | "github.com/usbarmory/tamago/dma" 15 | 16 | "github.com/u-root/u-root/pkg/boot/bzimage" 17 | ) 18 | 19 | // Linux kernel information 20 | // https://docs.kernel.org/arch/x86/boot.html 21 | const ( 22 | MinProtocolVersion = 0x0205 // https://docs.kernel.org/arch/x86/boot.html 23 | ) 24 | 25 | // Zero page offsets 26 | // https://docs.kernel.org/arch/x86/zero-page.html 27 | const ( 28 | screenInfoOffset = 0x00 29 | efiInfoOffset = 0x1c0 30 | ) 31 | 32 | // Screen information 33 | // https://github.com/torvalds/linux/blob/master/include/uapi/linux/screen_info.h 34 | const ( 35 | VideoTypeEFI = 0x70 36 | Video64BitBase = 1 << 1 37 | ) 38 | 39 | // EFI Information (efi_info) signatures 40 | var ( 41 | EFI64LoaderSignature = [4]byte{0x45, 0x4c, 0x36, 0x34} // "EL64" 42 | EFI32LoaderSignature = [4]byte{0x45, 0x4c, 0x33, 0x32} // "EL32" 43 | ) 44 | 45 | // EFI represents the Linux Zero Page `efi_info` structure. 46 | type EFI struct { 47 | LoaderSignature [4]byte 48 | SystemTable uint32 49 | MemoryDescSize uint32 50 | MemoryDescVersion uint32 51 | MemoryMap uint32 52 | MemoryMapSize uint32 53 | SystemTableHigh uint32 54 | MemoryMapHigh uint32 55 | } 56 | 57 | // MarshalBinary implements the [encoding.BinaryMarshaler] interface. 58 | func (d *EFI) MarshalBinary() (data []byte, err error) { 59 | buf := new(bytes.Buffer) 60 | err = binary.Write(buf, binary.LittleEndian, d) 61 | return buf.Bytes(), nil 62 | } 63 | 64 | // Screen represents the Linux Zero Page `screen_info` structure. 65 | type Screen struct { 66 | OrigX uint8 67 | OrigY uint8 68 | ExtMemK uint16 69 | OrigVideoPage uint16 70 | OrigVideoMode uint8 71 | OrigVideoCols uint8 72 | _ uint16 73 | OrigVideoeGabx uint16 74 | _ uint16 75 | OrigVideoLines uint8 76 | OrigVideoIsVGA uint8 77 | OrigVideoPoints uint16 78 | LfbWidth uint16 79 | LfbHeight uint16 80 | LfbDepth uint16 81 | LfbBase uint32 82 | LfbSize uint32 83 | CLMagic uint16 84 | CLOffset uint16 85 | LfbLineLength uint16 86 | RedSize uint8 87 | RedPos uint8 88 | GreenSize uint8 89 | GreenPos uint8 90 | BlueSize uint8 91 | BluePos uint8 92 | RsvdSize uint8 93 | RsvdPos uint8 94 | VesapmSeg uint16 95 | VesapmOff uint16 96 | Pages uint16 97 | VesaAttributes uint16 98 | Capabilities uint32 99 | ExtLfbBase uint32 100 | _ [2]uint8 101 | } 102 | 103 | // MarshalBinary implements the [encoding.BinaryMarshaler] interface. 104 | func (d *Screen) MarshalBinary() (data []byte, err error) { 105 | buf := new(bytes.Buffer) 106 | err = binary.Write(buf, binary.LittleEndian, d) 107 | return buf.Bytes(), nil 108 | } 109 | 110 | // LinuxImage represents a bootable Linux kernel image. 111 | type LinuxImage struct { 112 | // Region is the memory area for image loading. 113 | Region *dma.Region 114 | // Memory is the system memory map 115 | Memory []bzimage.E820Entry 116 | 117 | // Kernel is the Linux kernel image. 118 | Kernel []byte 119 | // KernelOffset is the Linux kernel offset from RAM start address. 120 | KernelOffset int 121 | 122 | // BzImage is the kernel extracted by by Parse() or Load(). 123 | BzImage *bzimage.BzImage 124 | 125 | // InitialRamDisk is the Linux kernel initrd file. 126 | InitialRamDisk []byte 127 | // InitialRamDiskOffset is the initrd offset from RAM start address. 128 | InitialRamDiskOffset int 129 | 130 | // CmdLine is the Linux kernel command line arguments. 131 | CmdLine string 132 | // CmdLineOffset is the command line offset from RAM start address. 133 | CmdLineOffset int 134 | 135 | // ParamsOffset is the boot parameters offset from RAM start address. 136 | ParamsOffset int 137 | 138 | // EFI is the boot parameters EFI information. 139 | EFI *EFI 140 | // Screen is the boot parameters frame buffer information. 141 | Screen *Screen 142 | 143 | // DMA pointers 144 | entry uint 145 | params uint 146 | } 147 | 148 | // https://docs.kernel.org/arch/x86/zero-page.html 149 | func (image *LinuxImage) buildBootParams() (err error) { 150 | var buf []byte 151 | 152 | start := image.Region.Start() 153 | 154 | params := &bzimage.LinuxParams{ 155 | MountRootReadonly: 0x01, 156 | LoaderType: 0xff, 157 | } 158 | 159 | if n := len(image.CmdLine); n > 0 { 160 | params.CLPtr = uint32(start) + uint32(image.CmdLineOffset) 161 | params.CmdLineSize = uint32(n) 162 | 163 | if params.CmdLineSize > image.BzImage.Header.CmdLineSize { 164 | return errors.New("incompatible command line length") 165 | } 166 | 167 | image.Region.Write(start, image.CmdLineOffset, []byte(image.CmdLine + "\x00")) 168 | } 169 | 170 | if len(image.Memory) > bzimage.E820Max { 171 | return errors.New("image Memory is invalid") 172 | } 173 | 174 | for i, entry := range image.Memory { 175 | params.E820Map[i] = entry 176 | params.E820MapNr += 1 177 | } 178 | 179 | if n := len(image.InitialRamDisk); n > 0 { 180 | params.Initrdstart = uint32(start) + uint32(image.InitialRamDiskOffset) 181 | params.Initrdsize = uint32(n) 182 | 183 | if params.Initrdstart > image.BzImage.Header.InitrdAddrMax { 184 | return errors.New("incompatible initrd addres") 185 | } 186 | 187 | image.Region.Write(start, image.InitialRamDiskOffset, image.InitialRamDisk) 188 | } 189 | 190 | if buf, err = params.MarshalBinary(); err != nil { 191 | return 192 | } 193 | 194 | image.Region.Write(start, image.ParamsOffset, buf) 195 | 196 | if image.EFI != nil { 197 | if buf, err = image.EFI.MarshalBinary(); err != nil { 198 | return 199 | } 200 | 201 | image.Region.Write(start, image.ParamsOffset+efiInfoOffset, buf) 202 | } 203 | 204 | if image.Screen != nil { 205 | if buf, err = image.Screen.MarshalBinary(); err != nil { 206 | return 207 | } 208 | 209 | image.Region.Write(start, image.ParamsOffset+screenInfoOffset, buf) 210 | } 211 | 212 | return 213 | } 214 | 215 | func (image *LinuxImage) Parse() (err error) { 216 | if image.BzImage != nil { 217 | return 218 | } 219 | 220 | image.BzImage = &bzimage.BzImage{} 221 | return image.BzImage.UnmarshalBinary(image.Kernel) 222 | } 223 | 224 | // Load loads a Linux kernel image in memory. 225 | func (image *LinuxImage) Load() (err error) { 226 | if err = image.Parse(); err != nil { 227 | return 228 | } 229 | 230 | bzImage := image.BzImage 231 | 232 | if image.Region == nil { 233 | return errors.New("image memory Region must be assigned") 234 | } 235 | 236 | if image.Region.Start() >= (1 << 32) { 237 | return errors.New("image memory Region above 4G not yet supported") 238 | } 239 | 240 | if bzImage.Header.Protocolversion < MinProtocolVersion { 241 | return fmt.Errorf("unsupported boot protocol (%v)", bzImage.Header.Protocolversion) 242 | } 243 | 244 | if bzImage.Header.RelocatableKernel == 0 { 245 | return errors.New("kernel must be relocatable") 246 | } 247 | 248 | kelf, err := bzImage.ELF() 249 | 250 | if err != nil { 251 | return 252 | } 253 | 254 | for _, p := range kelf.Progs { 255 | buf := make([]byte, p.Filesz) 256 | p.ReadAt(buf, 0) 257 | 258 | image.Region.Write(image.Region.Start(), image.KernelOffset+int(p.Paddr), buf) 259 | } 260 | 261 | if err = image.buildBootParams(); err != nil { 262 | return 263 | } 264 | 265 | image.entry = image.Region.Start() + uint(image.KernelOffset) + uint(kelf.Entry) 266 | image.params = image.Region.Start() + uint(image.ParamsOffset) 267 | 268 | return 269 | } 270 | 271 | // Entry returns the image entry address. 272 | func (image *LinuxImage) Entry() uint { 273 | return image.entry 274 | } 275 | 276 | // Boot calls a loaded Linux kernel image. 277 | func (image *LinuxImage) Boot(cleanup func()) (err error) { 278 | if image.params == 0 { 279 | return errors.New("Load() kernel before Boot()") 280 | } 281 | 282 | return boot(image.entry, image.params, cleanup, nil) 283 | } 284 | -------------------------------------------------------------------------------- /exec/linux_arm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package exec 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | "fmt" 13 | 14 | "github.com/usbarmory/tamago/dma" 15 | 16 | "github.com/u-root/u-root/pkg/dt" 17 | ) 18 | 19 | // LinuxImage represents a bootable Linux kernel image. 20 | type LinuxImage struct { 21 | // Region is the memory area for image loading. 22 | Region *dma.Region 23 | 24 | // Kernel is the Linux kernel image. 25 | Kernel []byte 26 | // KernelOffset is the Linux kernel offset from RAM start address. 27 | KernelOffset int 28 | 29 | // DeviceTreeBlob is the Linux kernel dtb file. 30 | DeviceTreeBlob []byte 31 | // DeviceTreeBlobOffset is the dtb offset from RAM start address. 32 | DeviceTreeBlobOffset int 33 | 34 | // InitialRamDisk is the Linux kernel initrd file. 35 | InitialRamDisk []byte 36 | // InitialRamDiskOffset is the initrd offset from RAM start address. 37 | InitialRamDiskOffset int 38 | 39 | // CmdLine is the Linux kernel command line arguments. 40 | CmdLine string 41 | 42 | // DMA pointers 43 | entry uint 44 | dtb uint 45 | } 46 | 47 | func (image *LinuxImage) fdt() (fdt *dt.FDT, err error) { 48 | return dt.ReadFDT(bytes.NewReader(image.DeviceTreeBlob)) 49 | } 50 | 51 | func (image *LinuxImage) updateDTB(fdt *dt.FDT) (err error) { 52 | dtbBuf := new(bytes.Buffer) 53 | _, err = fdt.Write(dtbBuf) 54 | 55 | if err != nil { 56 | return 57 | } 58 | 59 | image.DeviceTreeBlob = dtbBuf.Bytes() 60 | 61 | return 62 | } 63 | 64 | func (image *LinuxImage) fixupBootArgs() (err error) { 65 | fdt, err := image.fdt() 66 | 67 | if err != nil { 68 | return 69 | } 70 | 71 | for _, node := range fdt.RootNode.Children { 72 | if node.Name == "chosen" { 73 | bootargs := dt.Property{ 74 | Name: "bootargs", 75 | Value: []byte(image.CmdLine + "\x00"), 76 | } 77 | 78 | node.Properties = append(node.Properties, bootargs) 79 | } 80 | } 81 | 82 | return image.updateDTB(fdt) 83 | } 84 | 85 | func (image *LinuxImage) fixupInitrd(addr uint) (err error) { 86 | fdt, err := image.fdt() 87 | 88 | if err != nil { 89 | return 90 | } 91 | 92 | start := addr + uint(image.InitialRamDiskOffset) 93 | end := start + uint(len(image.InitialRamDisk)) 94 | 95 | for _, node := range fdt.RootNode.Children { 96 | if node.Name == "chosen" { 97 | initrdStart := dt.Property{ 98 | Name: "linux,initrd-start", 99 | Value: make([]byte, 8), 100 | } 101 | 102 | initrdEnd := dt.Property{ 103 | Name: "linux,initrd-end", 104 | Value: make([]byte, 8), 105 | } 106 | 107 | binary.BigEndian.PutUint64(initrdStart.Value, uint64(start)) 108 | binary.BigEndian.PutUint64(initrdEnd.Value, uint64(end)) 109 | 110 | node.Properties = append(node.Properties, initrdStart) 111 | node.Properties = append(node.Properties, initrdEnd) 112 | } 113 | } 114 | 115 | return image.updateDTB(fdt) 116 | } 117 | 118 | // Load loads a Linux kernel image in memory. 119 | func (image *LinuxImage) Load() (err error) { 120 | if image.Region == nil { 121 | return errors.New("image memory Region must be assigned") 122 | } 123 | 124 | if len(image.CmdLine) > 0 { 125 | if len(image.DeviceTreeBlob) == 0 { 126 | return errors.New("cmdline requires dtb") 127 | } 128 | 129 | if err = image.fixupBootArgs(); err != nil { 130 | return fmt.Errorf("cmdline dtb fixup error, %v", err) 131 | } 132 | } 133 | 134 | if len(image.InitialRamDisk) > 0 { 135 | if len(image.DeviceTreeBlob) == 0 { 136 | return errors.New("initrd requires dtb") 137 | } 138 | 139 | if err = image.fixupInitrd(image.Region.Start()); err != nil { 140 | return fmt.Errorf("initrd dtb fixup error, %v", err) 141 | } 142 | 143 | image.Region.Write(image.Region.Start(), image.InitialRamDiskOffset, image.InitialRamDisk) 144 | } 145 | 146 | image.Region.Write(image.Region.Start(), image.KernelOffset, image.Kernel) 147 | image.Region.Write(image.Region.Start(), image.DeviceTreeBlobOffset, image.DeviceTreeBlob) 148 | 149 | image.entry = image.Region.Start() + uint(image.KernelOffset) 150 | image.dtb = image.Region.Start() + uint(image.DeviceTreeBlobOffset) 151 | 152 | return 153 | } 154 | 155 | // Entry returns the image entry address. 156 | func (image *LinuxImage) Entry() uint { 157 | return image.entry 158 | } 159 | 160 | // DTB returns the image DTB address. 161 | func (image *LinuxImage) DTB() uint { 162 | return image.dtb 163 | } 164 | 165 | // Boot calls a loaded Linux kernel image. 166 | func (image *LinuxImage) Boot(cleanup func()) (err error) { 167 | if image.entry == 0 { 168 | return errors.New("Load() kernel before Boot()") 169 | } 170 | 171 | return boot(image.entry, image.dtb, cleanup, nil) 172 | } 173 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/usbarmory/armory-boot 2 | 3 | go 1.23.6 4 | 5 | require ( 6 | github.com/dsoprea/go-ext4 v0.0.0-20190528173430-c13b09fc0ff8 7 | github.com/u-root/u-root v0.14.0 8 | github.com/usbarmory/hid v0.0.0-20210318233634-85ced88a1ffe 9 | github.com/usbarmory/tamago v0.0.0-20250202174412-884bbd839bf9 10 | golang.org/x/crypto v0.32.0 11 | ) 12 | 13 | require ( 14 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect 15 | github.com/dustin/go-humanize v1.0.1 // indirect 16 | github.com/go-errors/errors v1.0.2 // indirect 17 | github.com/klauspost/compress v1.17.4 // indirect 18 | github.com/pierrec/lz4/v4 v4.1.14 // indirect 19 | github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect 20 | github.com/ulikunitz/xz v0.5.11 // indirect 21 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect 22 | golang.org/x/net v0.21.0 // indirect 23 | golang.org/x/sys v0.29.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dsoprea/go-ext4 v0.0.0-20190528173430-c13b09fc0ff8 h1:e3CYZInWqO0a3MWfD0WW/11Ki0qo3Fc1ZAHx0+whlhY= 2 | github.com/dsoprea/go-ext4 v0.0.0-20190528173430-c13b09fc0ff8/go.mod h1:UBig4B62vBWtudYo4RJPwdV5Lqo+oeh7AtSCmRIkRPc= 3 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= 4 | github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= 5 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 6 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 7 | github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4= 8 | github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= 9 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 10 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 11 | github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= 12 | github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 13 | github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg= 14 | github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE= 15 | github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og= 16 | github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= 17 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 18 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 19 | github.com/usbarmory/hid v0.0.0-20210318233634-85ced88a1ffe h1:WWop+y0QNxBEZAKu9xlNiZWgo5+y5VkzlyngVMViBlE= 20 | github.com/usbarmory/hid v0.0.0-20210318233634-85ced88a1ffe/go.mod h1:8ScVLh9aty8MLtypACxU7JdF5u2y2rEfhx3zJPS/tPc= 21 | github.com/usbarmory/tamago v0.0.0-20250202174412-884bbd839bf9 h1:j9AuTkyeUO4JSgY8v6kVIyHZsjObNNYn+LUNiPi84Kk= 22 | github.com/usbarmory/tamago v0.0.0-20250202174412-884bbd839bf9/go.mod h1:NL88q9ZsIPYFzXaosAeKgu1Kr5i1k4Rau3wnbNBL5bY= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 25 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 26 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= 27 | golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 28 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 29 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 30 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 31 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 32 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 33 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 34 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 35 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | 12 | "github.com/usbarmory/armory-boot/config" 13 | "github.com/usbarmory/armory-boot/disk" 14 | "github.com/usbarmory/armory-boot/exec" 15 | 16 | usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 17 | "github.com/usbarmory/tamago/soc/nxp/imx6ul" 18 | "github.com/usbarmory/tamago/soc/nxp/usdhc" 19 | ) 20 | 21 | func init() { 22 | log.SetFlags(0) 23 | 24 | switch imx6ul.Model() { 25 | case "i.MX6ULL", "i.MX6ULZ": 26 | imx6ul.SetARMFreq(imx6ul.FreqMax) 27 | case "i.MX6UL": 28 | imx6ul.SetARMFreq(imx6ul.Freq528) 29 | } 30 | } 31 | 32 | func preLaunch() { 33 | usbarmory.LED("blue", false) 34 | usbarmory.LED("white", false) 35 | } 36 | 37 | func main() { 38 | var card *usdhc.USDHC 39 | 40 | usbarmory.LED("blue", false) 41 | usbarmory.LED("white", false) 42 | 43 | switch Boot { 44 | case "eMMC": 45 | card = usbarmory.MMC 46 | case "uSD": 47 | card = usbarmory.SD 48 | default: 49 | panic("invalid boot parameter") 50 | } 51 | 52 | part, err := disk.Detect(card, Start) 53 | 54 | if err != nil { 55 | panic(fmt.Sprintf("boot media error, %v\n", err)) 56 | } 57 | 58 | usbarmory.LED("blue", true) 59 | 60 | if len(PublicKeyStr) == 0 { 61 | log.Printf("armory-boot: no public key, skipping signature verification") 62 | } 63 | 64 | conf, err := config.Load(part, config.DefaultConfigPath, config.DefaultSignaturePath, PublicKeyStr) 65 | 66 | if err != nil { 67 | panic(fmt.Sprintf("configuration error, %v\n", err)) 68 | } 69 | 70 | log.Printf("\n%s", conf.JSON) 71 | 72 | usbarmory.LED("white", true) 73 | 74 | var image exec.BootImage 75 | 76 | if conf.ELF { 77 | image = &exec.ELFImage{ 78 | Region: mem, 79 | ELF: conf.Kernel(), 80 | } 81 | } else { 82 | image = &exec.LinuxImage{ 83 | Region: mem, 84 | Kernel: conf.Kernel(), 85 | DeviceTreeBlob: conf.DeviceTreeBlob(), 86 | InitialRamDisk: conf.InitialRamDisk(), 87 | KernelOffset: kernelOffset, 88 | DeviceTreeBlobOffset: paramsOffset, 89 | InitialRamDiskOffset: initrdOffset, 90 | CmdLine: conf.CmdLine, 91 | } 92 | } 93 | 94 | if err = image.Load(); err != nil { 95 | panic(fmt.Sprintf("load error, %v\n", err)) 96 | } 97 | 98 | log.Printf("armory-boot: starting kernel@%.8x\n", image.Entry()) 99 | 100 | if err = image.Boot(preLaunch); err != nil { 101 | panic(fmt.Sprintf("load error, %v\n", err)) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mem.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | _ "unsafe" 10 | 11 | "github.com/usbarmory/tamago/dma" 12 | ) 13 | 14 | // Override imx6ul.ramStart, usbarmory.ramSize and dma allocation, as the 15 | // mapped kernel image needs to be within the first 128MiB of RAM. 16 | 17 | //go:linkname ramStart runtime.ramStart 18 | var ramStart uint32 = 0x90000000 19 | 20 | //go:linkname ramSize runtime.ramSize 21 | var ramSize uint32 = 0x08000000 22 | 23 | // DMA region for target kernel boot 24 | var mem *dma.Region 25 | 26 | // DMA region for bootloader operation 27 | const ( 28 | dmaStart = 0x98000000 29 | dmaSize = 0x08000000 30 | ) 31 | 32 | // DMA region for target kernel boot 33 | const ( 34 | memoryStart = 0x80000000 35 | memorySize = 0x10000000 36 | 37 | kernelOffset = 0x00800000 38 | paramsOffset = 0x07000000 39 | initrdOffset = 0x08000000 40 | ) 41 | 42 | func init() { 43 | dma.Init(dmaStart, dmaSize) 44 | 45 | mem, _ = dma.NewRegion(memoryStart, memorySize, false) 46 | mem.Reserve(memorySize, 0) 47 | } 48 | -------------------------------------------------------------------------------- /sdp/imx.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | package sdp 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | ) 13 | 14 | // Program image tags 15 | // (p309, 8.7 Program image, IMX6ULLRM). 16 | const ( 17 | TagIVT = 0xd1 18 | TagDCD = 0xd2 19 | ) 20 | 21 | // DCD constants 22 | // (p312, 8.7.2 Device Configuration Data (DCD), IMX6ULLRM). 23 | const ( 24 | // write command tag 25 | WriteData = 0xcc 26 | // DCD pointer offset within IVT 27 | DCDOffset = 12 28 | // maximum DCD size 29 | DCDSize = 1768 30 | ) 31 | 32 | // DCDHeader represents a DCD header 33 | // (p312, 8.7.2 Device Configuration Data (DCD), IMX6ULLRM). 34 | type DCDHeader struct { 35 | Tag uint8 36 | Length uint16 37 | Version uint8 38 | } 39 | 40 | // IVT represents an IVT entry 41 | // (p311, 8.7.1.1 Image vector table structure, IMX6ULLRM). 42 | type IVT struct { 43 | Tag uint8 44 | Length uint16 45 | Version uint8 46 | Entry uint32 47 | _ uint32 48 | DCD uint32 49 | BootData uint32 50 | Self uint32 51 | CSF uint32 52 | _ uint32 53 | } 54 | 55 | // ParseIVT extracts the Image Vector Table (IVT) from an imx format binary 56 | // image. 57 | func ParseIVT(imx []byte) (ivt *IVT, err error) { 58 | ivt = &IVT{} 59 | 60 | if err = binary.Read(bytes.NewReader(imx), binary.LittleEndian, ivt); err != nil { 61 | return nil, err 62 | } 63 | 64 | if ivt.Tag != TagIVT { 65 | return nil, errors.New("could not find IVT tag") 66 | } 67 | 68 | return 69 | } 70 | 71 | // ParseDCD extracts the Device Configuration Data (DCD) from an imx format 72 | // binary image. 73 | func ParseDCD(imx []byte, ivt *IVT) (dcd []byte, err error) { 74 | hdr := &DCDHeader{} 75 | dcdStart := ivt.DCD - ivt.Self 76 | 77 | if len(imx) < int(dcdStart+4) { 78 | return nil, errors.New("could not parse DCD, insufficient length") 79 | } 80 | 81 | if err = binary.Read(bytes.NewReader(imx[dcdStart:dcdStart+4]), binary.BigEndian, hdr); err != nil { 82 | return 83 | } 84 | 85 | if hdr.Tag != TagDCD { 86 | return nil, errors.New("could not find DCD tag") 87 | } 88 | 89 | if hdr.Length > DCDSize || int(hdr.Length) > int(dcdStart)+len(imx) { 90 | return nil, errors.New("could not parse DCD, invalid length") 91 | } 92 | 93 | dcd = imx[dcdStart : dcdStart+uint32(hdr.Length)] 94 | 95 | return 96 | } 97 | -------------------------------------------------------------------------------- /sdp/sdp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) WithSecure Corporation 2 | // 3 | // Use of this source code is governed by the license 4 | // that can be found in the LICENSE file. 5 | 6 | // Package sdp provide helpers for implementing the Serial Download Protocol 7 | // (SDP), used on NXP i.MX System-on-Chip (SoC) application processors. 8 | package sdp 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | ) 14 | 15 | const HIDReportSize = 1024 16 | 17 | // SDP command types 18 | // (p322, 8.9.3 Serial Download Protocol (SDP), IMX6ULLRM). 19 | const ( 20 | ReadRegister = 0x0101 21 | WriteFile = 0x0404 22 | DCDWrite = 0x0a0a 23 | JumpAddress = 0x0b0b 24 | SkipDCDHeader = 0x0c0c 25 | ) 26 | 27 | // SDP represents an SDP command 28 | // (p322, 8.9.3 Serial Download Protocol (SDP), IMX6ULLRM). 29 | type SDP struct { 30 | CommandType uint16 31 | Address uint32 32 | Format uint8 33 | DataCount uint32 34 | Data uint32 35 | _ byte 36 | } 37 | 38 | // Bytes converts the SDP command structure to byte array format. 39 | func (s *SDP) Bytes() []byte { 40 | buf := new(bytes.Buffer) 41 | binary.Write(buf, binary.BigEndian, s) 42 | return buf.Bytes() 43 | } 44 | 45 | // BuildReadRegisterReport generates USB HID reports (ID 1) that implement the 46 | // SDP READ_REGISTER command for reading a single 32-bit device register value 47 | // (p323, 8.9.3.1.1 READ_REGISTER, IMX6ULLRM). 48 | func BuildReadRegisterReport(addr uint32, size uint32) (r1 []byte) { 49 | sdp := &SDP{ 50 | CommandType: ReadRegister, 51 | Address: addr, 52 | Format: 0x20, // 32-bit access 53 | DataCount: size, 54 | } 55 | 56 | return sdp.Bytes() 57 | } 58 | 59 | // BuildDCDWriteReport generates USB HID reports (IDs 1 and 2) that implement 60 | // the SDP DCD_WRITE command sequence, used to load a DCD binary payload 61 | // (p327, 8.9.3.1.5 DCD_WRITE, IMX6ULLRM). 62 | func BuildDCDWriteReport(dcd []byte, addr uint32) (r1 []byte, r2 []byte) { 63 | sdp := &SDP{ 64 | CommandType: DCDWrite, 65 | Address: addr, 66 | DataCount: uint32(len(dcd)), 67 | } 68 | 69 | return sdp.Bytes(), dcd 70 | } 71 | 72 | // BuildFileWriteReport generates USB HID reports (IDs1 and 2) that implement 73 | // the SDP FILE_WRITE command sequence, used to load an imx binary payload 74 | // (p325, 8.9.3.1.3 FILE_WRITE, IMX6ULLRM). 75 | func BuildFileWriteReport(imx []byte, addr uint32) (r1 []byte, r2 [][]byte) { 76 | sdp := &SDP{ 77 | CommandType: WriteFile, 78 | Address: addr, 79 | DataCount: uint32(len(imx)), 80 | } 81 | 82 | // make a copy to leave input slice untouched 83 | imx = append(imx[:0:0], imx...) 84 | 85 | // DCD pointer must be cleared 86 | binary.LittleEndian.PutUint32(imx[DCDOffset:], 0) 87 | 88 | for j := 0; j < len(imx); j += HIDReportSize { 89 | k := j + HIDReportSize 90 | 91 | if k > len(imx) { 92 | k = len(imx) 93 | } 94 | 95 | r2 = append(r2, imx[j:k]) 96 | } 97 | 98 | return sdp.Bytes(), r2 99 | } 100 | 101 | // BuildJumpAddressReport generates the USB HID report (ID 1) that implements 102 | // the SDP JUMP_ADDRESS command, used to execute an imx binary payload 103 | // (p328, 8.9.3.1.7 JUMP_ADDRESS, IMX6ULLRM). 104 | func BuildJumpAddressReport(addr uint32) (r1 []byte) { 105 | sdp := &SDP{ 106 | CommandType: JumpAddress, 107 | Address: addr, 108 | } 109 | 110 | return sdp.Bytes() 111 | } 112 | --------------------------------------------------------------------------------