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

acpi: add VM-support for AML arithmetic opcodes

This commit is contained in:
Achilleas Anagnostopoulos 2017-11-21 08:21:11 +00:00
parent a172621af7
commit 38b2a3e4e2
14 changed files with 496 additions and 14 deletions

View File

@ -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.

View File

@ -167,7 +167,6 @@ func vmConvert(ctx *execContext, arg interface{}, toType valueType) (interface{}
return strconv.FormatUint(argAsInt, 16), nil
}
}
return nil, errConversionFailed
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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"},
},
}

View File

@ -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)
}

View File

@ -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)
}
}
})
}

View File

@ -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
}

View File

@ -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)
}
})
}

View File

@ -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])
}

View File

@ -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)
}
}

View File

@ -0,0 +1,84 @@
DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x00000002)
{
// Arithmetic ops
Method (AR00, 1, NotSerialized)
{
Add(Arg0, 5, Local0)
Return(Local0)
}
Method (ARI0, 1, NotSerialized)
{
Return(Arg0 + 5)
}
Method (AR01, 1, NotSerialized)
{
Subtract(Arg0, 5, Local0)
Return(Local0)
}
Method (ARI1, 1, NotSerialized)
{
Return(Arg0 - 5)
}
Method (AR02, 1, NotSerialized)
{
Multiply(Arg0, 8, Local0)
Return(Local0)
}
Method (ARI2, 1, NotSerialized)
{
Return(Arg0*8)
}
Method (AR03, 1, NotSerialized)
{
Local1 = Arg0
Local1--
Return(Local1)
}
Method (ARI3, 1, NotSerialized)
{
Return(Arg0--)
}
Method (AR04, 1, NotSerialized)
{
Local2 = Arg0
Local2++
Return(Local2)
}
Method (ARI4, 1, NotSerialized)
{
Return(Arg0++)
}
Method (AR05, 1, NotSerialized)
{
Mod(Arg0, 10, Local0)
Return(Local0)
}
Method (ARI5, 1, NotSerialized)
{
Return(Arg0 % 10)
}
Method (AR06, 1, NotSerialized)
{
// Local0: remainder
// Local1: quotient
Divide(Arg0, 10, Local0, Local1)
Return(Local0 + Local1)
}
Method (ARI6, 1, NotSerialized)
{
Return(Arg0 / 10)
}
}