diff --git a/src/gopheros/device/acpi/acpi.go b/src/gopheros/device/acpi/acpi.go new file mode 100644 index 0000000..5042bd0 --- /dev/null +++ b/src/gopheros/device/acpi/acpi.go @@ -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, + }) +} diff --git a/src/gopheros/device/acpi/acpi_test.go b/src/gopheros/device/acpi/acpi_test.go new file mode 100644 index 0000000..de235d3 --- /dev/null +++ b/src/gopheros/device/acpi/acpi_test.go @@ -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) +} diff --git a/src/gopheros/device/acpi/table/tables.go b/src/gopheros/device/acpi/table/tables.go new file mode 100644 index 0000000..915a12d --- /dev/null +++ b/src/gopheros/device/acpi/table/tables.go @@ -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 +} diff --git a/src/gopheros/device/acpi/table/tabletest/APIC.aml b/src/gopheros/device/acpi/table/tabletest/APIC.aml new file mode 100644 index 0000000..a7a3661 Binary files /dev/null and b/src/gopheros/device/acpi/table/tabletest/APIC.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/DSDT.aml b/src/gopheros/device/acpi/table/tabletest/DSDT.aml new file mode 100644 index 0000000..dda2995 Binary files /dev/null and b/src/gopheros/device/acpi/table/tabletest/DSDT.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/FACP.aml b/src/gopheros/device/acpi/table/tabletest/FACP.aml new file mode 100644 index 0000000..82d0a7d Binary files /dev/null and b/src/gopheros/device/acpi/table/tabletest/FACP.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/SSDT.aml b/src/gopheros/device/acpi/table/tabletest/SSDT.aml new file mode 100644 index 0000000..e659e09 Binary files /dev/null and b/src/gopheros/device/acpi/table/tabletest/SSDT.aml differ diff --git a/src/gopheros/kernel/hal/hal.go b/src/gopheros/kernel/hal/hal.go index 713759f..02cbce9 100644 --- a/src/gopheros/kernel/hal/hal.go +++ b/src/gopheros/kernel/hal/hal.go @@ -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) diff --git a/src/gopheros/kernel/mem/pmm/frame.go b/src/gopheros/kernel/mem/pmm/frame.go index 10d5df7..0189013 100644 --- a/src/gopheros/kernel/mem/pmm/frame.go +++ b/src/gopheros/kernel/mem/pmm/frame.go @@ -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) +} diff --git a/src/gopheros/kernel/mem/pmm/frame_test.go b/src/gopheros/kernel/mem/pmm/frame_test.go index f05968f..59c1988 100644 --- a/src/gopheros/kernel/mem/pmm/frame_test.go +++ b/src/gopheros/kernel/mem/pmm/frame_test.go @@ -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) + } + } +} diff --git a/src/gopheros/kernel/mem/vmm/map.go b/src/gopheros/kernel/mem/vmm/map.go index fe56f3b..ddc4b80 100644 --- a/src/gopheros/kernel/mem/vmm/map.go +++ b/src/gopheros/kernel/mem/vmm/map.go @@ -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 diff --git a/src/gopheros/kernel/mem/vmm/map_test.go b/src/gopheros/kernel/mem/vmm/map_test.go index 7a0d0f8..e5a68e9 100644 --- a/src/gopheros/kernel/mem/vmm/map_test.go +++ b/src/gopheros/kernel/mem/vmm/map_test.go @@ -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") diff --git a/src/gopheros/kernel/mem/vmm/translate.go b/src/gopheros/kernel/mem/vmm/translate.go index 4493f32..e1f07cf 100644 --- a/src/gopheros/kernel/mem/vmm/translate.go +++ b/src/gopheros/kernel/mem/vmm/translate.go @@ -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)) +}