/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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 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