Disable IO and mem decoding around BAR enumation as described in the PCI Local Bus specification. This behavior should be safer when messing around BARs for BAR lengths.
591 lines
18 KiB
C++
591 lines
18 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 "device.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 <lk/pow2.h>
|
|
#include <dev/bus/pci.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"
|
|
|
|
namespace pci {
|
|
|
|
device::device(pci_location_t loc, bus *bus) : loc_(loc), bus_(bus) {}
|
|
|
|
device::~device() {
|
|
LTRACE;
|
|
|
|
capability *cap;
|
|
while ((cap = list_remove_head_type(&capability_list_, capability, node))) {
|
|
delete cap;
|
|
}
|
|
}
|
|
|
|
// probe the device, return a new device
|
|
status_t device::probe(pci_location_t loc, bus *parent_bus, device **out_device) {
|
|
status_t err;
|
|
|
|
*out_device = nullptr;
|
|
|
|
// read vendor id and make sure this
|
|
uint16_t vendor_id;
|
|
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 base and sub class
|
|
uint8_t base_class;
|
|
err = pci_read_config_byte(loc, PCI_CONFIG_CLASS_CODE_BASE, &base_class);
|
|
if (err != NO_ERROR) {
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
uint8_t sub_class;
|
|
err = pci_read_config_byte(loc, PCI_CONFIG_CLASS_CODE_SUB, &sub_class);
|
|
if (err != NO_ERROR) {
|
|
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 != 0) {
|
|
LTRACEF("type %d header on device we don't understand, skipping\n", header_type);
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
|
|
// if it's a bridge, we should not have been called
|
|
if (base_class == 0x6) { // XXX replace with #define
|
|
// bridge
|
|
if (sub_class == 0x4) { // PCI-PCI bridge, normal decode
|
|
LTRACEF("found bridge, error\n");
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
LTRACEF_LEVEL(2, "type %#hhx\n", header_type);
|
|
|
|
// create a new device and pass it up
|
|
device *d = new device(loc, parent_bus);
|
|
|
|
// try to read in the basic config space for this device
|
|
err = d->load_config();
|
|
if (err < 0) {
|
|
delete d;
|
|
return err;
|
|
}
|
|
|
|
// save a copy of the BARs
|
|
d->load_bars();
|
|
|
|
// probe the device's capabilities
|
|
d->probe_capabilities();
|
|
|
|
// return the newly constructed device
|
|
*out_device = d;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void device::dump(size_t indent) {
|
|
for (size_t i = 0; i < indent; i++) {
|
|
printf(" ");
|
|
}
|
|
char str[14];
|
|
printf("dev %s vid:pid %04hx:%04hx base:sub:intr %hhu:%hhu:%hhu %s%s\n",
|
|
pci_loc_string(loc_, str), config_.vendor_id, config_.device_id,
|
|
base_class(), sub_class(), interface(),
|
|
has_msi() ? "msi " : "",
|
|
has_msix() ? "msix " : "");
|
|
for (size_t b = 0; b < countof(bars_); b++) {
|
|
if (bars_[b].valid) {
|
|
for (size_t i = 0; i < indent + 1; i++) {
|
|
printf(" ");
|
|
}
|
|
pci_dump_bar(bars_ + b, b);
|
|
}
|
|
}
|
|
}
|
|
|
|
status_t device::enable() {
|
|
char str[14];
|
|
LTRACEF("%s\n", pci_loc_string(loc(), str));
|
|
|
|
uint16_t command;
|
|
status_t err = pci_read_config_half(loc_, PCI_CONFIG_COMMAND, &command);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
command |= PCI_COMMAND_IO_EN | PCI_COMMAND_MEM_EN | PCI_COMMAND_BUS_MASTER_EN;
|
|
err = pci_write_config_half(loc_, PCI_CONFIG_COMMAND, command);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// walk the device's capability list, reading them in and creating sub objects per
|
|
status_t device::probe_capabilities() {
|
|
char str[14];
|
|
LTRACEF("%s\n", pci_loc_string(loc(), str));
|
|
|
|
// does this device have any capabilities?
|
|
if ((config_.status & PCI_STATUS_NEW_CAPS) == 0) {
|
|
// no capabilities, just move on
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t err;
|
|
size_t cap_ptr = config_.type0.capabilities_ptr; // type 0 and 1 are at same offset
|
|
for (;;) {
|
|
if (cap_ptr == 0) {
|
|
break;
|
|
}
|
|
|
|
// read the capability id
|
|
uint8_t cap_id;
|
|
err = pci_read_config_byte(loc(), cap_ptr, &cap_id);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
|
|
LTRACEF("cap id %#x at offset %#zx\n", cap_id, cap_ptr);
|
|
|
|
// we only handle a few kinds of capabilities at the moment
|
|
capability *cap = new capability;
|
|
cap->id = cap_id;
|
|
cap->config_offset = cap_ptr;
|
|
|
|
// add the cap to our list
|
|
if (cap) {
|
|
list_add_tail(&capability_list_, &cap->node);
|
|
}
|
|
|
|
switch (cap_id) {
|
|
case 0x5: { // MSI
|
|
LTRACEF("MSI\n");
|
|
if (init_msi_capability(cap) == NO_ERROR) {
|
|
msi_cap_ = cap;
|
|
}
|
|
break;
|
|
}
|
|
case 0x11: { // MSI-X
|
|
LTRACEF("MSI-X\n");
|
|
if (init_msix_capability(cap) == NO_ERROR) {
|
|
msix_cap_ = cap;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// read the next pointer
|
|
uint8_t next_cap_ptr;
|
|
err = pci_read_config_byte(loc(), cap_ptr + 1, &next_cap_ptr);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
|
|
cap_ptr = next_cap_ptr;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::init_msi_capability(capability *cap) {
|
|
LTRACE_ENTRY;
|
|
|
|
DEBUG_ASSERT(cap->id == 0x5);
|
|
|
|
// plain MSI
|
|
uint32_t cap_buf[6];
|
|
pci_read_config_word(loc(), cap->config_offset, &cap_buf[0]);
|
|
pci_read_config_word(loc(), cap->config_offset + 4, &cap_buf[1]);
|
|
pci_read_config_word(loc(), cap->config_offset + 8, &cap_buf[2]);
|
|
pci_read_config_word(loc(), cap->config_offset + 12, &cap_buf[3]);
|
|
pci_read_config_word(loc(), cap->config_offset + 16, &cap_buf[4]);
|
|
pci_read_config_word(loc(), cap->config_offset + 20, &cap_buf[5]);
|
|
//hexdump(cap_buf, sizeof(cap_buf));
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::init_msix_capability(capability *cap) {
|
|
LTRACE_ENTRY;
|
|
|
|
DEBUG_ASSERT(cap->id == 0x11);
|
|
|
|
// MSI-X
|
|
uint32_t cap_buf[3];
|
|
pci_read_config_word(loc(), cap->config_offset, &cap_buf[0]);
|
|
pci_read_config_word(loc(), cap->config_offset + 4, &cap_buf[1]);
|
|
pci_read_config_word(loc(), cap->config_offset + 8, &cap_buf[2]);
|
|
//hexdump(cap_buf, sizeof(cap_buf));
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::allocate_irq(uint *irq) {
|
|
LTRACE_ENTRY;
|
|
|
|
uint8_t interrupt_line;
|
|
status_t err = pci_read_config_byte(loc(), PCI_CONFIG_INTERRUPT_LINE, &interrupt_line);
|
|
if (err != NO_ERROR) return err;
|
|
|
|
if (interrupt_line == 0) {
|
|
return ERR_NO_RESOURCES;
|
|
}
|
|
|
|
// map the irq number in config space to platform vector space
|
|
err = platform_pci_int_to_vector(interrupt_line, irq);
|
|
return err;
|
|
}
|
|
|
|
status_t device::allocate_msi(size_t num_requested, uint *msi_base) {
|
|
LTRACE_ENTRY;
|
|
|
|
DEBUG_ASSERT(num_requested == 1);
|
|
|
|
if (!has_msi()) {
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
DEBUG_ASSERT(msi_cap_ && msi_cap_->is_msi());
|
|
|
|
// ask the platform for interrupts
|
|
uint vector_base;
|
|
status_t err = platform_allocate_interrupts(num_requested, 0, true, &vector_base);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
|
|
// compute the MSI message to construct
|
|
uint64_t msi_address = 0;
|
|
uint16_t msi_data = 0;
|
|
err = platform_compute_msi_values(vector_base, 0, true, &msi_address, &msi_data);
|
|
if (err != NO_ERROR) {
|
|
// TODO: return the allocated msi
|
|
return err;
|
|
}
|
|
|
|
// program it into the capability
|
|
const uint16_t cap_offset = msi_cap_->config_offset;
|
|
|
|
uint16_t control;
|
|
pci_read_config_half(loc(), cap_offset + 2, &control);
|
|
pci_write_config_half(loc(), cap_offset + 2, control & ~(0x1)); // disable MSI
|
|
pci_write_config_word(loc(), cap_offset + 4, msi_address & 0xffff'ffff); // lower 32bits
|
|
if (control & (1<<7)) {
|
|
// 64bit
|
|
pci_write_config_word(loc(), cap_offset + 8, msi_address >> 32); // upper 32bits
|
|
pci_write_config_half(loc(), cap_offset + 0xc, msi_data);
|
|
} else {
|
|
pci_write_config_half(loc(), cap_offset + 8, msi_data);
|
|
}
|
|
|
|
// set up the control register and enable it
|
|
control = 1; // NME/NMI = 1, no per vector masking, keep 64bit flag, enable
|
|
pci_write_config_half(loc(), cap_offset + 2, control);
|
|
|
|
// write it back to the pci config in the interrupt line offset
|
|
pci_write_config_byte(loc(), PCI_CONFIG_INTERRUPT_LINE, vector_base);
|
|
|
|
// pass back the allocated irq to the caller
|
|
*msi_base = vector_base;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::load_bars() {
|
|
size_t num_bars;
|
|
|
|
if (header_type() == 0) {
|
|
num_bars = 6;
|
|
} else if (header_type() == 1) {
|
|
// type 1 only has 2 bars, but are in the same location as type0
|
|
// so can use the same code below
|
|
num_bars = 2;
|
|
} else {
|
|
// type 2 header?
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
// Disable IO and MEM decoding around BAR detection, as we fiddle with
|
|
// BAR addresses themselves for length detection.
|
|
// This behavior is recommended by the PCI Local Bus Specification.
|
|
|
|
uint16_t command;
|
|
pci_read_config_half(loc(), PCI_CONFIG_COMMAND, &command);
|
|
pci_write_config_half(loc(), PCI_CONFIG_COMMAND, command & ~(PCI_COMMAND_IO_EN | PCI_COMMAND_MEM_EN));
|
|
|
|
for (size_t i=0; i < num_bars; i++) {
|
|
bars_[i] = {};
|
|
uint64_t bar_addr = config_.type0.base_addresses[i];
|
|
if (bar_addr & 0x1) {
|
|
// io address
|
|
bars_[i].io = true;
|
|
bars_[i].prefetchable = false;
|
|
bars_[i].size_64 = false;
|
|
bars_[i].addr = bar_addr & ~0x3;
|
|
|
|
// probe size by writing all 1s and seeing what bits are masked
|
|
uint32_t size = 0;
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, 0xffff);
|
|
pci_read_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, &size);
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, bars_[i].addr);
|
|
|
|
// mask out bottom bits, invert and add 1 to compute size
|
|
bars_[i].size = ((size & ~0b11) ^ 0xffff) + 1;
|
|
|
|
bars_[i].valid = (bars_[i].size != 0);
|
|
} else if ((bar_addr & 0b110) == 0b000) {
|
|
// 32bit memory address
|
|
bars_[i].io = false;
|
|
bars_[i].prefetchable = bar_addr & (1<<3);
|
|
bars_[i].size_64 = false;
|
|
bars_[i].addr = bar_addr & ~0xf;
|
|
|
|
// probe size by writing all 1s and seeing what bits are masked
|
|
uint32_t size = 0;
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, 0xffffffff);
|
|
pci_read_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, &size);
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, bars_[i].addr);
|
|
|
|
// mask out bottom bits, invert and add 1 to compute size
|
|
bars_[i].size = (~(size & ~0b1111)) + 1;
|
|
|
|
bars_[i].valid = (bars_[i].size != 0);
|
|
} else if ((bar_addr & 0b110) == 0b100) {
|
|
// 64bit memory address
|
|
if (i >= num_bars - 1) {
|
|
// root of 64bit memory range will use up two slots, so cant
|
|
// start at the last bar
|
|
continue;
|
|
}
|
|
bars_[i].io = false;
|
|
bars_[i].prefetchable = bar_addr & (1<<3);
|
|
bars_[i].size_64 = true;
|
|
bars_[i].addr = bar_addr & ~0xf;
|
|
bars_[i].addr |= (uint64_t)config_.type0.base_addresses[i + 1] << 32;
|
|
|
|
// probe size by writing all 1s and seeing what bits are masked
|
|
uint64_t size;
|
|
uint32_t size32 = 0;
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, 0xffffffff);
|
|
pci_read_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, &size32);
|
|
size = size32;
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4 + 4, 0xffffffff);
|
|
pci_read_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4 + 4, &size32);
|
|
size |= (uint64_t)size32 << 32;
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4, bars_[i].addr);
|
|
pci_write_config_word(loc_, PCI_CONFIG_BASE_ADDRESSES + i * 4 + 4, bars_[i].addr >> 32);
|
|
|
|
// mask out bottom bits, invert and add 1 to compute size
|
|
bars_[i].size = (~(size & ~(uint64_t)0b1111)) + 1;
|
|
|
|
bars_[i].valid = (bars_[i].size != 0);
|
|
|
|
// mark the next entry as invalid
|
|
i++;
|
|
bars_[i] = {}; // clears the valid bit
|
|
}
|
|
}
|
|
|
|
// Restore any IO and MEM decoding that was enabled before
|
|
pci_write_config_half(loc(), PCI_CONFIG_COMMAND, command);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::read_bars(pci_bar_t bar[6]) {
|
|
// copy the cached bar information
|
|
memcpy(bar, bars_, sizeof(bars_));
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::load_config() {
|
|
status_t err = pci_read_config(loc_, &config_);
|
|
return err;
|
|
}
|
|
|
|
status_t device::compute_bar_sizes(bar_sizes *sizes) {
|
|
char str[14];
|
|
LTRACEF("device at %s\n", pci_loc_string(loc(), str));
|
|
|
|
// iterate through the bars on this device and accumulate the size
|
|
// of all the bars of various types. also accumulate the maximum alignment
|
|
for (auto i = 0; i < 6; i++) {
|
|
const auto &bar = bars_[i];
|
|
if (!bar.valid) {
|
|
continue;
|
|
}
|
|
|
|
if (bar.io) {
|
|
// io case
|
|
sizes->io_size += ROUNDUP(bar.size, 16);
|
|
if (sizes->io_align < 4) {
|
|
sizes->io_align = 4;
|
|
}
|
|
} else if (bar.size_64 && bar.prefetchable) {
|
|
// 64bit mmio
|
|
auto size = ROUNDUP(bar.size, PAGE_SIZE);
|
|
auto align = __builtin_ctz(size);
|
|
sizes->prefetchable64_size += size;
|
|
if (sizes->prefetchable64_align < align) {
|
|
sizes->prefetchable64_align = align;
|
|
}
|
|
} else if (bar.size_64) {
|
|
// 64bit mmio
|
|
auto size = ROUNDUP(bar.size, PAGE_SIZE);
|
|
auto align = __builtin_ctz(size);
|
|
sizes->mmio64_size += size;
|
|
if (sizes->mmio64_align < align) {
|
|
sizes->mmio64_align = align;
|
|
}
|
|
} else if (bar.prefetchable) {
|
|
// 64bit prefetchable mmio
|
|
auto size = ROUNDUP(bar.size, PAGE_SIZE);
|
|
auto align = __builtin_ctz(size);
|
|
sizes->prefetchable_size += size;
|
|
if (sizes->prefetchable_align < align) {
|
|
sizes->prefetchable_align = align;
|
|
}
|
|
} else {
|
|
// 32bit mmio
|
|
auto size = ROUNDUP(bar.size, PAGE_SIZE);
|
|
auto align = __builtin_ctz(size);
|
|
sizes->mmio_size += size;
|
|
if (sizes->mmio_align < align) {
|
|
sizes->mmio_align = align;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::get_bar_alloc_requests(list_node *bar_alloc_requests) {
|
|
char str[14];
|
|
LTRACEF("device at %s\n", pci_loc_string(loc(), str));
|
|
|
|
DEBUG_ASSERT(bar_alloc_requests);
|
|
|
|
// iterate through the bars on this device and accumulate the size
|
|
// of all the bars of various types. also accumulate the maximum alignment
|
|
for (auto i = 0; i < 6; i++) {
|
|
const auto &bar = bars_[i];
|
|
if (!bar.valid) {
|
|
continue;
|
|
}
|
|
|
|
auto request = new bar_alloc_request;
|
|
*request = {};
|
|
request->bridge = false;
|
|
request->dev = this;
|
|
request->bar_num = i;
|
|
|
|
if (bar.io) {
|
|
// io case
|
|
request->size = ROUNDUP(bar.size, 16);
|
|
request->align = 4;
|
|
request->type = PCI_RESOURCE_IO_RANGE;
|
|
} else if (bar.size_64) {
|
|
// 64bit mmio
|
|
auto size = ROUNDUP(bar.size, PAGE_SIZE);
|
|
auto align = __builtin_ctz(size);
|
|
request->size = size;
|
|
request->align = align;
|
|
request->type = PCI_RESOURCE_MMIO64_RANGE;
|
|
request->prefetchable = bar.prefetchable;
|
|
} else {
|
|
// 32bit mmio
|
|
auto size = ROUNDUP(bar.size, PAGE_SIZE);
|
|
auto align = __builtin_ctz(size);
|
|
request->size = size;
|
|
request->align = align;
|
|
request->type = PCI_RESOURCE_MMIO_RANGE;
|
|
request->prefetchable = bar.prefetchable;
|
|
}
|
|
// add it to the list passed in
|
|
list_add_tail(bar_alloc_requests, &request->node);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t device::assign_resource(bar_alloc_request *request, uint64_t address) {
|
|
char str[14];
|
|
LTRACEF("device at %s resource addr %#llx request:\n", pci_loc_string(loc(), str), address);
|
|
if (LOCAL_TRACE) {
|
|
request->dump();
|
|
}
|
|
|
|
DEBUG_ASSERT(IS_ALIGNED(address, (1UL << request->align)));
|
|
|
|
uint32_t temp;
|
|
switch (request->type) {
|
|
case PCI_RESOURCE_IO_RANGE:
|
|
temp = (address & 0xfffc); // XXX do we need to write the bottom bits?
|
|
pci_write_config_word(loc(), PCI_CONFIG_BASE_ADDRESSES + request->bar_num * 4, temp);
|
|
break;
|
|
case PCI_RESOURCE_MMIO_RANGE:
|
|
temp = (address & 0xfffffff0); // XXX do we need to write the bottom bits?
|
|
pci_write_config_word(loc(), PCI_CONFIG_BASE_ADDRESSES + request->bar_num * 4, temp);
|
|
break;
|
|
case PCI_RESOURCE_MMIO64_RANGE:
|
|
temp = (address & 0xfffffff0); // XXX do we need to write the bottom bits?
|
|
pci_write_config_word(loc(), PCI_CONFIG_BASE_ADDRESSES + request->bar_num * 4, temp);
|
|
temp = address >> 32;
|
|
pci_write_config_word(loc(), PCI_CONFIG_BASE_ADDRESSES + request->bar_num * 4 + 4, temp);
|
|
break;
|
|
default:
|
|
panic("invalid request type %d\n", request->type);
|
|
}
|
|
|
|
load_config();
|
|
load_bars();
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void device::bar_alloc_request::dump() {
|
|
char str[14];
|
|
if (bridge) {
|
|
printf("BAR alloc request %p: bridge %s type %u (%s) pref %d size %#llx align %u\n",
|
|
this, pci_loc_string(dev->loc(), str), type, pci_resource_type_to_str(type), prefetchable, size, align);
|
|
} else {
|
|
printf("BAR alloc request %p: device %s type %u (%s) pref %d size %#llx align %u bar %u\n",
|
|
this, pci_loc_string(dev->loc(), str), type, pci_resource_type_to_str(type), prefetchable, size, align, bar_num);
|
|
}
|
|
}
|
|
|
|
} // namespace pci
|