aboutsummaryrefslogtreecommitdiffhomepage
path: root/builder/uf2.go
blob: 62bae5c66aa8fc21794d26ead60035ab236f8539 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package builder

// 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
}