debugger: implement burst reading

This commit is contained in:
David Anderson 2024-09-18 21:31:34 -07:00
parent 789d8d002b
commit 0d4855fc21
2 changed files with 75 additions and 21 deletions

View File

@ -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
}

View File

@ -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