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

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

uhci: Move function definition to avoid separate declaration.

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