From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.33) id 1BVvCt-0000FR-DR for qemu-devel@nongnu.org; Thu, 03 Jun 2004 12:36:43 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.33) id 1BVvCs-0000F6-6O for qemu-devel@nongnu.org; Thu, 03 Jun 2004 12:36:42 -0400 Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.33) id 1BVvCs-0000Ew-0i for qemu-devel@nongnu.org; Thu, 03 Jun 2004 12:36:42 -0400 Received: from [213.146.130.142] (helo=trantor.org.uk) by monty-python.gnu.org with esmtp (TLSv1:DES-CBC3-SHA:168) (Exim 4.34) id 1BVvCK-00007v-3N for qemu-devel@nongnu.org; Thu, 03 Jun 2004 12:36:08 -0400 From: Gianni Tedesco Content-Type: multipart/mixed; boundary="=-SS2FdZES5qfzaexhbtYx" Date: Thu, 03 Jun 2004 17:35:11 +0100 Message-Id: <1086280511.21922.118.camel@sherbert> Mime-Version: 1.0 Subject: [Qemu-devel] [PATCH]: PCI Host/Proxy version 2. Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org --=-SS2FdZES5qfzaexhbtYx Content-Type: text/plain Content-Transfer-Encoding: 7bit Hi, Attached is the latest version of my PCI Host/Proxy patch. It allows you to use *real* PCI devices in qemu. Improvements over the last version include: o Bugfixes o Support for memory resources o Support for ROM resources Still to do: o Support IRQ delivery (I need to dig up a kernel patch for this and get everything together). o Test fully on real hardware o Documentation (thats already a WIP) o Logging support o AGP support o Support for PCI-DMA with patched guest OS (any win2k hackers out there wanna help on this?). Until this is done the code won't be ready for normal users o Lock the PCI device Right now this code is able to get my 3Com 905C running under Linux and win98, the interface can be configured and link status monitered, tho no tx/rx. You can see in this new screenshot: http://www.scaramanga.co.uk/stuff/qemu-pciproxy-0.2.png Happy hacking kids ;) -- // Gianni Tedesco (gianni at scaramanga dot co dot uk) lynx --source www.scaramanga.co.uk/scaramanga.asc | gpg --import 8646BE7D: 6D9F 2287 870E A2C9 8F60 3A3C 91B5 7669 8646 BE7D --=-SS2FdZES5qfzaexhbtYx Content-Disposition: attachment; filename=qemu-pciproxy-0.2.diff Content-Type: text/x-patch; name=qemu-pciproxy-0.2.diff; charset=UTF-8 Content-Transfer-Encoding: 7bit diff -urN qemu.orig/Makefile.target qemu/Makefile.target --- qemu.orig/Makefile.target 2004-06-03 16:30:57.000000000 +0100 +++ qemu/Makefile.target 2004-06-03 16:31:34.000000000 +0100 @@ -232,7 +232,7 @@ endif # must use static linking to avoid leaving stuff in virtual address space -VL_OBJS=vl.o osdep.o block.o monitor.o pci.o +VL_OBJS=vl.o osdep.o block.o monitor.o pci.o pciproxy.o ifeq ($(TARGET_ARCH), i386) # Hardware support diff -urN qemu.orig/configure qemu/configure --- qemu.orig/configure 2004-06-03 16:30:57.000000000 +0100 +++ qemu/configure 2004-06-03 16:31:13.000000000 +0100 @@ -390,6 +390,11 @@ elif test -f "/usr/include/byteswap.h" ; then echo "#define HAVE_BYTESWAP_H 1" >> $config_h fi +if test -f "/usr/include/sys/io.h"; then + echo "#define HAVE_SYS_IO_H 1" >> $config_h + echo "#define HAVE_IOPL 1" >> $config_h + echo "#define HAVE_IOPERM 1" >> $config_h +fi if test "$gdbstub" = "yes" ; then echo "CONFIG_GDBSTUB=yes" >> $config_mak echo "#define CONFIG_GDBSTUB 1" >> $config_h diff -urN qemu.orig/hw/pc.c qemu/hw/pc.c --- qemu.orig/hw/pc.c 2004-06-03 16:30:57.000000000 +0100 +++ qemu/hw/pc.c 2004-06-03 16:31:42.000000000 +0100 @@ -389,6 +389,8 @@ if (pci_enabled) { i440fx_init(); piix3_init(); + if ( pciproxy_devpath ) + pciproxy_add_device(pciproxy_devpath); } /* init basic PC hardware */ diff -urN qemu.orig/hw/pciproxy.c qemu/hw/pciproxy.c --- qemu.orig/hw/pciproxy.c 1970-01-01 01:00:00.000000000 +0100 +++ qemu/hw/pciproxy.c 2004-06-03 17:23:03.000000000 +0100 @@ -0,0 +1,675 @@ +/* + * QEMU PCI Host proxy v0.2 + * Copyright (c) 2004 Gianni Tedesco + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * This code is dedicated towards my muse, my inspiration, Broadcom Corporation. + * Without your spite I would not have been compelled to write this code. + * + * TODO: + * o Map IRQs, fixup PCIIOC_SIGIRQ. + * o Data logging support + * o Test with PCMCIA + * o AGP support +*/ + +static const char copyright[] = + "pciproxy.c: v0.2 Copyright (c) Gianni Tedesco 2004"; + +#include "vl.h" +#include +#include +#if HAVE_SYS_IO_H +#include +#endif + +#define PROC_DEVICES "/proc/bus/pci/devices" + +/* Ioctls for /proc/bus/pci/X/Y nodes. */ +#define PCIIOC_BASE ('P' << 24 | 'C' << 16 | 'I' << 8) +/* Get controller for PCI device. */ +#define PCIIOC_CONTROLLER (PCIIOC_BASE | 0x00) +/* Set mmap state to I/O space. */ +#define PCIIOC_MMAP_IS_IO (PCIIOC_BASE | 0x01) +/* Set mmap state to MEM space. */ +#define PCIIOC_MMAP_IS_MEM (PCIIOC_BASE | 0x02) +/* Enable/disable write-combining. */ +#define PCIIOC_WRITE_COMBINE (PCIIOC_BASE | 0x03) +/* Send user-defined signal for irqs */ +#define PCIIOC_SIGIRQ (PCIIOC_BASE | 0x04) +struct pci_sigirq { + int sig; + void *ptr; +}; + +struct pciproxy { + struct PCIDevice pcidev; + uint32_t len[PCI_NUM_REGIONS]; + uint32_t qemu_map[PCI_NUM_REGIONS]; + void *map[PCI_NUM_REGIONS]; + uint32_t io_allowed; + uint32_t bar[PCI_NUM_REGIONS]; + int dev, bus, fn; + int fd; + int iomem_region; +}; + +static __inline__ const char *sys_err(void) +{ + return strerror(errno); +} + +static int easy_explode(char *str, char split, char **toks, int max_toks) +{ + char *tmp; + int tok; + int state; + + for(tmp=str,state=tok=0; *tmp && tok < max_toks; tmp++) { + if ( state == 0 ) { + if ( *tmp == split ) { + toks[tok++] = NULL; + }else if ( !isspace(*tmp) ) { + state = 1; + toks[tok++] = tmp; + } + }else if ( state == 1 ) { + if ( *tmp == split || isspace(*tmp) ) { + *tmp = '\0'; + state = 0; + } + } + } + + return tok; +} + +static int resource_lookup(struct pciproxy *pci, + target_phys_addr_t begin, + target_phys_addr_t end, + int type) +{ + int i; + + for(i=0; i < PCI_NUM_REGIONS; i++) { + target_phys_addr_t b, e; + + if ( pci->bar[i] == 0 ) + continue; + if ( type != PCI_ADDRESS_SPACE_IO ) { + if ( i != PCI_ROM_SLOT && + pci->bar[i] & PCI_ADDRESS_SPACE_IO ) + continue; + }else{ + if ( !(pci->bar[i] & PCI_ADDRESS_SPACE_IO) ) + continue; + } + + b = pci->qemu_map[i]; + e = b + pci->len[i]; + + if ( begin >= b && end <= e ) { + return i; + } + } + + fprintf(stderr, "resource_lookup: %x/%u not found\n", begin, end-begin); + return -1; +} + +static uint32_t mem_read(struct pciproxy *pci, target_phys_addr_t addr, int len) +{ + uint32_t ret, ofs; + int i; + + i = resource_lookup(pci, addr, addr + len, PCI_ADDRESS_SPACE_MEM); + if ( i < 0 ) + return 0xffffffff; + + ofs = addr - pci->qemu_map[i]; + switch ( len ) { + case 1: + ret = *(uint8_t *)(pci->map[i] + ofs); + break; + case 2: + ret = *(uint16_t *)(pci->map[i] + ofs); + break; + case 4: + ret = *(uint32_t *)(pci->map[i] + ofs); + break; + default: + return 0xffffffff; + } + + return ret; +} + +static void mem_write(struct pciproxy *pci, + target_phys_addr_t addr, + int len, uint32_t value) +{ + uint32_t ofs; + int i; + + i = resource_lookup(pci, addr, addr + len, PCI_ADDRESS_SPACE_MEM); + if ( i < 0 ) + return; + + ofs = addr - pci->qemu_map[i]; + switch ( len ) { + case 1: + *(uint8_t *)(pci->map[i] + ofs) = value; + break; + case 2: + *(uint16_t *)(pci->map[i] + ofs) = value; + break; + case 4: + *(uint32_t *)(pci->map[i] + ofs) = value; + break; + default: + return; + } +} + +static uint32_t mem_readb(void *ptr, target_phys_addr_t addr) +{ + return mem_read(ptr, addr, 1); +} +static uint32_t mem_readw(void *ptr, target_phys_addr_t addr) +{ + return mem_read(ptr, addr, 2); +} +static uint32_t mem_readl(void *ptr, target_phys_addr_t addr) +{ + return mem_read(ptr, addr, 4); +} +static void mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t value) +{ + return mem_write(ptr, addr, 1, value); +} +static void mem_writew(void *ptr, target_phys_addr_t addr, uint32_t value) +{ + return mem_write(ptr, addr, 2, value); +} +static void mem_writel(void *ptr, target_phys_addr_t addr, uint32_t value) +{ + return mem_write(ptr, addr, 4, value); +} + +static CPUReadMemoryFunc *cpu_callback_read[3]={ + mem_readb, + mem_readw, + mem_readl, +}; +static CPUWriteMemoryFunc *cpu_callback_write[3]={ + mem_writeb, + mem_writew, + mem_writel, +}; + +static int register_cbmem(struct pciproxy *pci, int i, + target_phys_addr_t start_addr) +{ + unsigned long size; + + size = (pci->len[i] + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK; + + if ( pci->iomem_region == 0 ) + pci->iomem_region = cpu_register_io_memory(0, + cpu_callback_read, + cpu_callback_write, + pci); + + cpu_register_physical_memory(start_addr, size, pci->iomem_region); + + pci->qemu_map[i] = start_addr; + + return 1; +} + +/* Uses /proc/bus/devices to grab the length of the resources on our PCI + * device in a platform independant way. + */ +static int peek_resources(struct pciproxy *pci) +{ + FILE *f; + char buf[512]; + char *tok[18]; + uint32_t num; + int n, i; + + f = fopen(PROC_DEVICES, "r"); + if ( f == NULL ) { + fprintf(stderr, "%s: open(): %s\n", PROC_DEVICES, sys_err()); + return 0; + } + + while ( fgets(buf, sizeof(buf), f) ) { + char *ptr; + + ptr = strchr(buf, '\r'); + if ( ptr == NULL ) + ptr = strchr(buf, '\n'); + if ( ptr == NULL ) + break; + *ptr = '\0'; + + n = easy_explode(buf, '\0', tok, 18); + if ( n < 17 ) + continue; + + num = strtoul(tok[0], NULL, 16); + if ( pci->bus != ((num & 0xff00) >> 8) ) + continue; + if ( pci->dev != ((num & 0x00ff) >> 3) ) + continue; + if ( pci->fn != (num & 0x7) ) + continue; + + for(i=0; i < PCI_NUM_REGIONS; i++) { + num = strtoul(tok[10 + i], NULL, 16); + pci->len[i] = num; + } + + fclose(f); + return 1; + } + + fclose(f); + return 0; +} + +/* Final fallback path if mmap and ioperm fails, we just use iopl to raise + * our I/O priv level so that we can do any I/O we like. + */ +static int map_with_iopl(struct pciproxy *pci, int i) +{ +#if HAVE_IOPL + int ret; + + ret = iopl(3); + if ( ret == 0 ) { + pci->io_allowed |= (1<bus, pci->dev, pci->fn, i, sys_err()); +#endif + return 0; +} + +/* First fallback path if mmap failes, try to use ioperm for granular + * permission granting to the I/O ports. + */ +static int map_with_ioperm(struct pciproxy *pci, int i, uint32_t bar) +{ +#if HAVE_IOPERM + u_int32_t from, to; + int ret; + + from = bar & ~0x3; + to = from + pci->len[i]; + + ret = ioperm(from, to, 1); + if ( ret < 0 ) + return map_with_iopl(pci, i); + pci->io_allowed |= (1<bus, pci->dev, pci->fn, i, sys_err()); + } + + return 0; +#endif +} + + +static int resource_is_mapped(struct pciproxy *pci, int i) +{ + if ( pci->map[i] ) + return 1; + if ( pci->io_allowed & (1<len[i]; + bar = pci->bar[i]; + + if ( i == PCI_ROM_SLOT ) { + prot = PROT_READ; + }else{ + prot = PROT_READ|PROT_WRITE; + } + + if ( i == PCI_ROM_SLOT || !(bar & PCI_ADDRESS_SPACE_IO) ) { + flags = (bar & 0xf); + base = (bar & ~0xf); + ioc = PCIIOC_MMAP_IS_MEM; + }else{ + flags = (bar & 0x3); + base = (bar & ~0x3); + ioc = PCIIOC_MMAP_IS_IO; + } + + /* Round base address down to the nearest page boundary and length + * up to the the nearest page boundary in order that we may mmap + * the resource range. + * + * XXX: Assumes page size is a power of two. (big deal) + */ + pgmask = sysconf(_SC_PAGESIZE) - 1; + + ofs = base & pgmask; + base &= ~pgmask; + len += pgmask; + len &= ~pgmask; + + if ( ioctl(pci->fd, ioc) ) { + fprintf(stderr, "%.2x:%.2x.%x ioctl() slot %i: %s\n", + pci->bus, pci->dev, pci->fn, i, sys_err()); + return 0; + } + + map = mmap(NULL, len, prot, MAP_SHARED, pci->fd, base); + if ( (map == MAP_FAILED) ) { + int ret = 0 ; + + if ( i != PCI_ROM_SLOT && (flags & PCI_ADDRESS_SPACE_IO ) ) + ret = map_with_ioperm(pci, i, bar); + + if ( ret == 0 ) + fprintf(stderr, "%.2x:%.2x.%x mmap() slot %i: %s\n", + pci->bus, pci->dev, pci->fn, i, sys_err()); + + return ret; + } + + pci->map[i] = map + ofs; + return 1; +} + +/* Construct the filename of the /proc/bus/pci/XX/YY.Z file to open for our + * device and open it. + */ +static int open_proc_bus_pci(struct pciproxy *pci) +{ + char path[1024]; + ssize_t ret; + + snprintf(path, sizeof(path), "/proc/bus/pci/%.2x/%.2x.%x", + pci->bus, pci->dev, pci->fn); + + pci->fd = open(path, O_RDWR); + if ( pci->fd < 0 ) { + fprintf(stderr, "%s: open(): %s\n", path, sys_err()); + return 0; + } + + ret = pread(pci->fd, pci->pcidev.config, sizeof(pci->pcidev.config), 0); + if ( ret != (ssize_t)sizeof(pci->pcidev.config) ) { + fprintf(stderr, "%s: pread %u bytes of config space: %s\n", + path, sizeof(pci->pcidev.config), sys_err()); +intr: + if ( close(pci->fd) && errno == EINTR ) + goto intr; + return 0; + } + + /* Directly read the bar because on some architectures like PPC, the + * kernels mapped base doesn't correspond to the address we need + * to mmap on this FD. Also we need this info later on... + */ + pci->bar[0] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x10)); + pci->bar[1] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x14)); + pci->bar[2] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x18)); + pci->bar[3] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x1c)); + pci->bar[4] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x20)); + pci->bar[5] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x24)); + pci->bar[6] = le32_to_cpu(*(uint32_t *)(pci->pcidev.config + 0x30)); + + return 1; +} + +static void iowrite(void *ptr, uint32_t addr, uint32_t data, uint32_t len) +{ + struct pciproxy *pci = (struct pciproxy *)ptr; + uint32_t ofs; + int i; + + i = resource_lookup(pci, addr, addr + len, PCI_ADDRESS_SPACE_IO); + if ( i < 0 ) + return; + + ofs = addr - pci->qemu_map[i]; + addr = pci->bar[i] & ~0x03; + + if ( pci->map[i] ) { + switch( len ) { + case 1: + *(uint8_t *)(pci->map[i] + ofs) = data; + break; + case 2: + *(uint16_t *)(pci->map[i] + ofs) = data; + break; + case 4: + *(uint32_t *)(pci->map[i] + ofs) = data; + break; + default: + return; + } + }else{ +#if HAVE_SYS_IO_H + if ( !(pci->io_allowed & (1<qemu_map[i]; + addr = pci->bar[i] & ~0x03; + + if ( pci->map[i] ) { + switch( len ) { + case 1: + ret = *(uint8_t *)(pci->map[i] + ofs); + break; + case 2: + ret = *(uint16_t *)(pci->map[i] + ofs); + break; + case 4: + ret = *(uint32_t *)(pci->map[i] + ofs); + break; + default: + return 0xffffffff; + } +#if HAVE_SYS_IO_H + }else{ + if ( !(pci->io_allowed & (1<len[i]; + + if ( !resource_is_mapped(pci, i) && + !map_resource(pci, i) ) + return; + + pci->qemu_map[i] = addr; + + register_ioport_read(addr, len, 4, ioreadl, pci); + register_ioport_read(addr, len, 2, ioreadw, pci); + register_ioport_read(addr, len, 1, ioreadb, pci); + register_ioport_write(addr, len, 4, iowritel, pci); + register_ioport_write(addr, len, 2, iowritew, pci); + register_ioport_write(addr, len, 1, iowriteb, pci); + + printf("Mapped io slot #%i [0x%.8x] 0x%.8x (len=%u)\n", + i, pci->bar[i], addr, len); + }else{ + if ( !resource_is_mapped(pci, i) && + !map_resource(pci, i) ) + return; + + register_cbmem(pci, i, addr); + + printf("Mapped mem slot #%i [0x%.8x] 0x%.8x\n", + i, pci->bar[i], pci->qemu_map[i]); + } +} + +void pciproxy_add_device(char *devpath) +{ + char *tok[3] = {NULL}; + struct pciproxy *pci; + int i; + + easy_explode(devpath, '.', &tok[1], 2); + if ( easy_explode(devpath, ':', &tok[0], 2) == 1 ) { + tok[1] = tok[0]; + tok[0] = NULL; + } + for(i=0; i < 3; i++) + if ( tok[i] == NULL ) + tok[i] = "0"; + + pci = (struct pciproxy *)pci_register_device("pciproxy", + sizeof(*pci), 0, -1, + NULL, NULL); + + if ( pci == NULL ) { + fprintf(stderr, "pciproxy: Adding device failed\n"); + return; + } + + pci->bus = strtol(tok[0], NULL, 16); + pci->dev = strtol(tok[1], NULL, 16); + pci->fn = strtol(tok[2], NULL, 16); + + printf("Adding PCI Host Proxy device: %.2x:%.2x:%x\n", + pci->bus, pci->dev, pci->fn); + + peek_resources(pci); + + if ( !open_proc_bus_pci(pci) ) + return; + + for(i=0; i < PCI_NUM_REGIONS; i++) { + uint32_t type; + + type = (pci->bar[i] & PCI_ADDRESS_SPACE_IO) ? + PCI_ADDRESS_SPACE_IO : + PCI_ADDRESS_SPACE_MEM ; + + if ( i == PCI_ROM_SLOT ) + type = PCI_ADDRESS_SPACE_MEM; + + if ( pci->bar[i] ) + pci_register_io_region((struct PCIDevice *)pci, + i, pci->len[i], type, mapfunc); + } + + /* TODO: Setup sigirg if present, if not, warn */ +} diff -urN qemu.orig/vl.c qemu/vl.c --- qemu.orig/vl.c 2004-06-03 16:30:57.000000000 +0100 +++ qemu/vl.c 2004-06-03 17:23:19.000000000 +0100 @@ -130,6 +130,7 @@ int pci_enabled = 0; int prep_enabled = 0; int rtc_utc = 1; +char *pciproxy_devpath = NULL; /***********************************************************/ /* x86 ISA bus support */ @@ -2018,6 +2019,7 @@ QEMU_OPTION_pci, QEMU_OPTION_prep, QEMU_OPTION_localtime, + QEMU_OPTION_pciproxy, }; typedef struct QEMUOption { @@ -2068,6 +2070,7 @@ #ifdef TARGET_PPC { "prep", 0, QEMU_OPTION_prep }, #endif + { "pciproxy", HAS_ARG, QEMU_OPTION_pciproxy }, { "localtime", 0, QEMU_OPTION_localtime }, { NULL }, }; @@ -2348,6 +2351,9 @@ case QEMU_OPTION_localtime: rtc_utc = 0; break; + case QEMU_OPTION_pciproxy: + pciproxy_devpath = (char *)optarg; + break; } } } diff -urN qemu.orig/vl.h qemu/vl.h --- qemu.orig/vl.h 2004-06-03 16:30:57.000000000 +0100 +++ qemu/vl.h 2004-06-03 16:31:27.000000000 +0100 @@ -437,6 +437,10 @@ void pci_pmac_init(void); void pci_ppc_bios_init(void); +/* pciproxy.c */ +extern char *pciproxy_devpath; +void pciproxy_add_device(char *devpath); + /* vga.c */ #define VGA_RAM_SIZE (4096 * 1024) --=-SS2FdZES5qfzaexhbtYx--