Index: uhci.c =================================================================== RCS file: /home/iedowse/CVS/src/sys/dev/usb/uhci.c,v retrieving revision 1.40.2.7 diff -u -r1.40.2.7 uhci.c --- uhci.c 31 Oct 2000 23:23:29 -0000 1.40.2.7 +++ uhci.c 7 Apr 2002 20:21:05 -0000 @@ -78,6 +78,9 @@ #include #include +/* Use bandwidth reclamation for control transfers. Some devices choke on it. */ +/*#define UHCI_CTL_LOOP */ + #if defined(__FreeBSD__) #include @@ -179,11 +182,15 @@ Static void uhci_timeout(void *); Static void uhci_lock_frames(uhci_softc_t *); Static void uhci_unlock_frames(uhci_softc_t *); -Static void uhci_add_ctrl(uhci_softc_t *, uhci_soft_qh_t *); +Static void uhci_add_ls_ctrl(uhci_softc_t *, uhci_soft_qh_t *); +Static void uhci_add_hs_ctrl(uhci_softc_t *, uhci_soft_qh_t *); Static void uhci_add_bulk(uhci_softc_t *, uhci_soft_qh_t *); -Static void uhci_remove_ctrl(uhci_softc_t *,uhci_soft_qh_t *); +Static void uhci_remove_ls_ctrl(uhci_softc_t *,uhci_soft_qh_t *); +Static void uhci_remove_hs_ctrl(uhci_softc_t *,uhci_soft_qh_t *); Static void uhci_remove_bulk(uhci_softc_t *,uhci_soft_qh_t *); Static int uhci_str(usb_string_descriptor_t *, int, char *); +Static void uhci_add_loop(uhci_softc_t *sc); +Static void uhci_rem_loop(uhci_softc_t *sc); Static usbd_status uhci_setup_isoc(usbd_pipe_handle pipe); Static void uhci_device_isoc_enter(usbd_xfer_handle); @@ -242,6 +249,10 @@ Static void uhci_device_clear_toggle(usbd_pipe_handle pipe); Static void uhci_noop(usbd_pipe_handle pipe); +Static __inline__ uhci_soft_qh_t *uhci_find_prev_qh(uhci_soft_qh_t *, + uhci_soft_qh_t *); + + #ifdef UHCI_DEBUG Static void uhci_dumpregs(uhci_softc_t *); Static void uhci_dump_qhs(uhci_soft_qh_t *); @@ -329,6 +340,22 @@ uhci_device_isoc_done, }; +Static __inline__ uhci_soft_qh_t * +uhci_find_prev_qh(uhci_soft_qh_t *pqh, uhci_soft_qh_t *sqh) +{ + DPRINTFN(15,("uhci_find_prev_qh: pqh=%p sqh=%p\n", pqh, sqh)); + + for (; pqh->hlink != sqh; pqh = pqh->hlink) { +#if defined(DIAGNOSTIC) || defined(UHCI_DEBUG) + if (le32toh(pqh->qh.qh_hlink) & UHCI_PTR_T) { + printf("uhci_find_prev_qh: QH not found\n"); + return (NULL); + } +#endif + } + return (pqh); +} + void uhci_busreset(uhci_softc_t *sc) { @@ -342,7 +369,7 @@ { usbd_status err; int i, j; - uhci_soft_qh_t *csqh, *bsqh, *sqh; + uhci_soft_qh_t *clsqh, *chsqh, *bsqh, *sqh, *lsqh; uhci_soft_td_t *std; DPRINTFN(1,("uhci_init: start\n")); @@ -367,22 +394,59 @@ UWRITE2(sc, UHCI_FRNUM, 0); /* set frame number to 0 */ UWRITE4(sc, UHCI_FLBASEADDR, DMAADDR(&sc->sc_dma, 0)); /* set frame list*/ + /* + * Allocate a TD, inactive, that hangs from the last QH. + * This is to avoid a bug in the PIIX that makes it run berserk + * otherwise. + */ + std = uhci_alloc_std(sc); + if (std == NULL) + return (USBD_NOMEM); + std->link.std = NULL; + std->td.td_link = LE(UHCI_PTR_T); + std->td.td_status = LE(0); /* inactive */ + std->td.td_token = LE(0); + std->td.td_buffer = LE(0); + + /* Allocate the dummy QH marking the end and used for looping the QHs.*/ + lsqh = uhci_alloc_sqh(sc); + if (lsqh == NULL) + return (USBD_NOMEM); + lsqh->hlink = NULL; + lsqh->qh.qh_hlink = LE(UHCI_PTR_T); /* end of QH chain */ + lsqh->elink = std; + lsqh->qh.qh_elink = LE(std->physaddr | UHCI_PTR_TD); + sc->sc_last_qh = lsqh; + /* Allocate the dummy QH where bulk traffic will be queued. */ bsqh = uhci_alloc_sqh(sc); if (bsqh == NULL) return (USBD_NOMEM); - bsqh->qh.qh_hlink = LE(UHCI_PTR_T); /* end of QH chain */ + bsqh->hlink = lsqh; + bsqh->qh.qh_hlink = LE(lsqh->physaddr | UHCI_PTR_QH); + bsqh->elink = NULL; bsqh->qh.qh_elink = LE(UHCI_PTR_T); sc->sc_bulk_start = sc->sc_bulk_end = bsqh; - /* Allocate the dummy QH where control traffic will be queued. */ - csqh = uhci_alloc_sqh(sc); - if (csqh == NULL) + /* Allocate dummy QH where high speed control traffic will be queued. */ + chsqh = uhci_alloc_sqh(sc); + if (chsqh == NULL) + return (USBD_NOMEM); + chsqh->hlink = bsqh; + chsqh->qh.qh_hlink = LE(bsqh->physaddr | UHCI_PTR_QH); + chsqh->elink = NULL; + chsqh->qh.qh_elink = LE(UHCI_PTR_T); + sc->sc_hctl_start = sc->sc_hctl_end = chsqh; + + /* Allocate dummy QH where control traffic will be queued. */ + clsqh = uhci_alloc_sqh(sc); + if (clsqh == NULL) return (USBD_NOMEM); - csqh->hlink = bsqh; - csqh->qh.qh_hlink = LE(bsqh->physaddr | UHCI_PTR_Q); - csqh->qh.qh_elink = LE(UHCI_PTR_T); - sc->sc_ctl_start = sc->sc_ctl_end = csqh; + clsqh->hlink = bsqh; + clsqh->qh.qh_hlink = LE(chsqh->physaddr | UHCI_PTR_QH); + clsqh->elink = NULL; + clsqh->qh.qh_elink = LE(UHCI_PTR_T); + sc->sc_lctl_start = sc->sc_lctl_end = clsqh; /* * Make all (virtual) frame list pointers point to the interrupt @@ -395,13 +459,13 @@ if (std == NULL || sqh == NULL) return (USBD_NOMEM); std->link.sqh = sqh; - std->td.td_link = LE(sqh->physaddr | UHCI_PTR_Q); - std->td.td_status = LE(UHCI_TD_IOS); /* iso, inactive */ + std->td.td_link = LE(sqh->physaddr | UHCI_PTR_QH); + std->td.td_status = LE(UHCI_TD_IOS); /* iso, inactive */ std->td.td_token = LE(0); std->td.td_buffer = LE(0); - sqh->hlink = csqh; - sqh->qh.qh_hlink = LE(csqh->physaddr | UHCI_PTR_Q); - sqh->elink = 0; + sqh->hlink = clsqh; + sqh->qh.qh_hlink = LE(clsqh->physaddr | UHCI_PTR_QH); + sqh->elink = NULL; sqh->qh.qh_elink = LE(UHCI_PTR_T); sc->sc_vframes[i].htd = std; sc->sc_vframes[i].etd = std; @@ -669,8 +733,8 @@ uhci_dumpregs(sc); printf("intrs=%d\n", sc->sc_bus.no_intrs); - printf("framelist[i].link = %08x\n", sc->sc_framelist[0].link); - uhci_dump_qh(sc->sc_ctl_start->qh.hlink); + /*printf("framelist[i].link = %08x\n", sc->sc_framelist[0].link);*/ + uhci_dump_qh(sc->sc_lctl_start); } #endif @@ -821,45 +885,128 @@ LIST_INSERT_HEAD(&uhci_ii_free, ii, list); /* and put on free list */ } -/* Add control QH, called at splusb(). */ +/* + * Let the last QH loop back to the high 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. + */ +void +uhci_add_loop(uhci_softc_t *sc) { +#ifdef UHCI_DEBUG + if (uhcinoloop) + return; +#endif + if (++sc->sc_loops == 1) { + DPRINTFN(5,("uhci_start_loop: add\n")); + /* Note, we don't loop back the soft pointer. */ + sc->sc_last_qh->qh.qh_hlink = + LE(sc->sc_hctl_start->physaddr | UHCI_PTR_QH); + } +} + void -uhci_add_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +uhci_rem_loop(uhci_softc_t *sc) { +#ifdef UHCI_DEBUG + if (uhcinoloop) + return; +#endif + if (--sc->sc_loops == 0) { + DPRINTFN(5,("uhci_end_loop: remove\n")); + sc->sc_last_qh->qh.qh_hlink = LE(UHCI_PTR_T); + } +} + +/* Add high speed control QH, called at splusb(). */ +void +uhci_add_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *eqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_add_ctrl: sqh=%p\n", sqh)); - eqh = sc->sc_ctl_end; + eqh = sc->sc_hctl_end; sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; - eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_Q); - sc->sc_ctl_end = sqh; + eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_QH); + sc->sc_hctl_end = sqh; +#ifdef UHCI_CTL_LOOP + uhci_add_loop(sc); +#endif } -/* Remove control QH, called at splusb(). */ +/* Remove high speed control QH, called at splusb(). */ void -uhci_remove_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +uhci_remove_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *pqh; SPLUSBCHECK; - DPRINTFN(10, ("uhci_remove_ctrl: sqh=%p\n", sqh)); - for (pqh = sc->sc_ctl_start; pqh->hlink != sqh; pqh=pqh->hlink) -#if defined(DIAGNOSTIC) || defined(UHCI_DEBUG) - if (LE(pqh->qh.qh_hlink) & UHCI_PTR_T) { - printf("uhci_remove_ctrl: QH not found\n"); - return; - } -#else - ; + DPRINTFN(10, ("uhci_remove_hs_ctrl: sqh=%p\n", sqh)); +#ifdef UHCI_CTL_LOOP + uhci_rem_loop(sc); #endif - pqh->hlink = sqh->hlink; + /* + * The T bit should be set in the elink of the QH so that the HC + * doesn't follow the pointer. This condition may fail if the + * the transferred packet was short so that the QH still points + * at the last used TD. + * In this case we set the T bit and wait a little for the HC + * to stop looking at the TD. + */ + if (!(sqh->qh.qh_elink & LE(UHCI_PTR_T))) { + sqh->qh.qh_elink = LE(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + + pqh = uhci_find_prev_qh(sc->sc_hctl_start, sqh); + pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; - if (sc->sc_ctl_end == sqh) - sc->sc_ctl_end = pqh; + delay(UHCI_QH_REMOVE_DELAY); + if (sc->sc_hctl_end == sqh) + sc->sc_hctl_end = pqh; +} + +/* Add low speed control QH, called at splusb(). */ +void +uhci_add_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *eqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_add_ls_ctrl: sqh=%p\n", sqh)); + eqh = sc->sc_lctl_end; + sqh->hlink = eqh->hlink; + sqh->qh.qh_hlink = eqh->qh.qh_hlink; + eqh->hlink = sqh; + eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_QH); + sc->sc_lctl_end = sqh; +} + +/* Remove low speed control QH, called at splusb(). */ +void +uhci_remove_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *pqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_remove_ls_ctrl: sqh=%p\n", sqh)); + /* See comment in uhci_remove_hs_ctrl() */ + if (!(sqh->qh.qh_elink & LE(UHCI_PTR_T))) { + sqh->qh.qh_elink = LE(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + pqh = uhci_find_prev_qh(sc->sc_lctl_start, sqh); + pqh->hlink = sqh->hlink; + pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); + if (sc->sc_lctl_end == sqh) + sc->sc_lctl_end = pqh; } /* Add bulk QH, called at splusb(). */ @@ -875,8 +1022,9 @@ sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; - eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_Q); + eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_QH); sc->sc_bulk_end = sqh; + uhci_add_loop(sc); } /* Remove bulk QH, called at splusb(). */ @@ -888,17 +1036,16 @@ SPLUSBCHECK; DPRINTFN(10, ("uhci_remove_bulk: sqh=%p\n", sqh)); - for (pqh = sc->sc_bulk_start; pqh->hlink != sqh; pqh = pqh->hlink) -#if defined(DIAGNOSTIC) || defined(UHCI_DEBUG) - if (LE(pqh->qh.qh_hlink) & UHCI_PTR_T) { - printf("uhci_remove_bulk: QH not found\n"); - return; - } -#else - ; -#endif - pqh->hlink = sqh->hlink; + uhci_rem_loop(sc); + /* See comment in uhci_remove_hs_ctrl() */ + if (!(sqh->qh.qh_elink & LE(UHCI_PTR_T))) { + sqh->qh.qh_elink = LE(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + pqh = uhci_find_prev_qh(sc->sc_bulk_start, sqh); + pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); if (sc->sc_bulk_end == sqh) sc->sc_bulk_end = pqh; } @@ -1437,10 +1584,7 @@ return (USBD_NOMEM); } p->link.std = lastp; - if (lastlink == UHCI_PTR_T) - p->td.td_link = LE(lastlink); - else - p->td.td_link = LE(lastlink|UHCI_PTR_VF); + p->td.td_link = LE(lastlink | UHCI_PTR_VF | UHCI_PTR_TD); lastp = p; lastlink = p->physaddr; p->td.td_status = LE(status); @@ -1549,7 +1693,7 @@ #endif sqh->elink = data; - sqh->qh.qh_elink = LE(data->physaddr); + sqh->qh.qh_elink = LE(data->physaddr | UHCI_PTR_TD); sqh->intr_info = ii; s = splusb(); @@ -1754,7 +1898,7 @@ for (i = 0; i < upipe->u.intr.npoll; i++) { sqh = upipe->u.intr.qhs[i]; sqh->elink = data; - sqh->qh.qh_elink = LE(data->physaddr); + sqh->qh.qh_elink = LE(data->physaddr | UHCI_PTR_TD); } splx(s); @@ -1876,7 +2020,7 @@ return (err); next = data; dataend->link.std = stat; - dataend->td.td_link = LE(stat->physaddr | UHCI_PTR_VF); + dataend->td.td_link = LE(stat->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); } else { next = stat; } @@ -1885,12 +2029,13 @@ memcpy(KERNADDR(&upipe->u.ctl.reqdma, 0), req, sizeof *req); setup->link.std = next; - setup->td.td_link = LE(next->physaddr | UHCI_PTR_VF); - setup->td.td_status = LE(UHCI_TD_SET_ERRCNT(3) | ls | UHCI_TD_ACTIVE); + setup->td.td_link = LE(next->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); + setup->td.td_status = LE(UHCI_TD_SET_ERRCNT(3) | ls | + UHCI_TD_ACTIVE); setup->td.td_token = LE(UHCI_TD_SETUP(sizeof *req, endpt, addr)); setup->td.td_buffer = LE(DMAADDR(&upipe->u.ctl.reqdma, 0)); - stat->link.std = 0; + stat->link.std = NULL; stat->td.td_link = LE(UHCI_PTR_T); stat->td.td_status = LE(UHCI_TD_SET_ERRCNT(3) | ls | UHCI_TD_ACTIVE | UHCI_TD_IOC); @@ -1921,11 +2066,14 @@ #endif sqh->elink = setup; - sqh->qh.qh_elink = LE(setup->physaddr); + sqh->qh.qh_elink = LE(setup->physaddr | UHCI_PTR_TD); sqh->intr_info = ii; s = splusb(); - uhci_add_ctrl(sc, sqh); + if (dev->lowspeed) + uhci_add_ls_ctrl(sc, sqh); + else + uhci_add_hs_ctrl(sc, sqh); LIST_INSERT_HEAD(&sc->sc_intrhead, ii, list); #ifdef UHCI_DEBUG if (uhcidebug > 12) { @@ -1936,7 +2084,7 @@ uhci_physaddr_t link; DPRINTF(("uhci_enter_ctl_q: follow from [0]\n")); for (std = sc->sc_vframes[0].htd, link = 0; - (link & UHCI_PTR_Q) == 0; + (link & UHCI_PTR_QH) == 0; std = std->link.std) { link = LE(std->td.td_link); uhci_dump_td(std); @@ -2215,7 +2363,7 @@ std->link = vstd->link; std->td.td_link = vstd->td.td_link; vstd->link.std = std; - vstd->td.td_link = LE(std->physaddr); + vstd->td.td_link = LE(std->physaddr | UHCI_PTR_TD); } uhci_unlock_frames(sc); @@ -2294,7 +2442,7 @@ for (i = 0; i < npoll; i++) { sqh = upipe->u.intr.qhs[i]; sqh->elink = data; - sqh->qh.qh_elink = LE(data->physaddr); + sqh->qh.qh_elink = LE(data->physaddr | UHCI_PTR_TD); } } else { ii->stdstart = 0; /* mark as inactive */ @@ -2316,7 +2464,10 @@ LIST_REMOVE(ii, list); /* remove from active list */ - uhci_remove_ctrl(sc, upipe->u.ctl.sqh); + if (upipe->pipe.device->lowspeed) + uhci_remove_ls_ctrl(sc, upipe->u.ctl.sqh); + else + uhci_remove_hs_ctrl(sc, upipe->u.ctl.sqh); if (upipe->u.ctl.length != 0) uhci_free_std_chain(sc, ii->stdstart->link.std, ii->stdend); @@ -2353,7 +2504,7 @@ sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; - eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_Q); + eqh->qh.qh_hlink = LE(sqh->physaddr | UHCI_PTR_QH); vf->eqh = sqh; vf->bandwidth++; } @@ -2367,6 +2518,12 @@ DPRINTFN(4, ("uhci_remove_intr: n=%d sqh=%p\n", n, sqh)); + /* See comment in uhci_remove_ctrl() */ + if (!(sqh->qh.qh_elink & LE(UHCI_PTR_T))) { + sqh->qh.qh_elink = LE(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + for (pqh = vf->hqh; pqh->hlink != sqh; pqh = pqh->hlink) #if defined(DIAGNOSTIC) || defined(UHCI_DEBUG) if (LE(pqh->qh.qh_hlink) & UHCI_PTR_T) { @@ -2378,6 +2535,7 @@ #endif pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); if (vf->eqh == sqh) vf->eqh = pqh; vf->bandwidth--; @@ -2914,7 +3072,7 @@ delay(100); x = UREAD2(sc, port); UWRITE2(sc, port, x | UHCI_PORTSC_PE); - delay(100); + usb_delay_ms(&sc->sc_bus, 10); /* XXX */ DPRINTFN(3,("uhci port %d reset, status = 0x%04x\n", index, UREAD2(sc, port))); sc->sc_isreset = 1; Index: uhcireg.h =================================================================== RCS file: /home/iedowse/CVS/src/sys/dev/usb/uhcireg.h,v retrieving revision 1.14.2.1 diff -u -r1.14.2.1 uhcireg.h --- uhcireg.h 2 Jul 2000 11:43:59 -0000 1.14.2.1 +++ uhcireg.h 7 Apr 2002 20:15:09 -0000 @@ -1,4 +1,4 @@ -/* $NetBSD: uhcireg.h,v 1.9 1999/11/20 00:57:09 augustss Exp $ */ +/* $NetBSD: usb/uhcireg.h,v 1.13 2000/08/13 18:20:15 augustss Exp $ */ /* $FreeBSD$ */ /* @@ -114,12 +114,19 @@ typedef u_int32_t uhci_physaddr_t; #define UHCI_PTR_T 0x00000001 -#define UHCI_PTR_Q 0x00000002 +#define UHCI_PTR_TD 0x00000000 +#define UHCI_PTR_QH 0x00000002 #define UHCI_PTR_VF 0x00000004 +/* + * Wait this long after a QH has been removed. This gives that HC a + * chance to stop looking at it before it's recycled. + */ +#define UHCI_QH_REMOVE_DELAY 5 + /* - * The Queue Heads and Transfer Descriptors and accessed - * by both the CPU and the USB controller which runs + * The Queue Heads and Transfer Descriptors are accessed + * by both the CPU and the USB controller which run * concurrently. This means that they have to be accessed * with great care. As long as the data structures are * not linked into the controller's frame list they cannot Index: uhcivar.h =================================================================== RCS file: /home/iedowse/CVS/src/sys/dev/usb/uhcivar.h,v retrieving revision 1.16.2.5 diff -u -r1.16.2.5 uhcivar.h --- uhcivar.h 31 Oct 2000 23:23:29 -0000 1.16.2.5 +++ uhcivar.h 7 Apr 2002 20:14:16 -0000 @@ -141,10 +141,14 @@ usb_dma_t sc_dma; struct uhci_vframe sc_vframes[UHCI_VFRAMELIST_COUNT]; - uhci_soft_qh_t *sc_ctl_start; /* dummy QH for control */ - uhci_soft_qh_t *sc_ctl_end; /* last control QH */ + uhci_soft_qh_t *sc_lctl_start; /* dummy QH for low speed control */ + uhci_soft_qh_t *sc_lctl_end; /* last control QH */ + uhci_soft_qh_t *sc_hctl_start; /* dummy QH for high speed control */ + uhci_soft_qh_t *sc_hctl_end; /* last control QH */ uhci_soft_qh_t *sc_bulk_start; /* dummy QH for bulk */ uhci_soft_qh_t *sc_bulk_end; /* last bulk transfer */ + uhci_soft_qh_t *sc_last_qh; /* dummy QH at the end */ + u_int32_t sc_loops; /* number of QHs that wants looping */ uhci_soft_td_t *sc_freetds; /* TD free list */ uhci_soft_qh_t *sc_freeqhs; /* QH free list */