source: mainline/uspace/drv/bus/usb/uhci/uhci_rh.c@ 99013b84

Last change on this file since 99013b84 was 3fafe5e0, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix incorrectly indented double-slash comments.

  • Property mode set to 100644
File size: 16.5 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
30#include <assert.h>
31#include <async.h>
32#include <ddi.h>
33#include <errno.h>
34#include <macros.h>
35#include <mem.h>
36#include <sys/time.h>
37
38#include <usb/debug.h>
39#include <usb/descriptor.h>
40#include <usb/classes/hub.h>
41#include <usb/request.h>
42#include <usb/host/endpoint.h>
43#include <usb/usb.h>
44
45#include "uhci_rh.h"
46
47enum {
48 UHCI_RH_PORT_COUNT = 2,
49 UHCI_PORT_BYTES = STATUS_BYTES(UHCI_RH_PORT_COUNT),
50};
51
52/** Hub descriptor. */
53static const struct {
54 /** Common hub descriptor header */
55 usb_hub_descriptor_header_t header;
56 /** Port removable status bits */
57 uint8_t removable[UHCI_PORT_BYTES];
58 /** Port powered status bits */
59 uint8_t powered[UHCI_PORT_BYTES];
60} __attribute__((packed)) hub_descriptor = {
61 .header = {
62 .length = sizeof(hub_descriptor),
63 .descriptor_type = USB_DESCTYPE_HUB,
64 .port_count = UHCI_RH_PORT_COUNT,
65 .characteristics =
66 HUB_CHAR_NO_POWER_SWITCH_FLAG | HUB_CHAR_NO_OC_FLAG,
67 .power_good_time = 50,
68 .max_current = 0,
69 },
70 .removable = { 0 },
71 .powered = { 0xFF },
72};
73
74static usbvirt_device_ops_t ops;
75
76/** Initialize uhci rh structure.
77 * @param instance Memory place to initialize.
78 * @param ports Pointer to TWO UHCI RH port registers.
79 * @param name device name, passed to virthub init
80 * @return Error code, EOK on success.
81 */
82errno_t uhci_rh_init(uhci_rh_t *instance, ioport16_t *ports, const char *name)
83{
84 assert(instance);
85 instance->ports[0] = ports;
86 instance->ports[1] = ports + 1;
87 instance->reset_changed[0] = false;
88 instance->reset_changed[1] = false;
89 return virthub_base_init(&instance->base, name, &ops, instance,
90 NULL, &hub_descriptor.header, HUB_STATUS_CHANGE_PIPE);
91}
92
93/** Schedule USB batch for the root hub.
94 *
95 * @param instance UHCI rh instance
96 * @param batch USB communication batch
97 * @return EOK.
98 *
99 * The result of scheduling is always EOK. The result of communication does
100 * not have to be.
101 */
102errno_t uhci_rh_schedule(uhci_rh_t *instance, usb_transfer_batch_t *batch)
103{
104 assert(instance);
105 assert(batch);
106
107 do {
108 batch->error = virthub_base_request(&instance->base, batch->target,
109 batch->dir, (void *) batch->setup.buffer,
110 batch->dma_buffer.virt, batch->size, &batch->transferred_size);
111 if (batch->error == ENAK)
112 async_usleep(instance->base.endpoint_descriptor.poll_interval * 1000);
113 //TODO This is flimsy, but we can't exit early because
114 //ENAK is technically an error condition
115 } while (batch->error == ENAK);
116 usb_transfer_batch_finish(batch);
117 return EOK;
118}
119
120/** UHCI port register bits */
121enum {
122 STATUS_CONNECTED = (1 << 0),
123 STATUS_CONNECTED_CHANGED = (1 << 1),
124 STATUS_ENABLED = (1 << 2),
125 STATUS_ENABLED_CHANGED = (1 << 3),
126 STATUS_LINE_D_PLUS = (1 << 4),
127 STATUS_LINE_D_MINUS = (1 << 5),
128 STATUS_RESUME = (1 << 6),
129 STATUS_ALWAYS_ONE = (1 << 7),
130
131 STATUS_LOW_SPEED = (1 << 8),
132 STATUS_IN_RESET = (1 << 9),
133 STATUS_SUSPEND = (1 << 12),
134
135 STATUS_CHANGE_BITS = STATUS_CONNECTED_CHANGED | STATUS_ENABLED_CHANGED,
136 STATUS_WC_BITS = STATUS_CHANGE_BITS,
137};
138
139/* HUB ROUTINES IMPLEMENTATION */
140
141static void uhci_port_reset_enable(ioport16_t *port)
142{
143 assert(port);
144 uint16_t port_status = pio_read_16(port);
145 /* We don't wan to remove changes, that's hub drivers work */
146 port_status &= ~STATUS_WC_BITS;
147 port_status |= STATUS_IN_RESET;
148 pio_write_16(port, port_status);
149 async_usleep(50000);
150 port_status = pio_read_16(port);
151 port_status &= ~STATUS_IN_RESET;
152 pio_write_16(port, port_status);
153 while ((port_status = pio_read_16(port)) & STATUS_IN_RESET)
154 ;
155 /* PIO delay, should not be longer than 3ms as the device might
156 * enter suspend state. */
157 udelay(10);
158 /* Drop ConnectionChange as some UHCI hw
159 * sets this bit after reset, that is incorrect */
160 port_status &= ~STATUS_WC_BITS;
161 pio_write_16(port, port_status | STATUS_ENABLED | STATUS_CONNECTED_CHANGED);
162}
163#define TEST_SIZE_INIT(size, port, hub) \
164do { \
165 if (uint16_usb2host(setup_packet->length) != size) \
166 return ESTALL; \
167 port = uint16_usb2host(setup_packet->index) - 1; \
168 if (port != 0 && port != 1) \
169 return EINVAL; \
170 hub = virthub_get_data(device); \
171 assert(hub);\
172} while (0)
173
174#define RH_DEBUG(hub, port, msg, ...) \
175do { \
176 if ((int)port >= 0) \
177 usb_log_debug("RH(%p-%d): " msg, hub, port, ##__VA_ARGS__); \
178 else \
179 usb_log_debug("RH(%p):" msg, hub, ##__VA_ARGS__); \
180} while (0)
181
182/** USB HUB port state request handler.
183 * @param device Virtual hub device
184 * @param setup_packet USB setup stage data.
185 * @param[out] data destination data buffer, size must be at least
186 * setup_packet->length bytes
187 * @param[out] act_size Sized of the valid response part of the buffer.
188 * @return Error code.
189 *
190 * Do not confuse with port status. Port state reports data line states,
191 * it is usefull for debuging purposes only.
192 */
193static errno_t req_get_port_state(usbvirt_device_t *device,
194 const usb_device_request_setup_packet_t *setup_packet,
195 uint8_t *data, size_t *act_size)
196{
197 uhci_rh_t *hub;
198 unsigned port;
199 TEST_SIZE_INIT(1, port, hub);
200 if (setup_packet->value != 0)
201 return EINVAL;
202
203 const uint16_t value = pio_read_16(hub->ports[port]);
204 data[0] = ((value & STATUS_LINE_D_MINUS) ? 1 : 0) |
205 ((value & STATUS_LINE_D_PLUS) ? 2 : 0);
206 RH_DEBUG(hub, port, "Bus state %" PRIx8 "(source %" PRIx16 ")",
207 data[0], value);
208 *act_size = 1;
209 return EOK;
210}
211
212#define BIT_VAL(val, bit) \
213 ((val & bit) ? 1 : 0)
214#define UHCI2USB(val, bit, mask) \
215 (BIT_VAL(val, bit) ? (mask) : 0)
216
217/** Port status request handler.
218 * @param device Virtual hub device
219 * @param setup_packet USB setup stage data.
220 * @param[out] data destination data buffer, size must be at least
221 * setup_packet->length bytes
222 * @param[out] act_size Sized of the valid response part of the buffer.
223 * @return Error code.
224 *
225 * Converts status reported via ioport to USB format.
226 * @note: reset change status needs to be handled in sw.
227 */
228static errno_t req_get_port_status(usbvirt_device_t *device,
229 const usb_device_request_setup_packet_t *setup_packet,
230 uint8_t *data, size_t *act_size)
231{
232 uhci_rh_t *hub;
233 unsigned port;
234 TEST_SIZE_INIT(4, port, hub);
235 if (setup_packet->value != 0)
236 return EINVAL;
237
238 const uint16_t val = pio_read_16(hub->ports[port]);
239 const uint32_t status = uint32_host2usb(
240 UHCI2USB(val, STATUS_CONNECTED, USB_HUB_PORT_STATUS_CONNECTION) |
241 UHCI2USB(val, STATUS_ENABLED, USB_HUB_PORT_STATUS_ENABLE) |
242 UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_SUSPEND) |
243 UHCI2USB(val, STATUS_IN_RESET, USB_HUB_PORT_STATUS_RESET) |
244 UHCI2USB(val, STATUS_ALWAYS_ONE, USB2_HUB_PORT_STATUS_POWER) |
245 UHCI2USB(val, STATUS_LOW_SPEED, USB2_HUB_PORT_STATUS_LOW_SPEED) |
246 UHCI2USB(val, STATUS_CONNECTED_CHANGED, USB_HUB_PORT_STATUS_C_CONNECTION) |
247 UHCI2USB(val, STATUS_ENABLED_CHANGED, USB2_HUB_PORT_STATUS_C_ENABLE) |
248#if 0
249 UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_C_SUSPEND) |
250#endif
251 (hub->reset_changed[port] ? USB_HUB_PORT_STATUS_C_RESET : 0));
252 RH_DEBUG(hub, port, "Port status %" PRIx32 " (source %" PRIx16
253 "%s)", uint32_usb2host(status), val,
254 hub->reset_changed[port] ? "-reset" : "");
255 memcpy(data, &status, sizeof(status));
256 *act_size = sizeof(status);
257 return EOK;
258}
259
260/** Port clear feature request handler.
261 * @param device Virtual hub device
262 * @param setup_packet USB setup stage data.
263 * @param[out] data destination data buffer, size must be at least
264 * setup_packet->length bytes
265 * @param[out] act_size Sized of the valid response part of the buffer.
266 * @return Error code.
267 */
268static errno_t req_clear_port_feature(usbvirt_device_t *device,
269 const usb_device_request_setup_packet_t *setup_packet,
270 uint8_t *data, size_t *act_size)
271{
272 uhci_rh_t *hub;
273 unsigned port;
274 TEST_SIZE_INIT(0, port, hub);
275 const unsigned feature = uint16_usb2host(setup_packet->value);
276 const uint16_t status = pio_read_16(hub->ports[port]);
277 const uint16_t val = status & (~STATUS_WC_BITS);
278 switch (feature) {
279 case USB2_HUB_FEATURE_PORT_ENABLE:
280 RH_DEBUG(hub, port, "Clear port enable (status %"
281 PRIx16 ")", status);
282 pio_write_16(hub->ports[port], val & ~STATUS_ENABLED);
283 break;
284 case USB2_HUB_FEATURE_PORT_SUSPEND:
285 RH_DEBUG(hub, port, "Clear port suspend (status %"
286 PRIx16 ")", status);
287 pio_write_16(hub->ports[port], val & ~STATUS_SUSPEND);
288 // TODO we should do resume magic
289 usb_log_warning("Resume is not implemented on port %u", port);
290 break;
291 case USB_HUB_FEATURE_PORT_POWER:
292 RH_DEBUG(hub, port, "Clear port power (status %" PRIx16 ")",
293 status);
294 /* We are always powered */
295 usb_log_warning("Tried to power off port %u", port);
296 break;
297 case USB_HUB_FEATURE_C_PORT_CONNECTION:
298 RH_DEBUG(hub, port, "Clear port conn change (status %"
299 PRIx16 ")", status);
300 pio_write_16(hub->ports[port], val | STATUS_CONNECTED_CHANGED);
301 break;
302 case USB_HUB_FEATURE_C_PORT_RESET:
303 RH_DEBUG(hub, port, "Clear port reset change (status %"
304 PRIx16 ")", status);
305 hub->reset_changed[port] = false;
306 break;
307 case USB2_HUB_FEATURE_C_PORT_ENABLE:
308 RH_DEBUG(hub, port, "Clear port enable change (status %"
309 PRIx16 ")", status);
310 pio_write_16(hub->ports[port], status | STATUS_ENABLED_CHANGED);
311 break;
312 case USB2_HUB_FEATURE_C_PORT_SUSPEND:
313 RH_DEBUG(hub, port, "Clear port suspend change (status %"
314 PRIx16 ")", status);
315 //TODO
316 return ENOTSUP;
317 case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
318 RH_DEBUG(hub, port, "Clear port OC change (status %"
319 PRIx16 ")", status);
320 /* UHCI Does not report over current */
321 //TODO: newer chips do, but some have broken wiring
322 break;
323 default:
324 RH_DEBUG(hub, port, "Clear unknown feature %d (status %"
325 PRIx16 ")", feature, status);
326 usb_log_warning("Clearing feature %d is unsupported",
327 feature);
328 return ESTALL;
329 }
330 return EOK;
331}
332
333/** Port set feature request handler.
334 * @param device Virtual hub device
335 * @param setup_packet USB setup stage data.
336 * @param[out] data destination data buffer, size must be at least
337 * setup_packet->length bytes
338 * @param[out] act_size Sized of the valid response part of the buffer.
339 * @return Error code.
340 */
341static errno_t req_set_port_feature(usbvirt_device_t *device,
342 const usb_device_request_setup_packet_t *setup_packet,
343 uint8_t *data, size_t *act_size)
344{
345 uhci_rh_t *hub;
346 unsigned port;
347 TEST_SIZE_INIT(0, port, hub);
348 const unsigned feature = uint16_usb2host(setup_packet->value);
349 const uint16_t status = pio_read_16(hub->ports[port]);
350 switch (feature) {
351 case USB_HUB_FEATURE_PORT_RESET:
352 RH_DEBUG(hub, port, "Set port reset before (status %" PRIx16
353 ")", status);
354 uhci_port_reset_enable(hub->ports[port]);
355 hub->reset_changed[port] = true;
356 RH_DEBUG(hub, port, "Set port reset after (status %" PRIx16
357 ")", pio_read_16(hub->ports[port]));
358 break;
359 case USB2_HUB_FEATURE_PORT_SUSPEND:
360 RH_DEBUG(hub, port, "Set port suspend (status %" PRIx16
361 ")", status);
362 pio_write_16(hub->ports[port],
363 (status & ~STATUS_WC_BITS) | STATUS_SUSPEND);
364 usb_log_warning("Suspend is not implemented on port %u", port);
365 break;
366 case USB_HUB_FEATURE_PORT_POWER:
367 RH_DEBUG(hub, port, "Set port power (status %" PRIx16
368 ")", status);
369 /* We are always powered */
370 usb_log_warning("Tried to power port %u", port);
371 break;
372 case USB_HUB_FEATURE_C_PORT_CONNECTION:
373 case USB2_HUB_FEATURE_C_PORT_ENABLE:
374 case USB2_HUB_FEATURE_C_PORT_SUSPEND:
375 case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
376 RH_DEBUG(hub, port, "Set port change flag (status %" PRIx16
377 ")", status);
378 /* These are voluntary and don't have to be set
379 * there is no way we could do it on UHCI anyway */
380 break;
381 default:
382 RH_DEBUG(hub, port, "Set unknown feature %d (status %" PRIx16
383 ")", feature, status);
384 usb_log_warning("Setting feature %d is unsupported",
385 feature);
386 return ESTALL;
387 }
388 return EOK;
389}
390
391/** Status change handler.
392 * @param device Virtual hub device
393 * @param endpoint Endpoint number
394 * @param tr_type Transfer type
395 * @param buffer Response destination
396 * @param buffer_size Bytes available in buffer
397 * @param actual_size Size us the used part of the dest buffer.
398 *
399 * Produces status mask. Bit 0 indicates hub status change the other bits
400 * represent port status change. Endian does not matter as UHCI root hubs
401 * only need 1 byte.
402 */
403static errno_t req_status_change_handler(usbvirt_device_t *device,
404 usb_endpoint_t endpoint, usb_transfer_type_t tr_type,
405 void *buffer, size_t buffer_size, size_t *actual_size)
406{
407 uhci_rh_t *hub = virthub_get_data(device);
408 assert(hub);
409
410 if (buffer_size < 1)
411 return ESTALL;
412
413 const uint16_t status_a = pio_read_16(hub->ports[0]);
414 const uint16_t status_b = pio_read_16(hub->ports[1]);
415 const uint8_t status =
416 ((((status_a & STATUS_CHANGE_BITS) != 0) || hub->reset_changed[0]) ?
417 0x2 : 0) |
418 ((((status_b & STATUS_CHANGE_BITS) != 0) || hub->reset_changed[1]) ?
419 0x4 : 0);
420 if (status)
421 RH_DEBUG(hub, -1, "Event mask %" PRIx8
422 " (status_a %" PRIx16 "%s),"
423 " (status_b %" PRIx16 "%s)", status,
424 status_a, hub->reset_changed[0] ? "-reset" : "",
425 status_b, hub->reset_changed[1] ? "-reset" : "");
426 ((uint8_t *)buffer)[0] = status;
427 *actual_size = 1;
428 return (status != 0 ? EOK : ENAK);
429}
430
431/** UHCI root hub request handlers */
432static const usbvirt_control_request_handler_t control_transfer_handlers[] = {
433 {
434 STD_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
435 .name = "GetDescriptor",
436 .callback = virthub_base_get_hub_descriptor,
437 },
438 {
439 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
440 .name = "GetDescriptor",
441 .callback = virthub_base_get_hub_descriptor,
442 },
443 {
444 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_DESCRIPTOR),
445 .name = "GetHubDescriptor",
446 .callback = virthub_base_get_hub_descriptor,
447 },
448 {
449 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATE),
450 .name = "GetBusState",
451 .callback = req_get_port_state,
452 },
453 {
454 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
455 .name = "GetPortStatus",
456 .callback = req_get_port_status
457 },
458 {
459 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_CLEAR_FEATURE),
460 .name = "ClearHubFeature",
461 /* Hub features are overcurrent and supply good,
462 * this request may only clear changes that we never report*/
463 .callback = req_nop,
464 },
465 {
466 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_CLEAR_FEATURE),
467 .name = "ClearPortFeature",
468 .callback = req_clear_port_feature
469 },
470 {
471 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_STATUS),
472 .name = "GetHubStatus",
473 /* UHCI can't report OC condition or,
474 * lose power source */
475 .callback = virthub_base_get_null_status,
476 },
477 {
478 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
479 .name = "GetPortStatus",
480 .callback = req_get_port_status
481 },
482 {
483 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_SET_FEATURE),
484 .name = "SetHubFeature",
485 /* Hub features are overcurrent and supply good,
486 * this request may only set changes that we never report*/
487 .callback = req_nop,
488 },
489 {
490 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_SET_FEATURE),
491 .name = "SetPortFeature",
492 .callback = req_set_port_feature
493 },
494 {
495 .callback = NULL
496 }
497};
498
499static usbvirt_device_ops_t ops = {
500 .control = control_transfer_handlers,
501 .data_in[HUB_STATUS_CHANGE_PIPE] = req_status_change_handler,
502};
Note: See TracBrowser for help on using the repository browser.