binary-format supports uf2, nrf-dfu
This commit is contained in:
2
go.mod
2
go.mod
@@ -15,6 +15,8 @@ require (
|
|||||||
golang.org/x/tools v0.36.0
|
golang.org/x/tools v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/mod v0.27.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -12,6 +12,8 @@ github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94=
|
|||||||
github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50=
|
github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50=
|
||||||
github.com/qiniu/x v1.15.1 h1:avE+YQaowp8ZExjylOeSM73rUo3MQKBAYVxh4NJ8dY8=
|
github.com/qiniu/x v1.15.1 h1:avE+YQaowp8ZExjylOeSM73rUo3MQKBAYVxh4NJ8dY8=
|
||||||
github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
|
github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
|
||||||
|
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I=
|
||||||
|
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
|||||||
@@ -760,7 +760,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
|||||||
|
|
||||||
if orgApp != app {
|
if orgApp != app {
|
||||||
fmt.Printf("cross compile: %#v\n", ctx.crossCompile)
|
fmt.Printf("cross compile: %#v\n", ctx.crossCompile)
|
||||||
err = firmware.MakeFirmwareImage(orgApp, app, ctx.crossCompile.BinaryFormat)
|
err = firmware.MakeFirmwareImage(orgApp, app, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
||||||
check(err)
|
check(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Export struct {
|
|||||||
ClangBinPath string // Path to clang binary directory
|
ClangBinPath string // Path to clang binary directory
|
||||||
|
|
||||||
BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2")
|
BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2")
|
||||||
|
FormatDetail string // For uf2, it's uf2FamilyID
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -462,6 +463,7 @@ func useTarget(targetName string) (export Export, err error) {
|
|||||||
export.GOARCH = config.GOARCH
|
export.GOARCH = config.GOARCH
|
||||||
export.ExtraFiles = config.ExtraFiles
|
export.ExtraFiles = config.ExtraFiles
|
||||||
export.BinaryFormat = config.BinaryFormat
|
export.BinaryFormat = config.BinaryFormat
|
||||||
|
export.FormatDetail = config.FormatDetail()
|
||||||
|
|
||||||
// Build environment map for template variable expansion
|
// Build environment map for template variable expansion
|
||||||
envs := buildEnvMap(env.LLGoROOT())
|
envs := buildEnvMap(env.LLGoROOT())
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// From tinygo/builder/esp.go
|
||||||
|
|
||||||
package firmware
|
package firmware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,8 +13,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// From tinygo/builder/esp.go
|
|
||||||
|
|
||||||
type espImageSegment struct {
|
type espImageSegment struct {
|
||||||
addr uint32
|
addr uint32
|
||||||
data []byte
|
data []byte
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import "strings"
|
|||||||
func BinaryExt(binaryFormat string) string {
|
func BinaryExt(binaryFormat string) string {
|
||||||
if strings.HasPrefix(binaryFormat, "esp") {
|
if strings.HasPrefix(binaryFormat, "esp") {
|
||||||
return ".bin"
|
return ".bin"
|
||||||
|
} else if strings.HasPrefix(binaryFormat, "uf2") {
|
||||||
|
return ".uf2"
|
||||||
|
} else if strings.HasPrefix(binaryFormat, "nrf-dfu") {
|
||||||
|
return ".zip"
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ func TestBinaryExt(t *testing.T) {
|
|||||||
{"ESP32", "esp32", ".bin"},
|
{"ESP32", "esp32", ".bin"},
|
||||||
{"ESP8266", "esp8266", ".bin"},
|
{"ESP8266", "esp8266", ".bin"},
|
||||||
{"ESP32C3", "esp32c3", ".bin"},
|
{"ESP32C3", "esp32c3", ".bin"},
|
||||||
{"UF2", "uf2", ""},
|
{"UF2", "uf2", ".uf2"},
|
||||||
{"ELF", "elf", ""},
|
{"ELF", "elf", ""},
|
||||||
{"Empty", "", ""},
|
{"Empty", "", ""},
|
||||||
{"NRF-DFU", "nrf-dfu", ""},
|
{"NRF-DFU", "nrf-dfu", ".zip"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeFirmwareImage(infile, outfile, format string) error {
|
// MakeFirmwareImage creates a firmware image from the given input file.
|
||||||
fmt.Printf("Creating firmware image from %s to %s with format %s\n", infile, outfile, format)
|
func MakeFirmwareImage(infile, outfile, format, fmtDetail string) error {
|
||||||
if strings.HasPrefix(format, "esp") {
|
if strings.HasPrefix(format, "esp") {
|
||||||
return makeESPFirmareImage(infile, outfile, format)
|
return makeESPFirmareImage(infile, outfile, format)
|
||||||
|
} else if format == "uf2" {
|
||||||
|
uf2Family := fmtDetail
|
||||||
|
return convertELFFileToUF2File(infile, outfile, uf2Family)
|
||||||
|
} else if format == "nrf-dfu" {
|
||||||
|
return makeDFUFirmwareImage(infile, outfile)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unsupported firmware format: %s", format)
|
return fmt.Errorf("unsupported firmware format: %s", format)
|
||||||
}
|
}
|
||||||
|
|||||||
118
internal/firmware/nrfutil.go
Normal file
118
internal/firmware/nrfutil.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// From tinygo/builder/nrfutil.go
|
||||||
|
|
||||||
|
package firmware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sigurn/crc16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Structure of the manifest.json file.
|
||||||
|
type jsonManifest struct {
|
||||||
|
Manifest struct {
|
||||||
|
Application struct {
|
||||||
|
BinaryFile string `json:"bin_file"`
|
||||||
|
DataFile string `json:"dat_file"`
|
||||||
|
InitPacketData nrfInitPacket `json:"init_packet_data"`
|
||||||
|
} `json:"application"`
|
||||||
|
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
|
||||||
|
} `json:"manifest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structure of the init packet.
|
||||||
|
// Source:
|
||||||
|
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
|
||||||
|
type nrfInitPacket struct {
|
||||||
|
ApplicationVersion uint32 `json:"application_version"`
|
||||||
|
DeviceRevision uint16 `json:"device_revision"`
|
||||||
|
DeviceType uint16 `json:"device_type"`
|
||||||
|
FirmwareCRC16 uint16 `json:"firmware_crc16"`
|
||||||
|
SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the init packet (the contents of application.dat).
|
||||||
|
func (p nrfInitPacket) createInitPacket() []byte {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type;
|
||||||
|
binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev;
|
||||||
|
binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version;
|
||||||
|
binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len;
|
||||||
|
binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1];
|
||||||
|
binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a Nordic DFU firmware image from an ELF file.
|
||||||
|
func makeDFUFirmwareImage(infile, outfile string) error {
|
||||||
|
// Read ELF file as input and convert it to a binary image file.
|
||||||
|
_, data, err := extractROM(infile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the zip file in memory.
|
||||||
|
// It won't be very large anyway.
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
w := zip.NewWriter(buf)
|
||||||
|
|
||||||
|
// Write the application binary to the zip file.
|
||||||
|
binw, err := w.Create("application.bin")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = binw.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the init packet.
|
||||||
|
initPacket := nrfInitPacket{
|
||||||
|
ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader
|
||||||
|
DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY
|
||||||
|
DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE
|
||||||
|
FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)),
|
||||||
|
SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the init packet to the zip file.
|
||||||
|
datw, err := w.Create("application.dat")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = datw.Write(initPacket.createInitPacket())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the JSON manifest.
|
||||||
|
manifest := &jsonManifest{}
|
||||||
|
manifest.Manifest.Application.BinaryFile = "application.bin"
|
||||||
|
manifest.Manifest.Application.DataFile = "application.dat"
|
||||||
|
manifest.Manifest.Application.InitPacketData = initPacket
|
||||||
|
manifest.Manifest.DFUVersion = 0.5
|
||||||
|
|
||||||
|
// Write the JSON manifest to the file.
|
||||||
|
jsonw, err := w.Create("manifest.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(jsonw)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
err = enc.Encode(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the zip file.
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(outfile, buf.Bytes(), 0o666)
|
||||||
|
}
|
||||||
133
internal/firmware/objcopy.go
Normal file
133
internal/firmware/objcopy.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// From tinygo/builder/objcopy.go
|
||||||
|
|
||||||
|
package firmware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
|
||||||
|
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
|
||||||
|
const maxPadBytes = 4095
|
||||||
|
|
||||||
|
// objcopyError is an error returned by functions that act like objcopy.
|
||||||
|
type objcopyError struct {
|
||||||
|
Op string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e objcopyError) Error() string {
|
||||||
|
if e.Err == nil {
|
||||||
|
return e.Op
|
||||||
|
}
|
||||||
|
return e.Op + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
type progSlice []*elf.Prog
|
||||||
|
|
||||||
|
func (s progSlice) Len() int { return len(s) }
|
||||||
|
func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
|
||||||
|
func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
// extractROM extracts a firmware image and the first load address from the
|
||||||
|
// given ELF file. It tries to emulate the behavior of objcopy.
|
||||||
|
func extractROM(path string) (uint64, []byte, error) {
|
||||||
|
f, err := elf.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// The GNU objcopy command does the following for firmware extraction (from
|
||||||
|
// the man page):
|
||||||
|
// > When objcopy generates a raw binary file, it will essentially produce a
|
||||||
|
// > memory dump of the contents of the input object file. All symbols and
|
||||||
|
// > relocation information will be discarded. The memory dump will start at
|
||||||
|
// > the load address of the lowest section copied into the output file.
|
||||||
|
|
||||||
|
// Find the lowest section address.
|
||||||
|
startAddr := ^uint64(0)
|
||||||
|
for _, section := range f.Sections {
|
||||||
|
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if section.Addr < startAddr {
|
||||||
|
startAddr = section.Addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progs := make(progSlice, 0, 2)
|
||||||
|
for _, prog := range f.Progs {
|
||||||
|
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
progs = append(progs, prog)
|
||||||
|
}
|
||||||
|
if len(progs) == 0 {
|
||||||
|
return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil}
|
||||||
|
}
|
||||||
|
sort.Sort(progs)
|
||||||
|
|
||||||
|
var rom []byte
|
||||||
|
for _, prog := range progs {
|
||||||
|
romEnd := progs[0].Paddr + uint64(len(rom))
|
||||||
|
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
|
||||||
|
// Sometimes, the linker seems to insert a bit of padding between
|
||||||
|
// segments. Simply zero-fill these parts.
|
||||||
|
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
|
||||||
|
}
|
||||||
|
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||||
|
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
|
||||||
|
if diff > maxPadBytes {
|
||||||
|
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||||
|
}
|
||||||
|
// Pad the difference
|
||||||
|
rom = append(rom, make([]byte, diff)...)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(prog.Open())
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||||
|
}
|
||||||
|
rom = append(rom, data...)
|
||||||
|
}
|
||||||
|
if progs[0].Paddr < startAddr {
|
||||||
|
// The lowest memory address is before the first section. This means
|
||||||
|
// that there is some extra data loaded at the start of the image that
|
||||||
|
// should be discarded.
|
||||||
|
// Example: ELF files where .text doesn't start at address 0 because
|
||||||
|
// there is a bootloader at the start.
|
||||||
|
return startAddr, rom[startAddr-progs[0].Paddr:], nil
|
||||||
|
} else {
|
||||||
|
return progs[0].Paddr, rom, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// objcopy converts an ELF file to a different (simpler) output file format:
|
||||||
|
// .bin or .hex. It extracts only the .text section.
|
||||||
|
func objcopy(infile, outfile, binaryFormat string) error {
|
||||||
|
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Read the .text segment.
|
||||||
|
_, data, err := extractROM(infile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the file, in the correct format.
|
||||||
|
switch binaryFormat {
|
||||||
|
case "bin":
|
||||||
|
// The start address is not stored in raw firmware files (therefore you
|
||||||
|
// should use .hex files in most cases).
|
||||||
|
_, err := f.Write(data)
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
155
internal/firmware/uf2.go
Normal file
155
internal/firmware/uf2.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// From tinygo/builder/uf2.go
|
||||||
|
|
||||||
|
package firmware
|
||||||
|
|
||||||
|
// This file converts firmware files from BIN to UF2 format before flashing.
|
||||||
|
//
|
||||||
|
// For more information about the UF2 firmware file format, please see:
|
||||||
|
// https://github.com/Microsoft/uf2
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// convertELFFileToUF2File converts an ELF file to a UF2 file.
|
||||||
|
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
|
||||||
|
// Read the .text segment.
|
||||||
|
targetAddress, data, err := extractROM(infile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(outfile, output, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
||||||
|
func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) {
|
||||||
|
blocks := split(input, 256)
|
||||||
|
output := make([]byte, 0)
|
||||||
|
|
||||||
|
bl, err := newUF2Block(targetAddr, uf2FamilyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
bl.SetNumBlocks(len(blocks))
|
||||||
|
|
||||||
|
for i := 0; i < len(blocks); i++ {
|
||||||
|
bl.SetBlockNo(i)
|
||||||
|
bl.SetData(blocks[i])
|
||||||
|
|
||||||
|
output = append(output, bl.Bytes()...)
|
||||||
|
bl.IncrementAddress(bl.payloadSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, len(blocks), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
uf2MagicStart0 = 0x0A324655 // "UF2\n"
|
||||||
|
uf2MagicStart1 = 0x9E5D5157 // Randomly selected
|
||||||
|
uf2MagicEnd = 0x0AB16F30 // Ditto
|
||||||
|
)
|
||||||
|
|
||||||
|
// uf2Block is the structure used for each UF2 code block sent to device.
|
||||||
|
type uf2Block struct {
|
||||||
|
magicStart0 uint32
|
||||||
|
magicStart1 uint32
|
||||||
|
flags uint32
|
||||||
|
targetAddr uint32
|
||||||
|
payloadSize uint32
|
||||||
|
blockNo uint32
|
||||||
|
numBlocks uint32
|
||||||
|
familyID uint32
|
||||||
|
data []uint8
|
||||||
|
magicEnd uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUF2Block returns a new uf2Block struct that has been correctly populated
|
||||||
|
func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) {
|
||||||
|
var flags uint32
|
||||||
|
var familyID uint32
|
||||||
|
if uf2FamilyID != "" {
|
||||||
|
flags |= flagFamilyIDPresent
|
||||||
|
v, err := strconv.ParseUint(uf2FamilyID, 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
familyID = uint32(v)
|
||||||
|
}
|
||||||
|
return &uf2Block{magicStart0: uf2MagicStart0,
|
||||||
|
magicStart1: uf2MagicStart1,
|
||||||
|
magicEnd: uf2MagicEnd,
|
||||||
|
targetAddr: targetAddr,
|
||||||
|
flags: flags,
|
||||||
|
familyID: familyID,
|
||||||
|
payloadSize: 256,
|
||||||
|
data: make([]byte, 476),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagFamilyIDPresent = 0x00002000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
|
||||||
|
func (b *uf2Block) Bytes() []byte {
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, 512))
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.magicStart0)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.magicStart1)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.flags)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.targetAddr)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.payloadSize)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.blockNo)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.numBlocks)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.familyID)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.data)
|
||||||
|
binary.Write(buf, binary.LittleEndian, b.magicEnd)
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementAddress moves the target address pointer forward by count bytes.
|
||||||
|
func (b *uf2Block) IncrementAddress(count uint32) {
|
||||||
|
b.targetAddr += b.payloadSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetData sets the data to be used for the current block.
|
||||||
|
func (b *uf2Block) SetData(d []byte) {
|
||||||
|
b.data = make([]byte, 476)
|
||||||
|
copy(b.data[:], d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockNo sets the current block number to be used.
|
||||||
|
func (b *uf2Block) SetBlockNo(bn int) {
|
||||||
|
b.blockNo = uint32(bn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNumBlocks sets the total number of blocks for this UF2 file.
|
||||||
|
func (b *uf2Block) SetNumBlocks(total int) {
|
||||||
|
b.numBlocks = uint32(total)
|
||||||
|
}
|
||||||
|
|
||||||
|
// split splits a slice of bytes into a slice of byte slices of a specific size limit.
|
||||||
|
func split(input []byte, limit int) [][]byte {
|
||||||
|
var block []byte
|
||||||
|
output := make([][]byte, 0, len(input)/limit+1)
|
||||||
|
for len(input) >= limit {
|
||||||
|
// add all blocks
|
||||||
|
block, input = input[:limit], input[limit:]
|
||||||
|
output = append(output, block)
|
||||||
|
}
|
||||||
|
if len(input) > 0 {
|
||||||
|
// add remaining block (that isn't full sized)
|
||||||
|
output = append(output, input)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ type Config struct {
|
|||||||
|
|
||||||
// Binary and firmware configuration
|
// Binary and firmware configuration
|
||||||
BinaryFormat string `json:"binary-format"`
|
BinaryFormat string `json:"binary-format"`
|
||||||
|
// UF2 configuration
|
||||||
|
UF2FamilyID string `json:"uf2-family-id"`
|
||||||
|
|
||||||
// Flash and deployment configuration
|
// Flash and deployment configuration
|
||||||
FlashCommand string `json:"flash-command"`
|
FlashCommand string `json:"flash-command"`
|
||||||
@@ -39,9 +41,6 @@ type Config struct {
|
|||||||
MSDVolumeName []string `json:"msd-volume-name"`
|
MSDVolumeName []string `json:"msd-volume-name"`
|
||||||
MSDFirmwareName string `json:"msd-firmware-name"`
|
MSDFirmwareName string `json:"msd-firmware-name"`
|
||||||
|
|
||||||
// UF2 configuration
|
|
||||||
UF2FamilyID string `json:"uf2-family-id"`
|
|
||||||
|
|
||||||
// Device-specific configuration
|
// Device-specific configuration
|
||||||
RP2040BootPatch bool `json:"rp2040-boot-patch"`
|
RP2040BootPatch bool `json:"rp2040-boot-patch"`
|
||||||
|
|
||||||
@@ -66,6 +65,13 @@ func (c *Config) IsEmpty() bool {
|
|||||||
return c.Name == "" && c.LLVMTarget == "" && c.GOOS == "" && c.GOARCH == ""
|
return c.Name == "" && c.LLVMTarget == "" && c.GOOS == "" && c.GOARCH == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) FormatDetail() string {
|
||||||
|
if c.BinaryFormat == "uf2" {
|
||||||
|
return c.UF2FamilyID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// HasInheritance returns true if this config inherits from other configs
|
// HasInheritance returns true if this config inherits from other configs
|
||||||
func (rc *RawConfig) HasInheritance() bool {
|
func (rc *RawConfig) HasInheritance() bool {
|
||||||
return len(rc.Inherits) > 0
|
return len(rc.Inherits) > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user