pci: pcie host and mmcfg support.

This patch adds common routines for pcie host bridge and pcie mmcfg.
This will be used by q35 based chipset emulation.

Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
Isaku Yamahata 2009-10-30 21:21:18 +09:00 committed by Anthony Liguori
parent 9cae69bd8d
commit a9f4994611
6 changed files with 349 additions and 19 deletions

View file

@ -154,7 +154,7 @@ endif #CONFIG_BSD_USER
# System emulator target
ifdef CONFIG_SOFTMMU
obj-y = vl.o async.o monitor.o pci.o pci_host.o machine.o gdbstub.o
obj-y = vl.o async.o monitor.o pci.o pci_host.o pcie_host.o machine.o gdbstub.o
# virtio has to be here due to weird dependency between PCI and virtio-net.
# need to fix this properly
obj-y += virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o virtio-pci.o

11
hw/hw.h
View file

@ -512,6 +512,17 @@ extern const VMStateDescription vmstate_pci_device;
.offset = vmstate_offset_value(_state, _field, PCIDevice), \
}
extern const VMStateDescription vmstate_pcie_device;
#define VMSTATE_PCIE_DEVICE(_field, _state) { \
.name = (stringify(_field)), \
.version_id = 2, \
.size = sizeof(PCIDevice), \
.vmsd = &vmstate_pcie_device, \
.flags = VMS_STRUCT, \
.offset = vmstate_offset_value(_state, _field, PCIDevice), \
}
extern const VMStateDescription vmstate_i2c_slave;
#define VMSTATE_I2C_SLAVE(_field, _state) { \

View file

