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 fb1e009..3e1e916 100644 Binary files a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml and b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl index 86c6761..ce4ad60 100644 --- a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl +++ b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl @@ -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) + } + }