source: mainline/uspace/drv/bus/usb/ohci/ohci_rh.c@ c91db2a

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since c91db2a was c91db2a, checked in by Jan Vesely <jano.vesely@…>, 13 years ago

ohci: Add new root hub implementation.

Uses libusbvirt.

  • Property mode set to 100644
File size: 16.0 KB
Line 
1/*
2 * Copyright (c) 2013 Jan Vesely
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28/** @addtogroup drvusbohci
29 * @{
30 */
31/** @file
32 * @brief OHCI driver
33 */
34#include <assert.h>
35#include <usb/debug.h>
36
37#include "ohci_rh.h"
38
39enum {
40 HUB_STATUS_CHANGE_PIPE = 1,
41};
42
43static usbvirt_device_ops_t ops;
44
45static void ohci_rh_hub_desc_init(ohci_rh_t *instance)
46{
47 assert(instance);
48 const unsigned dsize = sizeof(usb_hub_descriptor_header_t) +
49 STATUS_BYTES(instance->port_count);
50 assert(dsize <= sizeof(instance->hub_descriptor));
51 const uint32_t hub_desc = OHCI_RD(instance->registers->rh_desc_a);
52 const uint32_t port_desc = OHCI_RD(instance->registers->rh_desc_b);
53
54 instance->hub_descriptor.header.length = dsize;
55 instance->hub_descriptor.header.descriptor_type = USB_DESCTYPE_HUB;
56 instance->hub_descriptor.header.port_count = instance->port_count;
57 instance->hub_descriptor.header.characteristics = 0 |
58 /* Bits 0,1 indicate power switching mode */
59 ((hub_desc & RHDA_PSM_FLAG) ? 0x01 : 0) |
60 ((hub_desc & RHDA_NPS_FLAG) ? 0x02 : 0) |
61 /* Bit 2 indicates device type (compound device) */
62 ((hub_desc & RHDA_DT_FLAG) ? 0x04 : 0) |
63 /* Bits 3,4 indicate over-current protection mode */
64 ((hub_desc & RHDA_OCPM_FLAG) ? 0x08 : 0) |
65 ((hub_desc & RHDA_NOCP_FLAG) ? 0x10 : 0);
66 instance->hub_descriptor.header.power_good_time =
67 hub_desc >> RHDA_POTPGT_SHIFT;
68 /* bHubContrCurrent, root hubs don't need no power. */
69 instance->hub_descriptor.header.max_current = 0;
70
71 /* Device Removable and some legacy 1.0 stuff*/
72 instance->hub_descriptor.rempow[0] =
73 (port_desc >> RHDB_DR_SHIFT) & 0xff;
74 if (STATUS_BYTES(instance->port_count) == 1) {
75 instance->hub_descriptor.rempow[1] = 0xff;
76 } else {
77 instance->hub_descriptor.rempow[1] =
78 ((port_desc >> RHDB_DR_SHIFT) >> 8) & 0xff;
79 }
80
81 instance->hub_descriptor.rempow[2] = 0xff;
82 instance->hub_descriptor.rempow[3] = 0xff;
83
84}
85
86int ohci_rh_init(ohci_rh_t *instance, ohci_regs_t *regs, const char *name)
87{
88 assert(instance);
89 instance->registers = regs;
90 instance->port_count = OHCI_RD(regs->rh_desc_a) & RHDA_NDS_MASK;
91 usb_log_debug2("rh_desc_a: %x.\n", OHCI_RD(regs->rh_desc_a));
92 if (instance->port_count > OHCI_MAX_PORTS) {
93 usb_log_warning("OHCI specification does not allow %d ports. "
94 "Max %d ports will be used.\n", instance->port_count,
95 OHCI_MAX_PORTS);
96 instance->port_count = OHCI_MAX_PORTS;
97 }
98 usb_log_info("%s: Found %u ports.\n", name, instance->port_count);
99
100#if defined OHCI_POWER_SWITCH_no
101 usb_log_info("%s: Set power mode to no power switching.\n", name);
102 /* Set port power mode to no power-switching. (always on) */
103 OHCI_SET(regs->rh_desc_a, RHDA_NPS_FLAG);
104
105 /* Set to no over-current reporting */
106 OHCI_SET(regs->rh_desc_a, RHDA_NOCP_FLAG);
107
108#elif defined OHCI_POWER_SWITCH_ganged
109 usb_log_info("%s: Set power mode to ganged power switching.\n", name);
110 /* Set port power mode to ganged power-switching. */
111 OHCI_CLR(regs->rh_desc_a, RHDA_NPS_FLAG);
112 OHCI_CLR(regs->rh_desc_a, RHDA_PSM_FLAG);
113
114 /* Turn off power (hub driver will turn this back on)*/
115 OHCI_WR(regs->rh_status, RHS_CLEAR_GLOBAL_POWER);
116
117 /* Set to global over-current */
118 OHCI_CLR(regs->rh_desc_a, RHDA_NOCP_FLAG);
119 OHCI_CLR(regs->rh_desc_a, RHDA_OCPM_FLAG);
120#else
121 usb_log_info("%s: Set power mode to per-port power switching.\n", name);
122 /* Set port power mode to per port power-switching. */
123 OHCI_CLR(regs->rh_desc_a, RHDA_NPS_FLAG);
124 OHCI_SET(regs->rh_desc_a, RHDA_PSM_FLAG);
125
126 /* Control all ports by global switch and turn them off */
127 OHCI_CLR(regs->rh_desc_b, RHDB_PCC_MASK << RHDB_PCC_SHIFT);
128 OHCI_WR(regs->rh_status, RHS_CLEAR_GLOBAL_POWER);
129
130 /* Return control to per port state */
131 OHCI_SET(regs->rh_desc_b, RHDB_PCC_MASK << RHDB_PCC_SHIFT);
132
133 /* Set per port over-current */
134 OHCI_CLR(regs->rh_desc_a, RHDA_NOCP_FLAG);
135 OHCI_SET(regs->rh_desc_a, RHDA_OCPM_FLAG);
136#endif
137
138 ohci_rh_hub_desc_init(instance);
139 return virthub_base_init(&instance->base, name, &ops, instance,
140 NULL, &instance->hub_descriptor.header, HUB_STATUS_CHANGE_PIPE);
141}
142
143int ohci_rh_schedule(ohci_rh_t *instance, usb_transfer_batch_t *batch)
144{
145 assert(instance);
146 assert(batch);
147 const usb_target_t target = {{
148 .address = batch->ep->address,
149 .endpoint = batch->ep->endpoint,
150 }};
151 batch->error = virthub_base_request(&instance->base, target,
152 usb_transfer_batch_direction(batch), (void*)batch->setup_buffer,
153 batch->buffer, batch->buffer_size, &batch->transfered_size);
154 if (batch->error == ENAK) {
155 /* This is safe because only status change interrupt transfers
156 * return NAK. The assertion holds tru because the batch
157 * existence prevents communication with that ep */
158 assert(instance->unfinished_interrupt_transfer == NULL);
159 instance->unfinished_interrupt_transfer = batch;
160 } else {
161 usb_transfer_batch_finish(batch, NULL);
162 usb_transfer_batch_destroy(batch);
163 }
164 return EOK;
165}
166
167int ohci_rh_interrupt(ohci_rh_t *instance)
168{
169 usb_transfer_batch_t *batch = instance->unfinished_interrupt_transfer;
170 instance->unfinished_interrupt_transfer = NULL;
171 if (batch) {
172 const usb_target_t target = {{
173 .address = batch->ep->address,
174 .endpoint = batch->ep->endpoint,
175 }};
176 batch->error = virthub_base_request(&instance->base, target,
177 usb_transfer_batch_direction(batch),
178 (void*)batch->setup_buffer,
179 batch->buffer, batch->buffer_size, &batch->transfered_size);
180 usb_transfer_batch_finish(batch, NULL);
181 usb_transfer_batch_destroy(batch);
182 }
183 return EOK;
184}
185
186/* HUB ROUTINES IMPLEMENTATION */
187#define TEST_SIZE_INIT(size, port, hub) \
188do { \
189 hub = virthub_get_data(device); \
190 assert(hub);\
191 if (uint16_usb2host(setup_packet->length) != size) \
192 return ESTALL; \
193 port = uint16_usb2host(setup_packet->index) - 1; \
194 if (port > hub->port_count) \
195 return EINVAL; \
196} while (0)
197
198/** Hub status request handler.
199 * @param device Virtual hub device
200 * @param setup_packet USB setup stage data.
201 * @param[out] data destination data buffer, size must be at least
202 * setup_packet->length bytes
203 * @param[out] act_size Sized of the valid response part of the buffer.
204 * @return Error code.
205 */
206static int req_get_status(usbvirt_device_t *device,
207 const usb_device_request_setup_packet_t *setup_packet,
208 uint8_t *data, size_t *act_size)
209{
210 ohci_rh_t *hub = virthub_get_data(device);
211 assert(hub);
212 if (uint16_usb2host(setup_packet->length) != 4)
213 return ESTALL;
214 const uint32_t val = OHCI_RD(hub->registers->rh_status) &
215 (RHS_LPS_FLAG | RHS_LPSC_FLAG | RHS_OCI_FLAG | RHS_OCIC_FLAG);
216 memcpy(data, &val, sizeof(val));
217 *act_size = sizeof(val);
218 return EOK;
219}
220
221/** Hub set feature request handler.
222 * @param device Virtual hub device
223 * @param setup_packet USB setup stage data.
224 * @param[out] data destination data buffer, size must be at least
225 * setup_packet->length bytes
226 * @param[out] act_size Sized of the valid response part of the buffer.
227 * @return Error code.
228 */
229static int req_clear_hub_feature(usbvirt_device_t *device,
230 const usb_device_request_setup_packet_t *setup_packet,
231 uint8_t *data, size_t *act_size)
232{
233 ohci_rh_t *hub = virthub_get_data(device);
234 assert(hub);
235
236 /*
237 * Chapter 11.16.2 specifies that only C_HUB_LOCAL_POWER and
238 * C_HUB_OVER_CURRENT are supported.
239 * C_HUB_LOCAL_POWER is not supported
240 * because root hubs do not support local power status feature.
241 * C_HUB_OVER_CURRENT is represented by OHCI RHS_OCIC_FLAG.
242 * (OHCI pg. 127)
243 */
244 const unsigned feature = uint16_usb2host(setup_packet->value);
245 if (feature == USB_HUB_FEATURE_C_HUB_OVER_CURRENT) {
246 OHCI_WR(hub->registers->rh_status, RHS_OCIC_FLAG);
247 }
248 return EOK;
249}
250
251/** Port status request handler.
252 * @param device Virtual hub device
253 * @param setup_packet USB setup stage data.
254 * @param[out] data destination data buffer, size must be at least
255 * setup_packet->length bytes
256 * @param[out] act_size Sized of the valid response part of the buffer.
257 * @return Error code.
258 */
259static int req_get_port_status(usbvirt_device_t *device,
260 const usb_device_request_setup_packet_t *setup_packet,
261 uint8_t *data, size_t *act_size)
262{
263 ohci_rh_t *hub;
264 unsigned port;
265 TEST_SIZE_INIT(4, port, hub);
266 if (setup_packet->value != 0)
267 return EINVAL;
268
269 const uint32_t status = OHCI_RD(hub->registers->rh_port_status[port]);
270 memcpy(data, &status, sizeof(status));
271 *act_size = sizeof(status);
272 return EOK;
273}
274
275/** Port clear feature request handler.
276 * @param device Virtual hub device
277 * @param setup_packet USB setup stage data.
278 * @param[out] data destination data buffer, size must be at least
279 * setup_packet->length bytes
280 * @param[out] act_size Sized of the valid response part of the buffer.
281 * @return Error code.
282 */
283static int req_clear_port_feature(usbvirt_device_t *device,
284 const usb_device_request_setup_packet_t *setup_packet,
285 uint8_t *data, size_t *act_size)
286{
287 ohci_rh_t *hub;
288 unsigned port;
289 TEST_SIZE_INIT(0, port, hub);
290 const unsigned feature = uint16_usb2host(setup_packet->value);
291 /* Enabled features to clear: see page 269 of USB specs */
292 switch (feature)
293 {
294 case USB_HUB_FEATURE_PORT_POWER: /*8*/
295 {
296 const uint32_t rhda =
297 OHCI_RD(hub->registers->rh_desc_a);
298 /* No power switching */
299 if (rhda & RHDA_NPS_FLAG)
300 return ENOTSUP;
301 /* Ganged power switching, one port powers all */
302 if (!(rhda & RHDA_PSM_FLAG)) {
303 OHCI_WR(hub->registers->rh_status,
304 RHS_CLEAR_GLOBAL_POWER);
305 return EOK;
306 }
307 OHCI_WR(hub->registers->rh_port_status[port],
308 RHPS_CLEAR_PORT_POWER);
309 return EOK;
310 }
311
312 case USB_HUB_FEATURE_PORT_ENABLE: /*1*/
313 OHCI_WR(hub->registers->rh_port_status[port],
314 RHPS_CLEAR_PORT_ENABLE);
315 return EOK;
316
317 case USB_HUB_FEATURE_PORT_SUSPEND: /*2*/
318 OHCI_WR(hub->registers->rh_port_status[port],
319 RHPS_CLEAR_PORT_SUSPEND);
320 return EOK;
321
322 case USB_HUB_FEATURE_C_PORT_CONNECTION: /*16*/
323 case USB_HUB_FEATURE_C_PORT_ENABLE: /*17*/
324 case USB_HUB_FEATURE_C_PORT_SUSPEND: /*18*/
325 case USB_HUB_FEATURE_C_PORT_OVER_CURRENT: /*19*/
326 case USB_HUB_FEATURE_C_PORT_RESET: /*20*/
327 usb_log_debug2("Clearing port C_CONNECTION, C_ENABLE, "
328 "C_SUSPEND, C_OC or C_RESET on port %"PRIu16".\n", port);
329 /* Bit offsets correspond to the feature number */
330 OHCI_WR(hub->registers->rh_port_status[port],
331 1 << feature);
332 return EOK;
333
334 default:
335 return ENOTSUP;
336 }
337}
338
339/** Port set feature request handler.
340 * @param device Virtual hub device
341 * @param setup_packet USB setup stage data.
342 * @param[out] data destination data buffer, size must be at least
343 * setup_packet->length bytes
344 * @param[out] act_size Sized of the valid response part of the buffer.
345 * @return Error code.
346 */
347static int req_set_port_feature(usbvirt_device_t *device,
348 const usb_device_request_setup_packet_t *setup_packet,
349 uint8_t *data, size_t *act_size)
350{
351 ohci_rh_t *hub;
352 unsigned port;
353 TEST_SIZE_INIT(0, port, hub);
354 const unsigned feature = uint16_usb2host(setup_packet->value);
355 switch (feature) {
356 case USB_HUB_FEATURE_PORT_POWER: /*8*/
357 {
358 const uint32_t rhda = OHCI_RD(hub->registers->rh_desc_a);
359 /* No power switching */
360 if (rhda & RHDA_NPS_FLAG)
361 return EOK;
362 /* Ganged power switching, one port powers all */
363 if (!(rhda & RHDA_PSM_FLAG)) {
364 OHCI_WR(hub->registers->rh_status,RHS_SET_GLOBAL_POWER);
365 return EOK;
366 }
367 }
368 /* Fall through, for per port power */
369 case USB_HUB_FEATURE_PORT_ENABLE: /*1*/
370 case USB_HUB_FEATURE_PORT_SUSPEND: /*2*/
371 case USB_HUB_FEATURE_PORT_RESET: /*4*/
372 usb_log_debug2("Setting port POWER, ENABLE, SUSPEND or RESET "
373 "on port %"PRIu16".\n", port);
374 /* Bit offsets correspond to the feature number */
375 OHCI_WR(hub->registers->rh_port_status[port], 1 << feature);
376 return EOK;
377 default:
378 return ENOTSUP;
379 }
380}
381
382/** Status change handler.
383 * @param device Virtual hub device
384 * @param endpoint Endpoint number
385 * @param tr_type Transfer type
386 * @param buffer Response destination
387 * @param buffer_size Bytes available in buffer
388 * @param actual_size Size us the used part of the dest buffer.
389 *
390 * Produces status mask. Bit 0 indicates hub status change the other bits
391 * represent port status change. Endian does not matter as UHCI root hubs
392 * only need 1 byte.
393 */
394static int req_status_change_handler(usbvirt_device_t *device,
395 usb_endpoint_t endpoint, usb_transfer_type_t tr_type,
396 void *buffer, size_t buffer_size, size_t *actual_size)
397{
398 ohci_rh_t *hub = virthub_get_data(device);
399 assert(hub);
400
401 if (buffer_size < STATUS_BYTES(hub->port_count))
402 return ESTALL;
403
404 uint16_t mask = 0;
405
406 /* Only local power source change and over-current change can happen */
407 if (OHCI_RD(hub->registers->rh_status) & (RHS_LPSC_FLAG | RHS_OCIC_FLAG)) {
408 mask |= 1;
409 }
410
411 for (unsigned port = 1; port <= hub->port_count; ++port) {
412 /* Write-clean bits are those that indicate change */
413 if (OHCI_RD(hub->registers->rh_port_status[port - 1])
414 & RHPS_CHANGE_WC_MASK) {
415 mask |= (1 << port);
416 }
417 }
418
419 usb_log_debug2("OHCI root hub interrupt mask: %hx.\n", mask);
420
421 if (mask == 0)
422 return ENAK;
423 mask = uint16_host2usb(mask);
424 memcpy(buffer, &mask, STATUS_BYTES(hub->port_count));
425 *actual_size = STATUS_BYTES(hub->port_count);
426 return EOK;
427}
428
429/** OHCI root hub request handlers */
430static const usbvirt_control_request_handler_t control_transfer_handlers[] = {
431 {
432 STD_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
433 .name = "GetDescriptor",
434 .callback = virthub_base_get_hub_descriptor,
435 },
436 {
437 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
438 .name = "GetDescriptor",
439 .callback = virthub_base_get_hub_descriptor,
440 },
441 {
442 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_DESCRIPTOR),
443 .name = "GetHubDescriptor",
444 .callback = virthub_base_get_hub_descriptor,
445 },
446 {
447 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
448 .name = "GetPortStatus",
449 .callback = req_get_port_status,
450 },
451 {
452 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_CLEAR_FEATURE),
453 .name = "ClearHubFeature",
454 .callback = req_clear_hub_feature,
455 },
456 {
457 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_CLEAR_FEATURE),
458 .name = "ClearPortFeature",
459 .callback = req_clear_port_feature,
460 },
461 {
462 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_STATUS),
463 .name = "GetHubStatus",
464 .callback = req_get_status,
465 },
466 {
467 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
468 .name = "GetPortStatus",
469 .callback = req_get_port_status,
470 },
471 {
472 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_SET_FEATURE),
473 .name = "SetHubFeature",
474 .callback = req_nop,
475 },
476 {
477 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_SET_FEATURE),
478 .name = "SetPortFeature",
479 .callback = req_set_port_feature,
480 },
481 {
482 .callback = NULL
483 }
484};
485
486static usbvirt_device_ops_t ops = {
487 .control = control_transfer_handlers,
488 .data_in[HUB_STATUS_CHANGE_PIPE] = req_status_change_handler,
489};
Note: See TracBrowser for help on using the repository browser.