* [Qemu-devel] [PATCH]: USB-OHCI v0.1 (not ready for users)
@ 2004-06-19 17:22 Gianni Tedesco
0 siblings, 0 replies; only message in thread
From: Gianni Tedesco @ 2004-06-19 17:22 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1.1: Type: text/plain, Size: 864 bytes --]
Hi,
I've started working on USB support for qemu, by implementing an OHCI
controller frontend, which is usable on any of the architectures qemu
supports.
It's not ready yet, but thought I'd share what I have after day 1,
developments are updated on:
http://www.scaramanga.co.uk/stuff/qemu-usb/
All this code does currently is emulate a decent amount of the HC
frontend, enough to setup the root hub. All that means is that in linux
you should see /proc/bus/usb/ nodes! :)
TODO:
o Implement ED/TD list handling. ie. make transfers work.
o Implement USB device API and test devices mouse/kb/proxy/storage
o Make root hub look like standard devices
--
// 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
[-- Attachment #1.2: qemu-usb-0.1.diff --]
[-- Type: text/x-patch, Size: 16830 bytes --]
diff -urN qemu.orig/Makefile.target qemu.usb/Makefile.target
--- qemu.orig/Makefile.target 2004-06-15 03:02:36.000000000 +0100
+++ qemu.usb/Makefile.target 2004-06-17 19:35:51.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 usb-ohci.o
ifeq ($(TARGET_ARCH), i386)
# Hardware support
diff -urN qemu.orig/hw/pc.c qemu.usb/hw/pc.c
--- qemu.orig/hw/pc.c 2004-06-15 03:02:36.000000000 +0100
+++ qemu.usb/hw/pc.c 2004-06-18 14:29:00.000000000 +0100
@@ -432,6 +432,9 @@
pci_ne2000_init(&nd_table[i]);
}
pci_piix3_ide_init(bs_table);
+
+ /* Shut up! */
+ usb_ohci_init("Apple KeyLargo/Intrepid", 0x106b, 0x003f, 0, -1);
} else {
nb_nics1 = nb_nics;
if (nb_nics1 > NE2000_NB_MAX)
diff -urN qemu.orig/hw/usb-ohci.c qemu.usb/hw/usb-ohci.c
--- qemu.orig/hw/usb-ohci.c 1970-01-01 01:00:00.000000000 +0100
+++ qemu.usb/hw/usb-ohci.c 2004-06-19 17:44:55.000000000 +0100
@@ -0,0 +1,586 @@
+/*
+ * QEMU USB OHCI Interface v0.1
+ * 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
+ *
+ * TODO:
+ * o Dynamically manage hub ports
+ * o Implement actual data transfers
+*/
+
+static const char copyright[] =
+ "usb-ohci.c: v0.1 Copyright (c) Gianni Tedesco 2004";
+
+#include "vl.h"
+#include "cpu.h"
+
+#if 0
+#define dprintf(...)
+#else
+#define dprintf printf
+#endif
+
+/* Number of Downstream Ports on the root hub, if you change this
+ * you need to add more status register words to the 'opreg' array
+ */
+#define OHCI_NDP 8
+
+struct ohci {
+ struct PCIDevice pci_dev;
+ target_phys_addr_t mem_base;
+ int mem;
+
+ /* OHCI state */
+ /* Control partition */
+ uint32_t ctl, status;
+ uint32_t intr_status;
+ uint32_t intr;
+
+ /* memory pointer partition */
+ uint32_t hcca;
+ uint32_t ctrl_head;
+ uint32_t bulk_head;
+
+ /* Frame counter partition */
+ uint32_t fmi;
+ uint32_t pstart;
+
+ /* Root Hub partition */
+ uint32_t rhdesc_a, rhdesc_b;
+ uint32_t rhstatus;
+ uint32_t rhport[OHCI_NDP];
+};
+
+struct ohci_hcca {
+ uint32_t intr[32];
+ uint16_t frame, pad;
+ uint32_t done;
+};
+
+extern struct ohci_opreg opreg[];
+struct ohci_opreg {
+ const char *name;
+ uint32_t (*read)(struct ohci *ohci, uint32_t ofs);
+ void (*write)(struct ohci *ohci, uint32_t ofs, uint32_t val);
+};
+
+#define OHCI_CTL_IR (1<<8)
+
+#define OHCI_STATUS_HCR (1<<0)
+#define OHCI_STATUS_SOC ((1<<6)|(1<<7))
+
+#define OHCI_INTR_SO (1<<0) /* Scheduling overrun */
+#define OHCI_INTR_WDH (1<<1) /* HcDoneHead writeback */
+#define OHCI_INTR_SF (1<<2) /* Start of frame */
+#define OHCI_INTR_RD (1<<3) /* Resume detect */
+#define OHCI_INTR_UE (1<<4) /* Unrecoverable error */
+#define OHCI_INTR_FNO (1<<5) /* Frame number overflow */
+#define OHCI_INTR_RHSC (1<<6) /* Root hub status change */
+#define OHCI_INTR_OC (1<<30) /* Ownership change */
+#define OHCI_INTR_MIE (1<<31) /* Master Interrupt Enable */
+
+#define OHCI_HCCA_SIZE 0x100
+#define OHCI_HCCA_MASK 0xffffff00
+
+#define OHCI_FMI_FI 0x00003fff
+#define OHCI_FMI_FSMPS 0xffff0000
+#define OHCI_FMI_FIT 0x80000000
+
+#define OHCI_LS_THRESH 0x628
+
+#define OHCI_RHA_NPS (1<<9)
+
+#define OHCI_RHS_LPS (1<<0)
+#define OHCI_RHS_OCI (1<<1)
+#define OHCI_RHS_DRWE (1<<15)
+#define OHCI_RHS_LPSC (1<<16)
+#define OHCI_RHS_OCIC (1<<17)
+#define OHCI_RHS_CRWE (1<<31)
+
+#define OHCI_PORT_CCS (1<<0)
+#define OHCI_PORT_PES (1<<1)
+#define OHCI_PORT_PSS (1<<2)
+#define OHCI_PORT_POCI (1<<3)
+#define OHCI_PORT_PRS (1<<4)
+#define OHCI_PORT_PPS (1<<8)
+#define OHCI_PORT_LSDA (1<<9)
+#define OHCI_PORT_CSC (1<<16)
+#define OHCI_PORT_PESC (1<<17)
+#define OHCI_PORT_PSSC (1<<18)
+#define OHCI_PORT_OCIC (1<<19)
+#define OHCI_PORT_PRSC (1<<20)
+
+/* Reset the controller */
+static void ohci_reset(struct ohci *ohci)
+{
+ int i;
+
+ ohci->ctl = ohci->ctl & OHCI_CTL_IR;
+ ohci->status = 0;
+ ohci->intr_status = 0;
+ ohci->intr = 0;
+
+ ohci->hcca = 0;
+ ohci->ctrl_head = ohci->bulk_head = 0;
+
+ /* FSMPS is marked TBD in OCHI 1.0, what gives ffs?
+ * I took the value linux sets ...
+ */
+ ohci->fmi = (0x2778 << 16) | 0x2edf;
+ ohci->pstart = 0;
+
+ ohci->rhdesc_a = OHCI_RHA_NPS | OHCI_NDP;
+ ohci->rhdesc_b = 0x0; /* Impl. specific */
+ ohci->rhstatus = 0;
+
+ for(i=0; i < OHCI_NDP; i++)
+ ohci->rhport[i] = 0;
+
+ dprintf("usb-ohci: Reset %s\n", ohci->pci_dev.name);
+}
+
+/* Convert a guest pointer to a qemu pointer */
+static uint8_t *reg2ptr(uint32_t addr, uint32_t len)
+{
+ if ( addr + len > phys_ram_size ) {
+ fprintf(stderr, "usb-ohci: OS tried to access outside RAM\n");
+ return NULL;
+ }
+
+ return phys_ram_base + len;
+}
+
+static uint32_t rev_r(struct ohci *ohci, uint32_t reg)
+{
+ return 0x10;
+}
+
+/* HcControlRegister */
+static uint32_t ctl_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->ctl;
+}
+
+static void ctl_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ ohci->ctl = val;
+}
+
+/* HcCommandStatus */
+static uint32_t status_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->status;
+}
+
+static void status_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ /* SOC is read-only */
+ val = (val & ~OHCI_STATUS_SOC);
+
+ /* "bits written as '0' remain unchanged in the register */
+ ohci->status |= val;
+
+ if ( ohci->status & OHCI_STATUS_HCR )
+ ohci_reset(ohci);
+}
+
+/* HcInterruptStatus */
+static uint32_t is_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->intr_status;
+}
+
+static void is_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ /* "The Host Controller Driver may clear specific bits in this
+ * register by writing '1' to bit positions to be cleared
+ */
+ ohci->intr_status &= ~val;
+}
+
+/* HcInterruptEnable */
+static uint32_t ie_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->intr;
+}
+
+static void ie_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ ohci->intr |= val;
+}
+
+/* HcInterruptDisable */
+static uint32_t id_r(struct ohci *ohci, uint32_t reg)
+{
+ return ~ohci->intr;
+}
+
+static void id_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ ohci->intr &= ~val;
+}
+
+/* Host Controller Communications Area */
+static uint32_t hcca_r(struct ohci *ohci, uint32_t reg)
+{
+ /* We return minimum allowed alignment, because we don't care */
+ if ( ohci->hcca & 0x1 ) {
+ ohci->hcca &= ~1;
+ return ~OHCI_HCCA_MASK;
+ }
+
+ return ohci->hcca;
+}
+
+static void hcca_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ /* HCCA alignment querying mechanism, low order bits
+ * are safe for us to use privately as they can't ever
+ * be set...
+ */
+ if ( val == 0xffffffff ) {
+ ohci->hcca |= 0x1;
+ return;
+ }
+
+ ohci->hcca = val & OHCI_HCCA_MASK;
+}
+
+/* Bulk and control head ED's */
+static uint32_t head_r(struct ohci *ohci, uint32_t reg)
+{
+ uint32_t *head;
+
+ switch ( reg ) {
+ case 8:
+ head = &ohci->ctrl_head;
+ break;
+ case 10:
+ head = &ohci->bulk_head;
+ break;
+ default:
+ abort();
+ }
+
+ return *head;
+}
+
+static void head_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ uint32_t *head;
+
+ switch ( reg ) {
+ case 8:
+ head = &ohci->ctrl_head;
+ break;
+ case 10:
+ head = &ohci->bulk_head;
+ break;
+ default:
+ abort();
+ }
+
+ if ( val & ~0x7 ) {
+ fprintf(stderr, "usb-ohci: Mis-aligned %s pointer\n",
+ opreg[reg].name);
+ }
+
+ *head = val & ~0x7;
+}
+
+/* Frame Interval */
+static uint32_t fmi_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->fmi;
+}
+
+static void fmi_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ ohci->fmi = val;
+}
+
+/* Periodic Start */
+static uint32_t pstart_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->pstart;
+}
+
+static void pstart_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ ohci->pstart = val;
+}
+
+/* LS threshold */
+static uint32_t lst_r(struct ohci *ohci, uint32_t reg)
+{
+ return OHCI_LS_THRESH;
+}
+
+static void lst_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ /* "Neither the Host Controller nor the Host Controller Driver
+ * are allowed to change this value." -- However, they are allowed
+ * to write to it (WTF?!), so supress a warning if they write the
+ * correct value.
+ */
+ if ( val != OHCI_LS_THRESH ) {
+ fprintf(stderr, "usb-ohci: HCD tried to write bad LS "
+ "threshold: 0x%x\n", val);
+ }
+}
+
+/* Root Hub Descriptor A */
+static uint32_t rhda_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->rhdesc_a;
+}
+
+static void rhda_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ /* All the optional features are not supported */
+ if ( ohci->rhdesc_a != val )
+ fprintf(stderr, "usb-ohci: %s: invalid write to "
+ "root decriptor A: "
+ "0x%.8x -> 0x%.8x\n",
+ ohci->pci_dev.name,
+ ohci->rhdesc_a, val);
+}
+
+/* Root Hub Descriptor B */
+static uint32_t rhdb_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->rhdesc_b;
+}
+
+static void rhdb_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ if ( ohci->rhdesc_b != val )
+ fprintf(stderr, "usb-ohci: %s: invalid write to "
+ "root decriptor B: "
+ "0x%.8x -> 0x%.8x\n",
+ ohci->pci_dev.name,
+ ohci->rhdesc_b, val);
+}
+
+/* Root hub status */
+static uint32_t rhstatus_r(struct ohci *ohci, uint32_t reg)
+{
+ return ohci->rhstatus;
+}
+
+static void rhstatus_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ int i;
+
+ /* write 1 to clear OCIC */
+ if ( val & OHCI_RHS_OCIC )
+ ohci->rhstatus &= ~OHCI_RHS_OCIC;
+
+ if ( val & OHCI_RHS_LPS ) {
+ for(i=0; i < OHCI_NDP; i++)
+ ohci->rhport[i] &= ~OHCI_PORT_PPS;
+ dprintf("usb-ohci: %s: powered down all ports\n",
+ ohci->pci_dev.name);
+ }
+
+ if ( val & OHCI_RHS_LPSC ) {
+ for(i=0; i < OHCI_NDP; i++)
+ ohci->rhport[i] |= OHCI_PORT_PPS;
+ dprintf("usb-ohci: %s: powered up all ports\n",
+ ohci->pci_dev.name);
+ }
+
+ if ( val & OHCI_RHS_DRWE ) {
+ /* TODO: set DeviceRemoveWakeupEnable */
+ }
+
+ if ( val & OHCI_RHS_CRWE ) {
+ /* TODO: clear DeviceRemoveWakeupEnable */
+ }
+}
+
+/* Root hub port status */
+static uint32_t ps_r(struct ohci *ohci, uint32_t reg)
+{
+ reg -= 21;
+ return ohci->rhport[reg];
+}
+
+static void ps_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+ reg -= 21;
+ //ohci->rhport[reg] = val;
+ return;
+}
+
+/* Register descriptor table */
+static struct ohci_opreg opreg[] = {
+ {.name = "HcRevision", .read = rev_r},
+ {.name = "HcControl", .read = ctl_r, .write = ctl_w},
+ {.name = "HcCommandStatus", .read = status_r, .write = status_w},
+ {.name = "HcInterruptStatus", .read = is_r, .write = is_w},
+ {.name = "HcInterruptEnable", .read = ie_r, .write = ie_w},
+ {.name = "HcInterruptDisable", .read = id_r, .write = id_w},
+ {.name = "HcHCCA", .read = hcca_r, .write = hcca_w},
+ {.name = "HcPeriodCurrentED"},
+ {.name = "HcControlHeadED", .read = head_r, .write = head_w},
+ {.name = "HcControlCurrentED"},
+ {.name = "HcBulkHeadED", .read = head_r, .write = head_w},
+ {.name = "HcBulkCurrentED"},
+ {.name = "HcDoneHead"},
+ {.name = "HcFmInterval", .read = fmi_r, .write = fmi_w},
+ {.name = "HcFmRemaining"},
+ {.name = "HcFmNumber"},
+ {.name = "HcPeriodicStart", .read = pstart_r, .write = pstart_w},
+ {.name = "HcLSThreshold", .read = lst_r, .write = lst_w},
+ {.name = "HcRhDescriptorA", .read = rhda_r, .write = rhda_w},
+ {.name = "HcRhDescriptorB", .read = rhdb_r, .write = rhdb_w},
+ {.name = "HcRhStatus", .read = rhstatus_r, .write = rhstatus_w},
+
+ /* The number of port status register depends on the definition
+ * of OHCI_NDP macro
+ */
+ {.name = "HcRhPortStatus[0]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[1]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[2]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[3]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[4]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[5]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[6]", .read = ps_r, .write = ps_w},
+ {.name = "HcRhPortStatus[7]", .read = ps_r, .write = ps_w},
+};
+
+static size_t num_opreg = sizeof(opreg)/sizeof(*opreg);
+
+static uint32_t mem_read(void *ptr, target_phys_addr_t addr)
+{
+ struct ohci *ohci = ptr;
+ uint32_t ofs = (addr - ohci->mem_base) >> 2;
+ struct ohci_opreg *reg;
+ uint32_t ret;
+
+ /* Only aligned reads are allowed on OHCI */
+ if ( addr & 3 ) {
+ fprintf(stderr, "usb-ohci: Mis-aligned read\n");
+ return 0xffffffff;
+ }
+
+ if ( ofs >= num_opreg ) {
+ fprintf(stderr, "usb-ohci: Trying to read register %u/%u\n",
+ ofs, num_opreg);
+ return 0xffffffff;
+ }
+
+ reg = &opreg[ofs];
+
+ if ( !reg->read ) {
+ fprintf(stderr, "usb-ohci: register %u (%s) is not readable\n",
+ ofs, reg->name);
+ return 0xffffffff;
+ }
+
+ ret = reg->read(ohci, ofs);
+
+ //dprintf("usb-ohci: read 0x%.8x from %u (%s)\n", ret, ofs, reg->name);
+
+ return ret;
+}
+
+static void mem_write(void *ptr, target_phys_addr_t addr, uint32_t value)
+{
+ struct ohci *ohci = ptr;
+ uint32_t ofs = (addr - ohci->mem_base) >> 2;
+ struct ohci_opreg *reg;
+
+ /* Only aligned writes are allowed on OHCI */
+ if ( addr & 3 ) {
+ fprintf(stderr, "usb-ohci: Mis-aligned write\n");
+ return;
+ }
+
+ if ( ofs >= num_opreg ) {
+ fprintf(stderr, "usb-ohci: Trying to write register %u/%u\n",
+ ofs, num_opreg);
+ return;
+ }
+
+ reg = &opreg[ofs];
+
+ if ( !reg->write ) {
+ fprintf(stderr, "usb-ohci: register %u (%s) is not writable\n",
+ ofs, reg->name);
+ return;
+ }
+
+ reg->write(ohci, ofs, value);
+
+ //dprintf("usb-ohci: write 0x%.8x to %u (%s)\n", value, ofs, reg->name);
+}
+
+/* Only dword reads are defined on OHCI register space */
+static CPUReadMemoryFunc *cpu_callback_read[3]={
+ NULL,
+ NULL,
+ mem_read,
+};
+
+/* Only dword writes are defined on OHCI register space */
+static CPUWriteMemoryFunc *cpu_callback_write[3]={
+ NULL,
+ NULL,
+ mem_write,
+};
+
+static void mapfunc(PCIDevice *pci_dev, int i,
+ uint32_t addr, uint32_t size, int type)
+{
+ struct ohci *ohci = (struct ohci *)pci_dev;
+ ohci->mem_base = addr;
+ cpu_register_physical_memory(addr, size, ohci->mem);
+}
+
+void usb_ohci_init(const char *name, uint16_t vid, uint16_t did,
+ int bus_num, int devfn)
+{
+ struct ohci *ohci;
+
+ ohci = (struct ohci *)pci_register_device(name, sizeof(*ohci),
+ bus_num, devfn,
+ NULL, NULL);
+ if ( ohci == NULL ) {
+ fprintf(stderr, "ohci: %s: Failed to register PCI device\n",
+ name);
+ return;
+ }
+
+ ohci->pci_dev.config[0x00] = vid & 0xff;
+ ohci->pci_dev.config[0x01] = (vid >> 8) & 0xff;
+ ohci->pci_dev.config[0x02] = did & 0xff;
+ ohci->pci_dev.config[0x03] = (did >> 8) & 0xff;
+ ohci->pci_dev.config[0x09] = 0x10; /* OHCI */
+ ohci->pci_dev.config[0x0a] = 0x3;
+ ohci->pci_dev.config[0x0b] = 0xc;
+ ohci->pci_dev.config[0x3d] = 0x01; /* interrupt pin 1 */
+
+ ohci->mem = cpu_register_io_memory(0,
+ cpu_callback_read,
+ cpu_callback_write,
+ ohci);
+ pci_register_io_region((struct PCIDevice *)ohci, 0, 256,
+ PCI_ADDRESS_SPACE_MEM, mapfunc);
+
+ ohci_reset(ohci);
+}
diff -urN qemu.orig/vl.h qemu.usb/vl.h
--- qemu.orig/vl.h 2004-06-15 03:02:36.000000000 +0100
+++ qemu.usb/vl.h 2004-06-17 19:32:29.000000000 +0100
@@ -510,6 +510,9 @@
void pci_bios_init(void);
void pci_info(void);
+void usb_ohci_init(const char *name, uint16_t vid, uint16_t did,
+ int bus_num, int devfn);
+
/* temporary: will be moved in platform specific file */
void pci_prep_init(void);
void pci_pmac_init(void);
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 189 bytes --]
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2004-06-19 17:25 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-06-19 17:22 [Qemu-devel] [PATCH]: USB-OHCI v0.1 (not ready for users) Gianni Tedesco
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).