github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/uf2.go (about)

     1  package builder
     2  
     3  // This file converts firmware files from BIN to UF2 format before flashing.
     4  //
     5  // For more information about the UF2 firmware file format, please see:
     6  // https://github.com/Microsoft/uf2
     7  //
     8  //
     9  
    10  import (
    11  	"bytes"
    12  	"encoding/binary"
    13  	"os"
    14  	"strconv"
    15  )
    16  
    17  // convertELFFileToUF2File converts an ELF file to a UF2 file.
    18  func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
    19  	// Read the .text segment.
    20  	targetAddress, data, err := extractROM(infile)
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID)
    26  	if err != nil {
    27  		return err
    28  	}
    29  	return os.WriteFile(outfile, output, 0644)
    30  }
    31  
    32  // convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
    33  func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) {
    34  	blocks := split(input, 256)
    35  	output := make([]byte, 0)
    36  
    37  	bl, err := newUF2Block(targetAddr, uf2FamilyID)
    38  	if err != nil {
    39  		return nil, 0, err
    40  	}
    41  	bl.SetNumBlocks(len(blocks))
    42  
    43  	for i := 0; i < len(blocks); i++ {
    44  		bl.SetBlockNo(i)
    45  		bl.SetData(blocks[i])
    46  
    47  		output = append(output, bl.Bytes()...)
    48  		bl.IncrementAddress(bl.payloadSize)
    49  	}
    50  
    51  	return output, len(blocks), nil
    52  }
    53  
    54  const (
    55  	uf2MagicStart0 = 0x0A324655 // "UF2\n"
    56  	uf2MagicStart1 = 0x9E5D5157 // Randomly selected
    57  	uf2MagicEnd    = 0x0AB16F30 // Ditto
    58  )
    59  
    60  // uf2Block is the structure used for each UF2 code block sent to device.
    61  type uf2Block struct {
    62  	magicStart0 uint32
    63  	magicStart1 uint32
    64  	flags       uint32
    65  	targetAddr  uint32
    66  	payloadSize uint32
    67  	blockNo     uint32
    68  	numBlocks   uint32
    69  	familyID    uint32
    70  	data        []uint8
    71  	magicEnd    uint32
    72  }
    73  
    74  // newUF2Block returns a new uf2Block struct that has been correctly populated
    75  func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) {
    76  	var flags uint32
    77  	var familyID uint32
    78  	if uf2FamilyID != "" {
    79  		flags |= flagFamilyIDPresent
    80  		v, err := strconv.ParseUint(uf2FamilyID, 0, 32)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		familyID = uint32(v)
    85  	}
    86  	return &uf2Block{magicStart0: uf2MagicStart0,
    87  		magicStart1: uf2MagicStart1,
    88  		magicEnd:    uf2MagicEnd,
    89  		targetAddr:  targetAddr,
    90  		flags:       flags,
    91  		familyID:    familyID,
    92  		payloadSize: 256,
    93  		data:        make([]byte, 476),
    94  	}, nil
    95  }
    96  
    97  const (
    98  	flagFamilyIDPresent = 0x00002000
    99  )
   100  
   101  // Bytes converts the uf2Block to a slice of bytes that can be written to file.
   102  func (b *uf2Block) Bytes() []byte {
   103  	buf := bytes.NewBuffer(make([]byte, 0, 512))
   104  	binary.Write(buf, binary.LittleEndian, b.magicStart0)
   105  	binary.Write(buf, binary.LittleEndian, b.magicStart1)
   106  	binary.Write(buf, binary.LittleEndian, b.flags)
   107  	binary.Write(buf, binary.LittleEndian, b.targetAddr)
   108  	binary.Write(buf, binary.LittleEndian, b.payloadSize)
   109  	binary.Write(buf, binary.LittleEndian, b.blockNo)
   110  	binary.Write(buf, binary.LittleEndian, b.numBlocks)
   111  	binary.Write(buf, binary.LittleEndian, b.familyID)
   112  	binary.Write(buf, binary.LittleEndian, b.data)
   113  	binary.Write(buf, binary.LittleEndian, b.magicEnd)
   114  
   115  	return buf.Bytes()
   116  }
   117  
   118  // IncrementAddress moves the target address pointer forward by count bytes.
   119  func (b *uf2Block) IncrementAddress(count uint32) {
   120  	b.targetAddr += b.payloadSize
   121  }
   122  
   123  // SetData sets the data to be used for the current block.
   124  func (b *uf2Block) SetData(d []byte) {
   125  	b.data = make([]byte, 476)
   126  	copy(b.data[:], d)
   127  }
   128  
   129  // SetBlockNo sets the current block number to be used.
   130  func (b *uf2Block) SetBlockNo(bn int) {
   131  	b.blockNo = uint32(bn)
   132  }
   133  
   134  // SetNumBlocks sets the total number of blocks for this UF2 file.
   135  func (b *uf2Block) SetNumBlocks(total int) {
   136  	b.numBlocks = uint32(total)
   137  }
   138  
   139  // split splits a slice of bytes into a slice of byte slices of a specific size limit.
   140  func split(input []byte, limit int) [][]byte {
   141  	var block []byte
   142  	output := make([][]byte, 0, len(input)/limit+1)
   143  	for len(input) >= limit {
   144  		// add all blocks
   145  		block, input = input[:limit], input[limit:]
   146  		output = append(output, block)
   147  	}
   148  	if len(input) > 0 {
   149  		// add remaining block (that isn't full sized)
   150  		output = append(output, input)
   151  	}
   152  	return output
   153  }