diff --git a/src/gopheros/device/acpi/aml/entity/visitor.go b/src/gopheros/device/acpi/aml/entity/visitor.go new file mode 100644 index 0000000..0b82550 --- /dev/null +++ b/src/gopheros/device/acpi/aml/entity/visitor.go @@ -0,0 +1,64 @@ +package entity + +// 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) + +// Type defines the type of entity that visitors should inspect. +type Type uint8 + +// The list of supported Type values. TypeAny works as a wildcard +// allowing the visitor to inspect all entities in the AML tree. +const ( + TypeAny Type = iota + TypeDevice + TypeProcessor + TypePowerResource + TypeThermalZone + TypeMethod + TypeMutex + TypeEvent + TypeField + TypeIndexField + TypeBankField +) + +// Visit descends a scope hierarchy and invokes visitorFn for each entity +// that matches entType. +func Visit(depth int, ent Entity, entType Type, visitorFn Visitor) bool { + op := ent.Opcode() + switch { + case (entType == TypeAny) || + (entType == TypeDevice && op == OpDevice) || + (entType == TypeProcessor && op == OpProcessor) || + (entType == TypePowerResource && op == OpPowerRes) || + (entType == TypeThermalZone && op == OpThermalZone) || + (entType == TypeMethod && op == OpMethod) || + (entType == TypeMutex && op == OpMutex) || + (entType == TypeEvent && op == OpEvent) || + (entType == TypeField && op == OpField) || + (entType == TypeIndexField && op == OpIndexField) || + (entType == TypeBankField && op == OpBankField): + // If the visitor returned false we should not visit the children + if !visitorFn(depth, ent) { + return false + } + + // Visit any args that are also entities + for _, arg := range ent.Args() { + if argEnt, isEnt := arg.(Entity); isEnt && !Visit(depth+1, argEnt, entType, visitorFn) { + return false + } + } + } + + // If the entity defines a scope we need to visit the child entities. + if container, isContainer := ent.(Container); isContainer { + for _, child := range container.Children() { + _ = Visit(depth+1, child, entType, visitorFn) + } + } + + return true +} diff --git a/src/gopheros/device/acpi/aml/entity/visitor_test.go b/src/gopheros/device/acpi/aml/entity/visitor_test.go new file mode 100644 index 0000000..3c7aa4e --- /dev/null +++ b/src/gopheros/device/acpi/aml/entity/visitor_test.go @@ -0,0 +1,93 @@ +package entity + +import "testing" + +func TestScopeVisit(t *testing.T) { + tableHandle := uint8(42) + keepRecursing := func(Entity) bool { return true } + stopRecursing := func(Entity) bool { return false } + + // Append special entities under IDE0 + root := NewScope(tableHandle, "IDE0") + root.Append(NewDevice(tableHandle, "DEV0")) + root.Append(NewProcessor(tableHandle, "FOO0")) + root.Append(NewProcessor(tableHandle, "FOO0")) + root.Append(NewPowerResource(tableHandle, "FOO0")) + root.Append(NewPowerResource(tableHandle, "FOO0")) + root.Append(NewPowerResource(tableHandle, "FOO0")) + root.Append(NewThermalZone(tableHandle, "FOO0")) + root.Append(NewThermalZone(tableHandle, "FOO0")) + root.Append(NewThermalZone(tableHandle, "FOO0")) + root.Append(NewThermalZone(tableHandle, "FOO0")) + root.Append(NewMethod(tableHandle, "MTH0")) + root.Append(NewMethod(tableHandle, "MTH1")) + root.Append(NewMethod(tableHandle, "MTH2")) + root.Append(NewMethod(tableHandle, "MTH3")) + root.Append(NewMethod(tableHandle, "MTH4")) + root.Append(NewMutex(tableHandle)) + root.Append(NewMutex(tableHandle)) + root.Append(NewEvent(tableHandle)) + root.Append(NewEvent(tableHandle)) + root.Append(NewEvent(tableHandle)) + root.Append(NewField(tableHandle)) + root.Append(NewIndexField(tableHandle)) + root.Append(NewBankField(tableHandle)) + root.Append(&Invocation{ + Generic: Generic{ + op: OpMethodInvocation, + args: []interface{}{ + NewConst(OpOne, tableHandle, uint64(1)), + NewConst(OpDwordPrefix, tableHandle, uint64(2)), + }, + }, + }) + + specs := []struct { + searchType Type + keepRecursingFn func(Entity) bool + wantHits int + }{ + {TypeAny, keepRecursing, 27}, + {TypeAny, stopRecursing, 1}, + { + TypeAny, + func(ent Entity) bool { + // Stop recursing after visiting the Invocation entity + _, isInv := ent.(*Invocation) + return !isInv + }, + 25, + }, + { + TypeAny, + func(ent Entity) bool { + // Stop recursing after visiting the first Const entity + _, isConst := ent.(*Const) + return !isConst + }, + 26, + }, + {TypeDevice, keepRecursing, 1}, + {TypeProcessor, keepRecursing, 2}, + {TypePowerResource, keepRecursing, 3}, + {TypeThermalZone, keepRecursing, 4}, + {TypeMethod, keepRecursing, 5}, + {TypeMutex, keepRecursing, 2}, + {TypeEvent, keepRecursing, 3}, + {TypeField, keepRecursing, 1}, + {TypeIndexField, keepRecursing, 1}, + {TypeBankField, keepRecursing, 1}, + } + + for specIndex, spec := range specs { + var hits int + Visit(0, root, spec.searchType, func(_ int, obj Entity) bool { + hits++ + return spec.keepRecursingFn(obj) + }) + + if hits != spec.wantHits { + t.Errorf("[spec %d] expected visitor to be called %d times; got %d", specIndex, spec.wantHits, hits) + } + } +}