diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index df881be..5d6a12b 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -13,12 +13,13 @@ const ( ) 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"} - errConversionFailed = &Error{message: "vmConvert: conversion failed"} - errArgIndexOutOfBounds = &Error{message: "vm: arg index out of bounds"} - errDivideByZero = &Error{message: "vm: division by zero"} + 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"} + errConversionFromEmptyString = &Error{message: "vmConvert: conversion from String requires a non-empty value"} + errArgIndexOutOfBounds = &Error{message: "vm: arg index out of bounds"} + errDivideByZero = &Error{message: "vm: division by zero"} + errInvalidComparisonType = &Error{message: "vm: logic opcodes can only be applied to Integer, String or Buffer arguments"} ) // objRef is a pointer to an argument (local or global) or a named AML object. @@ -122,7 +123,7 @@ func (vm *VM) Init() *Error { } vm.populateJumpTable() - return nil + return vm.checkEntities() } // Lookup traverses a potentially nested absolute AML path and returns the @@ -141,6 +142,62 @@ func (vm *VM) Lookup(absPath string) Entity { return scopeFindRelative(vm.rootNS, absPath[1:]) } +// checkEntities performs a DFS on the entity tree and initializes +// entities that defer their initialization until an AML interpreter +// is available. +func (vm *VM) checkEntities() *Error { + var ( + err *Error + ctx = &execContext{vm: vm} + ) + + vm.Visit(EntityTypeAny, func(_ int, ent Entity) bool { + // Stop recursing after the first detected error + if err != nil { + return false + } + + // Peek into named entities that wrap other entities + if namedEnt, ok := ent.(*namedEntity); ok { + if nestedEnt, ok := namedEnt.args[0].(Entity); ok { + ent = nestedEnt + } + } + + switch typ := ent.(type) { + case *Method: + // Do not recurse into methods; ath this stage we are only interested in + // initializing static entities. + return false + case *bufferEntity: + // According to p.911-912 of the spec: + // - if a size is specified but no initializer the VM should allocate + // a buffer of the requested size + // - if both a size and initializer are specified but size > len(data) + // then the data needs to be padded with zeroes + + // Evaluate size arg as an integer + var size interface{} + if size, err = vmConvert(ctx, typ.size, valueTypeInteger); err != nil { + return false + } + sizeAsInt := size.(uint64) + + if typ.data == nil { + typ.data = make([]byte, size.(uint64)) + } + + if dataLen := uint64(len(typ.data)); dataLen < sizeAsInt { + typ.data = append(typ.data, make([]byte, sizeAsInt-dataLen)...) + } + } + + return true + }) + + return err +} + // Visit performs a DFS on the AML namespace tree invoking the visitor for each // encountered entity whose type matches entType. Namespace nodes are visited // in parent to child order a property which allows the supplied visitor diff --git a/src/gopheros/device/acpi/aml/vm_convert.go b/src/gopheros/device/acpi/aml/vm_convert.go index a8a960f..c5d2c54 100644 --- a/src/gopheros/device/acpi/aml/vm_convert.go +++ b/src/gopheros/device/acpi/aml/vm_convert.go @@ -24,7 +24,6 @@ const ( valueTypeString valueTypePowerResource valueTypeProcessor - valueTypeRawDataBuffer valueTypeThermalZone ) @@ -63,8 +62,6 @@ func (vt valueType) String() string { return "PowerResource" case valueTypeProcessor: return "Processor" - case valueTypeRawDataBuffer: - return "RawDataBuffer" case valueTypeThermalZone: return "ThermalZone" default: @@ -139,7 +136,7 @@ func vmConvert(ctx *execContext, arg interface{}, toType valueType) (interface{} // conversion without error, and a “0x” prefix is not allowed. Conversion of a null // (zero-length) string to an integer is not allowed. if len(argAsStr) == 0 { - return nil, errConversionFailed + return nil, errConversionFromEmptyString } var res = uint64(0) @@ -167,7 +164,14 @@ func vmConvert(ctx *execContext, arg interface{}, toType valueType) (interface{} return strconv.FormatUint(argAsInt, 16), nil } } - return nil, errConversionFailed + + return nil, conversionError(argType, toType) +} + +func conversionError(from, to valueType) *Error { + return &Error{ + message: "vmConvert: conversion from " + from.String() + " to " + to.String() + " is not supported", + } } // vmTypeOf returns the type of data stored inside the supplied argument. @@ -225,7 +229,7 @@ func vmTypeOf(ctx *execContext, arg interface{}) valueType { case uint64, bool: return valueTypeInteger case []byte: - return valueTypeRawDataBuffer + return valueTypeBuffer default: return valueTypeUninitialized } diff --git a/src/gopheros/device/acpi/aml/vm_convert_test.go b/src/gopheros/device/acpi/aml/vm_convert_test.go index e55321d..08978ef 100644 --- a/src/gopheros/device/acpi/aml/vm_convert_test.go +++ b/src/gopheros/device/acpi/aml/vm_convert_test.go @@ -23,7 +23,6 @@ func TestValueTypeToString(t *testing.T) { valueTypeString: "String", valueTypePowerResource: "PowerResource", valueTypeProcessor: "Processor", - valueTypeRawDataBuffer: "RawDataBuffer", valueTypeThermalZone: "ThermalZone", valueTypeUninitialized: "Uninitialized", } @@ -153,7 +152,7 @@ func TestVMTypeOf(t *testing.T) { { nil, []byte("some data"), - valueTypeRawDataBuffer, + valueTypeBuffer, }, { nil, @@ -199,7 +198,7 @@ func TestVMToIntArg(t *testing.T) { }, 0, 0, - errConversionFailed, + errConversionFromEmptyString, }, { &unnamedEntity{}, @@ -245,7 +244,7 @@ func TestVMToIntArgs2(t *testing.T) { }, [2]int{0, 1}, [2]uint64{0, 0}, - errConversionFailed, + errConversionFromEmptyString, }, { &unnamedEntity{ @@ -253,7 +252,7 @@ func TestVMToIntArgs2(t *testing.T) { }, [2]int{0, 1}, [2]uint64{0, 0}, - errConversionFailed, + errConversionFromEmptyString, }, { &unnamedEntity{}, @@ -344,7 +343,7 @@ func TestVMConvert(t *testing.T) { "", valueTypeInteger, nil, - errConversionFailed, + errConversionFromEmptyString, }, // int -> string { @@ -360,7 +359,7 @@ func TestVMConvert(t *testing.T) { uint64(42), valueTypeDevice, nil, - errConversionFailed, + conversionError(valueTypeInteger, valueTypeDevice), }, { &execContext{vm: vm}, diff --git a/src/gopheros/device/acpi/aml/vm_jumptable.go b/src/gopheros/device/acpi/aml/vm_jumptable.go index e23f9a2..f40fada 100644 --- a/src/gopheros/device/acpi/aml/vm_jumptable.go +++ b/src/gopheros/device/acpi/aml/vm_jumptable.go @@ -22,6 +22,24 @@ func (vm *VM) populateJumpTable() { vm.jumpTable[opDivide] = vmOpDivide vm.jumpTable[opMod] = vmOpMod + vm.jumpTable[opShiftLeft] = vmOpShiftLeft + vm.jumpTable[opShiftRight] = vmOpShiftRight + vm.jumpTable[opAnd] = vmOpBitwiseAnd + vm.jumpTable[opOr] = vmOpBitwiseOr + vm.jumpTable[opNand] = vmOpBitwiseNand + vm.jumpTable[opNor] = vmOpBitwiseNor + vm.jumpTable[opXor] = vmOpBitwiseXor + vm.jumpTable[opNot] = vmOpBitwiseNot + vm.jumpTable[opFindSetLeftBit] = vmOpFindSetLeftBit + vm.jumpTable[opFindSetRightBit] = vmOpFindSetRightBit + + vm.jumpTable[opLnot] = vmOpLogicalNot + vm.jumpTable[opLand] = vmOpLogicalAnd + vm.jumpTable[opLor] = vmOpLogicalOr + vm.jumpTable[opLEqual] = vmOpLogicalEqual + vm.jumpTable[opLLess] = vmOpLogicalLess + vm.jumpTable[opLGreater] = vmOpLogicalGreater + // Store-related opcodes vm.jumpTable[opStore] = vmOpStore } diff --git a/src/gopheros/device/acpi/aml/vm_load_store.go b/src/gopheros/device/acpi/aml/vm_load_store.go index 5a86c4b..277023f 100644 --- a/src/gopheros/device/acpi/aml/vm_load_store.go +++ b/src/gopheros/device/acpi/aml/vm_load_store.go @@ -18,6 +18,8 @@ func vmLoad(ctx *execContext, arg interface{}) (interface{}, *Error) { arg = ctx.localArg[op-opLocal0] case opIsMethodArg(op): arg = ctx.methodArg[op-opArg0] + case op == opBuffer: + return typ.(*bufferEntity).data, nil default: // 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 diff --git a/src/gopheros/device/acpi/aml/vm_op_alu.go b/src/gopheros/device/acpi/aml/vm_op_alu.go index b528d91..b0602e0 100644 --- a/src/gopheros/device/acpi/aml/vm_op_alu.go +++ b/src/gopheros/device/acpi/aml/vm_op_alu.go @@ -1,5 +1,7 @@ package aml +import "bytes" + // Args: left, right, store? // Returns: left + right func vmOpAdd(ctx *execContext, ent Entity) *Error { @@ -134,3 +136,377 @@ func vmOpMod(ctx *execContext, ent Entity) *Error { ctx.retVal = left % right return vmCondStore(ctx, ctx.retVal, ent, 2) } + +// Args; left, right, store? +// Returns left << right +func vmOpShiftLeft(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 vmOpShiftRight(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 vmOpBitwiseAnd(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 vmOpBitwiseOr(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 vmOpBitwiseNand(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 vmOpBitwiseNor(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 vmOpBitwiseXor(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 +func vmOpBitwiseNot(ctx *execContext, ent Entity) *Error { + var ( + left uint64 + err *Error + ) + + if left, err = vmToIntArg(ctx, ent, 0); err != nil { + return err + } + + ctx.retVal = ^left + return vmCondStore(ctx, ctx.retVal, ent, 1) +} + +// Args; left, store? +// Returns the one-based bit location of the first MSb (most +// significant set bit). The result of 0 means no bit was +// set, 1 means the left-most bit set is the first bit, 2 +// means the left-most bit set is the second bit, and so on. +func vmOpFindSetLeftBit(ctx *execContext, ent Entity) *Error { + var ( + left uint64 + err *Error + ) + + if left, err = vmToIntArg(ctx, ent, 0); err != nil { + return err + } + + ctx.retVal = uint64(0) + for off, mask := uint8(1), uint64(1<<63); mask > 0; off, mask = off+1, mask>>1 { + if left&mask != 0 { + ctx.retVal = uint64(off) + break + } + } + + return vmCondStore(ctx, ctx.retVal, ent, 1) +} + +// Args; left, store? +// Returns the one-based bit location of the first LSb (least significant set +// bit). The result of 0 means no bit was set, 32 means the first bit set is +// the thirty-second bit, 31 means the first bit set is the thirty-first bit, +// and so on. +func vmOpFindSetRightBit(ctx *execContext, ent Entity) *Error { + var ( + left uint64 + err *Error + ) + + if left, err = vmToIntArg(ctx, ent, 0); err != nil { + return err + } + + ctx.retVal = uint64(0) + for off, mask := uint8(1), uint64(1); off <= 64; off, mask = off+1, mask<<1 { + if left&mask != 0 { + ctx.retVal = uint64(off) + break + } + } + + return vmCondStore(ctx, ctx.retVal, ent, 1) +} + +// Args: left +// Returns !left +func vmOpLogicalNot(ctx *execContext, ent Entity) *Error { + var ( + left uint64 + err *Error + ) + + if left, err = vmToIntArg(ctx, ent, 0); err != nil { + return err + } + + ctx.retVal = left == 0 + return nil +} + +// Args: left, right +// Returns left || right +func vmOpLogicalOr(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 != 0 || right != 0 + return nil +} + +// Args: left, right +// Returns left && right +func vmOpLogicalAnd(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 != 0 && right != 0 + return nil +} + +// Args: left, right +// Returns left == right +// Operands must evaluate to either a Number, a String or a Buffer. The type +// of the first operand dictates the type of the second +func vmOpLogicalEqual(ctx *execContext, ent Entity) *Error { + var ( + left, right interface{} + argType valueType + err *Error + args = ent.getArgs() + ) + + if len(args) != 2 { + return errArgIndexOutOfBounds + } + + if left, err = vmLoad(ctx, args[0]); err != nil { + return err + } + + if right, err = vmLoad(ctx, args[1]); err != nil { + return err + } + + argType = vmTypeOf(ctx, args[0]) + + // Right operand must be coerced to the same type as left + if right, err = vmConvert(ctx, right, argType); err != nil { + return err + } + + switch argType { + case valueTypeInteger, valueTypeString: + ctx.retVal = left == right + case valueTypeBuffer: + ctx.retVal = cmpBuffers(left.([]byte), right.([]byte)) == 0 + default: + return errInvalidComparisonType + } + + return nil +} + +// Args: left, right +// Returns left < right +// Operands must evaluate to either a Number, a String or a Buffer. The type +// of the first operand dictates the type of the second +func vmOpLogicalLess(ctx *execContext, ent Entity) *Error { + var ( + left, right interface{} + argType valueType + err *Error + args = ent.getArgs() + ) + + if len(args) != 2 { + return errArgIndexOutOfBounds + } + + if left, err = vmLoad(ctx, args[0]); err != nil { + return err + } + + if right, err = vmLoad(ctx, args[1]); err != nil { + return err + } + + argType = vmTypeOf(ctx, args[0]) + + // Right operand must be coerced to the same type as left + if right, err = vmConvert(ctx, right, argType); err != nil { + return err + } + + switch argType { + case valueTypeInteger: + ctx.retVal = left.(uint64) < right.(uint64) + case valueTypeString: + ctx.retVal = left.(string) < right.(string) + case valueTypeBuffer: + ctx.retVal = cmpBuffers(left.([]byte), right.([]byte)) == -1 + default: + return errInvalidComparisonType + } + return err +} + +// Args: left, right +// Returns left > right +// Operands must evaluate to either a Number, a String or a Buffer. The type +// of the first operand dictates the type of the second +func vmOpLogicalGreater(ctx *execContext, ent Entity) *Error { + var ( + left, right interface{} + argType valueType + err *Error + args = ent.getArgs() + ) + + if len(args) != 2 { + return errArgIndexOutOfBounds + } + + if left, err = vmLoad(ctx, args[0]); err != nil { + return err + } + + if right, err = vmLoad(ctx, args[1]); err != nil { + return err + } + + argType = vmTypeOf(ctx, args[0]) + + // Right operand must be coerced to the same type as left + if right, err = vmConvert(ctx, right, argType); err != nil { + return err + } + + switch argType { + case valueTypeInteger: + ctx.retVal = left.(uint64) > right.(uint64) + case valueTypeString: + ctx.retVal = left.(string) > right.(string) + case valueTypeBuffer: + ctx.retVal = cmpBuffers(left.([]byte), right.([]byte)) == 1 + default: + return errInvalidComparisonType + } + return err +} + +// cmpBuffers compares left and right and returns 0 if they are equal, -1 if +// left < right and 1 if left > right. According to the ACPI spec, cmpBuffers +// will first compare the lengths of the slices before delegating a +// lexicographical comparison to bytes.Compare. +func cmpBuffers(left, right []byte) int { + llen, rlen := len(left), len(right) + if llen < rlen { + return -1 + } else if llen > rlen { + return 1 + } + + return bytes.Compare(left, right) +} diff --git a/src/gopheros/device/acpi/aml/vm_op_alu_test.go b/src/gopheros/device/acpi/aml/vm_op_alu_test.go index 940d568..d1b8040 100644 --- a/src/gopheros/device/acpi/aml/vm_op_alu_test.go +++ b/src/gopheros/device/acpi/aml/vm_op_alu_test.go @@ -126,3 +126,305 @@ func TestArithmeticExpressionErrors(t *testing.T) { } }) } + +func TestBitwiseExpressions(t *testing.T) { + specs := []struct { + method string + input interface{} + exp uint64 + }{ + {`\BI00`, uint64(100), uint64(800)}, + {`\BI01`, uint64(100), uint64(25)}, + {`\BI02`, uint64(7), uint64(5)}, + {`\BI03`, uint64(32), uint64(40)}, + {`\BI04`, uint64(8), uint64(0)}, + {`\BI04`, uint64(9), uint64(1)}, + {`\BI05`, uint64(7), uint64(0xfffffffffffffff8)}, + {`\BI05`, uint64(12), uint64(0xfffffffffffffff0)}, + {`\BI06`, uint64(32), uint64(48)}, + {`\BI07`, uint64(0xffffffff), uint64(0xffffffff00000000)}, + {`\BI08`, uint64(1 << 63), uint64(1)}, + {`\BI08`, uint64(1), uint64(64)}, + {`\BI08`, uint64(0), uint64(0)}, + {`\BI09`, uint64(1 << 2), uint64(3)}, + {`\BI09`, uint64(1 << 63), uint64(64)}, + {`\BI09`, uint64(0), uint64(0)}, + } + + 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 TestBitwiseExpressionErrors(t *testing.T) { + t.Run("arg handling errors", func(t *testing.T) { + specs := []opHandler{ + vmOpShiftLeft, + vmOpShiftRight, + vmOpBitwiseAnd, + vmOpBitwiseOr, + vmOpBitwiseNand, + vmOpBitwiseNor, + vmOpBitwiseXor, + vmOpBitwiseNot, + vmOpFindSetLeftBit, + vmOpFindSetRightBit, + } + + for specIndex, handler := range specs { + if err := handler(nil, new(unnamedEntity)); err == nil { + t.Errorf("[spec %d] expected opHandler to return an error", specIndex) + } + } + }) +} + +func TestLogicExpressions(t *testing.T) { + bufOp := unnamedEntity{op: opBuffer} + + specs := []struct { + method string + inputA, inputB interface{} + exp interface{} + }{ + {`\LO00`, uint64(10), uint64(10), uint64(1)}, + {`\LO00`, uint64(5), uint64(0), uint64(0)}, + {`\LO00`, "lorem", "lorem", uint64(1)}, + {`\LO00`, "ipsum", "DOLOR", uint64(0)}, + { + `\LO00`, + &bufferEntity{unnamedEntity: bufOp, data: []byte{'!'}}, + &bufferEntity{unnamedEntity: bufOp, data: []byte{'!'}}, + uint64(1), + }, + { + `\LO00`, + &bufferEntity{unnamedEntity: bufOp, data: []byte("LOREM")}, + &bufferEntity{unnamedEntity: bufOp, data: []byte("lorem")}, + uint64(0), + }, + // + {`\LO01`, uint64(10), uint64(10), uint64(0)}, + {`\LO01`, uint64(5), uint64(0), uint64(1)}, + {`\LO01`, uint64(30), uint64(150), uint64(0)}, + {`\LO01`, "lorem", "lore", uint64(1)}, + {`\LO01`, "abc", "abd", uint64(0)}, + { + `\LO01`, + &bufferEntity{unnamedEntity: bufOp, data: []byte("lore0")}, + &bufferEntity{unnamedEntity: bufOp, data: []byte("lore1")}, + uint64(0), + }, + { + `\LO01`, + &bufferEntity{unnamedEntity: bufOp, data: []byte("1000")}, + &bufferEntity{unnamedEntity: bufOp, data: []byte("0111")}, + uint64(1), + }, + // + {`\LO02`, uint64(10), uint64(10), uint64(1)}, + {`\LO02`, uint64(50), uint64(49), uint64(1)}, + {`\LO02`, uint64(49), uint64(50), uint64(0)}, + // + {`\LO03`, uint64(10), uint64(10), uint64(0)}, + {`\LO03`, uint64(0), uint64(10), uint64(1)}, + // + {`\LO04`, uint64(10), uint64(10), uint64(0)}, + {`\LO04`, uint64(5), uint64(0), uint64(0)}, + {`\LO04`, uint64(30), uint64(150), uint64(1)}, + {`\LO04`, "123", "321", uint64(1)}, + {`\LO04`, "ab", "abc", uint64(1)}, + { + `\LO04`, + &bufferEntity{unnamedEntity: bufOp, data: []byte("lore000")}, + &bufferEntity{unnamedEntity: bufOp, data: []byte("lore1")}, + uint64(0), + }, + { + `\LO04`, + &bufferEntity{unnamedEntity: bufOp, data: []byte("1000")}, + &bufferEntity{unnamedEntity: bufOp, data: []byte("0111+1")}, + uint64(1), + }, + // + {`\LO05`, uint64(10), uint64(10), uint64(1)}, + {`\LO05`, uint64(50), uint64(49), uint64(0)}, + {`\LO05`, uint64(49), uint64(50), uint64(1)}, + // + {`\LO06`, true, false, uint64(0)}, + {`\LO06`, false, true, uint64(0)}, + {`\LO06`, true, true, uint64(1)}, + {`\LO06`, false, false, uint64(0)}, + {`\LO06`, "AA", "0", uint64(0)}, + {`\LO06`, "0", "F00", uint64(0)}, + // + {`\LO07`, true, false, uint64(1)}, + {`\LO07`, false, true, uint64(1)}, + {`\LO07`, true, true, uint64(1)}, + {`\LO07`, false, false, uint64(0)}, + {`\LO07`, "AA", "0", uint64(1)}, + {`\LO07`, "0", "F00", uint64(1)}, + {`\LO07`, "f00", "c0ffee", uint64(1)}, + {`\LO07`, "0", "0", uint64(0)}, + // + {`\LO08`, true, nil, uint64(0)}, + {`\LO08`, false, nil, uint64(1)}, + } + + 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.inputA, spec.inputB}, + 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 TestLogicExpressionErrors(t *testing.T) { + specs := []struct { + handler opHandler + argCount int + }{ + {vmOpLogicalNot, 1}, + {vmOpLogicalAnd, 2}, + {vmOpLogicalOr, 2}, + {vmOpLogicalEqual, 2}, + {vmOpLogicalLess, 2}, + {vmOpLogicalGreater, 2}, + } + + t.Run("arg handling errors", func(t *testing.T) { + for specIndex, spec := range specs { + if err := spec.handler(nil, new(unnamedEntity)); err == nil { + t.Errorf("[spec %d] expected opHandler to return an error", specIndex) + } + } + }) + + t.Run("arg loading errors", func(t *testing.T) { + ent := &unnamedEntity{ + args: make([]interface{}, 2), + } + + vm := &VM{sizeOfIntInBits: 64} + vm.populateJumpTable() + ctx := &execContext{vm: vm} + + for specIndex, spec := range specs { + ent.args[0] = &Device{} + ent.args[1] = uint64(9) + if err := spec.handler(ctx, ent); err == nil { + t.Errorf("[spec %d] expected opHandler to return an error", specIndex) + } + + if spec.argCount < 2 { + continue + } + ent.args[0] = uint64(123) + ent.args[1] = &Device{} + if err := spec.handler(ctx, ent); err == nil { + t.Errorf("[spec %d] expected opHandler to return an error", specIndex) + } + } + }) + + t.Run("2nd arg conversion error", func(t *testing.T) { + ent := &unnamedEntity{ + args: make([]interface{}, 2), + } + + vm := &VM{sizeOfIntInBits: 64} + vm.populateJumpTable() + ctx := &execContext{vm: vm} + + for specIndex, spec := range specs { + if spec.argCount < 2 { + continue + } + + ent.args[0] = uint64(30) + ent.args[1] = int64(9) + if err := spec.handler(ctx, ent); err == nil { + t.Errorf("[spec %d] expected opHandler to return an error", specIndex) + } + } + }) + + t.Run("unsupported comparison error", func(t *testing.T) { + specs := []opHandler{ + vmOpLogicalEqual, + vmOpLogicalLess, + vmOpLogicalGreater, + } + + ent := &unnamedEntity{ + args: make([]interface{}, 2), + } + + vm := &VM{sizeOfIntInBits: 64} + vm.populateJumpTable() + ctx := &execContext{vm: vm} + + for specIndex, handler := range specs { + ent.args[0] = int64(1) + ent.args[1] = int64(9) + if err := handler(ctx, ent); err != errInvalidComparisonType { + t.Errorf("[spec %d] expected opHandler to return errInvalidComparisonType; got %v", specIndex, err) + } + } + }) +} diff --git a/src/gopheros/device/acpi/aml/vm_test.go b/src/gopheros/device/acpi/aml/vm_test.go index 62b0d4b..728b5a6 100644 --- a/src/gopheros/device/acpi/aml/vm_test.go +++ b/src/gopheros/device/acpi/aml/vm_test.go @@ -25,6 +25,7 @@ func TestVMInit(t *testing.T) { } expErr := &Error{message: errParsingAML.Module + ": " + errParsingAML.Error()} + vm := NewVM(os.Stderr, resolver) if err := vm.Init(); !reflect.DeepEqual(err, expErr) { t.Fatalf("expected Init() to return errParsingAML; got %v", err) @@ -268,3 +269,33 @@ func TestVMExecBlockControlFlows(t *testing.T) { } }) } + +func TestVMCheckEntitiesErrors(t *testing.T) { + vm := NewVM(os.Stderr, nil) + + // Add a bogus named buffer entity to the root scope + vm.rootNS.Append( + &namedEntity{ + name: "F000", + args: []interface{}{ + &bufferEntity{ + size: "", // this will cause a errConversionFromEmptyString error + }, + }, + }, + ) + + // The previous error should prevent this entity from being processed + bufEnt := &bufferEntity{ + size: uint64(16), + } + vm.rootNS.Append(bufEnt) + + if err := vm.checkEntities(); err != errConversionFromEmptyString { + t.Fatalf("expected to get errConversionFromEmptyString; got %v", err) + } + + if len(bufEnt.data) != 0 { + t.Fatal("expected error to short-circuit the entity check") + } +} diff --git a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml index de778fb..dcfa0ea 100644 Binary files a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml and b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl index 0248a99..e965006 100644 --- a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl +++ b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl @@ -1,5 +1,22 @@ DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x00000002) { + // This example tests automatic allocation for buffers that specify a size + // but not a data initializer. + Scope(\_SB){ + // Buffer gets allocated with a length of 16 bytes + Name(BUF0, Buffer(16){}) + + // Buffer data gets padded with zeroes up to the declared length + Name(BUF1, Buffer(16){0xde, 0xad, 0xc0, 0xde}) + + OperationRegion(TOP1, GenericSerialBus, 0x00, 0x100) // GenericSerialBus device at command offset 0x00 + Field(TOP1, BufferAcc, NoLock, Preserve){ + Connection(I2cSerialBus(0x5b,,100000,, "\\_SB",,,,RawDataBuffer(16){})), + AccessAs(BufferAcc, AttribBytes(4)), + FLD2, 8, + } + } + // Arithmetic ops Method (AR00, 1, NotSerialized) { @@ -81,4 +98,110 @@ DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x000 { Return(Arg0 / 10) } + + // Bit ops + + Method (BI00, 1, NotSerialized) + { + ShiftLeft(Arg0, 3, Local0) + Return(Local0) + } + + Method (BI01, 1, NotSerialized) + { + ShiftRight(Arg0, 2, Local0) + Return(Local0) + } + + Method (BI02, 1, NotSerialized) + { + And(Arg0, 0xbadf00d, Local0) + Return(Local0) + } + + Method (BI03, 1, NotSerialized) + { + Or(Arg0, 8, Local0) + Return(Local0) + } + + Method (BI04, 1, NotSerialized) + { + Nand(Arg0, 8, Local0) + Return(Local0) + } + + Method (BI05, 1, NotSerialized) + { + Nor(Arg0, 0x7, Local0) + Return(Local0) + } + + Method (BI06, 1, NotSerialized) + { + Xor(Arg0, 16, Local0) + Return(Local0) + } + + Method (BI07, 1, NotSerialized) + { + Not(Arg0, Local0) + Return(Local0) + } + + Method (BI08, 1, NotSerialized) + { + Return(FindSetLeftBit(Arg0)) + } + + Method (BI09, 1, NotSerialized) + { + Return(FindSetRightBit(Arg0)) + } + + // Logic ops + Method (LO00, 2, NotSerialized) + { + Return(Arg0 == Arg1) + } + + Method (LO01, 2, NotSerialized) + { + Return(Arg0 > Arg1) + } + + Method (LO02, 2, NotSerialized) + { + Return(Arg0 >= Arg1) + } + + Method (LO03, 2, NotSerialized) + { + Return(Arg0 != Arg1) + } + + Method (LO04, 2, NotSerialized) + { + Return(Arg0 < Arg1) + } + + Method (LO05, 2, NotSerialized) + { + Return(Arg0 <= Arg1) + } + + Method (LO06, 2, NotSerialized) + { + Return(Arg0 && Arg1) + } + + Method (LO07, 2, NotSerialized) + { + Return(Arg0 || Arg1) + } + + Method (LO08, 1, NotSerialized) + { + Return(!Arg0) + } }