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:
parent
a172621af7
commit
38b2a3e4e2
@ -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.
|
||||
|
@ -167,7 +167,6 @@ func vmConvert(ctx *execContext, arg interface{}, toType valueType) (interface{}
|
||||
return strconv.FormatUint(argAsInt, 16), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errConversionFailed
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"},
|
||||
},
|
||||
}
|
||||
|
||||
|
136
src/gopheros/device/acpi/aml/vm_op_alu.go
Normal file
136
src/gopheros/device/acpi/aml/vm_op_alu.go
Normal 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)
|
||||
}
|
128
src/gopheros/device/acpi/aml/vm_op_alu_test.go
Normal file
128
src/gopheros/device/acpi/aml/vm_op_alu_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
16
src/gopheros/device/acpi/aml/vm_op_flow.go
Normal file
16
src/gopheros/device/acpi/aml/vm_op_flow.go
Normal 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
|
||||
}
|
12
src/gopheros/device/acpi/aml/vm_op_flow_test.go
Normal file
12
src/gopheros/device/acpi/aml/vm_op_flow_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
17
src/gopheros/device/acpi/aml/vm_op_store.go
Normal file
17
src/gopheros/device/acpi/aml/vm_op_store.go
Normal 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])
|
||||
}
|
41
src/gopheros/device/acpi/aml/vm_op_store_test.go
Normal file
41
src/gopheros/device/acpi/aml/vm_op_store_test.go
Normal 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)
|
||||
}
|
||||
}
|
BIN
src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml
Normal file
BIN
src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml
Normal file
Binary file not shown.
@ -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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user