From 6435f62dcc34dd64ac7e5d57532e260a34d11c3e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 6 May 2023 18:16:38 +0200 Subject: 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: https://github.com/wasp-os/ota-dfu-python/tree/3d6fd30d33c2b20bc86ff6b9269fddf4a1d4c7c6 There are some minor differences in the generated file that should not matter in practice (JSON whitespace, firmware file name, zip compression). --- builder/build.go | 7 +--- builder/nrfutil.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++------ go.mod | 1 + go.sum | 2 + 4 files changed, 106 insertions(+), 18 deletions(-) diff --git a/builder/build.go b/builder/build.go index fcd7a2f07..d95519b33 100644 --- a/builder/build.go +++ b/builder/build.go @@ -909,13 +909,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } case "nrf-dfu": // 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) - err = makeDFUFirmwareImage(config.Options, tmphexpath, result.Binary) + err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary) if err != nil { return result, err } diff --git a/builder/nrfutil.go b/builder/nrfutil.go index 73b04925c..6f33eb7c6 100644 --- a/builder/nrfutil.go +++ b/builder/nrfutil.go @@ -1,27 +1,117 @@ package builder import ( - "fmt" - "io" - "os/exec" + "archive/zip" + "bytes" + "encoding/binary" + "encoding/json" + "os" + "github.com/sigurn/crc16" "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 { + 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(options *compileopts.Options, infile, outfile string) error { - cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile} + // 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 + } - if options.PrintCommands != nil { - options.PrintCommands(cmdLine[0], cmdLine[1:]...) + // 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 } - cmd := exec.Command(cmdLine[0], cmdLine[1:]...) - cmd.Stdout = io.Discard - err := cmd.Run() + // Write the init packet to the zip file. + datw, err := w.Create("application.dat") if err != nil { - return fmt.Errorf("could not run nrfutil pkg generate: %w", err) + return err } - return nil + _, 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) } diff --git a/go.mod b/go.mod index 0a20b0960..3aabc46ae 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-tty v0.0.4 + github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 go.bug.st/serial v1.3.5 golang.org/x/sys v0.4.0 golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8 diff --git a/go.sum b/go.sum index ef4917769..78a7ea867 100644 --- a/go.sum +++ b/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/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= 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= go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE= go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= -- cgit v1.2.3