From 2a84c75d8e3927a32005497cffd6c41f7ae4c593 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Wed, 6 Sep 2017 10:47:57 +0100 Subject: [PATCH] acpi: implement AML parser for all AML opcodes in the ACPI 6.2 spec --- src/gopheros/device/acpi/aml/entity.go | 60 +- src/gopheros/device/acpi/aml/opcode_table.go | 24 +- src/gopheros/device/acpi/aml/opcode_test.go | 6 +- src/gopheros/device/acpi/aml/parser.go | 922 ++++++++++++++++++ src/gopheros/device/acpi/aml/parser_test.go | 69 ++ .../table/tabletest/parser-testsuite-DSDT.aml | Bin 0 -> 488 bytes .../table/tabletest/parser-testsuite-DSDT.dsl | 117 +++ 7 files changed, 1183 insertions(+), 15 deletions(-) create mode 100644 src/gopheros/device/acpi/aml/parser.go create mode 100644 src/gopheros/device/acpi/aml/parser_test.go create mode 100644 src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.aml create mode 100644 src/gopheros/device/acpi/table/tabletest/parser-testsuite-DSDT.dsl diff --git a/src/gopheros/device/acpi/aml/entity.go b/src/gopheros/device/acpi/aml/entity.go index 3d8003e..1c34c18 100644 --- a/src/gopheros/device/acpi/aml/entity.go +++ b/src/gopheros/device/acpi/aml/entity.go @@ -302,6 +302,10 @@ type fieldEntity struct { type fieldUnitEntity struct { fieldEntity + // The connection which this field references. + connectionName string + resolvedConnection Entity + // The region which this field references. regionName string resolvedRegion *regionEntity @@ -309,6 +313,13 @@ type fieldUnitEntity struct { 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) @@ -326,6 +337,10 @@ func (ent *fieldUnitEntity) Resolve(errWriter io.Writer, rootNs ScopeEntity) boo type indexFieldEntity struct { fieldEntity + // The connection which this field references. + connectionName string + resolvedConnection Entity + indexRegName string indexReg *fieldUnitEntity @@ -335,6 +350,13 @@ type indexFieldEntity struct { 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) @@ -363,7 +385,7 @@ type namedReference struct { func (ref *namedReference) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool { if ref.target = scopeFind(ref.parent, rootNs, ref.targetName); ref.target == nil { - kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s\n", ref.targetName) + kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, ref.parent.Name()) return false } @@ -397,3 +419,39 @@ type Device struct { } func (d *Device) getOpcode() opcode { return opDevice } + +// 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 +} + +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 { + // arg 0 is the mutex name + if argIndex == 0 { + var ok bool + ent.name, ok = arg.(string) + return ok + } + + // arg1 is the sync level (bits 0:3) + syncLevel, ok := arg.(uint64) + ent.syncLevel = uint8(syncLevel) & 0xf + return ok +} + +// eventEntity represents a named ACPI sync event. +type eventEntity struct { + namedEntity +} diff --git a/src/gopheros/device/acpi/aml/opcode_table.go b/src/gopheros/device/acpi/aml/opcode_table.go index d1659ac..ba8e500 100644 --- a/src/gopheros/device/acpi/aml/opcode_table.go +++ b/src/gopheros/device/acpi/aml/opcode_table.go @@ -245,7 +245,7 @@ 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, 0xff, 0xff, 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, @@ -260,15 +260,15 @@ var opcodeMap = [256]uint8{ /*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, 0xff, 0x3d, + /*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, 0xff, 0x4a, 0x4b, + /*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, 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, @@ -282,13 +282,13 @@ var opcodeMap = [256]uint8{ // invalid/unsupported opcode. var extendedOpcodeMap = [256]uint8{ /* 0 1 2 3 4 5 6 7*/ - /*0x00 - 0x07*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /*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, 0xff, 0xff, 0xff, 0xff, 0xff, - /*0x18 - 0x1f*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - /*0x20 - 0x27*/ 0xff, 0x5a, 0x5b, 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, + /*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, @@ -298,8 +298,8 @@ var extendedOpcodeMap = [256]uint8{ /*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, 0xff, - /*0x88 - 0x8f*/ 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, diff --git a/src/gopheros/device/acpi/aml/opcode_test.go b/src/gopheros/device/acpi/aml/opcode_test.go index 7d0fd20..7258759 100644 --- a/src/gopheros/device/acpi/aml/opcode_test.go +++ b/src/gopheros/device/acpi/aml/opcode_test.go @@ -156,6 +156,7 @@ func TestOpArgFlagToString(t *testing.T) { // 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 @@ -170,14 +171,15 @@ func TestFindUnmappedOpcodes(t *testing.T) { } for opIndex, opRef := range extendedOpcodeMap { - if opRef != badOpcode { + // 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, tabIndex, info.op.String()) + 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..f679ea6 --- /dev/null +++ b/src/gopheros/device/acpi/aml/parser.go @@ -0,0 +1,922 @@ +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 +} + +// 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. The parser emits any encountered errors to the specified errWriter. +func (p *Parser) ParseAML(tableName string, header *table.SDTHeader) *kernel.Error { + 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 { + 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 + p.scopeCurrent().removeLastChild() + prevObj := p.scopeCurrent().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 a 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, ok := p.parseNumConstant(1) + if !ok { + 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, ok := obj.(ScopeEntity); ok { + 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 + } + + return p.scopeCurrent().removeLastChild(), true +} + +func (p *Parser) makeObjForOpcode(info *opcodeInfo) Entity { + var obj Entity + + switch { + case info.objType == objTypeLocalScope: + obj = new(scopeEntity) + 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().removeLastChild() + } + } + + // 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] unsupported 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] unsupported 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 && !p.r.EOF() { + 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{ + 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{ + 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 actuall a NullName + p.r.SetOffset(curOffset + 1) + return &constEntity{op: opStringPrefix, val: ""}, true + case opIsArg(nextOpcode.op): // ArgObj | LocalObj + case nextOpcode.op == opRefOf || nextOpcode.op == opDerefOf || nextOpcode.op == opIndex || nextOpcode.op == opDebug: // 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 { + return p.scopeCurrent().removeLastChild(), 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) + 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() + 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..4f4d6bf --- /dev/null +++ b/src/gopheros/device/acpi/aml/parser_test.go @@ -0,0 +1,69 @@ +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(tableName, resolver.LookupTable(tableName)); err != nil { + t.Errorf("[spec %d] [%s]: %v", specIndex, tableName, err) + break + } + } + } +} + +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 +} 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 0000000000000000000000000000000000000000..dcf8dbd91c96c02be76594ec982c60da30c3e133 GIT binary patch literal 488 zcmYjNO-sW-5S>jM-6o`dAX-6C&?*Ybrs`Q6H%W`BThb`%2u(nt2bF+cytN+Glm9^Q z;MG6i$sgc9P&|7Q|3TcX7cXz#ytmBEKHlQd7Xi@TebKDB;U9{Y1_1S)D4=9~2D{wx z88LwwdF+|}hSyx1;&qvadyKZLJj`g#UiXThug{;~Lmw|=X4{WEcv9l9KiFiY!iV{lKx4)H=Ib?j+~Orza5OjQUWF zOL2Q?zxxQ6aXl7Z8tMNmeJl!YQ_3J{WU6z3p>`nALbvBVuAu!