mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
acpi: implement functions for working with AML scopes
The scope resolution rules are specified in page 252 of the ACPI 6.2 spec.
This commit is contained in:
parent
130e11507c
commit
4dd7c0b077
189
src/gopheros/device/acpi/aml/scope.go
Normal file
189
src/gopheros/device/acpi/aml/scope.go
Normal file
@ -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
|
||||
}
|
264
src/gopheros/device/acpi/aml/scope_test.go
Normal file
264
src/gopheros/device/acpi/aml/scope_test.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user