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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since af60409 was 84239b1, checked in by Jiri Svoboda <jiri@…>, 7 years ago

And there was much fixing.

  • 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 /* PIO delay, should not be longer than 3ms as the device might
155 * enter suspend state. */
156 udelay(10);
157 /* Drop ConnectionChange as some UHCI hw
158 * sets this bit after reset, that is incorrect */
159 port_status &= ~STATUS_WC_BITS;
160 pio_write_16(port, port_status | STATUS_ENABLED | STATUS_CONNECTED_CHANGED);
161}
162#define TEST_SIZE_INIT(size, port, hub) \
163do { \
164 if (uint16_usb2host(setup_packet->length) != size) \
165 return ESTALL; \
166 port = uint16_usb2host(setup_packet->index) - 1; \
167 if (port != 0 && port != 1) \
168 return EINVAL; \
169 hub = virthub_get_data(device); \
170 assert(hub);\
171} while (0)
172
173#define RH_DEBUG(hub, port, msg, ...) \
174do { \
175 if ((int)port >= 0) \
176 usb_log_debug("RH(%p-%d): " msg, hub, port, ##__VA_ARGS__); \
177 else \
178 usb_log_debug("RH(%p):" msg, hub, ##__VA_ARGS__); \
179} while (0)
180
181/** USB HUB port state request handler.
182 * @param device Virtual hub device
183 * @param setup_packet USB setup stage data.
184 * @param[out] data destination data buffer, size must be at least
185 * setup_packet->length bytes
186 * @param[out] act_size Sized of the valid response part of the buffer.
187 * @return Error code.
188 *
189 * Do not confuse with port status. Port state reports data line states,
190 * it is usefull for debuging purposes only.
191 */
192static errno_t req_get_port_state(usbvirt_device_t *device,
193 const usb_device_request_setup_packet_t *setup_packet,
194 uint8_t *data, size_t *act_size)
195{
196 uhci_rh_t *hub;
197 unsigned port;
198 TEST_SIZE_INIT(1, port, hub);
199 if (setup_packet->value != 0)
200 return EINVAL;
201
202 const uint16_t value = pio_read_16(hub->ports[port]);
203 data[0] = ((value & STATUS_LINE_D_MINUS) ? 1 : 0)
204 | ((value & STATUS_LINE_D_PLUS) ? 2 : 0);
205 RH_DEBUG(hub, port, "Bus state %" PRIx8 "(source %" PRIx16")",
206 data[0], value);
207 *act_size = 1;
208 return EOK;
209}
210
211#define BIT_VAL(val, bit) \
212 ((val & bit) ? 1 : 0)
213#define UHCI2USB(val, bit, mask) \
214 (BIT_VAL(val, bit) ? (mask) : 0)
215
216/** Port status request handler.
217 * @param device Virtual hub device
218 * @param setup_packet USB setup stage data.
219 * @param[out] data destination data buffer, size must be at least
220 * setup_packet->length bytes
221 * @param[out] act_size Sized of the valid response part of the buffer.
222 * @return Error code.
223 *
224 * Converts status reported via ioport to USB format.
225 * @note: reset change status needs to be handled in sw.
226 */
227static errno_t req_get_port_status(usbvirt_device_t *device,
228 const usb_device_request_setup_packet_t *setup_packet,
229 uint8_t *data, size_t *act_size)
230{
231 uhci_rh_t *hub;
232 unsigned port;
233 TEST_SIZE_INIT(4, port, hub);
234 if (setup_packet->value != 0)
235 return EINVAL;
236
237 const uint16_t val = pio_read_16(hub->ports[port]);
238 const uint32_t status = uint32_host2usb(
239 UHCI2USB(val, STATUS_CONNECTED, USB_HUB_PORT_STATUS_CONNECTION) |
240 UHCI2USB(val, STATUS_ENABLED, USB_HUB_PORT_STATUS_ENABLE) |
241 UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_SUSPEND) |
242 UHCI2USB(val, STATUS_IN_RESET, USB_HUB_PORT_STATUS_RESET) |
243 UHCI2USB(val, STATUS_ALWAYS_ONE, USB2_HUB_PORT_STATUS_POWER) |
244 UHCI2USB(val, STATUS_LOW_SPEED, USB2_HUB_PORT_STATUS_LOW_SPEED) |
245 UHCI2USB(val, STATUS_CONNECTED_CHANGED, USB_HUB_PORT_STATUS_C_CONNECTION) |
246 UHCI2USB(val, STATUS_ENABLED_CHANGED, USB2_HUB_PORT_STATUS_C_ENABLE) |
247// UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_C_SUSPEND) |
248 (hub->reset_changed[port] ? USB_HUB_PORT_STATUS_C_RESET : 0)
249 );
250 RH_DEBUG(hub, port, "Port status %" PRIx32 " (source %" PRIx16
251 "%s)", uint32_usb2host(status), val,
252 hub->reset_changed[port] ? "-reset" : "");
253 memcpy(data, &status, sizeof(status));
254 *act_size = sizeof(status);
255 return EOK;
256}
257
258/** Port clear feature request handler.
259 * @param device Virtual hub device
260 * @param setup_packet USB setup stage data.
261 * @param[out] data destination data buffer, size must be at least
262 * setup_packet->length bytes
263 * @param[out] act_size Sized of the valid response part of the buffer.
264 * @return Error code.
265 */
266static errno_t req_clear_port_feature(usbvirt_device_t *device,
267 const usb_device_request_setup_packet_t *setup_packet,
268 uint8_t *data, size_t *act_size)
269{
270 uhci_rh_t *hub;
271 unsigned port;
272 TEST_SIZE_INIT(0, port, hub);
273 const unsigned feature = uint16_usb2host(setup_packet->value);
274 const uint16_t status = pio_read_16(hub->ports[port]);
275 const uint16_t val = status & (~STATUS_WC_BITS);
276 switch (feature) {
277 case USB2_HUB_FEATURE_PORT_ENABLE:
278 RH_DEBUG(hub, port, "Clear port enable (status %"
279 PRIx16 ")", status);
280 pio_write_16(hub->ports[port], val & ~STATUS_ENABLED);
281 break;
282 case USB2_HUB_FEATURE_PORT_SUSPEND:
283 RH_DEBUG(hub, port, "Clear port suspend (status %"
284 PRIx16 ")", status);
285 pio_write_16(hub->ports[port], val & ~STATUS_SUSPEND);
286 // TODO we should do resume magic
287 usb_log_warning("Resume is not implemented on port %u", port);
288 break;
289 case USB_HUB_FEATURE_PORT_POWER:
290 RH_DEBUG(hub, port, "Clear port power (status %" PRIx16 ")",
291 status);
292 /* We are always powered */
293 usb_log_warning("Tried to power off port %u", port);
294 break;
295 case USB_HUB_FEATURE_C_PORT_CONNECTION:
296 RH_DEBUG(hub, port, "Clear port conn change (status %"
297 PRIx16 ")", status);
298 pio_write_16(hub->ports[port], val | STATUS_CONNECTED_CHANGED);
299 break;
300 case USB_HUB_FEATURE_C_PORT_RESET:
301 RH_DEBUG(hub, port, "Clear port reset change (status %"
302 PRIx16 ")", status);
303 hub->reset_changed[port] = false;
304 break;
305 case USB2_HUB_FEATURE_C_PORT_ENABLE:
306 RH_DEBUG(hub, port, "Clear port enable change (status %"
307 PRIx16 ")", status);
308 pio_write_16(hub->ports[port], status | STATUS_ENABLED_CHANGED);
309 break;
310 case USB2_HUB_FEATURE_C_PORT_SUSPEND:
311 RH_DEBUG(hub, port, "Clear port suspend change (status %"
312 PRIx16 ")", status);
313 //TODO
314 return ENOTSUP;
315 case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
316 RH_DEBUG(hub, port, "Clear port OC change (status %"
317 PRIx16 ")", status);
318 /* UHCI Does not report over current */
319 //TODO: newer chips do, but some have broken wiring
320 break;
321 default:
322 RH_DEBUG(hub, port, "Clear unknown feature %d (status %"
323 PRIx16 ")", feature, status);
324 usb_log_warning("Clearing feature %d is unsupported",
325 feature);
326 return ESTALL;
327 }
328 return EOK;
329}
330
331/** Port set 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_set_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 uhci_rh_t *hub;
344 unsigned port;
345 TEST_SIZE_INIT(0, port, hub);
346 const unsigned feature = uint16_usb2host(setup_packet->value);
347 const uint16_t status = pio_read_16(hub->ports[port]);
348 switch (feature) {
349 case USB_HUB_FEATURE_PORT_RESET:
350 RH_DEBUG(hub, port, "Set port reset before (status %" PRIx16
351 ")", status);
352 uhci_port_reset_enable(hub->ports[port]);
353 hub->reset_changed[port] = true;
354 RH_DEBUG(hub, port, "Set port reset after (status %" PRIx16
355 ")", pio_read_16(hub->ports[port]));
356 break;
357 case USB2_HUB_FEATURE_PORT_SUSPEND:
358 RH_DEBUG(hub, port, "Set port suspend (status %" PRIx16
359 ")", status);
360 pio_write_16(hub->ports[port],
361 (status & ~STATUS_WC_BITS) | STATUS_SUSPEND);
362 usb_log_warning("Suspend is not implemented on port %u", port);
363 break;
364 case USB_HUB_FEATURE_PORT_POWER:
365 RH_DEBUG(hub, port, "Set port power (status %" PRIx16
366 ")", status);
367 /* We are always powered */
368 usb_log_warning("Tried to power port %u", port);
369 break;
370 case USB_HUB_FEATURE_C_PORT_CONNECTION:
371 case USB2_HUB_FEATURE_C_PORT_ENABLE:
372 case USB2_HUB_FEATURE_C_PORT_SUSPEND:
373 case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
374 RH_DEBUG(hub, port, "Set port change flag (status %" PRIx16
375 ")", status);
376 /* These are voluntary and don't have to be set
377 * there is no way we could do it on UHCI anyway */
378 break;
379 default:
380 RH_DEBUG(hub, port, "Set unknown feature %d (status %" PRIx16
381 ")", feature, status);
382 usb_log_warning("Setting feature %d is unsupported",
383 feature);
384 return ESTALL;
385 }
386 return EOK;
387}
388
389/** Status change handler.
390 * @param device Virtual hub device
391 * @param endpoint Endpoint number
392 * @param tr_type Transfer type
393 * @param buffer Response destination
394 * @param buffer_size Bytes available in buffer
395 * @param actual_size Size us the used part of the dest buffer.
396 *
397 * Produces status mask. Bit 0 indicates hub status change the other bits
398 * represent port status change. Endian does not matter as UHCI root hubs
399 * only need 1 byte.
400 */
401static errno_t req_status_change_handler(usbvirt_device_t *device,
402 usb_endpoint_t endpoint, usb_transfer_type_t tr_type,
403 void *buffer, size_t buffer_size, size_t *actual_size)
404{
405 uhci_rh_t *hub = virthub_get_data(device);
406 assert(hub);
407
408 if (buffer_size < 1)
409 return ESTALL;
410
411 const uint16_t status_a = pio_read_16(hub->ports[0]);
412 const uint16_t status_b = pio_read_16(hub->ports[1]);
413 const uint8_t status =
414 ((((status_a & STATUS_CHANGE_BITS) != 0) || hub->reset_changed[0]) ?
415 0x2 : 0) |
416 ((((status_b & STATUS_CHANGE_BITS) != 0) || hub->reset_changed[1]) ?
417 0x4 : 0);
418 if (status)
419 RH_DEBUG(hub, -1, "Event mask %" PRIx8
420 " (status_a %" PRIx16 "%s),"
421 " (status_b %" PRIx16 "%s)", status,
422 status_a, hub->reset_changed[0] ? "-reset" : "",
423 status_b, hub->reset_changed[1] ? "-reset" : "" );
424 ((uint8_t *)buffer)[0] = status;
425 *actual_size = 1;
426 return (status != 0 ? EOK : ENAK);
427}
428
429/** UHCI 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_STATE),
448 .name = "GetBusState",
449 .callback = req_get_port_state,
450 },
451 {
452 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
453 .name = "GetPortStatus",
454 .callback = req_get_port_status
455 },
456 {
457 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_CLEAR_FEATURE),
458 .name = "ClearHubFeature",
459 /* Hub features are overcurrent and supply good,
460 * this request may only clear changes that we never report*/
461 .callback = req_nop,
462 },
463 {
464 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_CLEAR_FEATURE),
465 .name = "ClearPortFeature",
466 .callback = req_clear_port_feature
467 },
468 {
469 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_STATUS),
470 .name = "GetHubStatus",
471 /* UHCI can't report OC condition or,
472 * lose power source */
473 .callback = virthub_base_get_null_status,
474 },
475 {
476 CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
477 .name = "GetPortStatus",
478 .callback = req_get_port_status
479 },
480 {
481 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_SET_FEATURE),
482 .name = "SetHubFeature",
483 /* Hub features are overcurrent and supply good,
484 * this request may only set changes that we never report*/
485 .callback = req_nop,
486 },
487 {
488 CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_SET_FEATURE),
489 .name = "SetPortFeature",
490 .callback = req_set_port_feature
491 },
492 {
493 .callback = NULL
494 }
495};
496
497static usbvirt_device_ops_t ops = {
498 .control = control_transfer_handlers,
499 .data_in[HUB_STATUS_CHANGE_PIPE] = req_status_change_handler,
500};
Note: See TracBrowser for help on using the repository browser.