Skip to content

Commit f56a0c7

Browse files
committed
add rp2350 boot patching
1 parent c341b1b commit f56a0c7

File tree

6 files changed

+79
-251
lines changed

6 files changed

+79
-251
lines changed

Diff for: builder/build.go

+32-61
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"strings"
2626

2727
"github.com/gofrs/flock"
28+
"github.com/soypat/tinyboot/boot/picobin"
2829
"github.com/tinygo-org/tinygo/compileopts"
2930
"github.com/tinygo-org/tinygo/compiler"
3031
"github.com/tinygo-org/tinygo/goenv"
@@ -804,6 +805,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
804805
return fmt.Errorf("could not modify stack sizes: %w", err)
805806
}
806807
}
808+
809+
// Apply patches of bootloader in the order they appear.
810+
if len(config.Target.BootPatches) > 0 {
811+
err = applyPatches(result.Executable, config.Target.BootPatches)
812+
}
813+
807814
if config.RP2040BootPatch() {
808815
// Patch the second stage bootloader CRC into the .boot2 section
809816
err = patchRP2040BootCRC(result.Executable)
@@ -1422,6 +1429,23 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
14221429
}
14231430
}
14241431

1432+
func applyPatches(executable string, bootPatches []string) (err error) {
1433+
for _, patch := range bootPatches {
1434+
switch patch {
1435+
case "rp2040":
1436+
err = patchRP2040BootCRC(executable)
1437+
case "rp2350":
1438+
err = patchRP2350BootIMAGE_DEF(executable)
1439+
default:
1440+
err = errors.New("undefined boot patch name")
1441+
}
1442+
if err != nil {
1443+
return fmt.Errorf("apply boot patch %q: %w", patch, err)
1444+
}
1445+
}
1446+
return nil
1447+
}
1448+
14251449
// RP2040 second stage bootloader CRC32 calculation
14261450
//
14271451
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
@@ -1433,7 +1457,7 @@ func patchRP2040BootCRC(executable string) error {
14331457
}
14341458

14351459
if len(bytes) != 256 {
1436-
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
1460+
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes))
14371461
}
14381462

