qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] rediff of Gianni Tedesco's virtual ohci usb host controller


From: Lonnie Mendez
Subject: [Qemu-devel] rediff of Gianni Tedesco's virtual ohci usb host controller
Date: Thu, 9 Jun 2005 02:00:32 -0500
User-agent: KMail/1.8

 Hello list,

 Attached is a rediff of the virtual ohci host controller developed by Gianni 
Tedesco.  The primary changes are a fix in the handling of the HcFmInterval 
register so that the host controller driver toggles the Frame Interval Toggle 
bit instead of the host controller.  This was causing win9x guest to give up 
configuring the host controller on bootup and initial hc configuration.  The 
other change is to disable all eight ports on the root hub instead of leaving 
one enabled.  This was causing a hang when loading the ohci-hcd driver with 
linux 2.6 host kernel.

 I'll be messing with this code on/off.  My plans are to eventually use it to 
grab usb traffic from an old usb flatbed scanner which uses proprietary 
commands  (yes i could use usb-snoopy, but where is the fun in that?).

 The original patchset is here:
http://scaramanga.co.uk/stuff/qemu-usb/
diff -uNr qemu-orig/qemu-0.7.0/Makefile.target qemu-0.7.0/Makefile.target
--- qemu-orig/qemu-0.7.0/Makefile.target        2005-06-09 00:48:18.000000000 
-0500
+++ qemu-0.7.0/Makefile.target  2005-06-09 00:35:58.000000000 -0500
@@ -313,7 +313,7 @@
 endif
 
 # must use static linking to avoid leaving stuff in virtual address space
-VL_OBJS=vl.o osdep.o block.o readline.o monitor.o pci.o console.o
+VL_OBJS=vl.o osdep.o block.o readline.o monitor.o pci.o console.o usb-ohci.o
 VL_OBJS+=block-cow.o block-qcow.o aes.o block-vmdk.o block-cloop.o block-dmg.o 
block-bochs.o block-vpc.o
 
 SOUND_HW = sb16.o
diff -uNr qemu-orig/qemu-0.7.0/hw/pc.c qemu-0.7.0/hw/pc.c
--- qemu-orig/qemu-0.7.0/hw/pc.c        2005-04-27 15:52:05.000000000 -0500
+++ qemu-0.7.0/hw/pc.c  2005-06-09 00:37:42.000000000 -0500
@@ -500,6 +500,9 @@
     if (pci_enabled) {
         pci_bus = i440fx_init();
         piix3_init(pci_bus);
+ 
+        /* Shut up! */
+        usb_ohci_init("Apple KeyLargo/Intrepid", 0x106b, 0x003f, pci_bus, -1);
     } else {
         pci_bus = NULL;
     }
