190 lines
5.2 KiB
C++
190 lines
5.2 KiB
C++
// SPDX-License-Identifier: ISC
|
|
|
|
#pragma once
|
|
|
|
#include <zinnia/alloc.h>
|
|
#include <zinnia/compiler.h>
|
|
#include <zinnia/errno.h>
|
|
#include <zinnia/initgraph.h>
|
|
#include <zinnia/print.h>
|
|
#include <zinnia/types.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
struct pci_address {
|
|
uint16_t domain;
|
|
uint8_t bus;
|
|
uint8_t device;
|
|
uint8_t function;
|
|
};
|
|
|
|
#define PCI_ADDR_FMT "%.4hx:%.2hhx:%.2hhx.%hhx"
|
|
#define PCI_ADDR_ARGS(a) (a)->domain, (a)->bus, (a)->device, (a)->function
|
|
|
|
struct pci_id {
|
|
uint16_t vendor_id;
|
|
uint16_t device_id;
|
|
uint16_t subvendor_id;
|
|
uint16_t subdevice_id;
|
|
};
|
|
|
|
struct pci_class {
|
|
uint8_t class;
|
|
uint8_t subclass;
|
|
uint8_t prog_if;
|
|
};
|
|
|
|
enum pci_cfg : uint16_t {
|
|
PCI_VENDOR_ID = 0x00,
|
|
PCI_DEVICE_ID = 0x02,
|
|
PCI_COMMAND = 0x04,
|
|
PCI_STATUS = 0x06,
|
|
PCI_PROG_IF = 0x09,
|
|
PCI_SUBCLASS = 0x0a,
|
|
PCI_CLASS = 0x0b,
|
|
PCI_HEADER_TYPE = 0x0e,
|
|
PCI_BAR0 = 0x10,
|
|
PCI_SECONDARY_BUS = 0x19,
|
|
PCI_SUBSYS_VENDOR_ID = 0x2c,
|
|
PCI_SUBSYS_ID = 0x2e,
|
|
PCI_CAP_PTR = 0x34,
|
|
PCI_INTERRUPT_LINE = 0x3c,
|
|
PCI_INTERRUPT_PIN = 0x3d,
|
|
};
|
|
|
|
enum pci_command : uint16_t {
|
|
PCI_COMMAND_IO = 1 << 0,
|
|
PCI_COMMAND_MEM = 1 << 1,
|
|
PCI_COMMAND_MASTER = 1 << 2,
|
|
PCI_COMMAND_INTX_DISABLE = 1 << 10,
|
|
};
|
|
|
|
constexpr uint16_t PCI_STATUS_CAP_LIST = 1 << 4;
|
|
|
|
constexpr uint8_t PCI_HEADER_TYPE_MASK = 0x7f;
|
|
constexpr uint8_t PCI_HEADER_TYPE_MULTIFUNCTION = 0x80;
|
|
constexpr uint8_t PCI_HEADER_TYPE_BRIDGE = 0x01;
|
|
|
|
constexpr size_t PCI_NUM_BARS = 6;
|
|
constexpr uint8_t PCI_NUM_SLOTS = 32;
|
|
constexpr uint8_t PCI_NUM_FUNCTIONS = 8;
|
|
|
|
enum pci_cap_id : uint8_t {
|
|
PCI_CAP_MSI = 0x05,
|
|
PCI_CAP_PCIE = 0x10,
|
|
PCI_CAP_MSIX = 0x11,
|
|
};
|
|
|
|
struct pci_access {
|
|
const char* name;
|
|
uint16_t domain;
|
|
uint8_t start_bus;
|
|
uint8_t end_bus;
|
|
uint8_t priority;
|
|
uint32_t (*read32)(struct pci_access* self, const struct pci_address* addr, uint16_t offset);
|
|
void (*write32)(struct pci_access* self, const struct pci_address* addr, uint16_t offset, uint32_t value);
|
|
};
|
|
|
|
enum pci_access_priority : uint8_t {
|
|
PCI_ACCESS_PRIO_LEGACY = 0,
|
|
PCI_ACCESS_PRIO_ECAM = 128,
|
|
};
|
|
|
|
static bool pci_access_decodes(const struct pci_access* access, const struct pci_address* addr);
|
|
|
|
errno_t pci_access_register(struct pci_access* access);
|
|
struct pci_access* pci_access_find(const struct pci_address* addr);
|
|
|
|
errno_t pci_ecam_register(phys_t base, uint16_t domain, uint8_t start_bus, uint8_t end_bus);
|
|
|
|
struct pci_driver;
|
|
|
|
struct pci_device {
|
|
struct pci_address address;
|
|
struct pci_id id;
|
|
struct pci_class class;
|
|
struct pci_access* access;
|
|
uint8_t header_type;
|
|
struct pci_driver* driver;
|
|
};
|
|
|
|
uint8_t pci_read8(struct pci_device* dev, uint16_t offset);
|
|
uint16_t pci_read16(struct pci_device* dev, uint16_t offset);
|
|
uint32_t pci_read32(struct pci_device* dev, uint16_t offset);
|
|
void pci_write8(struct pci_device* dev, uint16_t offset, uint8_t value);
|
|
void pci_write16(struct pci_device* dev, uint16_t offset, uint16_t value);
|
|
void pci_write32(struct pci_device* dev, uint16_t offset, uint32_t value);
|
|
|
|
enum pci_bar_type : uint8_t {
|
|
PCI_BAR_NONE,
|
|
PCI_BAR_MMIO32,
|
|
PCI_BAR_MMIO64,
|
|
PCI_BAR_IO,
|
|
};
|
|
|
|
struct pci_bar {
|
|
enum pci_bar_type type;
|
|
bool prefetchable;
|
|
phys_t address;
|
|
size_t size;
|
|
};
|
|
|
|
errno_t pci_bar_get(struct pci_device* dev, size_t index, struct pci_bar* out);
|
|
|
|
uint8_t pci_cap_next(struct pci_device* dev, uint8_t cur);
|
|
uint8_t pci_cap_find(struct pci_device* dev, uint8_t id);
|
|
|
|
constexpr uint16_t PCI_ANY_ID = 0xffff;
|
|
constexpr uint8_t PCI_ANY_CLASS = 0xff;
|
|
|
|
// Matches a device by ID and class.
|
|
struct pci_match {
|
|
uint16_t vendor_id;
|
|
uint16_t device_id;
|
|
uint8_t class;
|
|
uint8_t subclass;
|
|
uint8_t prog_if;
|
|
|
|
// Not used to match, but can be used to pass device-specific context data.
|
|
uintptr_t data;
|
|
};
|
|
|
|
#define PCI_MATCH_DEVICE(vendor, device) \
|
|
.vendor_id = (vendor), .device_id = (device), .class = PCI_ANY_CLASS, .subclass = PCI_ANY_CLASS, \
|
|
.prog_if = PCI_ANY_CLASS
|
|
|
|
#define PCI_MATCH_CLASS(c, sc, pi) \
|
|
.vendor_id = PCI_ANY_ID, .device_id = PCI_ANY_ID, .class = (c), .subclass = (sc), .prog_if = (pi)
|
|
|
|
// Writes the address to `buf`.
|
|
static void pci_device_name(const struct pci_device* dev, char buf[static 18]);
|
|
|
|
struct pci_driver {
|
|
const char* name;
|
|
|
|
// Match table, array of `num_matches` entries, checked sequentially.
|
|
// When writing a match table, the most specific matches should come first, then more generic cases.
|
|
const struct pci_match* match;
|
|
const size_t num_matches;
|
|
|
|
errno_t (*probe)(struct pci_device* dev, const struct pci_match* match);
|
|
};
|
|
|
|
errno_t pci_driver_register(struct pci_driver* driver);
|
|
|
|
INIT_EXTERN(pci_stage);
|
|
|
|
#ifdef __MODULE_NAME
|
|
#define PCI_LOG_NAME __MODULE_NAME
|
|
#else
|
|
#define PCI_LOG_NAME "pci"
|
|
#endif
|
|
|
|
#define pci_log_level(level, dev, message, ...) \
|
|
log_write(level, PCI_LOG_NAME " " PCI_ADDR_FMT ": " message, PCI_ADDR_ARGS(&(dev)->address), ##__VA_ARGS__)
|
|
|
|
#define pci_log_debug(dev, message, ...) pci_log_level(LOG_DEBUG, dev, message, ##__VA_ARGS__)
|
|
#define pci_log(dev, message, ...) pci_log_level(LOG_INFO, dev, message, ##__VA_ARGS__)
|
|
#define pci_log_warn(dev, message, ...) pci_log_level(LOG_WARN, dev, message, ##__VA_ARGS__)
|
|
#define pci_log_err(dev, message, ...) pci_log_level(LOG_ERR, dev, message, ##__VA_ARGS__)
|