>From f692fc6162eb9da1f0a628f3f97124e19146a404 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 26 Mar 2009 15:50:58 +0900 Subject: [PATCH] ioemu: passthrough: PCI IO space multiplex. use PCI IO space multiplexer driver and command register emulation twist Signed-off-by: Isaku Yamahata --- hw/iomulti.h | 51 ++++++++++++++ hw/pass-through.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pass-through.h | 10 +++ 3 files changed, 260 insertions(+), 0 deletions(-) create mode 100644 hw/iomulti.h diff --git a/hw/iomulti.h b/hw/iomulti.h new file mode 100644 index 0000000..b76b032 --- /dev/null +++ b/hw/iomulti.h @@ -0,0 +1,51 @@ +#ifndef PCI_IOMULTI_H +#define PCI_IOMULTI_H +/* + * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + */ + +struct pci_iomul_setup { + uint16_t segment; + uint8_t bus; + uint8_t dev; + uint8_t func; +}; + +struct pci_iomul_in { + uint8_t bar; + uint64_t offset; + + uint8_t size; + uint32_t value; +}; + +struct pci_iomul_out { + uint8_t bar; + uint64_t offset; + + uint8_t size; + uint32_t value; +}; + +#define PCI_IOMUL_SETUP _IOW ('P', 0, struct pci_iomul_setup) +#define PCI_IOMUL_DISABLE_IO _IO ('P', 1) +#define PCI_IOMUL_IN _IOWR('P', 2, struct pci_iomul_in) +#define PCI_IOMUL_OUT _IOW ('P', 3, struct pci_iomul_out) + +#endif /* PCI_IOMULTI_H */ diff --git a/hw/pass-through.c b/hw/pass-through.c index 710acbf..1377da2 100644 --- a/hw/pass-through.c +++ b/hw/pass-through.c @@ -88,7 +88,10 @@ #include "pci/pci.h" #include "pt-msi.h" #include "qemu-xen.h" +#include "iomulti.h" + #include +#include struct php_dev { struct pt_dev *pt_dev; @@ -1051,6 +1054,176 @@ static void pt_iomem_map(PCIDevice *d, int i, uint32_t e_phys, uint32_t e_size, } } +#define PCI_IOMUL_DEV_PATH "/dev/xen/pci_iomul" +static void pt_iomul_init(struct pt_dev *assigned_device, + uint8_t r_bus, uint8_t r_dev, uint8_t r_func) +{ + int fd = PCI_IOMUL_INVALID_FD; + struct pci_iomul_setup setup = { + .segment = 0, + .bus = r_bus, + .dev = r_dev, + .func = r_func, + }; + + fd = open(PCI_IOMUL_DEV_PATH, O_RDWR); + if ( fd < 0 ) { + PT_LOG("Error: %s can't open file %s: %s: 0x%x:0x%x.0x%x\n", + __func__, PCI_IOMUL_DEV_PATH, strerror(errno), + r_bus, r_dev, r_func); + fd = PCI_IOMUL_INVALID_FD; + } + + if ( fd >= 0 && ioctl(fd, PCI_IOMUL_SETUP, &setup) ) + { + PT_LOG("Error: %s: %s: setup io multiplexing failed! 0x%x:0x%x.0x%x\n", + __func__, strerror(errno), r_bus, r_dev, r_func); + close(fd); + fd = PCI_IOMUL_INVALID_FD; + } + + assigned_device->fd = fd; + if (fd != PCI_IOMUL_INVALID_FD) + PT_LOG("io mul: 0x%x:0x%x.0x%x\n", r_bus, r_dev, r_func); +} + +static void pt_iomul_free(struct pt_dev *assigned_device) +{ + if ( !pt_is_iomul(assigned_device) ) + return; + + close(assigned_device->fd); + assigned_device->fd = PCI_IOMUL_INVALID_FD; +} + +static void pt_iomul_get_bar_offset(struct pt_dev *assigned_device, + uint32_t addr, + uint8_t *bar, uint64_t *offset) +{ + for ( *bar = 0; *bar < PCI_BAR_ENTRIES; (*bar)++ ) + { + const struct pt_region* r = &assigned_device->bases[*bar]; + if ( r->bar_flag != PT_BAR_FLAG_IO ) + continue; + + if ( r->e_physbase <= addr && addr < r->e_physbase + r->e_size ) + { + *offset = addr - r->e_physbase; + return; + } + } +} + +static void pt_iomul_ioport_write(struct pt_dev *assigned_device, + uint32_t addr, uint32_t val, int size) +{ + uint8_t bar; + uint64_t offset; + struct pci_iomul_out out; + + if ( !assigned_device->io_enable ) + return; + + pt_iomul_get_bar_offset(assigned_device, addr, &bar, &offset); + if ( bar >= PCI_BAR_ENTRIES ) + { + PT_LOG("error: %s: addr 0x%x val 0x%x size %d\n", + __func__, addr, val, size); + return; + } + + out.bar = bar; + out.offset = offset; + out.size = size; + out.value = val; + if ( ioctl(assigned_device->fd, PCI_IOMUL_OUT, &out) ) + PT_LOG("error: %s: %s addr 0x%x size %d bar %d offset 0x%lx\n", + __func__, strerror(errno), addr, size, bar, offset); +} + +static uint32_t pt_iomul_ioport_read(struct pt_dev *assigned_device, + uint32_t addr, int size) +{ + uint8_t bar; + uint64_t offset; + struct pci_iomul_in in; + + if ( !assigned_device->io_enable ) + return -1; + + pt_iomul_get_bar_offset(assigned_device, addr, &bar, &offset); + if ( bar >= PCI_BAR_ENTRIES ) + { + PT_LOG("error: %s: addr 0x%x size %d\n", __func__, addr, size); + return -1; + } + + in.bar = bar; + in.offset = offset; + in.size = size; + if ( ioctl(assigned_device->fd, PCI_IOMUL_IN, &in) ) + { + PT_LOG("error: %s: %s addr 0x%x size %d bar %d offset 0x%lx\n", + __func__, strerror(errno), addr, size, bar, offset); + in.value = -1; + } + + return in.value; +} + +static void pt_iomul_ioport_write1(void *opaque, uint32_t addr, uint32_t val) +{ + pt_iomul_ioport_write((struct pt_dev *)opaque, addr, val, 1); +} + +static void pt_iomul_ioport_write2(void *opaque, uint32_t addr, uint32_t val) +{ + pt_iomul_ioport_write((struct pt_dev *)opaque, addr, val, 2); +} + +static void pt_iomul_ioport_write4(void *opaque, uint32_t addr, uint32_t val) +{ + pt_iomul_ioport_write((struct pt_dev *)opaque, addr, val, 4); +} + +static uint32_t pt_iomul_ioport_read1(void *opaque, uint32_t addr) +{ + return pt_iomul_ioport_read((struct pt_dev *)opaque, addr, 1); +} + +static uint32_t pt_iomul_ioport_read2(void *opaque, uint32_t addr) +{ + return pt_iomul_ioport_read((struct pt_dev *)opaque, addr, 2); +} + +static uint32_t pt_iomul_ioport_read4(void *opaque, uint32_t addr) +{ + return pt_iomul_ioport_read((struct pt_dev *)opaque, addr, 4); +} + +static void pt_iomul_ioport_map(struct pt_dev *assigned_device, + uint32_t old_ebase, uint32_t e_phys, + uint32_t e_size, int first_map) +{ + /* map only valid guest address (include 0) */ + if (e_phys != -1) + { + /* Create new mapping */ + register_ioport_write(e_phys, e_size, 1, + pt_iomul_ioport_write1, assigned_device); + register_ioport_write(e_phys, e_size, 2, + pt_iomul_ioport_write2, assigned_device); + register_ioport_write(e_phys, e_size, 4, + pt_iomul_ioport_write4, assigned_device); + register_ioport_read(e_phys, e_size, 1, + pt_iomul_ioport_read1, assigned_device); + register_ioport_read(e_phys, e_size, 2, + pt_iomul_ioport_read2, assigned_device); + register_ioport_read(e_phys, e_size, 4, + pt_iomul_ioport_read4, assigned_device); + } +} + /* Being called each time a pio region has been updated */ static void pt_ioport_map(PCIDevice *d, int i, uint32_t e_phys, uint32_t e_size, int type) @@ -1070,6 +1243,13 @@ static void pt_ioport_map(PCIDevice *d, int i, if ( e_size == 0 ) return; + if ( pt_is_iomul(assigned_device) ) + { + pt_iomul_ioport_map(assigned_device, + old_ebase, e_phys, e_size, first_map); + return; + } + if ( !first_map && old_ebase != -1 ) { /* Remove old mapping */ @@ -1800,6 +1980,7 @@ static void pt_bar_mapping(struct pt_dev *ptdev, int io_enable, int mem_enable) int ret = 0; int i; + ptdev->io_enable = !!io_enable; for (i=0; iio_regions[i]; @@ -2853,6 +3034,8 @@ static int pt_cmd_reg_read(struct pt_dev *ptdev, if ( ptdev->is_virtfn ) emu_mask |= PCI_COMMAND_MEMORY; + if ( pt_is_iomul(ptdev) ) + emu_mask |= PCI_COMMAND_IO; /* emulate word register */ valid_emu_mask = emu_mask & valid_mask; @@ -2999,6 +3182,8 @@ static int pt_cmd_reg_write(struct pt_dev *ptdev, if ( ptdev->is_virtfn ) emu_mask |= PCI_COMMAND_MEMORY; + if ( pt_is_iomul(ptdev) ) + emu_mask |= PCI_COMMAND_IO; /* modify emulate register */ writable_mask = emu_mask & ~reg->ro_mask & valid_mask; @@ -3029,6 +3214,12 @@ static int pt_cmd_reg_write(struct pt_dev *ptdev, pt_bar_mapping(ptdev, wr_value & PCI_COMMAND_IO, wr_value & PCI_COMMAND_MEMORY); + if ( pt_is_iomul(ptdev) ) + { + *value &= ~PCI_COMMAND_IO; + if (ioctl(ptdev->fd, PCI_IOMUL_DISABLE_IO)) + PT_LOG("error: %s: %s\n", __func__, strerror(errno)); + } return 0; } @@ -3624,6 +3815,11 @@ static int pt_cmd_reg_restore(struct pt_dev *ptdev, */ restorable_mask = reg->emu_mask & ~PCI_COMMAND_FAST_BACK; *value = PT_MERGE_VALUE(*value, dev_value, restorable_mask); + if ( pt_is_iomul(ptdev) ) { + *value &= ~PCI_COMMAND_IO; + if (ioctl(ptdev->fd, PCI_IOMUL_DISABLE_IO)) + PT_LOG("error: %s: %s\n", __func__, strerror(errno)); + } if (!ptdev->machine_irq) *value |= PCI_COMMAND_DISABLE_INTx; @@ -3807,6 +4003,7 @@ static struct pt_dev * register_real_device(PCIBus *e_bus, assigned_device->msi_trans_cap = msi_translate; assigned_device->power_mgmt = power_mgmt; assigned_device->is_virtfn = pt_dev_is_virtfn(pci_dev); + pt_iomul_init(assigned_device, r_bus, r_dev, r_func); /* Assign device */ machine_bdf.reg = 0; @@ -3970,6 +4167,8 @@ static int unregister_real_device(int slot) if ( (rc = xc_deassign_device(xc_handle, domid, bdf)) != 0) PT_LOG("Error: Revoking the device failed! rc=%d\n", rc); + pt_iomul_free(assigned_device); + /* mark this slot as free */ php_dev->valid = 0; php_dev->pt_dev = NULL; diff --git a/hw/pass-through.h b/hw/pass-through.h index a503e80..a87e5b8 100644 --- a/hw/pass-through.h +++ b/hw/pass-through.h @@ -226,8 +226,18 @@ struct pt_dev { unsigned power_mgmt:1; struct pt_pm_info *pm_state; /* PM virtualization */ unsigned is_virtfn:1; + + /* io port multiplexing */ +#define PCI_IOMUL_INVALID_FD (-1) + int fd; + unsigned io_enable:1; }; +static inline int pt_is_iomul(struct pt_dev *dev) +{ + return (dev->fd != PCI_IOMUL_INVALID_FD); +} + /* Used for formatting PCI BDF into cf8 format */ struct pci_config_cf8 { union { -- 1.6.0.2