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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since df6ded8 was e0a5d4c, checked in by Ondřej Hlavatý <aearsis@…>, 7 years ago

usb: update copyrights

The data was generated by a script, guided manually. If you feel your
name is missing somewhere, please add it!

The semi-automated process was roughly:

1) Changes per file and author (limited to our team) were counted
2) Trivial numbers were thrown away
3) Authors were sorted by lines added to file
4) All previous copyrights were replaced by the newly generated one
5) Hunks changing only year were discarded

It seems that a lot of my copyrights were added. It is due to me being
both sticking my nose everywhere and lazy to update the copyright right
away :)

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