Files
StarForth/src/starkernel/pci/pci.c
T

394 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pci.c — Generic PCI/PCIe subsystem for StarKernel
*
* Config space strategy:
* ARCH_AMD64 : Type 1 port I/O (0xCF8 / 0xCFC). No ECAM mapping needed
* for config access; ECAM is still parsed for completeness
* but port I/O is always the primary path on amd64.
* ARCH_AARCH64/ARCH_RISCV64 : ECAM via ACPI MCFG. UEFI identity-maps
* the window so no additional VMM call is required.
*
* BAR MMIO strategy:
* ARCH_AMD64 : vmm_map_range(phys, phys, size, WR|CD) before first access.
* others : no-op; the UEFI identity map covers PCI MMIO windows.
*
* Only bus 0 is scanned (sufficient for all QEMU virtio devices).
*/
#ifndef __STARKERNEL__
#error "pci.c is kernel-only"
#endif
#include <stddef.h>
#include <stdint.h>
#include "starkernel/pci.h"
#include "console.h"
#ifdef ARCH_AMD64
#include "vmm.h"
#endif
/* -------------------------------------------------------------------------
* ACPI structures for MCFG / ECAM discovery
* ------------------------------------------------------------------------- */
typedef struct {
uint8_t signature[8];
uint8_t checksum;
uint8_t oem_id[6];
uint8_t revision;
uint32_t rsdt_address;
uint32_t length;
uint64_t xsdt_address;
uint8_t extended_checksum;
uint8_t reserved[3];
} __attribute__((packed)) Rsdp2;
typedef struct {
uint8_t signature[4];
uint32_t length;
uint8_t revision;
uint8_t checksum;
uint8_t oem_id[6];
uint8_t oem_table_id[8];
uint32_t oem_revision;
uint32_t creator_id;
uint32_t creator_revision;
} __attribute__((packed)) AcpiHeader;
typedef struct {
uint64_t base_address;
uint16_t segment_group;
uint8_t start_bus;
uint8_t end_bus;
uint32_t reserved;
} __attribute__((packed)) McfgEntry;
typedef struct {
AcpiHeader hdr;
uint64_t reserved;
/* McfgEntry[] follows */
} __attribute__((packed)) McfgTable;
/* -------------------------------------------------------------------------
* Module state
* ------------------------------------------------------------------------- */
static uint64_t g_ecam_base = 0;
/* -------------------------------------------------------------------------
* Port I/O helpers (amd64 only)
* ------------------------------------------------------------------------- */
#ifdef ARCH_AMD64
static inline void sk_outl(uint16_t port, uint32_t val) {
__asm__ volatile("outl %0, %1" : : "a"(val), "Nd"(port) : "memory");
}
static inline uint32_t sk_inl(uint16_t port) {
uint32_t val;
__asm__ volatile("inl %1, %0" : "=a"(val) : "Nd"(port) : "memory");
return val;
}
static inline void sk_outw(uint16_t port, uint16_t val) {
__asm__ volatile("outw %0, %1" : : "a"(val), "Nd"(port) : "memory");
}
static inline uint16_t sk_inw(uint16_t port) {
uint16_t val;
__asm__ volatile("inw %1, %0" : "=a"(val) : "Nd"(port) : "memory");
return val;
}
static inline void sk_outb(uint16_t port, uint8_t val) {
__asm__ volatile("outb %0, %1" : : "a"(val), "Nd"(port) : "memory");
}
static inline uint8_t sk_inb(uint16_t port) {
uint8_t val;
__asm__ volatile("inb %1, %0" : "=a"(val) : "Nd"(port) : "memory");
return val;
}
#define PCI_CF8 ((uint16_t)0x0CF8)
#define PCI_CFC ((uint16_t)0x0CFC)
static uint32_t cfg_addr(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
return 0x80000000u | ((uint32_t)bus << 16) | ((uint32_t)slot << 11) |
((uint32_t)fn << 8) | (off & 0xFCu);
}
static uint32_t portio_read32(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
sk_outl(PCI_CF8, cfg_addr(bus, slot, fn, off));
return sk_inl(PCI_CFC);
}
static uint16_t portio_read16(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
sk_outl(PCI_CF8, cfg_addr(bus, slot, fn, off));
return sk_inw((uint16_t)(PCI_CFC + (off & 2u)));
}
static uint8_t portio_read8(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
sk_outl(PCI_CF8, cfg_addr(bus, slot, fn, off));
return sk_inb((uint16_t)(PCI_CFC + (off & 3u)));
}
static void portio_write32(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint32_t v) {
sk_outl(PCI_CF8, cfg_addr(bus, slot, fn, off));
sk_outl(PCI_CFC, v);
}
static void portio_write16(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint16_t v) {
sk_outl(PCI_CF8, cfg_addr(bus, slot, fn, off));
sk_outw((uint16_t)(PCI_CFC + (off & 2u)), v);
}
static void portio_write8(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint8_t v) {
sk_outl(PCI_CF8, cfg_addr(bus, slot, fn, off));
sk_outb((uint16_t)(PCI_CFC + (off & 3u)), v);
}
#endif /* ARCH_AMD64 */
/* -------------------------------------------------------------------------
* ECAM helpers (aarch64 / riscv64 only — amd64 uses port I/O)
* ------------------------------------------------------------------------- */
#ifndef ARCH_AMD64
static volatile uint32_t *ecam_cfg_ptr(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
uint64_t addr = g_ecam_base +
((uint64_t)bus << 20) +
((uint64_t)slot << 15) +
((uint64_t)fn << 12) +
(off & 0xFFCu);
return (volatile uint32_t *)(uintptr_t)addr;
}
static uint32_t ecam_read32(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
return *ecam_cfg_ptr(bus, slot, fn, off);
}
static uint16_t ecam_read16(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
uint32_t dw = *ecam_cfg_ptr(bus, slot, fn, off & ~2u);
return (uint16_t)(dw >> ((off & 2u) * 8u));
}
static uint8_t ecam_read8(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
uint32_t dw = *ecam_cfg_ptr(bus, slot, fn, off & ~3u);
return (uint8_t)(dw >> ((off & 3u) * 8u));
}
static void ecam_write32(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint32_t v) {
*ecam_cfg_ptr(bus, slot, fn, off) = v;
}
static void ecam_write16(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint16_t v) {
volatile uint32_t *p = ecam_cfg_ptr(bus, slot, fn, off & ~2u);
uint32_t dw = *p;
uint32_t shift = (off & 2u) * 8u;
dw = (dw & ~(0xFFFFu << shift)) | ((uint32_t)v << shift);
*p = dw;
}
static void ecam_write8(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint8_t v) {
volatile uint32_t *p = ecam_cfg_ptr(bus, slot, fn, off & ~3u);
uint32_t dw = *p;
uint32_t shift = (off & 3u) * 8u;
dw = (dw & ~(0xFFu << shift)) | ((uint32_t)v << shift);
*p = dw;
}
#endif /* !ARCH_AMD64 */
/* -------------------------------------------------------------------------
* Config space dispatch (portio on amd64, ecam on others)
* ------------------------------------------------------------------------- */
static uint32_t cfg_read32(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
#ifdef ARCH_AMD64
return portio_read32(bus, slot, fn, off);
#else
return ecam_read32(bus, slot, fn, off);
#endif
}
static uint16_t cfg_read16(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
#ifdef ARCH_AMD64
return portio_read16(bus, slot, fn, off);
#else
return ecam_read16(bus, slot, fn, off);
#endif
}
static uint8_t cfg_read8(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off) {
#ifdef ARCH_AMD64
return portio_read8(bus, slot, fn, off);
#else
return ecam_read8(bus, slot, fn, off);
#endif
}
static void cfg_write32(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint32_t v) {
#ifdef ARCH_AMD64
portio_write32(bus, slot, fn, off, v);
#else
ecam_write32(bus, slot, fn, off, v);
#endif
}
static void cfg_write16(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint16_t v) {
#ifdef ARCH_AMD64
portio_write16(bus, slot, fn, off, v);
#else
ecam_write16(bus, slot, fn, off, v);
#endif
}
static void cfg_write8(uint8_t bus, uint8_t slot, uint8_t fn, uint16_t off, uint8_t v) {
#ifdef ARCH_AMD64
portio_write8(bus, slot, fn, off, v);
#else
ecam_write8(bus, slot, fn, off, v);
#endif
}
/* -------------------------------------------------------------------------
* ACPI MCFG parsing
* ------------------------------------------------------------------------- */
static int sig4_eq(const uint8_t *p, const char *s) {
return p[0] == (uint8_t)s[0] && p[1] == (uint8_t)s[1] &&
p[2] == (uint8_t)s[2] && p[3] == (uint8_t)s[3];
}
static int sig8_eq(const uint8_t *p, const char *s) {
int i;
for (i = 0; i < 8; i++)
if (p[i] != (uint8_t)s[i]) return 0;
return 1;
}
static int parse_mcfg(void *rsdp) {
if (!rsdp) return -1;
Rsdp2 *r = (Rsdp2 *)rsdp;
if (!sig8_eq(r->signature, "RSD PTR ")) return -1;
if (r->revision < 2 || r->xsdt_address == 0) return -1;
AcpiHeader *xsdt = (AcpiHeader *)(uintptr_t)r->xsdt_address;
if (!sig4_eq(xsdt->signature, "XSDT")) return -1;
uint32_t hdr_len = xsdt->length;
if (hdr_len <= 36u) return -1;
uint32_t n_entries = (hdr_len - 36u) / 8u;
uint64_t *entries = (uint64_t *)((uint8_t *)xsdt + 36u);
uint32_t i;
for (i = 0; i < n_entries; i++) {
AcpiHeader *sdt = (AcpiHeader *)(uintptr_t)entries[i];
if (!sig4_eq(sdt->signature, "MCFG")) continue;
McfgTable *mcfg = (McfgTable *)sdt;
uint32_t mcfg_len = mcfg->hdr.length;
if (mcfg_len <= 44u) return -1;
uint32_t n_alloc = (mcfg_len - 44u) / 16u;
McfgEntry *ents = (McfgEntry *)((uint8_t *)mcfg + 44u);
uint32_t j;
for (j = 0; j < n_alloc; j++) {
if (ents[j].segment_group == 0) {
g_ecam_base = ents[j].base_address;
return 0;
}
}
return -1;
}
return -1;
}
/* -------------------------------------------------------------------------
* Public API
* ------------------------------------------------------------------------- */
int pci_init(void *rsdp) {
int rc = parse_mcfg(rsdp);
#if defined(ARCH_AARCH64) || defined(ARCH_RISCV64)
if (rc != 0) {
#ifdef ARCH_RISCV64
g_ecam_base = 0x30000000ULL;
console_println("PCI: MCFG miss; using riscv64 fallback ECAM 0x30000000");
rc = 0;
#else
console_println("PCI: MCFG parse failed on aarch64 — no ECAM");
#endif
}
#endif
#ifdef ARCH_AMD64
if (rc != 0) {
console_println("PCI: MCFG parse failed; using port I/O (amd64 default)");
rc = 0;
} else {
/* Map the ECAM window (256 buses × 1 MB = 256 MB) on amd64 so the
* VMM page tables cover it. */
if (g_ecam_base) {
vmm_map_range(g_ecam_base, g_ecam_base, 0x10000000ULL,
VMM_FLAG_WRITABLE | VMM_FLAG_CACHE_DISABLE);
}
console_println("PCI: ECAM mapped (amd64)");
}
#endif
return rc;
}
int pci_find_first(uint16_t vendor_id, uint16_t device_id, PciDevice *out) {
uint8_t slot;
for (slot = 0; slot < 32; slot++) {
uint32_t vid_did = cfg_read32(0, slot, 0, PCI_CFG_VENDOR_ID);
if ((vid_did & 0xFFFFu) == 0xFFFFu) continue;
if ((uint16_t)(vid_did & 0xFFFFu) == vendor_id &&
(uint16_t)(vid_did >> 16) == device_id) {
if (out) {
out->bus = 0;
out->device = slot;
out->function = 0;
out->vendor_id = vendor_id;
out->device_id = device_id;
}
return 0;
}
}
return -1;
}
uint32_t pci_read32(const PciDevice *d, uint16_t off) {
return cfg_read32(d->bus, d->device, d->function, off);
}
uint16_t pci_read16(const PciDevice *d, uint16_t off) {
return cfg_read16(d->bus, d->device, d->function, off);
}
uint8_t pci_read8(const PciDevice *d, uint16_t off) {
return cfg_read8(d->bus, d->device, d->function, off);
}
void pci_write32(const PciDevice *d, uint16_t off, uint32_t v) {
cfg_write32(d->bus, d->device, d->function, off, v);
}
void pci_write16(const PciDevice *d, uint16_t off, uint16_t v) {
cfg_write16(d->bus, d->device, d->function, off, v);
}
void pci_write8(const PciDevice *d, uint16_t off, uint8_t v) {
cfg_write8(d->bus, d->device, d->function, off, v);
}
void pci_enable(const PciDevice *d) {
uint16_t cmd = pci_read16(d, PCI_CFG_COMMAND);
cmd |= (uint16_t)(PCI_CMD_IO_SPACE | PCI_CMD_MEM_SPACE | PCI_CMD_BUS_MASTER);
pci_write16(d, PCI_CFG_COMMAND, cmd);
}
uint64_t pci_bar(const PciDevice *d, int bar_idx) {
uint16_t off = (uint16_t)(PCI_CFG_BAR0 + bar_idx * 4);
uint32_t lo = pci_read32(d, off);
if (lo & PCI_BAR_TYPE_MASK) return 0; /* I/O BAR — unsupported */
if ((lo & 0x6u) == PCI_BAR_MEM_64) {
uint32_t hi = pci_read32(d, (uint16_t)(off + 4));
return ((uint64_t)hi << 32) | (lo & ~0xFu);
}
return lo & ~0xFu;
}
int pci_map_bar(uint64_t phys_addr, uint64_t size) {
#ifdef ARCH_AMD64
if (!phys_addr || !size) return -1;
/* Round size up to page boundary */
uint64_t aligned_size = (size + 0xFFFu) & ~0xFFFu;
return vmm_map_range(phys_addr, phys_addr, aligned_size,
VMM_FLAG_WRITABLE | VMM_FLAG_CACHE_DISABLE);
#else
(void)phys_addr; (void)size;
return 0;
#endif
}