builder: implement Nordic DFU file writer in Go
This avoids a dependency on nrfutil. I have verified that it creates
equivalent zip files to a wasp-os DFU zip file I downloaded here:
https://github.com/wasp-os/wasp-os/releases/
I have also tested that it produces valid DFU files that can be uploaded
using the dfu.py program here to my PineTime:
3d6fd30d33
There are some minor differences in the generated file that should not
matter in practice (JSON whitespace, firmware file name, zip
compression).
Этот коммит содержится в:
родитель
f4c8c37b7b
коммит
6435f62dcc
4 изменённых файлов: 113 добавлений и 25 удалений
|
@ -909,13 +909,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||||
}
|
}
|
||||||
case "nrf-dfu":
|
case "nrf-dfu":
|
||||||
// special format for nrfutil for Nordic chips
|
// special format for nrfutil for Nordic chips
|
||||||
tmphexpath := filepath.Join(tmpdir, "main.hex")
|
|
||||||
err := objcopy(result.Executable, tmphexpath, "hex")
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.Binary = filepath.Join(tmpdir, "main"+outext)
|
result.Binary = filepath.Join(tmpdir, "main"+outext)
|
||||||
err = makeDFUFirmwareImage(config.Options, tmphexpath, result.Binary)
|
err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,117 @@
|
||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"archive/zip"
|
||||||
"io"
|
"bytes"
|
||||||
"os/exec"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sigurn/crc16"
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
|
// Structure of the manifest.json file.
|
||||||
|
type jsonManifest struct {
|
||||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
Manifest struct {
|
||||||
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
|
Application struct {
|
||||||
|
BinaryFile string `json:"bin_file"`
|
||||||
if options.PrintCommands != nil {
|
DataFile string `json:"dat_file"`
|
||||||
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
|
InitPacketData nrfInitPacket `json:"init_packet_data"`
|
||||||
}
|
} `json:"application"`
|
||||||
|
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
|
||||||
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
|
} `json:"manifest"`
|
||||||
cmd.Stdout = io.Discard
|
}
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
// Structure of the init packet.
|
||||||
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
|
// Source:
|
||||||
}
|
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
|
||||||
return nil
|
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(options *compileopts.Options, 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)
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
||||||
github.com/mattn/go-colorable v0.1.8
|
github.com/mattn/go-colorable v0.1.8
|
||||||
github.com/mattn/go-tty v0.0.4
|
github.com/mattn/go-tty v0.0.4
|
||||||
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||||
go.bug.st/serial v1.3.5
|
go.bug.st/serial v1.3.5
|
||||||
golang.org/x/sys v0.4.0
|
golang.org/x/sys v0.4.0
|
||||||
golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8
|
golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -43,6 +43,8 @@ github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3px
|
||||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
||||||
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE=
|
go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE=
|
||||||
go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304=
|
go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304=
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче