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

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

typo: transferred is spelled with two r

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