Files
lk/dev/bus/pci/bus_mgr/bridge.cpp
Travis Geiselbrecht 72112c0676 [pci] little bit of code cleanup in the pci bus driver
Mostly just suggestions by clang-tidy. No functional change.
2025-09-23 23:16:55 -07:00

487 lines
16 KiB
C++

/*
* Copyright (c) 2021 Travis Geiseblrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include "bridge.h"
#include <sys/types.h>
#include <lk/cpp.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/list.h>
#include <lk/trace.h>
#include <dev/bus/pci.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <platform/interrupts.h>
#define LOCAL_TRACE 0
#include "bus_mgr.h"
#include "bridge.h"
#include "bus.h"
#include "resource.h"
namespace pci {
bridge::bridge(pci_location_t loc, bus *bus) : device(loc, bus) {}
bridge::~bridge() = default;
// examine the bridge device, figuring out the bus range it controls and recurse
status_t bridge::probe(pci_location_t loc, bus *parent_bus, bridge **out_bridge) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));
*out_bridge = nullptr;
// read vendor id and see if this is a real device
uint16_t vendor_id;
status_t err = pci_read_config_half(loc, PCI_CONFIG_VENDOR_ID, &vendor_id);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
if (vendor_id == 0xffff) {
return ERR_NOT_FOUND;
}
// read header type (0 or 1)
uint8_t header_type;
err = pci_read_config_byte(loc, PCI_CONFIG_HEADER_TYPE, &header_type);
if (err != NO_ERROR) {
return ERR_NOT_FOUND;
}
header_type &= PCI_HEADER_TYPE_MASK;
if (header_type != 1) {
LTRACEF("type %d header on bridge we don't understand, skipping\n", header_type);
return ERR_NOT_FOUND;
}
// we are a bridge to a new set of busses
bridge *br = new bridge(loc, parent_bus);
// we only grok type 1 headers here
err = br->load_config();
if (err < 0) {
delete br;
return err;
}
LTRACEF("primary bus %hhd secondary %hhd subordinate %hhd\n",
br->primary_bus(), br->secondary_bus(), br->subordinate_bus());
// probe the bridge's capabilities
br->probe_capabilities();
*out_bridge = br;
if (br->secondary_bus() == 0) {
// allocate a new secondary bus
uint8_t new_secondary_bus = allocate_next_bus();
// we do not yet know the range of busses we will find downstream, lower level bridges will have to
// back patch us as they find new children.
uint8_t new_subordinate_bus = new_secondary_bus;
LTRACEF("assigning secondary bus %d, parent bus %d\n", new_secondary_bus, parent_bus->bus_num());
br->assign_bus_numbers(parent_bus->bus_num(), new_secondary_bus, new_subordinate_bus);
// tell the parent bridge that we have a new range
auto *parent_bridge = parent_bus->get_bridge();
if (parent_bridge) {
parent_bridge->extend_subordinate_range(new_secondary_bus);
}
}
// sanity check that we don't have overlapping busses
if (br->secondary_bus() < get_last_bus()) {
TRACEF("secondary bus %u of bridge we've already seen (last bus seen %u)\n", br->secondary_bus(), get_last_bus());
delete br;
return ERR_NO_RESOURCES;
}
// start a scan of the secondary bus downstream of this.
// via bridge devices on this bus, should find all of the subordinate busses.
pci_location_t bus_location = {};
bus_location.segment = loc.segment;
bus_location.bus = br->secondary_bus();
bus *new_bus;
err = bus::probe(bus_location, br, &new_bus);
if (err != NO_ERROR) {
// TODO: don't leak bridge and/or bus
return err;
}
// add the bus to our list of children
DEBUG_ASSERT(new_bus);
br->add_bus(new_bus);
// add the bus to the global bus list
new_bus->add_to_global_list();
return NO_ERROR;
}
void bridge::extend_subordinate_range(uint8_t new_secondary_bus) {
LTRACEF("new_secondary_bus %u existing primary bus %hhd secondary %hhd subordinate %hhd\n",
new_secondary_bus, primary_bus(), secondary_bus(), subordinate_bus());
if (new_secondary_bus > subordinate_bus()) {
assign_bus_numbers(primary_bus(), secondary_bus(), new_secondary_bus);
DEBUG_ASSERT(subordinate_bus() == new_secondary_bus);
// tell the parent bridge that we have a new range
auto *parent_bridge = parent_bus()->get_bridge();
if (parent_bridge) {
parent_bridge->extend_subordinate_range(new_secondary_bus);
}
}
}
void bridge::assign_bus_numbers(uint8_t primary, uint8_t secondary, uint8_t subordinate) {
LTRACEF("primary %u secondary %u subordinate %u\n", primary, secondary, subordinate);
uint32_t temp;
pci_read_config_word(loc(), 0x18, &temp);
temp &= 0xff000000; // leave latency timer alone
temp |= subordinate << 16;
temp |= secondary << 8;
temp |= primary << 0;
pci_write_config_word(loc(), 0x18, temp);
// reread the config
load_config();
}
void bridge::dump(size_t indent) {
auto scoot = [&]() {
for (size_t i = 0; i < indent; i++) {
printf(" ");
}
};
char str[14];
scoot();
printf("bridge %s %04hx:%04hx primary bus %d child busses [%d..%d]\n", pci_loc_string(loc(), str),
vendor_id(), device_id(), primary_bus(),
secondary_bus(), subordinate_bus());
auto mr = mem_range();
auto ir = io_range();
auto pr = prefetch_range();
scoot();
printf("mem_range [%#x..%#x] io_range [%#x..%#x] pref_range [%#" PRIx64 "..%#" PRIx64"] \n",
mr.base, mr.limit, ir.base, ir.limit, pr.base, pr.limit);
for (size_t b = 0; b < 2; b++) {
if (bar(b).valid) {
for (size_t i = 0; i < indent + 1; i++) {
printf(" ");
}
printf("BAR %zu: addr %#" PRIx64 " size %#zx io %d valid %d\n", b, bar(b).addr, bar(b).size, bar(b).io, bar(b).valid);
}
}
if (secondary_bus_) {
secondary_bus_->dump(indent + 1);
}
}
// accessors to compute the io and memory range of the bridge
bridge::range<uint32_t> bridge::io_range() {
if (config().type1.io_limit < config().type1.io_base) {
return { 0, 0 };
} else {
// TODO: handle 32bit io (does this really exist?)
return { ((uint32_t)config().type1.io_base >> 4) << 12,
(((uint32_t)config().type1.io_limit >> 4) << 12) | 0xfff };
}
}
bridge::range<uint32_t> bridge::mem_range() {
if (config().type1.memory_limit < config().type1.memory_base) {
return { 0, 0 };
} else {
return { ((uint32_t)config().type1.memory_base >> 4) << 20,
(((uint32_t)config().type1.memory_limit >> 4) << 20) | 0xfffff };
}
}
bridge::range<uint64_t> bridge::prefetch_range() {
if (config().type1.prefetchable_memory_limit < config().type1.prefetchable_memory_base) {
return { 0, 0 };
} else {
bool is_64 = (config().type1.prefetchable_memory_base & 0xf) == 1;
uint64_t base, limit;
base = (((uint64_t)config().type1.prefetchable_memory_base >> 4) << 20);
if (is_64) {
base |= (uint64_t)config().type1.prefetchable_base_upper << 32;
}
limit = (((uint64_t)config().type1.prefetchable_memory_limit >> 4) << 20) | 0xfffff;
if (is_64) {
limit |= (uint64_t)config().type1.prefetchable_limit_upper << 32;
}
return { base, limit };
}
}
status_t bridge::compute_bar_sizes_no_local_bar(bar_sizes *bridge_sizes) {
bar_sizes bus_sizes = {};
// drill into our bus and accumulate from there
auto perdev = [&](device *d) -> status_t {
device::bar_sizes sizes = {};
d->compute_bar_sizes(&sizes);
if (LOCAL_TRACE) {
char str[14];
printf("bar sizes for device %s: io %#x align %u mmio %#x align %u mmio64 %#" PRIx64 " align %u prefetch %#" PRIx64 " align %u prefetch64 %#" PRIx64 " align %u\n",
pci_loc_string(d->loc(), str), sizes.io_size, sizes.io_align, sizes.mmio_size, sizes.mmio_align,
sizes.mmio64_size, sizes.mmio64_align, sizes.prefetchable_size, sizes.prefetchable_align,
sizes.prefetchable64_size, sizes.prefetchable64_align);
}
// accumulate to the passed in size struct
bus_sizes += sizes;
return 0;
};
LTRACEF("recursing into bus %u\n", secondary_bus_->bus_num());
secondary_bus_->for_every_device(perdev);
// we've accumulated the size of the bus and everything below it
// round up some of the accumulated sizes based on limitations in the bridge
// bridges can only pass through io ports in blocks of 0x1000
if (bus_sizes.io_size > 0 && bus_sizes.io_align < 12) {
bus_sizes.io_align = 12;
}
bus_sizes.io_size = ROUNDUP(bus_sizes.io_size, 0x1000);
// mmio ranges can only pass through in units of 1MB (1<<20)
if (bus_sizes.mmio_size > 0 && bus_sizes.mmio_align < 20) {
bus_sizes.mmio_align = 20;
}
bus_sizes.mmio_size = ROUNDUP(bus_sizes.mmio_size, (1UL << 20));
// 64bit mmio ranges can't be passed through bridges, so merge with the mmio range
if (bus_sizes.mmio64_size > 0 && bus_sizes.mmio_align < bus_sizes.mmio64_align) {
bus_sizes.mmio_align = bus_sizes.mmio64_align;
}
bus_sizes.mmio_size += ROUNDUP(bus_sizes.mmio64_size, (1UL << 20));
bus_sizes.mmio64_align = 0;
bus_sizes.mmio64_size = 0;
// prefetchable ranges are sent through like anything else
if (bus_sizes.prefetchable_size > 0 && bus_sizes.prefetchable_align < 20) {
bus_sizes.prefetchable_align = 20;
}
bus_sizes.prefetchable_size = ROUNDUP(bus_sizes.prefetchable_size, (1UL << 20));
// 64bit prefetchable ranges are sent through like anything else
if (bus_sizes.prefetchable64_size > 0 && bus_sizes.prefetchable64_align < 20) {
bus_sizes.prefetchable64_align = 20;
}
bus_sizes.prefetchable64_size = ROUNDUP(bus_sizes.prefetchable64_size, (1UL << 20));
*bridge_sizes += bus_sizes;
return NO_ERROR;
}
status_t bridge::compute_bar_sizes(bar_sizes *bridge_sizes) {
char str[14];
LTRACEF("bridge at %s\n", pci_loc_string(loc(), str));
// accumulate our BARs
device::compute_bar_sizes(bridge_sizes);
return compute_bar_sizes_no_local_bar(bridge_sizes);
}
status_t bridge::get_bar_alloc_requests(list_node *bar_alloc_requests) {
char str[14];
LTRACEF("bridge at %s\n", pci_loc_string(loc(), str));
// add our local bars to the list of requests
device::get_bar_alloc_requests(bar_alloc_requests);
// accumulate the size of our children
bar_sizes bus_sizes = {};
compute_bar_sizes_no_local_bar(&bus_sizes);
// TODO: test if bridge supports prefetchable and if so if it supports 64bit
// construct a request out of the different ranges returned
auto make_request = [this, &bar_alloc_requests](pci_resource_type type, bool prefetchable, uint64_t size, uint8_t align) {
if (size > 0) {
auto request = new bar_alloc_request;
*request = {};
request->dev = this;
request->bridge = true;
request->type = type;
request->prefetchable = prefetchable;
request->size = size;
request->align = align;
list_add_tail(bar_alloc_requests, &request->node);
}
};
make_request(PCI_RESOURCE_IO_RANGE, false, bus_sizes.io_size, bus_sizes.io_align);
make_request(PCI_RESOURCE_MMIO_RANGE, false, bus_sizes.mmio_size, bus_sizes.mmio_align);
make_request(PCI_RESOURCE_MMIO64_RANGE, false, bus_sizes.mmio64_size, bus_sizes.mmio64_align);
// TODO: merge prefetchable 64 and 32bit?
// should only be able to deal with one or the other
DEBUG_ASSERT(!(bus_sizes.prefetchable_size && bus_sizes.prefetchable64_size));
make_request(PCI_RESOURCE_MMIO_RANGE, true, bus_sizes.prefetchable_size, bus_sizes.prefetchable_align);
make_request(PCI_RESOURCE_MMIO64_RANGE, true, bus_sizes.prefetchable64_size, bus_sizes.prefetchable64_align);
return NO_ERROR;
}
status_t bridge::assign_resource(bar_alloc_request *request, uint64_t address) {
char str[14];
LTRACEF("bridge at %s resource addr %#" PRIx64 " request:\n", pci_loc_string(loc(), str), address);
if (LOCAL_TRACE) {
request->dump();
}
if (!request->bridge) {
// this is a request for one of our bars, pass it to the device base class virtual
return device::assign_resource(request, address);
}
DEBUG_ASSERT(IS_ALIGNED(address, (1UL << request->align)));
// this is an allocation for one of the bridge resources
uint32_t temp;
switch (request->type) {
case PCI_RESOURCE_IO_RANGE:
// write to the io configuration bits
DEBUG_ASSERT(IS_ALIGNED(address, (1UL << 12)));
temp = ((address >> 8) & 0xf0); // io base
temp |= (((address + request->size - 1) >> 8) & 0xf0) << 8;
pci_write_config_word(loc(), 0x1c, temp);
break;
case PCI_RESOURCE_MMIO_RANGE:
DEBUG_ASSERT(IS_ALIGNED(address, (1UL << 20)));
DEBUG_ASSERT(IS_ALIGNED(request->size, (1UL << 20)));
if (request->prefetchable) {
temp = ((address >> 16) & 0xfff0); // mmio base
temp |= ((address + request->size - 1) >> 16 & 0xfff0) << 16;
pci_write_config_word(loc(), 0x24, temp);
} else {
// non prefetchable mmio range
temp = ((address >> 16) & 0xfff0); // mmio base
temp |= ((address + request->size - 1) >> 16 & 0xfff0) << 16;
pci_write_config_word(loc(), 0x20, temp);
}
break;
case PCI_RESOURCE_MMIO64_RANGE:
if (!request->prefetchable) {
// cannot handle non prefetchable 64bit due to the way the bus works
printf("PCI bridge at %s: invalid 64bit non prefetchable range\n", pci_loc_string(loc(), str));
return ERR_NOT_SUPPORTED;
}
// assert that the device supports 64bit addresses
DEBUG_ASSERT((config().type1.prefetchable_memory_base & 0xf) == 1);
DEBUG_ASSERT(IS_ALIGNED(address, (1UL << 20)));
DEBUG_ASSERT(IS_ALIGNED(request->size, (1UL << 20)));
// bottom part of prefetchable base and limit
temp = ((address >> 16) & 0xfff0); // mmio base
temp |= ((address + request->size - 1) >> 16 & 0xfff0) << 16;
pci_write_config_word(loc(), 0x24, temp);
// high part of base and limit
temp = (address >> 32);
pci_write_config_word(loc(), 0x28, temp);
temp = (address + request->size - 1) >> 32;
pci_write_config_word(loc(), 0x2c, temp);
break;
default:
PANIC_UNIMPLEMENTED;
}
load_config();
// PROBLEM: do we recurse here and start the bus allocation for the children with a restricted resource allocator
// but we have only a subset of resources to allocate. Or do we wait until the bridge is completely configured,
// and then trigger a recursive assignment on each sub bus (this is probably the right strategy).
return NO_ERROR;
}
status_t bridge::assign_child_resources() {
char str[14];
LTRACEF("bridge at %s\n", pci_loc_string(loc(), str));
// construct a resource allocator that covers what we've been assigned
resource_allocator allocator;
auto io = io_range();
if (io.limit > io.base) {
resource_range r;
r.base = io.base;
r.size = io.limit + io.base + 1;
r.type = PCI_RESOURCE_IO_RANGE;
allocator.set_range(r);
}
auto mmio = mem_range();
if (mmio.limit > mmio.base) {
resource_range r;
r.base = mmio.base;
r.size = mmio.limit - mmio.base + 1;
r.type = PCI_RESOURCE_MMIO_RANGE;
allocator.set_range(r);
}
auto pref = prefetch_range();
if (pref.limit > pref.base) {
resource_range r;
r.base = pref.base;
r.size = pref.limit - pref.base + 1;
// if the prefetch window is completely < 4GB, set it up as a 32bit mmio prefetchable
if (pref.base < (1ULL << 32) || (pref.base + pref.limit < (1ULL << 32))) {
r.type = PCI_RESOURCE_MMIO_RANGE;
} else {
r.type = PCI_RESOURCE_MMIO64_RANGE;
}
allocator.set_range(r, true);
}
// recurse into the secondary bus with the allocator we've set up for it
secondary_bus_->allocate_resources(allocator);
// enable the bridge
enable();
return NO_ERROR;
}
} // namespace pci