1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

acpi: implement AML parser for all AML opcodes in the ACPI 6.2 spec

This commit is contained in:
Achilleas Anagnostopoulos 2017-09-06 10:47:57 +01:00
parent 4dd7c0b077
commit 2a84c75d8e
7 changed files with 1183 additions and 15 deletions

View File

@ -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
}

View File

@ -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,

View File

@ -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
}
}

View File

@ -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]
}

View File

@ -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
}

View File

@ -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")
}