1
0
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:
Achilleas Anagnostopoulos 2017-11-12 09:58:57 +00:00
parent 1a2d075aa2
commit dfaf068735
3 changed files with 283 additions and 1 deletions

View File

@ -12,6 +12,21 @@ const (
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
// while executing a set of AML opcodes.
type ctrlFlowType uint8

View File

@ -28,8 +28,108 @@ func vmLoad(ctx *execContext, arg interface{}) (interface{}, *Error) {
return uint64(1), 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:
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
}

View File

@ -6,6 +6,11 @@ import (
)
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 {
ctx *execContext
argIn interface{}
@ -56,6 +61,19 @@ func TestVMLoad(t *testing.T) {
"foo",
nil,
},
// reference handling
{
nil,
&objRef{isArgRef: true, ref: uniqueVal},
uniqueVal,
nil,
},
{
nil,
aRef,
aRef,
nil,
},
// Unsupported reads
{
nil,
@ -76,6 +94,155 @@ func TestVMLoad(t *testing.T) {
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)
}
}
}