\ *****************************************************************************
\ * Copyright (c) 2004, 2008 IBM Corporation
\ * All rights reserved.
\ * This program and the accompanying materials
\ * are made available under the terms of the BSD License
\ * which accompanies this distribution, and is available at
\ * http://www.opensource.org/licenses/bsd-license.php
\ *
\ * Contributors:
\ *     IBM Corporation - initial implementation
\ ****************************************************************************/

0 value NEXT-TD

0 VALUE num-tds
0 VALUE td-retire-count
0 VALUE saved-tail
0 VALUE poll-timer
VARIABLE controlxfer-cmd

\  Allocate an ED and populate it

: (ed-prepare) ( dir addr dlen setup-packet MPS ep-fun --
                 FALSE | dir addr dlen ed-ptr setup-ptr )
   allocate-ed ?dup 0= IF
      s" allocate-ed failed!" usb-debug-print
      4drop 2drop FALSE EXIT  ( FALSE )
   THEN                   ( dir addr dlen setup-packet MPS ep-fun ed-ptr )
   TO temp1               ( dir addr dlen setup-packet MPS ep-fun )
   temp1 zero-out-an-ed-except-link ( dir addr dlen setup-packet MPS ep-fun )
   temp1 ed>eattr l@-le or temp1 ed>eattr l!-le ( dir addr dlen setup-ptr MPS )
   dup TO temp2 10 lshift temp1 ed>eattr l@-le or temp1 ed>eattr l!-le
                          ( dir addr dlen setup-packet-address )
   temp1 swap TRUE            ( dir addr dlen ed-ptr setup-ptr TRUE )
;


\ Allocate TD list


: (td-prepare) ( dir addr dlen ed-ptr setup-ptr --
                 dir FALSE | dir addr dlen ed-ptr setup-ptr td-head td-tail )
   2 pick         ( dir addr dlen ed-ptr setup-ptr dlen )
   temp2          ( dir addr dlen ed-ptr setup-ptr dlen MPS )
   /mod           ( dir addr dlen ed-ptr setup-ptr rem quo )
   swap 0<>   IF  ( dir addr dlen ed-ptr setup-ptr quo )
      1+
   THEN
   2+
   dup TO num-tds                ( dir addr dlen ed-ptr setup-ptr quo+2 )
   allocate-td-list dup 0=  IF   ( dir addr dlen ed-ptr setup-ptr quo+2 )
      2drop                      ( dir addr dlen ed-ptr setup-ptr )
      drop                       ( dir addr dlen ed-ptr )
      free-ed                    ( dir addr dlen )
      2drop                      ( dir )
      FALSE                      ( dir FALSE )
      EXIT
   THEN TRUE
;


\ Fill in the ED structure completely.


