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
4 changes: 2 additions & 2 deletions coresight/component.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package coresight identifies debug components through target memory.
// Package coresight identifies debug components and inspects ROM tables through target memory.
// It borrows the reader and neither writes target memory nor owns cleanup.
package coresight

Expand All @@ -11,7 +11,7 @@ import (
)

// ScalarReader reads aligned target scalars as numeric values, independently
// of target byte order. Identify uses only dap.Size32. A dap.MemAP implements
// of target byte order. Inspection uses only dap.Size32. A dap.MemAP implements
// this interface; its owner remains responsible for serialization and release.
type ScalarReader interface {
ReadScalar(context.Context, uint64, dap.TransferSize) (uint64, error)
Expand Down
22 changes: 22 additions & 0 deletions coresight/memap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func TestIdentifyThroughMEMAP(t *testing.T) {
if got.CIDR != 0xb105900d || got.PIDR != 0x24523bb906 || got.DEVARCH != 0x47721a14 {
t.Fatalf("identity=%+v", got)
}
verifyWalkThroughMEMAP(t, mem, target, ap, order)
})
}
}
Expand All @@ -65,3 +66,24 @@ func releaseSimulation(t *testing.T, release func(context.Context) error) {
t.Error(err)
}
}

func verifyWalkThroughMEMAP(t *testing.T, mem *dap.MemAP, target *sim.Target, ap dap.APSel, order binary.ByteOrder) {
t.Helper()
const high = uint64(1) << 32
for address, word := range walkMemory().words {
var bytes [4]byte
order.PutUint32(bytes[:], word)
if err := target.SetMEMAPBytes(ap, high+address, bytes[:]); err != nil {
t.Fatal(err)
}
}
visits, err := coresight.Walk(t.Context(), mem, high+0x10000, walkLimits())
if err != nil || len(visits) != 4 {
t.Fatalf("visits=%+v err=%v", visits, err)
}
for i, base := range []uint64{0x10000, 0x20000, 0x30000, 0x40000} {
if visits[i].Component == nil || visits[i].Component.Base != high+base {
t.Fatalf("visit %d=%+v", i, visits[i])
}
}
}
134 changes: 134 additions & 0 deletions coresight/rom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package coresight

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

// ErrNotROMTable means the component does not advertise a supported ROM architecture.
var ErrNotROMTable = errors.New("coresight: not a ROM table")

// ROMTable describes a ROM table's entry layout, derived from a component identity.
// It owns no reader or resources. Its zero value is invalid.
type ROMTable struct {
base uint64
count int
stride uint64
class uint8
}

// ROMTable interprets a valid identification snapshot without memory traffic.
// It recognizes class 1 and Arm class 9 ROM architecture 0x0af7, revision 0.
// Unknown class 9 entry formats or revisions return errors, not ErrNotROMTable.
func (c Component) ROMTable() (ROMTable, error) {
if c.Base&0xfff != 0 || c.CIDR&0xffff0fff != 0xb105000d {
return ROMTable{}, errors.New("coresight: invalid component snapshot")
}
table := ROMTable{base: c.Base, count: 960, stride: 4, class: c.Class()}
if c.Class() == 1 {
return table, nil
}
arch, present := c.Architecture()
if !present || arch.Architect != 0x23b || arch.ID != 0x0af7 {
return ROMTable{}, ErrNotROMTable
}
if arch.Revision != 0 || c.DEVID&0xf > 1 {
return ROMTable{}, fmt.Errorf("coresight: unsupported ROM revision %d or format %#x", arch.Revision, c.DEVID&0xf)
}
table.count = 512
if c.DEVID&0xf == 1 {
table.count = 256
table.stride = 8
}
return table, nil
}

// EntryCount returns the architectural entry capacity, not a discovered length.
// A zero return means the table is invalid.
func (t ROMTable) EntryCount() int { return t.count }

// ROMEntry is a detached entry snapshot. Base and power metadata are meaningful
// only when Present is true. End marks a terminator, not an absent interior entry.
// PowerID is scoped to the containing table and is valid only with PowerIDValid.
// A power ID does not establish that the child is powered or safe to access.
type ROMEntry struct {
Raw uint64
Base uint64
Present bool
End bool
PowerID uint8
PowerIDValid bool
}

// ReadEntry reads one architectural entry using numeric 32-bit scalars. It
// validates the index and arguments before traffic, reads both words of a
// 64-bit entry before decoding, and returns a zero entry on failure. Reserved
// encodings, zero offsets in present entries, and address underflow or overflow fail.
// It does not read the child, request power, or acquire cleanup obligations.
func (t ROMTable) ReadEntry(ctx context.Context, reader ScalarReader, index int) (ROMEntry, error) {
if ctx == nil || reader == nil {
return ROMEntry{}, errors.New("coresight: nil context or scalar reader")
}
if index < 0 || index >= t.count {
return ROMEntry{}, fmt.Errorf("coresight: invalid ROM entry index %d", index)
}
address := t.base + uint64(index)*t.stride
low, err := readWord(ctx, reader, address)
if err != nil {
return ROMEntry{}, err
}
raw := uint64(low)
if t.stride == 8 {
high, err := readWord(ctx, reader, address+4)
if err != nil {
return ROMEntry{}, err
}
raw |= uint64(high) << 32
}
entry, err := t.decode(raw)
if err != nil {
return ROMEntry{}, fmt.Errorf("coresight: ROM entry at %#x: %w", address, err)
}
return entry, nil
}

