#include "qemu-timer.h"
#include "usb.h"
#include "irq.h"
+#include "hw.h"
/* Common USB registers */
#define MUSB_HDRC_FADDR 0x00 /* 8-bit */
#define MGC_M_ULPI_REGCTL_COMPLETE 0x02
#define MGC_M_ULPI_REGCTL_REG 0x01
-static void musb_attach(USBPort *port, USBDevice *dev);
+/* #define MUSB_DEBUG */
-typedef struct {
+#ifdef MUSB_DEBUG
+#define TRACE(fmt,...) fprintf(stderr, "%s@%d: " fmt "\n", __FUNCTION__, \
+ __LINE__, ##__VA_ARGS__)
+#else
+#define TRACE(...)
+#endif
+
+
+static void musb_attach(USBPort *port);
+static void musb_detach(USBPort *port);
+static void musb_child_detach(USBPort *port, USBDevice *child);
+static void musb_schedule_cb(USBPort *port, USBPacket *p);
+static void musb_async_cancel_device(MUSBState *s, USBDevice *dev);
+
+static USBPortOps musb_port_ops = {
+ .attach = musb_attach,
+ .detach = musb_detach,
+ .child_detach = musb_child_detach,
+ .complete = musb_schedule_cb,
+};
+
+static USBBusOps musb_bus_ops = {
+};
+
+typedef struct MUSBPacket MUSBPacket;
+typedef struct MUSBEndPoint MUSBEndPoint;
+
+struct MUSBPacket {
+ USBPacket p;
+ MUSBEndPoint *ep;
+ int dir;
+};
+
+struct MUSBEndPoint {
uint16_t faddr[2];
uint8_t haddr[2];
uint8_t hport[2];
uint8_t fifosize;
int timeout[2]; /* Always in microframes */
- uint32_t *buf[2];
+ uint8_t *buf[2];
int fifolen[2];
int fifostart[2];
int fifoaddr[2];
- USBPacket packey[2];
+ MUSBPacket packey[2];
int status[2];
int ext_size[2];
MUSBState *musb;
USBCallback *delayed_cb[2];
QEMUTimer *intv_timer[2];
-} MUSBEndPoint;
+};
struct MUSBState {
qemu_irq *irqs;
int setup_len;
int session;
- uint32_t buf[0x2000];
+ uint8_t buf[0x8000];
/* Duplicating the world since 2008!... probably we should have 32
* logical, single endpoints instead. */
MUSBEndPoint ep[16];
-} *musb_init(qemu_irq *irqs)
+};
+
+struct MUSBState *musb_init(qemu_irq *irqs)
{
- MUSBState *s = qemu_mallocz(sizeof(*s));
+ MUSBState *s = g_malloc0(sizeof(*s));
int i;
s->irqs = irqs;
s->ep[i].maxp[1] = 0x40;
s->ep[i].musb = s;
s->ep[i].epnum = i;
+ usb_packet_init(&s->ep[i].packey[0].p);
+ usb_packet_init(&s->ep[i].packey[1].p);
}
- usb_bus_new(&s->bus, NULL /* FIXME */);
- usb_register_port(&s->bus, &s->port, s, 0, musb_attach);
+ usb_bus_new(&s->bus, &musb_bus_ops, NULL /* FIXME */);
+ usb_register_port(&s->bus, &s->port, s, 0, &musb_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
return s;
}
}
/* Attach or detach a device on our only port. */
-static void musb_attach(USBPort *port, USBDevice *dev)
+static void musb_attach(USBPort *port)
{
MUSBState *s = (MUSBState *) port->opaque;
- USBDevice *curr;
-
- port = &s->port;
- curr = port->dev;
- if (dev) {
- if (curr) {
- usb_attach(port, NULL);
- /* TODO: signal some interrupts */
- }
+ musb_intr_set(s, musb_irq_vbus_request, 1);
+ musb_session_update(s, 0, s->session);
+}
- musb_intr_set(s, musb_irq_vbus_request, 1);
+static void musb_detach(USBPort *port)
+{
+ MUSBState *s = (MUSBState *) port->opaque;
- /* Send the attach message to device */
- usb_send_msg(dev, USB_MSG_ATTACH);
- } else if (curr) {
- /* Send the detach message */
- usb_send_msg(curr, USB_MSG_DETACH);
+ musb_async_cancel_device(s, port->dev);
- musb_intr_set(s, musb_irq_disconnect, 1);
- }
+ musb_intr_set(s, musb_irq_disconnect, 1);
+ musb_session_update(s, 1, s->session);
+}
- port->dev = dev;
+static void musb_child_detach(USBPort *port, USBDevice *child)
+{
+ MUSBState *s = (MUSBState *) port->opaque;
- musb_session_update(s, !!curr, s->session);
+ musb_async_cancel_device(s, child);
}
-static inline void musb_cb_tick0(void *opaque)
+static void musb_cb_tick0(void *opaque)
{
MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
- ep->delayed_cb[0](&ep->packey[0], opaque);
+ ep->delayed_cb[0](&ep->packey[0].p, opaque);
}
-static inline void musb_cb_tick1(void *opaque)
+static void musb_cb_tick1(void *opaque)
{
MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
- ep->delayed_cb[1](&ep->packey[1], opaque);
+ ep->delayed_cb[1](&ep->packey[1].p, opaque);
}
#define musb_cb_tick (dir ? musb_cb_tick1 : musb_cb_tick0)
-static inline void musb_schedule_cb(USBPacket *packey, void *opaque, int dir)
+static void musb_schedule_cb(USBPort *port, USBPacket *packey)
{
- MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
+ MUSBPacket *p = container_of(packey, MUSBPacket, p);
+ MUSBEndPoint *ep = p->ep;
+ int dir = p->dir;
int timeout = 0;
if (ep->status[dir] == USB_RET_NAK)
else if (ep->interrupt[dir])
timeout = 8;
else
- return musb_cb_tick(opaque);
+ return musb_cb_tick(ep);
if (!ep->intv_timer[dir])
- ep->intv_timer[dir] = qemu_new_timer(vm_clock, musb_cb_tick, opaque);
+ ep->intv_timer[dir] = qemu_new_timer_ns(vm_clock, musb_cb_tick, ep);
- qemu_mod_timer(ep->intv_timer[dir], qemu_get_clock(vm_clock) +
+ qemu_mod_timer(ep->intv_timer[dir], qemu_get_clock_ns(vm_clock) +
muldiv64(timeout, get_ticks_per_sec(), 8000));
}
-static void musb_schedule0_cb(USBPacket *packey, void *opaque)
-{
- return musb_schedule_cb(packey, opaque, 0);
-}
-
-static void musb_schedule1_cb(USBPacket *packey, void *opaque)
-{
- return musb_schedule_cb(packey, opaque, 1);
-}
-
static int musb_timeout(int ttype, int speed, int val)
{
#if 1
hw_error("bad interval\n");
}
-static inline void musb_packet(MUSBState *s, MUSBEndPoint *ep,
+static void musb_packet(MUSBState *s, MUSBEndPoint *ep,
int epnum, int pid, int len, USBCallback cb, int dir)
{
int ret;
ep->type[idx] >> 6, ep->interval[idx]);
ep->interrupt[dir] = ttype == USB_ENDPOINT_XFER_INT;
ep->delayed_cb[dir] = cb;
- cb = dir ? musb_schedule1_cb : musb_schedule0_cb;
- ep->packey[dir].pid = pid;
/* A wild guess on the FADDR semantics... */
- ep->packey[dir].devaddr = ep->faddr[idx];
- ep->packey[dir].devep = ep->type[idx] & 0xf;
- ep->packey[dir].data = (void *) ep->buf[idx];
- ep->packey[dir].len = len;
- ep->packey[dir].complete_cb = cb;
- ep->packey[dir].complete_opaque = ep;
+ usb_packet_setup(&ep->packey[dir].p, pid, ep->faddr[idx],
+ ep->type[idx] & 0xf);
+ usb_packet_addbuf(&ep->packey[dir].p, ep->buf[idx], len);
+ ep->packey[dir].ep = ep;
+ ep->packey[dir].dir = dir;
if (s->port.dev)
- ret = s->port.dev->info->handle_packet(s->port.dev, &ep->packey[dir]);
+ ret = usb_handle_packet(s->port.dev, &ep->packey[dir].p);
else
ret = USB_RET_NODEV;
}
ep->status[dir] = ret;
- usb_packet_complete(&ep->packey[dir]);
+ musb_schedule_cb(&s->port, &ep->packey[dir].p);
}
static void musb_tx_packet_complete(USBPacket *packey, void *opaque)
if (ep->status[1] == USB_RET_STALL) {
ep->status[1] = 0;
- packey->len = 0;
+ packey->result = 0;
ep->csr[1] |= MGC_M_RXCSR_H_RXSTALL;
if (!epnum)
* Data-errors in Isochronous. */
if (ep->interrupt[1])
return musb_packet(s, ep, epnum, USB_TOKEN_IN,
- packey->len, musb_rx_packet_complete, 1);
+ packey->iov.size, musb_rx_packet_complete, 1);
ep->csr[1] |= MGC_M_RXCSR_DATAERROR;
if (!epnum)
/* TODO: check len for over/underruns of an OUT packet? */
/* TODO: perhaps make use of e->ext_size[1] here. */
- packey->len = ep->status[1];
+ packey->result = ep->status[1];
if (!(ep->csr[1] & (MGC_M_RXCSR_H_RXSTALL | MGC_M_RXCSR_DATAERROR))) {
ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY;
if (!epnum)
ep->csr[0] |= MGC_M_CSR0_RXPKTRDY;
- ep->rxcount = packey->len; /* XXX: MIN(packey->len, ep->maxp[1]); */
+ ep->rxcount = packey->result; /* XXX: MIN(packey->len, ep->maxp[1]); */
/* In DMA mode: assert DMA request for this EP */
}
musb_rx_intr_set(s, epnum, 1);
}
+static void musb_async_cancel_device(MUSBState *s, USBDevice *dev)
+{
+ int ep, dir;
+
+ for (ep = 0; ep < 16; ep++) {
+ for (dir = 0; dir < 2; dir++) {
+ if (s->ep[ep].packey[dir].p.owner != dev) {
+ continue;
+ }
+ usb_cancel_packet(&s->ep[ep].packey[dir].p);
+ /* status updates needed here? */
+ }
+ }
+}
+
static void musb_tx_rdy(MUSBState *s, int epnum)
{
MUSBEndPoint *ep = s->ep + epnum;
int pid;
int total, valid = 0;
-
+ TRACE("start %d, len %d", ep->fifostart[0], ep->fifolen[0] );
ep->fifostart[0] += ep->fifolen[0];
ep->fifolen[0] = 0;
}
/* If the packet is not fully ready yet, wait for a next segment. */
- if (epnum && (ep->fifostart[0] << 2) < total)
+ if (epnum && (ep->fifostart[0]) < total)
return;
if (!valid)
- total = ep->fifostart[0] << 2;
+ total = ep->fifostart[0];
pid = USB_TOKEN_OUT;
if (!epnum && (ep->csr[0] & MGC_M_CSR0_H_SETUPPKT)) {
pid = USB_TOKEN_SETUP;
- if (total != 8)
- printf("%s: illegal SETUPPKT length of %i bytes\n",
- __FUNCTION__, total);
+ if (total != 8) {
+ TRACE("illegal SETUPPKT length of %i bytes", total);
+ }
/* Controller should retry SETUP packets three times on errors
* but it doesn't make sense for us to do that. */
}
/* If we already have a packet, which didn't fit into the
* 64 bytes of the FIFO, only move the FIFO start and return. (Obsolete) */
- if (ep->packey[1].pid == USB_TOKEN_IN && ep->status[1] >= 0 &&
- (ep->fifostart[1] << 2) + ep->rxcount <
- ep->packey[1].len) {
- ep->fifostart[1] += ep->rxcount >> 2;
+ if (ep->packey[1].p.pid == USB_TOKEN_IN && ep->status[1] >= 0 &&
+ (ep->fifostart[1]) + ep->rxcount <
+ ep->packey[1].p.iov.size) {
+ TRACE("0x%08x, %d", ep->fifostart[1], ep->rxcount );
+ ep->fifostart[1] += ep->rxcount;
ep->fifolen[1] = 0;
- ep->rxcount = MIN(ep->packey[0].len - (ep->fifostart[1] << 2),
+ ep->rxcount = MIN(ep->packey[0].p.iov.size - (ep->fifostart[1]),
ep->maxp[1]);
ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT;
#ifdef SETUPLEN_HACK
/* Why should *we* do that instead of Linux? */
if (!epnum) {
- if (ep->packey[0].devaddr == 2)
+ if (ep->packey[0].p.devaddr == 2) {
total = MIN(s->setup_len, 8);
- else
+ } else {
total = MIN(s->setup_len, 64);
+ }
s->setup_len -= total;
}
#endif
total, musb_rx_packet_complete, 1);
}
+static uint8_t musb_read_fifo(MUSBEndPoint *ep)
+{
+ uint8_t value;
+ if (ep->fifolen[1] >= 64) {
+ /* We have a FIFO underrun */
+ TRACE("EP%d FIFO is now empty, stop reading", ep->epnum);
+ return 0x00000000;
+ }
+ /* In DMA mode clear RXPKTRDY and set REQPKT automatically
+ * (if AUTOREQ is set) */
+
+ ep->csr[1] &= ~MGC_M_RXCSR_FIFOFULL;
+ value=ep->buf[1][ep->fifostart[1] + ep->fifolen[1] ++];
+ TRACE("EP%d 0x%02x, %d", ep->epnum, value, ep->fifolen[1] );
+ return value;
+}
+
+static void musb_write_fifo(MUSBEndPoint *ep, uint8_t value)
+{
+ TRACE("EP%d = %02x", ep->epnum, value);
+ if (ep->fifolen[0] >= 64) {
+ /* We have a FIFO overrun */
+ TRACE("EP%d FIFO exceeded 64 bytes, stop feeding data", ep->epnum);
+ return;
+ }
+
+ ep->buf[0][ep->fifostart[0] + ep->fifolen[0] ++] = value;
+ ep->csr[0] |= MGC_M_TXCSR_FIFONOTEMPTY;
+}
+
static void musb_ep_frame_cancel(MUSBEndPoint *ep, int dir)
{
if (ep->intv_timer[dir])
return s->ep[ep].hport[1];
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, addr);
+ TRACE("unknown register 0x%02x", addr);
return 0x00;
};
}
MUSBState *s = (MUSBState *) opaque;
switch (addr) {
+ case MUSB_HDRC_TXFUNCADDR:
+ s->ep[ep].faddr[0] = value;
+ break;
+ case MUSB_HDRC_RXFUNCADDR:
+ s->ep[ep].faddr[1] = value;
+ break;
case MUSB_HDRC_TXHUBADDR:
s->ep[ep].haddr[0] = value;
break;
break;
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, addr);
+ TRACE("unknown register 0x%02x", addr);
+ break;
};
}
return 0x00;
case MUSB_HDRC_FIFOSIZE:
return ep ? s->ep[ep].fifosize : s->ep[ep].config;
+ case MUSB_HDRC_RXCOUNT:
+ return s->ep[ep].rxcount;
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, addr);
+ TRACE("unknown register 0x%02x", addr);
return 0x00;
};
}
case (MUSB_HDRC_FIFOSIZE & ~1):
break;
case MUSB_HDRC_FIFOSIZE:
- printf("%s: somebody messes with fifosize (now %i bytes)\n",
- __FUNCTION__, value);
+ TRACE("somebody messes with fifosize (now %i bytes)", value);
s->ep[ep].fifosize = value;
break;
-
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, addr);
+ TRACE("unknown register 0x%02x", addr);
+ break;
};
}
ep = (addr >> 4) & 0xf;
return musb_ep_readb(s, ep, addr & 0xf);
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ return musb_read_fifo(s->ep + ep);
+
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr);
+ TRACE("unknown register 0x%02x", (int) addr);
return 0x00;
};
}
musb_ep_writeb(s, ep, addr & 0xf, value);
break;
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ musb_write_fifo(s->ep + ep, value & 0xff);
+ break;
+
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr);
+ TRACE("unknown register 0x%02x", (int) addr);
+ break;
};
}
ep = (addr >> 4) & 0xf;
return musb_ep_readh(s, ep, addr & 0xf);
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ return (musb_read_fifo(s->ep + ep) | musb_read_fifo(s->ep + ep) << 8);
+
default:
return musb_readb(s, addr) | (musb_readb(s, addr | 1) << 8);
};
case MUSB_HDRC_TXFIFOADDR:
s->ep[s->idx].fifoaddr[0] = value;
s->ep[s->idx].buf[0] =
- s->buf + ((value << 1) & (sizeof(s->buf) / 4 - 1));
+ s->buf + ((value << 3) & 0x7ff );
break;
case MUSB_HDRC_RXFIFOADDR:
s->ep[s->idx].fifoaddr[1] = value;
s->ep[s->idx].buf[1] =
- s->buf + ((value << 1) & (sizeof(s->buf) / 4 - 1));
+ s->buf + ((value << 3) & 0x7ff);
break;
case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf):
musb_ep_writeh(s, ep, addr & 0xf, value);
break;
+ case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ musb_write_fifo(s->ep + ep, value & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 8) & 0xff);
+ break;
+
default:
musb_writeb(s, addr, value & 0xff);
musb_writeb(s, addr | 1, value >> 8);
static uint32_t musb_readw(void *opaque, target_phys_addr_t addr)
{
MUSBState *s = (MUSBState *) opaque;
- MUSBEndPoint *ep;
- int epnum;
+ int ep;
switch (addr) {
case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
- epnum = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
- ep = s->ep + epnum;
-
- if (ep->fifolen[1] >= 16) {
- /* We have a FIFO underrun */
- printf("%s: EP%i FIFO is now empty, stop reading\n",
- __FUNCTION__, epnum);
- return 0x00000000;
- }
- /* In DMA mode clear RXPKTRDY and set REQPKT automatically
- * (if AUTOREQ is set) */
-
- ep->csr[1] &= ~MGC_M_RXCSR_FIFOFULL;
- return ep->buf[1][ep->fifostart[1] + ep->fifolen[1] ++];
-
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ return ( musb_read_fifo(s->ep + ep) |
+ musb_read_fifo(s->ep + ep) << 8 |
+ musb_read_fifo(s->ep + ep) << 16 |
+ musb_read_fifo(s->ep + ep) << 24 );
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr);
+ TRACE("unknown register 0x%02x", (int) addr);
return 0x00000000;
};
}
static void musb_writew(void *opaque, target_phys_addr_t addr, uint32_t value)
{
MUSBState *s = (MUSBState *) opaque;
- MUSBEndPoint *ep;
- int epnum;
+ int ep;
switch (addr) {
case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f):
- epnum = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
- ep = s->ep + epnum;
-
- if (ep->fifolen[0] >= 16) {
- /* We have a FIFO overrun */
- printf("%s: EP%i FIFO exceeded 64 bytes, stop feeding data\n",
- __FUNCTION__, epnum);
+ ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf;
+ musb_write_fifo(s->ep + ep, value & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 8 ) & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 16) & 0xff);
+ musb_write_fifo(s->ep + ep, (value >> 24) & 0xff);
break;
- }
-
- ep->buf[0][ep->fifostart[0] + ep->fifolen[0] ++] = value;
- if (epnum)
- ep->csr[0] |= MGC_M_TXCSR_FIFONOTEMPTY;
- break;
-
default:
- printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr);
+ TRACE("unknown register 0x%02x", (int) addr);
+ break;
};
}