| \ ***************************************************************************** |
| \ * Copyright (c) 2004, 2011 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 |
| \ ****************************************************************************/ |
| |
| |
| \ Open Firmware Properties |
| |
| s" usb" device-type |
| 1 encode-int s" #address-cells" property |
| 0 encode-int s" #size-cells" property |
| |
| |
| \ converts physical address to text unit string |
| |
| : encode-unit ( port -- unit-str unit-len ) 1 hex-encode-unit ; |
| |
| |
| \ Converts text unit string to phyical address |
| |
| : decode-unit ( addr len -- port ) 1 hex-decode-unit ; |
| |
| |
| \ Data Structure Definitions |
| \ OHCI Task Descriptor Structure. |
| |
| STRUCT |
| /l field td>tattr |
| /l field td>cbptr |
| /l field td>ntd |
| /l field td>bfrend |
| CONSTANT /tdlen |
| |
| |
| \ OHCI Endpoint Descriptor Structure. |
| |
| STRUCT |
| /l field ed>eattr |
| /l field ed>tdqtp |
| /l field ed>tdqhp |
| /l field ed>ned |
| CONSTANT /edlen |
| |
| |
| \ HCCA Done queue location packaged as a structure for ease of use. |
| |
| STRUCT |
| /l field hc>hcattr |
| /l field hc>hcdone |
| CONSTANT /hclen |
| |
| |
| \ OHCI Memory Mapped Registers |
| |
| : get-base-address ( -- baseaddr ) |
| s" assigned-addresses" get-node get-property |
| ABORT" Could not get OHCI base address" |
| decode-int drop ( addr len ) |
| decode-64 nip nip ( n ) |
| translate-my-address |
| ; |
| |
| get-base-address CONSTANT baseaddrs |
| |
| baseaddrs CONSTANT HcRevision |
| baseaddrs 4 + CONSTANT hccontrol |
| baseaddrs 8 + CONSTANT hccomstat |
| baseaddrs 0c + CONSTANT hcintstat |
| baseaddrs 14 + CONSTANT hcintdsbl |
| baseaddrs 18 + CONSTANT hchccareg |
| baseaddrs 20 + CONSTANT hcctrhead |
| baseaddrs 24 + CONSTANT hccurcont |
| baseaddrs 28 + CONSTANT hcbulkhead |
| baseaddrs 2c + CONSTANT hccurbulk |
| baseaddrs 30 + CONSTANT hcdnehead |
| baseaddrs 34 + CONSTANT hcintrval |
| baseaddrs 40 + CONSTANT HcPeriodicStart |
| baseaddrs 48 + CONSTANT hcrhdescA |
| baseaddrs 4c + CONSTANT hcrhdescB |
| baseaddrs 50 + CONSTANT HcRhStatus |
| baseaddrs 54 + CONSTANT hcrhpstat |
| baseaddrs 58 + CONSTANT hcrhpstat2 |
| baseaddrs 5c + CONSTANT hcrhpstat3 |
| |
| usb-debug-flag IF |
| 0 config-l@ ." - VENDOR: " 8 .r cr |
| 40 config-l@ ." - PMC : " 8 .r |
| 44 config-l@ ." PMCSR : " 8 .r cr |
| E0 config-l@ ." - EXT1 : " 8 .r |
| E4 config-l@ ." EXT2 : " 8 .r cr |
| THEN |
| |
| \ Constants for INTSTAT register |
| |
| 2 CONSTANT WDH |
| |
| \ Constants for RH Port Status Register |
| |
| 1 CONSTANT RHP-CCS \ Current Connect Status |
| 2 CONSTANT RHP-PES \ Port Enable Status |
| 10 CONSTANT RHP-PRS \ Port Reset Status |
| 100 CONSTANT RHP-PPS \ Port Power Status |
| 10000 CONSTANT RHP-CSC \ Connect Status Changed |
| 100000 CONSTANT RHP-PRSC \ Port Reset Status Changed |
| |
| |
| \ Constants for OHCI |
| |
| 0 CONSTANT OHCI-DP-SETUP |
| 1 CONSTANT OHCI-DP-OUT |
| 2 CONSTANT OHCI-DP-IN |
| 3 CONSTANT OHCI-DP-INVALID |
| |
| \ 8-byte Standard Device Requests + Hub class specific requests. |
| |
| 8006000100001200 CONSTANT get-ddescp |
| 8006000200000900 CONSTANT get-cdescp |
| 8006000400000900 CONSTANT get-idescp |
| 8006000500000700 CONSTANT get-edescp |
| A006000000001000 CONSTANT get-hdescp |
| 0009010000000000 CONSTANT set-cdescp |
| 2303010004000000 CONSTANT hpenable-set |
| 2303040001000000 CONSTANT hp1rst-set |
| 2303040002000000 CONSTANT hp2rst-set |
| 2303040003000000 CONSTANT hp3rst-set |
| 2303040004000000 CONSTANT hp4rst-set |
| 2303080001000000 CONSTANT hp1pwr-set |
| 2303080002000000 CONSTANT hp2pwr-set |
| 2303080003000000 CONSTANT hp3pwr-set |
| 2303080004000000 CONSTANT hp4pwr-set |
| A003000000000400 CONSTANT hstatus-get |
| A300000001000400 CONSTANT hp1sta-get |
| A300000002000400 CONSTANT hp2sta-get |
| A300000003000400 CONSTANT hp3sta-get |
| A300000004000400 CONSTANT hp4sta-get |
| 8008000000000100 CONSTANT get-config |
| |
| A1FE000000000100 CONSTANT GET-MAX-LUN |
| |
| 2 18 lshift CONSTANT DATA0-TOGGLE |
| 3 18 lshift CONSTANT DATA1-TOGGLE |
| 0f 1c lshift CONSTANT CC-FRESH-TD |
| 8 CONSTANT STD-REQUEST-SETUP-SIZE |
| 0 13 lshift CONSTANT TD-DP-SETUP |
| 1 13 lshift CONSTANT TD-DP-OUT |
| 2 13 lshift CONSTANT TD-DP-IN |
| |
| 400001 CONSTANT ed-cntatr |
| 400002 CONSTANT ed-cntatr1 |
| 80081 CONSTANT ed-hubatr |
| 80000 CONSTANT ed-defatr |
| 0f0e40000 CONSTANT td-attr |
| 00 VALUE ptr |
| |
| |
| 0 VALUE instance-count |
| |
| \ TD Management constants and Data structures. |
| |
| |
| 200 CONSTANT MAX-TDS |
| 0 VALUE td-freelist-head |
| 0 VALUE td-freelist-tail |
| 0 VALUE num-free-tds |
| 0 VALUE max-rh-ports |
| 0 VALUE current-stat |
| |
| VARIABLE td-list-region |
| VARIABLE td-list-region-dma |
| |
| \ ED Management constants |
| |
| |
| 14 CONSTANT MAX-EDS |
| 0 VALUE ed-freelist-head |
| 0 VALUE num-free-eds |
| VARIABLE ed-list-region |
| VARIABLE ed-list-region-dma |
| 0 VALUE usb-address |
| 0 VALUE initial-hub-address |
| 0 VALUE new-device-address |
| 0 VALUE mps |
| 0 VALUE DEBUG-TDS |
| 0 VALUE case-failed \ available for general use to see IF a CASE statement |
| \ failed or not. |
| 0 VALUE WHILE-failed \ available for general use to see IF a WHILE LOOP |
| \ failed in the middle. Used to break from the |
| \ WHILE LOOP |
| |
| 8 CONSTANT DEFAULT-CONTROL-MPS |
| 12 CONSTANT DEVICE-DESCRIPTOR-LEN |
| 1 CONSTANT DEVICE-DESCRIPTOR-TYPE |
| 1 CONSTANT DEVICE-DESCRIPTOR-TYPE-OFFSET |
| 4 CONSTANT DEVICE-DESCRIPTOR-DEVCLASS-OFFSET |
| 7 CONSTANT DEVICE-DESCRIPTOR-MPS-OFFSET |
| |
| 20 CONSTANT BULK-CONFIG-DESCRIPTOR-LEN |
| |
| 9 CONSTANT HUB-DEVICE-CLASS |
| 0 CONSTANT NO-CLASS |
| |
| |
| \ Temporary variables for functions. These variables have to be initialized |
| \ before usage in functions and their values assume significance only during |
| \ the function's execution time. Should be used like local variables. |
| \ CAUTION: |
| \ If you are calling functions that destroy contents of these variables, be |
| \ smart enuf to save the values before calling them. |
| \ It is recommended that these temporary variables are used only amidst normal |
| \ FORTH words -- not among the vicinity of any of the functions of this node. |
| |
| 0 VALUE temp1 |
| 0 VALUE temp2 |
| 0 VALUE temp3 |
| 0 VALUE extra-bytes |
| 0 VALUE num-td |
| 0 VALUE current |
| |
| 0 VALUE device-speed |
| |
| |
| \ DMA-able buffers: |
| |
| 0 VALUE setup-packet \ 8 bytes for setup packet |
| 0 VALUE ch-buffer \ 1 byte character buffer |
| |
| VARIABLE dd-buffer |
| VARIABLE dd-buffer-dma |
| VARIABLE cd-buffer |
| VARIABLE cd-buffer-dma |
| |
| |
| \ Global buffer allocation |
| \ ------------------------ |
| |
| \ Memory size for HCCA (0x100), setup-packet (8) and ch-buf (1) |
| 109 CONSTANT OHCI-GLOBAL-DMA-BUF-SIZE |
| |
| \ Memory for the HCCA - must stay allocated as long as the HC is operational! |
| |
| 0 VALUE hchcca |
| 0 VALUE hchcca-dma |
| |
| : (init-global-dma-bufs) |
| \ Allocate memory for HCCA (0x100), setup-packet (8) and ch-buf (1) |
| OHCI-GLOBAL-DMA-BUF-SIZE dma-alloc TO hchcca |
| hchcca OHCI-GLOBAL-DMA-BUF-SIZE 0 dma-map-in TO hchcca-dma |
| hchcca ff and IF |
| \ This should never happen - alloc-mem always aligns |
| s" Warning: hchcca not aligned!" usb-debug-print |
| THEN |
| hchcca 8 + TO setup-packet |
| setup-packet 1 + TO ch-buffer |
| |
| s" hchcca = " hchcca usb-debug-print-val |
| ; |
| (init-global-dma-bufs) |
| |
| 84 hchcca + CONSTANT hchccadneq \ HccaDoneHead |
| |
| \ Convert virtual address to physical |
| hchcca-dma hchcca - CONSTANT virt2phys-offset |
| : virt2phys ( virt -- phys ) |
| dup 0<> IF |
| virt2phys-offset + |
| THEN |
| ; |
| |
| \ Convert physical address to virtual |
| : phys2virt ( phys -- virt ) |
| dup 0<> IF |
| virt2phys-offset - |
| THEN |
| ; |
| |
| |
| \ Debug functions for displaying ED, TD and their combo list. |
| |
| : Show-OHCI-Register |
| ." -> OHCI-Register: " cr |
| ." - HcControl : " hccontrol rl@-le 8 .r |
| ." CmdStat : " hccomstat rl@-le 8 .r |
| ." HcInterr. : " hcintstat rl@-le 8 .r cr |
| |
| ." - HcFmIntval: " hcintrval rl@-le 8 .r |
| ." Per. Start: " HcPeriodicStart rl@-le 8 .r cr |
| |
| ." - PortStat-1: " hcrhpstat rl@-le 8 .r |
| ." PortStat-2: " hcrhpstat2 rl@-le 8 .r |
| ." PortStat-3: " hcrhpstat3 rl@-le 8 .r cr |
| |
| ." Descr-A : " hcrhdescA rl@-le 8 .r |
| ." Descr-B : " hcrhdescB rl@-le 8 .r |
| ." HcRhStat : " HcRhStatus rl@-le 8 .r cr |
| ; |
| |
| : display-ed ( ED-ADDRESS -- ) |
| TO temp1 |
| usb-debug-flag IF |
| s" Dump OF ED " type temp1 u. cr |
| s" eattr : " type temp1 ed>eattr l@-le u. cr |
| s" tdqhp : " type temp1 ed>tdqhp l@-le u. cr |
| s" tdqtp : " type temp1 ed>tdqtp l@-le u. cr |
| s" ned : " type temp1 ed>ned l@-le u. cr |
| THEN |
| ; |
| |
| |
| \ Displays the transfer descriptors |
| |
| : display-td ( TD-ADDRESS -- ) |
| TO temp1 |
| usb-debug-flag IF |
| s" TD " type temp1 u. s" dump: " type cr |
| s" td>tattr : " type temp1 td>tattr l@-le u. cr |
| s" td>cbptr : " type temp1 td>cbptr l@-le u. cr |
| s" td>ntd : " type temp1 td>ntd l@-le u. cr |
| s" td>bfrend : " type temp1 td>bfrend l@-le u. cr |
| THEN |
| ; |
| |
| |
| \ display's the descriptors |
| |
| : display-descriptors ( ED-ADDRESS -- ) |
| 10 1- not and ( ED-ADDRESS~ ) |
| dup display-ed ed>tdqhp l@-le phys2virt ( ED-ADDRESS~ ) |
| BEGIN |
| 10 1- not and ( ED-ADDRESS~ ) |
| dup 0<> ( ED-ADDRESS~ TRUE | FALSE ) |
| WHILE |
| dup display-td td>ntd l@-le phys2virt ( ED-ADDRESS~ ) |
| REPEAT |
| drop |
| ; |
| |
| |
| \ --------------------------------------------------------------------------- |
| \ TD LIST MANAGEMENT WORDS |
| \ ------------------------ |
| \ The following are WORDS internal to this node. They are supposed to |
| \ be used by other WORDS inside this device node. |
| \ The first three WORDS below form the interface. The fourth and fifth |
| \ word is a helper function and is not exposed to other portions of this |
| \ device node. |
| \ a) initialize-td-free-list |
| \ b) allocate-td-list |
| \ c) (free-td-list) |
| \ d) find-td-list-tail-and-size |
| \ e) zero-out-a-td-except-link |
| \ ---------------------------------------------------------------------------- |
| |
| |
| : zero-out-a-td-except-link ( td -- ) |
| |
| \ There are definitely smarter ways to do it especially |
| \ on a 64-bit machine. |
| |
| \ Optimization, Portability: |
| \ -------------------------- |
| \ Replace the following code by two "!" of zeroes. Since |
| \ we know that an "td" is actually 16 bytes and that we |
| \ will be executing on a 64-bit machine, we can finish off |
| \ with 2 stores. But that WONT be portable. |
| |
| dup 0 swap td>tattr l!-le ( td ) |
| dup 0 swap td>cbptr l!-le ( td ) |
| dup 0 swap td>bfrend l!-le ( td ) |
| drop |
| ; |
| |
| |
| \ COLON DEFINITION: initialize-td-free-list - Internal Function |
| |
| \ Initialize the TD Free List Region and create a linked list of successive |
| \ TDs. Note that the NEXT pointers are all in little-endian and they |
| \ can be directly used for HC purposes. |
| |
| |
| : initialize-td-free-list ( -- ) |
| MAX-TDS 0= IF EXIT THEN |
| td-list-region @ 0= IF EXIT THEN |
| td-list-region @ TO temp1 |
| 0 TO temp2 BEGIN |
| temp1 zero-out-a-td-except-link |
| temp1 /tdlen + dup virt2phys temp1 td>ntd l!-le TO temp1 |
| temp2 1+ TO temp2 |
| temp2 MAX-TDS = ( TRUE | FALSE ) |
| UNTIL |
| temp1 /tdlen - dup 0 swap td>ntd l!-le TO td-freelist-tail |
| td-list-region @ TO td-freelist-head |
| MAX-TDS TO num-free-tds |
| ; |
| |
| |
| \ COLON DEFINITION: allocate-td-list -- Internal function |
| \ Argument: |
| \ The function accepts a non-negative number and allocates |
| \ a TD-LIST containing that many TDs. A TD-LIST is a list |
| \ of TDs that are linked by the next-td field. The next-td |
| \ field is in little-endian mode so that the TD list can |
| \ be directly re-used by the HC. |
| \ Return value: |
| \ The function returns "head" and "tail" of the allocated |
| \ TD-LIST. If for any reason, the function cannot allocate |
| \ the TD-LIST, the function returns 2 NULL pointers in the |
| \ stack indicating that the allocation failed. |
| |
| \ Note that the TD list returned is NULL terminated. i.e |
| \ the nextTd field of the tail is NULL. |
| |
| : allocate-td-list ( n -- head tail ) |
| dup 0= IF drop 0 0 EXIT THEN ( 0 0 ) |
| dup num-free-tds > IF drop 0 0 EXIT THEN ( 0 0 ) |
| dup num-free-tds = IF ( n ) |
| drop td-freelist-head td-freelist-tail ( td-freelist-head td-freelist-tail ) |
| 0 TO td-freelist-head ( td-freelist-head td-freelist-tail ) |
| 0 TO td-freelist-tail ( td-freelist-head td-freelist-tail ) |
| 0 TO num-free-tds ( td-freelist-head td-freelist-tail ) |
| EXIT |
| THEN |
| |
| \ If we are here then we know that the requested number of TDs is less |
| \ than what we actually have. We need to traverse the list and find the |
| \ new Head pointer position and then update the head pointer accordingly. |
| \ Update num-free-tds |
| |
| dup num-free-tds swap - TO num-free-tds ( n ) |
| |
| \ Traverse through the Free list to identify the element that exists after |
| \ "n" TDs. Use the info to return the head and tail pointer and update |
| \ the new td-list-head |
| |
| td-freelist-head ( n td-list-head ) |
| dup TO temp1 ( n td-list-head ) |
| swap ( td-list-head n ) |
| 0 DO ( td-list-head ) |
| temp1 TO temp2 ( td-list-head ) |
| temp1 td>ntd l@-le phys2virt TO temp1 ( td-list-head ) |
| LOOP ( td-list-head ) |
| temp2 ( td-list-head td-list-tail ) |
| dup td>ntd 0 swap l!-le ( td-list-head td-list-tail ) |
| temp1 TO td-freelist-head ( td-list-head td-list-tail ) |
| ; |
| |
| |
| \ COLON DEFINITION: find-td-list-tail-and-size |
| \ This function counts the number of TD elements |
| \ in the given list. It also returns the last tail |
| \ TD of the TD list. |
| |
| \ ASSUMPTION: |
| \ A NULL terminated TD list is assumed. A not-well formed |
| \ list can result in in-determinate behaviour. |
| |
| \ ROOM FOR ENHANCEMENT: |
| \ We could arrive at a generic function for counting |
| \ list elements to which the next-ptr offset can also |
| \ be passed as an argument (in this case it is >ntd) |
| \ This function can then be changed to call the |
| \ function with "0 >ntd" as an additional argument |
| \ (apart from head and tail) |
| |
| |
| : find-td-list-tail-and-size ( head -- tail n ) |
| TO temp1 |
| 0 TO temp2 |
| 0 TO temp3 |
| DEBUG-TDS IF |
| s" BEGIN find-td-list-tail-and-size: " usb-debug-print |
| THEN |
| BEGIN |
| temp1 0<> ( TRUE|FALSE ) |
| WHILE |
| DEBUG-TDS IF |
| temp1 u. cr |
| THEN |
| temp1 TO temp3 |
| temp1 td>ntd l@-le phys2virt TO temp1 |
| temp2 1+ TO temp2 |
| REPEAT |
| temp3 temp2 ( tail n ) |
| DEBUG-TDS IF |
| s" END find-td-list-tail-and-size" usb-debug-print |
| THEN |
| ; |
| |
| |
| \ COLON DEFINITION: (free-td-list) |
| |
| \ Arguments: (head --) |
| \ The "head" pointer of the TD-LIST to be freed is passed as |
| \ an argument to this function. The function merely adds the list to the |
| \ already existing TD-LIST |
| |
| \ Assumptions: |
| \ The function assumes that the TD-LIST passed as argument is a well-formed |
| \ list. The function does not do any check on it. |
| \ But since, the "TD-LIST" is generally freed from the DONE-QUEUE which is |
| \ a well-formed list, the interface makes much sense. |
| |
| \ Return values: |
| \ Nothing is returned. The arguments passed are popped off. |
| |
| |
| : (free-td-list) ( head -- ) |
| |
| \ Enhancement: |
| \ We could zero-out-a-td-except-link for the TD list that is being freed. |
| \ This way, we could prevent some nasty repercussions of bugs (that are yet |
| \ to be discovered). but we can include this enhancement during the testing |
| \ phase. |
| |
| dup find-td-list-tail-and-size num-free-tds + TO num-free-tds ( head tail ) |
| td-freelist-tail 0= IF ( head tail ) |
| dup TO td-freelist-tail ( head tail ) |
| THEN ( head tail ) |
| td>ntd td-freelist-head virt2phys swap l!-le ( head ) |
| TO td-freelist-head |
| ; |
| |
| |
| \ END OF TD LIST MANAGEMENT WORDS |
| \ ED Management section BEGINs |
| \ ---------------------------- |
| |
| |
| : zero-out-an-ed-except-link ( ed -- ) |
| dup 0 swap ed>eattr l!-le ( ed ) |
| dup 0 swap ed>tdqtp l!-le ( ed ) |
| dup 0 swap ed>tdqhp l!-le ( ed ) |
| drop |
| ; |
| |
| \ Intialises ed-list afresh |
| |
| : initialize-ed-free-list ( -- ) |
| MAX-EDS 0= IF EXIT THEN |
| ed-list-region @ 0= IF |
| s" init-ed-list: ed-list-region is not allocated!" usb-debug-print |
| EXIT |
| THEN |
| ed-list-region @ TO temp1 |
| 0 TO temp2 BEGIN |
| temp1 zero-out-an-ed-except-link |
| usb-debug-flag IF |
| ." ED " temp2 . ." v: " temp1 . ." p: " temp1 virt2phys . cr |
| THEN |
| temp1 /edlen + dup virt2phys temp1 ed>ned l!-le TO temp1 |
| temp2 1+ TO temp2 |
| temp2 MAX-EDS = |
| UNTIL |
| temp1 /edlen - ed>ned 0 swap l!-le |
| ed-list-region @ TO ed-freelist-head |
| MAX-EDS TO num-free-eds |
| ; |
| |
| |
| \ allocate an ed and return ed address |
| |
| |
| : allocate-ed ( -- ed-ptr ) |
| num-free-eds 0= IF 0 EXIT THEN |
| ed-freelist-head ( ed-freelist-head ) |
| ed-freelist-head ed>ned ( ed-freelist-head ned ) |
| l@-le phys2virt TO ed-freelist-head ( ed-freelist-head ) |
| num-free-eds 1- TO num-free-eds ( ed-freelist-head ) |
| dup ed>ned 0 swap l!-le \ Terminate the Link. ( ed-freelist-head ) |
| ; |
| |
| |
| \ free the given ed pointer |
| |
| : free-ed ( ed-ptr -- ) |
| dup zero-out-an-ed-except-link ( ed-ptr ) |
| dup ed>ned ed-freelist-head virt2phys swap l!-le ( ed-ptr ) |
| TO ed-freelist-head |
| num-free-eds 1+ TO num-free-eds |
| ; |
| |
| |
| \ Instance buffer allocation |
| \ -------------------------- |
| |
| : (allocate-mem) ( -- ) |
| /tdlen MAX-TDS * 10 + |
| dup dma-alloc ( td-region-size td-list-region-ptr ) |
| dup td-list-region ! |
| dup f and IF |
| s" Warning: td-list-region not aligned!" usb-debug-print |
| THEN |
| swap 0 dma-map-in td-list-region-dma ! |
| initialize-td-free-list |
| |
| /edlen MAX-EDS * 10 + |
| dup dma-alloc ( ed-region-size ed-list-region-ptr ) |
| dup ed-list-region ! |
| dup f and IF |
| s" Warning: ed-list-region not aligned!" usb-debug-print |
| THEN |
| swap 0 dma-map-in ed-list-region-dma ! |
| initialize-ed-free-list |
| |
| DEVICE-DESCRIPTOR-LEN chars dup dma-alloc |
| dup dd-buffer ! ( dd-len dd-buf ) |
| swap 0 dma-map-in dd-buffer-dma ! |
| |
| BULK-CONFIG-DESCRIPTOR-LEN chars dup dma-alloc |
| dup cd-buffer ! ( cd-len cd-buf ) |
| swap 0 dma-map-in cd-buffer-dma ! |
| |
| s" td-list-region = " td-list-region @ usb-debug-print-val |
| s" ed-list-region = " ed-list-region @ usb-debug-print-val |
| s" dd-buffer = " dd-buffer @ usb-debug-print-val |
| s" cd-buffer-dma = " cd-buffer-dma @ usb-debug-print-val |
| ; |
| |
| |
| \ The method makes sure that when the host node is closed all |
| \ associated buffer allocations made for data-structures as |
| \ well as data-buffers are freed |
| |
| : (de-allocate-mem) ( -- ) |
| td-list-region @ ?dup IF |
| /tdlen MAX-TDS * 10 + ( td-list-region td-region-size ) |
| 2dup td-list-region-dma @ swap dma-map-out |
| dma-free |
| 0 td-list-region ! |
| 0 td-list-region-dma ! |
| THEN |
| ed-list-region @ ?dup IF |
| /edlen MAX-EDS * 10 + |
| 2dup ed-list-region-dma @ swap dma-map-out |
| dma-free |
| 0 ed-list-region ! |
| 0 ed-list-region-dma ! |
| THEN |
| dd-buffer @ ?dup IF |
| DEVICE-DESCRIPTOR-LEN |
| 2dup dd-buffer-dma @ swap dma-map-out |
| dma-free |
| 0 dd-buffer ! |
| 0 dd-buffer-dma ! |
| THEN |
| cd-buffer @ ?dup IF |
| BULK-CONFIG-DESCRIPTOR-LEN |
| 2dup cd-buffer-dma @ swap dma-map-out |
| dma-free |
| 0 cd-buffer ! |
| 0 cd-buffer-dma ! |
| THEN |
| ; |
| |
| |
| \ Suspend hostcontroller (and the bus). |
| \ This method must be called before the operating system starts. |
| \ It prevents the HC from doing DMA in the background during boot |
| \ (e.g. updating its frame number counter in the HCCA) |
| |
| : hc-quiesce ( -- ) |
| \ s" USB HC suspend with hccontrol=" type hccontrol . cr |
| 00C3 hccontrol rl!-le \ Suspend USB host controller |
| \ Release memory for HCCA etc: |
| hchcca hchcca-dma OHCI-GLOBAL-DMA-BUF-SIZE dma-map-out |
| hchcca OHCI-GLOBAL-DMA-BUF-SIZE dma-free |
| ; |
| |
| ' hc-quiesce add-quiesce-xt \ Assert that HC will be supsended |
| |
| |
| \ OF methods |
| |
| : open ( -- TRUE|FALSE ) |
| instance-count dup 0= IF |
| s" OHCI First open" usb-debug-print |
| (allocate-mem) |
| THEN |
| 1 + TO instance-count |
| s" OHCI Open instance count now: " instance-count usb-debug-print-val |
| TRUE |
| ; |
| |
| : close ( -- ) |
| instance-count dup 1 = IF |
| s" OHCI Last close" usb-debug-print |
| (de-allocate-mem) |
| THEN |
| 1 - TO instance-count |
| s" OHCI Close instance count now: " instance-count usb-debug-print-val |
| ; |
| |
| |
| \ COLON DEFINITION: HC-enable-control-list-processing |
| \ Enables USB HC transactions on control list. |
| |
| : HC-enable-control-list-processing ( -- ) |
| hccomstat dup rl@-le 02 or swap rl!-le |
| hccontrol dup rl@-le 10 or swap rl!-le |
| ; |
| |
| |
| \ COLON DEFINTION: HC-enable-bulk-list-processing |
| \ PENDING: Remove Hard coded constants. |
| |
| : HC-enable-bulk-list-processing ( -- ) |
| hccomstat dup rl@-le 04 or swap rl!-le |
| hccontrol dup rl@-le 20 or swap rl!-le |
| ; |
| |
| |
| : HC-enable-interrupt-list-processing ( -- ) |
| hccontrol dup rl@-le 04 or swap rl!-le |
| ; |
| |
| |
| \ Clearing WDH to allow HC to write into done queue again |
| |
| : (HC-ACK-WDH) ( -- ) |
| WDH hcintstat rl!-le |
| ; |
| |
| \ Checking whether anything has been written into done queue |
| |
| : (HC-CHECK-WDH) ( -- updated? ) |
| hcintstat rl@-le WDH and 0<> |
| ; |
| |
| |
| \ Disable USB transaction and keep it ready |
| |
| : disable-control-list-processing ( -- ) |
| hccontrol dup rl@-le ffffffef and swap rl!-le |
| hccomstat dup rl@-le fffffffd and swap rl!-le |
| ; |
| |
| : disable-bulk-list-processing ( -- ) |
| hccontrol dup rl@-le ffffffdf and swap rl!-le |
| hccomstat dup rl@-le fffffffb and swap rl!-le |
| ; |
| |
| |
| : disable-interrupt-list-processing ( -- ) |
| hccontrol dup rl@-le fffffffb and swap rl!-le |
| ; |
| |
| |
| \ COLON DEFINITION: fill-TD-list |
| |
| \ This function accepts a TD list and a data-buffer and |
| \ distributes this data buffer over the TD list depending |
| \ on the Max Packet Size. |
| |
| \ Arguments: |
| \ ---------- |
| \ (from bottom of stack) |
| \ 1. addr -- Address of the data buffer |
| \ 2. dlen -- Length of the data buffer above. |
| \ 3. dir -- Tells whether the TDs r for an IN or |
| \ OUT transaction. |
| \ 4. MPS -- Maximum Packet Size associated with the endpoint |
| \ that will use this TD list. |
| \ 5. TD-List-Head - Head pointer of the List of TDs. |
| \ This list is NOT expected to be NULL terminated. |
| |
| \ Assumptions: |
| \ ----------- |
| \ 1. TD-List for data is well-formed and has sufficient entries |
| \ to hold "dlen". |
| \ 2. The TDs toggle field is assumed to be taken from the endpoint |
| \ descriptor's "toggle carry" field. |
| \ 3. Assumes that the caller specifies the correct start-toggle. |
| \ If the caller specifies a wrong data toggle of 1 for a SETUP |
| \ PACKET, this method will not find it out. |
| |
| \ COLON DEFINTION: (toggle-current-toggle) |
| \ Scope: Internal to fill-TD-list |
| \ Functionality: |
| \ Toggles the "T" field that is passed as argument. |
| \ "T" as in the "T" field of the TD. |
| |
| 0 VALUE current-toggle |
| : fill-TD-list ( start-toggle addr dlen dp MPS TD-List-Head -- ) |
| TO temp1 ( start-toggle addr dlen dp MPS ) |
| TO temp2 ( start-toggle addr dlen dp ) |
| CASE ( start-toggle addr dlen ) |
| OHCI-DP-SETUP OF TD-DP-SETUP TO temp3 ENDOF ( start-toggle addr dlen ) |
| OHCI-DP-IN OF TD-DP-IN TO temp3 ENDOF ( start-toggle addr dlen ) |
| OHCI-DP-OUT OF TD-DP-OUT TO temp3 ENDOF ( start-toggle addr dlen ) |
| dup OF -1 TO temp3 ( start-toggle addr dlen ) |
| s" fill-TD-list: Invalid DP specified" usb-debug-print |
| ENDOF |
| ENDCASE |
| temp3 -1 = IF EXIT THEN ( start-toggle addr dlen ) |
| |
| \ temp1 -- TD-List-Head |
| \ temp2 -- Max Packet Size |
| \ temp3 -- TD-DP-IN or TD-DP-OUT or TD-DP-SETUP |
| |
| rot ( addr dlen start-toggle ) |
| TO current-toggle swap ( dlen addr ) |
| BEGIN |
| over temp2 >= ( dlen addr TRUE|FALSE ) |
| WHILE ( dlen addr ) |
| dup virt2phys temp1 td>cbptr l!-le ( dlen addr ) |
| current-toggle 18 lshift ( dlen addr current-toggle~ ) |
| DATA0-TOGGLE ( dlen addr current-toggle~ toggle ) |
| CC-FRESH-TD temp3 or or or ( dlen addr or-result ) |
| temp1 td>tattr l!-le ( dlen addr~ ) |
| dup temp2 1- + virt2phys temp1 td>bfrend l!-le ( dlen addr~ ) |
| temp2 + ( dlen next-addr ) |
| swap temp2 - swap |
| temp1 td>ntd l@-le phys2virt TO temp1 ( dlen next-addr ) |
| current-toggle ( dlen next-addr current-toggle ) |
| CASE |
| 0 OF 1 TO current-toggle ENDOF |
| 1 OF 0 TO current-toggle ENDOF |
| ENDCASE |
| REPEAT ( dlen addr ) |
| over 0<> IF |
| dup virt2phys temp1 td>cbptr l!-le ( dlen addr ) |
| current-toggle 18 lshift ( dlen addr curent-toggle~ ) |
| DATA0-TOGGLE ( dlen addr curent-toggle~ toggle ) |
| CC-FRESH-TD temp3 or or or ( dlen addr or-result ) |
| temp1 td>tattr l!-le ( dlen addr ) |
| + 1- virt2phys temp1 td>bfrend l!-le |
| ELSE |
| 2drop |
| THEN |
| ; |
| |
| |
| \ COLON DEFINITION: (td-list-status ) |
| \ FUNCTIONALITY: |
| \ To traverse the TD list to check for a TD carrying non-zero CC return the |
| \ respective TD address and CC ELSE 0 |
| \ SCOPE: |
| \ Internal method |
| |
| : (td-list-status) ( PointerToTDlist -- failingTD CCode TRUE | 0 ) |
| BEGIN ( PointerToTDlist ) |
| dup 0<> ( PointerToTDlist TRUE|FALSE ) |
| IF ( PointerToTDlist ) |
| dup td>tattr l@-le f0000000 and 1c rshift dup 0= TRUE swap |
| ( PointerToTDlist CCode TRUE TRUE|FALSE ) |
| ELSE |
| drop FALSE dup ( FALSE ) |
| THEN |
| WHILE |
| drop drop td>ntd l@-le phys2virt |
| REPEAT |
| ; |
| |
| |
| \ ================================================================== |
| \ COLON DEFINITION: (wait-for-done-q) |
| \ FUNCTIONALITY: |
| \ To do a timed polling of the done queue and acknowledge and return |
| \ the address of the last retired Td list |
| \ SCOPE: |
| \ Internal method |
| \ ================================================================== |
| |
| : (wait-for-done-q) ( timeout -- TD-list TRUE | FALSE ) |
| BEGIN ( timeout ) |
| dup 0<> ( timeout TRUE|FALSE ) |
| (HC-CHECK-WDH) NOT ( timeout TRUE|FALSE TRUE|FALSE ) |
| AND \ not timed out AND WDH-bit not set |
| WHILE |
| 1 ms \ wait |
| 1- ( timeout ) |
| dup ff and 0= IF show-proceed THEN |
| REPEAT ( timeout ) |
| drop |
| hchccadneq l@-le phys2virt \ read last HcDoneHead (RAM) |
| (HC-CHECK-WDH) \ HcDoneHead was updated ? |
| IF |
| (HC-ACK-WDH) \ clear register bit: WDH |
| TRUE ( td-list TRUE ) |
| ELSE |
| FALSE |
| THEN |
| ; |
| |
| |
| \ displays free tds |
| |
| |
| : debug-td ( -- ) |
| s" Num Free TDs = " num-free-tds usb-debug-print-val |
| ; |
| |
| |
| \ display content of frame counter |
| |
| \ : debug-frame-counter ( -- ) |
| \ 40 1 DO |
| \ ." Frame ct at HCCA at end of enumeration = " |
| \ hchcca 80 + rl@-le . |
| \ LOOP |
| \ ; |
| |
| \ ============================================================================ |
| \ COLON DEFINITION: HC-reset |
| \ This routine should be the first to be executed. |
| \ This routine will reset the HC and will bring it to Operational |
| \ state. |
| \ PENDING: |
| \ Arrive at the right value of FrameInterval. Currently we are hardcoding |
| \ it. |
| \ ========================================================================== |
| : HC-reset ( -- ) |
| hcrhdescA rl@-le ff and ( total-rh-ports ) |
| to max-rh-ports |
| |
| \ if no hardware-reset was issued (rescan) |
| \ switch off all ports first ! |
| hcrhpstat TO current-stat \ start with first port status reg |
| 0 \ port status default |
| max-rh-ports 0 \ checking all ports |
| ?DO |
| current-stat rl@-le or \ OR-ing all stats |
| 200 current-stat rl!-le \ Clear Port Power (CPP) |
| current-stat 4 + TO current-stat \ check next RH-Port |
| LOOP |
| 100 and 0<> \ any of the ports had power ? |
| IF |
| d# 750 wait-proceed \ wait for power discharge |
| THEN |
| |
| \ Reset HC and wait until reset has been cleared |
| hccomstat dup rl@-le 01 or swap rl!-le \ issue HC reset |
| BEGIN |
| hccomstat rl@-le 01 and 0<> \ wait for reset end |
| WHILE |
| REPEAT |
| |
| 23f02edf hcintrval rl!-le \ frame-interval register |
| hchcca-dma hchccareg rl!-le \ HC communication area |
| 0000 hcctrhead rl!-le \ control transfer head |
| 0000 hcbulkhead rl!-le \ bulk transfer head |
| 0ffff hcintdsbl rl!-le \ interrupt disable reg. |
| |
| \ all devices are still in reset-state |
| \ next command starts sending SOFs |
| 83 hccontrol rl!-le \ set USBOPERATIONAL |
| |
| \ these two repeated register settings are necessary for Bimini |
| \ Its OHCI controller (AM8111) behaves different to NEC's one |
| 23f02edf hcintrval rl!-le \ frame-interval register |
| hchcca-dma hchccareg rl!-le \ HC communication area |
| |
| d# 50 ms |
| |
| \ now power on all ports of this root-hub |
| hcrhpstat TO current-stat \ start with first port status reg |
| max-rh-ports 0 |
| ?DO |
| 102 current-stat rl!-le \ power on and enable |
| hcrhdescA rl@-le 18 rshift 2 * ms \ startup delay 30 ms (2 * POTPGT) |
| current-stat 4 + TO current-stat \ check next RH-Port |
| LOOP |
| d# 500 wait-proceed \ STEC device needs 300 ms |
| ; |
| |
| : error-recovery ( -- ) |
| initialize-td-free-list |
| initialize-ed-free-list |
| HC-reset |
| ; |
| |
| \ ================================================================ |
| : store-initial-usb-hub-address ( -- ) |
| usb-address TO initial-hub-address |
| ; |
| |
| : reset-to-initial-usb-hub-address ( -- ) |
| initial-hub-address TO usb-address |
| ; |
| |
| \ allocate-usb-address: |
| \ Function allocates an USB address. |
| \ See RISK below. |
| |
| |
| : allocate-usb-address ( -- usb-address ) |
| usb-address 7f <> ( TRUE|FALSE ) |
| IF |
| usb-address 1+ TO usb-address \ RISK: Check to see IF it overflows 127 |
| usb-address ( usb-address ) |
| THEN ( usb-address ) |
| ; |
| |
| s" usb-support.fs" INCLUDED |
| |
| |
| |
| \ ===================================================================== |
| \ COLON DEFINTION: control-std-set-address |
| \ INTERFACE FUNCTION |
| \ Function allocates an USB addrss and uses it to send SET-ADDRESS packet |
| \ to the default USB address. |
| \ This is an interface function available to child nodes. |
| |
| : control-std-set-address ( speedbit -- usb-address TRUE | FALSE ) |
| >r ( R: speedbit ) |
| 0005000000000000 setup-packet ! |
| allocate-usb-address dup setup-packet 2 + c! ( usb-addr R: speedbit ) |
| s" USB set-address: " 2 pick usb-debug-print-val ( usb-addr R: speedbit ) |
| 0 0 0 setup-packet 8 r> controlxfer ( usb-addr TRUE | FALSE ) |
| IF ( TRUE | FALSE ) |
| TRUE ( TRUE ) |
| ELSE |
| drop FALSE \ PENDING: Return the allocated address back. ( FALSE ) |
| THEN ( TRUE | FALSE ) |
| ; |
| |
| |
| \ Fetches the device decriptor of the usb-device |
| |
| |
| : control-std-get-device-descriptor |
| ( data-buffer data-len MPS fa -- TRUE|FALSE ) |
| |
| 8006000100000000 setup-packet ! |
| 2 pick setup-packet 6 + w!-le |
| ( data-buffer data-len MPS fa ) |
| setup-packet -rot ( data-buffer data-len setup-packet MPS fa ) |
| >r >r >r >r >r 0 r> r> r> r> r> |
| ( 0 data-buffer data-len setup-packet MPS fa ) |
| controlxfer ( TRUE | FALSE ) |
| ; |
| |
| |
| \ ================================================================== |
| \ To retrieve the configuration descriptor of a device |
| \ with a valid USB address |
| |
| |
| : control-std-get-configuration-descriptor |
| ( data-buffer data-len MPS FuncAddr -- TRUE|FALSE ) |
| TO temp1 ( data-buffer data-len MPS ) |
| TO temp2 ( data-buffer data-len ) |
| TO temp3 ( data-buffer ) |
| 8006000200000000 setup-packet ! |
| temp3 setup-packet 6 + w!-le |
| 0 swap temp3 setup-packet temp2 temp1 controlxfer |
| ; |
| |
| \ Fetches num of logical units available for a device |
| : control-std-get-maxlun ( MPS fun-addr dir data-buff data-len -- TRUE | FALSE ) |
| GET-MAX-LUN setup-packet ! ( MPS fun-addr dir data-buff data-len ) |
| setup-packet 5 pick 5 pick |
| ( MPS fun-addr dir data-buff data-len setup-packet MPS fun-addr ) |
| controlxfer ( MPS fun-addr TRUE | FALSE ) |
| nip nip ( TRUE | FALSE ) |
| ; |
| |
| \ Bulk-Only Mass Storage Reset |
| \ fixed to interface #0 |
| : control-bulk-reset ( MPS fun-addr dir data-buff data-len -- TRUE | FALSE ) |
| 21FF000000000000 setup-packet ! ( MPS fun-addr dir data-buff data-len ) |
| setup-packet 5 pick 5 pick |
| ( MPS fun-addr dir data-buff data-len setup-packet MPS fun-addr ) |
| controlxfer ( MPS fun-addr TRUE | FALSE ) |
| nip nip ( TRUE | FALSE ) |
| ; |
| |
| |
| |
| \ get the string descriptor of the usb device |
| |
| |
| : control-std-get-string-descriptor |
| ( StringIndex data-buffer data-len MPS FuncAddr -- TRUE | FALSE ) |
| TO temp1 ( StringIndex data-buffer data-len MPS ) |
| TO temp2 ( StringIndex data-buffer data-len ) |
| TO temp3 ( StringIndex ) |
| 8006000300000000 setup-packet ! |
| temp3 setup-packet 6 + w!-le |
| 409 setup-packet 4 + w!-le \ US English Language code. |
| swap ( data buffer StringIndex ) |
| setup-packet 2 + c! ( data-buffer ) |
| 0 swap temp3 setup-packet temp2 temp1 controlxfer ( TRUE | FALSE ) |
| ; |
| |
| \ sets a valid usb configaration for a device |
| |
| : control-std-set-configuration ( configvalue FuncAddr -- TRUE|FALSE ) |
| TO temp1 ( configvalue ) |
| TO temp2 |
| 0009000000000000 setup-packet ! \ RISK: Endian and 64-bit assumptions |
| temp2 setup-packet 2 + w!-le |
| 0 0 0 setup-packet DEFAULT-CONTROL-MPS temp1 controlxfer |
| |
| \ NOTE: We could use DEFAULT-CONTROL-MPS because there is no data phase |
| \ associated with this control xfer. Its a dont care. |
| ; |
| |
| |
| \ To set the device address retrive the device descriptor and build the |
| \ usb device tree by passing device class |
| |
| |
| 0 VALUE port-number |
| |
| s" usb-enumerate.fs" INCLUDED |
| |
| : rhport-enumerate ( port-num -- ) |
| TO port-number |
| device-speed control-std-set-address ( usb-addr TRUE | FALSE ) |
| IF |
| device-speed or ( usb-addr+speedbit ) |
| TO new-device-address |
| dd-buffer @ 8 erase |
| |
| \ Read Device Descriptor - First 8 bytes. |
| |
| dd-buffer @ DEFAULT-CONTROL-MPS DEFAULT-CONTROL-MPS ( buffer mps mps ) |
| new-device-address control-std-get-device-descriptor ( TRUE | FALSE ) |
| IF |
| ELSE |
| s" USB: Read Dev Descriptor failed" usb-debug-print EXIT |
| |
| \ NOTE: Tomorrow, IF there is a LOOP here,we may need to UNLOOP before |
| \ "EXIT"ing. Beware. Much depends on what LOOPing construct is used. |
| |
| THEN |
| |
| \ Read the Descriptor Type and check IF we have read correctly. |
| |
| dd-buffer @ DEVICE-DESCRIPTOR-TYPE-OFFSET + c@ ( Descriptor-type ) |
| DEVICE-DESCRIPTOR-TYPE <> IF |
| s" USB: Error Reading Device Descriptor" usb-debug-print |
| s" Read descriptor is not of the right type" usb-debug-print |
| s" Aborting enumeration" usb-debug-print |
| EXIT |
| \ NOTE: Tomorrow, if you have a LOOP here then we may need to |
| \ UNLOOP before EXITing. Depends on what type of looping construct |
| \ is used. Beware. |
| |
| THEN |
| |
| \ Read the MPS and store it. |
| |
| dd-buffer @ DEVICE-DESCRIPTOR-MPS-OFFSET + c@ TO mps |
| |
| \ NOTE: Probably, we could check MPS for only 8/16/32/64 |
| \ hmm.. not now... |
| |
| \ Read the device class to see what type of device it is and create an |
| \ appropriate child node here. |
| create-usb-device-tree |
| ELSE |
| s" Set address failed on port " port-number usb-debug-print-val |
| s" Aborting Enumeration." usb-debug-print |
| EXIT |
| |
| \ NOTE: Tomorrow , if you have a LOOP here then we may need to |
| \ UNLOOP before EXITing. Depends on what type of looping construct |
| \ is used. Beware. |
| |
| THEN |
| ; |
| |
| |
| \ ========================================================================= |
| \ PROTOTYPE FUNCTION: "rhport-initialize" |
| \ Detect Device, reset and enable the respective port. |
| \ COLON Definition rhport-initialize accepts the total number of root hub |
| \ ports as an argument and probes every available root hub port and initiates |
| \ the build of the USB devie sub-tree so is effectively the mother of all |
| \ USB device nodes that are to be detected and instantiated. |
| \ ========================================================================== |
| : rhport-initialize ( -- ) |
| |
| hcrhpstat TO current-stat \ start with first port status reg |
| max-rh-ports 1+ 1 |
| ?DO |
| usb-debug-flag IF |
| ." Initializing RH port " i . cr |
| THEN |
| \ any Device connected to that port ? |
| current-stat rl@-le RHP-CCS and 0<> ( TRUE|FALSE ) |
| IF |
| s" Device connected to this port!" usb-debug-print |
| RHP-PRS current-stat rl!-le \ issue a port reset |
| BEGIN |
| current-stat rl@-le RHP-PRS AND \ wait for reset end |
| WHILE |
| REPEAT |
| hcrhdescA rl@-le 18 rshift 2 * ms \ startup delay 30 ms (POTPGT) |
| d# 100 ms |
| |
| current-stat rl@-le 200 and 4 lshift |
| to device-speed \ store speed bit |
| |
| RHP-CSC RHP-PRSC or current-stat rl!-le |
| |
| I ['] rhport-enumerate CATCH IF \ Scan port |
| s" USB scan failed on root hub port: " rot usb-debug-print-val |
| reset-to-initial-usb-hub-address |
| THEN |
| |
| ELSE |
| s" No device detected at this port." usb-debug-print |
| current-stat rl@-le 80000 and 0<> \ is over-current detected ? |
| IF |
| s" Warning: Overcurrent indicated" usb-debug-print |
| THEN |
| THEN |
| current-stat 4 + TO current-stat \ check next RH-Port |
| LOOP |
| ; |
| |
| |
| \ =================================================== |
| \ Enumeration at Host level |
| \ =================================================== |
| |
| : enumerate ( -- ) |
| HC-reset |
| store-initial-usb-hub-address |
| rhport-initialize \ Probe all available RH ports |
| reset-to-initial-usb-hub-address |
| ; |