14391463
// From the 'official' RP2040 checksum script:
@@ -1462,70 +1486,17 @@ func patchRP2040BootCRC(executable string) error {
14621486

14631487
// RP2350 block patching.
14641488
func patchRP2350BootIMAGE_DEF(executable string) error {
1465-
const (
1466-
picobinBlockMarkerStart = 0xffffded3
1467-
picobinBlockMarkerEnd = 0xab123579
1468-
picobinBlockItem1BSImageType = 3
1469-
)
1470-
bytes, _, err := getElfSectionData(executable, ".boot2")
1489+
boot2, _, err := getElfSectionData(executable, ".boot2")
14711490
if err != nil {
14721491
return err
14731492
}
1474-
type item struct {
1475-
head uint8 // [0:1]:size_flag(0 means 1 byte size, 1 means 2 byte size) [1:7]: item type.
1476-
s0mod uint8 // s0%256
1477-
s0div uint8 // s0/256 if size_flag==1, or is type specific data for blocks that are never >256 words.
1478-
tpdata uint8 // Type specific data.
1479-
data []byte // item data.
1480-
}
1481-
type block struct {
1482-
items []item
1483-
}
1484-
makeIMAGE_DEF := func(imgType, exeSec, exeCPU, exeChip uint8, exeTBYB bool) item {
1485-
return item{
1486-
head: 0x42, s0mod: 1, s0div: 0b111&imgType | (0b11 & exeSec << 4),
1487-
tpdata: 0b111&exeCPU | ((0b111 & exeChip) << 4) | b2u8(exeTBYB)<<7,
1488-
}
1489-
}
1490-
appendItem := func(dst []byte, it item) []byte {
1491-
dst = append(dst, it.head, it.s0mod, it.s0div, it.tpdata)
1492-
return append(dst, it.data...)
1493-
}
1494-
appendBlock := func(dst []byte, blk block) []byte {
1495-
dst = binary.LittleEndian.AppendUint32(dst, picobinBlockMarkerStart)
1496-
startSize := len(dst)
1497-
for _, item := range blk.items {
1498-
dst = appendItem(dst, item)
1499-
}
1500-
itemsSize := len(dst) - startSize
1501-
// Last item append:
1502-
dst = append(dst,
1503-
0xff, // size_flag=1, item_type==blockItemLast
1504-
uint8((itemsSize/4)%256),
1505-
uint8((itemsSize/4)/256),
1506-
0, // Pad.
1507-
)
1508-
// Relative position in bytes to next block HEADER relative to this blocks HEADER.
1509-
// Use 0 to denote a loop to this same block.
1510-
dst = binary.LittleEndian.AppendUint32(dst, 0)
1511-
dst = binary.LittleEndian.AppendUint32(dst, picobinBlockMarkerEnd) // Footer.
1512-
return nil
1513-
}
1514-
boot2IMGDEF := makeIMAGE_DEF(
1515-
1, // 1:Executable; 2: Data.
1516-
2, // 0:Non-Secure; 1:Run in secure mode.
1517-
0, // 0:ARM arch; 1:RISCV arch
1518-
1, // 0:RP2040; 1:RP2350
1519-
false, // true: Image is flagged for "Try Before You Buy"
1520-
)
1521-
1522-
if len(bytes) != 256 {
1523-
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
1524-
}
1525-
_ = appendBlock
1526-
_ = boot2IMGDEF
1493+
item0 := picobin.MakeImageDef(picobin.ImageTypeExecutable, picobin.ExeSecSecure, picobin.ExeCPUARM, picobin.ExeChipRP2350, false)
1494+
newBoot := make([]byte, 256)
1495+
newBoot, _, err = picobin.AppendBlockFromItems(newBoot[:0], []picobin.Item{item0.Item}, boot2, 0)
1496+
off := len(newBoot)
1497+
newBoot, _, err = picobin.AppendFinalBlock(newBoot, -off)
15271498
// Update the .boot2 section to included the CRC
1528-
return replaceElfSection(executable, ".boot2", bytes)
1499+
return replaceElfSection(executable, ".boot2", newBoot)
15291500
}
15301501

15311502
// lock may acquire a lock at the specified path.

Diff for: builder/objcopy.go

+25-60
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package builder
22

33
import (
44
"debug/elf"
5-
"io"
5+
"fmt"
66
"os"
7-
"sort"
87

98
"github.com/marcinbor85/gohex"
9+
"github.com/soypat/tinyboot/build/elfutil"
1010
)
1111

1212
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
@@ -26,18 +26,12 @@ func (e objcopyError) Error() string {
2626
return e.Op + ": " + e.Err.Error()
2727
}
2828

29-
type progSlice []*elf.Prog
30-
31-
func (s progSlice) Len() int { return len(s) }
32-
func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
33-
func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
34-
3529
// extractROM extracts a firmware image and the first load address from the
3630
// given ELF file. It tries to emulate the behavior of objcopy.
3731
func extractROM(path string) (uint64, []byte, error) {
3832
f, err := elf.Open(path)
3933
if err != nil {
40-
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
34+
return 0, nil, objcopyError{Op: "failed to open ELF file to extract text segment", Err: err}
4135
}
4236
defer f.Close()
4337

@@ -47,62 +41,33 @@ func extractROM(path string) (uint64, []byte, error) {
4741
// > memory dump of the contents of the input object file. All symbols and
4842
// > relocation information will be discarded. The memory dump will start at
4943
// > the load address of the lowest section copied into the output file.
50-
51-
// Find the lowest section address.
52-
startAddr := ^uint64(0)
53-
for _, section := range f.Sections {
54-
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
55-
continue
56-
}
57-
if section.Addr < startAddr {
58-
startAddr = section.Addr
59-
}
44+
start, end, err := elfutil.ROMAddr(f)
45+
if err != nil {
46+
return 0, nil, objcopyError{Op: "failed to calculate ELF ROM addresses", Err: err}
6047
}
61-
62-
progs := make(progSlice, 0, 2)
63-
for _, prog := range f.Progs {
64-
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
65-
continue
66-
}
67-
progs = append(progs, prog)
48+
err = elfutil.EnsureROMContiguous(f, start, end, maxPadBytes)
49+
if err != nil {
50+
return 0, nil, objcopyError{Op: "checking if ELF ROM contiguous", Err: err}
6851
}
69-
if len(progs) == 0 {
70-
return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil}
52+
const (
53+
_ = 1 << (iota * 10)
54+
kB
55+
MB
56+
GB
57+
)
58+
const maxSize = 1 * GB
59+
if end-start > maxSize {
60+
return 0, nil, objcopyError{Op: fmt.Sprintf("obj size exceeds max %d/%d, bad ELF address calculation?", end-start, maxSize)}
7161
}
72-
sort.Sort(progs)
7362

74-
var rom []byte
75-
for _, prog := range progs {
76-
romEnd := progs[0].Paddr + uint64(len(rom))
77-
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
78-
// Sometimes, the linker seems to insert a bit of padding between
79-
// segments. Simply zero-fill these parts.
80-
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
81-
}
82-
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
83-
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
84-
if diff > maxPadBytes {
85-
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
86-
}
87-
// Pad the difference
88-
rom = append(rom, make([]byte, diff)...)
89-
}
90-
data, err := io.ReadAll(prog.Open())
91-
if err != nil {
92-
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
93-
}
94-
rom = append(rom, data...)
95-
}
96-
if progs[0].Paddr < startAddr {
97-
// The lowest memory address is before the first section. This means
98-
// that there is some extra data loaded at the start of the image that
99-
// should be discarded.
100-
// Example: ELF files where .text doesn't start at address 0 because
101-
// there is a bootloader at the start.
102-
return startAddr, rom[startAddr-progs[0].Paddr:], nil
103-
} else {
104-
return progs[0].Paddr, rom, nil
63+
ROM := make([]byte, end-start)
64+
n, err := elfutil.ReadROMAt(f, ROM, start)
65+
if err != nil {
66+
return 0, nil, objcopyError{Op: "reading ELF ROM", Err: err}
67+
} else if n != len(ROM) {
68+
return 0, nil, objcopyError{Op: "short ELF ROM read"}
10569
}
70+
return start, ROM, nil
10671
}
10772

10873
// objcopy converts an ELF file to a different (simpler) output file format:

0 commit comments

Comments
 (0)