diff --git a/src/gopheros/device/acpi/aml/obj_tree.go b/src/gopheros/device/acpi/aml/obj_tree.go new file mode 100644 index 0000000..f34ee5a --- /dev/null +++ b/src/gopheros/device/acpi/aml/obj_tree.go @@ -0,0 +1,398 @@ +package aml + +const ( + // InvalidIndex is a sentinel value used by ObjectTree to indicate that + // a returned object index is not valid. + InvalidIndex uint32 = (1 << 32) - 1 + + // The size of AML name identifiers in bytes. + amlNameLen = 4 +) + +// fieldElement groups together information about a field element. This +// information can also be obtained by scanning a field element's siblings but +// it is summarized in this structure for convenience. +type fieldElement struct { + // The offset in the address space defined by parent field. + offset uint32 + + // The width of this field element in bits. + width uint32 + + accessLength uint8 + _pad [3]uint8 + + accessType uint8 + accessAttrib uint8 + lockType uint8 + updateType uint8 + + // The index of an Object to use as a connection target. Used only for + // GenericSerialBus and GeneralPurposeIO operation regions. + connectionIndex uint32 + + // The index of the Field (pOpField, pOpIndexField or pOpBankField) that this + // field element belongs to. According to the spec, named fields appear + // at the same scope as their parent. + fieldIndex uint32 +} + +// Object describes an entity encoded in the AML bytestream. +type Object struct { + // The AML ocode that describes this oBject. + opcode uint16 + + // The Index in the opcodeTable for this opcode + infoIndex uint8 + + // The table handle which contains this entity. + tableHandle uint8 + + // Named AML entities provide a fixed-width name which is padded by '_' chars. + name [amlNameLen]byte + + // The following indices refer to other Objects in the ObjectTree + // that allocated this Object instance. Uninitialized indices are + // represented via a math.MaxUint32 value. + index uint32 + parentIndex uint32 + prevSiblingIndex uint32 + nextSiblingIndex uint32 + firstArgIndex uint32 + lastArgIndex uint32 + + // The byte offset in the AML stream where this opcode is defined. + amlOffset uint32 + + // A non-zero value for pkgEnd indicates that this opcode requires deferred + // parsing due to its potentially ambiguous contents. + pkgEnd uint32 + + // A value placeholder for entites that contain values (e.g. int + // or string constants byte slices e.t.c) + value interface{} +} + +// ObjectTree is a structure that contains a tree of AML entities where each +// entity is allocated from a contiguous Object pool. Index #0 of the pool +// contains the root scope ('\') of the AML tree. +type ObjectTree struct { + objPool []*Object + freeListHeadIndex uint32 +} + +// NewObjectTree returns a new ObjectTree instance. +func NewObjectTree() *ObjectTree { + return &ObjectTree{ + freeListHeadIndex: InvalidIndex, + } +} + +// CreateDefaultScopes populates the Object pool with the default scopes +// specified by the ACPI standard: +// +// +-[\] (Root scope) +// +- [_GPE] (General events in GPE register block) +// +- [_PR_] (ACPI 1.0 processor namespace) +// +- [_SB_] (System bus with all device objects) +// +- [_SI_] (System indicators) +// +- [_TZ_] (ACPI 1.0 thermal zone namespace) +func (tree *ObjectTree) CreateDefaultScopes(tableHandle uint8) { + root := tree.newNamedObject(pOpIntScopeBlock, tableHandle, [amlNameLen]byte{'\\'}) + tree.append(root, tree.newNamedObject(pOpIntScopeBlock, tableHandle, [amlNameLen]byte{'_', 'G', 'P', 'E'})) // General events in GPE register block + tree.append(root, tree.newNamedObject(pOpIntScopeBlock, tableHandle, [amlNameLen]byte{'_', 'P', 'R', '_'})) // ACPI 1.0 processor namespace + tree.append(root, tree.newNamedObject(pOpIntScopeBlock, tableHandle, [amlNameLen]byte{'_', 'S', 'B', '_'})) // System bus with all device objects + tree.append(root, tree.newNamedObject(pOpIntScopeBlock, tableHandle, [amlNameLen]byte{'_', 'S', 'I', '_'})) // System indicators + tree.append(root, tree.newNamedObject(pOpIntScopeBlock, tableHandle, [amlNameLen]byte{'_', 'T', 'Z', '_'})) // ACPI 1.0 thermal zone namespace +} + +// newObject allocates a new Object from the Object pool, populates its +// contents and returns back a pointer to it. +func (tree *ObjectTree) newObject(opcode uint16, tableHandle uint8) *Object { + var obj *Object + + // Check the free list first + if tree.freeListHeadIndex != InvalidIndex { + obj = tree.objPool[tree.freeListHeadIndex] + tree.freeListHeadIndex = obj.nextSiblingIndex + } else { + // Allocate new object and attach it to the pool + obj = new(Object) + obj.index = uint32(len(tree.objPool)) + tree.objPool = append(tree.objPool, obj) + } + + obj.opcode = opcode + obj.infoIndex = pOpcodeTableIndex(opcode, true) + obj.tableHandle = tableHandle + obj.parentIndex = InvalidIndex + obj.prevSiblingIndex = InvalidIndex + obj.nextSiblingIndex = InvalidIndex + obj.firstArgIndex = InvalidIndex + obj.lastArgIndex = InvalidIndex + obj.value = nil + + return obj +} + +// newNamedObject allocates a new Object from the Object pool, populates its +// name and returns back a pointer to it. +func (tree *ObjectTree) newNamedObject(opcode uint16, tableHandle uint8, name [amlNameLen]byte) *Object { + obj := tree.newObject(opcode, tableHandle) + obj.name = name + return obj +} + +// append appends arg to obj's argument list. +func (tree *ObjectTree) append(obj, arg *Object) { + arg.parentIndex = obj.index + + if obj.lastArgIndex == InvalidIndex { + obj.firstArgIndex = arg.index + obj.lastArgIndex = arg.index + return + } + + LastArg := tree.ObjectAt(obj.lastArgIndex) + LastArg.nextSiblingIndex = arg.index + arg.prevSiblingIndex = LastArg.index + arg.nextSiblingIndex = InvalidIndex + obj.lastArgIndex = arg.index +} + +// appendAfter appends arg to obj's argument list after nextTo. +func (tree *ObjectTree) appendAfter(obj, arg, nextTo *Object) { + // nextTo is the last arg of obj; this is equivalent to a regular append + if nextTo.nextSiblingIndex == InvalidIndex { + tree.append(obj, arg) + return + } + + arg.parentIndex = obj.index + arg.prevSiblingIndex = nextTo.index + arg.nextSiblingIndex = nextTo.nextSiblingIndex + + tree.ObjectAt(arg.nextSiblingIndex).prevSiblingIndex = arg.index + nextTo.nextSiblingIndex = arg.index +} + +// free appends obj to the tree's free object list allowing it to be re-used by +// future calls to NewObject and NewNamedObject. Callers must ensure that any +// held pointers to the object are not used after calling free. +func (tree *ObjectTree) free(obj *Object) { + if obj.parentIndex != InvalidIndex { + tree.detach(tree.ObjectAt(obj.parentIndex), obj) + } + + if obj.firstArgIndex != InvalidIndex || obj.lastArgIndex != InvalidIndex { + panic("aml.ObjectTree: attempted to free object that still contains argument references") + } + + // Push the object to the top of the free list and change its opcode to + // indicate this is a freed node + obj.opcode = pOpIntFreedObject + obj.nextSiblingIndex = tree.freeListHeadIndex + tree.freeListHeadIndex = obj.index +} + +// detach detaches arg from obj's argument list. +func (tree *ObjectTree) detach(obj, arg *Object) { + if obj.firstArgIndex == arg.index { + obj.firstArgIndex = arg.nextSiblingIndex + } + + if obj.lastArgIndex == arg.index { + obj.lastArgIndex = arg.prevSiblingIndex + } + + if arg.nextSiblingIndex != InvalidIndex { + tree.ObjectAt(arg.nextSiblingIndex).prevSiblingIndex = arg.prevSiblingIndex + } + + if arg.prevSiblingIndex != InvalidIndex { + tree.ObjectAt(arg.prevSiblingIndex).nextSiblingIndex = arg.nextSiblingIndex + } + + arg.prevSiblingIndex = InvalidIndex + arg.nextSiblingIndex = InvalidIndex + arg.parentIndex = InvalidIndex +} + +// ObjectAt returns a pointer to the Object at the specified index or nil if +// no object with this index exists inside the object tree. +func (tree *ObjectTree) ObjectAt(index uint32) *Object { + if index >= uint32(len(tree.objPool)) { + return nil + } + obj := tree.objPool[index] + if obj.opcode == pOpIntFreedObject { + return nil + } + + return obj +} + +// Find attempts to resolve the given expression into an Object using the rules +// specified in page 252 of the ACPI 6.2 spec: +// +// There are two types of namespace paths: an absolute namespace path (that is, +// one that starts with a ‘\’ prefix), and a relative namespace path (that is, +// one that is relative to the current namespace). The namespace search rules +// discussed above, only apply to single NameSeg paths, which is a relative +// namespace path. For those relative name paths that contain multiple NameSegs +// or Parent Prefixes, ‘^’, the search rules do not apply. If the search rules +// do not apply to a relative namespace path, the namespace object is looked up +// relative to the current namespace +func (tree *ObjectTree) Find(scopeIndex uint32, expr []byte) uint32 { + exprLen := len(expr) + if exprLen == 0 || scopeIndex == InvalidIndex { + return InvalidIndex + } + + switch { + case expr[0] == '\\': // relative to the root scope + // Name was just `\`; this matches the root namespace + if exprLen == 1 { + return 0 + } + + return tree.findRelative(0, expr[1:]) + case expr[0] == '^': // relative to the parent scope(s) + for startIndex := 0; startIndex < exprLen; startIndex++ { + switch expr[startIndex] { + case '^': + // Mpve up one parent. If we were at the root scope + // then the lookup failed. + if scopeIndex = tree.ObjectAt(scopeIndex).parentIndex; scopeIndex == InvalidIndex { + return InvalidIndex + } + default: + // Found the start of the name. Look it up relative to current scope + return tree.findRelative(scopeIndex, expr[startIndex:]) + } + } + + // Name was just a sequence of '^'; this matches the last scopeIndex value + return scopeIndex + case exprLen > amlNameLen: + // The expression consists of multiple name segments joined together (e.g. FOOFBAR0) + // In this case we need to apply relative lookup rules for FOOF.BAR0 + return tree.findRelative(scopeIndex, expr) + default: + // expr is a simple name. According to the spec, we need to + // search for it in this scope and all its parent scopes till + // we reach the root. + for nextScopeIndex := scopeIndex; nextScopeIndex != InvalidIndex; nextScopeIndex = tree.ObjectAt(nextScopeIndex).parentIndex { + scopeObj := tree.ObjectAt(nextScopeIndex) + checkNextSibling: + for nextIndex := scopeObj.firstArgIndex; nextIndex != InvalidIndex; nextIndex = tree.ObjectAt(nextIndex).nextSiblingIndex { + obj := tree.ObjectAt(nextIndex) + for byteIndex := 0; byteIndex < amlNameLen; byteIndex++ { + if expr[byteIndex] != obj.name[byteIndex] { + continue checkNextSibling + } + } + + // Found match + return obj.index + } + } + } + + // Not found + return InvalidIndex +} + +// findRelative attempts to resolve an object using relative scope lookup rules. +func (tree *ObjectTree) findRelative(scopeIndex uint32, expr []byte) uint32 { + exprLen := len(expr) + +nextSegment: + for segIndex := 0; segIndex < exprLen; segIndex += amlNameLen { + // If expr contains a dual or multinamed path then we may encounter special + // prefix chars in the stream (the parser extracts the raw data). In this + // case skip over them. + for ; segIndex < exprLen && expr[segIndex] != '_' && (expr[segIndex] < 'A' || expr[segIndex] > 'Z'); segIndex++ { + } + if segIndex >= exprLen { + return InvalidIndex + } + + // Search current scope for an entity matching the next name segment + scopeObj := tree.ObjectAt(scopeIndex) + + checkNextSibling: + for nextIndex := scopeObj.firstArgIndex; nextIndex != InvalidIndex; nextIndex = tree.ObjectAt(nextIndex).nextSiblingIndex { + obj := tree.ObjectAt(nextIndex) + for byteIndex := 0; byteIndex < amlNameLen; byteIndex++ { + if expr[segIndex+byteIndex] != obj.name[byteIndex] { + continue checkNextSibling + } + } + + // Found match; set match as the next scope index and + // try to match the next segment + scopeIndex = nextIndex + continue nextSegment + } + + // Failed to match next segment. Lookup failed + return InvalidIndex + } + + // scopeIndex contains the index of the last matched name in the expression + // which is the target we were looking for. + return scopeIndex +} + +// ClosestNamedAncestor returns the index of the first named object that is an +// ancestor of obj. If any of obj's parents are unresolved scope directives +// then the call will return InvalidIndex. +func (tree *ObjectTree) ClosestNamedAncestor(obj *Object) uint32 { + if obj == nil { + return InvalidIndex + } + + for ancestorIndex := obj.parentIndex; ancestorIndex != InvalidIndex; { + ancestor := tree.ObjectAt(ancestorIndex) + if ancestor.opcode == pOpScope { + break + } + + if pOpcodeTable[ancestor.infoIndex].flags&pOpFlagNamed != 0 { + return ancestorIndex + } + + ancestorIndex = ancestor.parentIndex + } + + return InvalidIndex +} + +// NumArgs returns the number of arguments contained in obj. +func (tree *ObjectTree) NumArgs(obj *Object) uint32 { + if obj == nil { + return 0 + } + + var argCount uint32 + for siblingIndex := obj.firstArgIndex; siblingIndex != InvalidIndex; siblingIndex = tree.ObjectAt(siblingIndex).nextSiblingIndex { + argCount++ + } + + return argCount +} + +// ArgAt returns a pointer to obj's arg located at index. +func (tree *ObjectTree) ArgAt(obj *Object, index uint32) *Object { + if obj == nil { + return nil + } + for argIndex, siblingIndex := uint32(0), obj.firstArgIndex; siblingIndex != InvalidIndex; argIndex, siblingIndex = argIndex+1, tree.ObjectAt(siblingIndex).nextSiblingIndex { + if argIndex == index { + return tree.ObjectAt(siblingIndex) + } + } + + return nil +} diff --git a/src/gopheros/device/acpi/aml/obj_tree_test.go b/src/gopheros/device/acpi/aml/obj_tree_test.go new file mode 100644 index 0000000..fb488a0 --- /dev/null +++ b/src/gopheros/device/acpi/aml/obj_tree_test.go @@ -0,0 +1,434 @@ +package aml + +import ( + "fmt" + "testing" +) + +func TestTreeObjectAt(t *testing.T) { + tree := NewObjectTree() + + root := tree.newObject(pOpIntScopeBlock, 0) + obj1 := tree.newObject(pOpIntScopeBlock, 1) + + tree.append(root, obj1) + + if got := tree.ObjectAt(obj1.index); got != obj1 { + t.Fatalf("expected to get back obj1; got %#+v", got) + } + + if got := tree.ObjectAt(obj1.index + 1); got != nil { + t.Fatalf("expected to get nil; got %#+v", got) + } + + tree.free(obj1) + if got := tree.ObjectAt(obj1.index); got != nil { + t.Fatalf("expected to get back nil after freeing obj1; got %#+v", got) + } +} + +func TestTreeFreelist(t *testing.T) { + tree := NewObjectTree() + + root := tree.newObject(pOpIntScopeBlock, 0) + obj1 := tree.newObject(pOpIntScopeBlock, 1) + obj2 := tree.newObject(pOpIntScopeBlock, 2) + obj3 := tree.newObject(pOpIntScopeBlock, 3) + + tree.append(root, obj1) + tree.append(root, obj2) + tree.append(root, obj3) + + // By freeing these objects they will be re-used by the following NewObject + // calls in LIFO order. + obj2Index := obj2.index + obj3Index := obj3.index + tree.free(obj3) + tree.free(obj2) + + newObj := tree.newObject(pOpIntScopeBlock, 4) + if newObj.index != obj2Index { + t.Errorf("expected object index to be %d; got %d", obj2Index, newObj.index) + } + + newObj = tree.newObject(pOpIntScopeBlock, 4) + if newObj.index != obj3Index { + t.Errorf("expected object index to be %d; got %d", obj3Index, newObj.index) + } + + if tree.freeListHeadIndex != InvalidIndex { + t.Errorf("expected free list head index to be InvalidIndex; got %d", tree.freeListHeadIndex) + } +} + +func TestTreeFreelistPanic(t *testing.T) { + tree := NewObjectTree() + tree.CreateDefaultScopes(42) + + defer func() { + expErr := "aml.ObjectTree: attempted to free object that still contains argument references" + if err := recover(); err != expErr { + t.Fatalf("expected call to Free to panic with: %s; got: %v", expErr, err) + } + }() + + // Call should panic as root contains argument references + tree.free(tree.ObjectAt(0)) +} + +func TestTreeAppend(t *testing.T) { + tree := NewObjectTree() + + root := tree.newObject(pOpIntScopeBlock, 0) + obj1 := tree.newObject(pOpIntScopeBlock, 1) + obj2 := tree.newObject(pOpIntScopeBlock, 2) + obj3 := tree.newObject(pOpIntScopeBlock, 3) + + if root.firstArgIndex != InvalidIndex || root.lastArgIndex != InvalidIndex { + t.Fatal("expected root First/Last arg indices to be InvalidIndex") + } + + tree.append(root, obj1) + if root.firstArgIndex != obj1.index || root.lastArgIndex != obj1.index { + t.Fatal("expected root First/Last arg indices to point to obj1") + } + + if obj1.parentIndex != root.index { + t.Fatal("expected obj1 parent index to point to root") + } + + if obj1.firstArgIndex != InvalidIndex || obj1.lastArgIndex != InvalidIndex { + t.Fatal("expected obj1 First/Last arg indices to be InvalidIndex") + } + + // Attach the remaining pointers and follow the links both ways + tree.append(root, obj2) + tree.append(root, obj3) + + visitedObjects := 0 + for i := root.firstArgIndex; i != InvalidIndex; i = tree.ObjectAt(i).nextSiblingIndex { + visitedObjects++ + } + + if want := 3; visitedObjects != want { + t.Fatalf("expected to visit %d objects in left -> right traversal; visited %d", want, visitedObjects) + } + + visitedObjects = 0 + for i := root.lastArgIndex; i != InvalidIndex; i = tree.ObjectAt(i).prevSiblingIndex { + visitedObjects++ + } + + if want := 3; visitedObjects != want { + t.Fatalf("expected to visit %d objects in right -> left traversal; visited %d", want, visitedObjects) + } +} + +func TestTreeAppendAfter(t *testing.T) { + tree := NewObjectTree() + + root := tree.newObject(pOpIntScopeBlock, 0) + obj1 := tree.newObject(pOpIntScopeBlock, 1) + obj2 := tree.newObject(pOpIntScopeBlock, 2) + obj3 := tree.newObject(pOpIntScopeBlock, 3) + + if root.firstArgIndex != InvalidIndex || root.lastArgIndex != InvalidIndex { + t.Fatal("expected root First/Last arg indices to be InvalidIndex") + } + + tree.append(root, obj1) + + tree.appendAfter(root, obj2, obj1) + tree.appendAfter(root, obj3, obj1) + + expIndexList := []uint32{obj1.index, obj3.index, obj2.index} + + visitedObjects := 0 + for i := root.firstArgIndex; i != InvalidIndex; i = tree.ObjectAt(i).nextSiblingIndex { + if i != expIndexList[visitedObjects] { + t.Fatalf("expected arg %d to have index %d; got %d", visitedObjects, expIndexList[visitedObjects], i) + } + visitedObjects++ + } + + if want := 3; visitedObjects != want { + t.Fatalf("expected to visit %d objects in left -> right traversal; visited %d", want, visitedObjects) + } +} + +func TestTreeDetach(t *testing.T) { + tree := NewObjectTree() + + root := tree.newObject(pOpIntScopeBlock, 0) + obj1 := tree.newObject(pOpIntScopeBlock, 1) + obj2 := tree.newObject(pOpIntScopeBlock, 2) + obj3 := tree.newObject(pOpIntScopeBlock, 3) + + t.Run("detach in reverse order", func(t *testing.T) { + tree.append(root, obj1) + tree.append(root, obj2) + tree.append(root, obj3) + + tree.detach(root, obj3) + if root.firstArgIndex != obj1.index || root.lastArgIndex != obj2.index { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", obj1.index, obj2.index, root.firstArgIndex, root.lastArgIndex) + } + + tree.detach(root, obj2) + if root.firstArgIndex != obj1.index || root.lastArgIndex != obj1.index { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", obj1.index, obj1.index, root.firstArgIndex, root.lastArgIndex) + } + + tree.detach(root, obj1) + if root.firstArgIndex != InvalidIndex || root.lastArgIndex != InvalidIndex { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", InvalidIndex, InvalidIndex, root.firstArgIndex, root.lastArgIndex) + } + }) + + t.Run("detach in insertion order", func(t *testing.T) { + tree.append(root, obj1) + tree.append(root, obj2) + tree.append(root, obj3) + + tree.detach(root, obj1) + if root.firstArgIndex != obj2.index || root.lastArgIndex != obj3.index { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", obj2.index, obj3.index, root.firstArgIndex, root.lastArgIndex) + } + + tree.detach(root, obj2) + if root.firstArgIndex != obj3.index || root.lastArgIndex != obj3.index { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", obj3.index, obj3.index, root.firstArgIndex, root.lastArgIndex) + } + + tree.detach(root, obj3) + if root.firstArgIndex != InvalidIndex || root.lastArgIndex != InvalidIndex { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", InvalidIndex, InvalidIndex, root.firstArgIndex, root.lastArgIndex) + } + }) + + t.Run("detach middle node and then edges", func(t *testing.T) { + tree.append(root, obj1) + tree.append(root, obj2) + tree.append(root, obj3) + + tree.detach(root, obj2) + if root.firstArgIndex != obj1.index || root.lastArgIndex != obj3.index { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", obj1.index, obj3.index, root.firstArgIndex, root.lastArgIndex) + } + + if obj1.nextSiblingIndex != obj3.index { + t.Fatalf("expected obj1 NextSiblingIndex to be %d; got %d", obj3.index, obj1.nextSiblingIndex) + } + + if obj3.prevSiblingIndex != obj1.index { + t.Fatalf("expected obj3 PrevSiblingIndex to be %d; got %d", obj1.index, obj3.prevSiblingIndex) + } + + tree.detach(root, obj1) + if root.firstArgIndex != obj3.index || root.lastArgIndex != obj3.index { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", obj3.index, obj3.index, root.firstArgIndex, root.lastArgIndex) + } + + tree.detach(root, obj3) + if root.firstArgIndex != InvalidIndex || root.lastArgIndex != InvalidIndex { + t.Fatalf("unexpected first/last indices: want (%d, %d); got (%d, %d)", InvalidIndex, InvalidIndex, root.firstArgIndex, root.lastArgIndex) + } + }) +} + +func TestFind(t *testing.T) { + tree, scopeMap := genTestScopes() + + specs := []struct { + curScope uint32 + expr string + want uint32 + }{ + // Search rules do not apply for these cases + { + scopeMap["PCI0"], + `\`, + scopeMap[`\`], + }, + { + scopeMap["PCI0"], + "IDE0_ADR", + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"], + "^^PCI0IDE0_ADR", + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"], + `\_SB_PCI0IDE0_ADR`, + scopeMap["_ADR"], + }, + // Raw multi-segment path (prefix 0x2f, segCount: 3) + { + scopeMap["_ADR"], + fmt.Sprintf("\\%c%c_SB_PCI0IDE0", 0x2f, 0x03), + scopeMap["IDE0"], + }, + { + scopeMap["IDE0"], + `\_SB_PCI0`, + scopeMap["PCI0"], + }, + { + scopeMap["IDE0"], + `^`, + scopeMap["PCI0"], + }, + // Bad queries + { + scopeMap["_SB_"], + "PCI0USB0_CRS", + InvalidIndex, + }, + { + scopeMap["IDE0"], + "^^^^^^^^^^^^^^^^^^^", + InvalidIndex, + }, + { + scopeMap["IDE0"], + `^^^^^^^^^^^FOO`, + InvalidIndex, + }, + { + scopeMap["IDE0"], + "FOO", + InvalidIndex, + }, + { + scopeMap["IDE0"], + "", + InvalidIndex, + }, + // Incomplete multi-segment path (prefix 0x2f, segCount: 3) + { + scopeMap["_ADR"], + fmt.Sprintf("\\%c%c?", 0x2f, 0x03), + InvalidIndex, + }, + // Search rules apply for these cases + { + scopeMap["IDE0"], + "_CRS", + scopeMap["_CRS"], + }, + } + + for specIndex, spec := range specs { + if got := tree.Find(spec.curScope, []byte(spec.expr)); got != spec.want { + t.Errorf("[spec %d] expected lookup to return index %d; got %d", specIndex, spec.want, got) + } + } +} + +func TestNumArgs(t *testing.T) { + tree := NewObjectTree() + tree.CreateDefaultScopes(42) + + if exp, got := uint32(5), tree.NumArgs(tree.ObjectAt(0)); got != exp { + t.Fatalf("expected NumArgs(root) to return %d; got %d", exp, got) + } + + if got := tree.NumArgs(nil); got != 0 { + t.Fatalf("expected NumArgs(nil) to return 0; got %d", got) + } +} + +func TestArgAt(t *testing.T) { + tree := NewObjectTree() + tree.CreateDefaultScopes(42) + + root := tree.ObjectAt(0) + arg0 := tree.ArgAt(root, 0) + expName := [amlNameLen]byte{'_', 'G', 'P', 'E'} + if arg0.name != expName { + t.Errorf("expected ArgAt(root, 0) to return object with name: %s; got: %s", string(expName[:]), string(arg0.name[:])) + } + + if got := tree.ArgAt(root, InvalidIndex); got != nil { + t.Errorf("expected ArgAt(root, InvalidIndex) to return nil; got: %v", got) + } + + if got := tree.ArgAt(nil, 1); got != nil { + t.Fatalf("expected ArgAt(nil, x) to return nil; got %#+v", got) + } +} + +func TestClosestNamedAncestor(t *testing.T) { + tree := NewObjectTree() + root := tree.newObject(pOpIntScopeBlock, 0) + obj1 := tree.newObject(pOpMethod, 1) + obj2 := tree.newObject(pOpIf, 2) + scope := tree.newObject(pOpScope, 3) + obj3 := tree.newObject(pOpIf, 4) + + tree.append(root, obj1) + tree.append(obj1, obj2) + tree.append(obj2, scope) + tree.append(scope, obj3) + + if got := tree.ClosestNamedAncestor(obj3); got != InvalidIndex { + t.Errorf("expected ClosestNamedAncestor to return InvalidIndex; got %d", got) + } + + // No parent exists + if got := tree.ClosestNamedAncestor(root); got != InvalidIndex { + t.Errorf("expected ClosestNamedAncestor to return InvalidIndex; got %d", got) + } + + // Make the root a non-named object and call ClosestNamedAncestor on its child + root.opcode = pOpAdd + root.infoIndex = pOpcodeTableIndex(root.opcode, false) + if got := tree.ClosestNamedAncestor(obj1); got != InvalidIndex { + t.Errorf("expected ClosestNamedAncestor to return InvalidIndex; got %d", got) + } + + if got := tree.ClosestNamedAncestor(nil); got != InvalidIndex { + t.Fatalf("expected ClosestNamedAncestor(nil) to return InvalidIndex; got %d", got) + } +} + +func genTestScopes() (*ObjectTree, map[string]uint32) { + // Setup the example tree from page 252 of the acpi 6.2 spec + // \ + // SB + // \ + // PCI0 + // | _CRS + // \ + // IDE0 + // | _ADR + + tree := NewObjectTree() + + root := tree.newNamedObject(pOpIntScopeBlock, 0, [4]byte{'\\'}) + pci := tree.newNamedObject(pOpIntScopeBlock, 0, [4]byte{'P', 'C', 'I', '0'}) + ide := tree.newNamedObject(pOpIntScopeBlock, 0, [4]byte{'I', 'D', 'E', '0'}) + sb := tree.newNamedObject(pOpIntScopeBlock, 0, [4]byte{'_', 'S', 'B', '_'}) + + crs := tree.newNamedObject(pOpIntScopeBlock, 0, [4]byte{'_', 'C', 'R', 'S'}) + adr := tree.newNamedObject(pOpIntScopeBlock, 0, [4]byte{'_', 'A', 'D', 'R'}) + + // Setup tree + tree.append(root, sb) + tree.append(sb, pci) + tree.append(pci, crs) + tree.append(pci, ide) + tree.append(ide, adr) + + return tree, map[string]uint32{ + "IDE0": ide.index, + "PCI0": pci.index, + "_SB_": sb.index, + "\\": root.index, + "_ADR": adr.index, + "_CRS": crs.index, + } +}