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

     1  package builder
     2  
     3  // This file implements support for writing ESP image files. These image files
     4  // are read by the ROM bootloader so have to be in a particular format.
     5  //
     6  // In the future, it may be necessary to implement support for other image
     7  // formats, such as the ESP8266 image formats (again, used by the ROM bootloader
     8  // to load the firmware).
     9  
    10  import (
    11  	"bytes"
    12  	"crypto/sha256"
    13  	"debug/elf"
    14  	"encoding/binary"
    15  	"fmt"
    16  	"os"
    17  	"sort"
    18  	"strings"
    19  )
    20  
    21  type espImageSegment struct {
    22  	addr uint32
    23  	data []byte
    24  }
    25  
    26  // makeESPFirmareImage converts an input ELF file to an image file for an ESP32 or
    27  // ESP8266 chip. This is a special purpose image format just for the ESP chip
    28  // family, and is parsed by the on-chip mask ROM bootloader.
    29  //
    30  // The following documentation has been used:
    31  // https://github.com/espressif/esptool/wiki/Firmware-Image-Format
    32  // https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
    33  // https://github.com/espressif/esptool/blob/master/esptool.py
    34  func makeESPFirmareImage(infile, outfile, format string) error {
    35  	inf, err := elf.Open(infile)
    36  	if err != nil {
    37  		return err
    38  	}
    39  	defer inf.Close()
    40  
    41  	// Load all segments to be written to the image. These are actually ELF
    42  	// sections, not true ELF segments (similar to how esptool does it).
    43  	var segments []*espImageSegment
    44  	for _, section := range inf.Sections {
    45  		if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 {
    46  			continue
    47  		}
    48  		data, err := section.Data()
    49  		if err != nil {
    50  			return fmt.Errorf("failed to read section data: %w", err)
    51  		}
    52  		for len(data)%4 != 0 {
    53  			// Align segment to 4 bytes.
    54  			data = append(data, 0)
    55  		}
    56  		if uint64(uint32(section.Addr)) != section.Addr {
    57  			return fmt.Errorf("section address too big: 0x%x", section.Addr)
    58  		}
    59  		segments = append(segments, &espImageSegment{
    60  			addr: uint32(section.Addr),
    61  			data: data,
    62  		})
    63  	}
    64  
    65  	// Sort the segments by address. This is what esptool does too.
    66  	sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
    67  
    68  	// Calculate checksum over the segment data. This is used in the image
    69  	// footer.
    70  	checksum := uint8(0xef)
    71  	for _, segment := range segments {
    72  		for _, b := range segment.data {
    73  			checksum ^= b
    74  		}
    75  	}
    76  
    77  	// Write first to an in-memory buffer, primarily so that we can easily
    78  	// calculate a hash over the entire image.
    79  	// An added benefit is that we don't need to check for errors all the time.
    80  	outf := &bytes.Buffer{}
    81  
    82  	// Separate esp32 and esp32-img. The -img suffix indicates we should make an
    83  	// image, not just a binary to be flashed at 0x1000 for example.
    84  	chip := format
    85  	makeImage := false
    86  	if strings.HasSuffix(format, "-img") {
    87  		makeImage = true
    88  		chip = format[:len(format)-len("-img")]
    89  	}
    90  
    91  	if makeImage {
    92  		// The bootloader starts at 0x1000, or 4096.
    93  		// TinyGo doesn't use a separate bootloader and runs the entire
    94  		// application in the bootloader location.
    95  		outf.Write(make([]byte, 4096))
    96  	}
    97  
    98  	// Chip IDs. Source:
    99  	// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
   100  	chip_id := map[string]uint16{
   101  		"esp32":   0x0000,
   102  		"esp32c3": 0x0005,
   103  	}[chip]
   104  
   105  	// Image header.
   106  	switch chip {
   107  	case "esp32", "esp32c3":
   108  		// Header format:
   109  		// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
   110  		// Note: not adding a SHA256 hash as the binary is modified by
   111  		// esptool.py while flashing and therefore the hash won't be valid
   112  		// anymore.
   113  		binary.Write(outf, binary.LittleEndian, struct {
   114  			magic          uint8
   115  			segment_count  uint8
   116  			spi_mode       uint8
   117  			spi_speed_size uint8
   118  			entry_addr     uint32
   119  			wp_pin         uint8
   120  			spi_pin_drv    [3]uint8
   121  			chip_id        uint16
   122  			min_chip_rev   uint8
   123  			reserved       [8]uint8
   124  			hash_appended  bool
   125  		}{
   126  			magic:          0xE9,
   127  			segment_count:  byte(len(segments)),
   128  			spi_mode:       2,    // ESP_IMAGE_SPI_MODE_DIO
   129  			spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
   130  			entry_addr:     uint32(inf.Entry),
   131  			wp_pin:         0xEE, // disable WP pin
   132  			chip_id:        chip_id,
   133  			hash_appended:  true, // add a SHA256 hash
   134  		})
   135  	case "esp8266":
   136  		// Header format:
   137  		// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
   138  		// Basically a truncated version of the ESP32 header.
   139  		binary.Write(outf, binary.LittleEndian, struct {
   140  			magic          uint8
   141  			segment_count  uint8
   142  			spi_mode       uint8
   143  			spi_speed_size uint8
   144  			entry_addr     uint32
   145  		}{
   146  			magic:          0xE9,
   147  			segment_count:  byte(len(segments)),
   148  			spi_mode:       0,    // irrelevant, replaced by esptool when flashing
   149  			spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing
   150  			entry_addr:     uint32(inf.Entry),
   151  		})
   152  	default:
   153  		return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format)
   154  	}
   155  
   156  	// Write all segments to the image.
   157  	// https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment
   158  	for _, segment := range segments {
   159  		binary.Write(outf, binary.LittleEndian, struct {
   160  			addr   uint32
   161  			length uint32
   162  		}{
   163  			addr:   segment.addr,
   164  			length: uint32(len(segment.data)),
   165  		})
   166  		outf.Write(segment.data)
   167  	}
   168  
   169  	// Footer, including checksum.
   170  	// The entire image size must be a multiple of 16, so pad the image to one
   171  	// byte less than that before writing the checksum.
   172  	outf.Write(make([]byte, 15-outf.Len()%16))
   173  	outf.WriteByte(checksum)
   174  
   175  	if chip != "esp8266" {
   176  		// SHA256 hash (to protect against image corruption, not for security).
   177  		hash := sha256.Sum256(outf.Bytes())
   178  		outf.Write(hash[:])
   179  	}
   180  
   181  	// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
   182  	// image to be a certain size.
   183  	if makeImage {
   184  		// Use a default image size of 4MB.
   185  		grow := 4096*1024 - outf.Len()
   186  		if grow > 0 {
   187  			outf.Write(make([]byte, grow))
   188  		}
   189  	}
   190  
   191  	// Write the image to the output file.
   192  	return os.WriteFile(outfile, outf.Bytes(), 0666)
   193  }