Skip to content

Commit

Permalink
Add flash-write command for ESP
Browse files Browse the repository at this point in the history
Params are address and file name ("-" for stdin).

```
$ mos flash-write --platform esp8266 0 flash.bin
```

CL: mos: Add flash-write command for ESP
  • Loading branch information
rojer committed Dec 21, 2019
1 parent 463a002 commit cf67f5f
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 75 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ Build the binary:
$ make
```

It will produce `mos/mos` (or mos/mos.exe` on Windows.
It will produce `mos/mos` (or `mos/mos.exe` on Windows).
175 changes: 104 additions & 71 deletions mos/flash/esp/flasher/flash.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ const (
)

type image struct {
addr uint32
data []byte
part *fwbundle.FirmwarePart
Name string
Type string
Addr uint32
Data []byte
ESP32Encrypt bool
}

type imagesByAddr []*image

func (pp imagesByAddr) Len() int { return len(pp) }
func (pp imagesByAddr) Swap(i, j int) { pp[i], pp[j] = pp[j], pp[i] }
func (pp imagesByAddr) Less(i, j int) bool {
return pp[i].addr < pp[j].addr
return pp[i].Addr < pp[j].Addr
}

func enDis(enabled bool) string {
Expand All @@ -75,32 +77,9 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
}
defer cfr.rc.Disconnect()

common.Reportf("Flash size: %d, params: %s", cfr.flashParams.Size(), cfr.flashParams)

encryptionEnabled := false
secureBootEnabled := false
var esp32EncryptionKey []byte
var fusesByName map[string]*esp32.Fuse
kcs := esp32.KeyEncodingSchemeNone
if ct == esp.ChipESP8266 {
// Based on our knowledge of flash size, adjust type=sys_params image.
adjustSysParamsLocation(fw, cfr.flashParams.Size())
} else {
_, _, fusesByName, err = esp32.ReadFuses(cfr.fc)
if err != nil {
return errors.Annotatef(err, "failed to read eFuses")
}

if fcnt, err := fusesByName[esp32.FlashCryptCntFuseName].Value(true /* withDiffs */); err == nil {
encryptionEnabled = (bits.OnesCount64(fcnt.Uint64())%2 != 0)
kcs = esp32.GetKeyEncodingScheme(fusesByName)
common.Reportf("Flash encryption: %s, scheme: %s", enDis(encryptionEnabled), kcs)
}

if abs0, err := fusesByName[esp32.AbstractDone0FuseName].Value(true /* withDiffs */); err == nil {
secureBootEnabled = (abs0.Int64() != 0)
common.Reportf("Secure boot: %s", enDis(secureBootEnabled))
}
}

// Sort images by address
Expand All @@ -122,11 +101,52 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
glog.V(1).Infof("%s -> %s -> 0x%x", p.Name, p.ESP32PartitionName, pti.Pos.Offset)
p.Addr = pti.Pos.Offset
}
im := &image{addr: p.Addr, data: data, part: p}
if im.addr == 0 || im.addr == 0x1000 && len(data) >= 4 && data[0] == 0xe9 {
im.data[2], im.data[3] = cfr.flashParams.Bytes()
im := &image{
Name: p.Name,
Type: p.Type,
Addr: p.Addr,
Data: data,
ESP32Encrypt: p.ESP32Encrypt,
}
if ct == esp.ChipESP32 && p.ESP32Encrypt && encryptionEnabled {
images = append(images, im)
}

return errors.Trace(writeImages(ct, cfr, images, opts, true))
}

