Rev 3534 | Rev 4418 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
3520 | clevermous | 1 | ; Initialization of the USB subsystem. |
2 | ; Provides usb_init procedure, includes all needed files. |
||
3 | |||
4 | ; General notes: |
||
5 | ; * There is one entry point for external kernel code: usb_init is called |
||
6 | ; from initialization code and initializes USB subsystem. |
||
7 | ; * There are several entry points for API; see the docs for description. |
||
8 | ; * There are several functions which are called from controller-specific |
||
9 | ; parts of USB subsystem. The most important is usb_new_device, |
||
10 | ; which is called when a new device has been connected (over some time), |
||
11 | ; has been reset and is ready to start configuring. |
||
12 | ; * IRQ handlers are very restricted. They can not take any locks, |
||
13 | ; since otherwise a deadlock is possible: imagine that a code has taken the |
||
14 | ; lock and was interrupted by IRQ handler. Now IRQ handler would wait for |
||
15 | ; releasing the lock, and a lock owner would wait for exiting IRQ handler |
||
16 | ; to get the control. |
||
17 | ; * Thus, there is the special USB thread which processes almost all activity. |
||
18 | ; IRQ handlers do the minimal processing and wake this thread. |
||
19 | ; * Also the USB thread wakes occasionally to process tasks which can be |
||
20 | ; predicted without interrupts. These include e.g. a periodic roothub |
||
21 | ; scanning in UHCI and initializing in USB_CONNECT_DELAY ticks |
||
22 | ; after connecting a new device. |
||
23 | ; * The main procedure of USB thread, usb_thread_proc, does all its work |
||
24 | ; by querying usb_hardware_func.ProcessDeferred for every controller |
||
25 | ; and usb_hub_process_deferred for every hub. |
||
26 | ; ProcessDeferred does controller-specific actions and calculates the time |
||
27 | ; when it should be invoked again, possibly infinite. |
||
28 | ; usb_thread_proc selects the minimum from all times returned by |
||
29 | ; ProcessDeferred and sleeps until this moment is reached or the thread |
||
30 | ; is awakened by IRQ handler. |
||
31 | |||
32 | ; Initializes the USB subsystem. |
||
33 | proc usb_init |
||
34 | ; 1. Initialize all locks. |
||
35 | mov ecx, usb_controllers_list_mutex |
||
36 | call mutex_init |
||
37 | mov ecx, usb1_ep_mutex |
||
38 | call mutex_init |
||
39 | mov ecx, usb_gtd_mutex |
||
40 | call mutex_init |
||
41 | mov ecx, ehci_ep_mutex |
||
42 | call mutex_init |
||
43 | mov ecx, ehci_gtd_mutex |
||
44 | call mutex_init |
||
45 | ; 2. Kick off BIOS from all USB controllers, calling the corresponding function |
||
46 | ; *hci_kickoff_bios. Also count USB controllers for the next step. |
||
47 | ; Note: USB1 companion(s) must go before the corresponding EHCI controller, |
||
48 | ; otherwise BIOS could see a device moving from EHCI to a companion; |
||
49 | ; first, this always wastes time; |
||
50 | ; second, some BIOSes are buggy, do not expect that move and try to refer to |
||
51 | ; previously-assigned controller instead of actual; sometimes that leads to |
||
52 | ; hangoff. |
||
53 | ; Thus, process controllers in PCI order. |
||
54 | mov esi, pcidev_list |
||
55 | push 0 |
||
56 | .kickoff: |
||
57 | mov esi, [esi+PCIDEV.fd] |
||
58 | cmp esi, pcidev_list |
||
59 | jz .done_kickoff |
||
60 | cmp word [esi+PCIDEV.class+1], 0x0C03 |
||
61 | jnz .kickoff |
||
62 | mov eax, uhci_kickoff_bios |
||
63 | cmp byte [esi+PCIDEV.class], 0x00 |
||
64 | jz .do_kickoff |
||
65 | mov eax, ohci_kickoff_bios |
||
66 | cmp byte [esi+PCIDEV.class], 0x10 |
||
67 | jz .do_kickoff |
||
68 | mov eax, ehci_kickoff_bios |
||
69 | cmp byte [esi+PCIDEV.class], 0x20 |
||
70 | jnz .kickoff |
||
71 | .do_kickoff: |
||
72 | inc dword [esp] |
||
73 | call eax |
||
74 | jmp .kickoff |
||
75 | .done_kickoff: |
||
76 | pop eax |
||
77 | ; 3. If no controllers were found, exit. |
||
78 | ; Otherwise, run the USB thread. |
||
79 | test eax, eax |
||
80 | jz .nothing |
||
81 | call create_usb_thread |
||
82 | jz .nothing |
||
83 | ; 4. Initialize all USB controllers, calling usb_init_controller for each. |
||
84 | ; Note: USB1 companion(s) should go before the corresponding EHCI controller, |
||
85 | ; although this is not strictly necessary (this way, a companion would not try |
||
86 | ; to initialize high-speed device only to see a disconnect when EHCI takes |
||
87 | ; control). |
||
88 | ; Thus, process all EHCI controllers in the first loop, all USB1 controllers |
||
89 | ; in the second loop. (One loop in reversed PCI order could also be used, |
||
90 | ; but seems less natural.) |
||
91 | ; 4a. Loop over all PCI devices, call usb_init_controller |
||
92 | ; for all EHCI controllers. |
||
93 | mov eax, pcidev_list |
||
94 | .scan_ehci: |
||
95 | mov eax, [eax+PCIDEV.fd] |
||
96 | cmp eax, pcidev_list |
||
97 | jz .done_ehci |
||
98 | cmp [eax+PCIDEV.class], 0x0C0320 |
||
99 | jnz .scan_ehci |
||
100 | mov edi, ehci_hardware_func |
||
101 | call usb_init_controller |
||
102 | jmp .scan_ehci |
||
103 | .done_ehci: |
||
104 | ; 4b. Loop over all PCI devices, call usb_init_controller |
||
105 | ; for all UHCI and OHCI controllers. |
||
106 | mov eax, pcidev_list |
||
107 | .scan_usb1: |
||
108 | mov eax, [eax+PCIDEV.fd] |
||
109 | cmp eax, pcidev_list |
||
110 | jz .done_usb1 |
||
111 | mov edi, uhci_hardware_func |
||
112 | cmp [eax+PCIDEV.class], 0x0C0300 |
||
113 | jz @f |
||
114 | mov edi, ohci_hardware_func |
||
115 | cmp [eax+PCIDEV.class], 0x0C0310 |
||
116 | jnz .scan_usb1 |
||
117 | @@: |
||
118 | call usb_init_controller |
||
119 | jmp .scan_usb1 |
||
120 | .done_usb1: |
||
121 | .nothing: |
||
122 | ret |
||
123 | endp |
||
124 | |||
125 | uglobal |
||
126 | align 4 |
||
127 | usb_event dd ? |
||
128 | endg |
||
129 | |||
130 | ; Helper function for usb_init. Creates and initializes the USB thread. |
||
131 | proc create_usb_thread |
||
132 | ; 1. Create the thread. |
||
133 | push edi |
||
3598 | clevermous | 134 | movi ebx, 1 |
3520 | clevermous | 135 | mov ecx, usb_thread_proc |
136 | xor edx, edx |
||
137 | call new_sys_threads |
||
138 | pop edi |
||
139 | ; If failed, say something to the debug board and return with ZF set. |
||
140 | test eax, eax |
||
141 | jns @f |
||
142 | DEBUGF 1,'K : cannot create kernel thread for USB, error %d\n',eax |
||
143 | .clear: |
||
144 | xor eax, eax |
||
145 | jmp .nothing |
||
146 | @@: |
||
147 | ; 2. Wait while the USB thread initializes itself. |
||
148 | @@: |
||
149 | call change_task |
||
150 | cmp [usb_event], 0 |
||
151 | jz @b |
||
152 | ; 3. If initialization failed, the USB thread sets [usb_event] to -1. |
||
153 | ; Return with ZF set or cleared corresponding to the result. |
||
154 | cmp [usb_event], -1 |
||
155 | jz .clear |
||
156 | .nothing: |
||
157 | ret |
||
158 | endp |
||
159 | |||
160 | ; Helper function for IRQ handlers. Wakes the USB thread if ebx is nonzero. |
||
161 | proc usb_wakeup_if_needed |
||
162 | test ebx, ebx |
||
163 | jz usb_wakeup.nothing |
||
164 | usb_wakeup: |
||
165 | xor edx, edx |
||
166 | mov eax, [usb_event] |
||
167 | mov ebx, [eax+EVENT.id] |
||
168 | xor esi, esi |
||
169 | call raise_event |
||
170 | .nothing: |
||
171 | ret |
||
172 | endp |
||
173 | |||
174 | ; Main loop of the USB thread. |
||
175 | proc usb_thread_proc |
||
176 | ; 1. Initialize: create event to allow wakeup by interrupt handlers. |
||
177 | xor esi, esi |
||
178 | mov ecx, MANUAL_DESTROY |
||
179 | call create_event |
||
180 | test eax, eax |
||
181 | jnz @f |
||
182 | ; If failed, set [usb_event] to -1 and terminate myself. |
||
183 | dbgstr 'cannot create event for USB thread' |
||
184 | or [usb_event], -1 |
||
185 | jmp sys_end |
||
186 | @@: |
||
187 | mov [usb_event], eax |
||
188 | push -1 ; initial timeout: infinite |
||
189 | usb_thread_wait: |
||
190 | ; 2. Main loop: wait for either wakeup event or timeout. |
||
191 | pop ecx ; get timeout |
||
192 | mov eax, [usb_event] |
||
193 | mov ebx, [eax+EVENT.id] |
||
194 | call wait_event_timeout |
||
195 | push -1 ; default timeout: infinite |
||
196 | ; 3. Main loop: call worker functions of all controllers; |
||
197 | ; if some function schedules wakeup in timeout less than the current value, |
||
198 | ; replace that value with the returned timeout. |
||
199 | mov esi, usb_controllers_list |
||
200 | @@: |
||
201 | mov esi, [esi+usb_controller.Next] |
||
202 | cmp esi, usb_controllers_list |
||
203 | jz .controllers_done |
||
204 | mov eax, [esi+usb_controller.HardwareFunc] |
||
205 | call [eax+usb_hardware_func.ProcessDeferred] |
||
206 | cmp [esp], eax |
||
207 | jb @b |
||
208 | mov [esp], eax |
||
209 | jmp @b |
||
210 | .controllers_done: |
||
211 | ; 4. Main loop: call hub worker function for all hubs, |
||
212 | ; similarly calculating minimum of all returned timeouts. |
||
213 | ; When done, continue to 2. |
||
214 | mov esi, usb_hubs_list |
||
215 | @@: |
||
216 | mov esi, [esi+usb_hub.Next] |
||
217 | cmp esi, usb_hubs_list |
||
218 | jz usb_thread_wait |
||
219 | call usb_hub_process_deferred |
||
220 | cmp [esp], eax |
||
221 | jb @b |
||
222 | mov [esp], eax |
||
223 | jmp @b |
||
224 | endp |
||
225 | |||
226 | iglobal |
||
227 | align 4 |
||
228 | usb_controllers_list: |
||
229 | dd usb_controllers_list |
||
230 | dd usb_controllers_list |
||
231 | usb_hubs_list: |
||
232 | dd usb_hubs_list |
||
233 | dd usb_hubs_list |
||
234 | endg |
||
235 | uglobal |
||
236 | align 4 |
||
237 | usb_controllers_list_mutex MUTEX |
||
238 | endg |
||
239 | |||
240 | include "memory.inc" |
||
241 | include "hccommon.inc" |
||
242 | include "pipe.inc" |
||
243 | include "ohci.inc" |
||
244 | include "uhci.inc" |
||
245 | include "ehci.inc" |
||
246 | include "protocol.inc" |
||
247 | include "hub.inc" |
||
248 | include "scheduler.inc" |