mirror of
https://github.com/qemu/qemu.git
synced 2025-07-29 06:26:06 +00:00

In the libqos PCI code we now have accessors both for registers (byte significance preserving) and for streaming data (byte address order preserving). These exist in both the interface for qtest drivers and in the machine specific backends. However, the register-style accessors aren't actually necessary in the backend. They can be implemented in terms of the byte address order preserving accessors by the libqos wrappers. This works because PCI is always little endian. This does assume that the back end byte address order preserving accessors will perform the equivalent of a single bus transaction for short lengths. This is the case, and in fact they currently end up using the same cpu_physical_memory_rw() implementation within the qtest accelerator. Signed-off-by: David Gibson <david@gibson.dropbear.id.au> Reviewed-by: Laurent Vivier <lvivier@redhat.com> Reviewed-by: Greg Kurz <groug@kaod.org>
403 lines
10 KiB
C
403 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 {
|
|
uint8_t val;
|
|
dev->bus->memread(dev->bus, addr, &val, sizeof(val));
|
|
return val;
|
|
}
|
|
}
|
|
|
|
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 {
|
|
uint16_t val;
|
|
dev->bus->memread(dev->bus, addr, &val, sizeof(val));
|
|
return le16_to_cpu(val);
|
|
}
|
|
}
|
|
|
|
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 {
|
|
uint32_t val;
|
|
dev->bus->memread(dev->bus, addr, &val, sizeof(val));
|
|
return le32_to_cpu(val);
|
|
}
|
|
}
|
|
|
|
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->memwrite(dev->bus, addr, &value, sizeof(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 {
|
|
value = cpu_to_le16(value);
|
|
dev->bus->memwrite(dev->bus, addr, &value, sizeof(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 {
|
|
value = cpu_to_le32(value);
|
|
dev->bus->memwrite(dev->bus, addr, &value, sizeof(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);
|
|
}
|