From 0d4855fc213bacb03bafe59ec610390d1a825ca5 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 18 Sep 2024 21:31:34 -0700 Subject: [PATCH] debugger: implement burst reading --- debugger/serial/serial.go | 92 ++++++++++++++++++++++++++++++--------- debugger/ui.go | 4 +- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/debugger/serial/serial.go b/debugger/serial/serial.go index 351711d..34a10f5 100644 --- a/debugger/serial/serial.go +++ b/debugger/serial/serial.go @@ -1,15 +1,23 @@ package serial import ( + "encoding/binary" "errors" + "fmt" + "io" "sync" "time" "go.bug.st/serial" ) -const portDev = "/dev/ttyUSB0" -const maxAddr = 1 << 17 +const ( + portDev = "/dev/ttyUSB0" + portSpeed = 115_200 + maxAddr = 1 << 17 + pingResponse = 0xEA + readTimeout = 2 * time.Second +) type Serial struct { portMu sync.Mutex @@ -25,6 +33,21 @@ func Open() (*Serial, error) { return nil, err } + packet := encode(cmdPing, 0, 0) + if _, err := port.Write(packet[:]); err != nil { + port.Close() + return nil, err + } + port.SetReadTimeout(readTimeout) + if _, err := io.ReadFull(port, packet[:1]); err != nil { + port.Close() + return nil, err + } + if packet[0] != pingResponse { + port.Close() + return nil, fmt.Errorf("wrong ping response: got %x, want %x", packet[0], pingResponse) + } + return &Serial{port: port}, nil } @@ -34,42 +57,71 @@ func (d *Serial) Close() error { return d.port.Close() } -func encode(addr int, write bool, data byte) [4]byte { - writeVal := uint8(0) - if write { - writeVal = 1 - } +const ( + cmdReadByte = 1 + cmdWriteByte = 2 + cmdReadRange = 3 + cmdPing = 0x7F +) + +func encode(cmd int, addr int, data byte) [4]byte { + cmd = cmd & (1<<7 - 1) + addr = addr & (1<<17 - 1) + raw := (uint32(cmd) << 25) + (uint32(addr) << 8) + uint32(data) var ret [4]byte - ret[3] = data - ret[2] = byte(addr<<1) | writeVal - ret[1] = byte(addr >> 7) - ret[0] = byte(addr >> 15) + binary.BigEndian.PutUint32(ret[:], raw) return ret } -func (d *Serial) ReadAt(bs []byte, off int64) (int, error) { +// readSlow reads memory one byte at a time, and is 10x slower than +// burst reading. The code is preserved here just because it's a +// simpler implementation in the hardware, and is thus sometimes +// useful for debugging. +func (d *Serial) readSlow(bs []byte, off int64) (int, error) { d.portMu.Lock() defer d.portMu.Unlock() if off+int64(len(bs)) >= maxAddr { return 0, errors.New("OOB read") } for i := range bs { - packet := encode(int(off)+i, false, 0) + packet := encode(cmdReadByte, int(off)+i, 0) if _, err := d.port.Write(packet[:]); err != nil { return i, err } - d.port.SetReadTimeout(2 * time.Second) - n, err := d.port.Read(packet[:1]) + d.port.SetReadTimeout(readTimeout) + n, err := io.ReadFull(d.port, bs[i:i+1]) if err != nil { - return i, err - } else if n == 0 { - return i, errors.New("short read") + return i + n, err } - bs[i] = packet[0] } return len(bs), nil } +func (d *Serial) readBurst(bs []byte, off int64) (int, error) { + d.portMu.Lock() + defer d.portMu.Unlock() + if off+int64(len(bs)) >= maxAddr { + return 0, errors.New("OOB read") + } + for i := 0; i < len(bs); i += 255 { + burstLen := min(len(bs)-i, 255) + packet := encode(cmdReadRange, int(off)+i, byte(burstLen)) + if _, err := d.port.Write(packet[:]); err != nil { + return i, err + } + d.port.SetReadTimeout(readTimeout) + n, err := io.ReadFull(d.port, bs[i:i+burstLen]) + if err != nil { + return i + n, err + } + } + return len(bs), nil +} + +func (d *Serial) ReadAt(bs []byte, off int64) (int, error) { + return d.readBurst(bs, off) +} + func (d *Serial) WriteAt(bs []byte, off int64) (int, error) { d.portMu.Lock() defer d.portMu.Unlock() @@ -77,7 +129,7 @@ func (d *Serial) WriteAt(bs []byte, off int64) (int, error) { return 0, errors.New("OOB write") } for i, v := range bs { - packet := encode(int(off)+i, true, v) + packet := encode(cmdWriteByte, int(off)+i, v) if _, err := d.port.Write(packet[:]); err != nil { return i, err } diff --git a/debugger/ui.go b/debugger/ui.go index 2101e02..2cffbc3 100644 --- a/debugger/ui.go +++ b/debugger/ui.go @@ -90,7 +90,7 @@ func (m debugger) readFullMemoryCmds() []tea.Cmd { func (m debugger) readMemory(start, count int) tea.Cmd { return func() tea.Msg { - if err := m.mem.Load(start, count); err != nil { + if err := m.mem.Refresh(start, count); err != nil { return msgErr{fmt.Errorf("loading region 0x%x+%x: %w", start, count, err)} } return msgMemoryChanged{start, count} @@ -131,6 +131,8 @@ func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "r": start, end := m.hex.VisibleBytes() return m, m.readMemory(start, end-start) + case "R": + return m, m.readFullMemory() } case msgErr: m.lastErr = msg.err