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

Merge pull request #52 from achilleasa/detect-acpi-support-and-enumerate-acpi-tables

Detect ACPI support and enumerate ACPI tables
This commit is contained in:
Achilleas Anagnostopoulos 2017-08-28 07:49:00 +01:00 committed by GitHub
commit 14aef16459
13 changed files with 1119 additions and 3 deletions

View File

@ -0,0 +1,277 @@
package acpi
import (
"gopheros/device"
"gopheros/device/acpi/table"
"gopheros/kernel"
"gopheros/kernel/kfmt"
"gopheros/kernel/mem"
"gopheros/kernel/mem/pmm"
"gopheros/kernel/mem/vmm"
"io"
"unsafe"
)
const (
acpiRev1 uint8 = 0
acpiRev2Plus uint8 = 2
)
var (
errMissingRSDP = &kernel.Error{Module: "acpi", Message: "could not locate ACPI RSDP"}
errTableChecksumMismatch = &kernel.Error{Module: "acpi", Message: "detected checksum mismatch while parsing ACPI table header"}
mapFn = vmm.Map
identityMapFn = vmm.IdentityMapRegion
unmapFn = vmm.Unmap
// RDSP must be located in the physical memory region 0xe0000 to 0xfffff
rsdpLocationLow uintptr = 0xe0000
rsdpLocationHi uintptr = 0xfffff
rsdpAlignment uintptr = 16
rsdpSignature = [8]byte{'R', 'S', 'D', ' ', 'P', 'T', 'R', ' '}
fadtSignature = "FACP"
)
type acpiDriver struct {
// rsdtAddr holds the address to the root system descriptor table.
rsdtAddr uintptr
// useXSDT specifies if the driver must use the XSDT or the RSDT table.
useXSDT bool
// The ACPI table map allows the driver to lookup an ACPI table header
// by the table name. All tables included in this map are mapped into
// memory.
tableMap map[string]*table.SDTHeader
}
// DriverInit initializes this driver.
func (drv *acpiDriver) DriverInit(w io.Writer) *kernel.Error {
if err := drv.enumerateTables(w); err != nil {
return err
}
drv.printTableInfo(w)
return nil
}
// DriverName returns the name of this driver.
func (*acpiDriver) DriverName() string {
return "ACPI"
}
// DriverVersion returns the version of this driver.
func (*acpiDriver) DriverVersion() (uint16, uint16, uint16) {
return 0, 0, 1
}
func (drv *acpiDriver) printTableInfo(w io.Writer) {
for name, header := range drv.tableMap {
kfmt.Fprintf(w, "%s at 0x%16x %6x (%6s %8s)\n",
name,
uintptr(unsafe.Pointer(header)),
header.Length,
string(header.OEMID[:]),
string(header.OEMTableID[:]),
)
}
}
// enumerateTables detects and maps all ACPI tables that are present. Besides
// the table list defined by the RSDP, this method will also peek into the
// FADT (if found) looking for the address of DSDT.
func (drv *acpiDriver) enumerateTables(w io.Writer) *kernel.Error {
header, sizeofHeader, err := mapACPITable(drv.rsdtAddr)
if err != nil {
return err
}
drv.tableMap = make(map[string]*table.SDTHeader)
var (
acpiRev = header.Revision
payloadLen = header.Length - uint32(sizeofHeader)
sdtAddresses []uintptr
)
// RSDT uses 4-byte long pointers whereas the XSDT uses 8-byte long.
switch drv.useXSDT {
case true:
sdtAddresses = make([]uintptr, payloadLen>>3)
for curPtr, i := drv.rsdtAddr+sizeofHeader, 0; i < len(sdtAddresses); curPtr, i = curPtr+8, i+1 {
sdtAddresses[i] = uintptr(*(*uint64)(unsafe.Pointer(curPtr)))
}
default:
sdtAddresses = make([]uintptr, payloadLen>>2)
for curPtr, i := drv.rsdtAddr+sizeofHeader, 0; i < len(sdtAddresses); curPtr, i = curPtr+4, i+1 {
sdtAddresses[i] = uintptr(*(*uint32)(unsafe.Pointer(curPtr)))
}
}
for _, addr := range sdtAddresses {
if header, _, err = mapACPITable(addr); err != nil {
switch err {
case errTableChecksumMismatch:
kfmt.Fprintf(w, "%s at 0x%16x %6x [checksum mismatch; skipping]\n",
string(header.Signature[:]),
uintptr(unsafe.Pointer(header)),
header.Length,
)
continue
default:
return err
}
}
signature := string(header.Signature[:])
drv.tableMap[signature] = header
// The FADT allows us to lookup the DSDT table address
if signature == fadtSignature {
fadt := (*table.FADT)(unsafe.Pointer(header))
dsdtAddr := uintptr(fadt.Dsdt)
if acpiRev >= acpiRev2Plus {
dsdtAddr = uintptr(fadt.Ext.Dsdt)
}
if header, _, err = mapACPITable(dsdtAddr); err != nil {
switch err {
case errTableChecksumMismatch:
kfmt.Fprintf(w, "%s at 0x%16x %6x [checksum mismatch; skipping]\n",
string(header.Signature[:]),
uintptr(unsafe.Pointer(header)),
header.Length,
)
continue
default:
return err
}
}
drv.tableMap[string(header.Signature[:])] = header
}
}
return nil
}
// mapACPITable attempts to map and parse the header for the ACPI table starting
// at the given address. It then uses the length field for the header to expand
// the mapping to cover the table contents and verifies the checksum before
// returning a pointer to the table header.
func mapACPITable(tableAddr uintptr) (header *table.SDTHeader, sizeofHeader uintptr, err *kernel.Error) {
var headerPage vmm.Page
// Identity-map the table header so we can access its length field
sizeofHeader = unsafe.Sizeof(table.SDTHeader{})
if headerPage, err = identityMapFn(pmm.FrameFromAddress(tableAddr), mem.Size(sizeofHeader), vmm.FlagPresent); err != nil {
return nil, sizeofHeader, err
}
// Expand mapping to cover the table contents
headerPageAddr := headerPage.Address() + vmm.PageOffset(tableAddr)
header = (*table.SDTHeader)(unsafe.Pointer(headerPageAddr))
if _, err = identityMapFn(pmm.FrameFromAddress(tableAddr), mem.Size(header.Length), vmm.FlagPresent); err != nil {
return nil, sizeofHeader, err
}
if !validTable(headerPageAddr, header.Length) {
err = errTableChecksumMismatch
}
return header, sizeofHeader, err
}
// locateRSDT scans the memory region [rsdpLocationLow, rsdpLocationHi] looking
// for the signature of the root system descriptor pointer (RSDP). If the RSDP
// is found and is valid, locateRSDT returns the physical address of the root
// system descriptor table (RSDT) or the extended system descriptor table (XSDT)
// if the system supports ACPI 2.0+.
func locateRSDT() (uintptr, bool, *kernel.Error) {
var (
rsdp *table.RSDPDescriptor
rsdp2 *table.ExtRSDPDescriptor
)
// Cleanup temporary identity mappings when the function returns
defer func() {
for curPage := vmm.PageFromAddress(rsdpLocationLow); curPage <= vmm.PageFromAddress(rsdpLocationHi); curPage++ {
unmapFn(curPage)
}
}()
// Setup temporary identity mapping so we can scan for the header
for curPage := vmm.PageFromAddress(rsdpLocationLow); curPage <= vmm.PageFromAddress(rsdpLocationHi); curPage++ {
if err := mapFn(curPage, pmm.Frame(curPage), vmm.FlagPresent); err != nil {
return 0, false, err
}
}
// The RSDP should be aligned on a 16-byte boundary
checkNextBlock:
for curPtr := rsdpLocationLow; curPtr < rsdpLocationHi; curPtr += rsdpAlignment {
rsdp = (*table.RSDPDescriptor)(unsafe.Pointer(curPtr))
for i, b := range rsdpSignature {
if rsdp.Signature[i] != b {
continue checkNextBlock
}
}
if rsdp.Revision == acpiRev1 {
if !validTable(curPtr, uint32(unsafe.Sizeof(*rsdp))) {
continue
}
return uintptr(rsdp.RSDTAddr), false, nil
}
// System uses ACPI revision > 1 and provides an extended RSDP
// which can be accessed at the same place.
rsdp2 = (*table.ExtRSDPDescriptor)(unsafe.Pointer(curPtr))
if !validTable(curPtr, uint32(unsafe.Sizeof(*rsdp2))) {
continue
}
return uintptr(rsdp2.XSDTAddr), true, nil
}
return 0, false, errMissingRSDP
}
// validTable calculates the checksum for an ACPI table of length tableLength
// that starts at tablePtr and returns true if the table is valid.
func validTable(tablePtr uintptr, tableLength uint32) bool {
var (
i uint32
sum uint8
)
for i = 0; i < tableLength; i++ {
sum += *(*uint8)(unsafe.Pointer(tablePtr + uintptr(i)))
}
return sum == 0
}
func probeForACPI() device.Driver {
if rsdtAddr, useXSDT, err := locateRSDT(); err == nil {
return &acpiDriver{
rsdtAddr: rsdtAddr,
useXSDT: useXSDT,
}
}
return nil
}
func init() {
device.RegisterDriver(&device.DriverInfo{
Order: device.DetectOrderBeforeACPI,
Probe: probeForACPI,
})
}

