diff --git a/src/gopheros/device/acpi/aml/entity.go b/src/gopheros/device/acpi/aml/entity.go new file mode 100644 index 0000000..0e0658b --- /dev/null +++ b/src/gopheros/device/acpi/aml/entity.go @@ -0,0 +1,492 @@ +package aml + +import ( + "gopheros/kernel/kfmt" + "io" +) + +type resolver interface { + Resolve(io.Writer, ScopeEntity) bool +} + +// Entity is an interface implemented by all AML entities. +type Entity interface { + Name() string + Parent() ScopeEntity + TableHandle() uint8 + + setTableHandle(uint8) + getOpcode() opcode + setOpcode(opcode) + setParent(ScopeEntity) + getArgs() []interface{} + setArg(uint8, interface{}) bool +} + +// ScopeEntity is an interface that is implemented by entities that define an +// AML scope. +type ScopeEntity interface { + Entity + + Children() []Entity + Append(Entity) bool + + removeChild(Entity) + lastChild() Entity +} + +// unnamedEntity defines an unnamed entity that can be attached to a parent scope. +type unnamedEntity struct { + tableHandle uint8 + op opcode + args []interface{} + parent ScopeEntity +} + +func (ent *unnamedEntity) getOpcode() opcode { return ent.op } +func (ent *unnamedEntity) setOpcode(op opcode) { ent.op = op } +func (ent *unnamedEntity) Name() string { return "" } +func (ent *unnamedEntity) Parent() ScopeEntity { return ent.parent } +func (ent *unnamedEntity) setParent(parent ScopeEntity) { ent.parent = parent } +func (ent *unnamedEntity) getArgs() []interface{} { return ent.args } +func (ent *unnamedEntity) setArg(_ uint8, arg interface{}) bool { + ent.args = append(ent.args, arg) + return true +} +func (ent *unnamedEntity) TableHandle() uint8 { return ent.tableHandle } +func (ent *unnamedEntity) setTableHandle(h uint8) { ent.tableHandle = h } + +// namedEntity is a named entity that can be attached to the parent scope. The +// setArg() implementation for this type expects arg at index 0 to contain the +// entity name. +type namedEntity struct { + tableHandle uint8 + op opcode + args []interface{} + parent ScopeEntity + + name string +} + +func (ent *namedEntity) getOpcode() opcode { return ent.op } +func (ent *namedEntity) setOpcode(op opcode) { ent.op = op } +func (ent *namedEntity) Name() string { return ent.name } +func (ent *namedEntity) Parent() ScopeEntity { return ent.parent } +func (ent *namedEntity) setParent(parent ScopeEntity) { ent.parent = parent } +func (ent *namedEntity) getArgs() []interface{} { return ent.args } +func (ent *namedEntity) setArg(argIndex uint8, arg interface{}) bool { + // arg 0 is the entity name + if argIndex == 0 { + var ok bool + ent.name, ok = arg.(string) + return ok + } + + ent.args = append(ent.args, arg) + return true +} +func (ent *namedEntity) TableHandle() uint8 { return ent.tableHandle } +func (ent *namedEntity) setTableHandle(h uint8) { ent.tableHandle = h } + +// constEntity is an unnamedEntity which always evaluates to a constant value. +// Calls to setArg for argument index 0 will memoize the argument value that is +// stored inside this entity. +type constEntity struct { + tableHandle uint8 + op opcode + args []interface{} + parent ScopeEntity + + val interface{} +} + +func (ent *constEntity) getOpcode() opcode { return ent.op } +func (ent *constEntity) setOpcode(op opcode) { + ent.op = op + + // special const opcode cases that have an implicit value + switch ent.op { + case opZero: + ent.val = uint64(0) + case opOne: + ent.val = uint64(1) + case opOnes: + ent.val = uint64(1<<64 - 1) + } +} +func (ent *constEntity) Name() string { return "" } +func (ent *constEntity) Parent() ScopeEntity { return ent.parent } +func (ent *constEntity) setParent(parent ScopeEntity) { ent.parent = parent } +func (ent *constEntity) getArgs() []interface{} { return ent.args } +func (ent *constEntity) setArg(argIndex uint8, arg interface{}) bool { + ent.val = arg + return argIndex == 0 +} +func (ent *constEntity) TableHandle() uint8 { return ent.tableHandle } +func (ent *constEntity) setTableHandle(h uint8) { ent.tableHandle = h } + +// scopeEntity is an optionally named entity that defines a scope. +type scopeEntity struct { + tableHandle uint8 + op opcode + args []interface{} + parent ScopeEntity + + name string + children []Entity +} + +func (ent *scopeEntity) getOpcode() opcode { return ent.op } +func (ent *scopeEntity) setOpcode(op opcode) { ent.op = op } +func (ent *scopeEntity) Name() string { return ent.name } +func (ent *scopeEntity) Parent() ScopeEntity { return ent.parent } +func (ent *scopeEntity) setParent(parent ScopeEntity) { ent.parent = parent } +func (ent *scopeEntity) getArgs() []interface{} { return ent.args } +func (ent *scopeEntity) setArg(argIndex uint8, arg interface{}) bool { + // arg 0 *may* be the entity name. If it's not a string just add it to + // the arg list. + if argIndex == 0 { + var ok bool + if ent.name, ok = arg.(string); ok { + return true + } + } + + ent.args = append(ent.args, arg) + return true +} +func (ent *scopeEntity) Children() []Entity { return ent.children } +func (ent *scopeEntity) Append(child Entity) bool { + child.setParent(ent) + ent.children = append(ent.children, child) + return true +} +func (ent *scopeEntity) lastChild() Entity { return ent.children[len(ent.children)-1] } +func (ent *scopeEntity) removeChild(child Entity) { + for index := 0; index < len(ent.children); index++ { + if ent.children[index] == child { + ent.children = append(ent.children[:index], ent.children[index+1:]...) + return + } + } +} +func (ent *scopeEntity) TableHandle() uint8 { return ent.tableHandle } +func (ent *scopeEntity) setTableHandle(h uint8) { ent.tableHandle = h } + +// bufferEntity defines a buffer object. +type bufferEntity struct { + unnamedEntity + + size interface{} + data []byte +} + +func (ent *bufferEntity) setArg(argIndex uint8, arg interface{}) bool { + switch argIndex { + case 0: // size + ent.size = arg + return true + case 1: // data + if byteSlice, ok := arg.([]byte); ok { + ent.data = byteSlice + return true + } + } + + return false +} + +// bufferFieldEntity describes a bit/byte/word/dword/qword or arbitrary length +// buffer field. +type bufferFieldEntity struct { + namedEntity +} + +func (ent *bufferFieldEntity) setArg(argIndex uint8, arg interface{}) bool { + // opCreateField specifies the name using the arg at index 3 while + // opCreateXXXField (byte, word e.t.c) specifies the name using the + // arg at index 2 + if (ent.op == opCreateField && argIndex == 3) || argIndex == 2 { + var ok bool + ent.name, ok = arg.(string) + return ok + } + ent.args = append(ent.args, arg) + return true +} + +// RegionSpace describes the memory space where a region is located. +type RegionSpace uint8 + +// The list of supported RegionSpace values. +const ( + RegionSpaceSystemMemory RegionSpace = iota + RegionSpaceSystemIO + RegionSpacePCIConfig + RegionSpaceEmbeddedControl + RegionSpaceSMBus + RegionSpacePCIBarTarget + RegionSpaceIPMI +) + +// regionEntity defines a region located at a particular space (e.g in memory, +// an embedded controller, the SMBus e.t.c). +type regionEntity struct { + namedEntity + + space RegionSpace +} + +func (ent *regionEntity) setArg(argIndex uint8, arg interface{}) bool { + var ok bool + switch argIndex { + case 0: + ok = ent.namedEntity.setArg(argIndex, arg) + case 1: + // the parser will convert ByteData types to uint64 + var space uint64 + space, ok = arg.(uint64) + ent.space = RegionSpace(space) + case 2, 3: + ent.args = append(ent.args, arg) + ok = true + } + + return ok +} + +// FieldAccessType specifies the type of access (byte, word, e.t.c) used to +// read/write to a field. +type FieldAccessType uint8 + +// The list of supported FieldAccessType values. +const ( + FieldAccessTypeAny FieldAccessType = iota + FieldAccessTypeByte + FieldAccessTypeWord + FieldAccessTypeDword + FieldAccessTypeQword + FieldAccessTypeBuffer +) + +// FieldUpdateRule specifies how a field value is updated when a write uses +// a value with a smaller width than the field. +type FieldUpdateRule uint8 + +// The list of supported FieldUpdateRule values. +const ( + FieldUpdateRulePreserve FieldUpdateRule = iota + FieldUpdateRuleWriteAsOnes + FieldUpdateRuleWriteAsZeros +) + +// FieldAccessAttrib specifies additional information about a particular field +// access. +type FieldAccessAttrib uint8 + +// The list of supported FieldAccessAttrib values. +const ( + FieldAccessAttribQuick FieldAccessAttrib = 0x02 + FieldAccessAttribSendReceive = 0x04 + FieldAccessAttribByte = 0x06 + FieldAccessAttribWord = 0x08 + FieldAccessAttribBlock = 0x0a + FieldAccessAttribBytes = 0x0b // byteCount contains the number of bytes + FieldAccessAttribProcessCall = 0x0c + FieldAccessAttribBlockProcessCall = 0x0d + FieldAccessAttribRawBytes = 0x0e // byteCount contains the number of bytes + FieldAccessAttribRawProcessBytes = 0x0f // byteCount contains the number of bytes +) + +// fieldEntity is a named object that encapsulates the data shared between regular +// fields and index fields. +type fieldEntity struct { + namedEntity + + bitOffset uint32 + bitWidth uint32 + + lock bool + updateRule FieldUpdateRule + + // accessAttrib is valid if accessType is BufferAcc + // for the SMB or GPIO OpRegions. + accessAttrib FieldAccessAttrib + accessType FieldAccessType + + // byteCount is valid when accessAttrib is one of: + // Bytes, RawBytes or RawProcessBytes + byteCount uint8 +} + +// fieldUnitEntity is a field defined inside an operating region. +type fieldUnitEntity struct { + fieldEntity + + // The connection which this field references. + connectionName string + resolvedConnection Entity + + // The region which this field references. + regionName string + resolvedRegion *regionEntity +} + +func (ent *fieldUnitEntity) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool { + var ok bool + if ent.connectionName != "" && ent.resolvedConnection == nil { + if ent.resolvedConnection = scopeFind(ent.parent, rootNs, ent.connectionName); ent.resolvedConnection == nil { + kfmt.Fprintf(errWriter, "[field %s] could not resolve connection reference: %s\n", ent.name, ent.connectionName) + return false + } + } + + if ent.resolvedRegion == nil { + if ent.resolvedRegion, ok = scopeFind(ent.parent, rootNs, ent.regionName).(*regionEntity); !ok { + kfmt.Fprintf(errWriter, "[field %s] could not resolve referenced region: %s\n", ent.name, ent.regionName) + } + } + + return ent.resolvedRegion != nil +} + +// indexFieldEntity is a special field that groups together two field units so a +// index/data register pattern can be implemented. To write a value to an +// indexField, the interpreter must first write the appropriate offset to +// the indexRegister (using the alignment specifid by accessType) and then +// write the actual value to the dataRegister. +type indexFieldEntity struct { + fieldEntity + + // The connection which this field references. + connectionName string + resolvedConnection Entity + + indexRegName string + indexReg *fieldUnitEntity + + dataRegName string + dataReg *fieldUnitEntity +} + +func (ent *indexFieldEntity) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool { + var ok bool + if ent.connectionName != "" && ent.resolvedConnection == nil { + if ent.resolvedConnection = scopeFind(ent.parent, rootNs, ent.connectionName); ent.resolvedConnection == nil { + kfmt.Fprintf(errWriter, "[field %s] could not resolve connection reference: %s\n", ent.name, ent.connectionName) + return false + } + } + + if ent.indexReg == nil { + if ent.indexReg, ok = scopeFind(ent.parent, rootNs, ent.indexRegName).(*fieldUnitEntity); !ok { + kfmt.Fprintf(errWriter, "[indexField %s] could not resolve referenced index register: %s\n", ent.name, ent.indexRegName) + } + } + + if ent.dataReg == nil { + if ent.dataReg, ok = scopeFind(ent.parent, rootNs, ent.dataRegName).(*fieldUnitEntity); !ok { + kfmt.Fprintf(errWriter, "[dataField %s] could not resolve referenced data register: %s\n", ent.name, ent.dataRegName) + } + } + + return ent.indexReg != nil && ent.dataReg != nil +} + +// namedReference holds a named reference to an AML symbol. The spec allows +// the symbol not to be defined at the time when the reference is parsed. In +// such a case (forward reference) it will be resolved after the entire AML +// stream has successfully been parsed. +type namedReference struct { + unnamedEntity + + targetName string + target Entity +} + +func (ref *namedReference) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool { + if ref.target == nil { + ref.target = scopeFind(ref.parent, rootNs, ref.targetName) + } + + if ref.target == nil { + kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, ref.parent.Name()) + } + + return ref.target != nil +} + +// methodInvocationEntity describes an AML method invocation. +type methodInvocationEntity struct { + unnamedEntity + + methodDef *Method +} + +// Method defines an invocable AML method. +type Method struct { + scopeEntity + + tableHandle uint8 + argCount uint8 + serialized bool + syncLevel uint8 +} + +func (m *Method) getOpcode() opcode { return opMethod } + +// Device defines a device. +type Device struct { + scopeEntity + + tableHandle uint8 + + // The methodMap keeps track of all methods exposed by this device. + methodMap map[string]*Method +} + +func (d *Device) getOpcode() opcode { return opDevice } +func (d *Device) setTableHandle(h uint8) { d.tableHandle = h } + +// TableHandle returns the handle of the ACPI table that defines this device. +func (d *Device) TableHandle() uint8 { return d.tableHandle } + +// mutexEntity represents a named mutex object +type mutexEntity struct { + parent ScopeEntity + + // isGlobal is set to true for the pre-defined global mutex (\_GL object) + isGlobal bool + + name string + syncLevel uint8 + tableHandle uint8 +} + +func (ent *mutexEntity) getOpcode() opcode { return opMutex } +func (ent *mutexEntity) setOpcode(op opcode) {} +func (ent *mutexEntity) Name() string { return ent.name } +func (ent *mutexEntity) Parent() ScopeEntity { return ent.parent } +func (ent *mutexEntity) setParent(parent ScopeEntity) { ent.parent = parent } +func (ent *mutexEntity) getArgs() []interface{} { return nil } +func (ent *mutexEntity) setArg(argIndex uint8, arg interface{}) bool { + var ok bool + switch argIndex { + case 0: + // arg 0 is the mutex name + ent.name, ok = arg.(string) + case 1: + // arg1 is the sync level (bits 0:3) + var syncLevel uint64 + syncLevel, ok = arg.(uint64) + ent.syncLevel = uint8(syncLevel) & 0xf + } + return ok +} +func (ent *mutexEntity) TableHandle() uint8 { return ent.tableHandle } +func (ent *mutexEntity) setTableHandle(h uint8) { ent.tableHandle = h } + +// eventEntity represents a named ACPI sync event. +type eventEntity struct { + namedEntity +} diff --git a/src/gopheros/device/acpi/aml/entity_test.go b/src/gopheros/device/acpi/aml/entity_test.go new file mode 100644 index 0000000..7e49e97 --- /dev/null +++ b/src/gopheros/device/acpi/aml/entity_test.go @@ -0,0 +1,143 @@ +package aml + +import ( + "io/ioutil" + "reflect" + "testing" +) + +func TestEntityMethods(t *testing.T) { + specs := []Entity{ + &unnamedEntity{}, + &constEntity{}, + &scopeEntity{}, + &bufferEntity{}, + &fieldUnitEntity{}, + &indexFieldEntity{}, + &namedReference{}, + &methodInvocationEntity{}, + &Method{}, + &Device{}, + &mutexEntity{}, + &eventEntity{}, + } + + t.Run("table handle methods", func(t *testing.T) { + exp := uint8(42) + for specIndex, spec := range specs { + spec.setTableHandle(exp) + if got := spec.TableHandle(); got != exp { + t.Errorf("[spec %d] expected to get back handle %d; got %d", specIndex, exp, got) + } + } + }) + + t.Run("append/remove/get parent methods", func(t *testing.T) { + parent := &scopeEntity{name: `\`} + + for specIndex, spec := range specs { + parent.Append(spec) + if got := spec.Parent(); got != parent { + t.Errorf("[spec %d] expected to get back parent %v; got %v", specIndex, parent, got) + } + + parent.removeChild(spec) + } + + if got := len(parent.Children()); got != 0 { + t.Fatalf("expected parent not to have any child nodes; got %d", got) + } + }) +} + +func TestEntityArgAssignment(t *testing.T) { + specs := []struct { + ent Entity + argList []interface{} + expArgList []interface{} + limitedArgs bool + }{ + { + &unnamedEntity{}, + []interface{}{"foo", 1, "bar"}, + []interface{}{"foo", 1, "bar"}, + false, + }, + { + &constEntity{}, + []interface{}{"foo"}, + nil, // constEntity populates its internal state using the 1st arg + true, + }, + { + &scopeEntity{}, + []interface{}{"foo", 1, 2, 3}, + []interface{}{1, 2, 3}, // scopeEntity will treat arg0 as the scope name if it is a string + false, + }, + { + &bufferEntity{}, + []interface{}{1, []byte{}}, + nil, // bufferEntity populates its internal state using the first 2 args + true, + }, + { + ®ionEntity{}, + []interface{}{"REG0", uint64(0x4), 0, 10}, + []interface{}{0, 10}, // region populates its internal state using the first 2 args + true, + }, + { + &mutexEntity{}, + []interface{}{"MUT0", uint64(1)}, + nil, // mutexEntity populates its internal state using the first 2 args + true, + }, + } + +nextSpec: + for specIndex, spec := range specs { + for i, arg := range spec.argList { + if !spec.ent.setArg(uint8(i), arg) { + t.Errorf("[spec %d] error setting arg %d", specIndex, i) + continue nextSpec + } + } + + if spec.limitedArgs { + if spec.ent.setArg(uint8(len(spec.argList)), nil) { + t.Errorf("[spec %d] expected additional calls to setArg to return false", specIndex) + continue nextSpec + } + } + + if got := spec.ent.getArgs(); !reflect.DeepEqual(got, spec.expArgList) { + t.Errorf("[spec %d] expected to get back arg list %v; got %v", specIndex, spec.expArgList, got) + } + } +} + +func TestEntityResolveErrors(t *testing.T) { + scope := &scopeEntity{name: `\`} + + specs := []resolver{ + // Unknown connection entity + &fieldUnitEntity{connectionName: "CON0"}, + // Unknown region + &fieldUnitEntity{connectionName: `\`, regionName: "REG0"}, + // Unknown connection entity + &indexFieldEntity{connectionName: "CON0"}, + // Unknown index register + &indexFieldEntity{connectionName: `\`, indexRegName: "IND0"}, + // Unknown data register + &indexFieldEntity{connectionName: `\`, indexRegName: `\`, dataRegName: "DAT0"}, + // Unknown reference + &namedReference{unnamedEntity: unnamedEntity{parent: scope}, targetName: "TRG0"}, + } + + for specIndex, spec := range specs { + if spec.Resolve(ioutil.Discard, scope) { + t.Errorf("[spec %d] expected Resolve() to fail", specIndex) + } + } +} diff --git a/src/gopheros/device/acpi/aml/opcode.go b/src/gopheros/device/acpi/aml/opcode.go new file mode 100644 index 0000000..617fc7a --- /dev/null +++ b/src/gopheros/device/acpi/aml/opcode.go @@ -0,0 +1,269 @@ +package aml + +// opcode describes an AML opcode. While AML supports 256 opcodes, some of them +// are specified using a combination of an extension prefix and a code. To map +// each opcode into a single unique value the parser uses an uint16 +// representation of the opcode values. +type opcode uint16 + +// String implements fmt.Stringer for opcode. +func (op opcode) String() string { + for _, entry := range opcodeTable { + if entry.op == op { + return entry.name + } + } + + return "unknown" +} + +// opIsLocalArg returns true if this opcode represents any of the supported local +// function args 0 to 7. +func opIsLocalArg(op opcode) bool { + return op >= opLocal0 && op <= opLocal7 +} + +// opIsMethodArg returns true if this opcode represents any of the supported +// input function args 0 to 6. +func opIsMethodArg(op opcode) bool { + return op >= opArg0 && op <= opArg6 +} + +// opIsArg returns true if this opcode is either a local or a method arg. +func opIsArg(op opcode) bool { + return opIsLocalArg(op) || opIsMethodArg(op) +} + +// opIsDataObject returns true if this opcode is part of a DataObject definition +// +// Grammar: +// DataObject := ComputationalData | DefPackage | DefVarPackage +// ComputationalData := ByteConst | WordConst | DWordConst | QWordConst | String | ConstObj | RevisionOp | DefBuffer +// ConstObj := ZeroOp | OneOp | OnesOp +func opIsDataObject(op opcode) bool { + switch op { + case opBytePrefix, opWordPrefix, opDwordPrefix, opQwordPrefix, opStringPrefix, + opZero, opOne, opOnes, opRevision, opBuffer, opPackage, opVarPackage: + return true + default: + return false + } +} + +// opIsType2 returns true if this is a Type2Opcode. +// +// Grammar: +// Type2Opcode := DefAcquire | DefAdd | DefAnd | DefBuffer | DefConcat | +// DefConcatRes | DefCondRefOf | DefCopyObject | DefDecrement | +// DefDerefOf | DefDivide | DefFindSetLeftBit | DefFindSetRightBit | +// DefFromBCD | DefIncrement | DefIndex | DefLAnd | DefLEqual | +// DefLGreater | DefLGreaterEqual | DefLLess | DefLLessEqual | DefMid | +// DefLNot | DefLNotEqual | DefLoadTable | DefLOr | DefMatch | DefMod | +// DefMultiply | DefNAnd | DefNOr | DefNot | DefObjectType | DefOr | +// DefPackage | DefVarPackage | DefRefOf | DefShiftLeft | DefShiftRight | +// DefSizeOf | DefStore | DefSubtract | DefTimer | DefToBCD | DefToBuffer | +// DefToDecimalString | DefToHexString | DefToInteger | DefToString | +// DefWait | DefXOr +func opIsType2(op opcode) bool { + switch op { + case opAcquire, opAdd, opAnd, opBuffer, opConcat, + opConcatRes, opCondRefOf, opCopyObject, opDecrement, + opDerefOf, opDivide, opFindSetLeftBit, opFindSetRightBit, + opFromBCD, opIncrement, opIndex, opLand, opLEqual, + opLGreater, opLLess, opMid, + opLnot, opLoadTable, opLor, opMatch, opMod, + opMultiply, opNand, opNor, opNot, opObjectType, opOr, + opPackage, opVarPackage, opRefOf, opShiftLeft, opShiftRight, + opSizeOf, opStore, opSubtract, opTimer, opToBCD, opToBuffer, + opToDecimalString, opToHexString, opToInteger, opToString, + opWait, opXor: + return true + default: + return false + } +} + +// opIsBufferField returens true if this opcode describes a +// buffer field creation operation. +func opIsBufferField(op opcode) bool { + switch op { + case opCreateField, opCreateBitField, opCreateByteField, opCreateWordField, opCreateDWordField, opCreateQWordField: + return true + default: + return false + } +} + +// objType represents the object types that are supported by the AML parser. +type objType uint8 + +// The list of AML object types. +const ( + objTypeAny objType = iota + objTypeInteger + objTypeString + objTypeBuffer + objTypePackage + objTypeDevice + objTypeEvent + objTypeMethod + objTypeMutex + objTypeRegion + objTypePower + objTypeProcessor + objTypeThermal + objTypeBufferField + objTypeLocalRegionField + objTypeLocalBankField + objTypeLocalReference + objTypeLocalAlias + objTypeLocalScope + objTypeLocalVariable + objTypeMethodArgument +) + +// opFlag specifies a list of OR-able flags that describe the object +// type/attributes generated by a particular opcode. +type opFlag uint16 + +const ( + opFlagNone opFlag = 1 << iota + opFlagHasPkgLen + opFlagNamed + opFlagConstant + opFlagReference + opFlagArithmetic + opFlagCreate + opFlagReturn + opFlagExecutable + opFlagNoOp + opFlagScoped +) + +// is returns true if f is set in this opFlag. +func (fl opFlag) is(f opFlag) bool { + return (fl & f) != 0 +} + +// opArgFlags encodes up to 7 opArgFlag values in a uint64 value. +type opArgFlags uint64 + +// argCount returns the number of encoded args in the given flag. +func (fl opArgFlags) argCount() (count uint8) { + // Each argument is specified using 8 bits with 0x0 indicating the end of the + // argument list + for ; fl&0xf != 0; fl, count = fl>>8, count+1 { + } + + return count +} + +// arg returns the arg flags for argument "num" where num is the 0-based index +// of the argument to return. The allowed values for num are 0-6. +func (fl opArgFlags) arg(num uint8) opArgFlag { + return opArgFlag((fl >> (num * 8)) & 0xf) +} + +// contains returns true if the arg flags contain any argument with type x. +func (fl opArgFlags) contains(x opArgFlag) bool { + // Each argument is specified using 8 bits with 0x0 indicating the end of the + // argument list + for ; fl&0xf != 0; fl >>= 8 { + if opArgFlag(fl&0xf) == x { + return true + } + } + + return false +} + +// opArgFlag represents the type of an argument expected by a particular opcode. +type opArgFlag uint8 + +// The list of supported opArgFlag values. +const ( + _ opArgFlag = iota + opArgTermList + opArgTermObj + opArgByteList + opArgPackage + opArgString + opArgByteData + opArgWord + opArgDword + opArgQword + opArgNameString + opArgSuperName + opArgSimpleName + opArgDataRefObj + opArgTarget + opArgFieldList +) + +// String implements fmt.Stringer for opArgFlag. +func (fl opArgFlag) String() string { + switch fl { + case opArgTermList: + return "opArgTermList" + case opArgTermObj: + return "opArgTermObj" + case opArgByteList: + return "opArgByteList" + case opArgPackage: + return "opArgPackage" + case opArgString: + return "opArgString" + case opArgByteData: + return "opArgByteData" + case opArgWord: + return "opArgWord" + case opArgDword: + return "opArgDword" + case opArgQword: + return "opArgQword" + case opArgNameString: + return "opArgNameString" + case opArgSuperName: + return "opArgSuperName" + case opArgSimpleName: + return "opArgSimpleName" + case opArgDataRefObj: + return "opArgDataRefObj" + case opArgTarget: + return "opArgTarget" + case opArgFieldList: + return "opArgFieldList" + } + return "" +} + +func makeArg0() opArgFlags { return 0 } +func makeArg1(arg0 opArgFlag) opArgFlags { return opArgFlags(arg0) } +func makeArg2(arg0, arg1 opArgFlag) opArgFlags { return opArgFlags(arg1)<<8 | opArgFlags(arg0) } +func makeArg3(arg0, arg1, arg2 opArgFlag) opArgFlags { + return opArgFlags(arg2)<<16 | opArgFlags(arg1)<<8 | opArgFlags(arg0) +} +func makeArg4(arg0, arg1, arg2, arg3 opArgFlag) opArgFlags { + return opArgFlags(arg3)<<24 | opArgFlags(arg2)<<16 | opArgFlags(arg1)<<8 | opArgFlags(arg0) +} +func makeArg5(arg0, arg1, arg2, arg3, arg4 opArgFlag) opArgFlags { + return opArgFlags(arg4)<<32 | opArgFlags(arg3)<<24 | opArgFlags(arg2)<<16 | opArgFlags(arg1)<<8 | opArgFlags(arg0) +} +func makeArg6(arg0, arg1, arg2, arg3, arg4, arg5 opArgFlag) opArgFlags { + return opArgFlags(arg5)<<40 | opArgFlags(arg4)<<32 | opArgFlags(arg3)<<24 | opArgFlags(arg2)<<16 | opArgFlags(arg1)<<8 | opArgFlags(arg0) +} +func makeArg7(arg0, arg1, arg2, arg3, arg4, arg5, arg6 opArgFlag) opArgFlags { + return opArgFlags(arg6)<<48 | opArgFlags(arg5)<<40 | opArgFlags(arg4)<<32 | opArgFlags(arg3)<<24 | opArgFlags(arg2)<<16 | opArgFlags(arg1)<<8 | opArgFlags(arg0) +} + +// opcodeInfo contains all known information about an opcode, +// its argument count and types as well as the type of object +// represented by it. +type opcodeInfo struct { + op opcode + name string + objType objType + + flags opFlag + argFlags opArgFlags +} diff --git a/src/gopheros/device/acpi/aml/opcode_table.go b/src/gopheros/device/acpi/aml/opcode_table.go new file mode 100644 index 0000000..ba8e500 --- /dev/null +++ b/src/gopheros/device/acpi/aml/opcode_table.go @@ -0,0 +1,317 @@ +package aml + +const ( + badOpcode = 0xff + extOpPrefix = 0x5b + + // Regular opcode list + opZero = opcode(0x00) + opOne = opcode(0x01) + opAlias = opcode(0x06) + opName = opcode(0x08) + opBytePrefix = opcode(0x0a) + opWordPrefix = opcode(0x0b) + opDwordPrefix = opcode(0x0c) + opStringPrefix = opcode(0x0d) + opQwordPrefix = opcode(0x0e) + opScope = opcode(0x10) + opBuffer = opcode(0x11) + opPackage = opcode(0x12) + opVarPackage = opcode(0x13) + opMethod = opcode(0x14) + opExternal = opcode(0x15) + opLocal0 = opcode(0x60) + opLocal1 = opcode(0x61) + opLocal2 = opcode(0x62) + opLocal3 = opcode(0x63) + opLocal4 = opcode(0x64) + opLocal5 = opcode(0x65) + opLocal6 = opcode(0x66) + opLocal7 = opcode(0x67) + opArg0 = opcode(0x68) + opArg1 = opcode(0x69) + opArg2 = opcode(0x6a) + opArg3 = opcode(0x6b) + opArg4 = opcode(0x6c) + opArg5 = opcode(0x6d) + opArg6 = opcode(0x6e) + opStore = opcode(0x70) + opRefOf = opcode(0x71) + opAdd = opcode(0x72) + opConcat = opcode(0x73) + opSubtract = opcode(0x74) + opIncrement = opcode(0x75) + opDecrement = opcode(0x76) + opMultiply = opcode(0x77) + opDivide = opcode(0x78) + opShiftLeft = opcode(0x79) + opShiftRight = opcode(0x7a) + opAnd = opcode(0x7b) + opNand = opcode(0x7c) + opOr = opcode(0x7d) + opNor = opcode(0x7e) + opXor = opcode(0x7f) + opNot = opcode(0x80) + opFindSetLeftBit = opcode(0x81) + opFindSetRightBit = opcode(0x82) + opDerefOf = opcode(0x83) + opConcatRes = opcode(0x84) + opMod = opcode(0x85) + opNotify = opcode(0x86) + opSizeOf = opcode(0x87) + opIndex = opcode(0x88) + opMatch = opcode(0x89) + opCreateDWordField = opcode(0x8a) + opCreateWordField = opcode(0x8b) + opCreateByteField = opcode(0x8c) + opCreateBitField = opcode(0x8d) + opObjectType = opcode(0x8e) + opCreateQWordField = opcode(0x8f) + opLand = opcode(0x90) + opLor = opcode(0x91) + opLnot = opcode(0x92) + opLEqual = opcode(0x93) + opLGreater = opcode(0x94) + opLLess = opcode(0x95) + opToBuffer = opcode(0x96) + opToDecimalString = opcode(0x97) + opToHexString = opcode(0x98) + opToInteger = opcode(0x99) + opToString = opcode(0x9c) + opCopyObject = opcode(0x9d) + opMid = opcode(0x9e) + opContinue = opcode(0x9f) + opIf = opcode(0xa0) + opElse = opcode(0xa1) + opWhile = opcode(0xa2) + opNoop = opcode(0xa3) + opReturn = opcode(0xa4) + opBreak = opcode(0xa5) + opBreakPoint = opcode(0xcc) + opOnes = opcode(0xff) + // Extended opcodes + opMutex = opcode(0xff + 0x01) + opEvent = opcode(0xff + 0x02) + opCondRefOf = opcode(0xff + 0x12) + opCreateField = opcode(0xff + 0x13) + opLoadTable = opcode(0xff + 0x1f) + opLoad = opcode(0xff + 0x20) + opStall = opcode(0xff + 0x21) + opSleep = opcode(0xff + 0x22) + opAcquire = opcode(0xff + 0x23) + opSignal = opcode(0xff + 0x24) + opWait = opcode(0xff + 0x25) + opReset = opcode(0xff + 0x26) + opRelease = opcode(0xff + 0x27) + opFromBCD = opcode(0xff + 0x28) + opToBCD = opcode(0xff + 0x29) + opUnload = opcode(0xff + 0x2a) + opRevision = opcode(0xff + 0x30) + opDebug = opcode(0xff + 0x31) + opFatal = opcode(0xff + 0x32) + opTimer = opcode(0xff + 0x33) + opOpRegion = opcode(0xff + 0x80) + opField = opcode(0xff + 0x81) + opDevice = opcode(0xff + 0x82) + opProcessor = opcode(0xff + 0x83) + opPowerRes = opcode(0xff + 0x84) + opThermalZone = opcode(0xff + 0x85) + opIndexField = opcode(0xff + 0x86) + opBankField = opcode(0xff + 0x87) + opDataRegion = opcode(0xff + 0x88) +) + +// The opcode table contains all opcode-related information that the parser knows. +// This table is modeled after a similar table used in the acpica implementation. +var opcodeTable = []opcodeInfo{ + /*0x00*/ {opZero, "Zero", objTypeInteger, opFlagConstant, makeArg0()}, + /*0x01*/ {opOne, "One", objTypeInteger, opFlagConstant, makeArg0()}, + /*0x02*/ {opAlias, "Alias", objTypeLocalAlias, opFlagNamed, makeArg2(opArgNameString, opArgNameString)}, + /*0x03*/ {opName, "Name", objTypeAny, opFlagNamed, makeArg2(opArgNameString, opArgDataRefObj)}, + /*0x04*/ {opBytePrefix, "Byte", objTypeInteger, opFlagConstant, makeArg1(opArgByteData)}, + /*0x05*/ {opWordPrefix, "Word", objTypeInteger, opFlagConstant, makeArg1(opArgWord)}, + /*0x06*/ {opDwordPrefix, "Dword", objTypeInteger, opFlagConstant, makeArg1(opArgDword)}, + /*0x07*/ {opStringPrefix, "String", objTypeString, opFlagConstant, makeArg1(opArgString)}, + /*0x08*/ {opQwordPrefix, "Qword", objTypeInteger, opFlagConstant, makeArg1(opArgQword)}, + /*0x09*/ {opScope, "Scope", objTypeLocalScope, opFlagNamed, makeArg2(opArgNameString, opArgTermList)}, + /*0x0a*/ {opBuffer, "Buffer", objTypeBuffer, opFlagHasPkgLen, makeArg2(opArgTermObj, opArgByteList)}, + /*0x0b*/ {opPackage, "Package", objTypePackage, opFlagNone, makeArg2(opArgByteData, opArgTermList)}, + /*0x0c*/ {opVarPackage, "VarPackage", objTypePackage, opFlagNone, makeArg2(opArgByteData, opArgTermList)}, + /*0x0d*/ {opMethod, "Method", objTypeMethod, opFlagNamed | opFlagScoped, makeArg3(opArgNameString, opArgByteData, opArgTermList)}, + /*0x0e*/ {opExternal, "External", objTypeAny, opFlagNamed, makeArg3(opArgNameString, opArgByteData, opArgByteData)}, + /*0x0f*/ {opLocal0, "Local0", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x10*/ {opLocal1, "Local1", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x11*/ {opLocal2, "Local2", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x12*/ {opLocal3, "Local3", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x13*/ {opLocal4, "Local4", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0120*/ {opLocal5, "Local5", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x15*/ {opLocal6, "Local6", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x16*/ {opLocal7, "Local7", objTypeLocalVariable, opFlagExecutable, makeArg0()}, + /*0x17*/ {opArg0, "Arg0", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x18*/ {opArg1, "Arg1", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x19*/ {opArg2, "Arg2", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x1a*/ {opArg3, "Arg3", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x1b*/ {opArg4, "Arg4", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x1c*/ {opArg5, "Arg5", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x1d*/ {opArg6, "Arg6", objTypeMethodArgument, opFlagExecutable, makeArg0()}, + /*0x1e*/ {opStore, "Store", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgSuperName)}, + /*0x1f*/ {opRefOf, "RefOf", objTypeAny, opFlagReference | opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x20*/ {opAdd, "Add", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x21*/ {opConcat, "Concat", objTypeAny, opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x22*/ {opSubtract, "Subtract", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x23*/ {opIncrement, "Increment", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x24*/ {opDecrement, "Decrement", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x25*/ {opMultiply, "Multiply", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x26*/ {opDivide, "Divide", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg4(opArgTermObj, opArgTermObj, opArgTarget, opArgTarget)}, + /*0x27*/ {opShiftLeft, "ShiftLeft", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x28*/ {opShiftRight, "ShiftRight", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x29*/ {opAnd, "And", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x2a*/ {opNand, "Nand", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x2b*/ {opOr, "Or", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x2c*/ {opNor, "Nor", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x2d*/ {opXor, "Xor", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x2e*/ {opNot, "Not", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x2f*/ {opFindSetLeftBit, "FindSetLeftBit", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x30*/ {opFindSetRightBit, "FindSetRightBit", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x31*/ {opDerefOf, "DerefOf", objTypeAny, opFlagExecutable, makeArg1(opArgTermObj)}, + /*0x32*/ {opConcatRes, "ConcatRes", objTypeAny, opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x33*/ {opMod, "Mod", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x34*/ {opNotify, "Notify", objTypeAny, opFlagExecutable, makeArg2(opArgSuperName, opArgTermObj)}, + /*0x35*/ {opSizeOf, "SizeOf", objTypeAny, opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x36*/ {opIndex, "Index", objTypeAny, opFlagExecutable, makeArg3(opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x37*/ {opMatch, "Match", objTypeAny, opFlagExecutable, makeArg6(opArgTermObj, opArgByteData, opArgTermObj, opArgByteData, opArgTermObj, opArgTermObj)}, + /*0x38*/ {opCreateDWordField, "CreateDWordField", objTypeBufferField, opFlagNamed | opFlagCreate, makeArg3(opArgTermObj, opArgTermObj, opArgNameString)}, + /*0x39*/ {opCreateWordField, "CreateWordField", objTypeBufferField, opFlagNamed | opFlagCreate, makeArg3(opArgTermObj, opArgTermObj, opArgNameString)}, + /*0x3a*/ {opCreateByteField, "CreateByteField", objTypeBufferField, opFlagNamed | opFlagCreate, makeArg3(opArgTermObj, opArgTermObj, opArgNameString)}, + /*0x3b*/ {opCreateBitField, "CreateBitField", objTypeBufferField, opFlagNamed | opFlagCreate, makeArg3(opArgTermObj, opArgTermObj, opArgNameString)}, + /*0x3c*/ {opObjectType, "ObjectType", objTypeAny, opFlagNone, makeArg1(opArgSuperName)}, + /*0x3d*/ {opCreateQWordField, "CreateQWordField", objTypeBufferField, opFlagNamed | opFlagCreate, makeArg3(opArgTermObj, opArgTermObj, opArgNameString)}, + /*0x3e*/ {opLand, "Land", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTermObj)}, + /*0x3f*/ {opLor, "Lor", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTermObj)}, + /*0x40*/ {opLnot, "Lnot", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg1(opArgTermObj)}, + /*0x41*/ {opLEqual, "LEqual", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTermObj)}, + /*0x42*/ {opLGreater, "LGreater", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTermObj)}, + /*0x43*/ {opLLess, "LLess", objTypeAny, opFlagArithmetic | opFlagExecutable, makeArg2(opArgTermObj, opArgTermObj)}, + /*0x44*/ {opToBuffer, "ToBuffer", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x45*/ {opToDecimalString, "ToDecimalString", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x46*/ {opToHexString, "ToHexString", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x47*/ {opToInteger, "ToInteger", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x48*/ {opToString, "ToString", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x49*/ {opCopyObject, "CopyObject", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgSimpleName)}, + /*0x4a*/ {opMid, "Mid", objTypeAny, opFlagExecutable, makeArg4(opArgTermObj, opArgTermObj, opArgTermObj, opArgTarget)}, + /*0x4b*/ {opContinue, "Continue", objTypeAny, opFlagExecutable, makeArg0()}, + /*0x4c*/ {opIf, "If", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTermList)}, + /*0x4d*/ {opElse, "Else", objTypeAny, opFlagExecutable | opFlagScoped, makeArg1(opArgTermList)}, + /*0x4e*/ {opWhile, "While", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTermList)}, + /*0x4f*/ {opNoop, "Noop", objTypeAny, opFlagNoOp, makeArg0()}, + /*0x50*/ {opReturn, "Return", objTypeAny, opFlagReturn, makeArg1(opArgTermObj)}, + /*0x51*/ {opBreak, "Break", objTypeAny, opFlagExecutable, makeArg0()}, + /*0x52*/ {opBreakPoint, "BreakPoint", objTypeAny, opFlagNoOp, makeArg0()}, + /*0x53*/ {opOnes, "Ones", objTypeInteger, opFlagConstant, makeArg0()}, + /*0x54*/ {opMutex, "Mutex", objTypeMutex, opFlagNamed, makeArg2(opArgNameString, opArgByteData)}, + /*0x55*/ {opEvent, "Event", objTypeEvent, opFlagNamed, makeArg1(opArgNameString)}, + /*0x56*/ {opCondRefOf, "CondRefOf", objTypeAny, opFlagExecutable, makeArg2(opArgSuperName, opArgSuperName)}, + /*0x57*/ {opCreateField, "CreateField", objTypeBufferField, opFlagExecutable, makeArg4(opArgTermObj, opArgTermObj, opArgTermObj, opArgNameString)}, + /*0x58*/ {opLoadTable, "LoadTable", objTypeAny, opFlagExecutable, makeArg7(opArgTermObj, opArgTermObj, opArgTermObj, opArgTermObj, opArgTermObj, opArgTermObj, opArgTermObj)}, + /*0x59*/ {opLoad, "Load", objTypeAny, opFlagExecutable, makeArg2(opArgNameString, opArgSuperName)}, + /*0x5a*/ {opStall, "Stall", objTypeAny, opFlagExecutable, makeArg1(opArgTermObj)}, + /*0x5b*/ {opSleep, "Sleep", objTypeAny, opFlagExecutable, makeArg1(opArgTermObj)}, + /*0x5c*/ {opAcquire, "Acquire", objTypeAny, opFlagExecutable, makeArg2(opArgNameString, opArgSuperName)}, + /*0x5d*/ {opSignal, "Signal", objTypeAny, opFlagExecutable, makeArg1(opArgTermObj)}, + /*0x5e*/ {opWait, "Wait", objTypeAny, opFlagExecutable, makeArg2(opArgSuperName, opArgTermObj)}, + /*0x5f*/ {opReset, "Reset", objTypeAny, opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x60*/ {opRelease, "Release", objTypeAny, opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x61*/ {opFromBCD, "FromBCD", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x62*/ {opToBCD, "ToBCD", objTypeAny, opFlagExecutable, makeArg2(opArgTermObj, opArgTarget)}, + /*0x63*/ {opUnload, "Unload", objTypeAny, opFlagExecutable, makeArg1(opArgSuperName)}, + /*0x64*/ {opRevision, "Revision", objTypeInteger, opFlagConstant | opFlagExecutable, makeArg0()}, + /*0x65*/ {opDebug, "Debug", objTypeLocalReference, opFlagExecutable, makeArg0()}, + /*0x66*/ {opFatal, "Fatal", objTypeAny, opFlagExecutable, makeArg3(opArgByteData, opArgDword, opArgTermObj)}, + /*0x67*/ {opTimer, "Timer", objTypeAny, opFlagNone, makeArg0()}, + /*0x68*/ {opOpRegion, "OpRegion", objTypeRegion, opFlagNamed, makeArg4(opArgNameString, opArgByteData, opArgTermObj, opArgTermObj)}, + /*0x69*/ {opField, "Field", objTypeAny, opFlagNone, makeArg3(opArgNameString, opArgByteData, opArgFieldList)}, + /*0x6a*/ {opDevice, "Device", objTypeDevice, opFlagNamed | opFlagScoped, makeArg2(opArgNameString, opArgTermList)}, + /*0x6b*/ {opProcessor, "Processor", objTypeProcessor, opFlagNamed | opFlagScoped, makeArg5(opArgNameString, opArgByteData, opArgDword, opArgByteData, opArgTermList)}, + /*0x6c*/ {opPowerRes, "PowerRes", objTypePower, opFlagNamed | opFlagScoped, makeArg4(opArgNameString, opArgByteData, opArgWord, opArgTermList)}, + /*0x6d*/ {opThermalZone, "ThermalZone", objTypeThermal, opFlagNamed | opFlagScoped, makeArg2(opArgNameString, opArgTermList)}, + /*0x6e*/ {opIndexField, "IndexField", objTypeAny, opFlagNone, makeArg4(opArgNameString, opArgNameString, opArgByteData, opArgFieldList)}, + /*0x6f*/ {opBankField, "BankField", objTypeLocalBankField, opFlagNamed, makeArg5(opArgNameString, opArgNameString, opArgTermObj, opArgByteData, opArgFieldList)}, + /*0x70*/ {opDataRegion, "DataRegion", objTypeLocalRegionField, opFlagNamed, makeArg4(opArgNameString, opArgTermObj, opArgTermObj, opArgTermObj)}, +} + +// opcodeMap maps an AML opcode to an entry in the opcode table. Entries with +// the value 0xff indicate an invalid/unsupported opcode. +var opcodeMap = [256]uint8{ + /* 0 1 2 3 4 5 6 7*/ + /*0x00 - 0x07*/ 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x02, 0xff, + /*0x08 - 0x0f*/ 0x03, 0xff, 0x04, 0x05, 0x06, 0x07, 0x08, 0xff, + /*0x10 - 0x17*/ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xff, 0xff, + /*0x18 - 0x1f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x20 - 0x27*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x28 - 0x2f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x30 - 0x37*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x38 - 0x3f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x40 - 0x47*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x48 - 0x4f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x50 - 0x57*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x58 - 0x5f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x60 - 0x67*/ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + /*0x68 - 0x6f*/ 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0xff, + /*0x70 - 0x77*/ 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + /*0x78 - 0x7f*/ 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + /*0x80 - 0x87*/ 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + /*0x88 - 0x8f*/ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + /*0x90 - 0x97*/ 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + /*0x98 - 0x9f*/ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x49, 0x4a, 0x4b, + /*0xa0 - 0xa7*/ 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0xff, 0xff, + /*0xa8 - 0xaf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xb0 - 0xb7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xb8 - 0xbf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xc0 - 0xc7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xc8 - 0xcf*/ 0xff, 0xff, 0xff, 0xff, 0x52, 0xff, 0xff, 0xff, + /*0xd0 - 0xd7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xd8 - 0xdf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xe0 - 0xe7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xe8 - 0xef*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xf0 - 0xf7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xf8 - 0xff*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x53, +} + +// extendedOpcodeMap maps an AML extended opcode (extOpPrefix + code) to an +// entry in the opcode table. Entries with the value 0xff indicate an +// invalid/unsupported opcode. +var extendedOpcodeMap = [256]uint8{ + /* 0 1 2 3 4 5 6 7*/ + /*0x00 - 0x07*/ 0xff, 0x54, 0x55, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x08 - 0x0f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x10 - 0x17*/ 0xff, 0xff, 0x56, 0x57, 0xff, 0xff, 0xff, 0xff, + /*0x18 - 0x1f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x58, + /*0x20 - 0x27*/ 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, + /*0x28 - 0x2f*/ 0x61, 0x62, 0x63, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x30 - 0x37*/ 0x64, 0x65, 0x66, 0x67, 0xff, 0xff, 0xff, 0xff, + /*0x38 - 0x3f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x40 - 0x47*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x48 - 0x4f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x50 - 0x57*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x58 - 0x5f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x60 - 0x67*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x68 - 0x6f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x70 - 0x77*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x78 - 0x7f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x80 - 0x87*/ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + /*0x88 - 0x8f*/ 0x70, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x90 - 0x97*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0x98 - 0x9f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xa0 - 0xa7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xa8 - 0xaf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xb0 - 0xb7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xb8 - 0xbf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xc0 - 0xc7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xc8 - 0xcf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xd0 - 0xd7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xd8 - 0xdf*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xe0 - 0xe7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xe8 - 0xef*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xf0 - 0xf7*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*0xf8 - 0xff*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x53, +} diff --git a/src/gopheros/device/acpi/aml/opcode_test.go b/src/gopheros/device/acpi/aml/opcode_test.go new file mode 100644 index 0000000..7258759 --- /dev/null +++ b/src/gopheros/device/acpi/aml/opcode_test.go @@ -0,0 +1,187 @@ +package aml + +import "testing" + +func TestOpcodeToString(t *testing.T) { + if exp, got := "Acquire", opAcquire.String(); got != exp { + t.Fatalf("expected opAcquire.toString() to return %q; got %q", exp, got) + } + + if exp, got := "unknown", opcode(0xffff).String(); got != exp { + t.Fatalf("expected opcode.String() to return %q; got %q", exp, got) + } +} + +func TestOpcodeIsX(t *testing.T) { + specs := []struct { + op opcode + testFn func(opcode) bool + want bool + }{ + // opIsLocalArg + {opLocal0, opIsLocalArg, true}, + {opLocal1, opIsLocalArg, true}, + {opLocal2, opIsLocalArg, true}, + {opLocal3, opIsLocalArg, true}, + {opLocal4, opIsLocalArg, true}, + {opLocal5, opIsLocalArg, true}, + {opLocal6, opIsLocalArg, true}, + {opLocal7, opIsLocalArg, true}, + {opArg0, opIsLocalArg, false}, + {opDivide, opIsLocalArg, false}, + // opIsMethodArg + {opArg0, opIsMethodArg, true}, + {opArg1, opIsMethodArg, true}, + {opArg2, opIsMethodArg, true}, + {opArg3, opIsMethodArg, true}, + {opArg4, opIsMethodArg, true}, + {opArg5, opIsMethodArg, true}, + {opArg6, opIsMethodArg, true}, + {opLocal7, opIsMethodArg, false}, + {opIf, opIsMethodArg, false}, + // opIsArg + {opLocal5, opIsArg, true}, + {opArg1, opIsArg, true}, + {opDivide, opIsArg, false}, + // opIsType2 + {opAcquire, opIsType2, true}, + {opAdd, opIsType2, true}, + {opAnd, opIsType2, true}, + {opBuffer, opIsType2, true}, + {opConcat, opIsType2, true}, + {opConcatRes, opIsType2, true}, + {opCondRefOf, opIsType2, true}, + {opCopyObject, opIsType2, true}, + {opDecrement, opIsType2, true}, + {opDerefOf, opIsType2, true}, + {opDivide, opIsType2, true}, + {opFindSetLeftBit, opIsType2, true}, + {opFindSetRightBit, opIsType2, true}, + {opFromBCD, opIsType2, true}, + {opIncrement, opIsType2, true}, + {opIndex, opIsType2, true}, + {opLand, opIsType2, true}, + {opLEqual, opIsType2, true}, + {opLGreater, opIsType2, true}, + {opLLess, opIsType2, true}, + {opMid, opIsType2, true}, + {opLnot, opIsType2, true}, + {opLoadTable, opIsType2, true}, + {opLor, opIsType2, true}, + {opMatch, opIsType2, true}, + {opMod, opIsType2, true}, + {opMultiply, opIsType2, true}, + {opNand, opIsType2, true}, + {opNor, opIsType2, true}, + {opNot, opIsType2, true}, + {opObjectType, opIsType2, true}, + {opOr, opIsType2, true}, + {opPackage, opIsType2, true}, + {opVarPackage, opIsType2, true}, + {opRefOf, opIsType2, true}, + {opShiftLeft, opIsType2, true}, + {opShiftRight, opIsType2, true}, + {opSizeOf, opIsType2, true}, + {opStore, opIsType2, true}, + {opSubtract, opIsType2, true}, + {opTimer, opIsType2, true}, + {opToBCD, opIsType2, true}, + {opToBuffer, opIsType2, true}, + {opToDecimalString, opIsType2, true}, + {opToHexString, opIsType2, true}, + {opToInteger, opIsType2, true}, + {opToString, opIsType2, true}, + {opWait, opIsType2, true}, + {opXor, opIsType2, true}, + {opBytePrefix, opIsType2, false}, + // opIsDataObject + {opBytePrefix, opIsDataObject, true}, + {opWordPrefix, opIsDataObject, true}, + {opDwordPrefix, opIsDataObject, true}, + {opQwordPrefix, opIsDataObject, true}, + {opStringPrefix, opIsDataObject, true}, + {opZero, opIsDataObject, true}, + {opOne, opIsDataObject, true}, + {opOnes, opIsDataObject, true}, + {opRevision, opIsDataObject, true}, + {opBuffer, opIsDataObject, true}, + {opPackage, opIsDataObject, true}, + {opVarPackage, opIsDataObject, true}, + {opLor, opIsDataObject, false}, + // opIsBufferField + {opCreateField, opIsBufferField, true}, + {opCreateBitField, opIsBufferField, true}, + {opCreateByteField, opIsBufferField, true}, + {opCreateWordField, opIsBufferField, true}, + {opCreateDWordField, opIsBufferField, true}, + {opCreateQWordField, opIsBufferField, true}, + {opRevision, opIsBufferField, false}, + } + + for specIndex, spec := range specs { + if got := spec.testFn(spec.op); got != spec.want { + t.Errorf("[spec %d] opcode %q: expected to get %t; got %t", specIndex, spec.op, spec.want, got) + } + } +} + +func TestOpArgFlagToString(t *testing.T) { + specs := map[opArgFlag]string{ + opArgTermList: "opArgTermList", + opArgTermObj: "opArgTermObj", + opArgByteList: "opArgByteList", + opArgPackage: "opArgPackage", + opArgString: "opArgString", + opArgByteData: "opArgByteData", + opArgWord: "opArgWord", + opArgDword: "opArgDword", + opArgQword: "opArgQword", + opArgNameString: "opArgNameString", + opArgSuperName: "opArgSuperName", + opArgSimpleName: "opArgSimpleName", + opArgDataRefObj: "opArgDataRefObj", + opArgTarget: "opArgTarget", + opArgFieldList: "opArgFieldList", + opArgFlag(0xff): "", + } + + for flag, want := range specs { + if got := flag.String(); got != want { + t.Errorf("expected %q; got %q", want, got) + } + } +} + +// TestFindUnmappedOpcodes is a helper test that pinpoints opcodes that have +// not yet been mapped via an opcode table. This test will be removed once all +// opcodes are supported. +func TestFindUnmappedOpcodes(t *testing.T) { + //t.SkipNow() + for opIndex, opRef := range opcodeMap { + if opRef != badOpcode { + continue + } + + for tabIndex, info := range opcodeTable { + if uint16(info.op) == uint16(opIndex) { + t.Errorf("set opcodeMap[0x%02x] = 0x%02x // %s\n", opIndex, tabIndex, info.op.String()) + break + } + } + } + + for opIndex, opRef := range extendedOpcodeMap { + // 0xff (opOnes) is defined in opcodeTable + if opRef != badOpcode || opIndex == 0 { + continue + } + + opIndex += 0xff + for tabIndex, info := range opcodeTable { + if uint16(info.op) == uint16(opIndex) { + t.Errorf("set extendedOpcodeMap[0x%02x] = 0x%02x // %s\n", opIndex-0xff, tabIndex, info.op.String()) + break + } + } + } +} diff --git a/src/gopheros/device/acpi/aml/parser.go b/src/gopheros/device/acpi/aml/parser.go new file mode 100644 index 0000000..2bcc3d8 --- /dev/null +++ b/src/gopheros/device/acpi/aml/parser.go @@ -0,0 +1,937 @@ +package aml + +import ( + "gopheros/device/acpi/table" + "gopheros/kernel" + "gopheros/kernel/kfmt" + "io" + "unsafe" +) + +var ( + errParsingAML = &kernel.Error{Module: "acpi_aml_parser", Message: "could not parse AML bytecode"} + errResolvingEntities = &kernel.Error{Module: "acpi_aml_parser", Message: "AML bytecode contains unresolvable entities"} +) + +// Parser implements an AML parser. +type Parser struct { + r amlStreamReader + errWriter io.Writer + root ScopeEntity + scopeStack []ScopeEntity + tableName string + tableHandle uint8 +} + +// NewParser returns a new AML parser instance. +func NewParser(errWriter io.Writer, rootEntity ScopeEntity) *Parser { + return &Parser{ + errWriter: errWriter, + root: rootEntity, + } +} + +// ParseAML attempts to parse the AML byte-code contained in the supplied ACPI +// table tagging each scoped entity with the supplied table handle. The parser +// emits any encountered errors to the specified errWriter. +func (p *Parser) ParseAML(tableHandle uint8, tableName string, header *table.SDTHeader) *kernel.Error { + p.tableHandle = tableHandle + p.tableName = tableName + p.r.Init( + uintptr(unsafe.Pointer(header)), + header.Length, + uint32(unsafe.Sizeof(table.SDTHeader{})), + ) + + // Pass 1: decode bytecode and build entitites + p.scopeStack = nil + p.scopeEnter(p.root) + if !p.parseObjList(header.Length) { + lastOp, _ := p.r.LastByte() + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] error parsing AML bytecode (last op 0x%x)\n", p.tableName, p.r.Offset()-1, lastOp) + return errParsingAML + } + p.scopeExit() + + // Pass 2: resolve forward references + var resolveFailed bool + scopeVisit(0, p.root, EntityTypeAny, func(_ int, ent Entity) bool { + if res, ok := ent.(resolver); ok && !res.Resolve(p.errWriter, p.root) { + resolveFailed = true + } + return true + }) + + if resolveFailed { + return errResolvingEntities + } + + return nil +} + +// parseObjList tries to parse an AML object list. Object lists are usually +// specified together with a pkgLen block which is used to calculate the max +// read offset that the parser may reach. +func (p *Parser) parseObjList(maxOffset uint32) bool { + for !p.r.EOF() && p.r.Offset() < maxOffset { + if !p.parseObj() { + return false + } + } + + return true +} + +func (p *Parser) parseObj() bool { + var ( + curOffset uint32 + pkgLen uint32 + info *opcodeInfo + ok bool + ) + + // If we cannot decode the next opcode then this may be a method + // invocation or a name reference. If neither is the case, we need to + // rewind the stream and parse a method invocation before giving up. + curOffset = p.r.Offset() + if info, ok = p.nextOpcode(); !ok { + p.r.SetOffset(curOffset) + return p.parseMethodInvocationOrNameRef() + } + + hasPkgLen := info.flags.is(opFlagHasPkgLen) || info.argFlags.contains(opArgTermList) || info.argFlags.contains(opArgFieldList) + + if hasPkgLen { + curOffset = p.r.Offset() + if pkgLen, ok = p.parsePkgLength(); !ok { + return false + } + } + + // If we encounter a named scope we need to look it up and parse the arg list relative to it + switch info.op { + case opScope: + return p.parseScope(curOffset + pkgLen) + case opDevice, opMethod: + return p.parseNamespacedObj(info.op, curOffset+pkgLen) + } + + // Create appropriate object for opcode type and attach it to current scope unless it is + // a device named scope in which case it may define a relative scope name + obj := p.makeObjForOpcode(info) + p.scopeCurrent().Append(obj) + + if argCount := info.argFlags.argCount(); argCount > 0 { + for argIndex := uint8(0); argIndex < argCount; argIndex++ { + if !p.parseArg( + info, + obj, + argIndex, + info.argFlags.arg(argIndex), + curOffset+pkgLen, + ) { + return false + } + } + } + + return p.finalizeObj(info.op, obj) +} + +// finalizeObj applies post-parse logic for special object types. +func (p *Parser) finalizeObj(op opcode, obj Entity) bool { + obj.setTableHandle(p.tableHandle) + + switch op { + case opElse: + // If this is an else block we need to append it as an argument to the + // If block + // Pop Else block of the current scope + curScope := p.scopeCurrent() + curScope.removeChild(curScope.lastChild()) + prevObj := curScope.lastChild() + if prevObj.getOpcode() != opIf { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] encountered else block without a matching if block\n", p.tableName, p.r.Offset()) + return false + } + + // If predicate(0) then(1) else(2) + prevObj.setArg(2, obj) + case opDevice: + // Build method map + dev := obj.(*Device) + dev.methodMap = make(map[string]*Method) + scopeVisit(0, dev, EntityTypeMethod, func(_ int, ent Entity) bool { + method := ent.(*Method) + dev.methodMap[method.name] = method + return false + }) + } + + return true +} + +// parseScope reads a scope name from the AML bytestream, enters it and parses +// an objlist relative to it. The referenced scope must be one of: +// - one of the pre-defined scopes +// - device +// - processor +// - thermal zone +// - power resource +func (p *Parser) parseScope(maxReadOffset uint32) bool { + name, ok := p.parseNameString() + if !ok { + return false + } + + target := scopeFind(p.scopeCurrent(), p.root, name) + if target == nil { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] undefined scope: %s\n", p.tableName, p.r.Offset(), name) + return false + } + + switch target.getOpcode() { + case opDevice, opProcessor, opThermalZone, opPowerRes: + // ok + default: + // Only allow if this is a named scope + if target.Name() == "" { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] %s does not refer to a scoped object\n", p.tableName, p.r.Offset(), name) + return false + } + } + + p.scopeEnter(target.(ScopeEntity)) + ok = p.parseObjList(maxReadOffset) + p.scopeExit() + + return ok +} + +// parseNamespacedObj reads a scope target name from the AML bytestream, +// attaches the device or method (depending on the opcode) object to the +// correct parent scope, enters the device scope and parses the object list +// contained in the device definition. +func (p *Parser) parseNamespacedObj(op opcode, maxReadOffset uint32) bool { + scopeExpr, ok := p.parseNameString() + if !ok { + return false + } + + parent, name := scopeResolvePath(p.scopeCurrent(), p.root, scopeExpr) + if parent == nil { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] undefined scope target: %s (current scope: %s)\n", p.tableName, p.r.Offset(), scopeExpr, p.scopeCurrent().Name()) + return false + } + + var obj ScopeEntity + switch op { + case opDevice: + obj = &Device{scopeEntity: scopeEntity{name: name}} + case opMethod: + m := &Method{scopeEntity: scopeEntity{name: name}} + + flags, flagOk := p.parseNumConstant(1) + if !flagOk { + return false + } + m.argCount = (uint8(flags) & 0x7) // bits[0:2] + m.serialized = (uint8(flags)>>3)&0x1 == 0x1 // bit 3 + m.syncLevel = (uint8(flags) >> 4) & 0xf // bits[4:7] + + obj = m + } + + parent.Append(obj) + p.scopeEnter(obj) + ok = p.parseObjList(maxReadOffset) + p.scopeExit() + + return ok && p.finalizeObj(op, obj) +} + +func (p *Parser) parseArg(info *opcodeInfo, obj Entity, argIndex uint8, argType opArgFlag, maxReadOffset uint32) bool { + var ( + arg interface{} + ok bool + ) + + switch argType { + case opArgNameString: + arg, ok = p.parseNameString() + case opArgByteData: + arg, ok = p.parseNumConstant(1) + case opArgWord: + arg, ok = p.parseNumConstant(2) + case opArgDword: + arg, ok = p.parseNumConstant(4) + case opArgQword: + arg, ok = p.parseNumConstant(8) + case opArgString: + arg, ok = p.parseString() + case opArgTermObj, opArgDataRefObj: + arg, ok = p.parseArgObj() + case opArgSimpleName: + arg, ok = p.parseSimpleName() + case opArgSuperName: + arg, ok = p.parseSuperName() + case opArgTarget: + arg, ok = p.parseTarget() + case opArgTermList: + // If object is a scoped entity enter it's scope before parsing + // the term list. Otherwise, create an unnamed scope, attach it + // as the next argument to obj and enter that. + if s, isScopeEnt := obj.(ScopeEntity); isScopeEnt { + p.scopeEnter(s) + } else { + ns := &scopeEntity{op: opScope} + p.scopeEnter(ns) + obj.setArg(argIndex, ns) + } + + ok = p.parseObjList(maxReadOffset) + p.scopeExit() + return ok + case opArgFieldList: + return p.parseFieldList(info.op, obj.getArgs(), maxReadOffset) + case opArgByteList: + var bl []byte + for p.r.Offset() < maxReadOffset { + b, err := p.r.ReadByte() + if err != nil { + return false + } + bl = append(bl, b) + } + arg, ok = bl, true + } + + if !ok { + return false + } + + return obj.setArg(argIndex, arg) +} + +func (p *Parser) parseArgObj() (Entity, bool) { + if ok := p.parseObj(); !ok { + return nil, false + } + + curScope := p.scopeCurrent() + obj := curScope.lastChild() + curScope.removeChild(obj) + return obj, true +} + +func (p *Parser) makeObjForOpcode(info *opcodeInfo) Entity { + var obj Entity + + switch { + case info.op == opOpRegion: + obj = new(regionEntity) + case info.op == opBuffer: + obj = new(bufferEntity) + case info.op == opMutex: + obj = new(mutexEntity) + case info.op == opEvent: + obj = new(eventEntity) + case opIsBufferField(info.op): + obj = new(bufferFieldEntity) + case info.flags.is(opFlagConstant): + obj = new(constEntity) + case info.flags.is(opFlagScoped): + obj = new(scopeEntity) + case info.flags.is(opFlagNamed): + obj = new(namedEntity) + default: + obj = new(unnamedEntity) + } + + obj.setOpcode(info.op) + return obj +} + +// parseMethodInvocationOrNameRef attempts to parse a method invocation and its term +// args. This method first scans the NameString and performs a lookup. If the +// lookup returns a method definition then we consult it to figure out how many +// arguments we need to parse. +// +// Grammar: +// MethodInvocation := NameString TermArgList +// TermArgList = Nothing | TermArg TermArgList +// TermArg = Type2Opcode | DataObject | ArgObj | LocalObj | MethodInvocation +func (p *Parser) parseMethodInvocationOrNameRef() bool { + invocationStartOffset := p.r.Offset() + name, ok := p.parseNameString() + if !ok { + return false + } + + // Lookup Name and try matching it to a function definition + if methodDef, ok := scopeFind(p.scopeCurrent(), p.root, name).(*Method); ok { + var ( + invocation = &methodInvocationEntity{ + methodDef: methodDef, + } + curOffset uint32 + argIndex uint8 + arg Entity + ) + + for argIndex < methodDef.argCount && !p.r.EOF() { + // Peek next opcode + curOffset = p.r.Offset() + nextOpcode, ok := p.nextOpcode() + p.r.SetOffset(curOffset) + + switch { + case ok && (opIsType2(nextOpcode.op) || opIsArg(nextOpcode.op) || opIsDataObject(nextOpcode.op)): + arg, ok = p.parseArgObj() + default: + // It may be a nested invocation or named ref + ok = p.parseMethodInvocationOrNameRef() + if ok { + arg = p.scopeCurrent().lastChild() + p.scopeCurrent().removeChild(arg) + } + } + + // No more TermArgs to parse + if !ok { + p.r.SetOffset(curOffset) + break + } + + invocation.setArg(argIndex, arg) + argIndex++ + } + + if argIndex != methodDef.argCount { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] argument mismatch (exp: %d, got %d) for invocation of method: %s\n", p.tableName, invocationStartOffset, methodDef.argCount, argIndex, name) + return false + } + + p.scopeCurrent().Append(invocation) + return true + } + + // This is a name reference; assume it's a forward reference for now + // and delegate its resolution to a post-parse step. + p.scopeCurrent().Append(&namedReference{targetName: name}) + return true +} + +func (p *Parser) nextOpcode() (*opcodeInfo, bool) { + next, err := p.r.ReadByte() + if err != nil { + return nil, false + } + + if next != extOpPrefix { + index := opcodeMap[next] + if index == badOpcode { + return nil, false + } + return &opcodeTable[index], true + } + + // Scan next byte to figure out the opcode + if next, err = p.r.ReadByte(); err != nil { + return nil, false + } + + index := extendedOpcodeMap[next] + if index == badOpcode { + return nil, false + } + return &opcodeTable[index], true +} + +// parseFieldList parses a list of FieldElements until the reader reaches +// maxReadOffset and appends them to the current scope. Depending on the opcode +// this method will emit either fieldUnit objects or indexField objects +// +// Grammar: +// FieldElement := NamedField | ReservedField | AccessField | ExtendedAccessField | ConnectField +// NamedField := NameSeg PkgLength +// ReservedField := 0x00 PkgLength +// AccessField := 0x1 AccessType AccessAttrib +// ConnectField := 0x02 NameString | 0x02 BufferData +// ExtendedAccessField := 0x3 AccessType ExtendedAccessType AccessLength +func (p *Parser) parseFieldList(op opcode, args []interface{}, maxReadOffset uint32) bool { + var ( + // for fieldUnit, name0 is the region name and name1 is not used; + // for indexField, + name0, name1 string + flags uint64 + + ok bool + bitWidth uint32 + curBitOffset uint32 + accessAttrib FieldAccessAttrib + accessByteCount uint8 + unitName string + ) + + switch op { + case opField: // Field := PkgLength Region AccessFlags FieldList + if len(args) != 2 { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d, opcode 0x%2x] invalid arg count: %d\n", p.tableName, p.r.Offset(), uint32(op), len(args)) + return false + } + + name0, ok = args[0].(string) + if !ok { + return false + } + + flags, ok = args[1].(uint64) + if !ok { + return false + } + case opIndexField: // Field := PkgLength IndexFieldName DataFieldName AccessFlags FieldList + if len(args) != 3 { + kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d, opcode 0x%2x] invalid arg count: %d\n", p.tableName, p.r.Offset(), uint32(op), len(args)) + return false + } + + name0, ok = args[0].(string) + if !ok { + return false + } + + name1, ok = args[1].(string) + if !ok { + return false + } + + flags, ok = args[2].(uint64) + if !ok { + return false + } + } + + // Decode flags + accessType := FieldAccessType(flags & 0xf) // access type; bits[0:3] + lock := (flags>>4)&0x1 == 0x1 // lock; bit 4 + updateRule := FieldUpdateRule((flags >> 5) & 0x3) // update rule; bits[5:6] + + var ( + connectionName string + resolvedConnection Entity + ) + + for p.r.Offset() < maxReadOffset { + next, err := p.r.ReadByte() + if err != nil { + return false + } + + switch next { + case 0x00: // ReservedField; generated by the Offset() command + bitWidth, ok = p.parsePkgLength() + if !ok { + return false + } + + curBitOffset += bitWidth + continue + case 0x1: // AccessField; set access attributes for following fields + next, err := p.r.ReadByte() + if err != nil { + return false + } + accessType = FieldAccessType(next & 0xf) // access type; bits[0:3] + + attrib, err := p.r.ReadByte() + if err != nil { + return false + } + + // To specify AccessAttribBytes, RawBytes and RawProcessBytes + // the ASL compiler will emit an ExtendedAccessField opcode. + accessByteCount = 0 + accessAttrib = FieldAccessAttrib(attrib) + + continue + case 0x2: // ConnectField => <0x2> NameString> | <0x02> TermObj => Buffer + curOffset := p.r.Offset() + if connectionName, ok = p.parseNameString(); !ok { + // Rewind and try parsing it as an object + p.r.SetOffset(curOffset) + if resolvedConnection, ok = p.parseArgObj(); !ok { + return false + } + } + case 0x3: // ExtendedAccessField => <0x03> AccessType ExtendedAccessAttrib AccessLength + next, err := p.r.ReadByte() + if err != nil { + return false + } + accessType = FieldAccessType(next & 0xf) // access type; bits[0:3] + + extAccessAttrib, err := p.r.ReadByte() + if err != nil { + return false + } + + accessByteCount, err = p.r.ReadByte() + if err != nil { + return false + } + + switch extAccessAttrib { + case 0x0b: + accessAttrib = FieldAccessAttribBytes + case 0xe: + accessAttrib = FieldAccessAttribRawBytes + case 0x0f: + accessAttrib = FieldAccessAttribRawProcessBytes + } + default: // NamedField + p.r.UnreadByte() + if unitName, ok = p.parseNameString(); !ok { + return false + } + + bitWidth, ok = p.parsePkgLength() + if !ok { + return false + } + + // According to the spec, the field elements are should + // be visible at the same scope as the Field/IndexField + switch op { + case opField: + p.scopeCurrent().Append(&fieldUnitEntity{ + fieldEntity: fieldEntity{ + namedEntity: namedEntity{ + tableHandle: p.tableHandle, + op: op, + name: unitName, + }, + bitOffset: curBitOffset, + bitWidth: bitWidth, + lock: lock, + updateRule: updateRule, + accessType: accessType, + accessAttrib: accessAttrib, + byteCount: accessByteCount, + }, + connectionName: connectionName, + resolvedConnection: resolvedConnection, + regionName: name0, + }) + case opIndexField: + p.scopeCurrent().Append(&indexFieldEntity{ + fieldEntity: fieldEntity{ + namedEntity: namedEntity{ + tableHandle: p.tableHandle, + op: op, + name: unitName, + }, + bitOffset: curBitOffset, + bitWidth: bitWidth, + lock: lock, + updateRule: updateRule, + accessType: accessType, + accessAttrib: accessAttrib, + byteCount: accessByteCount, + }, + connectionName: connectionName, + resolvedConnection: resolvedConnection, + indexRegName: name0, + dataRegName: name1, + }) + } + + curBitOffset += bitWidth + + } + + } + + return ok && p.r.Offset() == maxReadOffset +} + +// parsePkgLength parses a PkgLength value from the AML bytestream. +func (p *Parser) parsePkgLength() (uint32, bool) { + lead, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + // The high 2 bits of the lead byte indicate how many bytes follow. + var pkgLen uint32 + switch lead >> 6 { + case 0: + pkgLen = uint32(lead) + case 1: + b1, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + // lead bits 0-3 are the lsb of the length nybble + pkgLen = uint32(b1)<<4 | uint32(lead&0xf) + case 2: + b1, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + b2, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + // lead bits 0-3 are the lsb of the length nybble + pkgLen = uint32(b2)<<12 | uint32(b1)<<4 | uint32(lead&0xf) + case 3: + b1, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + b2, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + b3, err := p.r.ReadByte() + if err != nil { + return 0, false + } + + // lead bits 0-3 are the lsb of the length nybble + pkgLen = uint32(b3)<<20 | uint32(b2)<<12 | uint32(b1)<<4 | uint32(lead&0xf) + } + + return pkgLen, true +} + +// parseNumConstant parses a byte/word/dword or qword value from the AML bytestream. +func (p *Parser) parseNumConstant(numBytes uint8) (uint64, bool) { + var ( + next byte + err error + res uint64 + ) + + for c := uint8(0); c < numBytes; c++ { + if next, err = p.r.ReadByte(); err != nil { + return 0, false + } + + res = res | (uint64(next) << (8 * c)) + } + + return res, true +} + +// parseString parses a string from the AML bytestream. +func (p *Parser) parseString() (string, bool) { + // Read ASCII chars till we reach a null byte + var ( + next byte + err error + str []byte + ) + + for { + next, err = p.r.ReadByte() + if err != nil { + return "", false + } + + if next == 0x00 { + break + } else if next >= 0x01 && next <= 0x7f { // AsciiChar + str = append(str, next) + } else { + return "", false + } + } + return string(str), true +} + +// parseSuperName attempts to pass a SuperName from the AML bytestream. +// +// Grammar: +// SuperName := SimpleName | DebugObj | Type6Opcode +// SimpleName := NameString | ArgObj | LocalObj +func (p *Parser) parseSuperName() (interface{}, bool) { + // Try parsing as SimpleName + curOffset := p.r.Offset() + if obj, ok := p.parseSimpleName(); ok { + return obj, ok + } + + // Rewind and try parsing as object + p.r.SetOffset(curOffset) + return p.parseArgObj() +} + +// parseSimpleName attempts to pass a SimpleName from the AML bytestream. +// +// Grammar: +// SimpleName := NameString | ArgObj | LocalObj +func (p *Parser) parseSimpleName() (interface{}, bool) { + // Peek next opcode + curOffset := p.r.Offset() + nextOpcode, ok := p.nextOpcode() + + var obj interface{} + + switch { + case ok && nextOpcode.op >= opLocal0 && nextOpcode.op <= opLocal7: + obj, ok = &unnamedEntity{op: nextOpcode.op}, true + case ok && nextOpcode.op >= opArg0 && nextOpcode.op <= opArg6: + obj, ok = &unnamedEntity{op: nextOpcode.op}, true + default: + // Rewind and try parsing as NameString + p.r.SetOffset(curOffset) + obj, ok = p.parseNameString() + } + + return obj, ok +} + +// parseTarget attempts to pass a Target from the AML bytestream. +// +// Grammar: +// Target := SuperName | NullName +// NullName := 0x00 +// SuperName := SimpleName | DebugObj | Type6Opcode +// Type6Opcode := DefRefOf | DefDerefOf | DefIndex | UserTermObj +// SimpleName := NameString | ArgObj | LocalObj +// +// UserTermObj is a control method invocation. +func (p *Parser) parseTarget() (interface{}, bool) { + // Peek next opcode + curOffset := p.r.Offset() + nextOpcode, ok := p.nextOpcode() + p.r.SetOffset(curOffset) + + if ok { + switch { + case nextOpcode.op == opZero: // this is actually a NullName + p.r.SetOffset(curOffset + 1) + return &constEntity{op: opStringPrefix, val: ""}, true + case opIsArg(nextOpcode.op) || nextOpcode.op == opRefOf || nextOpcode.op == opDerefOf || nextOpcode.op == opIndex || nextOpcode.op == opDebug: // LocalObj | ArgObj | Type6 | DebugObj + default: + // Unexpected opcode + return nil, false + } + + // We can use parseObj for parsing + return p.parseArgObj() + } + + // In this case, this is either a NameString or a control method invocation. + if ok := p.parseMethodInvocationOrNameRef(); ok { + obj := p.scopeCurrent().lastChild() + p.scopeCurrent().removeChild(obj) + return obj, ok + } + + return nil, false +} + +// parseNameString parses a NameString from the AML bytestream. +// +// Grammar: +// NameString := RootChar NamePath | PrefixPath NamePath +// PrefixPath := Nothing | '^' PrefixPath +// NamePath := NameSeg | DualNamePath | MultiNamePath | NullName +func (p *Parser) parseNameString() (string, bool) { + var str []byte + + // NameString := RootChar NamePath | PrefixPath NamePath + next, err := p.r.PeekByte() + if err != nil { + return "", false + } + + switch next { + case '\\': // RootChar + str = append(str, next) + p.r.ReadByte() + case '^': // PrefixPath := Nothing | '^' PrefixPath + str = append(str, next) + p.r.ReadByte() + for { + next, err = p.r.PeekByte() + if err != nil { + return "", false + } + + if next != '^' { + break + } + + str = append(str, next) + p.r.ReadByte() + } + } + + // NamePath := NameSeg | DualNamePath | MultiNamePath | NullName + next, err = p.r.ReadByte() + if err != nil { + return "", false + } + var readCount int + switch next { + case 0x00: // NullName + case 0x2e: // DualNamePath := DualNamePrefix NameSeg NameSeg + readCount = 8 // NameSeg x 2 + case 0x2f: // MultiNamePath := MultiNamePrefix SegCount NameSeg(SegCount) + segCount, err := p.r.ReadByte() + if segCount == 0 || err != nil { + return "", false + } + + readCount = int(segCount) * 4 + default: // NameSeg := LeadNameChar NameChar NameChar NameChar + // LeadNameChar := 'A' - 'Z' | '_' + if (next < 'A' || next > 'Z') && next != '_' { + return "", false + } + + str = append(str, next) // LeadNameChar + readCount = 3 // NameChar x 3 + } + + for index := 0; readCount > 0; readCount, index = readCount-1, index+1 { + next, err := p.r.ReadByte() + if err != nil { + return "", false + } + + // Inject a '.' every 4 chars except for the last segment so + // scoped lookups can work properly. + if index > 0 && index%4 == 0 && readCount > 1 { + str = append(str, '.') + } + + str = append(str, next) + } + + return string(str), true +} + +// scopeCurrent returns the currently active scope. +func (p *Parser) scopeCurrent() ScopeEntity { + return p.scopeStack[len(p.scopeStack)-1] +} + +// scopeEnter enters the given scope. +func (p *Parser) scopeEnter(s ScopeEntity) { + p.scopeStack = append(p.scopeStack, s) +} + +// scopeExit exits the current scope. +func (p *Parser) scopeExit() { + p.scopeStack = p.scopeStack[:len(p.scopeStack)-1] +} diff --git a/src/gopheros/device/acpi/aml/parser_test.go b/src/gopheros/device/acpi/aml/parser_test.go new file mode 100644 index 0000000..a7db7ec --- /dev/null +++ b/src/gopheros/device/acpi/aml/parser_test.go @@ -0,0 +1,611 @@ +package aml + +import ( + "gopheros/device/acpi/table" + "io/ioutil" + "path/filepath" + "runtime" + "strings" + "testing" + "unsafe" +) + +func TestParser(t *testing.T) { + specs := [][]string{ + []string{"DSDT.aml", "SSDT.aml"}, + []string{"parser-testsuite-DSDT.aml"}, + } + + for specIndex, spec := range specs { + var resolver = mockResolver{ + tableFiles: spec, + } + + // Create default scopes + rootNS := &scopeEntity{op: opScope, name: `\`} + rootNS.Append(&scopeEntity{op: opScope, name: `_GPE`}) // General events in GPE register block + rootNS.Append(&scopeEntity{op: opScope, name: `_PR_`}) // ACPI 1.0 processor namespace + rootNS.Append(&scopeEntity{op: opScope, name: `_SB_`}) // System bus with all device objects + rootNS.Append(&scopeEntity{op: opScope, name: `_SI_`}) // System indicators + rootNS.Append(&scopeEntity{op: opScope, name: `_TZ_`}) // ACPI 1.0 thermal zone namespace + + p := NewParser(ioutil.Discard, rootNS) + + for _, tableName := range spec { + tableName = strings.Replace(tableName, ".aml", "", -1) + if err := p.ParseAML(0, tableName, resolver.LookupTable(tableName)); err != nil { + t.Errorf("[spec %d] [%s]: %v", specIndex, tableName, err) + break + } + } + } +} + +func TestTableHandleAssignment(t *testing.T) { + var resolver = mockResolver{tableFiles: []string{"parser-testsuite-DSDT.aml"}} + + // Create default scopes + rootNS := &scopeEntity{op: opScope, name: `\`} + rootNS.Append(&scopeEntity{op: opScope, name: `_GPE`}) // General events in GPE register block + rootNS.Append(&scopeEntity{op: opScope, name: `_PR_`}) // ACPI 1.0 processor namespace + rootNS.Append(&scopeEntity{op: opScope, name: `_SB_`}) // System bus with all device objects + rootNS.Append(&scopeEntity{op: opScope, name: `_SI_`}) // System indicators + rootNS.Append(&scopeEntity{op: opScope, name: `_TZ_`}) // ACPI 1.0 thermal zone namespace + + p := NewParser(ioutil.Discard, rootNS) + + expHandle := uint8(42) + tableName := "parser-testsuite-DSDT" + if err := p.ParseAML(expHandle, tableName, resolver.LookupTable(tableName)); err != nil { + t.Error(err) + } + + // Drop all entities that were assigned the handle value + var unloadList []Entity + scopeVisit(0, p.root, EntityTypeAny, func(_ int, ent Entity) bool { + if ent.TableHandle() == expHandle { + unloadList = append(unloadList, ent) + return false + } + return true + }) + + for _, ent := range unloadList { + if p := ent.Parent(); p != nil { + p.removeChild(ent) + } + } + + // We should end up with the original tree + var visitedNodes int + scopeVisit(0, p.root, EntityTypeAny, func(_ int, ent Entity) bool { + visitedNodes++ + if ent.TableHandle() == expHandle { + t.Errorf("encountered entity that should have been pruned: %#+v", ent) + } + return true + }) + + if exp := len(rootNS.Children()) + 1; visitedNodes != exp { + t.Errorf("expected to visit %d nodes; visited %d", exp, visitedNodes) + } +} + +func TestParsePkgLength(t *testing.T) { + specs := []struct { + payload []byte + exp uint32 + }{ + // lead byte bits (6:7) indicate 1 extra byte for the len. The + // parsed length will use bits 0:3 from the lead byte plus + // the full 8 bits of the following byte. + { + []byte{1<<6 | 7, 255}, + 4087, + }, + // lead byte bits (6:7) indicate 2 extra bytes for the len. The + // parsed length will use bits 0:3 from the lead byte plus + // the full 8 bits of the following bytes. + { + []byte{2<<6 | 8, 255, 128}, + 528376, + }, + // lead byte bits (6:7) indicate 3 extra bytes for the len. The + // parsed length will use bits 0:3 from the lead byte plus + // the full 8 bits of the following bytes. + { + []byte{3<<6 | 6, 255, 128, 42}, + 44568566, + }, + } + + p := &Parser{errWriter: ioutil.Discard} + + for specIndex, spec := range specs { + mockParserPayload(p, spec.payload) + got, ok := p.parsePkgLength() + if !ok { + t.Errorf("[spec %d] parsePkgLength returned false", specIndex) + continue + } + + if got != spec.exp { + t.Errorf("[spec %d] expected parsePkgLength to return %d; got %d", specIndex, spec.exp, got) + } + } +} + +func TestParserErrorHandling(t *testing.T) { + p := &Parser{ + errWriter: ioutil.Discard, + } + + t.Run("ParseAML errors", func(t *testing.T) { + t.Run("parseObjList error", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + // Setup resolver to serve an AML stream containing an invalid opcode + header := mockParserPayload(p, []byte{0x5b, 0x00}) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected ParseAML to return an error") + } + + // Setup resolver to serve an AML stream containing an incomplete extended opcode + header = mockParserPayload(p, []byte{0x5b}) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected ParseAML to return an error") + } + }) + + t.Run("unresolved entities", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + // Inject a reference entity to the tree + p.root.Append(&namedReference{ + targetName: "UNKNOWN", + }) + + // Setup resolver to serve an empty AML stream + header := mockParserPayload(p, nil) + + if err := p.ParseAML(uint8(42), "DSDT", header); err != errResolvingEntities { + t.Fatalf("expected ParseAML to return errResolvingEntities; got %v", err) + } + }) + }) + + t.Run("parseObj errors", func(t *testing.T) { + t.Run("parsePkgLength error", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + // Setup resolver to serve an AML stream containing an incomplete + // buffer specification + header := mockParserPayload(p, []byte{byte(opBuffer)}) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected parsePkgLength to return an error") + } + }) + + t.Run("incomplete object list", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + // Setup resolver to serve an AML stream containing an incomplete + // buffer arglist specification + header := mockParserPayload(p, []byte{byte(opBuffer), 0x10}) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected parsePkgLength to return an error") + } + }) + }) + + t.Run("finalizeObj errors", func(t *testing.T) { + t.Run("else without matching if", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + p.root.Append(&constEntity{val: 0x42}) + p.root.Append(&scopeEntity{op: opElse}) + + // Setup resolver to serve an AML stream containing an + // empty else statement without a matching if + header := mockParserPayload(p, []byte{byte(opElse), 0x0}) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected finalizeObj to return an error") + } + }) + + }) + + t.Run("parseScope errors", func(t *testing.T) { + t.Run("parseNameString error", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + header := mockParserPayload(p, []byte{ + byte(opScope), + 0x10, // pkglen + }) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected parseScope to return an error") + } + }) + + t.Run("unknown scope", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + header := mockParserPayload(p, []byte{ + byte(opScope), + 0x10, // pkglen + 'F', 'O', 'O', 'F', + }) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected parseScope to return an error") + } + }) + + t.Run("nameless scope", func(t *testing.T) { + p.root = &scopeEntity{} + + header := mockParserPayload(p, []byte{ + byte(opScope), + 0x02, // pkglen + '\\', // scope name: "\" (root scope) + 0x00, // null string + }) + + if err := p.ParseAML(uint8(42), "DSDT", header); err == nil { + t.Fatal("expected parseScope to return an error") + } + }) + }) + + t.Run("parseNamespacedObj errors", func(t *testing.T) { + t.Run("parseNameString error", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + mockParserPayload(p, nil) + + if p.parseNamespacedObj(opDevice, 10) { + t.Fatal("expected parseNamespacedObj to return false") + } + }) + + t.Run("scope lookup error", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + header := mockParserPayload(p, []byte{'^', 'F', 'A', 'B', 'C'}) + + p.scopeEnter(p.root) + if p.parseNamespacedObj(opDevice, header.Length) { + t.Fatal("expected parseNamespacedObj to return false") + } + }) + + t.Run("error parsing method arg count", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + header := mockParserPayload(p, []byte{'F', 'A', 'B', 'C'}) + + p.scopeEnter(p.root) + if p.parseNamespacedObj(opMethod, header.Length) { + t.Fatal("expected parseNamespacedObj to return false") + } + }) + }) + + t.Run("parseArg bytelist errors", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + + mockParserPayload(p, nil) + + if p.parseArg(new(opcodeInfo), new(unnamedEntity), 0, opArgByteList, 42) { + t.Fatal("expected parseNamespacedObj to return false") + } + }) + + t.Run("parseMethodInvocationOrNameRef errors", func(t *testing.T) { + t.Run("missing args", func(t *testing.T) { + p.root = &scopeEntity{op: opScope, name: `\`} + p.root.Append(&Method{ + scopeEntity: scopeEntity{name: "MTHD"}, + argCount: 10, + }) + + mockParserPayload(p, []byte{ + 'M', 'T', 'H', 'D', + byte(opIf), // Incomplete type2 opcode + }) + + p.scopeEnter(p.root) + if p.parseMethodInvocationOrNameRef() { + t.Fatal("expected parseMethodInvocationOrNameRef to return false") + } + }) + }) + + t.Run("parseFieldList errors", func(t *testing.T) { + specs := []struct { + op opcode + args []interface{} + maxReadOffset uint32 + payload []byte + }{ + // Invalid arg count for opField + { + opField, + nil, + 0, + nil, + }, + // Wrong arg type for opField + { + opField, + []interface{}{0, uint64(42)}, + 0, + nil, + }, + { + opField, + []interface{}{"FLD0", uint32(42)}, + 0, + nil, + }, + // Invalid arg count for opIndexField + { + opIndexField, + nil, + 0, + nil, + }, + // Wrong arg type for opIndexField + { + opIndexField, + []interface{}{0, "FLD1", "FLD2"}, + 0, + nil, + }, + { + opIndexField, + []interface{}{"FLD0", 0, "FLD2"}, + 0, + nil, + }, + { + opIndexField, + []interface{}{"FLD0", "FLD1", 0}, + 0, + nil, + }, + // unexpected EOF parsing fields + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + nil, + }, + // reserved field (0x00) with missing pkgLen + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x00}, + }, + // access field (0x01) with missing accessType + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x01}, + }, + // access field (0x01) with missing attribute byte + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x01, 0x01}, + }, + // connect field (0x02) with incomplete TermObject => Buffer arg + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x02, byte(opBuffer)}, + }, + // extended access field (0x03) with missing ext. accessType + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x03}, + }, + // extended access field (0x03) with missing ext. attribute byte + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x03, 0x01}, + }, + // extended access field (0x03) with missing access byte count value + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0x03, 0x01, 0x02}, + }, + // named field with invalid name + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{0xff}, + }, + // named field with invalid pkgLen + { + opField, + []interface{}{"FLD0", uint64(42)}, + 128, + []byte{'N', 'A', 'M', 'E'}, + }, + } + + for specIndex, spec := range specs { + mockParserPayload(p, spec.payload) + + if p.parseFieldList(spec.op, spec.args, spec.maxReadOffset) { + t.Errorf("[spec %d] expected parseFieldLis to return false", specIndex) + } + } + }) + + t.Run("parsePkgLength errors", func(t *testing.T) { + specs := [][]byte{ + // lead byte bits (6:7) indicate 1 extra byte that is missing + []byte{1 << 6}, + // lead byte bits (6:7) indicate 2 extra bytes with the 1st and then 2nd missing + []byte{2 << 6}, + []byte{2 << 6, 0x1}, + // lead byte bits (6:7) indicate 3 extra bytes with the 1st and then 2nd and then 3rd missing + []byte{3 << 6}, + []byte{3 << 6, 0x1}, + []byte{3 << 6, 0x1, 0x2}, + } + + for specIndex, spec := range specs { + mockParserPayload(p, spec) + + if _, ok := p.parsePkgLength(); ok { + t.Errorf("[spec %d] expected parsePkgLength to return false", specIndex) + } + } + }) + + t.Run("parseString errors", func(t *testing.T) { + specs := [][]byte{ + // Unexpected EOF before terminating null byte + []byte{'A'}, + // Characters outside the allowed [0x01, 0x7f] range + []byte{'A', 0xba, 0xdf, 0x00}, + } + + for specIndex, spec := range specs { + mockParserPayload(p, spec) + + if _, ok := p.parseString(); ok { + t.Errorf("[spec %d] expected parseString to return false", specIndex) + } + } + }) + + t.Run("parseTarget errors", func(t *testing.T) { + t.Run("unexpected opcode", func(t *testing.T) { + // Unexpected opcode + mockParserPayload(p, []byte{byte(opAnd)}) + + if _, ok := p.parseTarget(); ok { + t.Error("expected parseTarget to return false") + } + }) + + t.Run("corrupted data", func(t *testing.T) { + // Invalid opcode and not a method invocation nor a namestring + mockParserPayload(p, []byte{0xba, 0xad}) + + if _, ok := p.parseTarget(); ok { + t.Error("expected parseTarget to return false") + } + }) + }) + + t.Run("parseNameString errors", func(t *testing.T) { + t.Run("EOF while parsing path prefix", func(t *testing.T) { + mockParserPayload(p, []byte{'^'}) + + if _, ok := p.parseNameString(); ok { + t.Error("expected parseNameString to return false") + } + }) + + t.Run("EOF while parsing multiname path", func(t *testing.T) { + specs := [][]byte{ + // multiname path prefix but no data following + []byte{0x2f}, + []byte{ + 0x2f, // multiname path prefix + 0x0, // no segments (segments must be > 0) + }, + []byte{ + 0x2f, // multiname path prefix + 0x1, // 1 expected segment but no more data available + }, + []byte{ + '\\', // RootChar and no more data + }, + } + + for specIndex, spec := range specs { + mockParserPayload(p, spec) + if _, ok := p.parseNameString(); ok { + t.Errorf("[spec %d] expected parseNameString to return false", specIndex) + } + } + }) + }) +} + +func mockParserPayload(p *Parser, payload []byte) *table.SDTHeader { + resolver := fixedPayloadResolver{payload} + header := resolver.LookupTable("DSDT") + p.r.Init( + uintptr(unsafe.Pointer(header)), + header.Length, + uint32(unsafe.Sizeof(table.SDTHeader{})), + ) + + return resolver.LookupTable("DSDT") +} + +func pkgDir() string { + _, f, _, _ := runtime.Caller(1) + return filepath.Dir(f) +} + +type mockResolver struct { + tableFiles []string +} + +func (m mockResolver) LookupTable(name string) *table.SDTHeader { + pathToDumps := pkgDir() + "/../table/tabletest/" + for _, f := range m.tableFiles { + if !strings.Contains(f, name) { + continue + } + + data, err := ioutil.ReadFile(pathToDumps + f) + if err != nil { + panic(err) + } + + return (*table.SDTHeader)(unsafe.Pointer(&data[0])) + } + + return nil +} + +type fixedPayloadResolver struct { + payload []byte +} + +func (f fixedPayloadResolver) LookupTable(name string) *table.SDTHeader { + hdrLen := int(unsafe.Sizeof(table.SDTHeader{})) + buf := make([]byte, len(f.payload)+hdrLen) + copy(buf[hdrLen:], f.payload) + + hdr := (*table.SDTHeader)(unsafe.Pointer(&buf[0])) + hdr.Length = uint32(len(buf)) + + return hdr +} diff --git a/src/gopheros/device/acpi/aml/scope.go b/src/gopheros/device/acpi/aml/scope.go new file mode 100644 index 0000000..51ed528 --- /dev/null +++ b/src/gopheros/device/acpi/aml/scope.go @@ -0,0 +1,189 @@ +package aml + +import "strings" + +// Visitor is a function invoked by the VM for each AML tree entity that matches +// a particular type. The return value controls whether the children of this +// entity should also be visited. +type Visitor func(depth int, obj Entity) (keepRecursing bool) + +// EntityType defines the type of entity that visitors should inspect. +type EntityType uint8 + +// The list of supported EntityType values. EntityTypeAny works as a wildcard +// allowing the visitor to inspect all entities in the AML tree. +const ( + EntityTypeAny EntityType = iota + EntityTypeDevice + EntityTypeProcessor + EntityTypePowerResource + EntityTypeThermalZone + EntityTypeMethod +) + +// scopeVisit descends a scope hierarchy and invokes visitorFn for each entity +// that matches entType. +func scopeVisit(depth int, ent Entity, entType EntityType, visitorFn Visitor) bool { + op := ent.getOpcode() + switch { + case (entType == EntityTypeAny) || + (entType == EntityTypeDevice && op == opDevice) || + (entType == EntityTypeProcessor && op == opProcessor) || + (entType == EntityTypePowerResource && op == opPowerRes) || + (entType == EntityTypeThermalZone && op == opThermalZone) || + (entType == EntityTypeMethod && op == opMethod): + // If the visitor returned false we should not visit the children + if !visitorFn(depth, ent) { + return false + } + } + + // If the entity defines a scope we need to visit the child entities. + if scopeEnt, ok := ent.(ScopeEntity); ok { + for _, child := range scopeEnt.Children() { + scopeVisit(depth+1, child, entType, visitorFn) + } + } + + return true +} + +// scopeResolvePath examines a path expression and attempts to break it down +// into a parent and child segment. The parent segment is looked up via the +// regular scope rules specified in page 252 of the ACPI 6.2 spec. If the +// parent scope is found then the function returns back the parent entity and +// the name of the child that should be appended to it. If the expression +// lookup fails then the function returns nil, "". +func scopeResolvePath(curScope, rootScope ScopeEntity, expr string) (parent ScopeEntity, name string) { + if len(expr) <= 1 { + return nil, "" + } + + // Pattern looks like \FOO or ^+BAR or BAZ (relative to curScope) + lastDotIndex := strings.LastIndexByte(expr, '.') + if lastDotIndex == -1 { + switch expr[0] { + case '\\': + return rootScope, expr[1:] + case '^': + lastHatIndex := strings.LastIndexByte(expr, '^') + if target := scopeFind(curScope, rootScope, expr[:lastHatIndex+1]); target != nil { + return target.(ScopeEntity), expr[lastHatIndex+1:] + } + + return nil, "" + default: + return curScope, expr + } + } + + // Pattern looks like: \FOO.BAR.BAZ or ^+FOO.BAR.BAZ or FOO.BAR.BAZ + if target := scopeFind(curScope, rootScope, expr[:lastDotIndex]); target != nil { + return target.(ScopeEntity), expr[lastDotIndex+1:] + } + + return nil, "" +} + +// scopeFind attempts to find an object with the given name using the rules +// specified in page 252 of the ACPI 6.2 spec: +// +// There are two types of namespace paths: an absolute namespace path (that is, +// one that starts with a ‘\’ prefix), and a relative namespace path (that is, +// one that is relative to the current namespace). The namespace search rules +// discussed above, only apply to single NameSeg paths, which is a relative +// namespace path. For those relative name paths that contain multiple NameSegs +// or Parent Prefixes, ‘^’, the search rules do not apply. If the search rules +// do not apply to a relative namespace path, the namespace object is looked up +// relative to the current namespace +func scopeFind(curScope, rootScope ScopeEntity, name string) Entity { + nameLen := len(name) + if nameLen == 0 { + return nil + } + + switch { + case name[0] == '\\': // relative to the root scope + if nameLen > 1 { + return scopeFindRelative(rootScope, name[1:]) + } + + // Name was just `\`; this matches the root namespace + return rootScope + case name[0] == '^': // relative to the parent scope(s) + for startIndex := 0; startIndex < nameLen; startIndex++ { + switch name[startIndex] { + case '^': + curScope = curScope.Parent() + + // No parent to visit + if curScope == nil { + return nil + } + default: + // Found the start of the name. Look it up relative to curNs + return scopeFindRelative(curScope, name[startIndex:]) + } + } + + // Name was just a sequence of '^'; this matches the last curScope value + return curScope + case strings.ContainsRune(name, '.'): + // If the name contains any '.' then we still need to look it + // up relative to the current scope + return scopeFindRelative(curScope, name) + default: + // We can apply the search rules described by the spec + for s := curScope; s != nil; s = s.Parent() { + for _, child := range s.Children() { + if child.Name() == name { + return child + } + } + } + } + + // Not found + return nil +} + +// scopeFindRelative returns the Entity referenced by path relative +// to the provided Namespace. If the name contains dots, each segment +// is used to access a nested namespace. If the path does not point +// to a NamedObject then lookupRelativeTo returns back nil. +func scopeFindRelative(ns ScopeEntity, path string) Entity { + var matchName string +matchNextPathSegment: + for { + dotSepIndex := strings.IndexRune(path, '.') + if dotSepIndex != -1 { + matchName = path[:dotSepIndex] + path = path[dotSepIndex+1:] + + // Search for a scoped child named "matchName" + for _, child := range ns.Children() { + childNs, ok := child.(ScopeEntity) + if !ok { + continue + } + + if childNs.Name() == matchName { + ns = childNs + continue matchNextPathSegment + } + } + } else { + // Search for a child named "name" + for _, child := range ns.Children() { + if child.Name() == path { + return child + } + } + } + + // Next segment in the path was not found or last segment not found + break + } + + return nil +} diff --git a/src/gopheros/device/acpi/aml/scope_test.go b/src/gopheros/device/acpi/aml/scope_test.go new file mode 100644 index 0000000..7dd8fec --- /dev/null +++ b/src/gopheros/device/acpi/aml/scope_test.go @@ -0,0 +1,264 @@ +package aml + +import ( + "reflect" + "testing" +) + +func TestScopeVisit(t *testing.T) { + scopeMap := genTestScopes() + root := scopeMap[`\`].(*scopeEntity) + + // Append special entities under IDE0 + ide := scopeMap["IDE0"].(*scopeEntity) + ide.Append(&Device{}) + ide.Append(&namedEntity{op: opProcessor}) + ide.Append(&namedEntity{op: opProcessor}) + ide.Append(&namedEntity{op: opPowerRes}) + ide.Append(&namedEntity{op: opPowerRes}) + ide.Append(&namedEntity{op: opPowerRes}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&namedEntity{op: opThermalZone}) + ide.Append(&Method{}) + ide.Append(&Method{}) + ide.Append(&Method{}) + ide.Append(&Method{}) + ide.Append(&Method{}) + + specs := []struct { + searchType EntityType + keepRecursing bool + wantHits int + }{ + {EntityTypeAny, true, 21}, + {EntityTypeAny, false, 1}, + {EntityTypeDevice, true, 1}, + {EntityTypeProcessor, true, 2}, + {EntityTypePowerResource, true, 3}, + {EntityTypeThermalZone, true, 4}, + {EntityTypeMethod, true, 5}, + } + + for specIndex, spec := range specs { + var hits int + scopeVisit(0, root, spec.searchType, func(_ int, obj Entity) bool { + hits++ + return spec.keepRecursing + }) + + if hits != spec.wantHits { + t.Errorf("[spec %d] expected visitor to be called %d times; got %d", specIndex, spec.wantHits, hits) + } + } +} + +func TestScopeResolvePath(t *testing.T) { + scopeMap := genTestScopes() + + specs := []struct { + curScope ScopeEntity + pathExpr string + wantParent Entity + wantName string + }{ + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_`, + scopeMap[`\`], + "_SB_", + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^FOO`, + scopeMap[`PCI0`], + "FOO", + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^^FOO`, + scopeMap[`_SB_`], + "FOO", + }, + { + scopeMap["IDE0"].(*scopeEntity), + `_ADR`, + scopeMap[`IDE0`], + "_ADR", + }, + // Paths with dots + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_.PCI0.IDE0._ADR`, + scopeMap[`IDE0`], + "_ADR", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `IDE0._ADR`, + scopeMap[`IDE0`], + "_ADR", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `_CRS`, + scopeMap[`PCI0`], + "_CRS", + }, + // Bad queries + { + scopeMap["PCI0"].(*scopeEntity), + `FOO.BAR.BAZ`, + nil, + "", + }, + { + scopeMap["PCI0"].(*scopeEntity), + ``, + nil, + "", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `\`, + nil, + "", + }, + { + scopeMap["PCI0"].(*scopeEntity), + `^^^^^^^^^BADPATH`, + nil, + "", + }, + } + + root := scopeMap[`\`].(*scopeEntity) + for specIndex, spec := range specs { + gotParent, gotName := scopeResolvePath(spec.curScope, root, spec.pathExpr) + if !reflect.DeepEqual(gotParent, spec.wantParent) { + t.Errorf("[spec %d] expected lookup to return %#v; got %#v", specIndex, spec.wantParent, gotParent) + continue + } + + if gotName != spec.wantName { + t.Errorf("[spec %d] expected lookup to return node name %q; got %q", specIndex, spec.wantName, gotName) + } + } +} + +func TestScopeFind(t *testing.T) { + scopeMap := genTestScopes() + + specs := []struct { + curScope ScopeEntity + lookup string + want Entity + }{ + // Search rules do not apply for these cases + { + scopeMap["PCI0"].(*scopeEntity), + `\`, + scopeMap[`\`], + }, + { + scopeMap["PCI0"].(*scopeEntity), + "IDE0._ADR", + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + "^^PCI0.IDE0._ADR", + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_.PCI0.IDE0._ADR`, + scopeMap["_ADR"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + `\_SB_.PCI0`, + scopeMap["PCI0"], + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^`, + scopeMap["PCI0"], + }, + // Bad queries + { + scopeMap["_SB_"].(*scopeEntity), + "PCI0.USB._CRS", + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + "^^^^^^^^^^^^^^^^^^^", + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + `^^^^^^^^^^^FOO`, + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + "FOO", + nil, + }, + { + scopeMap["IDE0"].(*scopeEntity), + "", + nil, + }, + // Search rules apply for these cases + { + scopeMap["IDE0"].(*scopeEntity), + "_CRS", + scopeMap["_CRS"], + }, + } + + root := scopeMap[`\`].(*scopeEntity) + for specIndex, spec := range specs { + if got := scopeFind(spec.curScope, root, spec.lookup); !reflect.DeepEqual(got, spec.want) { + t.Errorf("[spec %d] expected lookup to return %#v; got %#v", specIndex, spec.want, got) + } + } +} + +func genTestScopes() map[string]Entity { + // Setup the example tree from page 252 of the acpi 6.2 spec + // \ + // SB + // \ + // PCI0 + // | _CRS + // \ + // IDE0 + // | _ADR + ideScope := &scopeEntity{name: `IDE0`} + pciScope := &scopeEntity{name: `PCI0`} + sbScope := &scopeEntity{name: `_SB_`} + rootScope := &scopeEntity{name: `\`} + + adr := &namedEntity{name: `_ADR`} + crs := &namedEntity{name: `_CRS`} + + // Setup tree + ideScope.Append(adr) + pciScope.Append(crs) + pciScope.Append(ideScope) + sbScope.Append(pciScope) + rootScope.Append(sbScope) + + return map[string]Entity{ + "IDE0": ideScope, + "PCI0": pciScope, + "_SB_": sbScope, + "\\": rootScope, + "_ADR": adr, + "_CRS": crs, + } +} diff --git a/src/gopheros/device/acpi/aml/stream_reader.go b/src/gopheros/device/acpi/aml/stream_reader.go new file mode 100644 index 0000000..31e6e5b --- /dev/null +++ b/src/gopheros/device/acpi/aml/stream_reader.go @@ -0,0 +1,84 @@ +package aml + +import ( + "errors" + "io" + "reflect" + "unsafe" +) + +var ( + errInvalidUnreadByte = errors.New("amlStreamReader: invalid use of UnreadByte") +) + +type amlStreamReader struct { + offset uint32 + data []byte +} + +// Init sets up the reader so it can read up to dataLen bytes from the virtual +// memory address dataAddr. If a non-zero initialOffset is specified, it will +// be used as the current offset in the stream. +func (r *amlStreamReader) Init(dataAddr uintptr, dataLen, initialOffset uint32) { + // Overlay a byte slice on top of the memory block to be accessed. + r.data = *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ + Len: int(dataLen), + Cap: int(dataLen), + Data: dataAddr, + })) + + r.SetOffset(initialOffset) +} + +// EOF returns true if the end of the stream has been reached. +func (r *amlStreamReader) EOF() bool { + return r.offset == uint32(len(r.data)) +} + +// ReadByte returns the next byte from the stream. +func (r *amlStreamReader) ReadByte() (byte, error) { + if r.EOF() { + return 0, io.EOF + } + + r.offset++ + return r.data[r.offset-1], nil +} + +// PeekByte returns the next byte from the stream without advancing the read pointer. +func (r *amlStreamReader) PeekByte() (byte, error) { + if r.EOF() { + return 0, io.EOF + } + + return r.data[r.offset], nil +} + +// LastByte returns the last byte read off the stream +func (r *amlStreamReader) LastByte() (byte, error) { + if r.offset == 0 { + return 0, io.EOF + } + + return r.data[r.offset-1], nil +} + +// UnreadByte moves back the read pointer by one byte. +func (r *amlStreamReader) UnreadByte() error { + if r.offset == 0 { + return errInvalidUnreadByte + } + + r.offset-- + return nil +} + +// Offset returns the current offset. +func (r *amlStreamReader) Offset() uint32 { + return r.offset +} + +// SetOffset sets the reader offset to the supplied value. +func (r *amlStreamReader) SetOffset(off uint32) { + r.offset = off +} diff --git a/src/gopheros/device/acpi/aml/stream_reader_test.go b/src/gopheros/device/acpi/aml/stream_reader_test.go new file mode 100644 index 0000000..560a7ca --- /dev/null +++ b/src/gopheros/device/acpi/aml/stream_reader_test.go @@ -0,0 +1,97 @@ +package aml + +import ( + "io" + "testing" + "unsafe" +) + +func TestAMLStreamReader(t *testing.T) { + buf := make([]byte, 16) + for i := 0; i < len(buf); i++ { + buf[i] = byte(i) + } + + t.Run("without offset", func(t *testing.T) { + var r amlStreamReader + r.Init( + uintptr(unsafe.Pointer(&buf[0])), + uint32(len(buf)), + 0, + ) + + if r.EOF() { + t.Fatal("unexpected EOF") + } + + if err := r.UnreadByte(); err != errInvalidUnreadByte { + t.Fatalf("expected errInvalidUnreadByte; got %v", err) + } + + if _, err := r.LastByte(); err != io.EOF { + t.Fatalf("unexpected error: %v", err) + } + + for i := 0; i < len(buf); i++ { + exp := byte(i) + + next, err := r.PeekByte() + if err != nil { + t.Fatal(err) + } + if next != exp { + t.Fatalf("expected PeekByte to return %d; got %d", exp, next) + } + + next, err = r.ReadByte() + if err != nil { + t.Fatal(err) + } + if next != exp { + t.Fatalf("expected ReadByte to return %d; got %d", exp, next) + } + + last, err := r.LastByte() + if err != nil { + t.Fatal(err) + } + if last != exp { + t.Fatalf("expected LastByte to return %d; got %d", exp, last) + } + } + + if _, err := r.PeekByte(); err != io.EOF { + t.Fatalf("unexpected error: %v", err) + } + if _, err := r.ReadByte(); err != io.EOF { + t.Fatalf("unexpected error: %v", err) + } + exp := byte(len(buf) - 1) + if last, _ := r.LastByte(); last != exp { + t.Fatalf("expected LastByte to return %d; got %d", exp, last) + } + + }) + + t.Run("with offset", func(t *testing.T) { + var r amlStreamReader + r.Init( + uintptr(unsafe.Pointer(&buf[0])), + uint32(len(buf)), + 8, + ) + + if r.EOF() { + t.Fatal("unexpected EOF") + } + + if exp, got := uint32(8), r.Offset(); got != exp { + t.Fatalf("expected Offset() to return %d; got %d", exp, got) + } + + exp := byte(8) + if next, _ := r.ReadByte(); next != exp { + t.Fatalf("expected ReadByte to return %d; got %d", exp, next) + } + }) +} diff --git a/src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.aml b/src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.aml new file mode 100644 index 0000000..dcf8dbd Binary files /dev/null and b/src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.dsl b/src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.dsl new file mode 100644 index 0000000..54968ba --- /dev/null +++ b/src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.dsl @@ -0,0 +1,117 @@ +// DSDT-parser-testsuite +// +// This file contains various ASL constructs to ensure that the AML parser +// properly handles all possible ASL opcodes it may encounter. This test file +// is used in addition to the DSDT.aml file obtained by running acpidump inside +// virtualbox. +DefinitionBlock ("parser-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x00000002) +{ + OperationRegion (DBG0, SystemIO, 0x3000, 0x04) + Field (DBG0, ByteAcc, NoLock, Preserve) + { + DHE1, 8 + } + + Device (DRV0) + { + Name (_ADR, Ones) + + // named entity containing qword const + Name (H15F, 0xBADC0FEEDEADC0DE) + Method (_GTF, 0, NotSerialized) // _GTF: Get Task File + { + Return (H15F) + } + } + + // example from p. 268 of ACPI 6.2 spec + Scope(\_SB){ + OperationRegion(TOP1, GenericSerialBus, 0x00, 0x100) // GenericSerialBus device at command offset 0x00 + + Name (SDB0, ResourceTemplate() {}) + Field(TOP1, BufferAcc, NoLock, Preserve){ + Connection(SDB0), // Use the Resource Descriptor defined above + AccessAs(BufferAcc, AttribWord), + FLD0, 8, + FLD1, 8 + } + + Field(TOP1, BufferAcc, NoLock, Preserve){ + Connection(I2cSerialBus(0x5b,,100000,, "\\_SB",,,,RawDataBuffer(){3,9})), + AccessAs(BufferAcc, AttribBytes(4)), + FLD2, 8, + AccessAs(BufferAcc, AttribRawBytes(3)), + FLD3, 8, + AccessAs(BufferAcc, AttribRawProcessBytes(2)), + FLD4, 8 + } + } + + // Other entity types + Event(HLO0) + + // Other executable bits + Method (EXE0, 1, Serialized) + { + Local0 = Revision + + // NameString target + Local1 = SizeOf(GLB1) + + Local0 = "my-handle" + Load(DBG0, Local0) + Unload(Local0) + + // Example from p. 951 of the spec + Store ( + LoadTable ("OEM1", "MYOEM", "TABLE1", "\\_SB.PCI0","MYD", + Package () {0,"\\_SB.PCI0"} + ), Local0 + ) + + FromBCD(9, Arg0) + ToBCD(Arg0, Local1) + + Breakpoint + Debug = "test" + Fatal(0xf0, 0xdeadc0de, 1) + + Reset(HLO0) + + // Mutex support + Mutex(MUT0, 1) + Acquire(MUT0, 0xffff) // no timeout + Release(MUT0) + + // Signal/Wait + Signal(HLO0) + Wait(HLO0, 0xffff) + + // Get monotonic timer value + Local0 = Timer + + CopyObject(Local0, Local1) + Return(ObjectType(Local1)) + } + + // Misc regions + + // BankField example from p. 899 of the spec + // Define a 256-byte operational region in SystemIO space and name it GIO0 + OperationRegion (GIO0, SystemIO, 0x125, 0x100) + Field (GIO0, ByteAcc, NoLock, Preserve) { + GLB1, 1, + GLB2, 1, + Offset (1), // Move to offset for byte 1 + BNK1, 4 + } + + BankField (GIO0, BNK1, 0, ByteAcc, NoLock, Preserve) { + Offset (0x30), + FET0, 1, + FET1, 1 + } + + // Data Region + DataTableRegion (REG0, "FOOF", "BAR", "BAZ") +}