diff -uNr qemu-orig/qemu-0.7.0/hw/usb-ohci.c qemu-0.7.0/hw/usb-ohci.c
--- qemu-orig/qemu-0.7.0/hw/usb-ohci.c  1969-12-31 18:00:00.000000000 -0600
+++ qemu-0.7.0/hw/usb-ohci.c    2005-06-09 00:45:29.000000000 -0500
@@ -0,0 +1,1163 @@
+/*
+ * QEMU USB OHCI Interface 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
+ *
+ * TODO:
+ *  o Service general transfer descriptors
+ *  o Service isochronous/interrupt lists
+ *  o Allocate bandwidth in frames properly
+ *  o Disable timers when nothing needs to be done, or remove timer usage
+ *    all together.
+ *  o Handle unrecoverable errors properly
+ *  o USB device API and generic hub code
+ *  o Mouse/Keyboard/Storage devices
+ *  o BIOS work to boot from USB storage
+ *  o libusb based host proxy (with logging)
+*/
+
+static const char copyright[] =
+       "usb-ohci.c: v0.2 Copyright (c) Gianni Tedesco 2004";
+
+#include "vl.h"
+#include "cpu.h"
+
+#if 0
+#define dprintf(...)
+#else
+#define dprintf printf
+#endif
+
+/* This causes frames to occur 1000x slower */
+#define OHCI_TIME_WARP 1
+
+/* 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
+
+static int64_t usb_frame_time;
+static int64_t usb_bit_time;
+
+struct ohci {
+       struct PCIDevice pci_dev;
+       target_phys_addr_t mem_base;
+       int mem;
+
+       QEMUTimer *eof_timer;
+       int64_t sof_time;
+
+       /* 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, ctrl_cur;
+       uint32_t bulk_head, bulk_cur;
+       uint32_t per_cur;
+       uint32_t done;
+
+       /* Frame counter partition */
+       uint32_t fsmps:15;
+       uint32_t fit:1;
+       uint32_t fi:14;
+       uint32_t frt:1;
+       uint16_t frame_number;
+       uint16_t padding;
+       uint32_t pstart;
+
+       /* Root Hub partition */
+       uint32_t rhdesc_a, rhdesc_b;
+       uint32_t rhstatus;
+       uint32_t rhport[OHCI_NDP];
+};
+
+/* Host Controller Communications Area */
+struct ohci_hcca {
+       uint32_t intr[32];
+       uint16_t frame, pad;
+       uint32_t done;
+};
+
+/* endpoint descriptor */
+struct ohci_ed {
+       uint32_t fa:7, en:4, d:2, s:1, k:1, f:1, mps:11, res0:5;
+       uint32_t res1:4, tailp:28;
+       uint32_t h:1, c:1, res2:2, headp:28;
+       uint32_t res3:4, next:28;
+};
+
+/* General transfer descriptor */
+struct ohci_td {
+       uint32_t res0:17, r:1, dp:2, di:3, t:2, ec:2, cc:4;
+       uint32_t cbp;
+       uint32_t res1:4, next:28;
+       uint32_t be;
+};
+
+/* Isochronous transfer descriptor */
+struct ohci_isochronous_td {
+       uint32_t cc:4, res0:1, fc:3, di:3, res1:5, sf:16;
+       uint32_t bp0:20, res2:12;
+       uint32_t next;
+       uint32_t be;
+       uint16_t ofs[8]; /* 1 0 3 2 5 5 7 6 */
+};
+
+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);
+};
+
+/* Global USB stuff - move to a seperate file eventually */
+#define USB_HZ                         12000000
+#define USB_REQ_GET_STATUS             0x00
+#define USB_REQ_CLEAR_FEATURE          0x01
+#define USB_REQ_SET_FEATURE            0x03
+#define USB_REQ_SET_ADDRESS            0x05
+#define USB_REQ_GET_DESCRIPTOR         0x06
+#define USB_REQ_SET_DESCRIPTOR         0x07
+#define USB_REQ_GET_CONFIGURATION      0x08
+#define USB_REQ_SET_CONFIGURATION      0x09
+#define USB_REQ_GET_INTERFACE          0x0A
+#define USB_REQ_SET_INTERFACE          0x0B
+#define USB_REQ_SYNCH_FRAME            0x0C
+
+/* OHCI Local stuff */
+#define OHCI_CTL_CBSR          ((1<<0)|(1<<1))
+#define OHCI_CTL_PLE           (1<<2)
+#define OHCI_CTL_IE            (1<<3)
+#define OHCI_CTL_CLE           (1<<4)
+#define OHCI_CTL_BLE           (1<<5)
+#define OHCI_CTL_HCFS          ((1<<6)|(1<<7))
+#define  OHCI_USB_RESET                0x00
+#define  OHCI_USB_RESUME       0x40
+#define  OHCI_USB_OPERATIONAL  0x80
+#define  OHCI_USB_SUSPEND      0xc0
+#define OHCI_CTL_IR            (1<<8)
+#define OHCI_CTL_RWC           (1<<9)
+#define OHCI_CTL_RWE           (1<<10)
+
+#define OHCI_STATUS_HCR                (1<<0)
+#define OHCI_STATUS_CLF                (1<<1)
+#define OHCI_STATUS_BLF                (1<<2)
+#define OHCI_STATUS_OCR                (1<<3)
+#define OHCI_STATUS_SOC                ((1<<6)|(1<<7))
+
+#define OHCI_INTR_SO           (1<<0) /* Scheduling overrun */
+#define OHCI_INTR_WD           (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_FR_RT             (1<<31)
+
+#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)
+#define OHCI_PORT_WTC          (OHCI_PORT_CSC|OHCI_PORT_PESC|OHCI_PORT_PSSC|\
+                               OHCI_PORT_OCIC|OHCI_PORT_PRSC)
+
+#define OHCI_TD_DIR_SETUP      0x0
+#define OHCI_TD_DIR_OUT                0x1
+#define OHCI_TD_DIR_IN         0x2
+#define OHCI_TD_DIR_RESERVED   0x3
+
+#define OHCI_CC_NO_ERROR       0x00
+
+/* Reset the controller */
+static void ohci_reset(struct ohci *ohci)
+{
+       int i;
+
+       ohci->ctl = 0;
+       ohci->status = 0;
+       ohci->intr_status = 0;
+       ohci->intr = OHCI_INTR_MIE;
+
+       ohci->hcca = 0;
+       ohci->ctrl_head = ohci->ctrl_cur = 0;
+       ohci->bulk_head = ohci->bulk_cur = 0;
+       ohci->per_cur = 0;
+       ohci->done = 0;
+
+       /* FSMPS is marked TBD in OCHI 1.0, what gives ffs?
+        * I took the value linux sets ...
+        */
+       ohci->fsmps = 0x2778;
+       ohci->fi = 0x2edf;
+       ohci->fit = 0;
+       ohci->frt = 0;
+       ohci->frame_number = 0;
+       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;
+/*     ohci->rhport[0] = 
OHCI_PORT_CCS|OHCI_PORT_PES|OHCI_PORT_PPS|OHCI_PORT_CSC; */
+
+       dprintf("usb-ohci: Reset %s\n", ohci->pci_dev.name);
+}
+
+/* Update IRQ levels */
+static inline void ohci_intr_update(struct ohci *ohci)
+{
+       int level = 0;
+
+       if ( (ohci->intr & OHCI_INTR_MIE) &&
+               (ohci->intr_status & ohci->intr) )
+               level = 1;
+
+       pci_set_irq(&ohci->pci_dev, 0, level);
+       if ( level )
+               dprintf("usb-ohci: Fired off interrupt 0x%.8x\n",
+                       ohci->intr_status & ohci->intr);
+}
+
+/* Set an interrupt */
+static inline void ohci_set_interrupt(struct ohci *ohci, uint32_t intr)
+{
+       ohci->intr_status |= intr;
+       ohci_intr_update(ohci);
+}
+
+/* Get an array of dwords from main memory */
+static inline int get_dwords(uint32_t addr, uint32_t *buf, int num)
+{
+       int i;
+
+       for(i=0; i < num; i++, buf++, addr += sizeof(*buf)) {
+               cpu_physical_memory_rw(addr, (uint8_t *)buf, sizeof(*buf), 0);
+               *buf = tswap32(*buf);
+       }
+
+       return 1;
+}
+
+/* Put an array of dwords in to main memory */
+static inline int put_dwords(uint32_t addr, uint32_t *buf, int num)
+{
+       int i;
+
+       for(i=0; i < num; i++, buf++, addr += sizeof(*buf)) {
+               uint32_t tmp = tswap32(*buf);
+               cpu_physical_memory_rw(addr, (uint8_t *)&tmp, sizeof(tmp), 1);
+       }
+
+       return 1;
+}
+
+static inline int ohci_read_ed(uint32_t addr, struct ohci_ed *ed)
+{
+       return get_dwords(addr, (uint32_t *)ed, sizeof(*ed) >> 2);
+}
+
+static inline int ohci_read_td(uint32_t addr, struct ohci_td *td)
+{
+       return get_dwords(addr, (uint32_t *)td, sizeof(*td) >> 2);
+}
+
+static inline int ohci_put_ed(uint32_t addr, struct ohci_ed *ed)
+{
+       return put_dwords(addr, (uint32_t *)ed, sizeof(*ed) >> 2);
+}
+
+static inline int ohci_put_td(uint32_t addr, struct ohci_td *td)
+{
+       return put_dwords(addr, (uint32_t *)td, sizeof(*td) >> 2);
+}
+
+/* Service a transport descriptor  */
+static void ohci_service_td(struct ohci *ohci,
+                               struct ohci_ed *ed,
+                               struct ohci_td *td,
+                               uint32_t tdp)
+{
+       int dir;
+       size_t len = 0;
+       uint8_t *buf = NULL;
+       char *str = NULL;
+
+       switch ( ed->d ) {
+       case OHCI_TD_DIR_OUT:
+       case OHCI_TD_DIR_IN:
+               dir = ed->d;
+               break;
+       default:
+               dir = td->dp;
+               break;
+       }
+
+       if ( dir == OHCI_TD_DIR_RESERVED ) {
+               printf("RESERVED DIRECTION\n");
+               return;
+       }
+
+       if ( td->cbp && td->be ) {
+               len = (td->be - td->cbp) + 1;
+               if ( len ) {
+                       buf = malloc(len);
+                       if  ( buf )
+                               cpu_physical_memory_rw(td->cbp, buf, len, 0);
+               }
+
+               /* XXX: Check max packet size and warn if OS is buggy */
+       }
+
+       /* TODO: Send/Recv the packet to/from device */
+       switch ( dir ) {
+       case OHCI_TD_DIR_IN:
+               str = "in";
+               break;
+       case OHCI_TD_DIR_OUT:
+               str = "out";
+               break;
+       case OHCI_TD_DIR_SETUP:
+               str = "setup";
+               break;
+       }
+
+       dprintf(" TD @ 0x%.8x %u bytes %s cbp=0x%.8x be=0x%.8x\n",
+               tdp, len, str, td->cbp, td->be);
+
+       if ( len ) {
+               int i;
+               printf("  data:");
+               for(i = 0; i < len; i++)
+                       printf(" %.2x", buf[i]);
+               printf("\n");
+       }
+
+       /* Writeback */
+       td->cbp = 0;
+       td->t = 0x2 | ~(td->t & 0x1);
+       td->cc = OHCI_CC_NO_ERROR;
+
+       ed->h = 0;
+       ed->c = td->t & 0x1;
+
+       /* Retire this TD */
+       td->next = ohci->done >> 4;
+       ohci->done = ed->headp << 4;
+       ohci_put_td(ed->headp << 4, td);
+
+       if ( buf )
+               free(buf);
+}
+
+/* Service a control/bulk list */
+static void ohci_service_cb_list(struct ohci *ohci,
+                                       uint32_t head,
+                                       uint32_t cur,
+                                       uint32_t lf)
+{
+       struct ohci_ed ed;
+       struct ohci_td td;
+
+       /* Clear list fill flag */
+       ohci->status &= ~lf;
+
+       if ( head == 0 )
+               return;
+
+next_ed:
+       if ( cur == 0 ) {
+               cur = head;
+       }else if ( cur == head ) {
+               return;
+       }
+
+       if ( !ohci_read_ed(cur, &ed) ) {
+               printf("EEK\n");
+               return;
+       }
+
+       dprintf("ED @ 0x%.8x fa=%u en=%u d=%u s=%u k=%u f=%u mps=%u "
+               "tailp=0x%.8x h=%u c=%u head=0x%.8x next=0x%.8x\n", cur,
+               ed.fa, ed.en, ed.d, ed.s, ed.k, ed.f, ed.mps,
+               ed.tailp, ed.h, ed.c, ed.headp, ed.next);
+
+       while(ed.headp != ed.tailp) {
+               uint32_t nxt;
+
+               /* Update filled bit */
+               ohci->status |= ~lf;
+
+               if ( !ohci_read_td(ed.headp << 4, &td) ) {
+                       printf("EEK2\n");
+                       goto next_ed;
+               }
+
+               nxt = td.next;
+               ohci_service_td(ohci, &ed, &td, ed.headp << 4);
+               ed.headp = nxt;
+       }
+
+       ohci_put_ed(cur, &ed);
+       goto next_ed;
+}
+
+/* Generate a SOF event, and set a timer for EOF */
+static void ohci_sof(struct ohci *ohci)
+{
+       ohci->sof_time = qemu_get_clock(vm_clock);
+       qemu_mod_timer(ohci->eof_timer, ohci->sof_time + usb_frame_time);
+       ohci_set_interrupt(ohci, OHCI_INTR_SF);
+}
+
+/* Do frame processing on frame boundary */
+static void ohci_frame_boundary(void *opaque)
+{
+       struct ohci *ohci = opaque;
+       struct ohci_hcca hcca;
+
+       cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 0);
+
+       /* Process all the lists at the end of the frame */
+       if ( ohci->ctl & OHCI_CTL_PLE ) {
+               dprintf("usb-ohci: %s: servicing periodic list\n",
+                               ohci->pci_dev.name);
+       }
+       if ( ohci->ctl & OHCI_CTL_CLE ) {
+               dprintf("usb-ohci: %s: servicing control list\n",
+                               ohci->pci_dev.name);
+               ohci_service_cb_list(ohci,
+                                       ohci->ctrl_head,
+                                       ohci->ctrl_cur,
+                                       OHCI_STATUS_CLF);
+       }
+
+       if ( ohci->ctl & OHCI_CTL_BLE ) {
+               dprintf("usb-ohci: %s: servicing bulk list\n",
+                               ohci->pci_dev.name);
+               ohci_service_cb_list(ohci,
+                                       ohci->bulk_head,
+                                       ohci->bulk_cur,
+                                       OHCI_STATUS_BLF);
+       }
+
+       /* Frame boundary, so do EOF stuf here */
+       ohci->frt = ohci->fit;
+
+       /* XXX: endianness */
+       hcca.frame = tswap32(++ohci->frame_number);
+
+       if ( !(ohci->intr_status & OHCI_INTR_WD) ) {
+               hcca.done = ohci->done;
+               ohci->done = 0;
+               ohci_set_interrupt(ohci, OHCI_INTR_WD);
+       }
+
+       /* Do SOF stuff here */
+       ohci_sof(ohci);
+
+       /* Writeback HCCA */
+       cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 1);
+}
+
+/* Start sending SOF tokens across the USB bus, lists are processed in
+ * next frame
+ */
+static int ohci_bus_start(struct ohci *ohci)
+{
+       ohci->eof_timer = qemu_new_timer(vm_clock,
+                                       ohci_frame_boundary,
+                                       ohci);
+
+       if ( ohci->eof_timer == NULL ) {
+               fprintf(stderr, "usb-ohci: %s: qemu_new_timer failed\n",
+                       ohci->pci_dev.name);
+               /* TODO: Signal unrecoverable error */
+               return 0;
+       }
+
+       dprintf("usb-ohci: %s: USB Operational\n", ohci->pci_dev.name);
+
+       ohci_sof(ohci);
+
+       return 1;
+}
+
+/* Stop sending SOF tokens on the bus */
+static void ohci_bus_stop(struct ohci *ohci)
+{
+       if ( ohci->eof_timer )
+               qemu_del_timer(ohci->eof_timer);
+}
+
+/* Sets a flag in a port status register but only set it if the port is
+ * connected, if not set ConnectStatusChange flag. If flag is enabled
+ * return 1.
+ */
+static int ohci_port_set_if_connected(struct ohci *ohci, int i, uint32_t val)
+{
+       int ret = 1;
+
+       /* writing a 0 has no effect */
+       if ( val == 0 )
+               return 0;
+
+       /* If CurrentConnectStatus is cleared we set
+        * ConnectStatusChange
+        */
+       if ( !(ohci->rhport[i] & OHCI_PORT_CCS) ) {
+               ohci->rhport[i] |= OHCI_PORT_CSC;
+               if ( ohci->rhstatus & OHCI_RHS_DRWE ) {
+                       /* TODO: CSC is a wakeup event */
+               }
+               return 0;
+       }
+
+       if ( ohci->rhport[i] & val )
+               ret = 0;
+
+       /* set the bit */
+       ohci->rhport[i] |= val;
+
+       return ret;
+}
+
+/* Set the frame interval - frame interval toggle is manipulated by the hcd 
only */
+static void ohci_set_frame_interval(struct ohci *ohci, uint16_t val)
+{
+       val &= OHCI_FMI_FI;
+
+       if ( val != ohci->fi ) {
+               dprintf("usb-ohci: %s: FrameInterval = 0x%x (%u)\n",
+                       ohci->pci_dev.name, ohci->fi, ohci->fi);
+       }
+
+       ohci->fi = val;
+}
+
+static void ohci_port_power(struct ohci *ohci, int i, int p)
+{
+       if ( p ) {
+               ohci->rhport[i] |= OHCI_PORT_PPS;
+       }else{
+               ohci->rhport[i] &= ~(OHCI_PORT_PPS|
+                                       OHCI_PORT_CCS|
+                                       OHCI_PORT_PSS|
+                                       OHCI_PORT_PRS);
+       }
+}
+
+/* Revision register */
+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)
+{
+       uint32_t old_state;
+       uint32_t new_state;
+
+       old_state = ohci->ctl & OHCI_CTL_HCFS;
+       ohci->ctl = val;
+       new_state = ohci->ctl & OHCI_CTL_HCFS;
+
+       /* no state change */
+       if ( old_state == new_state )
+               return;
+
+       switch ( new_state ) {
+       case OHCI_USB_OPERATIONAL:
+               ohci_bus_start(ohci);
+               break;
+       case OHCI_USB_SUSPEND:
+               ohci_bus_stop(ohci);
+               dprintf("usb-ohci: %s: USB Suspended\n", ohci->pci_dev.name);
+               break;
+       case OHCI_USB_RESUME:
+               dprintf("usb-ohci: %s: USB Resume\n", ohci->pci_dev.name);
+               break;
+       case OHCI_USB_RESET:
+               dprintf("usb-ohci: %s: USB Reset\n", ohci->pci_dev.name);
+               break;
+       }
+}
+
+/* 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;
+       ohci_intr_update(ohci);
+}
+
+/* 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;
+       ohci_intr_update(ohci);
+}
+
+/* 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;
+       ohci_intr_update(ohci);
+}
+
+/* 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 7:
+               head = &ohci->per_cur;
+               break;
+       case 8:
+               head = &ohci->ctrl_head;
+               break;
+       case 9:
+               head = &ohci->ctrl_cur;
+               break;
+       case 10:
+               head = &ohci->bulk_head;
+               break;
+       case 11:
+               head = &ohci->bulk_cur;
+               break;
+       default:
+               abort();
+       }
+
+       return *head;
+}
+
+static void head_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+       uint32_t *head;
+
+       switch ( reg ) {
+       case 7:
+               head = &ohci->per_cur;
+               break;
+       case 8:
+               head = &ohci->ctrl_head;
+               break;
+       case 9:
+               head = &ohci->ctrl_cur;
+               break;
+       case 10:
+               head = &ohci->bulk_head;
+               break;
+       case 11:
+               head = &ohci->bulk_cur;
+               break;
+       default:
+               abort();
+       }
+
+       if ( val & 0x7 )
+               fprintf(stderr, "usb-ohci: Mis-aligned %s pointer\n",
+                       opreg[reg].name);
+
+       *head = val & ~0x7;
+}
+
+/* DoneHead */
+static uint32_t done_r(struct ohci *ohci, uint32_t reg)
+{
+       return ohci->done;
+}
+
+/* Frame Interval */
+static uint32_t fmi_r(struct ohci *ohci, uint32_t reg)
+{
+       return (ohci->fit << 31) | (ohci->fsmps << 16) | (ohci->fi);
+}
+
+static void fmi_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+       ohci->fsmps = (val & OHCI_FMI_FSMPS) >> 16;
+       ohci->fit = (val & OHCI_FMI_FIT) >> 31;
+       ohci_set_frame_interval(ohci, 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;
+}
+
+/* Frame number */
+static uint32_t fn_r(struct ohci *ohci, uint32_t reg)
+{
+       return ohci->frame_number;
+}
+
+/* Frame remaining */
+static uint32_t fr_r(struct ohci *ohci, uint32_t reg)
+{
+       uint16_t fr;
+       int64_t tks;
+
+       if ( (ohci->ctl & OHCI_CTL_HCFS) != OHCI_USB_OPERATIONAL )
+               return (ohci->frt << 31);
+
+       /* Being in USB operational state guarnatees sof_time was
+        * set already.
+        */
+       tks = qemu_get_clock(vm_clock) - ohci->sof_time;
+
+       /* avoid muldiv if possible */
+       if ( tks >= usb_frame_time )
+               return (ohci->frt << 31);
+
+       tks = muldiv64(1, tks, usb_bit_time);
+       fr = (uint16_t)(ohci->fi - tks);
+
+       return (ohci->frt << 31) | fr;
+}
+
+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)
+{
+       uint32_t old_state;
+
+       old_state = ohci->rhstatus;
+
+       /* write 1 to clear OCIC */
+       if ( val & OHCI_RHS_OCIC )
+               ohci->rhstatus &= ~OHCI_RHS_OCIC;
+
+       if ( val & OHCI_RHS_LPS ) {
+               int i;
+
+               for(i=0; i < OHCI_NDP; i++)
+                       ohci_port_power(ohci, i, 0);
+               dprintf("usb-ohci: %s: powered down all ports\n",
+                       ohci->pci_dev.name);
+       }
+
+       if ( val & OHCI_RHS_LPSC ) {
+               int i;
+
+               for(i=0; i < OHCI_NDP; i++)
+                       ohci_port_power(ohci, i, 1);
+               dprintf("usb-ohci: %s: powered up all ports\n",
+                       ohci->pci_dev.name);
+       }
+
+       if ( val & OHCI_RHS_DRWE )
+               ohci->rhstatus |= OHCI_RHS_DRWE;
+
+       if ( val & OHCI_RHS_CRWE )
+               ohci->rhstatus &= ~OHCI_RHS_DRWE;
+
+       if ( old_state != ohci->rhstatus )
+               ohci_set_interrupt(ohci, OHCI_INTR_RHSC);
+}
+
+/* Root hub port status */
+static uint32_t ps_r(struct ohci *ohci, uint32_t reg)
+{
+       int i = reg - 21;
+       return ohci->rhport[i] | OHCI_PORT_PPS;
+}
+
+static void ps_w(struct ohci *ohci, uint32_t reg, uint32_t val)
+{
+       int i = reg - 21;
+       uint32_t old_state;
+
+       old_state = ohci->rhport[i];
+
+       /* Write to clear CSC, PESC, PSSC, OCIC, PRSC */
+       if ( val & OHCI_PORT_WTC )
+               ohci->rhport[i] &= ~(val & OHCI_PORT_WTC);
+
+       if ( val & OHCI_PORT_CCS )
+               ohci->rhport[i] &= ~OHCI_PORT_PES;
+
+       ohci_port_set_if_connected(ohci, i, val & OHCI_PORT_PES);
+
+       if ( ohci_port_set_if_connected(ohci, i, val & OHCI_PORT_PSS) )
+               dprintf("usb-ohci: %s: port %i: SUSPEND\n",
+                       ohci->pci_dev.name, i);
+
+       if ( ohci_port_set_if_connected(ohci, i, val & OHCI_PORT_PRS) ) {
+               ohci->rhport[i] &= ~OHCI_PORT_PRS;
+               ohci->rhport[i] |= OHCI_PORT_PRSC;
+               dprintf("usb-ohci: %s: port %i: RESET\n",
+                       ohci->pci_dev.name, i);
+       }
+
+       /* Invert order here to ensure in ambiguous case, device is
+        * powered up...
+        */
+       if ( val & OHCI_PORT_LSDA )
+               ohci_port_power(ohci, i, 0);
+       if ( val & OHCI_PORT_PPS )
+               ohci_port_power(ohci, i, 1);
+
+       if ( old_state != ohci->rhport[i] )
+               ohci_set_interrupt(ohci, OHCI_INTR_RHSC);
+
+       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", .read = head_r, .write = head_w},
+       {.name = "HcControlHeadED", .read = head_r, .write = head_w},
+       {.name = "HcControlCurrentED", .read = head_r, .write = head_w},
+       {.name = "HcBulkHeadED", .read = head_r, .write = head_w},
+       {.name = "HcBulkCurrentED", .read = head_r, .write = head_w},
+       {.name = "HcDoneHead", .read = done_r},
+       {.name = "HcFmInterval", .read = fmi_r, .write = fmi_w},
+       {.name = "HcFmRemaining", .read = fr_r},
+       {.name = "HcFmNumber", .read = fn_r},
+       {.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);
+
+//     if ( ofs < 18 )
+//             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;
+       }
+
+//     if ( ofs < 18 )
+//             dprintf("usb-ohci: write 0x%.8x to %u (%s)\n",
+//                     value, ofs, reg->name);
+
+       reg->write(ohci, ofs, value);
+}
+
+/* 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,
+                       struct PCIBus *bus, int devfn)
+{
+       struct ohci *ohci;
+
+       if ( usb_frame_time == 0 ) {
+#if OHCI_TIME_WARP
+               usb_frame_time = ticks_per_sec;
+               usb_bit_time = muldiv64(1, ticks_per_sec, USB_HZ/1000);
+#else
+               usb_frame_time = muldiv64(1, ticks_per_sec, 1000);
+               if ( ticks_per_sec >= USB_HZ ) {
+                       usb_bit_time = muldiv64(1, ticks_per_sec, USB_HZ);
+               }else{
+                       usb_bit_time = 1;
+               }
+#endif
+               dprintf("usb-ohci: usb_bit_time=%lli usb_frame_time=%lli\n",
+                       usb_frame_time, usb_bit_time);
+       }
+
+       ohci = (struct ohci *)pci_register_device(bus, name, sizeof(*ohci),
+                                                       devfn,
+                                                       NULL, NULL);
+       if ( ohci == NULL ) {
+               fprintf(stderr, "usb-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 -uNr qemu-orig/qemu-0.7.0/vl.h qemu-0.7.0/vl.h
--- qemu-orig/qemu-0.7.0/vl.h   2005-06-09 00:48:18.000000000 -0500
+++ qemu-0.7.0/vl.h     2005-06-09 00:37:42.000000000 -0500
@@ -524,6 +524,9 @@
 void pci_bios_init(void);
 void pci_info(void);
 
+void usb_ohci_init(const char *name, uint16_t vid, uint16_t did,
+                       struct PCIBus *bus, int devfn);
+
 /* temporary: will be moved in platform specific file */
 PCIBus *pci_prep_init(void);
 struct openpic_t;

reply via email to

[Prev in Thread] Current Thread [Next in Thread]