func (t ROMTable) decode(raw uint64) (ROMEntry, error) {
e := ROMEntry{Raw: raw, End: raw == 0}
if e.End {
return e, nil
}
if t.class == 9 && raw&3 == 2 {
return e, nil
}
if raw&2 == 0 || t.class == 9 && raw&3 != 3 {
return ROMEntry{}, errors.New("invalid entry format or presence")
}
if raw&0xe08 != 0 || raw&4 == 0 && raw&0x1f0 != 0 {
return ROMEntry{}, errors.New("reserved entry bits are nonzero")
}
e.Present = raw&1 != 0
if !e.Present {
return e, nil
}
var err error
e.Base, err = t.entryBase(raw)
if err != nil {
return ROMEntry{}, err
}
e.PowerID = uint8(raw >> 4 & 0x1f)
e.PowerIDValid = raw&4 != 0
return e, nil
}

func (t ROMTable) entryBase(raw uint64) (uint64, error) {
offset := int64(raw &^ 0xfff)
if t.stride == 4 {
offset = int64(int32(raw &^ 0xfff))
}
base := t.base + uint64(offset)
if offset == 0 || offset > 0 && base < t.base || offset < 0 && base > t.base {
return 0, errors.New("zero offset or address outside uint64 range")
}
return base, nil
}
204 changes: 204 additions & 0 deletions coresight/rom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package coresight_test

