mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
acpi: implement vmStore/Copy for local/method args and references
The vmStore implementation does not support storing data to other AML entities such as Buffers, Fields or Regions. Support for these entities will be included in a separate commit. The current vmCopyObject implementation is as simple as possible for now; only copying of strings and uint64 values is supported. This matches the behavior of vmLoad. Both vmLoad/Store functions support reading/writing to/from object references following the ACPI spec rules about automatic dereferencing.
This commit is contained in:
parent
1a2d075aa2
commit
dfaf068735
@ -12,6 +12,21 @@ const (
|
|||||||
maxMethodArgs = 7
|
maxMethodArgs = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNilStoreOperands = &Error{message: "vmStore: src and/or dst operands are nil"}
|
||||||
|
errInvalidStoreDestination = &Error{message: "vmStore: destination operand is not an AML entity"}
|
||||||
|
errCopyFailed = &Error{message: "vmCopyObject: copy failed"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// objRef is a pointer to an argument (local or global) or a named AML object.
|
||||||
|
type objRef struct {
|
||||||
|
ref interface{}
|
||||||
|
|
||||||
|
// isArgRef specifies whether this is a reference to a method argument.
|
||||||
|
// Different rules (p.884) apply for this particular type of reference.
|
||||||
|
isArgRef bool
|
||||||
|
}
|
||||||
|
|
||||||
// ctrlFlowType describes the different ways that the control flow can be altered
|
// ctrlFlowType describes the different ways that the control flow can be altered
|
||||||
// while executing a set of AML opcodes.
|
// while executing a set of AML opcodes.
|
||||||
type ctrlFlowType uint8
|
type ctrlFlowType uint8
|
||||||
|
@ -28,8 +28,108 @@ func vmLoad(ctx *execContext, arg interface{}) (interface{}, *Error) {
|
|||||||
return uint64(1), nil
|
return uint64(1), nil
|
||||||
}
|
}
|
||||||
return uint64(0), nil
|
return uint64(0), nil
|
||||||
|
case *objRef:
|
||||||
|
// According to p. 884 of the spec, reading from a
|
||||||
|
// method argument reference automatically dereferences
|
||||||
|
// the value
|
||||||
|
if typ.isArgRef {
|
||||||
|
return typ.ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all other cases we return back the reference itself
|
||||||
|
return typ, nil
|
||||||
default:
|
default:
|
||||||
return typ, nil
|
return typ, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vmCondStore is a wrapper around vmWrite that checks whether argIndex
|
||||||
|
// contains a non-nil target before attempting to write val to it. If argIndex
|
||||||
|
// is out of bounds or it points to a nil target then this function behaves as
|
||||||
|
// a no-op.
|
||||||
|
func vmCondStore(ctx *execContext, val interface{}, ent Entity, argIndex int) *Error {
|
||||||
|
args := ent.getArgs()
|
||||||
|
if len(args) <= argIndex || vmIsNilTarget(args[argIndex]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return vmStore(ctx, val, args[argIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmStore attempts to write the value contained in src to dst.
|
||||||
|
func vmStore(ctx *execContext, src, dst interface{}) *Error {
|
||||||
|
if dst == nil || src == nil {
|
||||||
|
return errNilStoreOperands
|
||||||
|
}
|
||||||
|
|
||||||
|
// The target should be some type of AML Entity
|
||||||
|
dstEnt, ok := dst.(Entity)
|
||||||
|
if !ok {
|
||||||
|
return errInvalidStoreDestination
|
||||||
|
}
|
||||||
|
dstOp := dstEnt.getOpcode()
|
||||||
|
|
||||||
|
// According to the spec, storing to a constant is a no-op and not a
|
||||||
|
// fatal error. In addition, if the destination is the Debug opbject,
|
||||||
|
// the interpreter must display the value written to it. This
|
||||||
|
// interpreter implementation just treats this as a no-op.
|
||||||
|
if _, ok := dst.(*constEntity); ok || dstOp == opDebug {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The spec requires the interpreter to make a copy of the src object
|
||||||
|
// and apply the appropriate conversions depending on the destination
|
||||||
|
// object type
|
||||||
|
srcCopy, err := vmCopyObject(ctx, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case opIsLocalArg(dstOp):
|
||||||
|
// According to p.897 of the spec, writing to a local object
|
||||||
|
// always overwrites the previous value with a copy of src even
|
||||||
|
// if this is an object reference
|
||||||
|
ctx.localArg[dstOp-opLocal0] = srcCopy
|
||||||
|
return nil
|
||||||
|
case opIsMethodArg(dstOp):
|
||||||
|
// According to p.896 of the spec, if ArgX is a reference
|
||||||
|
// we need to dereference it and store the copied object
|
||||||
|
// in the reference. In all other cases we just overwrite the
|
||||||
|
// value in ArgX with the object copy
|
||||||
|
if dstRef, isRef := ctx.methodArg[dstOp-opArg0].(*objRef); isRef {
|
||||||
|
dstRef.ref = srcCopy
|
||||||
|
} else {
|
||||||
|
ctx.methodArg[dstOp-opArg0] = srcCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Error{message: "vmStore: unsupported opcode: " + dstOp.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmIsNilTarget returns true if t is nil or a nil const entity.
|
||||||
|
func vmIsNilTarget(target interface{}) bool {
|
||||||
|
if target == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ent, ok := target.(*constEntity); ok {
|
||||||
|
return ent.val != nil && ent.val != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmCopyObject returns a copy of obj.
|
||||||
|
func vmCopyObject(ctx *execContext, obj interface{}) (interface{}, *Error) {
|
||||||
|
switch typ := obj.(type) {
|
||||||
|
case string:
|
||||||
|
return typ, nil
|
||||||
|
case uint64:
|
||||||
|
return typ, nil
|
||||||
|
}
|
||||||
|
return nil, errCopyFailed
|
||||||
|
}
|
||||||
|
@ -6,6 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestVMLoad(t *testing.T) {
|
func TestVMLoad(t *testing.T) {
|
||||||
|
// Use a pointer to ensure that when we dereference an objRef we get
|
||||||
|
// back the same pointer
|
||||||
|
uniqueVal := &execContext{}
|
||||||
|
aRef := &objRef{isArgRef: false, ref: 42}
|
||||||
|
|
||||||
specs := []struct {
|
specs := []struct {
|
||||||
ctx *execContext
|
ctx *execContext
|
||||||
argIn interface{}
|
argIn interface{}
|
||||||
@ -56,6 +61,19 @@ func TestVMLoad(t *testing.T) {
|
|||||||
"foo",
|
"foo",
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
// reference handling
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
&objRef{isArgRef: true, ref: uniqueVal},
|
||||||
|
uniqueVal,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
aRef,
|
||||||
|
aRef,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
// Unsupported reads
|
// Unsupported reads
|
||||||
{
|
{
|
||||||
nil,
|
nil,
|
||||||
@ -76,6 +94,155 @@ func TestVMLoad(t *testing.T) {
|
|||||||
got, reflect.TypeOf(got),
|
got, reflect.TypeOf(got),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMStore(t *testing.T) {
|
||||||
|
t.Run("errors", func(t *testing.T) {
|
||||||
|
ctx := new(execContext)
|
||||||
|
|
||||||
|
if err := vmStore(ctx, nil, 42); err != errNilStoreOperands {
|
||||||
|
t.Fatal("expected to get errNilStoreOperands")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(ctx, "foo", nil); err != errNilStoreOperands {
|
||||||
|
t.Fatal("expected to get errNilStoreOperands")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(ctx, 42, "not-an-entity"); err != errInvalidStoreDestination {
|
||||||
|
t.Fatal("expected to get errInvalidStoreDestination")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(ctx, &unnamedEntity{}, &unnamedEntity{op: opArg0}); err != errCopyFailed {
|
||||||
|
t.Fatal("expected to get errCopyFailed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storing to fields, bufferFields & named objects is not yet supported
|
||||||
|
expErr := &Error{message: "vmStore: unsupported opcode: Buffer"}
|
||||||
|
if err := vmStore(ctx, uint64(42), &scopeEntity{op: opBuffer, name: "BUF0"}); err == nil || err.Error() != expErr.Error() {
|
||||||
|
t.Fatalf("expected to get error: %v; got %v", expErr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("store to local arg", func(t *testing.T) {
|
||||||
|
ctx := &execContext{
|
||||||
|
localArg: [maxLocalArgs]interface{}{
|
||||||
|
"foo",
|
||||||
|
uint64(42),
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(ctx, uint64(123), &unnamedEntity{op: opLocal1}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expArgs := [maxLocalArgs]interface{}{
|
||||||
|
"foo",
|
||||||
|
uint64(123),
|
||||||
|
10,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.localArg, expArgs) {
|
||||||
|
t.Fatalf("expected local args to be %v; got %v", expArgs, ctx.localArg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("store to method arg", func(t *testing.T) {
|
||||||
|
ref := &objRef{isArgRef: true, ref: "foo"}
|
||||||
|
ctx := &execContext{
|
||||||
|
methodArg: [maxMethodArgs]interface{}{
|
||||||
|
"foo",
|
||||||
|
uint64(42),
|
||||||
|
ref,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(ctx, uint64(123), &unnamedEntity{op: opArg1}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(ctx, "bar", &unnamedEntity{op: opArg2}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ctx.methodArg[0], "foo") {
|
||||||
|
t.Fatal("expected methodArg[0] not to be modified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp := uint64(123); !reflect.DeepEqual(ctx.methodArg[1], exp) {
|
||||||
|
t.Fatalf("expected methodArg[1] to be set to: %v; got %v", exp, ctx.methodArg[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.methodArg[2] != ref {
|
||||||
|
t.Fatal("expected the reference instance in methodArg[2] not to be modified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp := "bar"; ref.ref != exp {
|
||||||
|
t.Fatalf("expected the referenced value by methodArg[2] to be set to: %v; got %v", exp, ctx.methodArg[2])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("store to Debug object or a constant", func(t *testing.T) {
|
||||||
|
constant := &constEntity{val: "foo"}
|
||||||
|
if err := vmStore(nil, 42, constant); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp := "foo"; constant.val != exp {
|
||||||
|
t.Fatalf("expected storing to constant to be a no-op; constant value changed from %v to %v", exp, constant.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vmStore(nil, 42, &unnamedEntity{op: opDebug}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVMCondStore(t *testing.T) {
|
||||||
|
specs := []struct {
|
||||||
|
ctx *execContext
|
||||||
|
args []interface{}
|
||||||
|
argIndex int
|
||||||
|
val interface{}
|
||||||
|
}{
|
||||||
|
// Not enough args to get target
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
[]interface{}{"foo"},
|
||||||
|
2,
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
// Target is nil
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
[]interface{}{nil, "foo"},
|
||||||
|
0,
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
// Target is a constant with a nil value
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
[]interface{}{
|
||||||
|
&constEntity{},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
// Valid target
|
||||||
|
{
|
||||||
|
&execContext{},
|
||||||
|
[]interface{}{
|
||||||
|
&unnamedEntity{op: opLocal0},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for specIndex, spec := range specs {
|
||||||
|
if err := vmCondStore(spec.ctx, spec.val, &unnamedEntity{args: spec.args}, spec.argIndex); err != nil {
|
||||||
|
t.Errorf("[spec %d] error: %v", specIndex, spec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user