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 }