1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

acpi: add VM-support for method invocations

The getOpcode() method of methodInvocationEntity has been patched so
that a non-AML opcode is returned (selected to be lastOpcode + 1). The
jumpTable at position [lastOpcode+1] is populated with a method that
handles the method invocation logic.

Whenever a method invocation is encountered, the interpreter will try to
lazilly resolve the invoked method definition. Then, a new execution
context will be allocated on the stack and the method args will be
automatically populated by resolving (via a vmLoad call) all arguments
of the methodInvocationEntity instance. The interpreter will then invoke
the method using the new context, fetch the result value (new ctx
retVal) and resolve it (via vmLoad) back to value that can be stored in
the original context that triggered the method invocation.
This commit is contained in:
Achilleas Anagnostopoulos 2017-12-04 08:46:43 +00:00
parent ad6c7ee991
commit 540986cb0b
7 changed files with 135 additions and 4 deletions

View File

@ -409,7 +409,7 @@ type namedReference struct {
func (ref *namedReference) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool {
if ref.target == nil {
if ref.target = scopeFind(ref.parent, rootNs, ref.targetName); ref.target == nil {
kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, ref.parent.Name())
kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, entName(ref.parent))
return false
}
}
@ -425,11 +425,18 @@ type methodInvocationEntity struct {
method *Method
}
// getOpcode returns a fake opcode (set to numOpcodes) for method invocations.
// This allows the interpreter to register a handler for method invocations in
// its jumpTable that does not produce a conflict with any existing opcodes.
func (m *methodInvocationEntity) getOpcode() opcode {
return numOpcodes
}
func (m *methodInvocationEntity) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool {
if m.method == nil {
var isMethod bool
if m.method, isMethod = scopeFind(m.parent, rootNs, m.methodName).(*Method); !isMethod {
kfmt.Fprintf(errWriter, "could not resolve merenced method: %s (parent: %s)\n", m.methodName, m.parent.Name())
kfmt.Fprintf(errWriter, "could not resolve merenced method: %s (parent: %s)\n", m.methodName, entName(m.parent))
return false
}
}
@ -504,3 +511,12 @@ func (ent *mutexEntity) setTableHandle(h uint8) { ent.tableHandle = h }
type eventEntity struct {
namedEntity
}
// entName returns the entity name if ent is not nil or a blank string otherwise.
func entName(ent Entity) string {
if ent == nil {
return ""
}
return ent.Name()
}

View File

@ -89,7 +89,7 @@ type VM struct {
// value so that it can be used by the data conversion helpers.
sizeOfIntInBits int
jumpTable [numOpcodes]opHandler
jumpTable [numOpcodes + 1]opHandler
}
// NewVM creates a new AML VM and initializes it with the default scope
@ -169,7 +169,7 @@ func (vm *VM) checkEntities() *Error {
switch typ := ent.(type) {
case *Method:
// Do not recurse into methods; ath this stage we are only interested in
// Do not recurse into methods; at this stage we are only interested in
// initializing static entities.
return false
case *bufferEntity:
@ -209,6 +209,33 @@ func (vm *VM) Visit(entType EntityType, visitorFn Visitor) {
scopeVisit(0, vm.rootNS, entType, visitorFn)
}
// execMethod creates a new execution context and invokes the given method
// passing along the supplied args. It populates the retVal of the input
// context with the result of the method invocation.
func (vm *VM) execMethod(ctx *execContext, method *Method, args ...interface{}) *Error {
var (
invCtx = execContext{vm: vm}
err *Error
)
// Resolve invocation args and populate methodArgs for the new context
for argIndex := 0; argIndex < len(args); argIndex++ {
invCtx.methodArg[argIndex], err = vmLoad(ctx, args[argIndex])
if err != nil {
return err
}
}
// Execute method and resolve the return value before storing it to the
// parent context's retVal.
err = vm.execBlock(&invCtx, method)
if err == nil {
ctx.retVal, err = vmLoad(&invCtx, invCtx.retVal)
}
return err
}
// execBlock attempts to execute all AML opcodes in the supplied scoped entity.
// If all opcodes are successfully executed, the provided execContext will be
// updated to reflect the current VM state. Otherwise, an error will be

View File

@ -46,6 +46,10 @@ func (vm *VM) populateJumpTable() {
// Store-related opcodes
vm.jumpTable[opStore] = vmOpStore
// Method invocation dispatcher; to avoid clashes with real AML opcodes,
// method invocations are assigned an opcode with value (lastOpcode + 1)
vm.jumpTable[numOpcodes] = vmOpMethodInvocation
}
// opExecNotImplemented is a placeholder handler that returns a non-implemented

View File

@ -117,3 +117,17 @@ func vmOpIf(ctx *execContext, ent Entity) *Error {
return nil
}
// vmOpMethodInvocation dispatches a method invocation and sets ctx.retVal
// to the value returned by the method invocation. This function also supports
// invocation of methods that are provided by the kernel host such as the ones
// defined in section 5.7 of the ACPI spec.
func vmOpMethodInvocation(ctx *execContext, ent Entity) *Error {
// Make sure the target method is properly resolved
inv := ent.(*methodInvocationEntity)
if !inv.Resolve(ctx.vm.errWriter, ctx.vm.rootNS) {
return &Error{message: "call to undefined method: " + inv.methodName}
}
return ctx.vm.execMethod(ctx, inv.method, ent.getArgs()...)
}

View File

@ -1,6 +1,7 @@
package aml
import (
"io/ioutil"
"os"
"reflect"
"testing"
@ -187,3 +188,61 @@ func TestVMFlowOpErrors(t *testing.T) {
}
}
}
func TestVMNestedMethodCalls(t *testing.T) {
resolver := &mockResolver{
tableFiles: []string{"vm-testsuite-DSDT.aml"},
}
vm := NewVM(ioutil.Discard, resolver)
if err := vm.Init(); err != nil {
t.Fatal(err)
}
t.Run("nested call success", func(t *testing.T) {
inv := &methodInvocationEntity{
unnamedEntity: unnamedEntity{
args: []interface{}{uint64(10)},
},
methodName: `\NST0`,
}
ctx := &execContext{vm: vm}
if err := vmOpMethodInvocation(ctx, inv); err != nil {
t.Fatal(err)
}
if exp := uint64(52); !reflect.DeepEqual(ctx.retVal, exp) {
t.Fatalf("expected return value to be: %v; got: %v", exp, ctx.retVal)
}
})
t.Run("undefined method", func(t *testing.T) {
inv := &methodInvocationEntity{methodName: `UNDEFINED`}
ctx := &execContext{vm: vm}
expErr := "call to undefined method: UNDEFINED"
if err := vmOpMethodInvocation(ctx, inv); err == nil || err.Error() != expErr {
t.Fatalf("expected error: %s; got %v", expErr, err)
}
})
t.Run("method arg load error", func(t *testing.T) {
op0Err := &Error{message: "something went wrong with op 0"}
vm.jumpTable[0] = func(_ *execContext, ent Entity) *Error { return op0Err }
inv := &methodInvocationEntity{
unnamedEntity: unnamedEntity{
args: []interface{}{
&unnamedEntity{}, // vmLoad will invoke jumpTable[0] which always returns an error
},
},
methodName: `\NST0`,
}
ctx := &execContext{vm: vm}
if err := vmOpMethodInvocation(ctx, inv); err != op0Err {
t.Fatalf("expected error: %s; got %v", op0Err, err)
}
})
}

View File

@ -236,4 +236,15 @@ DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x000
Return(Local1)
}
// Netsed method invocations
Method(NST0, 1, NotSerialized)
{
Return(NST1(Arg0))
}
Method(NST1, 1, NotSerialized)
{
Return(Arg0+42)
}
}