: (td-ready)  ( ed-ptr setup-ptr td-head td-tail -- ed-ptr setup-ptr )
   swap virt2phys swap virt2phys   \ Convert td-head and td-tail to physical
   3 pick                      ( ed-ptr s-ptr td-head' td-tail' ed-ptr )
   tuck                        ( ed-ptr s-ptr td-head' ed-ptr td-tail' ed-ptr )
   ed>tdqtp l!-le              ( ed-ptr s-ptr td-head' ed-ptr )
   ed>tdqhp l!-le              ( ed-ptr s-ptr )
   over ed>ned 0 swap l!-le    ( ed-ptr s-ptr )
;


\ Initialize the HEAD and TAIL TDs for SETUP and
\ STATUS phase respectively.


: (td-setup-status) ( dir addr dlen ed-ptr setup-ptr -- dir addr dlen ed-ptr )
   over ed>tdqhp l@-le phys2virt   ( dir addr dlen ed-ptr setup-ptr td-head )
   dup zero-out-a-td-except-link   ( dir addr dlen ed-ptr setup-ptr td-head )
   dup td>tattr DATA0-TOGGLE CC-FRESH-TD or swap l!-le
                                   ( dir addr dlen ed-ptr setup-ptr td-head )
   2dup swap virt2phys swap td>cbptr l!-le 
                                   ( dir addr dlen ed-ptr setup-ptr td-head )
   2dup td>bfrend swap STD-REQUEST-SETUP-SIZE 1- + virt2phys swap l!-le
                                   ( dir addr dlen ed-ptr setup-ptr td-head )
   2drop                           ( dir addr dlen ed-ptr )
;

\ Initialize the TD TAIL pointer.


: (td-tailpointer) ( dir addr dlen ed-ptr -- dir addr dlen ed-ptr )
   dup ed>tdqtp l@-le phys2virt    ( dir addr dlen ed-ptr td-tail )
   dup zero-out-a-td-except-link   ( dir addr dlen ed-ptr td-tail )
   dup td>tattr dup l@-le DATA1-TOGGLE CC-FRESH-TD or or swap l!-le
                                   ( dir addr dlen ed-ptr td-tail )
   4 pick 0=                       ( dir addr dlen ed-ptr td-tail flag )
   3 pick 0<>                      ( dir addr dlen ed-ptr td-tail flag flag )
   and   IF                        ( dir addr dlen ed-ptr td-tail )
      dup td>tattr dup l@-le TD-DP-OUT or swap l!-le
                                   ( dir addr dlen ed-ptr td-tail )
   ELSE
      dup td>tattr dup l@-le TD-DP-IN or swap l!-le
                                  ( dir addr dlen ed-ptr td-tail )
   THEN
   drop                           ( dir addr dlen ed-ptr )
;

\  Initialize the Data TDs.


: (td-data) ( dir addr dlen ed-ptr --  ed-ptr )
   -rot             ( dir ed-ptr addr dlen )
   dup 0<>  IF      ( dir ed-ptr addr dlen )
      >r >r >r TO temp1 r> r> r> temp1 ( ed-ptr addr dlen dir )
      3 pick                        ( ed-ptr addr dlen dir ed-ptr )
      ed>tdqhp l@-le phys2virt      ( ed-ptr addr dlen dir tdqhp )
      td>ntd l@-le phys2virt        ( ed-ptr addr dlen dir td-datahead )
      4 pick                        ( ed-ptr addr dlen dir td-datahead ed-ptr )
      td>tattr l@-le 10 rshift      ( ed-ptr addr dlen dir td-head-data MPS )
      swap                          ( ed-ptr addr dlen dir MPS td-head-data )
      >r >r >r >r >r 1 r> r> r> r> r>
                                   ( ed-ptr 1 addr dlen dir MPS td-head-data )
      >r >r 0=  IF                 ( ed-ptr 1 addr dlen dir )
         OHCI-DP-IN                ( ed-ptr 1 addr dlen dir  OHCI-DP-IN )
      ELSE
         OHCI-DP-OUT               ( ed-ptr 1 addr dlen dir  OHCI-DP-OUT )
      THEN
      r> r>               ( ed-ptr 1 addr dlen dir  OHCI-DP- MPS td-head-data )
      fill-TD-list
   ELSE
      2drop nip           ( ed-ptr )
   THEN
;


\ Program the HC with the ed-ptr value and wait for status to
\ from the HC.
\ Free the ED and TDs associated with it.
\ PENDING: Above said.

10 CONSTANT max-retire-td

: (transfer-wait-for-doneq)  ( ed-ptr -- TRUE | FALSE )
   dup virt2phys                     ( ed-ptr ed-ptr-dma )
   hcctrhead rl!-le                  ( ed-ptr )
   HC-enable-control-list-processing ( ed-ptr )
   0 TO td-retire-count              ( ed-ptr )
   0 TO poll-timer                   ( ed-ptr )
   BEGIN
      td-retire-count num-tds <>     ( ed-ptr TRUE | FALSE )
      poll-timer max-retire-td < and       ( ed-ptr TRUE | FALSE )
   WHILE
      (HC-CHECK-WDH)                                      ( ed-ptr updated? )
      IF
         hchccadneq l@-le phys2virt
         find-td-list-tail-and-size nip                   ( ed-ptr n )
         td-retire-count + TO td-retire-count             ( ed-ptr )
         hchccadneq l@-le phys2virt dup     ( ed-ptr done-td done-td )
         (td-list-status)                   ( ed-ptr done-td failed-td CCcode )
         IF
            \ keep condition code of TD on return stack
            dup >r
            s" (transfer-wait-for-doneq: USB device communication error."
            usb-debug-print                 ( ed-ptr done-td failed-td CCcode R: CCcode )
            dup 4 = swap dup 5 = rot or     ( ed-ptr done-td failed-td CCcode R: CCcode )
            IF
                max-retire-td TO poll-timer ( ed-ptr done-td failed-td CCcode R: CCcode )
            THEN
            ( ed-ptr done-td failed-td CCcode R: CCcode )
            usb-debug-flag
            IF
               s" CC code ->" type . cr
               s" Failing TD contents:" type cr display-td
            ELSE
               2drop
            THEN                           ( ed-ptr done-td R: CCcode )
            controlxfer-cmd @ GET-MAX-LUN = r> 4 = and
            IF
               s" (transfer-wait-for-doneq): GET-MAX-LUN ControlXfer STALLed"
               usb-debug-print
               \ Condition Code = 4 means that the device does not support multiple LUNS
               \ see USB Massbulk 1.0 Standard
            ELSE
               drop
               5030 error" (USB) Device communication error."
               ABORT
               \ FIXME: ABORTing here might leave the HC in an unusable state.
               \        We should maybe rather ABORT at the end of this Forth
               \        word, when clean-up has been done (or not ABORT at all)
            THEN
         THEN                              ( ed-ptr done-td )
         (free-td-list)                    ( ed-ptr )
         0 hchccadneq l!-le                ( ed-ptr )
         (HC-ACK-WDH) \ TDs were written to done queue. ACK the HC.
      THEN
      poll-timer 1+ TO poll-timer
      4 ms              \ longer  1 ms
   REPEAT                                  ( ed-ptr )
   disable-control-list-processing         ( ed-ptr )
   td-retire-count num-tds <>              ( ed-ptr )
   IF
      dup display-descriptors              ( ed-ptr )
      s" maximum of retire " usb-debug-print
   THEN
   free-ed
   td-retire-count num-tds <>
   IF
      FALSE                                ( FALSE )
   ELSE
      TRUE                                 ( TRUE )
   THEN
;


\ COLON DEFINITION: controlxfer
\                     INTERFACE FUNCTION

\ ARGUMENTS:
\ (from the bottom of stack)
\ 1. dir -- This is the direction of data transfer associated with
\           the DATA STAGE of the control xfer.
\           If there is no data transfer (argument dlen is zero)
\           then this argument does not matter, nonethless it has
\           to be passed.
\           A "0" represents an IN and "1" represents an "OUT".
\ 2. addr -- If there is a data stage associated with the transfer,
\            then this argument holds the address of the data buffer
\ 3. dlen -- This arg holds the length of the data buffer discussed
\            in previous step (addr)
\ 4. setup-packet -- This holds the pointer to the setup packet that
\                    will be transmitted during the SETUP stage of
\                    the control xfer. The function assumes the length
\                    of the status packet to be 8 bytes.
\ 5. MPS -- This is the MAX PACKET SIZE of the endpoint.
\ 6. ep-fun -- This is the 11-bit value that holds the Endpoint and
\              the function address. bit 7 to bit 10 holds the Endpoint
\              address. Bits 0 to Bit 6 holds the Function Address.
\              The BIT numbering followed : The left most bit is referred
\              as bit 0. (not the one followed by PPC)
\              Bit 13 must be set for low-speed devices.

\ RETURN VALUE:
\ Returns TRUE | FALSE depending on the success of the transaction.

\ ASSUMPTIONS:
\ 1. Function assumes that the setup packet is 8-bytes in length.
\    If in future, IF we need to add a new argument, we need to change
\    the function in lot of places.

\ RISKS:
\ 1. If for some reason, the USB controller does not retire all the TDs
\    then the status checking part of this "word" can spin forever.


: controlxfer ( dir addr dlen setup-packet MPS ep-fun -- TRUE | FALSE )
   2 pick @ controlxfer-cmd !
   (ed-prepare)       ( FALSE | dir addr dlen ed-ptr setup-ptr  )
   invert IF FALSE EXIT THEN
   (td-prepare)       ( pt ed-type toggle buffer length mps head )
   invert IF FALSE EXIT THEN
   (td-ready)         ( dir addr dlen ed-ptr setup-ptr )
   (td-setup-status)  ( dir addr dlen ed-ptr )
   (td-tailpointer)   ( dir addr dlen ed-ptr )
   (td-data)          ( ed-ptr )

   \ FIXME:
   \ Clear the TAIL pointer in ED. This has got sthg to do with how
   \ the HC finds an empty queue condition. Refer spec.

   dup ed>tdqtp l@-le phys2virt TO saved-tail    ( ed-ptr )
   dup ed>tdqtp 0 swap l!-le                     ( ed-ptr )
   (transfer-wait-for-doneq)                     ( TRUE | FALSE )
;

0201000000000000 CONSTANT CLEARHALTFEATURE
0 VALUE endpt-num
0 VALUE usb-addr-contr-req
: control-std-clear-feature ( endpoint-nr usb-addr -- TRUE|FALSE )
   TO usb-addr-contr-req                        \ usb address
   TO endpt-num                                 \ endpoint number
   CLEARHALTFEATURE setup-packet !
   endpt-num setup-packet 4 + c!                \ endpoint number
   0 0 0 setup-packet DEFAULT-CONTROL-MPS usb-addr-contr-req controlxfer
   ( TRUE|FALSE )
;

\ It resets the usb bulk-device
21FF000000000000 CONSTANT BULK-RESET
: control-std-bulk-reset ( usb-addr -- TRUE|FALSE )
  TO usb-addr-contr-req
  BULK-RESET setup-packet !
  0 0 0 setup-packet DEFAULT-CONTROL-MPS usb-addr-contr-req controlxfer
  ( TRUE|FALSE )
;

: bulk-reset-recovery-procedure ( bulk-out-endp bulk-in-endp usb-addr -- )
    >r                                          ( bulk-out-endp bulk-in-endp R: usb-addr )
    \ perform a bulk reset
    r@ control-std-bulk-reset
    IF s" bulk reset OK"
    ELSE s" bulk reset failed"
    THEN usb-debug-print

    \ clear bulk-in endpoint                    ( bulk-out-endp bulk-in-endp R: usb-addr )
    80 or r@ control-std-clear-feature
    IF s" control-std-clear IN endpoint OK"
    ELSE s" control-std-clear-IN endpoint failed"
    THEN usb-debug-print

    \ clear bulk-out endpoint                   ( bulk-out-endp R: usb-addr )
    r@ control-std-clear-feature
    IF s" control-std-clear OUT endpoint OK"
    ELSE s" control-std-clear-OUT endpoint failed"
    THEN usb-debug-print
    r> drop
;

0 VALUE saved-rw-ed
0 VALUE num-rw-tds
0 VALUE num-rw-retired-tds
0 VALUE saved-rw-start-toggle
0 VALUE saved-list-type

\ Allocate an ED and populate what you can.


: (ed-prepare-rw)
   ( pt ed-type toggle buffer length mps address ed-ptr --
      FALSE | pt ed-type toggle buffer length mps TRUE )
   allocate-ed dup 0=  IF
   ( pt ed-type toggle buffer length mps address ed-ptr )
      drop 2drop 2drop 2drop drop
      saved-rw-start-toggle FALSE EXIT  ( toggle FALSE )
   THEN
   TO saved-rw-ed             ( pt ed-type toggle buffer length mps address )
   saved-rw-ed zero-out-an-ed-except-link
                              ( pt ed-type toggle buffer length mps address )
   saved-rw-ed ed>eattr l!-le   ( pt ed-type toggle buffer length mps )
   dup 10 lshift saved-rw-ed ed>eattr l@-le or
                              ( pt ed-type toggle buffer length mps mps~ )
   saved-rw-ed ed>eattr l!-le TRUE  ( pt ed-type toggle buffer length mps TRUE )
;


\  Allocate TD List


: (td-prepare-rw)
   ( pt ed-type toggle buffer length mps --
     FALSE | pt ed-type toggle buffer length mps head TRUE )
   2dup              ( pt ed-type toggle buffer length mps  length mps )
   /mod              ( pt ed-type toggle buffer length mps num-tds rem )
   swap 0<> IF       ( pt ed-type toggle buffer length mps num-tds )
      1+             ( pt ed-type toggle buffer length mps num-tds+1 )
   THEN
   dup TO num-rw-tds ( pt ed-type toggle buffer length mps num-tds )
   allocate-td-list  ( pt ed-type toggle buffer length mps head tail )
   dup 0=  IF
      2drop 2drop 2drop 2drop
      saved-rw-ed free-ed
      ." rw-endpoint: TD list allocation failed" cr
      saved-rw-start-toggle FALSE   ( FALSE )
      EXIT
   THEN
   drop  TRUE         ( pt ed-type toggle buffer length mps head TRUE )
;


\ Populate TD list with data buffers and toggle info.

: (td-data-rw)
   ( pt ed-type toggle buffer length mps head -- FALSE | pt et head TRUE )
   6 pick                    ( pt ed-type toggle buffer length mps head  pt )
   FALSE TO case-failed  CASE
      0   OF OHCI-DP-IN    ENDOF
      1   OF OHCI-DP-OUT   ENDOF
      2   OF OHCI-DP-SETUP ENDOF
      dup OF TRUE TO case-failed
      ." rw-endpoint: Invalid Packet Type!" cr
      ENDOF
   ENDCASE                   ( pt ed-type toggle buffer length mps head dp )
   case-failed  IF
      saved-rw-ed free-ed    ( pt ed-type toggle buffer length mps head dp )
      drop (free-td-list)         ( pt ed-type toggle buffer length mps head )
      2drop 2drop 2drop
      saved-rw-start-toggle FALSE ( FALSE )
      EXIT                        ( FALSE )
   THEN
   -rot                      ( pt ed-type toggle buffer length dp mps head )
   dup >r                      ( pt ed-type toggle buffer length dp mps head )
   fill-TD-list r>  TRUE      ( pt et head TRUE )
;


\ Enqueue the ED with the appropriate list

: (ed-ready-rw)  ( pt et  -- - | toggle FALSE )
   nip           ( et )
   FALSE TO case-failed  CASE
      0   OF \ Control List. Queue the ED to control list
      0 TO saved-list-type
      saved-rw-ed virt2phys hcctrhead rl!-le
      HC-enable-control-list-processing
      ENDOF
      1   OF \ Bulk List. Queue the ED to bulk list
      1 TO saved-list-type
      saved-rw-ed virt2phys hcbulkhead rl!-le
      HC-enable-bulk-list-processing
      ENDOF
      2   OF \ Interrupt List.
      2 TO saved-list-type
      saved-rw-ed virt2phys  hchccareg rl@-le phys2virt  rl!-le
      HC-enable-interrupt-list-processing
      ENDOF
      dup OF
      saved-rw-ed ed>tdqhp l@-le phys2virt (free-td-list)
      saved-rw-ed free-ed
      TRUE TO case-failed
      ENDOF
   ENDCASE
   case-failed  IF
      saved-rw-start-toggle FALSE ( toggle FALSE )
      EXIT
   THEN
   TRUE                           ( TRUE )
;


\  Wait for TDs to return to the Done Q.

: (wait-td-retire) ( -- )
   0 TO num-rw-retired-tds
   FALSE TO while-failed
   BEGIN
      num-rw-retired-tds num-rw-tds <           ( TRUE | FALSE )
      while-failed FALSE =  and                 ( TRUE | FALSE )
      WHILE
      d# 5000 (wait-for-done-q)                  ( TD-list TRUE|FALSE )
      IF
         dup find-td-list-tail-and-size nip         ( td-list size )
         num-rw-retired-tds + TO num-rw-retired-tds ( td-list )
         dup (td-list-status)                   ( td-list failed-TD CC )
         IF
            dup 4 =
            IF
               saved-list-type
               CASE
                  0 OF
                     0 0 control-std-clear-feature
                     s" clear feature " usb-debug-print
                  ENDOF
                  1 OF                             \ clean bulk stalled
                     s" clear bulk when stalled " usb-debug-print
                     disable-bulk-list-processing   \ disable procesing
                     saved-rw-ed ed>eattr l@-le dup \ extract
                     780 and 7 rshift 80 or         \ endpoint and
                     swap 7f and                    \ usb addr
                     control-std-clear-feature
                  ENDOF
                  2 OF
                     0 saved-rw-ed ed>eattr l@-le
                     control-std-clear-feature
                  ENDOF
                  dup OF
                     s" unknown status " usb-debug-print
                  ENDOF
               ENDCASE
            ELSE                             ( td-list failed-TD CC )
               ."  TD failed  " 5b emit .s 5d emit cr
               5040 error" (USB) device transaction error (wait-td-retire)."
               ABORT
            THEN
            2drop drop
            TRUE TO while-failed                \ transaction failed
            NEXT-TD 0<>                         \ clean the TD if we
            IF
               NEXT-TD (free-td-list)           \ had a stalled
            THEN
         THEN
         (free-td-list)
      ELSE
         drop                                   \ drop td-list pointer
         scan-time? IF 2e emit THEN             \ show proceeding dots
         TRUE TO while-failed
         s" time out wait for done" usb-debug-print
         20 ms     \ wait for bad device
      THEN
   REPEAT
;


\ Process retired TDs


: (process-retired-td)   ( -- TRUE | FALSE )
   saved-list-type  CASE
      0 OF disable-control-list-processing ENDOF
      1 OF disable-bulk-list-processing ENDOF
      2 OF disable-interrupt-list-processing ENDOF
   ENDCASE
   saved-rw-ed ed>tdqhp l@-le 2 and 0<> IF
      1
      s" retired 1" usb-debug-print
   ELSE
      0
      s" retired 0" usb-debug-print
   THEN
   \ s" retired " usb-debug-print-val
   WHILE-failed   IF
      FALSE           ( FALSE )
   ELSE
      TRUE            ( TRUE )
   THEN
   saved-rw-ed free-ed
;


\ (do-rw-endpoint): T1 12 80 0 0chis method is an privately visible function
\ 		    to be used by the "rw-endpoint" the required
\ 		    number of times based on the actual length
\ 		    to be transferred

\ Arguments:
\ pt: Packet type
\     0 -> IN
\     1 -> OUT
\     2 -> SETUP
\ et: Endpoint type
\     0 -> Control
\     1 -> Bulk
\ toggle: Starting toggle for this transfer
\ buffer length: Data buffer associated with the transfer limited
\     accordingly by the "rw-endpoint" method to the
\     value of max packet size
\ mps: Max Packet Size.
\ address: Address of endpoint. 11-bit address. The lower 7-bits represent
\          the USB addres and the upper 4-bits represent the Endpoint
\          number.

: (do-rw-endpoint)
   ( pt ed-type toggle buffer length mps address -- toggle TRUE|toggle FALSE )
   4 pick              ( pt ed-type toggle buffer length mps address toggle )
   TO saved-rw-start-toggle ( pt ed-type toggle buffer length mps address )
   (ed-prepare-rw)     ( FALSE | pt ed-type toggle buffer length mps TRUE )
   invert IF FALSE EXIT THEN
   (td-prepare-rw)     ( FALSE | pt ed-type toggle buffer length mps head TRUE )
   invert IF FALSE EXIT THEN
   (td-data-rw)        ( FALSE | pt et head TRUE )
   invert IF FALSE EXIT THEN
   virt2phys saved-rw-ed ed>tdqhp l!-le  ( pt et )
   saved-rw-ed ed>tdqhp l@-le phys2virt
   td>ntd l@-le phys2virt  TO NEXT-TD    \ save for a stalled
   (ed-ready-rw)
   invert IF FALSE EXIT THEN
   (wait-td-retire)
   (process-retired-td)         ( TRUE | FALSE )
;


\ rw-endpoint: The method is an externally visible method to be exported
\	       to the child nodes. It uses the internal method
\	       "(do-rw-endpoint)", the required number of times based on the
\	       actual length of transfer, so that the limitation of MAX-TDS
\	       do not hinder the transfer.

\ Arguments:
\ pt: Packet type
\     0 -> IN
\     1 -> OUT
\     2 -> SETUP
\ et: Endpoint type
\     0 -> Control
\     1 -> Bulk
\ toggle: Starting toggle for this transfer
\ buffer length: Data buffer associated with the transfer
\ mps: Max Packet Size.
\ address: Address of endpoint. 11-bit address. The lower 7-bits represent
\          the USB addres and the upper 4-bits represent the Endpoint
\          number.


0 VALUE transfer-len
0 VALUE mps-current
0 VALUE addr-current
0 VALUE usb-addr
0 VALUE toggle-current
0 VALUE type-current
0 VALUE pt-current
0 VALUE read-status
0 VALUE counter
0 VALUE residue


: rw-endpoint
   ( pt ed-type toggle buffer length mps address -- )
   ( toggle TRUE |toggle FALSE )

   \ a single transfer descriptor can point to a buffer of
   \ 8192 bytes a block on the CDROM has 2048 bytes
   \ but a single transfer is constrained by the MPS

   2 pick TO transfer-len  ( pt ed-type toggle buffer length mps address )
   1 pick TO mps-current   ( pt ed-type toggle buffer length mps address )
   TRUE TO read-status     ( pt ed-type toggle buffer length mps address )
   transfer-len mps-current num-free-tds * <=  IF
      (do-rw-endpoint)     ( toggle TRUE | toggle FALSE )
      TO read-status       ( toggle )
      TO toggle-current
   ELSE
      TO usb-addr          ( pt ed-type toggle buffer length mps )
      2drop                ( pt ed-type toggle buffer )
      TO addr-current      ( pt ed-type toggle )
      TO toggle-current    ( pt ed-type )
      TO type-current      ( pt )
      TO pt-current
      transfer-len mps-current num-free-tds * /mod  ( residue count )
                           ( remainder=residue quotient=count )
      TO counter           ( residue )
      TO residue
      mps-current num-free-tds * TO transfer-len   BEGIN
         counter 0 >       ( TRUE | FALSE )
         read-status TRUE = and   ( TRUE | FALSE )
      WHILE
         pt-current type-current toggle-current ( pt ed-type toggle )
         addr-current transfer-len  ( pt ed-type toggle buffer length )
         mps-current                ( pt ed-type toggle buffer length mps )
         usb-addr (do-rw-endpoint)  ( toggle TRUE | toggle FALSE )
         TO read-status             ( toggle )
         TO toggle-current
         addr-current transfer-len + TO addr-current
         counter 1- TO counter
      REPEAT
      residue 0<>                    ( TRUE |FALSE )
      read-status TRUE = and IF
         residue TO transfer-len
         pt-current type-current toggle-current ( pt ed-type toggle )
         addr-current transfer-len   ( pt ed-type toggle buffer length )
         mps-current                 ( pt ed-type toggle buffer length mps )
         usb-addr (do-rw-endpoint)   ( toggle TRUE | toggle FALSE )
         TO read-status
         TO toggle-current
      THEN
   THEN
   read-status invert  IF
   THEN
   toggle-current                    ( toggle )
   read-status                       ( TRUE | FALSE )
;
