README: rewritten around what runs (Linux software, Xorg, apk, Linux's drivers), the honest gaps, measured numbers. plan/: 1.0.md is the finish line, where each clause of the acceptance test stands, and the register (one failing clause: a root that survives a reboot); built plans (foundations, hybrid, alpine, xorg) and the decided-against one (wayland) are gone, their content lives in architecture/; roadmap.md is only what is still open. guides: building/layout/testing/adding-a-driver (Linux code may be used). history: four entries (ls -lR, TLB shootdowns, MDIO 0x02460246, netlink MSG_TRUNC). Website: cards, stack, download page, docs index. Code comments that pointed at deleted plan docs point at the architecture docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
182 lines
8 KiB
C
182 lines
8 KiB
C
#pragma once
|
|
|
|
// PCI bus enumeration and driver binding.
|
|
//
|
|
// Until this existed, every driver in SchOS found its hardware by knowing
|
|
// where it was: IDE at 0x1F0 because IDE has always been at 0x1F0, the
|
|
// framebuffer wherever the BIOS's VBE call said. That works for exactly the
|
|
// devices that predate PCI and for nothing else. A NIC, an AHCI controller,
|
|
// a virtio device or a real GPU has no fixed address at all -- you find it by
|
|
// asking the bus what is plugged into it, which is what this does.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config space
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define PCI_CONFIG_ADDR 0xCF8
|
|
#define PCI_CONFIG_DATA 0xCFC
|
|
|
|
#define PCI_VENDOR_ID 0x00
|
|
#define PCI_DEVICE_ID 0x02
|
|
#define PCI_COMMAND 0x04
|
|
#define PCI_STATUS 0x06
|
|
#define PCI_REVISION 0x08
|
|
#define PCI_PROG_IF 0x09
|
|
#define PCI_SUBCLASS 0x0A
|
|
#define PCI_CLASS 0x0B
|
|
#define PCI_HEADER_TYPE 0x0E
|
|
#define PCI_BAR0 0x10
|
|
#define PCI_SECONDARY_BUS 0x19
|
|
#define PCI_INTERRUPT_LINE 0x3C
|
|
#define PCI_INTERRUPT_PIN 0x3D
|
|
|
|
// PCI_COMMAND bits a driver usually wants set before touching its device.
|
|
#define PCI_CMD_IO 0x0001
|
|
#define PCI_CMD_MEMORY 0x0002
|
|
#define PCI_CMD_BUS_MASTER 0x0004
|
|
|
|
// Base classes worth naming. The full list is long and mostly irrelevant here.
|
|
#define PCI_CLASS_STORAGE 0x01
|
|
#define PCI_CLASS_NETWORK 0x02
|
|
#define PCI_CLASS_DISPLAY 0x03
|
|
#define PCI_CLASS_MULTIMEDIA 0x04
|
|
#define PCI_CLASS_BRIDGE 0x06
|
|
#define PCI_CLASS_SERIAL_BUS 0x0C
|
|
|
|
#define PCI_STATUS_CAPLIST 0x0010 // device has a capability list
|
|
#define PCI_CAP_PTR 0x34 // offset of the first capability
|
|
|
|
#define PCI_CAP_MSI 0x05
|
|
#define PCI_CAP_MSIX 0x11
|
|
#define PCI_CAP_VENDOR 0x09 // virtio's configuration lives here
|
|
#define PCI_CAP_PCIE 0x10
|
|
|
|
#define PCI_ANY 0xFFFF // wildcard in a match table
|
|
|
|
// Devices we will remember. A laptop has 30 to 60 functions, a desktop with
|
|
// a couple of cards and Thunderbolt more than 64, and every one past the limit
|
|
// was ignored -- including whatever disk or NIC came last in bus order.
|
|
#define NPCI 256
|
|
|
|
struct pci_bar {
|
|
uint64 base; // already masked; 0 means "no BAR here"
|
|
uint64 size; // determined by write-ones-and-read-back
|
|
int is_io; // I/O port range rather than memory
|
|
int is_64; // consumed the following BAR slot too
|
|
int prefetch;
|
|
};
|
|
|
|
struct pci_dev {
|
|
uint8 bus, slot, func;
|
|
uint16 vendor, device;
|
|
uint8 class, subclass, prog_if, revision;
|
|
uint8 irq; // interrupt line, 0xFF when unrouted
|
|
// How that line is signalled, an IRQ_* mode from irq.h. PCI's level/low
|
|
// unless ACPI's routing says otherwise -- see kernel/acpi/aml.c.
|
|
uint8 irq_mode;
|
|
uint8 header_type;
|
|
struct pci_bar bar[6];
|
|
const char *driver; // name of whatever claimed it, or 0
|
|
// The driver itself, so it can be asked to release the device. The name
|
|
// alone cannot be called.
|
|
const struct pci_driver *drv;
|
|
// How pci_irq_attach() connected the interrupt: PCI_IRQ_INTX, _MSI or
|
|
// _MSIX, and on which line. See kernel/dev/pci.c.
|
|
uint8 irq_kind;
|
|
int irq_line;
|
|
};
|
|
|
|
#define PCI_IRQ_NONE 0
|
|
#define PCI_IRQ_INTX 1
|
|
#define PCI_IRQ_MSI 2
|
|
#define PCI_IRQ_MSIX 3
|
|
|
|
// pci_irq_attach() flags. MSI is always tried first; MSI-X only when the
|
|
// driver says it can take it, because some parts change how they report
|
|
// interrupt causes once MSI-X is on (the 82574's IVAR, for one).
|
|
#define PCI_IRQF_MSIX 0x1
|
|
|
|
// One row of a driver's match table. Terminate the table with a row whose
|
|
// vendor is 0.
|
|
struct pci_match {
|
|
uint16 vendor, device; // PCI_ANY to ignore
|
|
uint8 class, subclass; // used only when vendor is PCI_ANY
|
|
};
|
|
|
|
// Declared here rather than defs.h because both take a struct pci_dev, which
|
|
// only this header defines. See kernel/dev/device.c for the model they feed.
|
|
// device.h is included rather than forward-declaring `enum dev_class`, which
|
|
// is a compiler extension and warns.
|
|
#include "dev/device.h"
|
|
|
|
enum dev_class pci_class_of(const struct pci_dev *);
|
|
|
|
// Connect a device's interrupt to a handler: an MSI vector if the device has
|
|
// one, MSI-X if allowed and it has that instead, and its INTx line otherwise.
|
|
// Returns the line, or -1 if there is no way to interrupt at all -- which is
|
|
// not fatal; a driver with a poll path should say so and carry on.
|
|
#include "core/irq.h"
|
|
int pci_irq_attach(struct pci_dev *, irq_handler_t, void *arg,
|
|
const char *name, int flags);
|
|
void pci_irq_detach(struct pci_dev *, irq_handler_t, void *arg);
|
|
const char *pci_irq_kind(const struct pci_dev *);
|
|
void pci_describe(const struct pci_dev *, char *out, int cap);
|
|
|
|
struct pci_driver {
|
|
const char *name;
|
|
// Base for the device's name in /proc/devices, if the driver's own name
|
|
// makes a bad one. `e1000` plus an index reads as "e10000"; a NIC wants to
|
|
// be `eth0`. Optional -- null means use `name`.
|
|
const char *devname;
|
|
const struct pci_match *match;
|
|
// Class-specific vtable, published on the device when this driver claims
|
|
// it -- struct display_ops for a display, and so on. Optional; a driver
|
|
// with nothing to offer a consumer leaves it null.
|
|
//
|
|
// It lives on the driver rather than being set inside probe() because
|
|
// pci_bind() registers the device *after* probe returns, so a driver has
|
|
// nothing to attach it to at the point it would want to.
|
|
const void *ops;
|
|
// Return 0 to claim the device. A driver that returns non-zero leaves it
|
|
// unclaimed for anything else that matches.
|
|
int (*probe)(struct pci_dev *);
|
|
// Called right after the device is published, for a driver that supports
|
|
// more than one instance and needs to say which of its own is which.
|
|
// Optional -- a single-instance driver has nothing to attach.
|
|
//
|
|
// It exists because probe() runs *before* device_add(), so a driver has
|
|
// nowhere to put its per-instance pointer at the point it would want to.
|
|
// Without this the second card of the same model is either refused or
|
|
// silently shares the first one's registers.
|
|
void (*attached)(struct pci_dev *, struct device *);
|
|
// Give the device up, so a userspace driver can take it.
|
|
//
|
|
// docs/architecture/devices.md is built on the kernel *handing over* a device
|
|
// rather than a process wrestling it away: two drivers writing one set of
|
|
// registers is not a subtle failure, it is hardware that appears to have
|
|
// started lying. So the transfer is the incumbent's decision, and this is
|
|
// where it is made.
|
|
//
|
|
// A driver that implements this must leave the device in a state the next
|
|
// one can start from, and must stop touching it. A driver that does not
|
|
// implement it keeps its device: refusing is always safe, and for anything
|
|
// on the path to a shell it is also correct.
|
|
int (*release)(struct pci_dev *);
|
|
// Put one last frame on a device this driver was told to let go of.
|
|
//
|
|
// Called from panic(), and only from there: interrupts are off, no other
|
|
// CPU is coming back, and the process that was driving the device will not
|
|
// run again. The framebuffer is one the userspace driver registered with
|
|
// PCIIOC_PANICFB and the kernel has already painted the report into; all
|
|
// that is left is to make the device show it.
|
|
//
|
|
// The contract is what panic context allows: **no allocation, no locks, no
|
|
// sleeping.** Whatever memory this needs has to already exist, because the
|
|
// allocator may be exactly what was being held when the machine died. A
|
|
// driver that cannot meet that leaves this null, and the screen it handed
|
|
// over stays frozen -- which is what happens today and is not a
|
|
// regression.
|
|
//
|
|
// Returns 0 if something was put on screen.
|
|
int (*panic_present)(struct pci_dev *, uint64 fb_paddr, int w, int h);
|
|
};
|