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

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

uhci: Sanitize headers.

Include what you use.

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