From 38b2a3e4e27ed9ae1f39d4e76df557cfda30fefd Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Tue, 21 Nov 2017 08:21:11 +0000 Subject: [PATCH] acpi: add VM-support for AML arithmetic opcodes --- src/gopheros/device/acpi/aml/vm.go | 1 + src/gopheros/device/acpi/aml/vm_convert.go | 1 - .../device/acpi/aml/vm_convert_test.go | 24 ++-- src/gopheros/device/acpi/aml/vm_jumptable.go | 15 ++ src/gopheros/device/acpi/aml/vm_load_store.go | 10 +- .../device/acpi/aml/vm_load_store_test.go | 25 +++- src/gopheros/device/acpi/aml/vm_op_alu.go | 136 ++++++++++++++++++ .../device/acpi/aml/vm_op_alu_test.go | 128 +++++++++++++++++ src/gopheros/device/acpi/aml/vm_op_flow.go | 16 +++ .../device/acpi/aml/vm_op_flow_test.go | 12 ++ src/gopheros/device/acpi/aml/vm_op_store.go | 17 +++ .../device/acpi/aml/vm_op_store_test.go | 41 ++++++ .../table/tabletest/vm-testsuite-DSDT.aml | Bin 0 -> 224 bytes .../table/tabletest/vm-testsuite-DSDT.dsl | 84 +++++++++++ 14 files changed, 496 insertions(+), 14 deletions(-) create mode 100644 src/gopheros/device/acpi/aml/vm_op_alu.go create mode 100644 src/gopheros/device/acpi/aml/vm_op_alu_test.go create mode 100644 src/gopheros/device/acpi/aml/vm_op_flow.go create mode 100644 src/gopheros/device/acpi/aml/vm_op_flow_test.go create mode 100644 src/gopheros/device/acpi/aml/vm_op_store.go create mode 100644 src/gopheros/device/acpi/aml/vm_op_store_test.go create mode 100644 src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml create mode 100644 src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index cd05166..df881be 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -18,6 +18,7 @@ var ( errCopyFailed = &Error{message: "vmCopyObject: copy failed"} errConversionFailed = &Error{message: "vmConvert: conversion failed"} errArgIndexOutOfBounds = &Error{message: "vm: arg index out of bounds"} + errDivideByZero = &Error{message: "vm: division by zero"} ) // objRef is a pointer to an argument (local or global) or a named AML object. diff --git a/src/gopheros/device/acpi/aml/vm_convert.go b/src/gopheros/device/acpi/aml/vm_convert.go index dc689d1..a8a960f 100644 --- a/src/gopheros/device/acpi/aml/vm_convert.go +++ b/src/gopheros/device/acpi/aml/vm_convert.go @@ -167,7 +167,6 @@ func vmConvert(ctx *execContext, arg interface{}, toType valueType) (interface{} return strconv.FormatUint(argAsInt, 16), nil } } - return nil, errConversionFailed } diff --git a/src/gopheros/device/acpi/aml/vm_convert_test.go b/src/gopheros/device/acpi/aml/vm_convert_test.go index 9125f3f..e55321d 100644 --- a/src/gopheros/device/acpi/aml/vm_convert_test.go +++ b/src/gopheros/device/acpi/aml/vm_convert_test.go @@ -284,6 +284,13 @@ func TestVMToIntArgs2(t *testing.T) { } func TestVMConvert(t *testing.T) { + vm := NewVM(nil, nil) + vm.populateJumpTable() + + vm.jumpTable[0] = func(_ *execContext, ent Entity) *Error { + return &Error{message: "something went wrong"} + } + specs := []struct { ctx *execContext in interface{} @@ -347,15 +354,7 @@ func TestVMConvert(t *testing.T) { "feedfacedeadc0de", nil, }, - // vmLoad returns an error - { - nil, - &unnamedEntity{op: opAdd}, - valueTypeInteger, - nil, - &Error{message: "readArg: unsupported entity type: " + opAdd.String()}, - }, - // unsupported conversion + // conversion to unsupported type { nil, uint64(42), @@ -363,6 +362,13 @@ func TestVMConvert(t *testing.T) { nil, errConversionFailed, }, + { + &execContext{vm: vm}, + &unnamedEntity{op: 0}, // uses our patched jumpTable[0] that always errors + valueTypeString, + nil, + &Error{message: "vmLoad: something went wrong"}, + }, } for specIndex, spec := range specs { diff --git a/src/gopheros/device/acpi/aml/vm_jumptable.go b/src/gopheros/device/acpi/aml/vm_jumptable.go index 25f91bb..e23f9a2 100644 --- a/src/gopheros/device/acpi/aml/vm_jumptable.go +++ b/src/gopheros/device/acpi/aml/vm_jumptable.go @@ -9,6 +9,21 @@ func (vm *VM) populateJumpTable() { for i := 0; i < len(vm.jumpTable); i++ { vm.jumpTable[i] = opExecNotImplemented } + + // Control-flow opcodes + vm.jumpTable[opReturn] = vmOpReturn + + // ALU opcodes + vm.jumpTable[opAdd] = vmOpAdd + vm.jumpTable[opSubtract] = vmOpSubtract + vm.jumpTable[opIncrement] = vmOpIncrement + vm.jumpTable[opDecrement] = vmOpDecrement + vm.jumpTable[opMultiply] = vmOpMultiply + vm.jumpTable[opDivide] = vmOpDivide + vm.jumpTable[opMod] = vmOpMod + + // Store-related opcodes + vm.jumpTable[opStore] = vmOpStore } // opExecNotImplemented is a placeholder handler that returns a non-implemented diff --git a/src/gopheros/device/acpi/aml/vm_load_store.go b/src/gopheros/device/acpi/aml/vm_load_store.go index 4a91899..5a86c4b 100644 --- a/src/gopheros/device/acpi/aml/vm_load_store.go +++ b/src/gopheros/device/acpi/aml/vm_load_store.go @@ -19,7 +19,15 @@ func vmLoad(ctx *execContext, arg interface{}) (interface{}, *Error) { case opIsMethodArg(op): arg = ctx.methodArg[op-opArg0] default: - return nil, &Error{message: "readArg: unsupported entity type: " + op.String()} + // Val may be a nested opcode (e.g Add(Add(1,1), 2)) + // In this case, try evaluating the opcode and replace arg with the + // output value that gets stored stored into ctx.retVal + if err := ctx.vm.jumpTable[typ.getOpcode()](ctx, typ); err != nil { + err.message = "vmLoad: " + err.message + return nil, err + } + + arg = ctx.retVal } case bool: // Convert boolean results to ints so they can be used diff --git a/src/gopheros/device/acpi/aml/vm_load_store_test.go b/src/gopheros/device/acpi/aml/vm_load_store_test.go index caece4c..f9eed22 100644 --- a/src/gopheros/device/acpi/aml/vm_load_store_test.go +++ b/src/gopheros/device/acpi/aml/vm_load_store_test.go @@ -6,6 +6,18 @@ import ( ) func TestVMLoad(t *testing.T) { + vm := NewVM(nil, nil) + vm.populateJumpTable() + + vm.jumpTable[0] = func(_ *execContext, ent Entity) *Error { + return &Error{message: "something went wrong"} + } + + vm.jumpTable[1] = func(ctx *execContext, ent Entity) *Error { + ctx.retVal = uint64(123) + return nil + } + // Use a pointer to ensure that when we dereference an objRef we get // back the same pointer uniqueVal := &execContext{} @@ -74,12 +86,19 @@ func TestVMLoad(t *testing.T) { aRef, nil, }, - // Unsupported reads + // nested opcode which returns an error { + &execContext{vm: vm}, + &unnamedEntity{op: 0}, // uses our patched jumpTable[0] that always errors nil, - &unnamedEntity{op: opBuffer}, + &Error{message: "vmLoad: something went wrong"}, + }, + // nested opcode which does not return an error + { + &execContext{vm: vm}, + &unnamedEntity{op: 1}, // uses our patched jumpTable[1] + uint64(123), nil, - &Error{message: "readArg: unsupported entity type: Buffer"}, }, } diff --git a/src/gopheros/device/acpi/aml/vm_op_alu.go b/src/gopheros/device/acpi/aml/vm_op_alu.go new file mode 100644 index 0000000..b528d91 --- /dev/null +++ b/src/gopheros/device/acpi/aml/vm_op_alu.go @@ -0,0 +1,136 @@ +package aml + +// Args: left, right, store? +// Returns: left + right +func vmOpAdd(ctx *execContext, ent Entity) *Error { + var ( + left, right uint64 + err *Error + ) + + if left, right, err = vmToIntArgs2(ctx, ent, 0, 1); err != nil { + return err + } + + ctx.retVal = left + right + return vmCondStore(ctx, ctx.retVal, ent, 2) +} + +// Args: left, right, store? +// Returns: left - right +func vmOpSubtract(ctx *execContext, ent Entity) *Error { + var ( + left, right uint64 + err *Error + ) + + if left, right, err = vmToIntArgs2(ctx, ent, 0, 1); err != nil { + return err + } + + ctx.retVal = left - right + return vmCondStore(ctx, ctx.retVal, ent, 2) +} + +// Args: left, store? +// Returns: left + 1 +// Stores: left <= left + 1 +func vmOpIncrement(ctx *execContext, ent Entity) *Error { + var ( + left uint64 + err *Error + ) + + if left, err = vmToIntArg(ctx, ent, 0); err != nil { + return err + } + + // The result is stored back into the left operand + ctx.retVal = left + 1 + if err := vmStore(ctx, ctx.retVal, ent.getArgs()[0]); err != nil { + return err + } + return vmCondStore(ctx, ctx.retVal, ent, 1) +} + +// Args: left, store? +// Returns: left - 1 +// Stores: left <= left - 1 +func vmOpDecrement(ctx *execContext, ent Entity) *Error { + var ( + left uint64 + err *Error + ) + + if left, err = vmToIntArg(ctx, ent, 0); err != nil { + return err + } + + // The result is stored back into the left operand + ctx.retVal = left - 1 + if err := vmStore(ctx, ctx.retVal, ent.getArgs()[0]); err != nil { + return err + } + return vmCondStore(ctx, ctx.retVal, ent, 1) +} + +// Args: left, right, store? +// Returns: left * right +func vmOpMultiply(ctx *execContext, ent Entity) *Error { + var ( + left, right uint64 + err *Error + ) + + if left, right, err = vmToIntArgs2(ctx, ent, 0, 1); err != nil { + return err + } + + ctx.retVal = left * right + return vmCondStore(ctx, ctx.retVal, ent, 2) +} + +// Args: left, right, remainder_store?, quotient_store? +// Returns: left / right; errDivideByZero if right == 0 +func vmOpDivide(ctx *execContext, ent Entity) *Error { + var ( + left, right uint64 + err *Error + ) + + if left, right, err = vmToIntArgs2(ctx, ent, 0, 1); err != nil { + return err + } + + if right == 0 { + return errDivideByZero + } + + ctx.retVal = left / right + if err = vmCondStore(ctx, ctx.retVal, ent, 3); err != nil { + return err + } + + // opDivide can also spefify a target for storing the remainder + return vmCondStore(ctx, left%right, ent, 2) +} + +// Args: left, right, remainder_store? +// Returns: left % right; errDivideByZero if right == 0 +func vmOpMod(ctx *execContext, ent Entity) *Error { + var ( + left, right uint64 + err *Error + ) + + if left, right, err = vmToIntArgs2(ctx, ent, 0, 1); err != nil { + return err + } + + if right == 0 { + return errDivideByZero + } + + ctx.retVal = left % right + return vmCondStore(ctx, ctx.retVal, ent, 2) +} diff --git a/src/gopheros/device/acpi/aml/vm_op_alu_test.go b/src/gopheros/device/acpi/aml/vm_op_alu_test.go new file mode 100644 index 0000000..940d568 --- /dev/null +++ b/src/gopheros/device/acpi/aml/vm_op_alu_test.go @@ -0,0 +1,128 @@ +package aml + +import ( + "os" + "reflect" + "testing" +) + +func TestArithmeticExpressions(t *testing.T) { + specs := []struct { + method string + input interface{} + exp uint64 + }{ + {`\AR00`, uint64(10), uint64(15)}, + {`\ARI0`, uint64(10), uint64(15)}, + {`\AR01`, uint64(6), uint64(1)}, + {`\ARI1`, uint64(6), uint64(1)}, + {`\AR02`, uint64(3), uint64(24)}, + {`\ARI2`, uint64(3), uint64(24)}, + {`\AR03`, uint64(42), uint64(41)}, + {`\ARI3`, uint64(42), uint64(41)}, + {`\AR04`, uint64(42), uint64(43)}, + {`\ARI4`, uint64(42), uint64(43)}, + {`\AR05`, uint64(100), uint64(0)}, + {`\ARI5`, uint64(100), uint64(0)}, + {`\AR06`, uint64(100), uint64(10)}, + {`\ARI6`, uint64(100), uint64(10)}, + {`\AR06`, uint64(101), uint64(11)}, + {`\ARI6`, uint64(101), uint64(10)}, + } + + resolver := &mockResolver{ + tableFiles: []string{"vm-testsuite-DSDT.aml"}, + } + + vm := NewVM(os.Stderr, resolver) + if err := vm.Init(); err != nil { + t.Fatal(err) + } + + for specIndex, spec := range specs { + m := vm.Lookup(spec.method) + if m == nil { + t.Errorf("error looking up method: %q", spec.method) + continue + } + + method := m.(*Method) + + ctx := &execContext{ + methodArg: [maxMethodArgs]interface{}{spec.input}, + vm: vm, + } + + if err := vm.execBlock(ctx, method); err != nil { + t.Errorf("[spec %02d] %s: invocation failed: %v\n", specIndex, spec.method, err) + continue + } + + if !reflect.DeepEqual(ctx.retVal, spec.exp) { + t.Errorf("[spec %02d] %s: expected %d; got %v\n", specIndex, spec.method, spec.exp, ctx.retVal) + } + } +} + +func TestArithmeticExpressionErrors(t *testing.T) { + t.Run("arg handling errors", func(t *testing.T) { + specs := []opHandler{ + vmOpAdd, + vmOpSubtract, + vmOpIncrement, + vmOpDecrement, + vmOpMultiply, + vmOpDivide, + vmOpMod, + } + + for specIndex, handler := range specs { + if err := handler(nil, new(unnamedEntity)); err == nil { + t.Errorf("[spec %d] expected opHandler to return an error", specIndex) + } + } + }) + + t.Run("division by zero errors", func(t *testing.T) { + specs := []opHandler{ + vmOpDivide, + vmOpMod, + } + + ent := &unnamedEntity{ + args: []interface{}{ + &constEntity{val: uint64(1)}, + &constEntity{val: uint64(0)}, + }, + } + for specIndex, handler := range specs { + if err := handler(nil, ent); err != errDivideByZero { + t.Errorf("[spec %d] expected opHandler to return errDivideByZero; got %v", specIndex, err) + } + } + }) + + t.Run("secondary value store errors", func(t *testing.T) { + specs := []opHandler{ + vmOpIncrement, + vmOpDecrement, + vmOpDivide, + vmOpMod, + } + + ctx := new(execContext) + ent := &unnamedEntity{ + args: []interface{}{ + uint64(64), + &constEntity{val: uint64(4)}, + "foo", // error: store target must be an AML entity + "bar", // error: store target must be an AML entity + }, + } + for specIndex, handler := range specs { + if err := handler(ctx, ent); err != errInvalidStoreDestination { + t.Errorf("[spec %d] expected opHandler to return errInvalidStoreDestination; got %v", specIndex, err) + } + } + }) +} diff --git a/src/gopheros/device/acpi/aml/vm_op_flow.go b/src/gopheros/device/acpi/aml/vm_op_flow.go new file mode 100644 index 0000000..1782381 --- /dev/null +++ b/src/gopheros/device/acpi/aml/vm_op_flow.go @@ -0,0 +1,16 @@ +package aml + +// Args: val +// Set val as the return value in ctx and change the ctrlFlow +// type to ctrlFlowTypeFnReturn. +func vmOpReturn(ctx *execContext, ent Entity) *Error { + args := ent.getArgs() + if len(args) != 1 { + return errArgIndexOutOfBounds + } + + var err *Error + ctx.ctrlFlow = ctrlFlowTypeFnReturn + ctx.retVal, err = vmLoad(ctx, args[0]) + return err +} diff --git a/src/gopheros/device/acpi/aml/vm_op_flow_test.go b/src/gopheros/device/acpi/aml/vm_op_flow_test.go new file mode 100644 index 0000000..304f509 --- /dev/null +++ b/src/gopheros/device/acpi/aml/vm_op_flow_test.go @@ -0,0 +1,12 @@ +package aml + +import "testing" + +func TestFlowExpressionErrors(t *testing.T) { + t.Run("opReturn errors", func(t *testing.T) { + // opReturn expects an argument to evaluate as the return value + if err := vmOpReturn(nil, new(unnamedEntity)); err != errArgIndexOutOfBounds { + t.Errorf("expected to get errArgIndexOutOfBounds; got %v", err) + } + }) +} diff --git a/src/gopheros/device/acpi/aml/vm_op_store.go b/src/gopheros/device/acpi/aml/vm_op_store.go new file mode 100644 index 0000000..3cd719b --- /dev/null +++ b/src/gopheros/device/acpi/aml/vm_op_store.go @@ -0,0 +1,17 @@ +package aml + +// Args: src dst +// Store src into dst applying any required conversion. +func vmOpStore(ctx *execContext, ent Entity) *Error { + args := ent.getArgs() + if len(args) != 2 { + return errArgIndexOutOfBounds + } + + val, err := vmLoad(ctx, args[0]) + if err != nil { + return err + } + + return vmStore(ctx, val, args[1]) +} diff --git a/src/gopheros/device/acpi/aml/vm_op_store_test.go b/src/gopheros/device/acpi/aml/vm_op_store_test.go new file mode 100644 index 0000000..df84f7b --- /dev/null +++ b/src/gopheros/device/acpi/aml/vm_op_store_test.go @@ -0,0 +1,41 @@ +package aml + +import "testing" + +func TestVMOpStoreErrors(t *testing.T) { + // Wrong arg count + if err := vmOpStore(nil, new(unnamedEntity)); err != errArgIndexOutOfBounds { + t.Errorf("expected to get errArgIndexOutOfBounds; got %v", err) + } + + // Error loading the value to be stored + expErr := &Error{message: "something went wrong"} + + vm := NewVM(nil, nil) + vm.populateJumpTable() + vm.jumpTable[0] = func(_ *execContext, ent Entity) *Error { + return expErr + } + + ent := &unnamedEntity{ + args: []interface{}{ + // vmLoad will try to eval jumptable[0] which we monkey-patched to return an error + &unnamedEntity{op: 0}, + uint64(128), + }, + } + + if err := vmOpStore(&execContext{vm: vm}, ent); err != expErr { + t.Errorf("expected to get error %q; got %v", expErr.Error(), err) + } + + // Error storing the value + ent.args = []interface{}{ + uint64(128), + uint64(0xf00), // storing to a non-AML entity is an error + } + + if err := vmOpStore(nil, ent); err != errInvalidStoreDestination { + t.Errorf("expected to get errInvalidStoreDestination; got %v", 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 new file mode 100644 index 0000000000000000000000000000000000000000..de778fb30626935345fb4ffe474979436ebbc80f GIT binary patch literal 224 zcmYk0OA5j;6h(6jLJc|)f@7D^|GZGqf>>$mJn1MyDYm)+ck&kIQp|^flfXHLcS+S% z{g*Ko+)vHzd~E&Obb)d8wZDYeAH=q7qX?tsD@jsp%C?R`7MV*io_{F@S@bEvOY?BK zNtVH#*~J_AkO=lE#Om#G|Nk%^u8TY