1 /*
2 * Copyright (c) 2021 Travis Geiseblrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8
9 #include "bridge.h"
10
11 #include <sys/types.h>
12 #include <lk/cpp.h>
13 #include <lk/debug.h>
14 #include <lk/err.h>
15 #include <lk/list.h>
16 #include <lk/trace.h>
17 #include <dev/bus/pci.h>
18 #include <inttypes.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <platform/interrupts.h>
24
25 #define LOCAL_TRACE 0
26
27 #include "bus_mgr.h"
28 #include "bridge.h"
29 #include "bus.h"
30 #include "resource.h"
31
32 namespace pci {
33
bridge(pci_location_t loc,bus * bus)34 bridge::bridge(pci_location_t loc, bus *bus) : device(loc, bus) {}
35 bridge::~bridge() = default;
36
37 // examine the bridge device, figuring out the bus range it controls and recurse
probe(pci_location_t loc,bus * parent_bus,bridge ** out_bridge)38 status_t bridge::probe(pci_location_t loc, bus *parent_bus, bridge **out_bridge) {
39 char str[14];
40 LTRACEF("%s\n", pci_loc_string(loc, str));
41
42 *out_bridge = nullptr;
43
44 // read vendor id and see if this is a real device
45 uint16_t vendor_id;
46 status_t err = pci_read_config_half(loc, PCI_CONFIG_VENDOR_ID, &vendor_id);
47 if (err != NO_ERROR) {
48 return ERR_NOT_FOUND;
49 }
50 if (vendor_id == 0xffff) {
51 return ERR_NOT_FOUND;
52 }
53
54 // read header type (0 or 1)
55 uint8_t header_type;
56 err = pci_read_config_byte(loc, PCI_CONFIG_HEADER_TYPE, &header_type);
57 if (err != NO_ERROR) {
58 return ERR_NOT_FOUND;
59 }
60
61 header_type &= PCI_HEADER_TYPE_MASK;
62
63 if (header_type != 1) {
64 LTRACEF("type %d header on bridge we don't understand, skipping\n", header_type);
65 return ERR_NOT_FOUND;
66 }
67
68 // we are a bridge to a new set of busses
69 bridge *br = new bridge(loc, parent_bus);
70
71 // we only grok type 1 headers here
72 err = br->load_config();
73 if (err < 0) {
74 delete br;
75 return err;
76 }
77
78 LTRACEF("primary bus %hhd secondary %hhd subordinate %hhd\n",
79 br->primary_bus(), br->secondary_bus(), br->subordinate_bus());
80
81 // probe the bridge's capabilities
82 br->probe_capabilities();
83
84 *out_bridge = br;
85
86 if (br->secondary_bus() == 0) {
87 // allocate a new secondary bus
88 uint8_t new_secondary_bus = allocate_next_bus();
89
90 // we do not yet know the range of busses we will find downstream, lower level bridges will have to
91 // back patch us as they find new children.
92 uint8_t new_subordinate_bus = new_secondary_bus;
93
94 LTRACEF("assigning secondary bus %d, parent bus %d\n", new_secondary_bus, parent_bus->bus_num());
95
96 br->assign_bus_numbers(parent_bus->bus_num(), new_secondary_bus, new_subordinate_bus);
97
98 // tell the parent bridge that we have a new range
99 auto *parent_bridge = parent_bus->get_bridge();
100 if (parent_bridge) {
101 parent_bridge->extend_subordinate_range(new_secondary_bus);
102 }
103 }
104
105 // sanity check that we don't have overlapping busses
106 if (br->secondary_bus() < get_last_bus()) {
107 TRACEF("secondary bus %u of bridge we've already seen (last bus seen %u)\n", br->secondary_bus(), get_last_bus());
108 delete br;
109 return ERR_NO_RESOURCES;
110 }
111
112 // start a scan of the secondary bus downstream of this.
113 // via bridge devices on this bus, should find all of the subordinate busses.
114 pci_location_t bus_location = {};
115 bus_location.segment = loc.segment;
116 bus_location.bus = br->secondary_bus();
117
118 bus *new_bus;
119 err = bus::probe(bus_location, br, &new_bus);
120 if (err != NO_ERROR) {
121 // TODO: don't leak bridge and/or bus
122 return err;
123 }
124
125 // add the bus to our list of children
126 DEBUG_ASSERT(new_bus);
127 br->add_bus(new_bus);
128
129 // add the bus to the global bus list
130 new_bus->add_to_global_list();
131
132 return NO_ERROR;
133 }
134
extend_subordinate_range(uint8_t new_secondary_bus)135 void bridge::extend_subordinate_range(uint8_t new_secondary_bus) {
136 LTRACEF("new_secondary_bus %u existing primary bus %hhd secondary %hhd subordinate %hhd\n",
137 new_secondary_bus, primary_bus(), secondary_bus(), subordinate_bus());
138
139 if (new_secondary_bus > subordinate_bus()) {
140 assign_bus_numbers(primary_bus(), secondary_bus(), new_secondary_bus);
141
142 DEBUG_ASSERT(subordinate_bus() == new_secondary_bus);
143
144 // tell the parent bridge that we have a new range
145 auto *parent_bridge = bus_->get_bridge();
146 if (parent_bridge) {
147 parent_bridge->extend_subordinate_range(new_secondary_bus);
148 }
149 }
150 }
151
assign_bus_numbers(uint8_t primary,uint8_t secondary,uint8_t subordinate)152 void bridge::assign_bus_numbers(uint8_t primary, uint8_t secondary, uint8_t subordinate) {
153 LTRACEF("primary %u secondary %u subordinate %u\n", primary, secondary, subordinate);
154
155 uint32_t temp;
156
157 pci_read_config_word(loc_, 0x18, &temp);
158 temp &= 0xff000000; // leave latency timer alone
159 temp |= subordinate << 16;
160 temp |= secondary << 8;
161 temp |= primary << 0;
162 pci_write_config_word(loc_, 0x18, temp);
163
164 // reread the config
165 load_config();
166 }
167
dump(size_t indent)168 void bridge::dump(size_t indent) {
169 auto scoot = [&]() {
170 for (size_t i = 0; i < indent; i++) {
171 printf(" ");
172 }
173
174 };
175 char str[14];
176 scoot();
177 printf("bridge %s %04hx:%04hx primary bus %d child busses [%d..%d]\n", pci_loc_string(loc_, str),
178 vendor_id(), device_id(), primary_bus(),
179 secondary_bus(), subordinate_bus());
180
181 auto mr = mem_range();
182 auto ir = io_range();
183 auto pr = prefetch_range();
184 scoot();
185 printf("mem_range [%#x..%#x] io_range [%#x..%#x] pref_range [%#" PRIx64 "..%#" PRIx64"] \n",
186 mr.base, mr.limit, ir.base, ir.limit, pr.base, pr.limit);
187
188 for (size_t b = 0; b < 2; b++) {
189 if (bars_[b].valid) {
190 for (size_t i = 0; i < indent + 1; i++) {
191 printf(" ");
192 }
193 printf("BAR %zu: addr %#" PRIx64 " size %#zx io %d valid %d\n", b, bars_[b].addr, bars_[b].size, bars_[b].io, bars_[b].valid);
194 }
195 }
196
197 if (secondary_bus_) {
198 secondary_bus_->dump(indent + 1);
199 }
200 }
201
202 // accessors to compute the io and memory range of the bridge
io_range()203 bridge::range<uint32_t> bridge::io_range() {
204 if (config_.type1.io_limit < config_.type1.io_base) {
205 return { 0, 0 };
206 } else {
207 // TODO: handle 32bit io (does this really exist?)
208 return { ((uint32_t)config_.type1.io_base >> 4) << 12,
209 (((uint32_t)config_.type1.io_limit >> 4) << 12) | 0xfff };
210 }
211 }
212
mem_range()213 bridge::range<uint32_t> bridge::mem_range() {
214 if (config_.type1.memory_limit < config_.type1.memory_base) {
215 return { 0, 0 };
216 } else {
217 return { ((uint32_t)config_.type1.memory_base >> 4) << 20,
218 (((uint32_t)config_.type1.memory_limit >> 4) << 20) | 0xfffff };
219 }
220 }
221
prefetch_range()222 bridge::range<uint64_t> bridge::prefetch_range() {
223 if (config_.type1.prefetchable_memory_limit < config_.type1.prefetchable_memory_base) {
224 return { 0, 0 };
225 } else {
226 bool is_64 = (config_.type1.prefetchable_memory_base & 0xf) == 1;
227
228 uint64_t base, limit;
229
230 base = (((uint64_t)config_.type1.prefetchable_memory_base >> 4) << 20);
231 if (is_64) {
232 base |= (uint64_t)config_.type1.prefetchable_base_upper << 32;
233 }
234 limit = (((uint64_t)config_.type1.prefetchable_memory_limit >> 4) << 20) | 0xfffff;
235 if (is_64) {
236 limit |= (uint64_t)config_.type1.prefetchable_limit_upper << 32;
237 }
238 return { base, limit };
239 }
240 }
241
compute_bar_sizes_no_local_bar(bar_sizes * bridge_sizes)242 status_t bridge::compute_bar_sizes_no_local_bar(bar_sizes *bridge_sizes) {
243 bar_sizes bus_sizes = {};
244
245 // drill into our bus and accumulate from there
246 auto perdev = [&](device *d) -> status_t {
247 device::bar_sizes sizes = {};
248
249 d->compute_bar_sizes(&sizes);
250
251 if (LOCAL_TRACE) {
252 char str[14];
253 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",
254 pci_loc_string(d->loc(), str), sizes.io_size, sizes.io_align, sizes.mmio_size, sizes.mmio_align,
255 sizes.mmio64_size, sizes.mmio64_align, sizes.prefetchable_size, sizes.prefetchable_align,
256 sizes.prefetchable64_size, sizes.prefetchable64_align);
257 }
258
259 // accumulate to the passed in size struct
260 bus_sizes += sizes;
261
262 return 0;
263 };
264
265 LTRACEF("recursing into bus %u\n", secondary_bus_->bus_num());
266 secondary_bus_->for_every_device(perdev);
267
268 // we've accumulated the size of the bus and everything below it
269 // round up some of the accumulated sizes based on limitations in the bridge
270
271 // bridges can only pass through io ports in blocks of 0x1000
272 if (bus_sizes.io_size > 0 && bus_sizes.io_align < 12) {
273 bus_sizes.io_align = 12;
274 }
275 bus_sizes.io_size = ROUNDUP(bus_sizes.io_size, 0x1000);
276
277 // mmio ranges can only pass through in units of 1MB (1<<20)
278 if (bus_sizes.mmio_size > 0 && bus_sizes.mmio_align < 20) {
279 bus_sizes.mmio_align = 20;
280 }
281 bus_sizes.mmio_size = ROUNDUP(bus_sizes.mmio_size, (1UL << 20));
282
283 // 64bit mmio ranges can't be passed through bridges, so merge with the mmio range
284 if (bus_sizes.mmio64_size > 0 && bus_sizes.mmio_align < bus_sizes.mmio64_align) {
285 bus_sizes.mmio_align = bus_sizes.mmio64_align;
286 }
287 bus_sizes.mmio_size += ROUNDUP(bus_sizes.mmio64_size, (1UL << 20));
288 bus_sizes.mmio64_align = 0;
289 bus_sizes.mmio64_size = 0;
290
291 // prefetchable ranges are sent through like anything else
292 if (bus_sizes.prefetchable_size > 0 && bus_sizes.prefetchable_align < 20) {
293 bus_sizes.prefetchable_align = 20;
294 }
295 bus_sizes.prefetchable_size = ROUNDUP(bus_sizes.prefetchable_size, (1UL << 20));
296
297 // 64bit prefetchable ranges are sent through like anything else
298 if (bus_sizes.prefetchable64_size > 0 && bus_sizes.prefetchable64_align < 20) {
299 bus_sizes.prefetchable64_align = 20;
300 }
301 bus_sizes.prefetchable64_size = ROUNDUP(bus_sizes.prefetchable64_size, (1UL << 20));
302
303 *bridge_sizes += bus_sizes;
304
305 return NO_ERROR;
306 }
307
compute_bar_sizes(bar_sizes * bridge_sizes)308 status_t bridge::compute_bar_sizes(bar_sizes *bridge_sizes) {
309 char str[14];
310 LTRACEF("bridge at %s\n", pci_loc_string(loc(), str));
311
312 // accumulate our BARs
313 device::compute_bar_sizes(bridge_sizes);
314
315 return compute_bar_sizes_no_local_bar(bridge_sizes);
316 }
317
get_bar_alloc_requests(list_node * bar_alloc_requests)318 status_t bridge::get_bar_alloc_requests(list_node *bar_alloc_requests) {
319 char str[14];
320 LTRACEF("bridge at %s\n", pci_loc_string(loc(), str));
321
322 // add our local bars to the list of requests
323 device::get_bar_alloc_requests(bar_alloc_requests);
324
325 // accumulate the size of our children
326 bar_sizes bus_sizes = {};
327 compute_bar_sizes_no_local_bar(&bus_sizes);
328
329 // TODO: test if bridge supports prefetchable and if so if it supports 64bit
330
331 // construct a request out of the different ranges returned
332 auto make_request = [this, &bar_alloc_requests](pci_resource_type type, bool prefetchable, uint64_t size, uint8_t align) {
333 if (size > 0) {
334 auto request = new bar_alloc_request;
335 *request = {};
336 request->dev = this;
337 request->bridge = true;
338 request->type = type;
339 request->prefetchable = prefetchable;
340 request->size = size;
341 request->align = align;
342
343 list_add_tail(bar_alloc_requests, &request->node);
344 }
345 };
346
347 make_request(PCI_RESOURCE_IO_RANGE, false, bus_sizes.io_size, bus_sizes.io_align);
348 make_request(PCI_RESOURCE_MMIO_RANGE, false, bus_sizes.mmio_size, bus_sizes.mmio_align);
349 make_request(PCI_RESOURCE_MMIO64_RANGE, false, bus_sizes.mmio64_size, bus_sizes.mmio64_align);
350
351
352 // TODO: merge prefetchable 64 and 32bit?
353 // should only be able to deal with one or the other
354 DEBUG_ASSERT(!(bus_sizes.prefetchable_size && bus_sizes.prefetchable64_size));
355 make_request(PCI_RESOURCE_MMIO_RANGE, true, bus_sizes.prefetchable_size, bus_sizes.prefetchable_align);
356 make_request(PCI_RESOURCE_MMIO64_RANGE, true, bus_sizes.prefetchable64_size, bus_sizes.prefetchable64_align);
357
358 return NO_ERROR;
359 }
360
assign_resource(bar_alloc_request * request,uint64_t address)361 status_t bridge::assign_resource(bar_alloc_request *request, uint64_t address) {
362 char str[14];
363 LTRACEF("bridge at %s resource addr %#" PRIx64 " request:\n", pci_loc_string(loc(), str), address);
364 if (LOCAL_TRACE) {
365 request->dump();
366 }
367
368 if (!request->bridge) {
369 // this is a request for one of our bars, pass it to the device base class virtual
370 return device::assign_resource(request, address);
371 }
372
373 DEBUG_ASSERT(IS_ALIGNED(address, (1UL << request->align)));
374
375 // this is an allocation for one of the bridge resources
376 uint32_t temp;
377 switch (request->type) {
378 case PCI_RESOURCE_IO_RANGE:
379 // write to the io configuration bits
380 DEBUG_ASSERT(IS_ALIGNED(address, (1UL << 12)));
381 temp = ((address >> 8) & 0xf0); // io base
382 temp |= (((address + request->size - 1) >> 8) & 0xf0) << 8;
383 pci_write_config_word(loc(), 0x1c, temp);
384 break;
385 case PCI_RESOURCE_MMIO_RANGE:
386 DEBUG_ASSERT(IS_ALIGNED(address, (1UL << 20)));
387 DEBUG_ASSERT(IS_ALIGNED(request->size, (1UL << 20)));
388 if (request->prefetchable) {
389 temp = ((address >> 16) & 0xfff0); // mmio base
390 temp |= ((address + request->size - 1) >> 16 & 0xfff0) << 16;
391 pci_write_config_word(loc(), 0x24, temp);
392 } else {
393 // non prefetchable mmio range
394 temp = ((address >> 16) & 0xfff0); // mmio base
395 temp |= ((address + request->size - 1) >> 16 & 0xfff0) << 16;
396 pci_write_config_word(loc(), 0x20, temp);
397 }
398 break;
399 case PCI_RESOURCE_MMIO64_RANGE:
400 if (!request->prefetchable) {
401 // cannot handle non prefetchable 64bit due to the way the bus works
402 printf("PCI bridge at %s: invalid 64bit non prefetchable range\n", pci_loc_string(loc(), str));
403 return ERR_NOT_SUPPORTED;
404 }
405 // assert that the device supports 64bit addresses
406 DEBUG_ASSERT((config_.type1.prefetchable_memory_base & 0xf) == 1);
407
408 DEBUG_ASSERT(IS_ALIGNED(address, (1UL << 20)));
409 DEBUG_ASSERT(IS_ALIGNED(request->size, (1UL << 20)));
410
411 // bottom part of prefetchable base and limit
412 temp = ((address >> 16) & 0xfff0); // mmio base
413 temp |= ((address + request->size - 1) >> 16 & 0xfff0) << 16;
414 pci_write_config_word(loc(), 0x24, temp);
415
416 // high part of base and limit
417 temp = (address >> 32);
418 pci_write_config_word(loc(), 0x28, temp);
419 temp = (address + request->size - 1) >> 32;
420 pci_write_config_word(loc(), 0x2c, temp);
421 break;
422 default:
423 PANIC_UNIMPLEMENTED;
424 }
425 load_config();
426
427 // PROBLEM: do we recurse here and start the bus allocation for the children with a restricted resource allocator
428 // but we have only a subset of resources to allocate. Or do we wait until the bridge is completely configured,
429 // and then trigger a recursive assignment on each sub bus (this is probably the right strategy).
430
431
432 return NO_ERROR;
433 }
434
assign_child_resources()435 status_t bridge::assign_child_resources() {
436 char str[14];
437 LTRACEF("bridge at %s\n", pci_loc_string(loc(), str));
438
439 // construct a resource allocator that covers what we've been assigned
440 resource_allocator allocator;
441
442 auto io = io_range();
443 if (io.limit > io.base) {
444 resource_range r;
445 r.base = io.base;
446 r.size = io.limit + io.base + 1;
447 r.type = PCI_RESOURCE_IO_RANGE;
448 allocator.set_range(r);
449 }
450
451 auto mmio = mem_range();
452 if (mmio.limit > mmio.base) {
453 resource_range r;
454 r.base = mmio.base;
455 r.size = mmio.limit - mmio.base + 1;
456 r.type = PCI_RESOURCE_MMIO_RANGE;
457 allocator.set_range(r);
458 }
459
460 auto pref = prefetch_range();
461 if (pref.limit > pref.base) {
462 resource_range r;
463 r.base = pref.base;
464 r.size = pref.limit - pref.base + 1;
465
466 // if the prefetch window is completely < 4GB, set it up as a 32bit mmio prefetchable
467 if (pref.base < (1ULL << 32) || (pref.base + pref.limit < (1ULL << 32))) {
468 r.type = PCI_RESOURCE_MMIO_RANGE;
469 } else {
470 r.type = PCI_RESOURCE_MMIO64_RANGE;
471 }
472
473 allocator.set_range(r, true);
474 }
475
476 // recurse into the secondary bus with the allocator we've set up for it
477 secondary_bus_->allocate_resources(allocator);
478
479 // enable the bridge
480 enable();
481
482 return NO_ERROR;
483 }
484
485
486 } // namespace pci
487