diff --git a/src/gopheros/device/acpi/aml/scope.go b/src/gopheros/device/acpi/aml/scope.go new file mode 100644 index 0000000..51ed528 --- /dev/null +++ b/src/gopheros/device/acpi/aml/scope.go @@ -0,0 +1,189 @@ +package aml + +import "strings" + +// Visitor is a function invoked by the VM for each AML tree entity that matches +// a particular type. The return value controls whether the children of this +// entity should also be visited. +type Visitor func(depth int, obj Entity) (keepRecursing bool) + +// EntityType defines the type of entity that visitors should inspect. +type EntityType uint8 + +// The list of supported EntityType values. EntityTypeAny works as a wildcard +// allowing the visitor to inspect all entities in the AML tree. +const ( + EntityTypeAny EntityType = iota + EntityTypeDevice + EntityTypeProcessor + EntityTypePowerResource + EntityTypeThermalZone + EntityTypeMethod +) + +// scopeVisit descends a scope hierarchy and invokes visitorFn for each entity +// that matches entType. +func scopeVisit(depth int, ent Entity, entType EntityType, visitorFn Visitor) bool { + op := ent.getOpcode() + switch { + case (entType == EntityTypeAny) || + (entType == EntityTypeDevice && op == opDevice) || + (entType == EntityTypeProcessor && op == opProcessor) || + (entType == EntityTypePowerResource && op == opPowerRes) || + (entType == EntityTypeThermalZone && op == opThermalZone) || + (entType == EntityTypeMethod && op == opMethod): + // If the visitor returned false we should not visit the children + if !visitorFn(depth, ent) { + return false + } + } + + // If the entity defines a scope we need to visit the child entities. + if scopeEnt, ok := ent.(ScopeEntity); ok { + for _, child := range scopeEnt.Children() { + scopeVisit(depth+1, child, entType, visitorFn) + } + } + + return true +} + +// scopeResolvePath examines a path expression and attempts to break it down +// into a parent and child segment. The parent segment is looked up via the +// regular scope rules specified in page 252 of the ACPI 6.2 spec. If the +// parent scope is found then the function returns back the parent entity and +// the name of the child that should be appended to it. If the expression +// lookup fails then the function returns nil, "". +func scopeResolvePath(curScope, rootScope ScopeEntity, expr string) (parent ScopeEntity, name string) { + if len(expr) <= 1 { + return nil, "" + } + + // Pattern looks like \FOO or ^+BAR or BAZ (relative to curScope) + lastDotIndex := strings.LastIndexByte(expr, '.') + if lastDotIndex == -1 { + switch expr[0] { + case '\\': + return rootScope, expr[1:] + case '^': + lastHatIndex := strings.LastIndexByte(expr, '^') + if target := scopeFind(curScope, rootScope, expr[:lastHatIndex+1]); target != nil { + return target.(ScopeEntity), expr[lastHatIndex+1:] + } + + return nil, "" + default: + return curScope, expr + } + } + + // Pattern looks like: \FOO.BAR.BAZ or ^+FOO.BAR.BAZ or FOO.BAR.BAZ + if target := scopeFind(curScope, rootScope, expr[:lastDotIndex]); target != nil { + return target.(ScopeEntity), expr[lastDotIndex+1:] + } + + return nil, "" +} + +// scopeFind attempts to find an object with the given name 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 scopeFind(curScope, rootScope ScopeEntity, name string) Entity { + nameLen := len(name) + if nameLen == 0 { + return nil + } + + switch { + case name[0] == '\\': // relative to the root scope + if nameLen > 1 { + return scopeFindRelative(rootScope, name[1:]) + } + + // Name was just `\`; this matches the root namespace + return rootScope + case name[0] == '^': // relative to the parent scope(s) + for startIndex := 0; startIndex < nameLen; startIndex++ { + switch name[startIndex] { + case '^': + curScope = curScope.Parent() + + // No parent to visit + if curScope == nil { + return nil + } + default: + // Found the start of the name. Look it up relative to curNs + return scopeFindRelative(curScope, name[startIndex:]) + } + } + + // Name was just a sequence of '^'; this matches the last curScope value + return curScope + case strings.ContainsRune(name, '.'): + // If the name contains any '.' then we still need to look it + // up relative to the current scope + return scopeFindRelative(curScope, name) + default: + // We can apply the search rules described by the spec + for s := curScope; s != nil; s = s.Parent() { + for _, child := range s.Children() { + if child.Name() == name { + return child + } + } + } + } + + // Not found + return nil +} + +// scopeFindRelative returns the Entity referenced by path relative +// to the provided Namespace. If the name contains dots, each segment +// is used to access a nested namespace. If the path does not point +// to a NamedObject then lookupRelativeTo returns back nil. +func scopeFindRelative(ns ScopeEntity, path string) Entity { + var matchName string +matchNextPathSegment: + for { + dotSepIndex := strings.IndexRune(path, '.') + if dotSepIndex != -1 { + matchName = path[:dotSepIndex] + path = path[dotSepIndex+1:] + + // Search for a scoped child named "matchName" + for _, child := range ns.Children() { + childNs, ok := child.(ScopeEntity) + if !ok { + continue + } + + if childNs.Name() == matchName { + ns = childNs + continue matchNextPathSegment + } + } + } else { + // Search for a child named "name" + for _, child := range ns.Children() { + if child.Name() == path { + return child + } + } + } + + // Next segment in the path was not found or last segment not found + break + } + + return nil +} diff --git a/src/gopheros/device/acpi/aml/scope_test.go b/src/gopheros/device/acpi/aml/scope_test.go new file mode 100644 index 0000000..7dd8fec --- /dev/null +++ b/src/gopheros/device/acpi/aml/scope_test.go @@ -0,0 +1,264 @@ +package aml + +import ( + "reflect" + "testing" +) + +func TestScopeVisit(t *testing.T) { + scopeMap := genTestScopes() + root := scopeMap[`\`].(*scopeEntity) + + // Append special entities under IDE0 + ide := scopeMap["IDE0"].(*scopeEntity) + ide.Append(&Device{}) + ide.Append(&namedEntity{op: opProcessor}) + ide.Append(&namedEntity{op: opProcessor}) + ide.Append(&namedEntity{op: opPowerRes}) + ide.Append(&namedEntity{op: opPowerRes}) + ide.Append(&namedEntity{op: opPowerRes}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&Method{}) + ide.Append(&Method{}) + ide.Append(&Method{}) + ide.Append(&Method{}) + ide.Append(&Method{}) + + specs := []struct { + searchType EntityType + keepRecursing bool + wantHits int + }{ + {EntityTypeAny, true, 21}, + {EntityTypeAny, false, 1}, + {EntityTypeDevice, true, 1}, + {EntityTypeProcessor, true, 2}, + {EntityTypePowerResource, true, 3}, + {EntityTypeThermalZone, true, 4}, + {EntityTypeMethod, true, 5}, + } + + for specIndex, spec := range specs { + var hits int + scopeVisit(0, root, spec.searchType, func(_ int, obj Entity) bool { + hits++ + return spec.keepRecursing + }) + + if hits != spec.wantHits { + t.Errorf("[spec %d] expected visitor to be called %d times; got %d", specIndex, spec.wantHits, hits) + } + } +} + +func TestScopeResolvePath(t *testing.T) { + scopeMap := genTestScopes() + + specs := []struct { + curScope ScopeEntity + pathExpr string + wantParent Entity + wantName string + }{ + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_`, + scopeMap[`\`], + "_SB_", + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^FOO`, + scopeMap[`PCI0`], + "FOO", + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^^FOO`, + scopeMap[`_SB_`], + "FOO", + }, + { + scopeMap["IDE0"].(*scopeEntity), + `_ADR`, + scopeMap[`IDE0`], + "_ADR", + }, + // Paths with dots + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_.PCI0.IDE0._ADR`, + scopeMap[`IDE0`], + "_ADR", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `IDE0._ADR`, + scopeMap[`IDE0`], + "_ADR", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `_CRS`, + scopeMap[`PCI0`], + "_CRS", + }, + // Bad queries + { + scopeMap["PCI0"].(*scopeEntity), + `FOO.BAR.BAZ`, + nil, + "", + }, + { + scopeMap["PCI0"].(*scopeEntity), + ``, + nil, + "", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `\`, + nil, + "", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `^^^^^^^^^BADPATH`, + nil, + "", + }, + } + + root := scopeMap[`\`].(*scopeEntity) + for specIndex, spec := range specs { + gotParent, gotName := scopeResolvePath(spec.curScope, root, spec.pathExpr) + if !reflect.DeepEqual(gotParent, spec.wantParent) { + t.Errorf("[spec %d] expected lookup to return %#v; got %#v", specIndex, spec.wantParent, gotParent) + continue + } + + if gotName != spec.wantName { + t.Errorf("[spec %d] expected lookup to return node name %q; got %q", specIndex, spec.wantName, gotName) + } + } +} + +func TestScopeFind(t *testing.T) { + scopeMap := genTestScopes() + + specs := []struct { + curScope ScopeEntity + lookup string + want Entity + }{ + // Search rules do not apply for these cases + { + scopeMap["PCI0"].(*scopeEntity), + `\`, + scopeMap[`\`], + }, + { + scopeMap["PCI0"].(*scopeEntity), + "IDE0._ADR", + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + "^^PCI0.IDE0._ADR", + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_.PCI0.IDE0._ADR`, + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_.PCI0`, + scopeMap["PCI0"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^`, + scopeMap["PCI0"], + }, + // Bad queries + { + scopeMap["_SB_"].(*scopeEntity), + "PCI0.USB._CRS", + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + "^^^^^^^^^^^^^^^^^^^", + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^^^^^^^^^^^FOO`, + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + "FOO", + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + "", + nil, + }, + // Search rules apply for these cases + { + scopeMap["IDE0"].(*scopeEntity), + "_CRS", + scopeMap["_CRS"], + }, + } + + root := scopeMap[`\`].(*scopeEntity) + for specIndex, spec := range specs { + if got := scopeFind(spec.curScope, root, spec.lookup); !reflect.DeepEqual(got, spec.want) { + t.Errorf("[spec %d] expected lookup to return %#v; got %#v", specIndex, spec.want, got) + } + } +} + +func genTestScopes() map[string]Entity { + // Setup the example tree from page 252 of the acpi 6.2 spec + // \ + // SB + // \ + // PCI0 + // | _CRS + // \ + // IDE0 + // | _ADR + ideScope := &scopeEntity{name: `IDE0`} + pciScope := &scopeEntity{name: `PCI0`} + sbScope := &scopeEntity{name: `_SB_`} + rootScope := &scopeEntity{name: `\`} + + adr := &namedEntity{name: `_ADR`} + crs := &namedEntity{name: `_CRS`} + + // Setup tree + ideScope.Append(adr) + pciScope.Append(crs) + pciScope.Append(ideScope) + sbScope.Append(pciScope) + rootScope.Append(sbScope) + + return map[string]Entity{ + "IDE0": ideScope, + "PCI0": pciScope, + "_SB_": sbScope, + "\\": rootScope, + "_ADR": adr, + "_CRS": crs, + } +}