import (
"context"
"errors"
"math"
"testing"

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

func romComponent(class uint8, format uint32) coresight.Component {
return coresight.Component{Base: 0x100000000, CIDR: 0xb105000d | uint32(class)<<12, DEVARCH: 0x47700af7, DEVID: format}
}

func TestROMTableLayout(t *testing.T) {
for _, tt := range []struct {
class uint8
format uint32
count int
}{{1, 0, 960}, {9, 0, 512}, {9, 1, 256}} {
c := romComponent(tt.class, tt.format)
table, err := c.ROMTable()
if err != nil || table.EntryCount() != tt.count {
t.Fatalf("layout=%+v,%v", table, err)
}
m := &componentMemory{words: map[uint64]uint32{}}
stride := uint64(4)
if tt.format == 1 {
stride = 8
}
address := c.Base + uint64(tt.count-1)*stride
m.words[address] = 0x1003
if stride == 8 {
m.words[address+4] = 0
}
entry, err := table.ReadEntry(t.Context(), m, tt.count-1)
if err != nil || entry.Base != c.Base+0x1000 || !entry.Present {
t.Fatalf("last=%+v,%v", entry, err)
}
before := len(m.reads)
for _, i := range []int{-1, tt.count} {
if _, err = table.ReadEntry(t.Context(), m, i); err == nil {
t.Fatal("invalid index accepted")
}
}
if len(m.reads) != before {
t.Fatal("invalid index reached memory")
}
}
}

func TestROMTableRejectsInvalidIdentity(t *testing.T) {
for _, edit := range []func(*coresight.Component){
func(c *coresight.Component) { c.CIDR = 0 }, func(c *coresight.Component) { c.Base++ },
func(c *coresight.Component) { c.DEVID = 2 },
func(c *coresight.Component) { c.DEVARCH |= 1 << 16 }, func(c *coresight.Component) { c.DEVARCH &^= 1 << 20 },
func(c *coresight.Component) { c.DEVARCH ^= 1 << 21 }, func(c *coresight.Component) { c.DEVARCH++ },
} {
c := romComponent(9, 0)
edit(&c)
if _, err := c.ROMTable(); err == nil {
t.Fatalf("accepted %+v", c)
}
}
c := romComponent(0xe, 0)
if _, err := c.ROMTable(); !errors.Is(err, coresight.ErrNotROMTable) {
t.Fatalf("non-table error=%v", err)
}
m := memoryAt(0, 1)
var table coresight.ROMTable
if _, err := table.ReadEntry(t.Context(), m, 0); err == nil || len(m.reads) != 0 {
t.Fatal("zero table read memory")
}
}

func TestROMEntryFormats(t *testing.T) {
for _, tt := range []struct {
name string
class uint8
format uint32
raw uint64
base uint64
present, end, power, invalid bool
}{
{name: "class1 end", class: 1, end: true},
{name: "class1 absent", class: 1, raw: 0x1002},
{name: "class1 positive", class: 1, raw: 0x1003, base: 0x100001000, present: true},
{name: "class1 negative", class: 1, raw: 0xfffff003, base: 0xfffff000, present: true},
{name: "power domain zero", class: 1, raw: 0x1007, base: 0x100001000, present: true, power: true},
{name: "format zero", class: 1, raw: 0x1001, invalid: true},
{name: "all ones is malformed", class: 1, raw: 0xffffffff, invalid: true},
{name: "reserved", class: 1, raw: 0x1203, invalid: true},
{name: "unqualified power", class: 1, raw: 0x1013, invalid: true},
{name: "self", class: 1, raw: 3, invalid: true},
{name: "class9 end", class: 9, end: true},
{name: "class9 malformed end", class: 9, raw: 0x1000, invalid: true},
{name: "class9 reserved presence", class: 9, raw: 0x1001, invalid: true},
{name: "class9 absent unknown bits", class: 9, raw: 0xfffffffe},
{name: "class9 present", class: 9, raw: 0x1003, base: 0x100001000, present: true},
{name: "64-bit positive", class: 9, format: 1, raw: 0x200001003, base: 0x300001000, present: true},
{name: "64-bit negative", class: 9, format: 1, raw: 0xfffffffffffff003, base: 0xfffff000, present: true},
{name: "64-bit malformed end high", class: 9, format: 1, raw: 0x100000000, invalid: true},
} {
t.Run(tt.name, func(t *testing.T) {
c := romComponent(tt.class, tt.format)
table, err := c.ROMTable()
if err != nil {
t.Fatal(err)
}
m := &componentMemory{words: map[uint64]uint32{c.Base: uint32(tt.raw), c.Base + 4: uint32(tt.raw >> 32)}}
got, err := table.ReadEntry(t.Context(), m, 0)
if (err != nil) != tt.invalid {
t.Fatalf("entry=%+v,%v", got, err)
}
if tt.invalid {
if got != (coresight.ROMEntry{}) {
t.Fatal("partial entry returned")
}
return
}
if got.Raw != tt.raw || got.Base != tt.base || got.Present != tt.present || got.End != tt.end || got.PowerIDValid != tt.power {
t.Fatalf("entry=%+v", got)
}
reads := 1
if tt.format == 1 {
reads = 2
}
if len(m.reads) != reads {
t.Fatalf("reads=%v", m.reads)
}
})
}
}

func TestROMEntryAddressBounds(t *testing.T) {
for _, tt := range []struct {
base, raw uint64
valid bool
}{
{0, 0xfffffffffffff003, false}, {math.MaxUint64 - 0xfff, 0x1003, false},
{0x8000000000000000, 0x8000000000000003, true},
{0, 0x7ffffffffffff003, true},
} {
c := romComponent(9, 1)
c.Base = tt.base
table, err := c.ROMTable()
if err != nil {
t.Fatal(err)
}
m := &componentMemory{words: map[uint64]uint32{c.Base: uint32(tt.raw), c.Base + 4: uint32(tt.raw >> 32)}}
if _, err = table.ReadEntry(t.Context(), m, 0); (err == nil) != tt.valid {
t.Fatalf("base=%x raw=%x err=%v", tt.base, tt.raw, err)
}
}
}

func TestROMEntryReadFailure(t *testing.T) {
c := romComponent(9, 1)
table, err := c.ROMTable()
if err != nil {
t.Fatal(err)
}
failure := errors.New("memory failure")
for _, failAt := range []int{1, 2} {
m := &componentMemory{words: map[uint64]uint32{c.Base: 0, c.Base + 4: 0}, failAt: failAt, failure: failure}
got, err := table.ReadEntry(t.Context(), m, 0)
if !errors.Is(err, failure) || got != (coresight.ROMEntry{}) || len(m.reads) != failAt {
t.Fatalf("entry=%+v,%v reads=%v", got, err, m.reads)
}
}
m := memoryAt(c.Base, 1)
var nilContext context.Context
if _, err = table.ReadEntry(nilContext, m, 0); err == nil {
t.Fatal("nil context accepted")
}
if _, err = table.ReadEntry(t.Context(), nil, 0); err == nil {
t.Fatal("nil reader accepted")
}
}

func TestROMEntryCancellation(t *testing.T) {
c := romComponent(9, 1)
table, err := c.ROMTable()
if err != nil {
t.Fatal(err)
}
for _, before := range []bool{false, true} {
ctx, cancel := context.WithCancel(t.Context())
m := &componentMemory{words: map[uint64]uint32{c.Base: 3}, cancel: cancel}
if before {
cancel()
}
entry, err := table.ReadEntry(ctx, m, 0)
cancel()
want := 1
if before {
want = 0
}
if !errors.Is(err, context.Canceled) || entry != (coresight.ROMEntry{}) || len(m.reads) != want {
t.Fatalf("entry=%+v,%v reads=%v", entry, err, m.reads)
}
}
}
Loading
Loading