Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ had changed in them - which is the gap this file exists to close.

### Fixed

- **Emulated nRF52 boards keep what they store.** The factory registers never
said how big the flash was, so the SoftDevice refused every erase and write
with `NRF_ERROR_FORBIDDEN`: the filesystem could not be formatted, and
`IdentityStore::save()` failed on every boot. And Renode rebuilt flash from
the image at every start, so nothing would have survived a restart anyway.
The filesystem's pages are now kept in the node's directory and loaded back
when the same build starts again. A preference set over the console
survives a restart.
- **Menus close on Escape or a click outside them.** A dropdown stayed open
until its title was clicked again or an item picked. A click outside now
closes it without acting on whatever was underneath.
Expand Down
1 change: 1 addition & 0 deletions internal/firmware/emulated/emulated_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ func (e *EmulatedNode) Start(ctx context.Context, bridge string) (err error) {
fmt.Sprintf("%s=%d", EnvNoiseSeed, noiseSeedFor(e.RunSeed, e.NodeName, e.BoardName, e.Position)))

if e.Emulator == Renode {
e.radioEnv = append(e.radioEnv, "MESHBENCH_FLASH_KEEP="+e.keptStorage())
if err := e.startRenode(ctx, bridge); err != nil {
return err
}
Expand Down
33 changes: 30 additions & 3 deletions internal/firmware/emulated/nrf52.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package emulated

import (
"crypto/sha256"
"fmt"
"hash/fnv"
"os"
Expand All @@ -14,9 +15,11 @@ import (
// stands in for a flashed board.
const (
nrf52FlashBytes = 1024 * 1024
nrf52UICRBase = 0x10001000
nrf52UICRBytes = 0x1000
nrf52FICRBase = 0x10000000
// nrf52StorageBase is where the Adafruit core's InternalFS starts.
nrf52StorageBase = 0xED000
nrf52UICRBase = 0x10001000
nrf52UICRBytes = 0x1000
nrf52FICRBase = 0x10000000
)

// SoftDeviceHex finds the fetched SoftDevice for a version, or "" if it is not
Expand Down Expand Up @@ -84,6 +87,13 @@ func (e *EmulatedNode) renodeFlash() (string, error) {
fmt.Fprintf(&b, "sysbus LoadBinary @%s 0x%X\n", path, r.addr)
}

// What the firmware stored last time, over the erased pages, when it was
// this same build: see NRF52840_NVMC.Keep. A different build is a board
// reflashed, and starts with nothing, as the QEMU boards do.
if kept := e.keptStorage(); fileExists(kept) {
fmt.Fprintf(&b, "sysbus LoadBinary @%s 0x%X\n", kept, nrf52StorageBase)
}

// SP and PC come from the vector table of whatever boots first, which is
// the MBR at zero and not the application. Setting only VectorTableOffset
// leaves SP uninitialised and the firmware faults within 45 instructions
Expand Down Expand Up @@ -175,6 +185,13 @@ func (e *EmulatedNode) ficrWrites() string {
regs := map[uint32]uint32{
0x060: uint32(id),
0x064: uint32(id >> 32),
// The flash's geometry: 256 pages of 4 kB. The SoftDevice checks
// every page it is asked to erase or write against these, and with
// both reading zero there was no flash at all - every
// sd_flash_page_erase answered NRF_ERROR_FORBIDDEN, LittleFS could not
// format, and nothing a node stored survived a reboot (#565).
0x010: 0x00001000, // CODEPAGESIZE
0x014: 0x00000100, // CODESIZE
0x0A0: 0x00000000, // DEVICEADDRTYPE - public
0x0A4: uint32(id ^ 0xA1B2C3D4),
0x0A8: uint32(id>>32) & 0xFFFF,
Expand Down Expand Up @@ -238,3 +255,13 @@ func SoftDeviceDir() string {
}
return filepath.Join(dir, "meshbench", "softdevice")
}

// keptStorage is where this node's filesystem is kept between starts, named for
// the build so another build does not inherit it.
func (e *EmulatedNode) keptStorage() string {
sum := sha256.Sum256([]byte(e.Image))
if b, err := os.ReadFile(e.Image); err == nil {
sum = sha256.Sum256(b)
}
return filepath.Join(e.Dir, fmt.Sprintf("storage-%x.bin", sum[:6]))
}
38 changes: 38 additions & 0 deletions internal/firmware/emulated/nrf52_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,41 @@ func TestFlashLaysTheSoftDeviceUnderTheApplication(t *testing.T) {
t.Errorf("the byte at 0x10 is 0x%02X, want 0x42", got[16])
}
}

// The SoftDevice checks every flash operation against the flash's geometry,
// and with CODEPAGESIZE and CODESIZE reading zero it forbade every one (#565).
func TestFICRSaysHowBigTheFlashIs(t *testing.T) {
w := (&EmulatedNode{NodeName: "n1"}).ficrWrites()
for _, want := range []string{"0x10000010 0x00001000", "0x10000014 0x00000100"} {
if !strings.Contains(w, "sysbus WriteDoubleWord "+want) {
t.Errorf("FICR does not write %s:\n%s", want, w)
}
}
}

// What the firmware stored goes back into flash at the next start of the same
// build, and a different build starts with nothing (#565).
func TestKeptStorageComesBackForTheSameBuild(t *testing.T) {
dir := t.TempDir()
e := &EmulatedNode{Image: writeUF2(t, 0), Dir: dir}
script, err := e.renodeFlash()
if err != nil {
t.Fatal(err)
}
if strings.Contains(script, "0xED000") {
t.Fatalf("nothing was kept, yet storage is loaded:\n%s", script)
}
if err := os.WriteFile(e.keptStorage(), []byte{1}, 0o644); err != nil {
t.Fatal(err)
}
if script, _ = e.renodeFlash(); !strings.Contains(script, e.keptStorage()+" 0xED000") {
t.Errorf("the kept storage is not loaded:\n%s", script)
}
other := &EmulatedNode{Image: writeUF2(t, 0x0), Dir: dir}
if err := os.WriteFile(other.Image, uf2blk(0, []byte{9, 9, 9, 9}), 0o644); err != nil {
t.Fatal(err)
}
if other.keptStorage() == e.keptStorage() {
t.Error("another build would inherit this one's storage")
}
}
5 changes: 5 additions & 0 deletions tools/renode/ficr.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
**{0x080 + 4 * i: 0x11111111 * (i + 1) for i in range(4)}, # ER[0..3]
**{0x090 + 4 * i: 0x22222222 * (i + 1) for i in range(4)}, # IR[0..3]

# Flash geometry, which the SoftDevice checks every flash operation
# against: with these zero it forbids every erase and write.
0x010: 0x00001000, # CODEPAGESIZE — 4 kB
0x014: 0x00000100, # CODESIZE — 256 pages

0x0A0: 0x00000000, # DEVICEADDRTYPE — public
0x0A4: 0xA1B2C3D4, # DEVICEADDR[0]
0x0A8: 0x0000E5F6, # DEVICEADDR[1]
Expand Down
43 changes: 42 additions & 1 deletion tools/renode/peripherals/NRF52840_NVMC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
// rather than one worth pretending away.
//
using System;
using System.IO;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Logging;
Expand All @@ -47,6 +48,7 @@ public class NRF52840_NVMC : BasicDoubleWordPeripheral, IKnownSize
{
public NRF52840_NVMC(IMachine machine) : base(machine)
{
keepPath = Environment.GetEnvironmentVariable("MESHBENCH_FLASH_KEEP");
DefineRegisters();
}

Expand All @@ -65,8 +67,17 @@ private void DefineRegisters()
.WithReservedBits(1, 31)
;

// Flash going back to read-only is where a write or an erase has
// finished, so that is where the filesystem is kept: see Keep.
Registers.Config.Define(this)
.WithEnumField<DoubleWordRegister, ConfigMode>(0, 2, out config, name: "WEN")
.WithEnumField<DoubleWordRegister, ConfigMode>(0, 2, out config, name: "WEN",
changeCallback: (was, now) =>
{
if(was != ConfigMode.ReadOnly && now == ConfigMode.ReadOnly)
{
Keep();
}
})
.WithReservedBits(2, 30)
;

Expand Down Expand Up @@ -166,8 +177,38 @@ private void Erase(ulong start, ulong size)
}
}

// Keep writes the filesystem's pages to the node's own directory, so
// what the firmware stored is there when the node next starts (#565).
// Renode builds flash from the image at every start, so without this
// a board kept its preferences and identity only until it was stopped.
// The region is the Adafruit core's InternalFS on the nRF52840, which
// is where every nRF52 MeshCore build keeps its files.
private void Keep()
{
if(string.IsNullOrEmpty(keepPath))
{
return;
}
try
{
var bytes = sysbus.ReadBytes(KeepStart, (int)KeepSize);
var tmp = keepPath + ".tmp";
File.WriteAllBytes(tmp, bytes);
File.Copy(tmp, keepPath, true);
File.Delete(tmp);
}
catch(Exception e)
{
this.Log(LogLevel.Warning, "could not keep the filesystem at {0}: {1}", keepPath, e.Message);
}
}

private readonly string keepPath;
private IEnumRegisterField<ConfigMode> config;

private const ulong KeepStart = 0xED000;
private const ulong KeepSize = 0x7000;

private const ulong PageSize = 0x1000;
private const ulong FlashStart = 0x0;
private const ulong FlashSize = 0x100000;
Expand Down