271ae033e9
- Make it easier to port the USB code to other platforms by only using one set of memory functions for clearing and copying memory. None of the memory copies are overlapping. This means using bcopy() is not required. - Fix a compile warning when USB_HAVE_BUSDMA=0 - Add missing semicolon in avr32dci. - Update some comments. MFC after: 1 week
3231 lines
71 KiB
C
3231 lines
71 KiB
C
/*-
|
|
* Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
|
|
* Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
|
|
* Copyright (c) 1998 Lennart Augustsson. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* USB Universal Host Controller driver.
|
|
* Handles e.g. PIIX3 and PIIX4.
|
|
*
|
|
* UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm
|
|
* USB spec: http://www.usb.org/developers/docs/usbspec.zip
|
|
* PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf
|
|
* ftp://download.intel.com/design/intarch/datashts/29056201.pdf
|
|
*/
|
|
|
|
#include <sys/stdint.h>
|
|
#include <sys/stddef.h>
|
|
#include <sys/param.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/types.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/module.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/condvar.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/sx.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/callout.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/priv.h>
|
|
|
|
#include <dev/usb/usb.h>
|
|
#include <dev/usb/usbdi.h>
|
|
|
|
#define USB_DEBUG_VAR uhcidebug
|
|
|
|
#include <dev/usb/usb_core.h>
|
|
#include <dev/usb/usb_debug.h>
|
|
#include <dev/usb/usb_busdma.h>
|
|
#include <dev/usb/usb_process.h>
|
|
#include <dev/usb/usb_transfer.h>
|
|
#include <dev/usb/usb_device.h>
|
|
#include <dev/usb/usb_hub.h>
|
|
#include <dev/usb/usb_util.h>
|
|
|
|
#include <dev/usb/usb_controller.h>
|
|
#include <dev/usb/usb_bus.h>
|
|
#include <dev/usb/controller/uhci.h>
|
|
#include <dev/usb/controller/uhcireg.h>
|
|
|
|
#define alt_next next
|
|
#define UHCI_BUS2SC(bus) \
|
|
((uhci_softc_t *)(((uint8_t *)(bus)) - \
|
|
((uint8_t *)&(((uhci_softc_t *)0)->sc_bus))))
|
|
|
|
#ifdef USB_DEBUG
|
|
static int uhcidebug = 0;
|
|
static int uhcinoloop = 0;
|
|
|
|
static SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci");
|
|
SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RW,
|
|
&uhcidebug, 0, "uhci debug level");
|
|
SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RW,
|
|
&uhcinoloop, 0, "uhci noloop");
|
|
|
|
TUNABLE_INT("hw.usb.uhci.debug", &uhcidebug);
|
|
TUNABLE_INT("hw.usb.uhci.loop", &uhcinoloop);
|
|
|
|
static void uhci_dumpregs(uhci_softc_t *sc);
|
|
static void uhci_dump_tds(uhci_td_t *td);
|
|
|
|
#endif
|
|
|
|
#define UBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \
|
|
BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE)
|
|
#define UWRITE1(sc, r, x) \
|
|
do { UBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
|
|
} while (/*CONSTCOND*/0)
|
|
#define UWRITE2(sc, r, x) \
|
|
do { UBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
|
|
} while (/*CONSTCOND*/0)
|
|
#define UWRITE4(sc, r, x) \
|
|
do { UBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
|
|
} while (/*CONSTCOND*/0)
|
|
#define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
|
|
#define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
|
|
#define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
|
|
|
|
#define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd)
|
|
#define UHCISTS(sc) UREAD2(sc, UHCI_STS)
|
|
|
|
#define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */
|
|
|
|
#define UHCI_INTR_ENDPT 1
|
|
|
|
struct uhci_mem_layout {
|
|
|
|
struct usb_page_search buf_res;
|
|
struct usb_page_search fix_res;
|
|
|
|
struct usb_page_cache *buf_pc;
|
|
struct usb_page_cache *fix_pc;
|
|
|
|
uint32_t buf_offset;
|
|
|
|
uint16_t max_frame_size;
|
|
};
|
|
|
|
struct uhci_std_temp {
|
|
|
|
struct uhci_mem_layout ml;
|
|
uhci_td_t *td;
|
|
uhci_td_t *td_next;
|
|
uint32_t average;
|
|
uint32_t td_status;
|
|
uint32_t td_token;
|
|
uint32_t len;
|
|
uint16_t max_frame_size;
|
|
uint8_t shortpkt;
|
|
uint8_t setup_alt_next;
|
|
uint8_t last_frame;
|
|
};
|
|
|
|
extern struct usb_bus_methods uhci_bus_methods;
|
|
extern struct usb_pipe_methods uhci_device_bulk_methods;
|
|
extern struct usb_pipe_methods uhci_device_ctrl_methods;
|
|
extern struct usb_pipe_methods uhci_device_intr_methods;
|
|
extern struct usb_pipe_methods uhci_device_isoc_methods;
|
|
|
|
static uint8_t uhci_restart(uhci_softc_t *sc);
|
|
static void uhci_do_poll(struct usb_bus *);
|
|
static void uhci_device_done(struct usb_xfer *, usb_error_t);
|
|
static void uhci_transfer_intr_enqueue(struct usb_xfer *);
|
|
static void uhci_timeout(void *);
|
|
static uint8_t uhci_check_transfer(struct usb_xfer *);
|
|
static void uhci_root_intr(uhci_softc_t *sc);
|
|
|
|
void
|
|
uhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb)
|
|
{
|
|
struct uhci_softc *sc = UHCI_BUS2SC(bus);
|
|
uint32_t i;
|
|
|
|
cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg,
|
|
sizeof(uint32_t) * UHCI_FRAMELIST_COUNT, UHCI_FRAMELIST_ALIGN);
|
|
|
|
cb(bus, &sc->sc_hw.ls_ctl_start_pc, &sc->sc_hw.ls_ctl_start_pg,
|
|
sizeof(uhci_qh_t), UHCI_QH_ALIGN);
|
|
|
|
cb(bus, &sc->sc_hw.fs_ctl_start_pc, &sc->sc_hw.fs_ctl_start_pg,
|
|
sizeof(uhci_qh_t), UHCI_QH_ALIGN);
|
|
|
|
cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg,
|
|
sizeof(uhci_qh_t), UHCI_QH_ALIGN);
|
|
|
|
cb(bus, &sc->sc_hw.last_qh_pc, &sc->sc_hw.last_qh_pg,
|
|
sizeof(uhci_qh_t), UHCI_QH_ALIGN);
|
|
|
|
cb(bus, &sc->sc_hw.last_td_pc, &sc->sc_hw.last_td_pg,
|
|
sizeof(uhci_td_t), UHCI_TD_ALIGN);
|
|
|
|
for (i = 0; i != UHCI_VFRAMELIST_COUNT; i++) {
|
|
cb(bus, sc->sc_hw.isoc_start_pc + i,
|
|
sc->sc_hw.isoc_start_pg + i,
|
|
sizeof(uhci_td_t), UHCI_TD_ALIGN);
|
|
}
|
|
|
|
for (i = 0; i != UHCI_IFRAMELIST_COUNT; i++) {
|
|
cb(bus, sc->sc_hw.intr_start_pc + i,
|
|
sc->sc_hw.intr_start_pg + i,
|
|
sizeof(uhci_qh_t), UHCI_QH_ALIGN);
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_mem_layout_init(struct uhci_mem_layout *ml, struct usb_xfer *xfer)
|
|
{
|
|
ml->buf_pc = xfer->frbuffers + 0;
|
|
ml->fix_pc = xfer->buf_fixup;
|
|
|
|
ml->buf_offset = 0;
|
|
|
|
ml->max_frame_size = xfer->max_frame_size;
|
|
}
|
|
|
|
static void
|
|
uhci_mem_layout_fixup(struct uhci_mem_layout *ml, struct uhci_td *td)
|
|
{
|
|
usbd_get_page(ml->buf_pc, ml->buf_offset, &ml->buf_res);
|
|
|
|
if (ml->buf_res.length < td->len) {
|
|
|
|
/* need to do a fixup */
|
|
|
|
usbd_get_page(ml->fix_pc, 0, &ml->fix_res);
|
|
|
|
td->td_buffer = htole32(ml->fix_res.physaddr);
|
|
|
|
/*
|
|
* The UHCI driver cannot handle
|
|
* page crossings, so a fixup is
|
|
* needed:
|
|
*
|
|
* +----+----+ - - -
|
|
* | YYY|Y |
|
|
* +----+----+ - - -
|
|
* \ \
|
|
* \ \
|
|
* +----+
|
|
* |YYYY| (fixup)
|
|
* +----+
|
|
*/
|
|
|
|
if ((td->td_token & htole32(UHCI_TD_PID)) ==
|
|
htole32(UHCI_TD_PID_IN)) {
|
|
td->fix_pc = ml->fix_pc;
|
|
usb_pc_cpu_invalidate(ml->fix_pc);
|
|
|
|
} else {
|
|
td->fix_pc = NULL;
|
|
|
|
/* copy data to fixup location */
|
|
|
|
usbd_copy_out(ml->buf_pc, ml->buf_offset,
|
|
ml->fix_res.buffer, td->len);
|
|
|
|
usb_pc_cpu_flush(ml->fix_pc);
|
|
}
|
|
|
|
/* prepare next fixup */
|
|
|
|
ml->fix_pc++;
|
|
|
|
} else {
|
|
|
|
td->td_buffer = htole32(ml->buf_res.physaddr);
|
|
td->fix_pc = NULL;
|
|
}
|
|
|
|
/* prepare next data location */
|
|
|
|
ml->buf_offset += td->len;
|
|
}
|
|
|
|
/*
|
|
* Return values:
|
|
* 0: Success
|
|
* Else: Failure
|
|
*/
|
|
static uint8_t
|
|
uhci_restart(uhci_softc_t *sc)
|
|
{
|
|
struct usb_page_search buf_res;
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
if (UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS) {
|
|
DPRINTFN(2, "Already started\n");
|
|
return (0);
|
|
}
|
|
|
|
DPRINTFN(2, "Restarting\n");
|
|
|
|
usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
|
|
|
|
/* Reload fresh base address */
|
|
UWRITE4(sc, UHCI_FLBASEADDR, buf_res.physaddr);
|
|
|
|
/*
|
|
* Assume 64 byte packets at frame end and start HC controller:
|
|
*/
|
|
UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS));
|
|
|
|
/* wait 10 milliseconds */
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
|
|
|
|
/* check that controller has started */
|
|
|
|
if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) {
|
|
DPRINTFN(2, "Failed\n");
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
uhci_reset(uhci_softc_t *sc)
|
|
{
|
|
uint16_t n;
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
DPRINTF("resetting the HC\n");
|
|
|
|
/* disable interrupts */
|
|
|
|
UWRITE2(sc, UHCI_INTR, 0);
|
|
|
|
/* global reset */
|
|
|
|
UHCICMD(sc, UHCI_CMD_GRESET);
|
|
|
|
/* wait */
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx,
|
|
USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
|
|
|
|
/* terminate all transfers */
|
|
|
|
UHCICMD(sc, UHCI_CMD_HCRESET);
|
|
|
|
/* the reset bit goes low when the controller is done */
|
|
|
|
n = UHCI_RESET_TIMEOUT;
|
|
while (n--) {
|
|
/* wait one millisecond */
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
|
|
|
|
if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET)) {
|
|
goto done_1;
|
|
}
|
|
}
|
|
|
|
device_printf(sc->sc_bus.bdev,
|
|
"controller did not reset\n");
|
|
|
|
done_1:
|
|
|
|
n = 10;
|
|
while (n--) {
|
|
/* wait one millisecond */
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
|
|
|
|
/* check if HC is stopped */
|
|
if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) {
|
|
goto done_2;
|
|
}
|
|
}
|
|
|
|
device_printf(sc->sc_bus.bdev,
|
|
"controller did not stop\n");
|
|
|
|
done_2:
|
|
|
|
/* reload the configuration */
|
|
UWRITE2(sc, UHCI_FRNUM, sc->sc_saved_frnum);
|
|
UWRITE1(sc, UHCI_SOF, sc->sc_saved_sof);
|
|
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
|
|
/* stop root interrupt */
|
|
usb_callout_drain(&sc->sc_root_intr);
|
|
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
}
|
|
|
|
static void
|
|
uhci_start(uhci_softc_t *sc)
|
|
{
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
DPRINTFN(2, "enabling\n");
|
|
|
|
/* enable interrupts */
|
|
|
|
UWRITE2(sc, UHCI_INTR,
|
|
(UHCI_INTR_TOCRCIE |
|
|
UHCI_INTR_RIE |
|
|
UHCI_INTR_IOCE |
|
|
UHCI_INTR_SPIE));
|
|
|
|
if (uhci_restart(sc)) {
|
|
device_printf(sc->sc_bus.bdev,
|
|
"cannot start HC controller\n");
|
|
}
|
|
|
|
/* start root interrupt */
|
|
uhci_root_intr(sc);
|
|
}
|
|
|
|
static struct uhci_qh *
|
|
uhci_init_qh(struct usb_page_cache *pc)
|
|
{
|
|
struct usb_page_search buf_res;
|
|
struct uhci_qh *qh;
|
|
|
|
usbd_get_page(pc, 0, &buf_res);
|
|
|
|
qh = buf_res.buffer;
|
|
|
|
qh->qh_self =
|
|
htole32(buf_res.physaddr) |
|
|
htole32(UHCI_PTR_QH);
|
|
|
|
qh->page_cache = pc;
|
|
|
|
return (qh);
|
|
}
|
|
|
|
static struct uhci_td *
|
|
uhci_init_td(struct usb_page_cache *pc)
|
|
{
|
|
struct usb_page_search buf_res;
|
|
struct uhci_td *td;
|
|
|
|
usbd_get_page(pc, 0, &buf_res);
|
|
|
|
td = buf_res.buffer;
|
|
|
|
td->td_self =
|
|
htole32(buf_res.physaddr) |
|
|
htole32(UHCI_PTR_TD);
|
|
|
|
td->page_cache = pc;
|
|
|
|
return (td);
|
|
}
|
|
|
|
usb_error_t
|
|
uhci_init(uhci_softc_t *sc)
|
|
{
|
|
uint16_t bit;
|
|
uint16_t x;
|
|
uint16_t y;
|
|
|
|
DPRINTF("start\n");
|
|
|
|
usb_callout_init_mtx(&sc->sc_root_intr, &sc->sc_bus.bus_mtx, 0);
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 2) {
|
|
uhci_dumpregs(sc);
|
|
}
|
|
#endif
|
|
sc->sc_saved_sof = 0x40; /* default value */
|
|
sc->sc_saved_frnum = 0; /* default frame number */
|
|
|
|
/*
|
|
* Setup QH's
|
|
*/
|
|
sc->sc_ls_ctl_p_last =
|
|
uhci_init_qh(&sc->sc_hw.ls_ctl_start_pc);
|
|
|
|
sc->sc_fs_ctl_p_last =
|
|
uhci_init_qh(&sc->sc_hw.fs_ctl_start_pc);
|
|
|
|
sc->sc_bulk_p_last =
|
|
uhci_init_qh(&sc->sc_hw.bulk_start_pc);
|
|
#if 0
|
|
sc->sc_reclaim_qh_p =
|
|
sc->sc_fs_ctl_p_last;
|
|
#else
|
|
/* setup reclaim looping point */
|
|
sc->sc_reclaim_qh_p =
|
|
sc->sc_bulk_p_last;
|
|
#endif
|
|
|
|
sc->sc_last_qh_p =
|
|
uhci_init_qh(&sc->sc_hw.last_qh_pc);
|
|
|
|
sc->sc_last_td_p =
|
|
uhci_init_td(&sc->sc_hw.last_td_pc);
|
|
|
|
for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) {
|
|
sc->sc_isoc_p_last[x] =
|
|
uhci_init_td(sc->sc_hw.isoc_start_pc + x);
|
|
}
|
|
|
|
for (x = 0; x != UHCI_IFRAMELIST_COUNT; x++) {
|
|
sc->sc_intr_p_last[x] =
|
|
uhci_init_qh(sc->sc_hw.intr_start_pc + x);
|
|
}
|
|
|
|
/*
|
|
* the QHs are arranged to give poll intervals that are
|
|
* powers of 2 times 1ms
|
|
*/
|
|
bit = UHCI_IFRAMELIST_COUNT / 2;
|
|
while (bit) {
|
|
x = bit;
|
|
while (x & bit) {
|
|
uhci_qh_t *qh_x;
|
|
uhci_qh_t *qh_y;
|
|
|
|
y = (x ^ bit) | (bit / 2);
|
|
|
|
/*
|
|
* the next QH has half the poll interval
|
|
*/
|
|
qh_x = sc->sc_intr_p_last[x];
|
|
qh_y = sc->sc_intr_p_last[y];
|
|
|
|
qh_x->h_next = NULL;
|
|
qh_x->qh_h_next = qh_y->qh_self;
|
|
qh_x->e_next = NULL;
|
|
qh_x->qh_e_next = htole32(UHCI_PTR_T);
|
|
x++;
|
|
}
|
|
bit >>= 1;
|
|
}
|
|
|
|
if (1) {
|
|
uhci_qh_t *qh_ls;
|
|
uhci_qh_t *qh_intr;
|
|
|
|
qh_ls = sc->sc_ls_ctl_p_last;
|
|
qh_intr = sc->sc_intr_p_last[0];
|
|
|
|
/* start QH for interrupt traffic */
|
|
qh_intr->h_next = qh_ls;
|
|
qh_intr->qh_h_next = qh_ls->qh_self;
|
|
qh_intr->e_next = 0;
|
|
qh_intr->qh_e_next = htole32(UHCI_PTR_T);
|
|
}
|
|
for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) {
|
|
|
|
uhci_td_t *td_x;
|
|
uhci_qh_t *qh_intr;
|
|
|
|
td_x = sc->sc_isoc_p_last[x];
|
|
qh_intr = sc->sc_intr_p_last[x | (UHCI_IFRAMELIST_COUNT / 2)];
|
|
|
|
/* start TD for isochronous traffic */
|
|
td_x->next = NULL;
|
|
td_x->td_next = qh_intr->qh_self;
|
|
td_x->td_status = htole32(UHCI_TD_IOS);
|
|
td_x->td_token = htole32(0);
|
|
td_x->td_buffer = htole32(0);
|
|
}
|
|
|
|
if (1) {
|
|
uhci_qh_t *qh_ls;
|
|
uhci_qh_t *qh_fs;
|
|
|
|
qh_ls = sc->sc_ls_ctl_p_last;
|
|
qh_fs = sc->sc_fs_ctl_p_last;
|
|
|
|
/* start QH where low speed control traffic will be queued */
|
|
qh_ls->h_next = qh_fs;
|
|
qh_ls->qh_h_next = qh_fs->qh_self;
|
|
qh_ls->e_next = 0;
|
|
qh_ls->qh_e_next = htole32(UHCI_PTR_T);
|
|
}
|
|
if (1) {
|
|
uhci_qh_t *qh_ctl;
|
|
uhci_qh_t *qh_blk;
|
|
uhci_qh_t *qh_lst;
|
|
uhci_td_t *td_lst;
|
|
|
|
qh_ctl = sc->sc_fs_ctl_p_last;
|
|
qh_blk = sc->sc_bulk_p_last;
|
|
|
|
/* start QH where full speed control traffic will be queued */
|
|
qh_ctl->h_next = qh_blk;
|
|
qh_ctl->qh_h_next = qh_blk->qh_self;
|
|
qh_ctl->e_next = 0;
|
|
qh_ctl->qh_e_next = htole32(UHCI_PTR_T);
|
|
|
|
qh_lst = sc->sc_last_qh_p;
|
|
|
|
/* start QH where bulk traffic will be queued */
|
|
qh_blk->h_next = qh_lst;
|
|
qh_blk->qh_h_next = qh_lst->qh_self;
|
|
qh_blk->e_next = 0;
|
|
qh_blk->qh_e_next = htole32(UHCI_PTR_T);
|
|
|
|
td_lst = sc->sc_last_td_p;
|
|
|
|
/* end QH which is used for looping the QHs */
|
|
qh_lst->h_next = 0;
|
|
qh_lst->qh_h_next = htole32(UHCI_PTR_T); /* end of QH chain */
|
|
qh_lst->e_next = td_lst;
|
|
qh_lst->qh_e_next = td_lst->td_self;
|
|
|
|
/*
|
|
* end TD which hangs from the last QH, to avoid a bug in the PIIX
|
|
* that makes it run berserk otherwise
|
|
*/
|
|
td_lst->next = 0;
|
|
td_lst->td_next = htole32(UHCI_PTR_T);
|
|
td_lst->td_status = htole32(0); /* inactive */
|
|
td_lst->td_token = htole32(0);
|
|
td_lst->td_buffer = htole32(0);
|
|
}
|
|
if (1) {
|
|
struct usb_page_search buf_res;
|
|
uint32_t *pframes;
|
|
|
|
usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
|
|
|
|
pframes = buf_res.buffer;
|
|
|
|
|
|
/*
|
|
* Setup UHCI framelist
|
|
*
|
|
* Execution order:
|
|
*
|
|
* pframes -> full speed isochronous -> interrupt QH's -> low
|
|
* speed control -> full speed control -> bulk transfers
|
|
*
|
|
*/
|
|
|
|
for (x = 0; x != UHCI_FRAMELIST_COUNT; x++) {
|
|
pframes[x] =
|
|
sc->sc_isoc_p_last[x % UHCI_VFRAMELIST_COUNT]->td_self;
|
|
}
|
|
}
|
|
/* flush all cache into memory */
|
|
|
|
usb_bus_mem_flush_all(&sc->sc_bus, &uhci_iterate_hw_softc);
|
|
|
|
/* set up the bus struct */
|
|
sc->sc_bus.methods = &uhci_bus_methods;
|
|
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
/* reset the controller */
|
|
uhci_reset(sc);
|
|
|
|
/* start the controller */
|
|
uhci_start(sc);
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
|
|
/* catch lost interrupts */
|
|
uhci_do_poll(&sc->sc_bus);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* NOTE: suspend/resume is called from
|
|
* interrupt context and cannot sleep!
|
|
*/
|
|
|
|
void
|
|
uhci_suspend(uhci_softc_t *sc)
|
|
{
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 2) {
|
|
uhci_dumpregs(sc);
|
|
}
|
|
#endif
|
|
/* save some state if BIOS doesn't */
|
|
|
|
sc->sc_saved_frnum = UREAD2(sc, UHCI_FRNUM);
|
|
sc->sc_saved_sof = UREAD1(sc, UHCI_SOF);
|
|
|
|
/* stop the controller */
|
|
|
|
uhci_reset(sc);
|
|
|
|
/* enter global suspend */
|
|
|
|
UHCICMD(sc, UHCI_CMD_EGSM);
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx,
|
|
USB_MS_TO_TICKS(USB_RESUME_WAIT));
|
|
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
}
|
|
|
|
void
|
|
uhci_resume(uhci_softc_t *sc)
|
|
{
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
|
|
/* reset the controller */
|
|
|
|
uhci_reset(sc);
|
|
|
|
/* force global resume */
|
|
|
|
UHCICMD(sc, UHCI_CMD_FGR);
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx,
|
|
USB_MS_TO_TICKS(USB_RESUME_DELAY));
|
|
|
|
/* and start traffic again */
|
|
|
|
uhci_start(sc);
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 2) {
|
|
uhci_dumpregs(sc);
|
|
}
|
|
#endif
|
|
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
|
|
/* catch lost interrupts */
|
|
uhci_do_poll(&sc->sc_bus);
|
|
}
|
|
|
|
#ifdef USB_DEBUG
|
|
static void
|
|
uhci_dumpregs(uhci_softc_t *sc)
|
|
{
|
|
DPRINTFN(0, "%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, "
|
|
"flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n",
|
|
device_get_nameunit(sc->sc_bus.bdev),
|
|
UREAD2(sc, UHCI_CMD),
|
|
UREAD2(sc, UHCI_STS),
|
|
UREAD2(sc, UHCI_INTR),
|
|
UREAD2(sc, UHCI_FRNUM),
|
|
UREAD4(sc, UHCI_FLBASEADDR),
|
|
UREAD1(sc, UHCI_SOF),
|
|
UREAD2(sc, UHCI_PORTSC1),
|
|
UREAD2(sc, UHCI_PORTSC2));
|
|
}
|
|
|
|
static uint8_t
|
|
uhci_dump_td(uhci_td_t *p)
|
|
{
|
|
uint32_t td_next;
|
|
uint32_t td_status;
|
|
uint32_t td_token;
|
|
uint8_t temp;
|
|
|
|
usb_pc_cpu_invalidate(p->page_cache);
|
|
|
|
td_next = le32toh(p->td_next);
|
|
td_status = le32toh(p->td_status);
|
|
td_token = le32toh(p->td_token);
|
|
|
|
/*
|
|
* Check whether the link pointer in this TD marks the link pointer
|
|
* as end of queue:
|
|
*/
|
|
temp = ((td_next & UHCI_PTR_T) || (td_next == 0));
|
|
|
|
printf("TD(%p) at 0x%08x = link=0x%08x status=0x%08x "
|
|
"token=0x%08x buffer=0x%08x\n",
|
|
p,
|
|
le32toh(p->td_self),
|
|
td_next,
|
|
td_status,
|
|
td_token,
|
|
le32toh(p->td_buffer));
|
|
|
|
printf("TD(%p) td_next=%s%s%s td_status=%s%s%s%s%s%s%s%s%s%s%s, errcnt=%d, actlen=%d pid=%02x,"
|
|
"addr=%d,endpt=%d,D=%d,maxlen=%d\n",
|
|
p,
|
|
(td_next & 1) ? "-T" : "",
|
|
(td_next & 2) ? "-Q" : "",
|
|
(td_next & 4) ? "-VF" : "",
|
|
(td_status & UHCI_TD_BITSTUFF) ? "-BITSTUFF" : "",
|
|
(td_status & UHCI_TD_CRCTO) ? "-CRCTO" : "",
|
|
(td_status & UHCI_TD_NAK) ? "-NAK" : "",
|
|
(td_status & UHCI_TD_BABBLE) ? "-BABBLE" : "",
|
|
(td_status & UHCI_TD_DBUFFER) ? "-DBUFFER" : "",
|
|
(td_status & UHCI_TD_STALLED) ? "-STALLED" : "",
|
|
(td_status & UHCI_TD_ACTIVE) ? "-ACTIVE" : "",
|
|
(td_status & UHCI_TD_IOC) ? "-IOC" : "",
|
|
(td_status & UHCI_TD_IOS) ? "-IOS" : "",
|
|
(td_status & UHCI_TD_LS) ? "-LS" : "",
|
|
(td_status & UHCI_TD_SPD) ? "-SPD" : "",
|
|
UHCI_TD_GET_ERRCNT(td_status),
|
|
UHCI_TD_GET_ACTLEN(td_status),
|
|
UHCI_TD_GET_PID(td_token),
|
|
UHCI_TD_GET_DEVADDR(td_token),
|
|
UHCI_TD_GET_ENDPT(td_token),
|
|
UHCI_TD_GET_DT(td_token),
|
|
UHCI_TD_GET_MAXLEN(td_token));
|
|
|
|
return (temp);
|
|
}
|
|
|
|
static uint8_t
|
|
uhci_dump_qh(uhci_qh_t *sqh)
|
|
{
|
|
uint8_t temp;
|
|
uint32_t qh_h_next;
|
|
uint32_t qh_e_next;
|
|
|
|
usb_pc_cpu_invalidate(sqh->page_cache);
|
|
|
|
qh_h_next = le32toh(sqh->qh_h_next);
|
|
qh_e_next = le32toh(sqh->qh_e_next);
|
|
|
|
DPRINTFN(0, "QH(%p) at 0x%08x: h_next=0x%08x e_next=0x%08x\n", sqh,
|
|
le32toh(sqh->qh_self), qh_h_next, qh_e_next);
|
|
|
|
temp = ((((sqh->h_next != NULL) && !(qh_h_next & UHCI_PTR_T)) ? 1 : 0) |
|
|
(((sqh->e_next != NULL) && !(qh_e_next & UHCI_PTR_T)) ? 2 : 0));
|
|
|
|
return (temp);
|
|
}
|
|
|
|
static void
|
|
uhci_dump_all(uhci_softc_t *sc)
|
|
{
|
|
uhci_dumpregs(sc);
|
|
uhci_dump_qh(sc->sc_ls_ctl_p_last);
|
|
uhci_dump_qh(sc->sc_fs_ctl_p_last);
|
|
uhci_dump_qh(sc->sc_bulk_p_last);
|
|
uhci_dump_qh(sc->sc_last_qh_p);
|
|
}
|
|
|
|
static void
|
|
uhci_dump_tds(uhci_td_t *td)
|
|
{
|
|
for (;
|
|
td != NULL;
|
|
td = td->obj_next) {
|
|
if (uhci_dump_td(td)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Let the last QH loop back to the full speed control transfer QH.
|
|
* This is what intel calls "bandwidth reclamation" and improves
|
|
* USB performance a lot for some devices.
|
|
* If we are already looping, just count it.
|
|
*/
|
|
static void
|
|
uhci_add_loop(uhci_softc_t *sc)
|
|
{
|
|
struct uhci_qh *qh_lst;
|
|
struct uhci_qh *qh_rec;
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcinoloop) {
|
|
return;
|
|
}
|
|
#endif
|
|
if (++(sc->sc_loops) == 1) {
|
|
DPRINTFN(6, "add\n");
|
|
|
|
qh_lst = sc->sc_last_qh_p;
|
|
qh_rec = sc->sc_reclaim_qh_p;
|
|
|
|
/* NOTE: we don't loop back the soft pointer */
|
|
|
|
qh_lst->qh_h_next = qh_rec->qh_self;
|
|
usb_pc_cpu_flush(qh_lst->page_cache);
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_rem_loop(uhci_softc_t *sc)
|
|
{
|
|
struct uhci_qh *qh_lst;
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcinoloop) {
|
|
return;
|
|
}
|
|
#endif
|
|
if (--(sc->sc_loops) == 0) {
|
|
DPRINTFN(6, "remove\n");
|
|
|
|
qh_lst = sc->sc_last_qh_p;
|
|
qh_lst->qh_h_next = htole32(UHCI_PTR_T);
|
|
usb_pc_cpu_flush(qh_lst->page_cache);
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_transfer_intr_enqueue(struct usb_xfer *xfer)
|
|
{
|
|
/* check for early completion */
|
|
if (uhci_check_transfer(xfer)) {
|
|
return;
|
|
}
|
|
/* put transfer on interrupt queue */
|
|
usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
|
|
|
|
/* start timeout, if any */
|
|
if (xfer->timeout != 0) {
|
|
usbd_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout);
|
|
}
|
|
}
|
|
|
|
#define UHCI_APPEND_TD(std,last) (last) = _uhci_append_td(std,last)
|
|
static uhci_td_t *
|
|
_uhci_append_td(uhci_td_t *std, uhci_td_t *last)
|
|
{
|
|
DPRINTFN(11, "%p to %p\n", std, last);
|
|
|
|
/* (sc->sc_bus.mtx) must be locked */
|
|
|
|
std->next = last->next;
|
|
std->td_next = last->td_next;
|
|
|
|
std->prev = last;
|
|
|
|
usb_pc_cpu_flush(std->page_cache);
|
|
|
|
/*
|
|
* the last->next->prev is never followed: std->next->prev = std;
|
|
*/
|
|
last->next = std;
|
|
last->td_next = std->td_self;
|
|
|
|
usb_pc_cpu_flush(last->page_cache);
|
|
|
|
return (std);
|
|
}
|
|
|
|
#define UHCI_APPEND_QH(sqh,last) (last) = _uhci_append_qh(sqh,last)
|
|
static uhci_qh_t *
|
|
_uhci_append_qh(uhci_qh_t *sqh, uhci_qh_t *last)
|
|
{
|
|
DPRINTFN(11, "%p to %p\n", sqh, last);
|
|
|
|
if (sqh->h_prev != NULL) {
|
|
/* should not happen */
|
|
DPRINTFN(0, "QH already linked!\n");
|
|
return (last);
|
|
}
|
|
/* (sc->sc_bus.mtx) must be locked */
|
|
|
|
sqh->h_next = last->h_next;
|
|
sqh->qh_h_next = last->qh_h_next;
|
|
|
|
sqh->h_prev = last;
|
|
|
|
usb_pc_cpu_flush(sqh->page_cache);
|
|
|
|
/*
|
|
* The "last->h_next->h_prev" is never followed:
|
|
*
|
|
* "sqh->h_next->h_prev" = sqh;
|
|
*/
|
|
|
|
last->h_next = sqh;
|
|
last->qh_h_next = sqh->qh_self;
|
|
|
|
usb_pc_cpu_flush(last->page_cache);
|
|
|
|
return (sqh);
|
|
}
|
|
|
|
/**/
|
|
|
|
#define UHCI_REMOVE_TD(std,last) (last) = _uhci_remove_td(std,last)
|
|
static uhci_td_t *
|
|
_uhci_remove_td(uhci_td_t *std, uhci_td_t *last)
|
|
{
|
|
DPRINTFN(11, "%p from %p\n", std, last);
|
|
|
|
/* (sc->sc_bus.mtx) must be locked */
|
|
|
|
std->prev->next = std->next;
|
|
std->prev->td_next = std->td_next;
|
|
|
|
usb_pc_cpu_flush(std->prev->page_cache);
|
|
|
|
if (std->next) {
|
|
std->next->prev = std->prev;
|
|
usb_pc_cpu_flush(std->next->page_cache);
|
|
}
|
|
return ((last == std) ? std->prev : last);
|
|
}
|
|
|
|
#define UHCI_REMOVE_QH(sqh,last) (last) = _uhci_remove_qh(sqh,last)
|
|
static uhci_qh_t *
|
|
_uhci_remove_qh(uhci_qh_t *sqh, uhci_qh_t *last)
|
|
{
|
|
DPRINTFN(11, "%p from %p\n", sqh, last);
|
|
|
|
/* (sc->sc_bus.mtx) must be locked */
|
|
|
|
/* only remove if not removed from a queue */
|
|
if (sqh->h_prev) {
|
|
|
|
sqh->h_prev->h_next = sqh->h_next;
|
|
sqh->h_prev->qh_h_next = sqh->qh_h_next;
|
|
|
|
usb_pc_cpu_flush(sqh->h_prev->page_cache);
|
|
|
|
if (sqh->h_next) {
|
|
sqh->h_next->h_prev = sqh->h_prev;
|
|
usb_pc_cpu_flush(sqh->h_next->page_cache);
|
|
}
|
|
last = ((last == sqh) ? sqh->h_prev : last);
|
|
|
|
sqh->h_prev = 0;
|
|
|
|
usb_pc_cpu_flush(sqh->page_cache);
|
|
}
|
|
return (last);
|
|
}
|
|
|
|
static void
|
|
uhci_isoc_done(uhci_softc_t *sc, struct usb_xfer *xfer)
|
|
{
|
|
struct usb_page_search res;
|
|
uint32_t nframes = xfer->nframes;
|
|
uint32_t status;
|
|
uint32_t offset = 0;
|
|
uint32_t *plen = xfer->frlengths;
|
|
uint16_t len = 0;
|
|
uhci_td_t *td = xfer->td_transfer_first;
|
|
uhci_td_t **pp_last = &sc->sc_isoc_p_last[xfer->qh_pos];
|
|
|
|
DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
|
|
xfer, xfer->endpoint);
|
|
|
|
/* sync any DMA memory before doing fixups */
|
|
|
|
usb_bdma_post_sync(xfer);
|
|
|
|
while (nframes--) {
|
|
if (td == NULL) {
|
|
panic("%s:%d: out of TD's\n",
|
|
__FUNCTION__, __LINE__);
|
|
}
|
|
if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) {
|
|
pp_last = &sc->sc_isoc_p_last[0];
|
|
}
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 5) {
|
|
DPRINTF("isoc TD\n");
|
|
uhci_dump_td(td);
|
|
}
|
|
#endif
|
|
usb_pc_cpu_invalidate(td->page_cache);
|
|
status = le32toh(td->td_status);
|
|
|
|
len = UHCI_TD_GET_ACTLEN(status);
|
|
|
|
if (len > *plen) {
|
|
len = *plen;
|
|
}
|
|
if (td->fix_pc) {
|
|
|
|
usbd_get_page(td->fix_pc, 0, &res);
|
|
|
|
/* copy data from fixup location to real location */
|
|
|
|
usb_pc_cpu_invalidate(td->fix_pc);
|
|
|
|
usbd_copy_in(xfer->frbuffers, offset,
|
|
res.buffer, len);
|
|
}
|
|
offset += *plen;
|
|
|
|
*plen = len;
|
|
|
|
/* remove TD from schedule */
|
|
UHCI_REMOVE_TD(td, *pp_last);
|
|
|
|
pp_last++;
|
|
plen++;
|
|
td = td->obj_next;
|
|
}
|
|
|
|
xfer->aframes = xfer->nframes;
|
|
}
|
|
|
|
static usb_error_t
|
|
uhci_non_isoc_done_sub(struct usb_xfer *xfer)
|
|
{
|
|
struct usb_page_search res;
|
|
uhci_td_t *td;
|
|
uhci_td_t *td_alt_next;
|
|
uint32_t status;
|
|
uint32_t token;
|
|
uint16_t len;
|
|
|
|
td = xfer->td_transfer_cache;
|
|
td_alt_next = td->alt_next;
|
|
|
|
if (xfer->aframes != xfer->nframes) {
|
|
usbd_xfer_set_frame_len(xfer, xfer->aframes, 0);
|
|
}
|
|
while (1) {
|
|
|
|
usb_pc_cpu_invalidate(td->page_cache);
|
|
status = le32toh(td->td_status);
|
|
token = le32toh(td->td_token);
|
|
|
|
/*
|
|
* Verify the status and add
|
|
* up the actual length:
|
|
*/
|
|
|
|
len = UHCI_TD_GET_ACTLEN(status);
|
|
if (len > td->len) {
|
|
/* should not happen */
|
|
DPRINTF("Invalid status length, "
|
|
"0x%04x/0x%04x bytes\n", len, td->len);
|
|
status |= UHCI_TD_STALLED;
|
|
|
|
} else if ((xfer->aframes != xfer->nframes) && (len > 0)) {
|
|
|
|
if (td->fix_pc) {
|
|
|
|
usbd_get_page(td->fix_pc, 0, &res);
|
|
|
|
/*
|
|
* copy data from fixup location to real
|
|
* location
|
|
*/
|
|
|
|
usb_pc_cpu_invalidate(td->fix_pc);
|
|
|
|
usbd_copy_in(xfer->frbuffers + xfer->aframes,
|
|
xfer->frlengths[xfer->aframes], res.buffer, len);
|
|
}
|
|
/* update actual length */
|
|
|
|
xfer->frlengths[xfer->aframes] += len;
|
|
}
|
|
/* Check for last transfer */
|
|
if (((void *)td) == xfer->td_transfer_last) {
|
|
td = NULL;
|
|
break;
|
|
}
|
|
if (status & UHCI_TD_STALLED) {
|
|
/* the transfer is finished */
|
|
td = NULL;
|
|
break;
|
|
}
|
|
/* Check for short transfer */
|
|
if (len != td->len) {
|
|
if (xfer->flags_int.short_frames_ok) {
|
|
/* follow alt next */
|
|
td = td->alt_next;
|
|
} else {
|
|
/* the transfer is finished */
|
|
td = NULL;
|
|
}
|
|
break;
|
|
}
|
|
td = td->obj_next;
|
|
|
|
if (td->alt_next != td_alt_next) {
|
|
/* this USB frame is complete */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* update transfer cache */
|
|
|
|
xfer->td_transfer_cache = td;
|
|
|
|
/* update data toggle */
|
|
|
|
xfer->endpoint->toggle_next = (token & UHCI_TD_SET_DT(1)) ? 0 : 1;
|
|
|
|
#ifdef USB_DEBUG
|
|
if (status & UHCI_TD_ERROR) {
|
|
DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x "
|
|
"status=%s%s%s%s%s%s%s%s%s%s%s\n",
|
|
xfer->address, xfer->endpointno, xfer->aframes,
|
|
(status & UHCI_TD_BITSTUFF) ? "[BITSTUFF]" : "",
|
|
(status & UHCI_TD_CRCTO) ? "[CRCTO]" : "",
|
|
(status & UHCI_TD_NAK) ? "[NAK]" : "",
|
|
(status & UHCI_TD_BABBLE) ? "[BABBLE]" : "",
|
|
(status & UHCI_TD_DBUFFER) ? "[DBUFFER]" : "",
|
|
(status & UHCI_TD_STALLED) ? "[STALLED]" : "",
|
|
(status & UHCI_TD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]",
|
|
(status & UHCI_TD_IOC) ? "[IOC]" : "",
|
|
(status & UHCI_TD_IOS) ? "[IOS]" : "",
|
|
(status & UHCI_TD_LS) ? "[LS]" : "",
|
|
(status & UHCI_TD_SPD) ? "[SPD]" : "");
|
|
}
|
|
#endif
|
|
return (status & UHCI_TD_STALLED) ?
|
|
USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION;
|
|
}
|
|
|
|
static void
|
|
uhci_non_isoc_done(struct usb_xfer *xfer)
|
|
{
|
|
usb_error_t err = 0;
|
|
|
|
DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
|
|
xfer, xfer->endpoint);
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 10) {
|
|
uhci_dump_tds(xfer->td_transfer_first);
|
|
}
|
|
#endif
|
|
|
|
/* sync any DMA memory before doing fixups */
|
|
|
|
usb_bdma_post_sync(xfer);
|
|
|
|
/* reset scanner */
|
|
|
|
xfer->td_transfer_cache = xfer->td_transfer_first;
|
|
|
|
if (xfer->flags_int.control_xfr) {
|
|
if (xfer->flags_int.control_hdr) {
|
|
|
|
err = uhci_non_isoc_done_sub(xfer);
|
|
}
|
|
xfer->aframes = 1;
|
|
|
|
if (xfer->td_transfer_cache == NULL) {
|
|
goto done;
|
|
}
|
|
}
|
|
while (xfer->aframes != xfer->nframes) {
|
|
|
|
err = uhci_non_isoc_done_sub(xfer);
|
|
xfer->aframes++;
|
|
|
|
if (xfer->td_transfer_cache == NULL) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (xfer->flags_int.control_xfr &&
|
|
!xfer->flags_int.control_act) {
|
|
|
|
err = uhci_non_isoc_done_sub(xfer);
|
|
}
|
|
done:
|
|
uhci_device_done(xfer, err);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci_check_transfer_sub
|
|
*
|
|
* The main purpose of this function is to update the data-toggle
|
|
* in case it is wrong.
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
uhci_check_transfer_sub(struct usb_xfer *xfer)
|
|
{
|
|
uhci_qh_t *qh;
|
|
uhci_td_t *td;
|
|
uhci_td_t *td_alt_next;
|
|
|
|
uint32_t td_token;
|
|
uint32_t td_self;
|
|
|
|
td = xfer->td_transfer_cache;
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
|
|
td_token = td->obj_next->td_token;
|
|
td = td->alt_next;
|
|
xfer->td_transfer_cache = td;
|
|
td_self = td->td_self;
|
|
td_alt_next = td->alt_next;
|
|
|
|
if (xfer->flags_int.control_xfr)
|
|
goto skip; /* don't touch the DT value! */
|
|
|
|
if (!((td->td_token ^ td_token) & htole32(UHCI_TD_SET_DT(1))))
|
|
goto skip; /* data toggle has correct value */
|
|
|
|
/*
|
|
* The data toggle is wrong and we need to toggle it !
|
|
*/
|
|
while (1) {
|
|
|
|
td->td_token ^= htole32(UHCI_TD_SET_DT(1));
|
|
usb_pc_cpu_flush(td->page_cache);
|
|
|
|
if (td == xfer->td_transfer_last) {
|
|
/* last transfer */
|
|
break;
|
|
}
|
|
td = td->obj_next;
|
|
|
|
if (td->alt_next != td_alt_next) {
|
|
/* next frame */
|
|
break;
|
|
}
|
|
}
|
|
skip:
|
|
|
|
/* update the QH */
|
|
qh->qh_e_next = td_self;
|
|
usb_pc_cpu_flush(qh->page_cache);
|
|
|
|
DPRINTFN(13, "xfer=%p following alt next\n", xfer);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci_check_transfer
|
|
*
|
|
* Return values:
|
|
* 0: USB transfer is not finished
|
|
* Else: USB transfer is finished
|
|
*------------------------------------------------------------------------*/
|
|
static uint8_t
|
|
uhci_check_transfer(struct usb_xfer *xfer)
|
|
{
|
|
uint32_t status;
|
|
uint32_t token;
|
|
uhci_td_t *td;
|
|
|
|
DPRINTFN(16, "xfer=%p checking transfer\n", xfer);
|
|
|
|
if (xfer->endpoint->methods == &uhci_device_isoc_methods) {
|
|
/* isochronous transfer */
|
|
|
|
td = xfer->td_transfer_last;
|
|
|
|
usb_pc_cpu_invalidate(td->page_cache);
|
|
status = le32toh(td->td_status);
|
|
|
|
/* check also if the first is complete */
|
|
|
|
td = xfer->td_transfer_first;
|
|
|
|
usb_pc_cpu_invalidate(td->page_cache);
|
|
status |= le32toh(td->td_status);
|
|
|
|
if (!(status & UHCI_TD_ACTIVE)) {
|
|
uhci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
|
|
goto transferred;
|
|
}
|
|
} else {
|
|
/* non-isochronous transfer */
|
|
|
|
/*
|
|
* check whether there is an error somewhere
|
|
* in the middle, or whether there was a short
|
|
* packet (SPD and not ACTIVE)
|
|
*/
|
|
td = xfer->td_transfer_cache;
|
|
|
|
while (1) {
|
|
usb_pc_cpu_invalidate(td->page_cache);
|
|
status = le32toh(td->td_status);
|
|
token = le32toh(td->td_token);
|
|
|
|
/*
|
|
* if there is an active TD the transfer isn't done
|
|
*/
|
|
if (status & UHCI_TD_ACTIVE) {
|
|
/* update cache */
|
|
xfer->td_transfer_cache = td;
|
|
goto done;
|
|
}
|
|
/*
|
|
* last transfer descriptor makes the transfer done
|
|
*/
|
|
if (((void *)td) == xfer->td_transfer_last) {
|
|
break;
|
|
}
|
|
/*
|
|
* any kind of error makes the transfer done
|
|
*/
|
|
if (status & UHCI_TD_STALLED) {
|
|
break;
|
|
}
|
|
/*
|
|
* check if we reached the last packet
|
|
* or if there is a short packet:
|
|
*/
|
|
if ((td->td_next == htole32(UHCI_PTR_T)) ||
|
|
(UHCI_TD_GET_ACTLEN(status) < td->len)) {
|
|
|
|
if (xfer->flags_int.short_frames_ok) {
|
|
/* follow alt next */
|
|
if (td->alt_next) {
|
|
/* update cache */
|
|
xfer->td_transfer_cache = td;
|
|
uhci_check_transfer_sub(xfer);
|
|
goto done;
|
|
}
|
|
}
|
|
/* transfer is done */
|
|
break;
|
|
}
|
|
td = td->obj_next;
|
|
}
|
|
uhci_non_isoc_done(xfer);
|
|
goto transferred;
|
|
}
|
|
|
|
done:
|
|
DPRINTFN(13, "xfer=%p is still active\n", xfer);
|
|
return (0);
|
|
|
|
transferred:
|
|
return (1);
|
|
}
|
|
|
|
static void
|
|
uhci_interrupt_poll(uhci_softc_t *sc)
|
|
{
|
|
struct usb_xfer *xfer;
|
|
|
|
repeat:
|
|
TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
|
|
/*
|
|
* check if transfer is transferred
|
|
*/
|
|
if (uhci_check_transfer(xfer)) {
|
|
/* queue has been modified */
|
|
goto repeat;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci_interrupt - UHCI interrupt handler
|
|
*
|
|
* NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
|
|
* hence the interrupt handler will be setup before "sc->sc_bus.bdev"
|
|
* is present !
|
|
*------------------------------------------------------------------------*/
|
|
void
|
|
uhci_interrupt(uhci_softc_t *sc)
|
|
{
|
|
uint32_t status;
|
|
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
|
|
DPRINTFN(16, "real interrupt\n");
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 15) {
|
|
uhci_dumpregs(sc);
|
|
}
|
|
#endif
|
|
status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS;
|
|
if (status == 0) {
|
|
/* the interrupt was not for us */
|
|
goto done;
|
|
}
|
|
if (status & (UHCI_STS_RD | UHCI_STS_HSE |
|
|
UHCI_STS_HCPE | UHCI_STS_HCH)) {
|
|
|
|
if (status & UHCI_STS_RD) {
|
|
#ifdef USB_DEBUG
|
|
printf("%s: resume detect\n",
|
|
__FUNCTION__);
|
|
#endif
|
|
}
|
|
if (status & UHCI_STS_HSE) {
|
|
printf("%s: host system error\n",
|
|
__FUNCTION__);
|
|
}
|
|
if (status & UHCI_STS_HCPE) {
|
|
printf("%s: host controller process error\n",
|
|
__FUNCTION__);
|
|
}
|
|
if (status & UHCI_STS_HCH) {
|
|
/* no acknowledge needed */
|
|
DPRINTF("%s: host controller halted\n",
|
|
__FUNCTION__);
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 0) {
|
|
uhci_dump_all(sc);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
/* get acknowledge bits */
|
|
status &= (UHCI_STS_USBINT |
|
|
UHCI_STS_USBEI |
|
|
UHCI_STS_RD |
|
|
UHCI_STS_HSE |
|
|
UHCI_STS_HCPE);
|
|
|
|
if (status == 0) {
|
|
/* nothing to acknowledge */
|
|
goto done;
|
|
}
|
|
/* acknowledge interrupts */
|
|
UWRITE2(sc, UHCI_STS, status);
|
|
|
|
/* poll all the USB transfers */
|
|
uhci_interrupt_poll(sc);
|
|
|
|
done:
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
}
|
|
|
|
/*
|
|
* called when a request does not complete
|
|
*/
|
|
static void
|
|
uhci_timeout(void *arg)
|
|
{
|
|
struct usb_xfer *xfer = arg;
|
|
|
|
DPRINTF("xfer=%p\n", xfer);
|
|
|
|
USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
|
|
|
|
/* transfer is transferred */
|
|
uhci_device_done(xfer, USB_ERR_TIMEOUT);
|
|
}
|
|
|
|
static void
|
|
uhci_do_poll(struct usb_bus *bus)
|
|
{
|
|
struct uhci_softc *sc = UHCI_BUS2SC(bus);
|
|
|
|
USB_BUS_LOCK(&sc->sc_bus);
|
|
uhci_interrupt_poll(sc);
|
|
USB_BUS_UNLOCK(&sc->sc_bus);
|
|
}
|
|
|
|
static void
|
|
uhci_setup_standard_chain_sub(struct uhci_std_temp *temp)
|
|
{
|
|
uhci_td_t *td;
|
|
uhci_td_t *td_next;
|
|
uhci_td_t *td_alt_next;
|
|
uint32_t average;
|
|
uint32_t len_old;
|
|
uint8_t shortpkt_old;
|
|
uint8_t precompute;
|
|
|
|
td_alt_next = NULL;
|
|
shortpkt_old = temp->shortpkt;
|
|
len_old = temp->len;
|
|
precompute = 1;
|
|
|
|
/* software is used to detect short incoming transfers */
|
|
|
|
if ((temp->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) {
|
|
temp->td_status |= htole32(UHCI_TD_SPD);
|
|
} else {
|
|
temp->td_status &= ~htole32(UHCI_TD_SPD);
|
|
}
|
|
|
|
temp->ml.buf_offset = 0;
|
|
|
|
restart:
|
|
|
|
temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0));
|
|
temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->average));
|
|
|
|
td = temp->td;
|
|
td_next = temp->td_next;
|
|
|
|
while (1) {
|
|
|
|
if (temp->len == 0) {
|
|
|
|
if (temp->shortpkt) {
|
|
break;
|
|
}
|
|
/* send a Zero Length Packet, ZLP, last */
|
|
|
|
temp->shortpkt = 1;
|
|
temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(0));
|
|
average = 0;
|
|
|
|
} else {
|
|
|
|
average = temp->average;
|
|
|
|
if (temp->len < average) {
|
|
temp->shortpkt = 1;
|
|
temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0));
|
|
temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->len));
|
|
average = temp->len;
|
|
}
|
|
}
|
|
|
|
if (td_next == NULL) {
|
|
panic("%s: out of UHCI transfer descriptors!", __FUNCTION__);
|
|
}
|
|
/* get next TD */
|
|
|
|
td = td_next;
|
|
td_next = td->obj_next;
|
|
|
|
/* check if we are pre-computing */
|
|
|
|
if (precompute) {
|
|
|
|
/* update remaining length */
|
|
|
|
temp->len -= average;
|
|
|
|
continue;
|
|
}
|
|
/* fill out current TD */
|
|
|
|
td->td_status = temp->td_status;
|
|
td->td_token = temp->td_token;
|
|
|
|
/* update data toggle */
|
|
|
|
temp->td_token ^= htole32(UHCI_TD_SET_DT(1));
|
|
|
|
if (average == 0) {
|
|
|
|
td->len = 0;
|
|
td->td_buffer = 0;
|
|
td->fix_pc = NULL;
|
|
|
|
} else {
|
|
|
|
/* update remaining length */
|
|
|
|
temp->len -= average;
|
|
|
|
td->len = average;
|
|
|
|
/* fill out buffer pointer and do fixup, if any */
|
|
|
|
uhci_mem_layout_fixup(&temp->ml, td);
|
|
}
|
|
|
|
td->alt_next = td_alt_next;
|
|
|
|
if ((td_next == td_alt_next) && temp->setup_alt_next) {
|
|
/* we need to receive these frames one by one ! */
|
|
td->td_status |= htole32(UHCI_TD_IOC);
|
|
td->td_next = htole32(UHCI_PTR_T);
|
|
} else {
|
|
if (td_next) {
|
|
/* link the current TD with the next one */
|
|
td->td_next = td_next->td_self;
|
|
}
|
|
}
|
|
|
|
usb_pc_cpu_flush(td->page_cache);
|
|
}
|
|
|
|
if (precompute) {
|
|
precompute = 0;
|
|
|
|
/* setup alt next pointer, if any */
|
|
if (temp->last_frame) {
|
|
td_alt_next = NULL;
|
|
} else {
|
|
/* we use this field internally */
|
|
td_alt_next = td_next;
|
|
}
|
|
|
|
/* restore */
|
|
temp->shortpkt = shortpkt_old;
|
|
temp->len = len_old;
|
|
goto restart;
|
|
}
|
|
temp->td = td;
|
|
temp->td_next = td_next;
|
|
}
|
|
|
|
static uhci_td_t *
|
|
uhci_setup_standard_chain(struct usb_xfer *xfer)
|
|
{
|
|
struct uhci_std_temp temp;
|
|
uhci_td_t *td;
|
|
uint32_t x;
|
|
|
|
DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
|
|
xfer->address, UE_GET_ADDR(xfer->endpointno),
|
|
xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
|
|
|
|
temp.average = xfer->max_frame_size;
|
|
temp.max_frame_size = xfer->max_frame_size;
|
|
|
|
/* toggle the DMA set we are using */
|
|
xfer->flags_int.curr_dma_set ^= 1;
|
|
|
|
/* get next DMA set */
|
|
td = xfer->td_start[xfer->flags_int.curr_dma_set];
|
|
xfer->td_transfer_first = td;
|
|
xfer->td_transfer_cache = td;
|
|
|
|
temp.td = NULL;
|
|
temp.td_next = td;
|
|
temp.last_frame = 0;
|
|
temp.setup_alt_next = xfer->flags_int.short_frames_ok;
|
|
|
|
uhci_mem_layout_init(&temp.ml, xfer);
|
|
|
|
temp.td_status =
|
|
htole32(UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) |
|
|
UHCI_TD_ACTIVE));
|
|
|
|
if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
|
|
temp.td_status |= htole32(UHCI_TD_LS);
|
|
}
|
|
temp.td_token =
|
|
htole32(UHCI_TD_SET_ENDPT(xfer->endpointno) |
|
|
UHCI_TD_SET_DEVADDR(xfer->address));
|
|
|
|
if (xfer->endpoint->toggle_next) {
|
|
/* DATA1 is next */
|
|
temp.td_token |= htole32(UHCI_TD_SET_DT(1));
|
|
}
|
|
/* check if we should prepend a setup message */
|
|
|
|
if (xfer->flags_int.control_xfr) {
|
|
|
|
if (xfer->flags_int.control_hdr) {
|
|
|
|
temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
|
|
UHCI_TD_SET_ENDPT(0xF));
|
|
temp.td_token |= htole32(UHCI_TD_PID_SETUP |
|
|
UHCI_TD_SET_DT(0));
|
|
|
|
temp.len = xfer->frlengths[0];
|
|
temp.ml.buf_pc = xfer->frbuffers + 0;
|
|
temp.shortpkt = temp.len ? 1 : 0;
|
|
/* check for last frame */
|
|
if (xfer->nframes == 1) {
|
|
/* no STATUS stage yet, SETUP is last */
|
|
if (xfer->flags_int.control_act) {
|
|
temp.last_frame = 1;
|
|
temp.setup_alt_next = 0;
|
|
}
|
|
}
|
|
uhci_setup_standard_chain_sub(&temp);
|
|
}
|
|
x = 1;
|
|
} else {
|
|
x = 0;
|
|
}
|
|
|
|
while (x != xfer->nframes) {
|
|
|
|
/* DATA0 / DATA1 message */
|
|
|
|
temp.len = xfer->frlengths[x];
|
|
temp.ml.buf_pc = xfer->frbuffers + x;
|
|
|
|
x++;
|
|
|
|
if (x == xfer->nframes) {
|
|
if (xfer->flags_int.control_xfr) {
|
|
/* no STATUS stage yet, DATA is last */
|
|
if (xfer->flags_int.control_act) {
|
|
temp.last_frame = 1;
|
|
temp.setup_alt_next = 0;
|
|
}
|
|
} else {
|
|
temp.last_frame = 1;
|
|
temp.setup_alt_next = 0;
|
|
}
|
|
}
|
|
/*
|
|
* Keep previous data toggle,
|
|
* device address and endpoint number:
|
|
*/
|
|
|
|
temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
|
|
UHCI_TD_SET_ENDPT(0xF) |
|
|
UHCI_TD_SET_DT(1));
|
|
|
|
if (temp.len == 0) {
|
|
|
|
/* make sure that we send an USB packet */
|
|
|
|
temp.shortpkt = 0;
|
|
|
|
} else {
|
|
|
|
/* regular data transfer */
|
|
|
|
temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
|
|
}
|
|
|
|
/* set endpoint direction */
|
|
|
|
temp.td_token |=
|
|
(UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ?
|
|
htole32(UHCI_TD_PID_IN) :
|
|
htole32(UHCI_TD_PID_OUT);
|
|
|
|
uhci_setup_standard_chain_sub(&temp);
|
|
}
|
|
|
|
/* check if we should append a status stage */
|
|
|
|
if (xfer->flags_int.control_xfr &&
|
|
!xfer->flags_int.control_act) {
|
|
|
|
/*
|
|
* send a DATA1 message and reverse the current endpoint
|
|
* direction
|
|
*/
|
|
|
|
temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
|
|
UHCI_TD_SET_ENDPT(0xF) |
|
|
UHCI_TD_SET_DT(1));
|
|
temp.td_token |=
|
|
(UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ?
|
|
htole32(UHCI_TD_PID_IN | UHCI_TD_SET_DT(1)) :
|
|
htole32(UHCI_TD_PID_OUT | UHCI_TD_SET_DT(1));
|
|
|
|
temp.len = 0;
|
|
temp.ml.buf_pc = NULL;
|
|
temp.shortpkt = 0;
|
|
temp.last_frame = 1;
|
|
temp.setup_alt_next = 0;
|
|
|
|
uhci_setup_standard_chain_sub(&temp);
|
|
}
|
|
td = temp.td;
|
|
|
|
/* Ensure that last TD is terminating: */
|
|
td->td_next = htole32(UHCI_PTR_T);
|
|
|
|
/* set interrupt bit */
|
|
|
|
td->td_status |= htole32(UHCI_TD_IOC);
|
|
|
|
usb_pc_cpu_flush(td->page_cache);
|
|
|
|
/* must have at least one frame! */
|
|
|
|
xfer->td_transfer_last = td;
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 8) {
|
|
DPRINTF("nexttog=%d; data before transfer:\n",
|
|
xfer->endpoint->toggle_next);
|
|
uhci_dump_tds(xfer->td_transfer_first);
|
|
}
|
|
#endif
|
|
return (xfer->td_transfer_first);
|
|
}
|
|
|
|
/* NOTE: "done" can be run two times in a row,
|
|
* from close and from interrupt
|
|
*/
|
|
|
|
static void
|
|
uhci_device_done(struct usb_xfer *xfer, usb_error_t error)
|
|
{
|
|
struct usb_pipe_methods *methods = xfer->endpoint->methods;
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
uhci_qh_t *qh;
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
|
|
xfer, xfer->endpoint, error);
|
|
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
if (qh) {
|
|
usb_pc_cpu_invalidate(qh->page_cache);
|
|
}
|
|
if (xfer->flags_int.bandwidth_reclaimed) {
|
|
xfer->flags_int.bandwidth_reclaimed = 0;
|
|
uhci_rem_loop(sc);
|
|
}
|
|
if (methods == &uhci_device_bulk_methods) {
|
|
UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last);
|
|
}
|
|
if (methods == &uhci_device_ctrl_methods) {
|
|
if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
|
|
UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last);
|
|
} else {
|
|
UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last);
|
|
}
|
|
}
|
|
if (methods == &uhci_device_intr_methods) {
|
|
UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
|
|
}
|
|
/*
|
|
* Only finish isochronous transfers once
|
|
* which will update "xfer->frlengths".
|
|
*/
|
|
if (xfer->td_transfer_first &&
|
|
xfer->td_transfer_last) {
|
|
if (methods == &uhci_device_isoc_methods) {
|
|
uhci_isoc_done(sc, xfer);
|
|
}
|
|
xfer->td_transfer_first = NULL;
|
|
xfer->td_transfer_last = NULL;
|
|
}
|
|
/* dequeue transfer and start next transfer */
|
|
usbd_transfer_done(xfer, error);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci bulk support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
uhci_device_bulk_open(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_device_bulk_close(struct usb_xfer *xfer)
|
|
{
|
|
uhci_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
uhci_device_bulk_enter(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_device_bulk_start(struct usb_xfer *xfer)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
uhci_td_t *td;
|
|
uhci_qh_t *qh;
|
|
|
|
/* setup TD's */
|
|
td = uhci_setup_standard_chain(xfer);
|
|
|
|
/* setup QH */
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
|
|
qh->e_next = td;
|
|
qh->qh_e_next = td->td_self;
|
|
|
|
if (xfer->xroot->udev->flags.self_suspended == 0) {
|
|
UHCI_APPEND_QH(qh, sc->sc_bulk_p_last);
|
|
uhci_add_loop(sc);
|
|
xfer->flags_int.bandwidth_reclaimed = 1;
|
|
} else {
|
|
usb_pc_cpu_flush(qh->page_cache);
|
|
}
|
|
|
|
/* put transfer on interrupt queue */
|
|
uhci_transfer_intr_enqueue(xfer);
|
|
}
|
|
|
|
struct usb_pipe_methods uhci_device_bulk_methods =
|
|
{
|
|
.open = uhci_device_bulk_open,
|
|
.close = uhci_device_bulk_close,
|
|
.enter = uhci_device_bulk_enter,
|
|
.start = uhci_device_bulk_start,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci control support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
uhci_device_ctrl_open(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_device_ctrl_close(struct usb_xfer *xfer)
|
|
{
|
|
uhci_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
uhci_device_ctrl_enter(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_device_ctrl_start(struct usb_xfer *xfer)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
uhci_qh_t *qh;
|
|
uhci_td_t *td;
|
|
|
|
/* setup TD's */
|
|
td = uhci_setup_standard_chain(xfer);
|
|
|
|
/* setup QH */
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
|
|
qh->e_next = td;
|
|
qh->qh_e_next = td->td_self;
|
|
|
|
/*
|
|
* NOTE: some devices choke on bandwidth- reclamation for control
|
|
* transfers
|
|
*/
|
|
if (xfer->xroot->udev->flags.self_suspended == 0) {
|
|
if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
|
|
UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last);
|
|
} else {
|
|
UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last);
|
|
}
|
|
} else {
|
|
usb_pc_cpu_flush(qh->page_cache);
|
|
}
|
|
/* put transfer on interrupt queue */
|
|
uhci_transfer_intr_enqueue(xfer);
|
|
}
|
|
|
|
struct usb_pipe_methods uhci_device_ctrl_methods =
|
|
{
|
|
.open = uhci_device_ctrl_open,
|
|
.close = uhci_device_ctrl_close,
|
|
.enter = uhci_device_ctrl_enter,
|
|
.start = uhci_device_ctrl_start,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci interrupt support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
uhci_device_intr_open(struct usb_xfer *xfer)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
uint16_t best;
|
|
uint16_t bit;
|
|
uint16_t x;
|
|
|
|
best = 0;
|
|
bit = UHCI_IFRAMELIST_COUNT / 2;
|
|
while (bit) {
|
|
if (xfer->interval >= bit) {
|
|
x = bit;
|
|
best = bit;
|
|
while (x & bit) {
|
|
if (sc->sc_intr_stat[x] <
|
|
sc->sc_intr_stat[best]) {
|
|
best = x;
|
|
}
|
|
x++;
|
|
}
|
|
break;
|
|
}
|
|
bit >>= 1;
|
|
}
|
|
|
|
sc->sc_intr_stat[best]++;
|
|
xfer->qh_pos = best;
|
|
|
|
DPRINTFN(3, "best=%d interval=%d\n",
|
|
best, xfer->interval);
|
|
}
|
|
|
|
static void
|
|
uhci_device_intr_close(struct usb_xfer *xfer)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
|
|
sc->sc_intr_stat[xfer->qh_pos]--;
|
|
|
|
uhci_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
uhci_device_intr_enter(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_device_intr_start(struct usb_xfer *xfer)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
uhci_qh_t *qh;
|
|
uhci_td_t *td;
|
|
|
|
/* setup TD's */
|
|
td = uhci_setup_standard_chain(xfer);
|
|
|
|
/* setup QH */
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
|
|
qh->e_next = td;
|
|
qh->qh_e_next = td->td_self;
|
|
|
|
if (xfer->xroot->udev->flags.self_suspended == 0) {
|
|
/* enter QHs into the controller data structures */
|
|
UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
|
|
} else {
|
|
usb_pc_cpu_flush(qh->page_cache);
|
|
}
|
|
|
|
/* put transfer on interrupt queue */
|
|
uhci_transfer_intr_enqueue(xfer);
|
|
}
|
|
|
|
struct usb_pipe_methods uhci_device_intr_methods =
|
|
{
|
|
.open = uhci_device_intr_open,
|
|
.close = uhci_device_intr_close,
|
|
.enter = uhci_device_intr_enter,
|
|
.start = uhci_device_intr_start,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci isochronous support
|
|
*------------------------------------------------------------------------*/
|
|
static void
|
|
uhci_device_isoc_open(struct usb_xfer *xfer)
|
|
{
|
|
uhci_td_t *td;
|
|
uint32_t td_token;
|
|
uint8_t ds;
|
|
|
|
td_token =
|
|
(UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ?
|
|
UHCI_TD_IN(0, xfer->endpointno, xfer->address, 0) :
|
|
UHCI_TD_OUT(0, xfer->endpointno, xfer->address, 0);
|
|
|
|
td_token = htole32(td_token);
|
|
|
|
/* initialize all TD's */
|
|
|
|
for (ds = 0; ds != 2; ds++) {
|
|
|
|
for (td = xfer->td_start[ds]; td; td = td->obj_next) {
|
|
|
|
/* mark TD as inactive */
|
|
td->td_status = htole32(UHCI_TD_IOS);
|
|
td->td_token = td_token;
|
|
|
|
usb_pc_cpu_flush(td->page_cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_device_isoc_close(struct usb_xfer *xfer)
|
|
{
|
|
uhci_device_done(xfer, USB_ERR_CANCELLED);
|
|
}
|
|
|
|
static void
|
|
uhci_device_isoc_enter(struct usb_xfer *xfer)
|
|
{
|
|
struct uhci_mem_layout ml;
|
|
uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
|
|
uint32_t nframes;
|
|
uint32_t temp;
|
|
uint32_t *plen;
|
|
|
|
#ifdef USB_DEBUG
|
|
uint8_t once = 1;
|
|
|
|
#endif
|
|
uhci_td_t *td;
|
|
uhci_td_t *td_last = NULL;
|
|
uhci_td_t **pp_last;
|
|
|
|
DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
|
|
xfer, xfer->endpoint->isoc_next, xfer->nframes);
|
|
|
|
nframes = UREAD2(sc, UHCI_FRNUM);
|
|
|
|
temp = (nframes - xfer->endpoint->isoc_next) &
|
|
(UHCI_VFRAMELIST_COUNT - 1);
|
|
|
|
if ((xfer->endpoint->is_synced == 0) ||
|
|
(temp < xfer->nframes)) {
|
|
/*
|
|
* If there is data underflow or the pipe queue is empty we
|
|
* schedule the transfer a few frames ahead of the current
|
|
* frame position. Else two isochronous transfers might
|
|
* overlap.
|
|
*/
|
|
xfer->endpoint->isoc_next = (nframes + 3) & (UHCI_VFRAMELIST_COUNT - 1);
|
|
xfer->endpoint->is_synced = 1;
|
|
DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
|
|
}
|
|
/*
|
|
* compute how many milliseconds the insertion is ahead of the
|
|
* current frame position:
|
|
*/
|
|
temp = (xfer->endpoint->isoc_next - nframes) &
|
|
(UHCI_VFRAMELIST_COUNT - 1);
|
|
|
|
/*
|
|
* pre-compute when the isochronous transfer will be finished:
|
|
*/
|
|
xfer->isoc_time_complete =
|
|
usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
|
|
xfer->nframes;
|
|
|
|
/* get the real number of frames */
|
|
|
|
nframes = xfer->nframes;
|
|
|
|
uhci_mem_layout_init(&ml, xfer);
|
|
|
|
plen = xfer->frlengths;
|
|
|
|
/* toggle the DMA set we are using */
|
|
xfer->flags_int.curr_dma_set ^= 1;
|
|
|
|
/* get next DMA set */
|
|
td = xfer->td_start[xfer->flags_int.curr_dma_set];
|
|
xfer->td_transfer_first = td;
|
|
|
|
pp_last = &sc->sc_isoc_p_last[xfer->endpoint->isoc_next];
|
|
|
|
/* store starting position */
|
|
|
|
xfer->qh_pos = xfer->endpoint->isoc_next;
|
|
|
|
while (nframes--) {
|
|
if (td == NULL) {
|
|
panic("%s:%d: out of TD's\n",
|
|
__FUNCTION__, __LINE__);
|
|
}
|
|
if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) {
|
|
pp_last = &sc->sc_isoc_p_last[0];
|
|
}
|
|
if (*plen > xfer->max_frame_size) {
|
|
#ifdef USB_DEBUG
|
|
if (once) {
|
|
once = 0;
|
|
printf("%s: frame length(%d) exceeds %d "
|
|
"bytes (frame truncated)\n",
|
|
__FUNCTION__, *plen,
|
|
xfer->max_frame_size);
|
|
}
|
|
#endif
|
|
*plen = xfer->max_frame_size;
|
|
}
|
|
/* reuse td_token from last transfer */
|
|
|
|
td->td_token &= htole32(~UHCI_TD_MAXLEN_MASK);
|
|
td->td_token |= htole32(UHCI_TD_SET_MAXLEN(*plen));
|
|
|
|
td->len = *plen;
|
|
|
|
if (td->len == 0) {
|
|
/*
|
|
* Do not call "uhci_mem_layout_fixup()" when the
|
|
* length is zero!
|
|
*/
|
|
td->td_buffer = 0;
|
|
td->fix_pc = NULL;
|
|
|
|
} else {
|
|
|
|
/* fill out buffer pointer and do fixup, if any */
|
|
|
|
uhci_mem_layout_fixup(&ml, td);
|
|
|
|
}
|
|
|
|
/* update status */
|
|
if (nframes == 0) {
|
|
td->td_status = htole32
|
|
(UHCI_TD_ZERO_ACTLEN
|
|
(UHCI_TD_SET_ERRCNT(0) |
|
|
UHCI_TD_ACTIVE |
|
|
UHCI_TD_IOS |
|
|
UHCI_TD_IOC));
|
|
} else {
|
|
td->td_status = htole32
|
|
(UHCI_TD_ZERO_ACTLEN
|
|
(UHCI_TD_SET_ERRCNT(0) |
|
|
UHCI_TD_ACTIVE |
|
|
UHCI_TD_IOS));
|
|
}
|
|
|
|
usb_pc_cpu_flush(td->page_cache);
|
|
|
|
#ifdef USB_DEBUG
|
|
if (uhcidebug > 5) {
|
|
DPRINTF("TD %d\n", nframes);
|
|
uhci_dump_td(td);
|
|
}
|
|
#endif
|
|
/* insert TD into schedule */
|
|
UHCI_APPEND_TD(td, *pp_last);
|
|
pp_last++;
|
|
|
|
plen++;
|
|
td_last = td;
|
|
td = td->obj_next;
|
|
}
|
|
|
|
xfer->td_transfer_last = td_last;
|
|
|
|
/* update isoc_next */
|
|
xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_p_last[0]) &
|
|
(UHCI_VFRAMELIST_COUNT - 1);
|
|
}
|
|
|
|
static void
|
|
uhci_device_isoc_start(struct usb_xfer *xfer)
|
|
{
|
|
/* put transfer on interrupt queue */
|
|
uhci_transfer_intr_enqueue(xfer);
|
|
}
|
|
|
|
struct usb_pipe_methods uhci_device_isoc_methods =
|
|
{
|
|
.open = uhci_device_isoc_open,
|
|
.close = uhci_device_isoc_close,
|
|
.enter = uhci_device_isoc_enter,
|
|
.start = uhci_device_isoc_start,
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* uhci root control support
|
|
*------------------------------------------------------------------------*
|
|
* Simulate a hardware hub by handling all the necessary requests.
|
|
*------------------------------------------------------------------------*/
|
|
|
|
static const
|
|
struct usb_device_descriptor uhci_devd =
|
|
{
|
|
sizeof(struct usb_device_descriptor),
|
|
UDESC_DEVICE, /* type */
|
|
{0x00, 0x01}, /* USB version */
|
|
UDCLASS_HUB, /* class */
|
|
UDSUBCLASS_HUB, /* subclass */
|
|
UDPROTO_FSHUB, /* protocol */
|
|
64, /* max packet */
|
|
{0}, {0}, {0x00, 0x01}, /* device id */
|
|
1, 2, 0, /* string indicies */
|
|
1 /* # of configurations */
|
|
};
|
|
|
|
static const struct uhci_config_desc uhci_confd = {
|
|
.confd = {
|
|
.bLength = sizeof(struct usb_config_descriptor),
|
|
.bDescriptorType = UDESC_CONFIG,
|
|
.wTotalLength[0] = sizeof(uhci_confd),
|
|
.bNumInterface = 1,
|
|
.bConfigurationValue = 1,
|
|
.iConfiguration = 0,
|
|
.bmAttributes = UC_SELF_POWERED,
|
|
.bMaxPower = 0 /* max power */
|
|
},
|
|
.ifcd = {
|
|
.bLength = sizeof(struct usb_interface_descriptor),
|
|
.bDescriptorType = UDESC_INTERFACE,
|
|
.bNumEndpoints = 1,
|
|
.bInterfaceClass = UICLASS_HUB,
|
|
.bInterfaceSubClass = UISUBCLASS_HUB,
|
|
.bInterfaceProtocol = UIPROTO_FSHUB,
|
|
},
|
|
.endpd = {
|
|
.bLength = sizeof(struct usb_endpoint_descriptor),
|
|
.bDescriptorType = UDESC_ENDPOINT,
|
|
.bEndpointAddress = UE_DIR_IN | UHCI_INTR_ENDPT,
|
|
.bmAttributes = UE_INTERRUPT,
|
|
.wMaxPacketSize[0] = 8, /* max packet (63 ports) */
|
|
.bInterval = 255,
|
|
},
|
|
};
|
|
|
|
static const
|
|
struct usb_hub_descriptor_min uhci_hubd_piix =
|
|
{
|
|
sizeof(uhci_hubd_piix),
|
|
UDESC_HUB,
|
|
2,
|
|
{UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0},
|
|
50, /* power on to power good */
|
|
0,
|
|
{0x00}, /* both ports are removable */
|
|
};
|
|
|
|
/*
|
|
* The USB hub protocol requires that SET_FEATURE(PORT_RESET) also
|
|
* enables the port, and also states that SET_FEATURE(PORT_ENABLE)
|
|
* should not be used by the USB subsystem. As we cannot issue a
|
|
* SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port
|
|
* will be enabled as part of the reset.
|
|
*
|
|
* On the VT83C572, the port cannot be successfully enabled until the
|
|
* outstanding "port enable change" and "connection status change"
|
|
* events have been reset.
|
|
*/
|
|
static usb_error_t
|
|
uhci_portreset(uhci_softc_t *sc, uint16_t index)
|
|
{
|
|
uint16_t port;
|
|
uint16_t x;
|
|
uint8_t lim;
|
|
|
|
if (index == 1)
|
|
port = UHCI_PORTSC1;
|
|
else if (index == 2)
|
|
port = UHCI_PORTSC2;
|
|
else
|
|
return (USB_ERR_IOERROR);
|
|
|
|
/*
|
|
* Before we do anything, turn on SOF messages on the USB
|
|
* BUS. Some USB devices do not cope without them!
|
|
*/
|
|
uhci_restart(sc);
|
|
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_PR);
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx,
|
|
USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY));
|
|
|
|
DPRINTFN(4, "uhci port %d reset, status0 = 0x%04x\n",
|
|
index, UREAD2(sc, port));
|
|
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x & ~UHCI_PORTSC_PR);
|
|
|
|
|
|
mtx_unlock(&sc->sc_bus.bus_mtx);
|
|
|
|
/*
|
|
* This delay needs to be exactly 100us, else some USB devices
|
|
* fail to attach!
|
|
*/
|
|
DELAY(100);
|
|
|
|
mtx_lock(&sc->sc_bus.bus_mtx);
|
|
|
|
DPRINTFN(4, "uhci port %d reset, status1 = 0x%04x\n",
|
|
index, UREAD2(sc, port));
|
|
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_PE);
|
|
|
|
for (lim = 0; lim < 12; lim++) {
|
|
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx,
|
|
USB_MS_TO_TICKS(USB_PORT_RESET_DELAY));
|
|
|
|
x = UREAD2(sc, port);
|
|
|
|
DPRINTFN(4, "uhci port %d iteration %u, status = 0x%04x\n",
|
|
index, lim, x);
|
|
|
|
if (!(x & UHCI_PORTSC_CCS)) {
|
|
/*
|
|
* No device is connected (or was disconnected
|
|
* during reset). Consider the port reset.
|
|
* The delay must be long enough to ensure on
|
|
* the initial iteration that the device
|
|
* connection will have been registered. 50ms
|
|
* appears to be sufficient, but 20ms is not.
|
|
*/
|
|
DPRINTFN(4, "uhci port %d loop %u, device detached\n",
|
|
index, lim);
|
|
goto done;
|
|
}
|
|
if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) {
|
|
/*
|
|
* Port enabled changed and/or connection
|
|
* status changed were set. Reset either or
|
|
* both raised flags (by writing a 1 to that
|
|
* bit), and wait again for state to settle.
|
|
*/
|
|
UWRITE2(sc, port, URWMASK(x) |
|
|
(x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)));
|
|
continue;
|
|
}
|
|
if (x & UHCI_PORTSC_PE) {
|
|
/* port is enabled */
|
|
goto done;
|
|
}
|
|
UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE);
|
|
}
|
|
|
|
DPRINTFN(2, "uhci port %d reset timed out\n", index);
|
|
return (USB_ERR_TIMEOUT);
|
|
|
|
done:
|
|
DPRINTFN(4, "uhci port %d reset, status2 = 0x%04x\n",
|
|
index, UREAD2(sc, port));
|
|
|
|
sc->sc_isreset = 1;
|
|
return (USB_ERR_NORMAL_COMPLETION);
|
|
}
|
|
|
|
static usb_error_t
|
|
uhci_roothub_exec(struct usb_device *udev,
|
|
struct usb_device_request *req, const void **pptr, uint16_t *plength)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(udev->bus);
|
|
const void *ptr;
|
|
const char *str_ptr;
|
|
uint16_t x;
|
|
uint16_t port;
|
|
uint16_t value;
|
|
uint16_t index;
|
|
uint16_t status;
|
|
uint16_t change;
|
|
uint16_t len;
|
|
usb_error_t err;
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
/* buffer reset */
|
|
ptr = (const void *)&sc->sc_hub_desc.temp;
|
|
len = 0;
|
|
err = 0;
|
|
|
|
value = UGETW(req->wValue);
|
|
index = UGETW(req->wIndex);
|
|
|
|
DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
|
|
"wValue=0x%04x wIndex=0x%04x\n",
|
|
req->bmRequestType, req->bRequest,
|
|
UGETW(req->wLength), value, index);
|
|
|
|
#define C(x,y) ((x) | ((y) << 8))
|
|
switch (C(req->bRequest, req->bmRequestType)) {
|
|
case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
|
|
case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
|
|
case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
|
|
/*
|
|
* DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
|
|
* for the integrated root hub.
|
|
*/
|
|
break;
|
|
case C(UR_GET_CONFIG, UT_READ_DEVICE):
|
|
len = 1;
|
|
sc->sc_hub_desc.temp[0] = sc->sc_conf;
|
|
break;
|
|
case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
|
|
switch (value >> 8) {
|
|
case UDESC_DEVICE:
|
|
if ((value & 0xff) != 0) {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
len = sizeof(uhci_devd);
|
|
ptr = (const void *)&uhci_devd;
|
|
break;
|
|
|
|
case UDESC_CONFIG:
|
|
if ((value & 0xff) != 0) {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
len = sizeof(uhci_confd);
|
|
ptr = (const void *)&uhci_confd;
|
|
break;
|
|
|
|
case UDESC_STRING:
|
|
switch (value & 0xff) {
|
|
case 0: /* Language table */
|
|
str_ptr = "\001";
|
|
break;
|
|
|
|
case 1: /* Vendor */
|
|
str_ptr = sc->sc_vendor;
|
|
break;
|
|
|
|
case 2: /* Product */
|
|
str_ptr = "UHCI root HUB";
|
|
break;
|
|
|
|
default:
|
|
str_ptr = "";
|
|
break;
|
|
}
|
|
|
|
len = usb_make_str_desc
|
|
(sc->sc_hub_desc.temp,
|
|
sizeof(sc->sc_hub_desc.temp),
|
|
str_ptr);
|
|
break;
|
|
|
|
default:
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
break;
|
|
case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
|
|
len = 1;
|
|
sc->sc_hub_desc.temp[0] = 0;
|
|
break;
|
|
case C(UR_GET_STATUS, UT_READ_DEVICE):
|
|
len = 2;
|
|
USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
|
|
break;
|
|
case C(UR_GET_STATUS, UT_READ_INTERFACE):
|
|
case C(UR_GET_STATUS, UT_READ_ENDPOINT):
|
|
len = 2;
|
|
USETW(sc->sc_hub_desc.stat.wStatus, 0);
|
|
break;
|
|
case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
|
|
if (value >= UHCI_MAX_DEVICES) {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
sc->sc_addr = value;
|
|
break;
|
|
case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
|
|
if ((value != 0) && (value != 1)) {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
sc->sc_conf = value;
|
|
break;
|
|
case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
|
|
break;
|
|
case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
|
|
case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
|
|
case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
|
|
break;
|
|
case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
|
|
break;
|
|
/* Hub requests */
|
|
case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
|
|
break;
|
|
case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
|
|
DPRINTFN(4, "UR_CLEAR_PORT_FEATURE "
|
|
"port=%d feature=%d\n",
|
|
index, value);
|
|
if (index == 1)
|
|
port = UHCI_PORTSC1;
|
|
else if (index == 2)
|
|
port = UHCI_PORTSC2;
|
|
else {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
switch (value) {
|
|
case UHF_PORT_ENABLE:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x & ~UHCI_PORTSC_PE);
|
|
break;
|
|
case UHF_PORT_SUSPEND:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x & ~(UHCI_PORTSC_SUSP));
|
|
break;
|
|
case UHF_PORT_RESET:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x & ~UHCI_PORTSC_PR);
|
|
break;
|
|
case UHF_C_PORT_CONNECTION:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_CSC);
|
|
break;
|
|
case UHF_C_PORT_ENABLE:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_POEDC);
|
|
break;
|
|
case UHF_C_PORT_OVER_CURRENT:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_OCIC);
|
|
break;
|
|
case UHF_C_PORT_RESET:
|
|
sc->sc_isreset = 0;
|
|
err = USB_ERR_NORMAL_COMPLETION;
|
|
goto done;
|
|
case UHF_C_PORT_SUSPEND:
|
|
sc->sc_isresumed &= ~(1 << index);
|
|
break;
|
|
case UHF_PORT_CONNECTION:
|
|
case UHF_PORT_OVER_CURRENT:
|
|
case UHF_PORT_POWER:
|
|
case UHF_PORT_LOW_SPEED:
|
|
default:
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
break;
|
|
case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER):
|
|
if (index == 1)
|
|
port = UHCI_PORTSC1;
|
|
else if (index == 2)
|
|
port = UHCI_PORTSC2;
|
|
else {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
len = 1;
|
|
sc->sc_hub_desc.temp[0] =
|
|
((UREAD2(sc, port) & UHCI_PORTSC_LS) >>
|
|
UHCI_PORTSC_LS_SHIFT);
|
|
break;
|
|
case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
|
|
if ((value & 0xff) != 0) {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
len = sizeof(uhci_hubd_piix);
|
|
ptr = (const void *)&uhci_hubd_piix;
|
|
break;
|
|
case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
|
|
len = 16;
|
|
memset(sc->sc_hub_desc.temp, 0, 16);
|
|
break;
|
|
case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
|
|
if (index == 1)
|
|
port = UHCI_PORTSC1;
|
|
else if (index == 2)
|
|
port = UHCI_PORTSC2;
|
|
else {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
x = UREAD2(sc, port);
|
|
status = change = 0;
|
|
if (x & UHCI_PORTSC_CCS)
|
|
status |= UPS_CURRENT_CONNECT_STATUS;
|
|
if (x & UHCI_PORTSC_CSC)
|
|
change |= UPS_C_CONNECT_STATUS;
|
|
if (x & UHCI_PORTSC_PE)
|
|
status |= UPS_PORT_ENABLED;
|
|
if (x & UHCI_PORTSC_POEDC)
|
|
change |= UPS_C_PORT_ENABLED;
|
|
if (x & UHCI_PORTSC_OCI)
|
|
status |= UPS_OVERCURRENT_INDICATOR;
|
|
if (x & UHCI_PORTSC_OCIC)
|
|
change |= UPS_C_OVERCURRENT_INDICATOR;
|
|
if (x & UHCI_PORTSC_LSDA)
|
|
status |= UPS_LOW_SPEED;
|
|
if ((x & UHCI_PORTSC_PE) && (x & UHCI_PORTSC_RD)) {
|
|
/* need to do a write back */
|
|
UWRITE2(sc, port, URWMASK(x));
|
|
|
|
/* wait 20ms for resume sequence to complete */
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
|
|
|
|
/* clear suspend and resume detect */
|
|
UWRITE2(sc, port, URWMASK(x) & ~(UHCI_PORTSC_RD |
|
|
UHCI_PORTSC_SUSP));
|
|
|
|
/* wait a little bit */
|
|
usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 500);
|
|
|
|
sc->sc_isresumed |= (1 << index);
|
|
|
|
} else if (x & UHCI_PORTSC_SUSP) {
|
|
status |= UPS_SUSPEND;
|
|
}
|
|
status |= UPS_PORT_POWER;
|
|
if (sc->sc_isresumed & (1 << index))
|
|
change |= UPS_C_SUSPEND;
|
|
if (sc->sc_isreset)
|
|
change |= UPS_C_PORT_RESET;
|
|
USETW(sc->sc_hub_desc.ps.wPortStatus, status);
|
|
USETW(sc->sc_hub_desc.ps.wPortChange, change);
|
|
len = sizeof(sc->sc_hub_desc.ps);
|
|
break;
|
|
case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
|
|
break;
|
|
case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
|
|
if (index == 1)
|
|
port = UHCI_PORTSC1;
|
|
else if (index == 2)
|
|
port = UHCI_PORTSC2;
|
|
else {
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
switch (value) {
|
|
case UHF_PORT_ENABLE:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_PE);
|
|
break;
|
|
case UHF_PORT_SUSPEND:
|
|
x = URWMASK(UREAD2(sc, port));
|
|
UWRITE2(sc, port, x | UHCI_PORTSC_SUSP);
|
|
break;
|
|
case UHF_PORT_RESET:
|
|
err = uhci_portreset(sc, index);
|
|
goto done;
|
|
case UHF_PORT_POWER:
|
|
/* pretend we turned on power */
|
|
err = USB_ERR_NORMAL_COMPLETION;
|
|
goto done;
|
|
case UHF_C_PORT_CONNECTION:
|
|
case UHF_C_PORT_ENABLE:
|
|
case UHF_C_PORT_OVER_CURRENT:
|
|
case UHF_PORT_CONNECTION:
|
|
case UHF_PORT_OVER_CURRENT:
|
|
case UHF_PORT_LOW_SPEED:
|
|
case UHF_C_PORT_SUSPEND:
|
|
case UHF_C_PORT_RESET:
|
|
default:
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
break;
|
|
default:
|
|
err = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
done:
|
|
*plength = len;
|
|
*pptr = ptr;
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* This routine is executed periodically and simulates interrupts from
|
|
* the root controller interrupt pipe for port status change:
|
|
*/
|
|
static void
|
|
uhci_root_intr(uhci_softc_t *sc)
|
|
{
|
|
DPRINTFN(21, "\n");
|
|
|
|
USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
|
|
|
|
sc->sc_hub_idata[0] = 0;
|
|
|
|
if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC |
|
|
UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) {
|
|
sc->sc_hub_idata[0] |= 1 << 1;
|
|
}
|
|
if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC |
|
|
UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) {
|
|
sc->sc_hub_idata[0] |= 1 << 2;
|
|
}
|
|
|
|
/* restart timer */
|
|
usb_callout_reset(&sc->sc_root_intr, hz,
|
|
(void *)&uhci_root_intr, sc);
|
|
|
|
if (sc->sc_hub_idata[0] != 0) {
|
|
uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
|
|
sizeof(sc->sc_hub_idata));
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_xfer_setup(struct usb_setup_params *parm)
|
|
{
|
|
struct usb_page_search page_info;
|
|
struct usb_page_cache *pc;
|
|
uhci_softc_t *sc;
|
|
struct usb_xfer *xfer;
|
|
void *last_obj;
|
|
uint32_t ntd;
|
|
uint32_t nqh;
|
|
uint32_t nfixup;
|
|
uint32_t n;
|
|
uint16_t align;
|
|
|
|
sc = UHCI_BUS2SC(parm->udev->bus);
|
|
xfer = parm->curr_xfer;
|
|
|
|
parm->hc_max_packet_size = 0x500;
|
|
parm->hc_max_packet_count = 1;
|
|
parm->hc_max_frame_size = 0x500;
|
|
|
|
/*
|
|
* compute ntd and nqh
|
|
*/
|
|
if (parm->methods == &uhci_device_ctrl_methods) {
|
|
xfer->flags_int.bdma_enable = 1;
|
|
xfer->flags_int.bdma_no_post_sync = 1;
|
|
|
|
usbd_transfer_setup_sub(parm);
|
|
|
|
/* see EHCI HC driver for proof of "ntd" formula */
|
|
|
|
nqh = 1;
|
|
ntd = ((2 * xfer->nframes) + 1 /* STATUS */
|
|
+ (xfer->max_data_length / xfer->max_frame_size));
|
|
|
|
} else if (parm->methods == &uhci_device_bulk_methods) {
|
|
xfer->flags_int.bdma_enable = 1;
|
|
xfer->flags_int.bdma_no_post_sync = 1;
|
|
|
|
usbd_transfer_setup_sub(parm);
|
|
|
|
nqh = 1;
|
|
ntd = ((2 * xfer->nframes)
|
|
+ (xfer->max_data_length / xfer->max_frame_size));
|
|
|
|
} else if (parm->methods == &uhci_device_intr_methods) {
|
|
xfer->flags_int.bdma_enable = 1;
|
|
xfer->flags_int.bdma_no_post_sync = 1;
|
|
|
|
usbd_transfer_setup_sub(parm);
|
|
|
|
nqh = 1;
|
|
ntd = ((2 * xfer->nframes)
|
|
+ (xfer->max_data_length / xfer->max_frame_size));
|
|
|
|
} else if (parm->methods == &uhci_device_isoc_methods) {
|
|
xfer->flags_int.bdma_enable = 1;
|
|
xfer->flags_int.bdma_no_post_sync = 1;
|
|
|
|
usbd_transfer_setup_sub(parm);
|
|
|
|
nqh = 0;
|
|
ntd = xfer->nframes;
|
|
|
|
} else {
|
|
|
|
usbd_transfer_setup_sub(parm);
|
|
|
|
nqh = 0;
|
|
ntd = 0;
|
|
}
|
|
|
|
if (parm->err) {
|
|
return;
|
|
}
|
|
/*
|
|
* NOTE: the UHCI controller requires that
|
|
* every packet must be contiguous on
|
|
* the same USB memory page !
|
|
*/
|
|
nfixup = (parm->bufsize / USB_PAGE_SIZE) + 1;
|
|
|
|
/*
|
|
* Compute a suitable power of two alignment
|
|
* for our "max_frame_size" fixup buffer(s):
|
|
*/
|
|
align = xfer->max_frame_size;
|
|
n = 0;
|
|
while (align) {
|
|
align >>= 1;
|
|
n++;
|
|
}
|
|
|
|
/* check for power of two */
|
|
if (!(xfer->max_frame_size &
|
|
(xfer->max_frame_size - 1))) {
|
|
n--;
|
|
}
|
|
/*
|
|
* We don't allow alignments of
|
|
* less than 8 bytes:
|
|
*
|
|
* NOTE: Allocating using an aligment
|
|
* of 1 byte has special meaning!
|
|
*/
|
|
if (n < 3) {
|
|
n = 3;
|
|
}
|
|
align = (1 << n);
|
|
|
|
if (usbd_transfer_setup_sub_malloc(
|
|
parm, &pc, xfer->max_frame_size,
|
|
align, nfixup)) {
|
|
parm->err = USB_ERR_NOMEM;
|
|
return;
|
|
}
|
|
xfer->buf_fixup = pc;
|
|
|
|
alloc_dma_set:
|
|
|
|
if (parm->err) {
|
|
return;
|
|
}
|
|
last_obj = NULL;
|
|
|
|
if (usbd_transfer_setup_sub_malloc(
|
|
parm, &pc, sizeof(uhci_td_t),
|
|
UHCI_TD_ALIGN, ntd)) {
|
|
parm->err = USB_ERR_NOMEM;
|
|
return;
|
|
}
|
|
if (parm->buf) {
|
|
for (n = 0; n != ntd; n++) {
|
|
uhci_td_t *td;
|
|
|
|
usbd_get_page(pc + n, 0, &page_info);
|
|
|
|
td = page_info.buffer;
|
|
|
|
/* init TD */
|
|
if ((parm->methods == &uhci_device_bulk_methods) ||
|
|
(parm->methods == &uhci_device_ctrl_methods) ||
|
|
(parm->methods == &uhci_device_intr_methods)) {
|
|
/* set depth first bit */
|
|
td->td_self = htole32(page_info.physaddr |
|
|
UHCI_PTR_TD | UHCI_PTR_VF);
|
|
} else {
|
|
td->td_self = htole32(page_info.physaddr |
|
|
UHCI_PTR_TD);
|
|
}
|
|
|
|
td->obj_next = last_obj;
|
|
td->page_cache = pc + n;
|
|
|
|
last_obj = td;
|
|
|
|
usb_pc_cpu_flush(pc + n);
|
|
}
|
|
}
|
|
xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
|
|
|
|
last_obj = NULL;
|
|
|
|
if (usbd_transfer_setup_sub_malloc(
|
|
parm, &pc, sizeof(uhci_qh_t),
|
|
UHCI_QH_ALIGN, nqh)) {
|
|
parm->err = USB_ERR_NOMEM;
|
|
return;
|
|
}
|
|
if (parm->buf) {
|
|
for (n = 0; n != nqh; n++) {
|
|
uhci_qh_t *qh;
|
|
|
|
usbd_get_page(pc + n, 0, &page_info);
|
|
|
|
qh = page_info.buffer;
|
|
|
|
/* init QH */
|
|
qh->qh_self = htole32(page_info.physaddr | UHCI_PTR_QH);
|
|
qh->obj_next = last_obj;
|
|
qh->page_cache = pc + n;
|
|
|
|
last_obj = qh;
|
|
|
|
usb_pc_cpu_flush(pc + n);
|
|
}
|
|
}
|
|
xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
|
|
|
|
if (!xfer->flags_int.curr_dma_set) {
|
|
xfer->flags_int.curr_dma_set = 1;
|
|
goto alloc_dma_set;
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
|
|
struct usb_endpoint *ep)
|
|
{
|
|
uhci_softc_t *sc = UHCI_BUS2SC(udev->bus);
|
|
|
|
DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
|
|
ep, udev->address,
|
|
edesc->bEndpointAddress, udev->flags.usb_mode,
|
|
sc->sc_addr);
|
|
|
|
if (udev->flags.usb_mode != USB_MODE_HOST) {
|
|
/* not supported */
|
|
return;
|
|
}
|
|
if (udev->device_index != sc->sc_addr) {
|
|
switch (edesc->bmAttributes & UE_XFERTYPE) {
|
|
case UE_CONTROL:
|
|
ep->methods = &uhci_device_ctrl_methods;
|
|
break;
|
|
case UE_INTERRUPT:
|
|
ep->methods = &uhci_device_intr_methods;
|
|
break;
|
|
case UE_ISOCHRONOUS:
|
|
if (udev->speed == USB_SPEED_FULL) {
|
|
ep->methods = &uhci_device_isoc_methods;
|
|
}
|
|
break;
|
|
case UE_BULK:
|
|
ep->methods = &uhci_device_bulk_methods;
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
uhci_xfer_unsetup(struct usb_xfer *xfer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_get_dma_delay(struct usb_device *udev, uint32_t *pus)
|
|
{
|
|
/*
|
|
* Wait until hardware has finished any possible use of the
|
|
* transfer descriptor(s) and QH
|
|
*/
|
|
*pus = (1125); /* microseconds */
|
|
}
|
|
|
|
static void
|
|
uhci_device_resume(struct usb_device *udev)
|
|
{
|
|
struct uhci_softc *sc = UHCI_BUS2SC(udev->bus);
|
|
struct usb_xfer *xfer;
|
|
struct usb_pipe_methods *methods;
|
|
uhci_qh_t *qh;
|
|
|
|
DPRINTF("\n");
|
|
|
|
USB_BUS_LOCK(udev->bus);
|
|
|
|
TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
|
|
|
|
if (xfer->xroot->udev == udev) {
|
|
|
|
methods = xfer->endpoint->methods;
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
|
|
if (methods == &uhci_device_bulk_methods) {
|
|
UHCI_APPEND_QH(qh, sc->sc_bulk_p_last);
|
|
uhci_add_loop(sc);
|
|
xfer->flags_int.bandwidth_reclaimed = 1;
|
|
}
|
|
if (methods == &uhci_device_ctrl_methods) {
|
|
if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
|
|
UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last);
|
|
} else {
|
|
UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last);
|
|
}
|
|
}
|
|
if (methods == &uhci_device_intr_methods) {
|
|
UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
|
|
}
|
|
}
|
|
}
|
|
|
|
USB_BUS_UNLOCK(udev->bus);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_device_suspend(struct usb_device *udev)
|
|
{
|
|
struct uhci_softc *sc = UHCI_BUS2SC(udev->bus);
|
|
struct usb_xfer *xfer;
|
|
struct usb_pipe_methods *methods;
|
|
uhci_qh_t *qh;
|
|
|
|
DPRINTF("\n");
|
|
|
|
USB_BUS_LOCK(udev->bus);
|
|
|
|
TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
|
|
|
|
if (xfer->xroot->udev == udev) {
|
|
|
|
methods = xfer->endpoint->methods;
|
|
qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
|
|
|
|
if (xfer->flags_int.bandwidth_reclaimed) {
|
|
xfer->flags_int.bandwidth_reclaimed = 0;
|
|
uhci_rem_loop(sc);
|
|
}
|
|
if (methods == &uhci_device_bulk_methods) {
|
|
UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last);
|
|
}
|
|
if (methods == &uhci_device_ctrl_methods) {
|
|
if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
|
|
UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last);
|
|
} else {
|
|
UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last);
|
|
}
|
|
}
|
|
if (methods == &uhci_device_intr_methods) {
|
|
UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
|
|
}
|
|
}
|
|
}
|
|
|
|
USB_BUS_UNLOCK(udev->bus);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
uhci_set_hw_power(struct usb_bus *bus)
|
|
{
|
|
struct uhci_softc *sc = UHCI_BUS2SC(bus);
|
|
uint32_t flags;
|
|
|
|
DPRINTF("\n");
|
|
|
|
USB_BUS_LOCK(bus);
|
|
|
|
flags = bus->hw_power_state;
|
|
|
|
/*
|
|
* WARNING: Some FULL speed USB devices require periodic SOF
|
|
* messages! If any USB devices are connected through the
|
|
* UHCI, power save will be disabled!
|
|
*/
|
|
if (flags & (USB_HW_POWER_CONTROL |
|
|
USB_HW_POWER_NON_ROOT_HUB |
|
|
USB_HW_POWER_BULK |
|
|
USB_HW_POWER_INTERRUPT |
|
|
USB_HW_POWER_ISOC)) {
|
|
DPRINTF("Some USB transfer is "
|
|
"active on unit %u.\n",
|
|
device_get_unit(sc->sc_bus.bdev));
|
|
uhci_restart(sc);
|
|
} else {
|
|
DPRINTF("Power save on unit %u.\n",
|
|
device_get_unit(sc->sc_bus.bdev));
|
|
UHCICMD(sc, UHCI_CMD_MAXP);
|
|
}
|
|
|
|
USB_BUS_UNLOCK(bus);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
struct usb_bus_methods uhci_bus_methods =
|
|
{
|
|
.endpoint_init = uhci_ep_init,
|
|
.xfer_setup = uhci_xfer_setup,
|
|
.xfer_unsetup = uhci_xfer_unsetup,
|
|
.get_dma_delay = uhci_get_dma_delay,
|
|
.device_resume = uhci_device_resume,
|
|
.device_suspend = uhci_device_suspend,
|
|
.set_hw_power = uhci_set_hw_power,
|
|
.roothub_exec = uhci_roothub_exec,
|
|
.xfer_poll = uhci_do_poll,
|
|
};
|