443 lines
13 KiB
D
443 lines
13 KiB
D
/**
|
|
* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|