From a172621af719998afbe1faf749f9f00927333742 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Mon, 27 Nov 2017 07:25:33 +0000 Subject: [PATCH] acpi: implement string <=> int converter The converter is pretty basic at the moment and it only supports converting to/from Integer and String types. This commit also includes some argument => uint64 conversion helpers that will serve as the basis for implementing the ALU opcodes. --- src/gopheros/device/acpi/aml/vm.go | 2 + src/gopheros/device/acpi/aml/vm_convert.go | 101 +++++++++ .../device/acpi/aml/vm_convert_test.go | 206 ++++++++++++++++++ 3 files changed, 309 insertions(+) diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index 43ce83e..cd05166 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -16,6 +16,8 @@ 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"} ) // objRef is a pointer to an argument (local or global) or a named AML object. diff --git a/src/gopheros/device/acpi/aml/vm_convert.go b/src/gopheros/device/acpi/aml/vm_convert.go index 180133f..dc689d1 100644 --- a/src/gopheros/device/acpi/aml/vm_convert.go +++ b/src/gopheros/device/acpi/aml/vm_convert.go @@ -1,5 +1,7 @@ package aml +import "strconv" + // valueType represents the data types that the AML interpreter can process. type valueType uint8 @@ -70,6 +72,105 @@ func (vt valueType) String() string { } } +// vmToIntArg attempts to convert the entity argument at position argIndex to +// a uint64 value. +func vmToIntArg(ctx *execContext, ent Entity, argIndex int) (uint64, *Error) { + args := ent.getArgs() + if len(args) <= argIndex { + return 0, errArgIndexOutOfBounds + } + + argVal, err := vmConvert(ctx, args[argIndex], valueTypeInteger) + if err != nil { + return 0, err + } + + return argVal.(uint64), nil +} + +// vmToIntArgs2 attempts to convert the entity arguments at positions argIndex1 +// and argIndex2 to uint64 values. +func vmToIntArgs2(ctx *execContext, ent Entity, argIndex1, argIndex2 int) (uint64, uint64, *Error) { + args := ent.getArgs() + if len(args) <= argIndex1 || len(args) <= argIndex2 { + return 0, 0, errArgIndexOutOfBounds + } + + argVal1, err := vmConvert(ctx, args[argIndex1], valueTypeInteger) + if err != nil { + return 0, 0, err + } + + argVal2, err := vmConvert(ctx, args[argIndex2], valueTypeInteger) + if err != nil { + return 0, 0, err + } + + return argVal1.(uint64), argVal2.(uint64), nil + +} + +// vmConvert attempts to convert the input argument to the specified type. If +// the conversion is not possible then vmConvert returns back an error. +func vmConvert(ctx *execContext, arg interface{}, toType valueType) (interface{}, *Error) { + argVal, err := vmLoad(ctx, arg) + if err != nil { + return nil, err + } + + // Conversion not required; we can just read the value directly + argType := vmTypeOf(ctx, argVal) + if argType == toType { + return argVal, nil + } + + switch argType { + case valueTypeString: + argAsStr := argVal.(string) + switch toType { + case valueTypeInteger: + // According to the spec: If no integer object exists, a new integer is created. The + // integer is initialized to the value zero and the ASCII string is interpreted as a + // hexadecimal constant. Each string character is interpreted as a hexadecimal value + // (‘0’- ‘9’, ‘A’-‘F’, ‘a’-‘f’), starting with the first character as the most significant + // digit, and ending with the first non-hexadecimal character, end-of-string, or + // when the size of an integer is reached (8 characters for 32-bit integers and 16 + // characters for 64-bit integers). Note: the first non-hex character terminates the + // 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 + } + + var res = uint64(0) + for i := 0; i < len(argAsStr) && i < ctx.vm.sizeOfIntInBits>>2; i++ { + ch := argAsStr[i] + if ch >= '0' && ch <= '9' { + res = res<<4 | uint64(ch-'0') + } else if ch >= 'a' && ch <= 'f' { + res = res<<4 | uint64(ch-'a'+10) + } else if ch >= 'A' && ch <= 'F' { + res = res<<4 | uint64(ch-'A'+10) + } else { + // non-hex character; we should stop and return without an error + break + } + } + + return res, nil + } + case valueTypeInteger: + argAsInt := argVal.(uint64) + switch toType { + case valueTypeString: + // Integers are formatted as hex strings without a 0x prefix + return strconv.FormatUint(argAsInt, 16), nil + } + } + + return nil, errConversionFailed +} + // vmTypeOf returns the type of data stored inside the supplied argument. func vmTypeOf(ctx *execContext, arg interface{}) valueType { // Some objects (e.g args, constEntity contents) may require to perform diff --git a/src/gopheros/device/acpi/aml/vm_convert_test.go b/src/gopheros/device/acpi/aml/vm_convert_test.go index 446e9d6..9125f3f 100644 --- a/src/gopheros/device/acpi/aml/vm_convert_test.go +++ b/src/gopheros/device/acpi/aml/vm_convert_test.go @@ -1,6 +1,7 @@ package aml import ( + "reflect" "testing" ) @@ -172,3 +173,208 @@ func TestVMTypeOf(t *testing.T) { } } } + +func TestVMToIntArg(t *testing.T) { + ctx := &execContext{ + vm: &VM{sizeOfIntInBits: 64}, + } + + specs := []struct { + ent Entity + argIndex int + expVal uint64 + expErr *Error + }{ + { + &unnamedEntity{ + args: []interface{}{uint64(42)}, + }, + 0, + 42, + nil, + }, + { + &unnamedEntity{ + args: []interface{}{""}, + }, + 0, + 0, + errConversionFailed, + }, + { + &unnamedEntity{}, + 0, + 0, + errArgIndexOutOfBounds, + }, + } + + for specIndex, spec := range specs { + got, err := vmToIntArg(ctx, spec.ent, spec.argIndex) + switch { + case !reflect.DeepEqual(spec.expErr, err): + t.Errorf("[spec %d] expected error: %v; got: %v", specIndex, spec.expErr, err) + case got != spec.expVal: + t.Errorf("[spec %d] expected to get value %v; got %v", specIndex, spec.expVal, got) + } + } +} + +func TestVMToIntArgs2(t *testing.T) { + ctx := &execContext{ + vm: &VM{sizeOfIntInBits: 64}, + } + + specs := []struct { + ent Entity + argIndex [2]int + expVal [2]uint64 + expErr *Error + }{ + { + &unnamedEntity{ + args: []interface{}{uint64(42), uint64(999)}, + }, + [2]int{0, 1}, + [2]uint64{42, 999}, + nil, + }, + { + &unnamedEntity{ + args: []interface{}{"", uint64(999)}, + }, + [2]int{0, 1}, + [2]uint64{0, 0}, + errConversionFailed, + }, + { + &unnamedEntity{ + args: []interface{}{uint64(123), ""}, + }, + [2]int{0, 1}, + [2]uint64{0, 0}, + errConversionFailed, + }, + { + &unnamedEntity{}, + [2]int{128, 0}, + [2]uint64{0, 0}, + errArgIndexOutOfBounds, + }, + { + &unnamedEntity{args: []interface{}{uint64(42)}}, + [2]int{0, 128}, + [2]uint64{0, 0}, + errArgIndexOutOfBounds, + }, + } + + for specIndex, spec := range specs { + got1, got2, err := vmToIntArgs2(ctx, spec.ent, 0, 1) + switch { + case !reflect.DeepEqual(spec.expErr, err): + t.Errorf("[spec %d] expected error: %v; got: %v", specIndex, spec.expErr, err) + case got1 != spec.expVal[0] || got2 != spec.expVal[1]: + t.Errorf("[spec %d] expected to get values [%v, %v] ; got [%v, %v]", specIndex, + spec.expVal[0], spec.expVal[1], + got1, got2, + ) + } + } +} + +func TestVMConvert(t *testing.T) { + specs := []struct { + ctx *execContext + in interface{} + toType valueType + expVal interface{} + expErr *Error + }{ + // No conversion required + { + nil, + "foo", + valueTypeString, + "foo", + nil, + }, + // string -> int (32-bit mode) + { + &execContext{ + vm: &VM{sizeOfIntInBits: 32}, + }, + "bAdF00D9", + valueTypeInteger, + uint64(0xbadf00d9), + nil, + }, + // string -> int (64-bit mode) + { + &execContext{ + vm: &VM{sizeOfIntInBits: 64}, + }, + "feedfaceDEADC0DE-ignored-data", + valueTypeInteger, + uint64(0xfeedfacedeadc0de), + nil, + }, + // string -> int (64-bit mode) ; stop at first non-hex char + { + &execContext{ + vm: &VM{sizeOfIntInBits: 64}, + }, + "feedGARBAGE", + valueTypeInteger, + uint64(0xfeed), + nil, + }, + // string -> int; empty string should trigger an error + { + &execContext{ + vm: &VM{sizeOfIntInBits: 64}, + }, + "", + valueTypeInteger, + nil, + errConversionFailed, + }, + // int -> string + { + nil, + uint64(0xfeedfacedeadc0de), + valueTypeString, + "feedfacedeadc0de", + nil, + }, + // vmLoad returns an error + { + nil, + &unnamedEntity{op: opAdd}, + valueTypeInteger, + nil, + &Error{message: "readArg: unsupported entity type: " + opAdd.String()}, + }, + // unsupported conversion + { + nil, + uint64(42), + valueTypeDevice, + nil, + errConversionFailed, + }, + } + + for specIndex, spec := range specs { + got, err := vmConvert(spec.ctx, spec.in, spec.toType) + switch { + case !reflect.DeepEqual(spec.expErr, err): + t.Errorf("[spec %d] expected error: %v; got: %v", specIndex, spec.expErr, err) + case got != spec.expVal: + t.Errorf("[spec %d] expected to get value %v (type: %v); got %v (type %v)", specIndex, + spec.expVal, reflect.TypeOf(spec.expVal), + got, reflect.TypeOf(got), + ) + } + } +}