View File

@ -0,0 +1,487 @@
package acpi
import (
"gopheros/device/acpi/table"
"gopheros/kernel"
"gopheros/kernel/mem"
"gopheros/kernel/mem/pmm"
"gopheros/kernel/mem/vmm"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
"unsafe"
)
var (
dsdtSignature = "DSDT"
)
func TestProbe(t *testing.T) {
defer func(rsdpLow, rsdpHi, rsdpAlign uintptr) {
mapFn = vmm.Map
unmapFn = vmm.Unmap
rsdpLocationLow = rsdpLow
rsdpLocationHi = rsdpHi
rsdpAlignment = rsdpAlign
}(rsdpLocationLow, rsdpLocationHi, rsdpAlignment)
t.Run("ACPI1", func(t *testing.T) {
mapFn = func(_ vmm.Page, _ pmm.Frame, _ vmm.PageTableEntryFlag) *kernel.Error { return nil }
unmapFn = func(_ vmm.Page) *kernel.Error { return nil }
// Allocate space for 2 descriptors; leave the first entry
// blank to test that locateRSDT will jump over it and populate
// the second descriptor
sizeofRSDP := unsafe.Sizeof(table.RSDPDescriptor{})
buf := make([]byte, 2*sizeofRSDP)
rsdpHeader := (*table.RSDPDescriptor)(unsafe.Pointer(&buf[sizeofRSDP]))
rsdpHeader.Signature = rsdpSignature
rsdpHeader.Revision = acpiRev1
rsdpHeader.RSDTAddr = 0xbadf00
rsdpHeader.Checksum = -calcChecksum(uintptr(unsafe.Pointer(rsdpHeader)), uintptr(sizeofRSDP))
rsdpLocationLow = uintptr(unsafe.Pointer(&buf[0]))
rsdpLocationHi = uintptr(unsafe.Pointer(&buf[2*sizeofRSDP-1]))
// As we cannot ensure 16-byte alignment for our buffer we need to override the
// alignment so we scan all bytes in the buffer for the descriptor signature
rsdpAlignment = 1
drv := probeForACPI()
if drv == nil {
t.Fatal("ACPI probe failed")
}
drv.DriverName()
drv.DriverVersion()
acpiDrv := drv.(*acpiDriver)
if acpiDrv.rsdtAddr != uintptr(rsdpHeader.RSDTAddr) {
t.Fatalf("expected probed RSDT address to be 0x%x; got 0x%x", uintptr(rsdpHeader.RSDTAddr), acpiDrv.rsdtAddr)
}
if exp := false; acpiDrv.useXSDT != exp {
t.Fatal("expected probe to locate the RSDT and not the XSDT")
}
})
t.Run("ACPI2+", func(t *testing.T) {
mapFn = func(_ vmm.Page, _ pmm.Frame, _ vmm.PageTableEntryFlag) *kernel.Error { return nil }
unmapFn = func(_ vmm.Page) *kernel.Error { return nil }
// Allocate space for 2 descriptors; leave the first entry
// blank to test that locateRSDT will jump over it and populate
// the second descriptor
sizeofRSDP := unsafe.Sizeof(table.RSDPDescriptor{})
sizeofExtRSDP := unsafe.Sizeof(table.ExtRSDPDescriptor{})
buf := make([]byte, 2*sizeofExtRSDP)
rsdpHeader := (*table.ExtRSDPDescriptor)(unsafe.Pointer(&buf[sizeofExtRSDP]))
rsdpHeader.Signature = rsdpSignature
rsdpHeader.Revision = acpiRev2Plus
rsdpHeader.RSDTAddr = 0xbadf00 // we should ignore this and use XSDT instrad
rsdpHeader.Checksum = -calcChecksum(uintptr(unsafe.Pointer(rsdpHeader)), uintptr(sizeofRSDP))
rsdpHeader.XSDTAddr = 0xc0ffee
rsdpHeader.ExtendedChecksum = -calcChecksum(uintptr(unsafe.Pointer(rsdpHeader)), uintptr(sizeofExtRSDP))
rsdpLocationLow = uintptr(unsafe.Pointer(&buf[0]))
rsdpLocationHi = uintptr(unsafe.Pointer(&buf[2*sizeofExtRSDP-1]))
// As we cannot ensure 16-byte alignment for our buffer we need to override the
// alignment so we scan all bytes in the buffer for the descriptor signature
rsdpAlignment = 1
drv := probeForACPI()
if drv == nil {
t.Fatal("ACPI probe failed")
}
acpiDrv := drv.(*acpiDriver)
if acpiDrv.rsdtAddr != uintptr(rsdpHeader.XSDTAddr) {
t.Fatalf("expected probed RSDT address to be 0x%x; got 0x%x", uintptr(rsdpHeader.XSDTAddr), acpiDrv.rsdtAddr)
}
if exp := true; acpiDrv.useXSDT != exp {
t.Fatal("expected probe to locate the XSDT and not the RSDT")
}
})
t.Run("RSDP ACPI1 checksum mismatch", func(t *testing.T) {
mapFn = func(_ vmm.Page, _ pmm.Frame, _ vmm.PageTableEntryFlag) *kernel.Error { return nil }
unmapFn = func(_ vmm.Page) *kernel.Error { return nil }
sizeofRSDP := unsafe.Sizeof(table.RSDPDescriptor{})
buf := make([]byte, sizeofRSDP)
rsdpHeader := (*table.RSDPDescriptor)(unsafe.Pointer(&buf[0]))
rsdpHeader.Signature = rsdpSignature
rsdpHeader.Revision = acpiRev1
// Set wrong checksum
rsdpHeader.Checksum = 0
// As we cannot ensure 16-byte alignment for our buffer we need to override the
// alignment so we scan all bytes in the buffer for the descriptor signature
rsdpLocationLow = uintptr(unsafe.Pointer(&buf[0]))
rsdpLocationHi = uintptr(unsafe.Pointer(&buf[sizeofRSDP-1]))
rsdpAlignment = 1
drv := probeForACPI()
if drv != nil {
t.Fatal("expected ACPI probe to fail")
}
})
t.Run("RSDP ACPI2+ checksum mismatch", func(t *testing.T) {
mapFn = func(_ vmm.Page, _ pmm.Frame, _ vmm.PageTableEntryFlag) *kernel.Error { return nil }
unmapFn = func(_ vmm.Page) *kernel.Error { return nil }
sizeofExtRSDP := unsafe.Sizeof(table.ExtRSDPDescriptor{})
buf := make([]byte, sizeofExtRSDP)
rsdpHeader := (*table.ExtRSDPDescriptor)(unsafe.Pointer(&buf[0]))
rsdpHeader.Signature = rsdpSignature
rsdpHeader.Revision = acpiRev2Plus
// Set wrong checksum for extended rsdp
rsdpHeader.ExtendedChecksum = 0
// As we cannot ensure 16-byte alignment for our buffer we need to override the
// alignment so we scan all bytes in the buffer for the descriptor signature
rsdpLocationLow = uintptr(unsafe.Pointer(&buf[0]))
rsdpLocationHi = uintptr(unsafe.Pointer(&buf[sizeofExtRSDP-1]))
rsdpAlignment = 1
drv := probeForACPI()
if drv != nil {
t.Fatal("expected ACPI probe to fail")
}
})
t.Run("error mapping rsdp memory block", func(t *testing.T) {
expErr := &kernel.Error{Module: "test", Message: "vmm.Map failed"}
mapFn = func(_ vmm.Page, _ pmm.Frame, _ vmm.PageTableEntryFlag) *kernel.Error { return expErr }
unmapFn = func(_ vmm.Page) *kernel.Error { return nil }
drv := probeForACPI()
if drv != nil {
t.Fatal("expected ACPI probe to fail")
}
})
}
func TestDriverInit(t *testing.T) {
defer func() {
identityMapFn = vmm.IdentityMapRegion
}()
t.Run("success", func(t *testing.T) {
rsdtAddr, _ := genTestRDST(t, acpiRev2Plus)
identityMapFn = func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
return vmm.Page(frame), nil
}
drv := &acpiDriver{
rsdtAddr: rsdtAddr,
useXSDT: true,
}
if err := drv.DriverInit(os.Stderr); err != nil {
t.Fatal(err)
}
})
t.Run("map errors in enumerateTables", func(t *testing.T) {
rsdtAddr, tableList := genTestRDST(t, acpiRev2Plus)
var (
expErr = &kernel.Error{Module: "test", Message: "vmm.Map failed"}
callCount int
)
drv := &acpiDriver{
rsdtAddr: rsdtAddr,
useXSDT: true,
}
specs := []func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error){
func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
// fail while trying to map RSDT
return 0, expErr
},
func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
// fail while trying to map any other ACPI table
callCount++
if callCount > 2 {
return 0, expErr
}
return vmm.Page(frame), nil
},
func(frame pmm.Frame, size mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
// fail while trying to map DSDT
for _, header := range tableList {
if header.Length == uint32(size) && string(header.Signature[:]) == dsdtSignature {
return 0, expErr
}
}
return vmm.Page(frame), nil
},
}
// Test map errors for all map calls in enumerateTables
for specIndex, spec := range specs {
identityMapFn = spec
if err := drv.DriverInit(os.Stderr); err != expErr {
t.Errorf("[spec %d]; expected to get an error\n", specIndex)
}
}
})
}
func TestEnumerateTables(t *testing.T) {
defer func() {
identityMapFn = vmm.IdentityMapRegion
}()
var expTables = []string{"SSDT", "APIC", "FACP", "DSDT"}
t.Run("ACPI1", func(t *testing.T) {
rsdtAddr, tableList := genTestRDST(t, acpiRev1)
identityMapFn = func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
// The frame encodes the table index we need to lookup (see genTestRDST)
nextTableIndex := int(frame)
if nextTableIndex >= len(tableList) {
// This is the RSDT
return vmm.Page(frame), nil
}
header := tableList[nextTableIndex]
return vmm.PageFromAddress(uintptr(unsafe.Pointer(header))), nil
}
drv := &acpiDriver{
rsdtAddr: rsdtAddr,
useXSDT: false,
}
if err := drv.enumerateTables(os.Stderr); err != nil {
t.Fatal(err)
}
if exp, got := len(expTables), len(drv.tableMap); got != exp {
t.Fatalf("expected enumerateTables to discover %d tables; got %d\n", exp, got)
}
for _, tableName := range expTables {
if drv.tableMap[tableName] == nil {
t.Fatalf("expected enumerateTables to discover table %q", tableName)
}
}
drv.printTableInfo(os.Stderr)
})
t.Run("ACPI2+", func(t *testing.T) {
rsdtAddr, _ := genTestRDST(t, acpiRev2Plus)
identityMapFn = func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
return vmm.Page(frame), nil
}
drv := &acpiDriver{
rsdtAddr: rsdtAddr,
useXSDT: true,
}
if err := drv.enumerateTables(os.Stderr); err != nil {
t.Fatal(err)
}
if exp, got := len(expTables), len(drv.tableMap); got != exp {
t.Fatalf("expected enumerateTables to discover %d tables; got %d\n", exp, got)
}
for _, tableName := range expTables {
if drv.tableMap[tableName] == nil {
t.Fatalf("expected enumerateTables to discover table %q", tableName)
}
}
})
t.Run("checksum mismatch", func(t *testing.T) {
rsdtAddr, tableList := genTestRDST(t, acpiRev2Plus)
identityMapFn = func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
return vmm.Page(frame), nil
}
// Set bad checksum for "SSDT" and "DSDT"
for _, header := range tableList {
switch string(header.Signature[:]) {
case "SSDT", dsdtSignature:
header.Checksum++
}
}
drv := &acpiDriver{
rsdtAddr: rsdtAddr,
useXSDT: true,
}
if err := drv.enumerateTables(os.Stderr); err != nil {
t.Fatal(err)
}
expTables := []string{"APIC", "FACP"}
if exp, got := len(expTables), len(drv.tableMap); got != exp {
t.Fatalf("expected enumerateTables to discover %d tables; got %d\n", exp, got)
}
for _, tableName := range expTables {
if drv.tableMap[tableName] == nil {
t.Fatalf("expected enumerateTables to discover table %q", tableName)
}
}
})
}
func TestMapACPITableErrors(t *testing.T) {
defer func() {
identityMapFn = vmm.IdentityMapRegion
}()
var (
callCount int
expErr = &kernel.Error{Module: "test", Message: "identityMapRegion failed"}
header table.SDTHeader
)
identityMapFn = func(frame pmm.Frame, _ mem.Size, _ vmm.PageTableEntryFlag) (vmm.Page, *kernel.Error) {
callCount++
if callCount >= 2 {
return 0, expErr
}
return vmm.PageFromAddress(uintptr(unsafe.Pointer(&header))), nil
}
// Test errors while mapping the table contents and the table header
for i := 0; i < 2; i++ {
if _, _, err := mapACPITable(0xf00); err != expErr {
t.Errorf("[spec %d]; expected to get an error\n", i)
}
}
}
func genTestRDST(t *testing.T, acpiVersion uint8) (rsdtAddr uintptr, tableList []*table.SDTHeader) {
dumpFiles, err := filepath.Glob(pkgDir() + "/table/tabletest/*.aml")
if err != nil {
t.Fatal(err)
}
var fadt, dsdt *table.SDTHeader
var dsdtIndex int
for index, df := range dumpFiles {
dumpData, err := ioutil.ReadFile(df)
if err != nil {
t.Fatal(err)
}
header := (*table.SDTHeader)(unsafe.Pointer(&dumpData[0]))
tableName := string(header.Signature[:])
switch tableName {
case dsdtSignature, fadtSignature:
if tableName == dsdtSignature {
dsdt = header
dsdtIndex = index
} else {
fadt = header
}
}
tableList = append(tableList, header)
}
// Setup the pointer to the DSDT
if fadt != nil && dsdt != nil {
fadtHeader := (*table.FADT)(unsafe.Pointer(fadt))
if acpiVersion == acpiRev1 {
// Since the tests run in 64-bit mode these 32-bit addresses
// will be invalid and cause a page fault. So we cheat and
// encode the table index and page offset as the pointer.
// The test code will hook identityMapFn to reconstruct the
// correct pointer to the table contents.
offset := vmm.PageOffset(uintptr(unsafe.Pointer(dsdt)))
encodedTableLoc := (uintptr(dsdtIndex) << mem.PageShift) + offset
fadtHeader.Dsdt = uint32(encodedTableLoc)
} else {
fadtHeader.Ext.Dsdt = uint64(uintptr(unsafe.Pointer(dsdt)))
}
updateChecksum(fadt)
}
// Assemble the RDST
var (
sizeofSDTHeader = unsafe.Sizeof(table.SDTHeader{})
rsdtHeader *table.SDTHeader
)
switch acpiVersion {
case acpiRev1:
buf := make([]byte, int(sizeofSDTHeader)+4*len(tableList))
rsdtHeader = (*table.SDTHeader)(unsafe.Pointer(&buf[0]))
rsdtHeader.Signature = [4]byte{'R', 'S', 'D', 'T'}
rsdtHeader.Revision = acpiVersion
rsdtHeader.Length = uint32(sizeofSDTHeader)
// Since the tests run in 64-bit mode these 32-bit addresses
// will be invalid and cause a page fault. So we cheat and
// encode the table index and page offset as the pointer.
// The test code will hook identityMapFn to reconstruct the
// correct pointer to the table contents.
for index, tableHeader := range tableList {
offset := vmm.PageOffset(uintptr(unsafe.Pointer(tableHeader)))
encodedTableLoc := (uintptr(index) << mem.PageShift) + offset
*(*uint32)(unsafe.Pointer(&buf[rsdtHeader.Length])) = uint32(encodedTableLoc)
rsdtHeader.Length += 4
}
default:
buf := make([]byte, int(sizeofSDTHeader)+8*len(tableList))
rsdtHeader = (*table.SDTHeader)(unsafe.Pointer(&buf[0]))
rsdtHeader.Signature = [4]byte{'R', 'S', 'D', 'T'}
rsdtHeader.Revision = acpiVersion
rsdtHeader.Length = uint32(sizeofSDTHeader)
for _, tableHeader := range tableList {
// Do not include DSDT. This will be referenced via FADT
if string(tableHeader.Signature[:]) == dsdtSignature {
continue
}
*(*uint64)(unsafe.Pointer(&buf[rsdtHeader.Length])) = uint64(uintptr(unsafe.Pointer(tableHeader)))
rsdtHeader.Length += 8
}
}
updateChecksum(rsdtHeader)
return uintptr(unsafe.Pointer(rsdtHeader)), tableList
}
func updateChecksum(header *table.SDTHeader) {
header.Checksum = -calcChecksum(uintptr(unsafe.Pointer(header)), uintptr(header.Length))
}
func calcChecksum(tableAddr, length uintptr) uint8 {
var checksum uint8
for ptr := tableAddr; ptr < tableAddr+length; ptr++ {
checksum += *(*uint8)(unsafe.Pointer(ptr))
}
return checksum
}
func pkgDir() string {
_, f, _, _ := runtime.Caller(1)
return filepath.Dir(f)
}

