qemu-patch-raspberry4/tests/libqos/pci.c
David Gibson a795fc08f2 libqos: Handle PCI IO de-multiplexing in common code
The PCI IO space (aka PIO, aka legacy IO) and PCI memory space (aka MMIO)
are distinct address spaces by the PCI spec (although parts of one might be
aliased to parts of the other in some cases).

However, qpci_io_read*() and qpci_io_write*() can perform accesses to
either space depending on parameter.  That's convenient for test case
drivers, since there are a fair few devices which can be controlled via
either a PIO or MMIO BAR but with an otherwise identical driver.

This is implemented by having addresses below 64kiB treated as PIO, and
those above treated as MMIO.  This works because low addresses in memory
space are generally reserved for DMA rather than MMIO.

At the moment, this demultiplexing must be handled by each PCI backend
(pc and spapr, so far).  There's no real reason for this - the current
encoding is likely to work for all platforms, and even if it doesn't we
can still use a more complex common encoding since the value returned from
iomap are semi-opaque.

This patch moves the demultiplexing into the common part of the libqos PCI
code, with the backends having simpler, separate accessors for PIO and
MMIO space.  This also means we have a way of explicitly accessing either
space if it's necessary for some special case.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Reviewed-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: Greg Kurz <groug@kaod.org>
2016-10-28 09:38:27 +11:00

322 lines
8.3 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"
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_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr)
{
return dev->bus->iomap(dev->bus, dev, barno, sizeptr);
}
void qpci_iounmap(QPCIDevice *dev, void *data)
{
dev->bus->iounmap(dev->bus, data);
}
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);
}