From 540986cb0b9048ec403580ebf7b2e05e40cc0684 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Mon, 4 Dec 2017 08:46:43 +0000 Subject: [PATCH] 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. --- src/gopheros/device/acpi/aml/entity.go | 20 +++++- src/gopheros/device/acpi/aml/vm.go | 31 ++++++++- src/gopheros/device/acpi/aml/vm_jumptable.go | 4 ++ src/gopheros/device/acpi/aml/vm_op_flow.go | 14 +++++ .../device/acpi/aml/vm_op_flow_test.go | 59 ++++++++++++++++++ .../table/tabletest/vm-testsuite-DSDT.aml | Bin 646 -> 672 bytes .../table/tabletest/vm-testsuite-DSDT.dsl | 11 ++++ 7 files changed, 135 insertions(+), 4 deletions(-) diff --git a/src/gopheros/device/acpi/aml/entity.go b/src/gopheros/device/acpi/aml/entity.go index 6a70839..7fc13f0 100644 --- a/src/gopheros/device/acpi/aml/entity.go +++ b/src/gopheros/device/acpi/aml/entity.go @@ -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() +} diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index 5ef0d66..3c74690 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -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 diff --git a/src/gopheros/device/acpi/aml/vm_jumptable.go b/src/gopheros/device/acpi/aml/vm_jumptable.go index fa082a7..0c0cb63 100644 --- a/src/gopheros/device/acpi/aml/vm_jumptable.go +++ b/src/gopheros/device/acpi/aml/vm_jumptable.go @@ -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 diff --git a/src/gopheros/device/acpi/aml/vm_op_flow.go b/src/gopheros/device/acpi/aml/vm_op_flow.go index eac20ea..f452462 100644 --- a/src/gopheros/device/acpi/aml/vm_op_flow.go +++ b/src/gopheros/device/acpi/aml/vm_op_flow.go @@ -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()...) +} diff --git a/src/gopheros/device/acpi/aml/vm_op_flow_test.go b/src/gopheros/device/acpi/aml/vm_op_flow_test.go index 7005fb7..c0de2c3 100644 --- a/src/gopheros/device/acpi/aml/vm_op_flow_test.go +++ b/src/gopheros/device/acpi/aml/vm_op_flow_test.go @@ -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) + } + }) +} diff --git a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml index fb1e0090afaa54ba80ad6ec09abefa3a9b6a0ab3..3e1e91657ba5450866f3c15b91190fec06ad992e 100644 GIT binary patch delta 46 xcmZo;UBJra66_MPfQf;D>F`Fb8YU?b9>3ra1I8sl(l7(eF=Sj)l) delta 19 acmZ3$+Q!P|66_Mv#>BwD