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

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

uhci/rh: Poll the port status locally and in specified intervals

Significantly reduces uhci driver cpu usage

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