/** * PCI (Peripheral Component Interconnect) functionality. */ module hulk.pci; import hulk.cpu; import hulk.klog; import hulk.hurl; import hulk.hurl.a1; import hulk.range; import hulk.usb.xhci; import hulk.acpi; import hulk.list; struct Pci { /** IO address for PCI configuration address. */ enum IO_ADDR_CONFIG_ADDRESS = 0xCF8u; /** IO address for PCI configuration data. */ enum IO_ADDR_CONFIG_DATA = 0xCFCu; /** Maximum number of devices on a bus. */ enum MAX_DEVICES_PER_BUS = 32; /** Maximum number of functions on a devices. */ enum MAX_FUNCTIONS_PER_DEVICE = 8; /** XHCI controller device type. */ enum XHCI_CONTROLLER = Type(0x0Cu, 0x03u, 0x30u); /** * MCFG ACPI table structure. */ static struct MCFG { /* PCI Segment Group Memory Area Descriptor. */ static struct SGMADesc { uint[2] base_address_a; ushort pci_segment_group_number; ubyte start_pci_bus_number; ubyte end_pci_bus_number; uint _reserved; public @property ulong base_address() const { return base_address_a[0] | (cast(ulong)base_address_a[1] << 32); } } static assert(SGMADesc.sizeof == 16); static assert(SGMADesc.alignof == 4); Acpi.Header header; uint[2] _reserved; SGMADesc[0] sgma_descs; size_t n_sgma_descs() { return (header.length - MCFG.sgma_descs.offsetof) / MCFG.SGMADesc.sizeof; } } /** * PCI device address (bus number, device number, function number). */ struct Address { uint address; alias address this; this(ubyte bus_nr, ubyte device_nr, ubyte function_nr) { this.address = (bus_nr << 16u) | (device_nr << 8u) | function_nr; } /** * Bus number (0-255). */ public @property ubyte bus_nr() const { return cast(ubyte)(address >> 16u); } /** * Device number (0-31). */ public @property ubyte device_nr() const { return cast(ubyte)(address >> 8u); } /** * Function number (0-7). */ public @property ubyte function_nr() const { return cast(ubyte)address; } } /** * PCI device type (class ID, subclass ID, interface ID). */ struct Type { uint type; alias type this; this(ubyte class_id, ubyte subclass_id, ubyte interface_id) { this.type = (class_id << 16u) | (subclass_id << 8u) | interface_id; } /** * Class ID. */ public @property ubyte class_id() const { return cast(ubyte)(type >> 16u); } /** * Subclass ID. */ public @property ubyte subclass_id() const { return cast(ubyte)(type >> 8u); } /** * Interface ID. */ public @property ubyte interface_id() const { return cast(ubyte)type; } } /** * PCI device object. */ static struct Device { Configuration * config; Address address; Type type; bool multifunction; Range[6] memory_ranges; void initialize(Address address, Configuration * config) { this.config = config; this.address = address; type = Type(config.class_id, config.subclass_id, config.interface_id); Klog.writefln("Found PCI device %04x:%04x (%02x:%02x:%02x) at %02u:%02u.%u", config.vendor_id, config.device_id, type.class_id, type.subclass_id, type.interface_id, address.bus_nr, address.device_nr, address.function_nr); multifunction = (address.function_nr == 0u) && ((config.header_type & 0x80u) != 0u); ubyte header_type = config.header_type & 0x7Fu; if (header_type == 0u) { map_memory_regions(); } } private void map_memory_regions() { size_t range_index; uint[2] r; uint[2] s; for (uint bar_idx = 0u; bar_idx < config.header0.base_addresses.length; bar_idx++) { /* Read the BAR. */ r[0] = config.header0.base_addresses[bar_idx]; /* Skip zeroed BARs. */ if (r[0] == 0u) { continue; } /* Skip I/O BARs. */ if ((r[0] & 1u) != 0u) { continue; } bool is_64bit; uint bar_type = (r[0] >> 1u) & 0x3u; if (bar_type == 0u) { /* 32-bit region. */ r[1] = 0u; } else if ((bar_type == 2u) && (bar_idx < 5u)) { /* 64-bit region. */ is_64bit = true; r[1] = config.header0.base_addresses[bar_idx + 1]; } else { continue; } /* Determine the region length. */ config.header0.base_addresses[bar_idx] = 0xFFFF_FFFFu; s[0] = config.header0.base_addresses[bar_idx]; config.header0.base_addresses[bar_idx] = r[0]; if (is_64bit) { config.header0.base_addresses[bar_idx + 1] = 0xFFFF_FFFFu; s[1] = config.header0.base_addresses[bar_idx + 1]; config.header0.base_addresses[bar_idx + 1] = r[1]; } else { s[1] = 0xFFFF_FFFFu; } ulong mm_region_address = (cast(ulong)r[0] & 0xFFFF_FFF0u) | (cast(ulong)r[1] << 32u); ulong length = ~((cast(ulong)s[0] & 0xFFFF_FFF0u) | (cast(ulong)s[1] << 32u)) + 1u; ulong flags = (r[0] & 0x8) != 0u ? PT_WRITE_THROUGH : 0u; Hurl.identity_map_range(mm_region_address, length, flags); memory_ranges[range_index].address = mm_region_address; memory_ranges[range_index].length = length; range_index++; if (is_64bit) { bar_idx++; } } } } /** * Store a list of discovered PCI devices. */ public static __gshared List!Device devices; private static uint read_config_register(Address address, uint register_id) { uint cfg_addr = 0x8000_0000u | (address.bus_nr << 16u) | (address.device_nr << 11u) | (address.function_nr << 8u) | (register_id << 2u); out32(IO_ADDR_CONFIG_ADDRESS, cfg_addr); return in32(IO_ADDR_CONFIG_DATA); } private static void write_config_register(Address address, uint register_id, uint value) { uint cfg_addr = 0x8000_0000u | (address.bus_nr << 16u) | (address.device_nr << 11u) | (address.function_nr << 8u) | (register_id << 2u); out32(IO_ADDR_CONFIG_ADDRESS, cfg_addr); out32(IO_ADDR_CONFIG_DATA, value); } static struct Configuration { static struct HeaderType0 { ushort vendor_id; ushort device_id; ushort command; ushort status; ubyte revision_id; ubyte interface_id; ubyte subclass_id; ubyte class_id; ubyte cache_line_size; ubyte latency_timer; ubyte header_type; ubyte bist; uint[6] base_addresses; uint cardbus_cis_pointer; ushort subsystem_vendor_id; ushort subsystem_id; uint expansion_rom_base_address; ubyte capabilities_pointer; ubyte[7] _reserved; ubyte interrupt_line; ubyte interrupt_pin; ubyte min_grant; ubyte max_latency; } /** * PCI configuration format for header type 1 (PCI-to-PCI bridge). */ static struct HeaderType1 { ushort vendor_id; ushort device_id; ushort command; ushort status; ubyte revision_id; ubyte interface_id; ubyte subclass_id; ubyte class_id; ubyte cache_line_size; ubyte latency_timer; ubyte header_type; ubyte bist; uint[2] base_addresses; ubyte primary_bus_nr; ubyte secondary_bus_nr; ubyte subordinate_bus_nr; ubyte secondary_latency_timer; ubyte io_base; ubyte io_limit; ushort secondary_status; ushort memory_base; ushort memory_limit; ushort prefetchable_memory_base; ushort prefetchable_memory_limit; uint prefetchable_base_high; uint prefetchable_limit_high; ushort io_base_high; ushort io_limit_high; ubyte capability_pointer; ubyte[3] _reserved; uint expansion_rom_base_address; ubyte interrupt_line; ubyte interrupt_pin; ushort bridge_control; } /** * PCI configuration format for header type 2 (PCI-to-CardBus bridge). */ static struct HeaderType2 { ushort vendor_id; ushort device_id; ushort command; ushort status; ubyte revision_id; ubyte interface_id; ubyte subclass_id; ubyte class_id; ubyte cache_line_size; ubyte latency_timer; ubyte header_type; ubyte bist; uint cardbus_base_address; ubyte capability_list_offset; ubyte _reserved; ushort secondary_status; ubyte pci_bus_nr; ubyte cardbus_bus_nr; ubyte subordinate_bus_nr; ubyte cardbus_latency_timer; uint base_address_0; uint memory_limit_0; uint base_address_1; uint memory_limit_1; uint io_base_address_0; uint io_limit_0; uint io_base_address_1; uint io_limit_1; ubyte interrupt_line; ubyte interrupt_pin; ushort bridge_control; ushort subsystem_device_id; ushort subsystem_vendor_id; uint legacy_base_address; } union { struct { ushort vendor_id; ushort device_id; ushort command; ushort status; ubyte revision_id; ubyte interface_id; ubyte subclass_id; ubyte class_id; ubyte cache_line_size; ubyte latency_timer; ubyte header_type; ubyte bist; } HeaderType0 header0; HeaderType1 header1; HeaderType2 header2; } } private static void scan(ubyte bus_nr, ubyte device_nr, ubyte function_nr, ulong config_address) { Configuration * config = cast(Configuration *)config_address; if (config.vendor_id != 0xFFFFu) { Device * device = A1.allocate!Device(); devices.add(device); device.initialize(Address(bus_nr, device_nr, function_nr), config); if (device.multifunction) { for (function_nr = 1u; function_nr < MAX_FUNCTIONS_PER_DEVICE; function_nr++) { config_address += PAGE_SIZE; scan(bus_nr, device_nr, function_nr, config_address); } } } } public static void initialize() { Klog.writefln("\a3Initializing PCI"); MCFG * mcfg = cast(MCFG *)Acpi.get_table("MCFG"); size_t n_sgma_descs = mcfg.n_sgma_descs; for (size_t i = 0u; i < n_sgma_descs; i++) { MCFG.SGMADesc * sgma_desc = &mcfg.sgma_descs[i]; ulong base_address = sgma_desc.base_address; uint start_pci_bus = sgma_desc.start_pci_bus_number; uint end_pci_bus = sgma_desc.end_pci_bus_number; ulong n_buses = (end_pci_bus - start_pci_bus) + 1u; ulong map_length = PAGE_SIZE * MAX_FUNCTIONS_PER_DEVICE * MAX_DEVICES_PER_BUS * n_buses; Hurl.identity_map_range(base_address, map_length, PT_WRITABLE | PT_DISABLE_CACHE | PT_NO_EXECUTE); for (uint bus_nr = start_pci_bus; bus_nr <= end_pci_bus; bus_nr++) { for (ubyte device_nr = 0u; device_nr < MAX_DEVICES_PER_BUS; device_nr++) { ulong device_address = base_address + ((bus_nr - start_pci_bus) * (MAX_DEVICES_PER_BUS * MAX_FUNCTIONS_PER_DEVICE) + device_nr * MAX_FUNCTIONS_PER_DEVICE) * PAGE_SIZE; scan(cast(ubyte)bus_nr, device_nr, 0u, device_address); } } } } }