@ -23,6 +23,7 @@
*/
#include "hw.h"
#include "pci.h"
#include "pci_host.h"
#include "monitor.h"
#include "net.h"
#include "sysemu.h"
@ -248,18 +249,24 @@ static uint8_t pci_sub_bus(PCIBus *s)
static int get_pci_config_device(QEMUFile *f, void *pv, size_t size)
{
PCIDevice *s = container_of(pv, PCIDevice, config);
uint8_t config[PCI_CONFIG_SPACE_SIZE];
uint8_t *config;
int i;
assert(size == sizeof config);
qemu_get_buffer(f, config, sizeof config);
for (i = 0; i < sizeof config; ++i)
if ((config[i] ^ s->config[i]) & s->cmask[i] & ~s->wmask[i])
assert(size == pci_config_size(s));
config = qemu_malloc(size);
qemu_get_buffer(f, config, size);
for (i = 0; i < size; ++i) {
if ((config[i] ^ s->config[i]) & s->cmask[i] & ~s->wmask[i]) {
qemu_free(config);
return -EINVAL;
memcpy(s->config, config, sizeof config);
}
}
memcpy(s->config, config, size);
pci_update_mappings(s);
qemu_free(config);
return 0;
}
@ -267,6 +274,7 @@ static int get_pci_config_device(QEMUFile *f, void *pv, size_t size)
static void put_pci_config_device(QEMUFile *f, void *pv, size_t size)
{
const uint8_t *v = pv;
assert(size == pci_config_size(container_of(pv, PCIDevice, config)));
qemu_put_buffer(f, v, size);
}
@ -283,21 +291,42 @@ const VMStateDescription vmstate_pci_device = {
.minimum_version_id_old = 1,
.fields = (VMStateField []) {
VMSTATE_INT32_LE(version_id, PCIDevice),
VMSTATE_SINGLE(config, PCIDevice, 0, vmstate_info_pci_config,
typeof_field(PCIDevice,config)),
VMSTATE_BUFFER_UNSAFE_INFO(config, PCIDevice, 0,
vmstate_info_pci_config,
PCI_CONFIG_SPACE_SIZE),
VMSTATE_INT32_ARRAY_V(irq_state, PCIDevice, PCI_NUM_PINS, 2),
VMSTATE_END_OF_LIST()
}
};
const VMStateDescription vmstate_pcie_device = {
.name = "PCIDevice",
.version_id = 2,
.minimum_version_id = 1,
.minimum_version_id_old = 1,
.fields = (VMStateField []) {
VMSTATE_INT32_LE(version_id, PCIDevice),
VMSTATE_BUFFER_UNSAFE_INFO(config, PCIDevice, 0,
vmstate_info_pci_config,
PCIE_CONFIG_SPACE_SIZE),
VMSTATE_INT32_ARRAY_V(irq_state, PCIDevice, PCI_NUM_PINS, 2),
VMSTATE_END_OF_LIST()
}
};
static inline const VMStateDescription *pci_get_vmstate(PCIDevice *s)
{
return pci_is_express(s) ? &vmstate_pcie_device : &vmstate_pci_device;
}
void pci_device_save(PCIDevice *s, QEMUFile *f)
{
vmstate_save_state(f, &vmstate_pci_device, s);
vmstate_save_state(f, pci_get_vmstate(s), s);
}
int pci_device_load(PCIDevice *s, QEMUFile *f)
{
return vmstate_load_state(f, &vmstate_pci_device, s, s->version_id);
return vmstate_load_state(f, pci_get_vmstate(s), s, s->version_id);
}
static int pci_set_default_subsystem_id(PCIDevice *pci_dev)
@ -406,14 +435,34 @@ static void pci_init_cmask(PCIDevice *dev)
static void pci_init_wmask(PCIDevice *dev)
{
int i;
int config_size = pci_config_size(dev);
dev->wmask[PCI_CACHE_LINE_SIZE] = 0xff;
dev->wmask[PCI_INTERRUPT_LINE] = 0xff;
pci_set_word(dev->wmask + PCI_COMMAND,
PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
for (i = PCI_CONFIG_HEADER_SIZE; i < PCI_CONFIG_SPACE_SIZE; ++i)
for (i = PCI_CONFIG_HEADER_SIZE; i < config_size; ++i)
dev->wmask[i] = 0xff;
}
static void pci_config_alloc(PCIDevice *pci_dev)
{
int config_size = pci_config_size(pci_dev);
pci_dev->config = qemu_mallocz(config_size);
pci_dev->cmask = qemu_mallocz(config_size);
pci_dev->wmask = qemu_mallocz(config_size);
pci_dev->used = qemu_mallocz(config_size);
}
static void pci_config_free(PCIDevice *pci_dev)
{
qemu_free(pci_dev->config);
qemu_free(pci_dev->cmask);
qemu_free(pci_dev->wmask);
qemu_free(pci_dev->used);
}
/* -1 for devfn means auto assign */
static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus,
const char *name, int devfn,
@ -434,6 +483,7 @@ static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus,
pci_dev->devfn = devfn;
pstrcpy(pci_dev->name, sizeof(pci_dev->name), name);
memset(pci_dev->irq_state, 0, sizeof(pci_dev->irq_state));
pci_config_alloc(pci_dev);
pci_set_default_subsystem_id(pci_dev);
pci_init_cmask(pci_dev);
pci_init_wmask(pci_dev);
@ -501,6 +551,7 @@ static int pci_unregister_device(DeviceState *dev)
qemu_free_irqs(pci_dev->irq);
pci_dev->bus->devices[pci_dev->devfn] = NULL;
pci_config_free(pci_dev);
return 0;
}
@ -641,7 +692,7 @@ uint32_t pci_default_read_config(PCIDevice *d,
{
uint32_t val = 0;
assert(len == 1 || len == 2 || len == 4);
len = MIN(len, PCI_CONFIG_SPACE_SIZE - address);
len = MIN(len, pci_config_size(d) - address);
memcpy(&val, d->config + address, len);
return le32_to_cpu(val);
}
@ -650,10 +701,11 @@ void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
{
uint8_t orig[PCI_CONFIG_SPACE_SIZE];
int i;
uint32_t config_size = pci_config_size(d);
/* not efficient, but simple */
memcpy(orig, d->config, PCI_CONFIG_SPACE_SIZE);
for(i = 0; i < l && addr < PCI_CONFIG_SPACE_SIZE; val >>= 8, ++i, ++addr) {
for(i = 0; i < l && addr < config_size; val >>= 8, ++i, ++addr) {
uint8_t wmask = d->wmask[addr];
d->config[addr] = (d->config[addr] & ~wmask) | (val & wmask);
}
@ -1001,6 +1053,11 @@ static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base)
PCIBus *bus;
int devfn, rc;
/* initialize cap_present for pci_is_express() and pci_config_size() */
if (info->is_express) {
pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
}
bus = FROM_QBUS(PCIBus, qdev_get_parent_bus(qdev));
devfn = pci_dev->devfn;
pci_dev = do_pci_register_device(pci_dev, bus, base->name, devfn,
@ -1057,9 +1114,10 @@ PCIDevice *pci_create_simple(PCIBus *bus, int devfn, const char *name)
static int pci_find_space(PCIDevice *pdev, uint8_t size)
{
int config_size = pci_config_size(pdev);
int offset = PCI_CONFIG_HEADER_SIZE;
int i;
for (i = PCI_CONFIG_HEADER_SIZE; i < PCI_CONFIG_SPACE_SIZE; ++i)
for (i = PCI_CONFIG_HEADER_SIZE; i < config_size; ++i)
if (pdev->used[i])
offset = i + 1;
else if (i - offset + 1 == size)

View file

@ -163,28 +163,31 @@ typedef struct PCIIORegion {
#define PCI_CONFIG_HEADER_SIZE 0x40
/* Size of the standard PCI config space */
#define PCI_CONFIG_SPACE_SIZE 0x100
/* Size of the standart PCIe config space: 4KB */
#define PCIE_CONFIG_SPACE_SIZE 0x1000
#define PCI_NUM_PINS 4 /* A-D */
/* Bits in cap_present field. */
enum {
QEMU_PCI_CAP_MSIX = 0x1,
QEMU_PCI_CAP_EXPRESS = 0x2,
};
struct PCIDevice {
DeviceState qdev;
/* PCI config space */
uint8_t config[PCI_CONFIG_SPACE_SIZE];
uint8_t *config;
/* Used to enable config checks on load. Note that writeable bits are
* never checked even if set in cmask. */
uint8_t cmask[PCI_CONFIG_SPACE_SIZE];
uint8_t *cmask;
/* Used to implement R/W bytes */
uint8_t wmask[PCI_CONFIG_SPACE_SIZE];
uint8_t *wmask;
/* Used to allocate config space for capabilities. */
uint8_t used[PCI_CONFIG_SPACE_SIZE];
uint8_t *used;
/* the following fields are read only */
PCIBus *bus;
@ -354,6 +357,12 @@ typedef struct {
PCIUnregisterFunc *exit;
PCIConfigReadFunc *config_read;
PCIConfigWriteFunc *config_write;
/* pcie stuff */
int is_express; /* is this device pci express?
* initialization code needs to know this before
* each specific device initialization.
*/
} PCIDeviceInfo;
void pci_qdev_register(PCIDeviceInfo *info);
@ -362,6 +371,16 @@ void pci_qdev_register_many(PCIDeviceInfo *info);
PCIDevice *pci_create(PCIBus *bus, int devfn, const char *name);
PCIDevice *pci_create_simple(PCIBus *bus, int devfn, const char *name);
static inline int pci_is_express(PCIDevice *d)
{
return d->cap_present & QEMU_PCI_CAP_EXPRESS;
}
static inline uint32_t pci_config_size(PCIDevice *d)
{
return pci_is_express(d) ? PCIE_CONFIG_SPACE_SIZE : PCI_CONFIG_SPACE_SIZE;
}
/* lsi53c895a.c */
#define LSI_MAX_DEVS 7

192
hw/pcie_host.c Normal file
View file

@ -0,0 +1,192 @@
/*
* pcie_host.c
* utility functions for pci express host bridge.
*
* Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
* VA Linux Systems Japan K.K.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "hw.h"
#include "pci.h"
#include "pcie_host.h"
/*
* PCI express mmcfig address
* bit 20 - 28: bus number
* bit 15 - 19: device number
* bit 12 - 14: function number
* bit 0 - 11: offset in configuration space of a given device
*/
#define PCIE_MMCFG_SIZE_MAX (1ULL << 28)
#define PCIE_MMCFG_SIZE_MIN (1ULL << 20)
#define PCIE_MMCFG_BUS_BIT 20
#define PCIE_MMCFG_BUS_MASK 0x1ff
#define PCIE_MMCFG_DEVFN_BIT 12
#define PCIE_MMCFG_DEVFN_MASK 0xff
#define PCIE_MMCFG_CONFOFFSET_MASK 0xfff
#define PCIE_MMCFG_BUS(addr) (((addr) >> PCIE_MMCFG_BUS_BIT) & \
PCIE_MMCFG_BUS_MASK)
#define PCIE_MMCFG_DEVFN(addr) (((addr) >> PCIE_MMCFG_DEVFN_BIT) & \
PCIE_MMCFG_DEVFN_MASK)
#define PCIE_MMCFG_CONFOFFSET(addr) ((addr) & PCIE_MMCFG_CONFOFFSET_MASK)
/* a helper function to get a PCIDevice for a given mmconfig address */
static inline PCIDevice *pcie_mmcfg_addr_to_dev(PCIBus *s, uint32_t mmcfg_addr)
{
return pci_find_device(s, PCIE_MMCFG_BUS(mmcfg_addr),
PCI_SLOT(PCIE_MMCFG_DEVFN(mmcfg_addr)),
PCI_FUNC(PCIE_MMCFG_DEVFN(mmcfg_addr)));
}
static void pcie_mmcfg_data_write(PCIBus *s,
uint32_t mmcfg_addr, uint32_t val, int len)
{
PCIDevice *pci_dev = pcie_mmcfg_addr_to_dev(s, mmcfg_addr);
if (!pci_dev)
return;
pci_dev->config_write(pci_dev,
PCIE_MMCFG_CONFOFFSET(mmcfg_addr), val, len);
}
static uint32_t pcie_mmcfg_data_read(PCIBus *s,
uint32_t mmcfg_addr, int len)
{
PCIDevice *pci_dev = pcie_mmcfg_addr_to_dev(s, mmcfg_addr);
uint32_t val;
if (!pci_dev) {
switch(len) {
case 1:
val = 0xff;
break;
case 2:
val = 0xffff;
break;
default:
case 4:
val = 0xffffffff;
break;
}
} else {
val = pci_dev->config_read(pci_dev,
PCIE_MMCFG_CONFOFFSET(mmcfg_addr), len);
}
return val;
}
static void pcie_mmcfg_data_writeb(void *opaque,
target_phys_addr_t addr, uint32_t value)
{
PCIExpressHost *e = opaque;
pcie_mmcfg_data_write(e->pci.bus, addr - e->base_addr, value, 1);
}
static void pcie_mmcfg_data_writew(void *opaque,
target_phys_addr_t addr, uint32_t value)
{
PCIExpressHost *e = opaque;
pcie_mmcfg_data_write(e->pci.bus, addr - e->base_addr, value, 2);
}
static void pcie_mmcfg_data_writel(void *opaque,
target_phys_addr_t addr, uint32_t value)
{
PCIExpressHost *e = opaque;
pcie_mmcfg_data_write(e->pci.bus, addr - e->base_addr, value, 4);
}
static uint32_t pcie_mmcfg_data_readb(void *opaque, target_phys_addr_t addr)
{
PCIExpressHost *e = opaque;
return pcie_mmcfg_data_read(e->pci.bus, addr - e->base_addr, 1);
}
static uint32_t pcie_mmcfg_data_readw(void *opaque, target_phys_addr_t addr)
{
PCIExpressHost *e = opaque;
return pcie_mmcfg_data_read(e->pci.bus, addr - e->base_addr, 2);
}
static uint32_t pcie_mmcfg_data_readl(void *opaque, target_phys_addr_t addr)
{
PCIExpressHost *e = opaque;
return pcie_mmcfg_data_read(e->pci.bus, addr - e->base_addr, 4);
}
static CPUWriteMemoryFunc * const pcie_mmcfg_write[] =
{
pcie_mmcfg_data_writeb,
pcie_mmcfg_data_writew,
pcie_mmcfg_data_writel,
};
static CPUReadMemoryFunc * const pcie_mmcfg_read[] =
{
pcie_mmcfg_data_readb,
pcie_mmcfg_data_readw,
pcie_mmcfg_data_readl,
};
/* pcie_host::base_addr == PCIE_BASE_ADDR_UNMAPPED when it isn't mapped. */
#define PCIE_BASE_ADDR_UNMAPPED ((target_phys_addr_t)-1ULL)
int pcie_host_init(PCIExpressHost *e)
{
e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
e->mmio_index =
cpu_register_io_memory(pcie_mmcfg_read, pcie_mmcfg_write, e);
if (e->mmio_index < 0) {
return -1;
}
return 0;
}
void pcie_host_mmcfg_unmap(PCIExpressHost *e)
{
if (e->base_addr != PCIE_BASE_ADDR_UNMAPPED) {
cpu_register_physical_memory(e->base_addr, e->size, IO_MEM_UNASSIGNED);
e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
}
}
void pcie_host_mmcfg_map(PCIExpressHost *e,
target_phys_addr_t addr, uint32_t size)
{
assert(!(size & (size - 1))); /* power of 2 */
assert(size >= PCIE_MMCFG_SIZE_MIN);
assert(size <= PCIE_MMCFG_SIZE_MAX);
e->base_addr = addr;
e->size = size;
cpu_register_physical_memory(e->base_addr, e->size, e->mmio_index);
}
void pcie_host_mmcfg_update(PCIExpressHost *e,
int enable,
target_phys_addr_t addr, uint32_t size)
{
pcie_host_mmcfg_unmap(e);
if (enable) {
pcie_host_mmcfg_map(e, addr, size);
}
}

50
hw/pcie_host.h Normal file
View file

@ -0,0 +1,50 @@
/*
* pcie_host.h
*
* Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
* VA Linux Systems Japan K.K.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef PCIE_HOST_H
#define PCIE_HOST_H
#include "pci_host.h"
typedef struct {
PCIHostState pci;
/* express part */
/* base address where MMCONFIG area is mapped. */
target_phys_addr_t base_addr;
/* the size of MMCONFIG area. It's host bridge dependent */
target_phys_addr_t size;
/* result of cpu_register_io_memory() to map MMCONFIG area */
int mmio_index;
} PCIExpressHost;
int pcie_host_init(PCIExpressHost *e);
void pcie_host_mmcfg_unmap(PCIExpressHost *e);
void pcie_host_mmcfg_map(PCIExpressHost *e,
target_phys_addr_t addr, uint32_t size);
void pcie_host_mmcfg_update(PCIExpressHost *e,
int enable,
target_phys_addr_t addr, uint32_t size);
#endif /* PCIE_HOST_H */