View File

@ -0,0 +1,264 @@
package table
// Resolver is an interface implemented by objects that can lookup an ACPI table
// by its name.
//
// LookupTable attempts to locate a table by name returning back a pointer to
// its standard header or nil if the table could not be found. The resolver
// must make sure that the entire table contents are mapped so they can be
// accessed by the caller.
type Resolver interface {
LookupTable(string) *SDTHeader
}
// RSDPDescriptor defines the root system descriptor pointer for ACPI 1.0. This
// is used as the entry-point for parsing ACPI data.
type RSDPDescriptor struct {
// The signature must contain "RSD PTR " (last byte is a space).
Signature [8]byte
// A value that when added to the sum of all other bytes contained in
// this descriptor should result in the value 0.
Checksum uint8
OEMID [6]byte
// ACPI revision number. It is 0 for ACPI1.0 and 2 for versions 2.0 to 6.2.
Revision uint8
// Physical address of 32-bit root system descriptor table.
RSDTAddr uint32
}
// ExtRSDPDescriptor extends RSDPDescriptor with additional fields. It is used
// when RSDPDescriptor.revision > 1.
type ExtRSDPDescriptor struct {
RSDPDescriptor
// The size of the 64-bit root system descriptor table.
Length uint32
// Physical address of 64-bit root system descriptor table.
XSDTAddr uint64
// A value that when added to the sum of all other bytes contained in
// this descriptor should result in the value 0.
ExtendedChecksum uint8
reserved [3]byte
}
// SDTHeader defines the common header for all ACPI-related tables.
type SDTHeader struct {
// The signature defines the table type.
Signature [4]byte
// The length of the table
Length uint32
// If this header belongs to a DSDT/SSDT table, the revision is also
// used to indicate whether the AML VM should treat integers as 32-bits
// (revision < 2) or 64-bits (revision >= 2).
Revision uint8
// A value that when added to the sum of all other bytes in the table
// should result in the value 0.
Checksum uint8
// OEM specific information
OEMID [6]byte
OEMTableID [8]byte
OEMRevision uint32
// Information about the ASL compiler that generated this table
CreatorID uint32
CreatorRevision uint32
}
// AddressSpace defines the location where a set of registers resides.
type AddressSpace uint8
// The list of supported address space types.
const (
AddressSpaceSysMemory AddressSpace = iota
AddressSpaceSysIO
AddressSpacePCI
AddressSpaceEmbController
AddressSpaceSMBus
AddressSpaceFuncFixedHW = 0x7f
)
// GenericAddress specifies a register range located in a particular address
// space.
type GenericAddress struct {
Space AddressSpace
BitWidth uint8
BitOffset uint8
AccessSize uint8
Address uint64
}
// PowerProfileType describes a power profile referenced by the FADT table.
type PowerProfileType uint8
// The list of supported power profile types
const (
PowerProfileUnspecified PowerProfileType = iota
PowerProfileDesktop
PowerProfileMobile
PowerProfileWorkstation
PowerProfileEnterpriseServer
PowerProfileSOHOServer
PowerProfileAppliancePC
PowerProfilePerformanceServer
)
// FADT64 contains the 64-bit FADT extensions which are used by ACPI2+
type FADT64 struct {
FirmwareControl uint64
Dsdt uint64
PM1aEventBlock GenericAddress
PM1bEventBlock GenericAddress
PM1aControlBlock GenericAddress
PM1bControlBlock GenericAddress
PM2ControlBlock GenericAddress
PMTimerBlock GenericAddress
GPE0Block GenericAddress
GPE1Block GenericAddress
}
// FADT (Fixed ACPI Description Table) is an ACPI table containing information
// about fixed register blocks used for power management.
type FADT struct {
SDTHeader
FirmwareCtrl uint32
Dsdt uint32
reserved uint8
PreferredPowerManagementProfile PowerProfileType
SCIInterrupt uint16
SMICommandPort uint32
AcpiEnable uint8
AcpiDisable uint8
S4BIOSReq uint8
PSTATEControl uint8
PM1aEventBlock uint32
PM1bEventBlock uint32
PM1aControlBlock uint32
PM1bControlBlock uint32
PM2ControlBlock uint32
PMTimerBlock uint32
GPE0Block uint32
GPE1Block uint32
PM1EventLength uint8
PM1ControlLength uint8
PM2ControlLength uint8
PMTimerLength uint8
GPE0Length uint8
GPE1Length uint8
GPE1Base uint8
CStateControl uint8
WorstC2Latency uint16
WorstC3Latency uint16
FlushSize uint16
FlushStride uint16
DutyOffset uint8
DutyWidth uint8
DayAlarm uint8
MonthAlarm uint8
Century uint8
// Reserved in ACPI 1.0; used since ACPI 2.0+
BootArchitectureFlags uint16
reserved2 uint8
Flags uint32
ResetReg GenericAddress
ResetValue uint8
reserved3 [3]uint8
// 64-bit pointers to the above structures used by ACPI 2.0+
Ext FADT64
}
// MADT (Multiple APIC Description Table) is an ACPI table containing
// information about the interrupt controllers and the number of installed
// CPUs. Following the table header are a series of variable sized records
// (MADTEntry) which contain additional information.
type MADT struct {
SDTHeader
LocalControllerAddress uint32
Flags uint32
}
// MADTEntryLocalAPIC describes a single physical processor and its local
// interrupt controller.
type MADTEntryLocalAPIC struct {
ProcessorID uint8
APICID uint8
Flags uint32
}
// MADTEntryIOAPIC describes an I/O Advanced Programmable Interrupt Controller.
type MADTEntryIOAPIC struct {
APICID uint8
reserved uint8
// Address contains the address of the controller.
Address uint32
// SysInterruptBase defines the first interrupt number that this
// controller handles.
SysInterruptBase uint32
}
// MADTEntryInterruptSrcOverride contains the data for an Interrupt Source
// Override. This mechanism is used to map IRQ sources to global system
// interrupts.
type MADTEntryInterruptSrcOverride struct {
BusSrc uint8
IRQSrc uint8
GlobalInterrupt uint32
Flags uint16
}
// MADTEntryNMI describes a non-maskable interrupt that we need to set up for
// a single processor or all processors.
type MADTEntryNMI struct {
// Processor specifies the local APIC that we need to configure for
// this NMI. If set to 0xff we need to configure all processor APICs.
Processor uint8
Flags uint16
// This value will be either 0 or 1 and specifies which entry in the
// local vector table of the processor's local APIC we need to setup.
LINT uint8
}
// MADTEntryType describes the type of a MADT record.
type MADTEntryType uint8
// The list of supported MADT entry types.
const (
MADTEntryTypeLocalAPIC MADTEntryType = iota
MADTEntryTypeIOAPIC
MADTEntryTypeIntSrcOverride
MADTEntryTypeNMI
)
// MADTEntry describes a MADT table entry that follows the MADT definition. As
// MADT entries are variable sized records, this struct works as a union. The
// consumer of this struct must check the type value before accessing the union
// values.
type MADTEntry struct {
Type MADTEntryType
Length uint8
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -10,6 +10,9 @@ import (
"gopheros/kernel/hal/multiboot"
"gopheros/kernel/kfmt"
"sort"
// import and register acpi driver
_ "gopheros/device/acpi"
)
// managedDevices contains the devices discovered by the HAL.
@ -44,7 +47,7 @@ func DetectHardware() {
// probe executes the probe function for each driver and invokes
// onDriverInit for each successfully initialized driver.
func probe(driverInfoList device.DriverInfoList) {
var w = kfmt.PrefixWriter{Sink: kfmt.GetOutputSink()}
var w kfmt.PrefixWriter
for _, info := range driverInfoList {
drv := info.Probe()
@ -56,6 +59,7 @@ func probe(driverInfoList device.DriverInfoList) {
major, minor, patch := drv.DriverVersion()
kfmt.Fprintf(&strBuf, "[hal] %s(%d.%d.%d): ", drv.DriverName(), major, minor, patch)
w.Prefix = strBuf.Bytes()
w.Sink = kfmt.GetOutputSink()
if err := drv.DriverInit(&w); err != nil {
kfmt.Fprintf(&w, "init failed: %s\n", err.Message)

View File

@ -24,3 +24,12 @@ func (f Frame) Valid() bool {
func (f Frame) Address() uintptr {
return uintptr(f << mem.PageShift)
}
// FrameFromAddress returns a Frame that corresponds to
// the given physical address. This function can handle
// both page-aligned and not aligned addresses. in the
// latter case, the input address will be rounded down
// to the frame that contains it.
func FrameFromAddress(physAddr uintptr) Frame {
return Frame((physAddr & ^(uintptr(mem.PageSize - 1))) >> mem.PageShift)
}

View File

@ -23,3 +23,21 @@ func TestFrameMethods(t *testing.T) {
t.Error("expected InvalidFrame.Valid() to return false")
}
}
func TestFrameFromAddress(t *testing.T) {
specs := []struct {
input uintptr
expFrame Frame
}{
{0, Frame(0)},
{4095, Frame(0)},
{4096, Frame(1)},
{4123, Frame(1)},
}
for specIndex, spec := range specs {
if got := FrameFromAddress(spec.input); got != spec.expFrame {
t.Errorf("[spec %d] expected returned frame to be %v; got %v", specIndex, spec.expFrame, got)
}
}
}

View File

@ -130,6 +130,24 @@ func MapRegion(frame pmm.Frame, size mem.Size, flags PageTableEntryFlag) (Page,
return PageFromAddress(startPage), nil
}
// IdentityMapRegion establishes an identity mapping to the physical memory
// region which starts at the given frame and ends at frame + pages(size). The
// size argument is always rounded up to the nearest page boundary.
// IdentityMapRegion returns back the Page that corresponds to the region
// start.
func IdentityMapRegion(startFrame pmm.Frame, size mem.Size, flags PageTableEntryFlag) (Page, *kernel.Error) {
startPage := Page(startFrame)
pageCount := Page(((size + (mem.PageSize - 1)) & ^(mem.PageSize - 1)) >> mem.PageShift)
for curPage := startPage; curPage < startPage+pageCount; curPage++ {
if err := mapFn(curPage, pmm.Frame(curPage), flags); err != nil {
return 0, err
}
}
return startPage, nil
}
// MapTemporary establishes a temporary RW mapping of a physical memory frame
// to a fixed virtual address overwriting any previous mapping. The temporary
// mapping mechanism is primarily used by the kernel to access and initialize

View File

@ -164,6 +164,40 @@ func TestMapRegion(t *testing.T) {
})
}
func TestIdentityMapRegion(t *testing.T) {
defer func() {
mapFn = Map
}()
t.Run("success", func(t *testing.T) {
mapCallCount := 0
mapFn = func(_ Page, _ pmm.Frame, flags PageTableEntryFlag) *kernel.Error {
mapCallCount++
return nil
}
if _, err := IdentityMapRegion(pmm.Frame(0xdf0000), 4097, FlagPresent|FlagRW); err != nil {
t.Fatal(err)
}
if exp := 2; mapCallCount != exp {
t.Errorf("expected Map to be called %d time(s); got %d", exp, mapCallCount)
}
})
t.Run("Map fails", func(t *testing.T) {
expErr := &kernel.Error{Module: "test", Message: "map failed"}
mapFn = func(_ Page, _ pmm.Frame, flags PageTableEntryFlag) *kernel.Error {
return expErr
}
if _, err := IdentityMapRegion(pmm.Frame(0xdf0000), 128000, FlagPresent|FlagRW); err != expErr {
t.Fatalf("expected error: %v; got %v", expErr, err)
}
})
}
func TestMapTemporaryErrorsAmd64(t *testing.T) {
if runtime.GOARCH != "amd64" {
t.Skip("test requires amd64 runtime; skipping")

View File

@ -13,7 +13,12 @@ func Translate(virtAddr uintptr) (uintptr, *kernel.Error) {
// Calculate the physical address by taking the physical frame address and
// appending the offset from the virtual address
physAddr := pte.Frame().Address() + (virtAddr & ((1 << pageLevelShifts[pageLevels-1]) - 1))
physAddr := pte.Frame().Address() + PageOffset(virtAddr)
return physAddr, nil
}
// PageOffset returns the offset within the page specified by a virtual
// address.
func PageOffset(virtAddr uintptr) uintptr {
return (virtAddr & ((1 << pageLevelShifts[pageLevels-1]) - 1))
}