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: 4 additions & 4 deletions dap/sim/debugbase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ func TestMEMAPDebugBaseRegisters(t *testing.T) {
t.Fatal(err)
}
ap := target.aps[sel]
if got, err := ap.readRegister(0xf8); err != nil || got != 2 {
if got, err := ap.readRegister(t.Context(), 0xf8); err != nil || got != 2 {
t.Fatalf("default BASE=%#x, %v", got, err)
}
if err := target.SetMEMAPDebugBase(sel, 0xe00ff003, 0x12345678); err != nil {
t.Fatal(err)
}
if got, err := ap.readRegister(0xf0); err != nil || got != 0 {
if got, err := ap.readRegister(t.Context(), 0xf0); err != nil || got != 0 {
t.Fatalf("32-bit upper BASE=%#x, %v", got, err)
}
if err := target.SetMEMAPCFG(sel, 2); err != nil {
t.Fatal(err)
}
for reg, want := range map[uint8]uint32{0xf8: 0xe00ff003, 0xf0: 0x12345678} {
if err := ap.writeRegister(reg, 0); err != nil {
if err := ap.writeRegister(t.Context(), reg, 0); err != nil {
t.Fatal(err)
}
if got, err := ap.readRegister(reg); err != nil || got != want {
if got, err := ap.readRegister(t.Context(), reg); err != nil || got != want {
t.Fatalf("register %#x=%#x, %v; want %#x", reg, got, err, want)
}
}
Expand Down
131 changes: 131 additions & 0 deletions dap/sim/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package sim

import (
"context"
"errors"
"fmt"
"reflect"

"github.com/jon/ostiole/dap"
)

// ErrBusFault reports a target-bus access which completed with an error.
// The accepted AP transfer sets STICKYERR; subsequent SWD requests report FAULT.
// It does not represent a request-phase WAIT or a host transport failure.
var ErrBusFault = errors.New("dap/sim: target bus fault")

// MemoryDevice receives one complete target access at an absolute byte address.
// Data is in increasing address order, independent of MEM-AP byte order, and
// has length 1, 2, 4 or 8. Read fills data; Write consumes it without retaining
// the slice. Calls are serialized with their Target and all other shared views.
// Return ErrBusFault for a modeled bus error, including a rejected width.
// Other errors become ErrDeviceFailure. No access is replayed.
type MemoryDevice interface {
Read(ctx context.Context, addr uint64, data []byte) error
Write(ctx context.Context, addr uint64, data []byte) error
}

type deviceMapping struct {
start, end uint64
device MemoryDevice
}

// MapMEMAPDevice maps size bytes of one MEM-AP to device before any target
// traffic. Mappings cannot overlap or change after traffic starts. Unmapped
// addresses retain the ordinary byte-memory fixture behavior. The mapping owns
// no cleanup; the caller owns device and serializes all uses of shared devices.
func (t *Target) MapMEMAPDevice(sel dap.APSel, addr, size uint64, device MemoryDevice) error {
if t == nil {
return errors.New("dap/sim: nil target")
}
if t.started {
return errors.New("dap/sim: device mapping after target traffic")
}
if _, err := t.selectorValue(sel); err != nil {
return err
}
ap := t.aps[sel]
if ap == nil || !ap.memAP {
return errors.New("dap/sim: device mapping requires a MEM-AP")
}
if size == 0 || addr+size-1 < addr {
return errors.New("dap/sim: empty or overflowing device mapping")
}
if nilDevice(device) {
return errors.New("dap/sim: nil memory device")
}
end := addr + size - 1
if ap.overlapsDevice(addr, end) {
return errors.New("dap/sim: overlapping device mapping")
}
ap.devices = append(ap.devices, deviceMapping{addr, end, device})
return nil
}

func nilDevice(device MemoryDevice) bool {
if device == nil {
return true
}
v := reflect.ValueOf(device)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return v.IsNil()
default:
return false
}
}

func (ap *accessPort) overlapsDevice(start, end uint64) bool {
for _, mapping := range ap.devices {
if start <= mapping.end && end >= mapping.start {
return true
}
}
return false
}

func (ap *accessPort) deviceForAccess(addr uint64, width int) (MemoryDevice, error) {
end, err := memoryRangeEnd(addr, width)
if err != nil || addr%uint64(width) != 0 {
return nil, ErrBusFault
}
for _, mapping := range ap.devices {
if addr > mapping.end || end < mapping.start {
continue
}
if addr < mapping.start || end > mapping.end {
return nil, fmt.Errorf("%w: access crosses device mapping", ErrBusFault)
}
return mapping.device, nil
}
return nil, nil
}

func (ap *accessPort) readMemory(ctx context.Context, addr uint64, width int) ([]byte, error) {
device, err := ap.deviceForAccess(addr, width)
if err != nil {
return nil, err
}
data := make([]byte, width)
if device != nil {
return data, deviceResult(device.Read(ctx, addr, data))
}
for i := range data {
data[i] = ap.memory[addr+uint64(i)]
}
return data, nil
}

func (ap *accessPort) writeMemory(ctx context.Context, addr uint64, data []byte) error {
device, err := ap.deviceForAccess(addr, len(data))
if err != nil {
return err
}
if device != nil {
return deviceResult(device.Write(ctx, addr, data))
}
for i, b := range data {
ap.memory[addr+uint64(i)] = b
}
return nil
}
38 changes: 38 additions & 0 deletions dap/sim/device_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package sim

import (
"context"
"errors"
"fmt"
)

// ErrDeviceFailure reports a host or model failure inside a memory callback.
// The accepted access might have taken effect. The error text retains the
// callback diagnostic; only context cancellation/deadline identity is retained.
// Callback errors never identify SWD acknowledgements or unsent requests.
var ErrDeviceFailure = errors.New("dap/sim: memory device failure")

type deviceFailure struct {
cause error
}

func (e deviceFailure) Error() string {
return fmt.Sprintf("%v: %v", ErrDeviceFailure, e.cause)
}

// Is preserves cancellation without exposing protocol classifications from an
// accepted device access. Unwrapping the cause would let SWD treat a callback
// WAIT as permission to retry the accepted access.
func (e deviceFailure) Is(target error) bool {
if target == ErrDeviceFailure {
return true
}
return (target == context.Canceled || target == context.DeadlineExceeded) && errors.Is(e.cause, target)
}

func deviceResult(err error) error {
if err == nil || errors.Is(err, ErrBusFault) {
return err
}
return deviceFailure{cause: err}
}
Loading
Loading