![David Gibson](/assets/img/avatar_default.png)
Currently PCI memory (aka MMIO) space is accessed via a set of readb/writeb style accessors. This is what we want for accessing discrete registers of a certain size. However, there are a few cases where we instead need a "bag of bytes" style streaming interface to PCI MMIO space. This can be either for streaming data style registers or when there's actual memory rather than registers in PCI space, for example frame buffers or ivshmem. This patch adds backend callbacks, and libqos wrappers for this type of byte address order preserving accesses. Signed-off-by: David Gibson <david@gibson.dropbear.id.au> Reviewed-by: Laurent Vivier <lvivier@redhat.com> Reviewed-by: Greg Kurz <groug@kaod.org>
395 lines
10 KiB
C
395 lines
10 KiB
C
/*
|
|
* libqos PCI bindings
|
|
*
|
|
* Copyright IBM, Corp. 2012-2013
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "libqos/pci.h"
|
|
|
|
#include "hw/pci/pci_regs.h"
|
|
#include "qemu/host-utils.h"
|
|
|
|
void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
|
|
void (*func)(QPCIDevice *dev, int devfn, void *data),
|
|
void *data)
|
|
{
|
|
int slot;
|
|
|
|
for (slot = 0; slot < 32; slot++) {
|
|
int fn;
|
|
|
|
for (fn = 0; fn < 8; fn++) {
|
|
QPCIDevice *dev;
|
|
|
|
dev = qpci_device_find(bus, QPCI_DEVFN(slot, fn));
|
|
if (!dev) {
|
|
continue;
|
|
}
|
|
|
|
if (vendor_id != -1 &&
|
|
qpci_config_readw(dev, PCI_VENDOR_ID) != vendor_id) {
|
|
g_free(dev);
|
|
continue;
|
|
}
|
|
|
|
if (device_id != -1 &&
|
|
qpci_config_readw(dev, PCI_DEVICE_ID) != device_id) {
|
|
g_free(dev);
|
|
continue;
|
|
}
|
|
|
|
func(dev, QPCI_DEVFN(slot, fn), data);
|
|
}
|
|
}
|
|
}
|
|
|
|
QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn)
|
|
{
|
|
QPCIDevice *dev;
|
|
|
|
dev = g_malloc0(sizeof(*dev));
|
|
dev->bus = bus;
|
|
dev->devfn = devfn;
|
|
|
|
if (qpci_config_readw(dev, PCI_VENDOR_ID) == 0xFFFF) {
|
|
g_free(dev);
|
|
return NULL;
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
void qpci_device_enable(QPCIDevice *dev)
|
|
{
|
|
uint16_t cmd;
|
|
|
|
/* FIXME -- does this need to be a bus callout? */
|
|
cmd = qpci_config_readw(dev, PCI_COMMAND);
|
|
cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
|
|
qpci_config_writew(dev, PCI_COMMAND, cmd);
|
|
|
|
/* Verify the bits are now set. */
|
|
cmd = qpci_config_readw(dev, PCI_COMMAND);
|
|
g_assert_cmphex(cmd & PCI_COMMAND_IO, ==, PCI_COMMAND_IO);
|
|
g_assert_cmphex(cmd & PCI_COMMAND_MEMORY, ==, PCI_COMMAND_MEMORY);
|
|
g_assert_cmphex(cmd & PCI_COMMAND_MASTER, ==, PCI_COMMAND_MASTER);
|
|
}
|
|
|
|
uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id)
|
|
{
|
|
uint8_t cap;
|
|
uint8_t addr = qpci_config_readb(dev, PCI_CAPABILITY_LIST);
|
|
|
|
do {
|
|
cap = qpci_config_readb(dev, addr);
|
|
if (cap != id) {
|
|
addr = qpci_config_readb(dev, addr + PCI_CAP_LIST_NEXT);
|
|
}
|
|
} while (cap != id && addr != 0);
|
|
|
|
return addr;
|
|
}
|
|
|
|
void qpci_msix_enable(QPCIDevice *dev)
|
|
{
|
|
uint8_t addr;
|
|
uint16_t val;
|
|
uint32_t table;
|
|
uint8_t bir_table;
|
|
uint8_t bir_pba;
|
|
void *offset;
|
|
|
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
|
g_assert_cmphex(addr, !=, 0);
|
|
|
|
val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
|
qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, val | PCI_MSIX_FLAGS_ENABLE);
|
|
|
|
table = qpci_config_readl(dev, addr + PCI_MSIX_TABLE);
|
|
bir_table = table & PCI_MSIX_FLAGS_BIRMASK;
|
|
offset = qpci_iomap(dev, bir_table, NULL);
|
|
dev->msix_table = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK);
|
|
|
|
table = qpci_config_readl(dev, addr + PCI_MSIX_PBA);
|
|
bir_pba = table & PCI_MSIX_FLAGS_BIRMASK;
|
|
if (bir_pba != bir_table) {
|
|
offset = qpci_iomap(dev, bir_pba, NULL);
|
|
}
|
|
dev->msix_pba = offset + (table & ~PCI_MSIX_FLAGS_BIRMASK);
|
|
|
|
g_assert(dev->msix_table != NULL);
|
|
g_assert(dev->msix_pba != NULL);
|
|
dev->msix_enabled = true;
|
|
}
|
|
|
|
void qpci_msix_disable(QPCIDevice *dev)
|
|
{
|
|
uint8_t addr;
|
|
uint16_t val;
|
|
|
|
g_assert(dev->msix_enabled);
|
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
|
g_assert_cmphex(addr, !=, 0);
|
|
val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
|
qpci_config_writew(dev, addr + PCI_MSIX_FLAGS,
|
|
val & ~PCI_MSIX_FLAGS_ENABLE);
|
|
|
|
qpci_iounmap(dev, dev->msix_table);
|
|
qpci_iounmap(dev, dev->msix_pba);
|
|
dev->msix_enabled = 0;
|
|
dev->msix_table = NULL;
|
|
dev->msix_pba = NULL;
|
|
}
|
|
|
|
bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry)
|
|
{
|
|
uint32_t pba_entry;
|
|
uint8_t bit_n = entry % 32;
|
|
void *addr = dev->msix_pba + (entry / 32) * PCI_MSIX_ENTRY_SIZE / 4;
|
|
|
|
g_assert(dev->msix_enabled);
|
|
pba_entry = qpci_io_readl(dev, addr);
|
|
qpci_io_writel(dev, addr, pba_entry & ~(1 << bit_n));
|
|
return (pba_entry & (1 << bit_n)) != 0;
|
|
}
|
|
|
|
bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry)
|
|
{
|
|
uint8_t addr;
|
|
uint16_t val;
|
|
void *vector_addr = dev->msix_table + (entry * PCI_MSIX_ENTRY_SIZE);
|
|
|
|
g_assert(dev->msix_enabled);
|
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
|
g_assert_cmphex(addr, !=, 0);
|
|
val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
|
|
|
if (val & PCI_MSIX_FLAGS_MASKALL) {
|
|
return true;
|
|
} else {
|
|
return (qpci_io_readl(dev, vector_addr + PCI_MSIX_ENTRY_VECTOR_CTRL)
|
|
& PCI_MSIX_ENTRY_CTRL_MASKBIT) != 0;
|
|
}
|
|
}
|
|
|
|
uint16_t qpci_msix_table_size(QPCIDevice *dev)
|
|
{
|
|
uint8_t addr;
|
|
uint16_t control;
|
|
|
|
addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX);
|
|
g_assert_cmphex(addr, !=, 0);
|
|
|
|
control = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS);
|
|
return (control & PCI_MSIX_FLAGS_QSIZE) + 1;
|
|
}
|
|
|
|
uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset)
|
|
{
|
|
return dev->bus->config_readb(dev->bus, dev->devfn, offset);
|
|
}
|
|
|
|
uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset)
|
|
{
|
|
return dev->bus->config_readw(dev->bus, dev->devfn, offset);
|
|
}
|
|
|
|
uint32_t qpci_config_readl(QPCIDevice *dev, uint8_t offset)
|
|
{
|
|
return dev->bus->config_readl(dev->bus, dev->devfn, offset);
|
|
}
|
|
|
|
|
|
void qpci_config_writeb(QPCIDevice *dev, uint8_t offset, uint8_t value)
|
|
{
|
|
dev->bus->config_writeb(dev->bus, dev->devfn, offset, value);
|
|
}
|
|
|
|
void qpci_config_writew(QPCIDevice *dev, uint8_t offset, uint16_t value)
|
|
{
|
|
dev->bus->config_writew(dev->bus, dev->devfn, offset, value);
|
|
}
|
|
|
|
void qpci_config_writel(QPCIDevice *dev, uint8_t offset, uint32_t value)
|
|
{
|
|
dev->bus->config_writel(dev->bus, dev->devfn, offset, value);
|
|
}
|
|
|
|
|
|
uint8_t qpci_io_readb(QPCIDevice *dev, void *data)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
if (addr < QPCI_PIO_LIMIT) {
|
|
return dev->bus->pio_readb(dev->bus, addr);
|
|
} else {
|
|
return dev->bus->mmio_readb(dev->bus, addr);
|
|
}
|
|
}
|
|
|
|
uint16_t qpci_io_readw(QPCIDevice *dev, void *data)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
if (addr < QPCI_PIO_LIMIT) {
|
|
return dev->bus->pio_readw(dev->bus, addr);
|
|
} else {
|
|
return dev->bus->mmio_readw(dev->bus, addr);
|
|
}
|
|
}
|
|
|
|
uint32_t qpci_io_readl(QPCIDevice *dev, void *data)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
if (addr < QPCI_PIO_LIMIT) {
|
|
return dev->bus->pio_readl(dev->bus, addr);
|
|
} else {
|
|
return dev->bus->mmio_readl(dev->bus, addr);
|
|
}
|
|
}
|
|
|
|
void qpci_io_writeb(QPCIDevice *dev, void *data, uint8_t value)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
if (addr < QPCI_PIO_LIMIT) {
|
|
dev->bus->pio_writeb(dev->bus, addr, value);
|
|
} else {
|
|
dev->bus->mmio_writeb(dev->bus, addr, value);
|
|
}
|
|
}
|
|
|
|
void qpci_io_writew(QPCIDevice *dev, void *data, uint16_t value)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
if (addr < QPCI_PIO_LIMIT) {
|
|
dev->bus->pio_writew(dev->bus, addr, value);
|
|
} else {
|
|
dev->bus->mmio_writew(dev->bus, addr, value);
|
|
}
|
|
}
|
|
|
|
void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
if (addr < QPCI_PIO_LIMIT) {
|
|
dev->bus->pio_writel(dev->bus, addr, value);
|
|
} else {
|
|
dev->bus->mmio_writel(dev->bus, addr, value);
|
|
}
|
|
}
|
|
|
|
void qpci_memread(QPCIDevice *dev, void *data, void *buf, size_t len)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
g_assert(addr >= QPCI_PIO_LIMIT);
|
|
dev->bus->memread(dev->bus, addr, buf, len);
|
|
}
|
|
|
|
void qpci_memwrite(QPCIDevice *dev, void *data, const void *buf, size_t len)
|
|
{
|
|
uintptr_t addr = (uintptr_t)data;
|
|
|
|
g_assert(addr >= QPCI_PIO_LIMIT);
|
|
dev->bus->memwrite(dev->bus, addr, buf, len);
|
|
}
|
|
|
|
void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr)
|
|
{
|
|
QPCIBus *bus = dev->bus;
|
|
static const int bar_reg_map[] = {
|
|
PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2,
|
|
PCI_BASE_ADDRESS_3, PCI_BASE_ADDRESS_4, PCI_BASE_ADDRESS_5,
|
|
};
|
|
int bar_reg;
|
|
uint32_t addr, size;
|
|
uint32_t io_type;
|
|
uint64_t loc;
|
|
|
|
g_assert(barno >= 0 && barno <= 5);
|
|
bar_reg = bar_reg_map[barno];
|
|
|
|
qpci_config_writel(dev, bar_reg, 0xFFFFFFFF);
|
|
addr = qpci_config_readl(dev, bar_reg);
|
|
|
|
io_type = addr & PCI_BASE_ADDRESS_SPACE;
|
|
if (io_type == PCI_BASE_ADDRESS_SPACE_IO) {
|
|
addr &= PCI_BASE_ADDRESS_IO_MASK;
|
|
} else {
|
|
addr &= PCI_BASE_ADDRESS_MEM_MASK;
|
|
}
|
|
|
|
g_assert(addr); /* Must have *some* size bits */
|
|
|
|
size = 1U << ctz32(addr);
|
|
if (sizeptr) {
|
|
*sizeptr = size;
|
|
}
|
|
|
|
if (io_type == PCI_BASE_ADDRESS_SPACE_IO) {
|
|
loc = QEMU_ALIGN_UP(bus->pio_alloc_ptr, size);
|
|
|
|
g_assert(loc >= bus->pio_alloc_ptr);
|
|
g_assert(loc + size <= QPCI_PIO_LIMIT); /* Keep PIO below 64kiB */
|
|
|
|
bus->pio_alloc_ptr = loc + size;
|
|
|
|
qpci_config_writel(dev, bar_reg, loc | PCI_BASE_ADDRESS_SPACE_IO);
|
|
} else {
|
|
loc = QEMU_ALIGN_UP(bus->mmio_alloc_ptr, size);
|
|
|
|
/* Check for space */
|
|
g_assert(loc >= bus->mmio_alloc_ptr);
|
|
g_assert(loc + size <= bus->mmio_limit);
|
|
|
|
bus->mmio_alloc_ptr = loc + size;
|
|
|
|
qpci_config_writel(dev, bar_reg, loc);
|
|
}
|
|
|
|
return (void *)(uintptr_t)loc;
|
|
}
|
|
|
|
void qpci_iounmap(QPCIDevice *dev, void *data)
|
|
{
|
|
/* FIXME */
|
|
}
|
|
|
|
void *qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr)
|
|
{
|
|
return (void *)(uintptr_t)addr;
|
|
}
|
|
|
|
void qpci_plug_device_test(const char *driver, const char *id,
|
|
uint8_t slot, const char *opts)
|
|
{
|
|
QDict *response;
|
|
char *cmd;
|
|
|
|
cmd = g_strdup_printf("{'execute': 'device_add',"
|
|
" 'arguments': {"
|
|
" 'driver': '%s',"
|
|
" 'addr': '%d',"
|
|
" %s%s"
|
|
" 'id': '%s'"
|
|
"}}", driver, slot,
|
|
opts ? opts : "", opts ? "," : "",
|
|
id);
|
|
response = qmp(cmd);
|
|
g_free(cmd);
|
|
g_assert(response);
|
|
g_assert(!qdict_haskey(response, "error"));
|
|
QDECREF(response);
|
|
}
|