func writeImages(ct esp.ChipType, cfr *cfResult, images []*image, opts *esp.FlashOpts, sanityCheck bool) error {
var err error

common.Reportf("Flash size: %d, params: %s", cfr.flashParams.Size(), cfr.flashParams)

encryptionEnabled := false
secureBootEnabled := false
var esp32EncryptionKey []byte
var fusesByName map[string]*esp32.Fuse
kcs := esp32.KeyEncodingSchemeNone
if ct == esp.ChipESP32 {
_, _, fusesByName, err = esp32.ReadFuses(cfr.fc)
if err != nil {
return errors.Annotatef(err, "failed to read eFuses")
}

if fcnt, err := fusesByName[esp32.FlashCryptCntFuseName].Value(true /* withDiffs */); err == nil {
encryptionEnabled = (bits.OnesCount64(fcnt.Uint64())%2 != 0)
kcs = esp32.GetKeyEncodingScheme(fusesByName)
common.Reportf("Flash encryption: %s, scheme: %s", enDis(encryptionEnabled), kcs)
}

if abs0, err := fusesByName[esp32.AbstractDone0FuseName].Value(true /* withDiffs */); err == nil {
secureBootEnabled = (abs0.Int64() != 0)
common.Reportf("Secure boot: %s", enDis(secureBootEnabled))
}
}

for _, im := range images {
if im.Addr == 0 || im.Addr == 0x1000 && len(im.Data) >= 4 && im.Data[0] == 0xe9 {
im.Data[2], im.Data[3] = cfr.flashParams.Bytes()
}
if ct == esp.ChipESP32 && im.ESP32Encrypt && encryptionEnabled {
if esp32EncryptionKey == nil {
if opts.ESP32EncryptionKeyFile != "" {
mac := strings.ToUpper(strings.Replace(fusesByName[esp32.MACAddressFuseName].MACAddressString(), ":", "", -1))
Expand Down Expand Up @@ -154,19 +174,20 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
encrKey = append(encrKey, encrKey[8:16]...)
}
encData, err := esp32.ESP32EncryptImageData(
im.data, encrKey, im.addr, opts.ESP32FlashCryptConf)
im.Data, encrKey, im.Addr, opts.ESP32FlashCryptConf)
if err != nil {
return errors.Annotatef(err, "%s: failed to encrypt", p.Name)
return errors.Annotatef(err, "%s: failed to encrypt", im.Name)
}
im.data = encData
im.Data = encData
}
images = append(images, im)
}
sort.Sort(imagesByAddr(images))

err = sanityCheckImages(ct, images, cfr.flashParams.Size(), flashSectorSize)
if err != nil {
return errors.Trace(err)
if sanityCheck {
err = sanityCheckImages(ct, images, cfr.flashParams.Size(), flashSectorSize)
if err != nil {
return errors.Trace(err)
}
}

imagesToWrite := images
Expand All @@ -188,10 +209,10 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
start := time.Now()
totalBytesWritten := 0
for _, im := range imagesToWrite {
data := im.data
data := im.Data
numAttempts := 3
imageBytesWritten := 0
addr := im.addr
addr := im.Addr
if len(data)%flashSectorSize != 0 {
newData := make([]byte, len(data))
copy(newData, data)
Expand All @@ -201,7 +222,7 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
}
data = newData
}
for i := 1; imageBytesWritten < len(im.data); i++ {
for i := 1; imageBytesWritten < len(im.Data); i++ {
common.Reportf(" %7d @ 0x%x", len(data), addr)
bytesWritten, err := cfr.fc.Write(addr, data, true /* erase */, opts.EnableCompression)
if err != nil {
Expand All @@ -211,7 +232,7 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
}
err = errors.Annotatef(err, "write error (attempt %d/%d)", i, numAttempts)
if i >= numAttempts {
return errors.Annotatef(err, "%s: failed to write", im.part.Name)
return errors.Annotatef(err, "%s: failed to write", im.Name)
}
glog.Warningf("%s", err)
if err := cfr.fc.Sync(); err != nil {
Expand All @@ -224,7 +245,7 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er
imageBytesWritten += bytesWritten
addr += uint32(bytesWritten)
}
totalBytesWritten += len(im.data)
totalBytesWritten += len(im.Data)
}
seconds := time.Since(start).Seconds()
bytesPerSecond := float64(totalBytesWritten) / seconds
Expand All @@ -233,19 +254,19 @@ func Flash(ct esp.ChipType, fw *fwbundle.FirmwareBundle, opts *esp.FlashOpts) er

common.Reportf("Verifying...")
for _, im := range images {
common.Reportf(" %7d @ 0x%x", len(im.data), im.addr)
digest, err := cfr.fc.Digest(im.addr, uint32(len(im.data)), 0 /* blockSize */)
common.Reportf(" %7d @ 0x%x", len(im.Data), im.Addr)
digest, err := cfr.fc.Digest(im.Addr, uint32(len(im.Data)), 0 /* blockSize */)
if err != nil {
return errors.Annotatef(err, "%s: failed to compute digest %d @ 0x%x", im.part.Name, len(im.data), im.addr)
return errors.Annotatef(err, "%s: failed to compute digest %d @ 0x%x", im.Name, len(im.Data), im.Addr)
}
if len(digest) != 1 || len(digest[0]) != 16 {
return errors.Errorf("unexpected digest packetresult %+v", digest)
}
digestHex := strings.ToLower(hex.EncodeToString(digest[0]))
expectedDigest := md5.Sum(im.data)
expectedDigest := md5.Sum(im.Data)
expectedDigestHex := strings.ToLower(hex.EncodeToString(expectedDigest[:]))
if digestHex != expectedDigestHex {
return errors.Errorf("%d @ 0x%x: digest mismatch: expected %s, got %s", len(im.data), im.addr, expectedDigestHex, digestHex)
return errors.Errorf("%d @ 0x%x: digest mismatch: expected %s, got %s", len(im.Data), im.Addr, expectedDigestHex, digestHex)
}
}
if opts.BootFirmware {
Expand Down Expand Up @@ -274,34 +295,34 @@ func sanityCheckImages(ct esp.ChipType, images []*image, flashSize, flashSectorS
// Note: we require that images are sorted by address.
sort.Sort(imagesByAddr(images))
for i, im := range images {
imageBegin := int(im.addr)
imageEnd := imageBegin + len(im.data)
imageBegin := int(im.Addr)
imageEnd := imageBegin + len(im.Data)
if imageBegin >= flashSize || imageEnd > flashSize {
return errors.Errorf(
"Image %d @ 0x%x will not fit in flash (size %d)", len(im.data), imageBegin, flashSize)
"Image %d @ 0x%x will not fit in flash (size %d)", len(im.Data), imageBegin, flashSize)
}
if imageBegin%flashSectorSize != 0 {
return errors.Errorf("Image starting address (0x%x) is not on flash sector boundary (sector size %d)",
imageBegin,
flashSectorSize)
}
if imageBegin == 0 && len(im.data) > 0 {
if im.data[0] != espImageMagicByte {
if imageBegin == 0 && len(im.Data) > 0 {
if im.Data[0] != espImageMagicByte {
return errors.Errorf("Invalid magic byte in the first image")
}
}
if ct == esp.ChipESP8266 {
sysParamsBegin := flashSize - sysParamsAreaSize
if imageBegin == sysParamsBegin && im.part.Type == sysParamsPartType {
if imageBegin == sysParamsBegin && im.Type == sysParamsPartType {
// Ok, a sys_params image.
} else if imageEnd > sysParamsBegin {
return errors.Errorf("Image 0x%x overlaps with system params area (%d @ 0x%x)",
imageBegin, sysParamsAreaSize, sysParamsBegin)
}
}
if i > 0 {
prevImageBegin := int(images[i-1].addr)
prevImageEnd := prevImageBegin + len(images[i-1].data)
prevImageBegin := int(images[i-1].Addr)
prevImageEnd := prevImageBegin + len(images[i-1].Data)
// We traverse the list in order, so a simple check will suffice.
if prevImageEnd > imageBegin {
return errors.Errorf("Images 0x%x and 0x%x overlap", prevImageBegin, imageBegin)
Expand All @@ -314,29 +335,35 @@ func sanityCheckImages(ct esp.ChipType, images []*image, flashSize, flashSectorS
func dedupImages(fc *FlasherClient, images []*image) ([]*image, error) {
var dedupedImages []*image
for _, im := range images {
glog.V(2).Infof("%d @ 0x%x", len(im.data), im.addr)
imAddr := int(im.addr)
digests, err := fc.Digest(im.addr, uint32(len(im.data)), flashSectorSize)
glog.V(2).Infof("%d @ 0x%x", len(im.Data), im.Addr)
imAddr := int(im.Addr)
digests, err := fc.Digest(im.Addr, uint32(len(im.Data)), flashSectorSize)
if err != nil {
return nil, errors.Annotatef(err, "%s: failed to compute digest %d @ 0x%x", im.part.Name, len(im.data), im.addr)
return nil, errors.Annotatef(err, "%s: failed to compute digest %d @ 0x%x", im.Name, len(im.Data), im.Addr)
}
i, offset := 0, 0
var newImages []*image
newAddr, newLen, newTotalLen := imAddr, 0, 0
for offset < len(im.data) {
for offset < len(im.Data) {
blockLen := flashSectorSize
if offset+blockLen > len(im.data) {
blockLen = len(im.data) - offset
if offset+blockLen > len(im.Data) {
blockLen = len(im.Data) - offset
}
digestHex := strings.ToLower(hex.EncodeToString(digests[i]))
expectedDigest := md5.Sum(im.data[offset : offset+blockLen])
expectedDigest := md5.Sum(im.Data[offset : offset+blockLen])
expectedDigestHex := strings.ToLower(hex.EncodeToString(expectedDigest[:]))
glog.V(2).Infof("0x%06x %4d %s %s %t", imAddr+offset, blockLen, expectedDigestHex, digestHex, expectedDigestHex == digestHex)
if expectedDigestHex == digestHex {
// Found a matching sector. If we've been building an image, commit it.
if newLen > 0 {
nim := &image{addr: uint32(newAddr), data: im.data[newAddr-imAddr : newAddr-imAddr+newLen], part: im.part}
glog.V(2).Infof("%d @ 0x%x", len(nim.data), nim.addr)
nim := &image{
Name: im.Name,
Type: im.Type,
Addr: uint32(newAddr),
Data: im.Data[newAddr-imAddr : newAddr-imAddr+newLen],
ESP32Encrypt: im.ESP32Encrypt,
}
glog.V(2).Infof("%d @ 0x%x", len(nim.Data), nim.Addr)
newImages = append(newImages, nim)
newTotalLen += newLen
newAddr, newLen = 0, 0
Expand All @@ -352,19 +379,25 @@ func dedupImages(fc *FlasherClient, images []*image) ([]*image, error) {
i++
}
if newLen > 0 {
nim := &image{addr: uint32(newAddr), data: im.data[newAddr-imAddr : newAddr-imAddr+newLen], part: im.part}
nim := &image{
Name: im.Name,
Type: im.Type,
Addr: uint32(newAddr),
Data: im.Data[newAddr-imAddr : newAddr-imAddr+newLen],
ESP32Encrypt: im.ESP32Encrypt,
}
newImages = append(newImages, nim)
glog.V(2).Infof("%d @ %x", len(nim.data), nim.addr)
glog.V(2).Infof("%d @ %x", len(nim.Data), nim.Addr)
newTotalLen += newLen
newAddr, newLen = 0, 0
}
glog.V(2).Infof("%d @ 0x%x -> %d", len(im.data), im.addr, newTotalLen)
glog.V(2).Infof("%d @ 0x%x -> %d", len(im.Data), im.Addr, newTotalLen)
// There's a price for fragmenting a large image: erasing many individual
// sectors is slower than erasing a whole block. So unless the difference
// is substantial, don't bother.
if newTotalLen < len(im.data) && (newTotalLen < flashBlockSize || len(im.data)-newTotalLen >= flashBlockSize) {
if newTotalLen < len(im.Data) && (newTotalLen < flashBlockSize || len(im.Data)-newTotalLen >= flashBlockSize) {
dedupedImages = append(dedupedImages, newImages...)
common.Reportf(" %7d @ 0x%x -> %d", len(im.data), im.addr, newTotalLen)
common.Reportf(" %7d @ 0x%x -> %d", len(im.Data), im.Addr, newTotalLen)
} else {
dedupedImages = append(dedupedImages, im)
}
Expand Down
40 changes: 40 additions & 0 deletions mos/flash/esp/flasher/write_flash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Copyright (c) 2014-2019 Cesanta Software Limited
// All rights reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package flasher

import (
"github.com/juju/errors"

"github.com/mongoose-os/mos/mos/flash/esp"
)

func WriteFlash(ct esp.ChipType, addr uint32, data []byte, opts *esp.FlashOpts) error {
cfr, err := ConnectToFlasherClient(ct, opts)
if err != nil {
return errors.Trace(err)
}
defer cfr.rc.Disconnect()
im := &image{
Name: "image",
Type: "user",
Addr: addr,
Data: data,
ESP32Encrypt: (opts.ESP32EncryptionKeyFile != ""),
}
return errors.Trace(writeImages(ct, cfr, []*image{im}, opts, false))
}
4 changes: 1 addition & 3 deletions mos/flash_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ func flashRead(ctx context.Context, devConn dev.DevConn) error {
var data []byte
platform := flags.Platform()
switch platform {
case "cc3200":
err = errors.NotImplementedf("flash reading for %s", platform)
case "esp32":
espFlashOpts.ControlPort = port
data, err = espFlasher.ReadFlash(esp.ChipESP32, uint32(addr), int(length), &espFlashOpts)
Expand All @@ -82,7 +80,7 @@ func flashRead(ctx context.Context, devConn dev.DevConn) error {
case "stm32":
err = errors.NotImplementedf("flash reading for %s", platform)
default:
err = errors.Errorf("unsupported platform '%s'", platform)
err = errors.NotImplementedf("flash reading for %s", platform)
}

if err == nil {
Expand Down
Loading

0 comments on commit cf67f5f

Please sign in to comment.