source: mainline/uspace/lib/usbhost/src/hcd.c@ 32fb6bce

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

usbhost: refactoring

This commit moves interrupt, status and schedule to bus
operations. Then the purpose of hcd_t is better defined, and split into
hc_driver_t and hc_device_t. hc_driver_t is used to wrap driver
implementation by the library (similar to how usb_driver_t is used to
wrap usb device drivers). hc_device_t is used as a parent for hc_t
inside drivers, and is allocated inside the DDF device node.

To support these changes, some local identifiers were renamed, some
functions were moved and/or renamed and their arguments changed. The
most notable one being hcd_send_batch → bus_device_send_batch.

  • Property mode set to 100644
File size: 12.2 KB
RevLine 
[4daee7a]1/*
2 * Copyright (c) 2011 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/** @addtogroup libusbhost
30 * @{
31 */
32/** @file
33 *
34 */
35
[8d2e251]36#include <assert.h>
37#include <async.h>
[32fb6bce]38#include <ddf/interrupt.h>
[8d2e251]39#include <errno.h>
[306a36d]40#include <macros.h>
[2b61945]41#include <str_error.h>
[64fea02]42#include <usb/debug.h>
43#include <usb/descriptor.h>
44#include <usb/request.h>
45#include <usb_iface.h>
46
47#include "bus.h"
[32fb6bce]48#include "ddf_helpers.h"
[64fea02]49#include "endpoint.h"
50#include "usb_transfer_batch.h"
[8d2e251]51
[b4c1c95]52#include "hcd.h"
53
[32fb6bce]54int hc_dev_add(ddf_dev_t *);
55int hc_dev_remove(ddf_dev_t *);
56int hc_dev_gone(ddf_dev_t *);
57int hc_fun_online(ddf_fun_t *);
58int hc_fun_offline(ddf_fun_t *);
[41924f30]59
[32fb6bce]60static driver_ops_t hc_driver_ops = {
61 .dev_add = hc_dev_add,
62 .dev_remove = hc_dev_remove,
63 .dev_gone = hc_dev_gone,
64 .fun_online = hc_fun_offline,
65 .fun_offline = hc_fun_offline,
66};
67
68static const hc_driver_t *hc_driver;
69
70int hc_driver_main(const hc_driver_t *driver)
71{
72 driver_t ddf_driver = {
73 .name = driver->name,
74 .driver_ops = &hc_driver_ops,
75 };
76
77 /* Remember ops to call. */
78 hc_driver = driver;
79
80 return ddf_driver_main(&ddf_driver);
81}
82
83/** IRQ handling callback, forward status from call to diver structure.
[21be46a]84 *
[32fb6bce]85 * @param[in] dev DDF instance of the device to use.
86 * @param[in] iid (Unused).
87 * @param[in] call Pointer to the call from kernel.
88 */
89static void irq_handler(ipc_callid_t iid, ipc_call_t *call, ddf_dev_t *dev)
90{
91 assert(dev);
92 hc_device_t *hcd = dev_to_hcd(dev);
93
94 const bus_ops_t *ops = BUS_OPS_LOOKUP(hcd->bus->ops, interrupt);
95 assert(ops);
96
97 const uint32_t status = IPC_GET_ARG1(*call);
98 ops->interrupt(hcd->bus, status);
99}
100
101/** Worker for the HW interrupt replacement fibril.
[21be46a]102 */
[32fb6bce]103static int interrupt_polling(void *arg)
104{
105 hc_device_t *hcd = arg;
[21be46a]106 assert(hcd);
[32fb6bce]107 bus_t *bus = hcd->bus;
108
109 const bus_ops_t *interrupt_ops = BUS_OPS_LOOKUP(bus->ops, interrupt);
110 const bus_ops_t *status_ops = BUS_OPS_LOOKUP(bus->ops, status);
111 if (!interrupt_ops || !status_ops)
112 return ENOTSUP;
113
114 uint32_t status = 0;
115 while (status_ops->status(bus, &status) == EOK) {
116 interrupt_ops->interrupt(bus, status);
117 status = 0;
118 /* We should wait 1 frame - 1ms here, but this polling is a
119 * lame crutch anyway so don't hog the system. 10ms is still
120 * good enough for emergency mode */
121 async_usleep(10000);
122 }
123 return EOK;
124}
[21be46a]125
[32fb6bce]126static inline void irq_code_clean(irq_code_t *code)
127{
128 if (code) {
129 free(code->ranges);
130 free(code->cmds);
131 code->ranges = NULL;
132 code->cmds = NULL;
133 code->rangecount = 0;
134 code->cmdcount = 0;
135 }
136}
137
138/** Register interrupt handler
139 *
140 * @param[in] device Host controller DDF device
141 * @param[in] regs Register range
142 * @param[in] irq Interrupt number
143 * @paran[in] handler Interrupt handler
144 * @param[in] gen_irq_code IRQ code generator.
145 *
146 * @return IRQ capability handle on success.
147 * @return Negative error code.
148 */
149static int hcd_ddf_setup_interrupts(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
150{
151 assert(hcd);
152 irq_code_t irq_code = {0};
153
154 if (!hc_driver->irq_code_gen)
155 return ENOTSUP;
156
157 const int irq = hc_driver->irq_code_gen(&irq_code, hcd, hw_res);
158 if (irq < 0) {
159 usb_log_error("Failed to generate IRQ code: %s.\n",
160 str_error(irq));
161 return irq;
162 }
163
164 /* Register handler to avoid interrupt lockup */
165 const int irq_cap = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, &irq_code);
166 irq_code_clean(&irq_code);
167 if (irq_cap < 0) {
168 usb_log_error("Failed to register interrupt handler: %s.\n",
169 str_error(irq_cap));
170 return irq_cap;
171 }
172
173 /* Enable interrupts */
174 int ret = hcd_ddf_enable_interrupt(hcd, irq);
175 if (ret != EOK) {
176 usb_log_error("Failed to enable interrupts: %s.\n",
177 str_error(ret));
178 unregister_interrupt_handler(hcd->ddf_dev, irq_cap);
179 return ret;
180 }
181 return irq_cap;
182}
183
184/** Initialize HC in memory of the driver.
185 *
186 * @param device DDF instance of the device to use
187 * @return Error code
188 *
189 * This function does all the preparatory work for hc and rh drivers:
190 * - gets device's hw resources
191 * - attempts to enable interrupts
192 * - registers interrupt handler
193 * - calls driver specific initialization
194 * - registers root hub
195 */
196int hc_dev_add(ddf_dev_t *device)
197{
198 int ret = EOK;
199 assert(device);
200
201 if (!hc_driver->hc_add) {
202 usb_log_error("Driver '%s' does not support adding devices.", hc_driver->name);
203 return ENOTSUP;
204 }
205
206 ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
207 if (ret != EOK) {
208 usb_log_error("Failed to setup HC device.\n");
209 return ret;
210 }
211
212 hc_device_t *hcd = dev_to_hcd(device);
213
214 hw_res_list_parsed_t hw_res;
215 ret = hcd_ddf_get_registers(hcd, &hw_res);
216 if (ret != EOK) {
217 usb_log_error("Failed to get register memory addresses "
218 "for `%s': %s.\n", ddf_dev_get_name(device),
219 str_error(ret));
220 goto err_hcd;
221 }
222
223 ret = hc_driver->hc_add(hcd, &hw_res);
224 if (ret != EOK) {
225 usb_log_error("Failed to init HCD.\n");
226 goto err_hw_res;
227 }
228
229 assert(hcd->bus);
230
231 /* Setup interrupts */
232 hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res);
233 if (hcd->irq_cap >= 0) {
234 usb_log_debug("Hw interrupts enabled.\n");
235 }
236
237 /* Claim the device from BIOS */
238 if (hc_driver->claim)
239 ret = hc_driver->claim(hcd);
240 if (ret != EOK) {
241 usb_log_error("Failed to claim `%s' for `%s': %s",
242 ddf_dev_get_name(device), hc_driver->name, str_error(ret));
243 goto err_irq;
244 }
245
246 /* Start hw */
247 if (hc_driver->start)
248 ret = hc_driver->start(hcd);
249 if (ret != EOK) {
250 usb_log_error("Failed to start HCD: %s.\n", str_error(ret));
251 goto err_irq;
252 }
253
254 const bus_ops_t *ops = BUS_OPS_LOOKUP(hcd->bus->ops, status);
255
256 /* Need working irq replacement to setup root hub */
257 if (hcd->irq_cap < 0 && ops) {
258 hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
259 if (!hcd->polling_fibril) {
260 usb_log_error("Failed to create polling fibril\n");
261 ret = ENOMEM;
262 goto err_started;
263 }
264 fibril_add_ready(hcd->polling_fibril);
265 usb_log_warning("Failed to enable interrupts: %s."
266 " Falling back to polling.\n", str_error(hcd->irq_cap));
267 }
268
269 /*
270 * Creating root hub registers a new USB device so HC
271 * needs to be ready at this time.
272 */
273 if (hc_driver->setup_root_hub)
274 ret = hc_driver->setup_root_hub(hcd);
275 if (ret != EOK) {
276 usb_log_error("Failed to setup HC root hub: %s.\n",
277 str_error(ret));
278 goto err_polling;
279 }
280
281 usb_log_info("Controlling new `%s' device `%s'.\n",
282 hc_driver->name, ddf_dev_get_name(device));
283 return EOK;
284
285err_polling:
286 // TODO: Stop the polling fibril (refactor the interrupt_polling func)
287 //
288err_started:
289 if (hc_driver->stop)
290 hc_driver->stop(hcd);
291err_irq:
292 unregister_interrupt_handler(device, hcd->irq_cap);
293 if (hc_driver->hc_remove)
294 hc_driver->hc_remove(hcd);
295err_hw_res:
296 hw_res_list_parsed_clean(&hw_res);
297err_hcd:
298 hcd_ddf_clean_hc(hcd);
299 return ret;
300}
301
302int hc_dev_remove(ddf_dev_t *dev)
303{
304 int err;
305 hc_device_t *hcd = dev_to_hcd(dev);
306
307 if (hc_driver->stop)
308 if ((err = hc_driver->stop(hcd)))
309 return err;
310
311 unregister_interrupt_handler(dev, hcd->irq_cap);
312
313 if (hc_driver->hc_remove)
314 if ((err = hc_driver->hc_remove(hcd)))
315 return err;
316
317 hcd_ddf_clean_hc(hcd);
318
319 // TODO probably not complete
320
321 return EOK;
322}
323
324int hc_dev_gone(ddf_dev_t *dev)
325{
326 int err = ENOTSUP;
327 hc_device_t *hcd = dev_to_hcd(dev);
328
329 if (hc_driver->hc_gone)
330 err = hc_driver->hc_gone(hcd);
331
332 hcd_ddf_clean_hc(hcd);
333
334 return err;
335}
336
337int hc_fun_online(ddf_fun_t *fun)
338{
339 assert(fun);
340
341 device_t *dev = ddf_fun_data_get(fun);
342 assert(dev);
343
344 usb_log_info("Device(%d): Requested to be brought online.", dev->address);
345 return bus_device_online(dev);
346}
347
348int hc_fun_offline(ddf_fun_t *fun)
349{
350 assert(fun);
351
352 device_t *dev = ddf_fun_data_get(fun);
353 assert(dev);
354
355 usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
356 return bus_device_offline(dev);
[21be46a]357}
358
[306a36d]359/** Get max packet size for the control endpoint 0.
360 *
361 * For LS, HS, and SS devices this value is fixed. For FS devices we must fetch
362 * the first 8B of the device descriptor to determine it.
363 *
[375211d2]364 * @return Max packet size for EP 0
[306a36d]365 */
[32fb6bce]366int hcd_get_ep0_max_packet_size(uint16_t *mps, bus_t *bus, device_t *dev)
[306a36d]367{
[375211d2]368 assert(mps);
369
[306a36d]370 static const uint16_t mps_fixed [] = {
371 [USB_SPEED_LOW] = 8,
372 [USB_SPEED_HIGH] = 64,
373 [USB_SPEED_SUPER] = 512,
374 };
375
376 if (dev->speed < ARRAY_SIZE(mps_fixed) && mps_fixed[dev->speed] != 0) {
377 *mps = mps_fixed[dev->speed];
378 return EOK;
379 }
380
381 const usb_target_t control_ep = {{
382 .address = dev->address,
383 .endpoint = 0,
384 }};
385
386 usb_standard_device_descriptor_t desc = { 0 };
387 const usb_device_request_setup_packet_t get_device_desc_8 =
388 GET_DEVICE_DESC(CTRL_PIPE_MIN_PACKET_SIZE);
389
[375211d2]390 usb_log_debug("Requesting first 8B of device descriptor to determine MPS.");
[32fb6bce]391 ssize_t got = bus_device_send_batch_sync(dev, control_ep, USB_DIRECTION_IN,
[306a36d]392 (char *) &desc, CTRL_PIPE_MIN_PACKET_SIZE, *(uint64_t *)&get_device_desc_8,
393 "read first 8 bytes of dev descriptor");
394
395 if (got != CTRL_PIPE_MIN_PACKET_SIZE) {
396 const int err = got < 0 ? got : EOVERFLOW;
397 usb_log_error("Failed to get 8B of dev descr: %s.", str_error(err));
398 return err;
399 }
400
[375211d2]401 if (desc.descriptor_type != USB_DESCTYPE_DEVICE) {
402 usb_log_error("The device responded with wrong device descriptor.");
403 return EIO;
404 }
405
406 uint16_t version = uint16_usb2host(desc.usb_spec_version);
407 if (version < 0x0300) {
408 /* USB 2 and below have MPS raw in the field */
409 *mps = desc.max_packet_size;
410 } else {
411 /* USB 3 have MPS as an 2-based exponent */
412 *mps = (1 << desc.max_packet_size);
413 }
[306a36d]414 return EOK;
415}
416
[ff14aede]417/**
418 * Setup devices Transaction Translation.
419 *
420 * This applies for Low/Full speed devices under High speed hub only. Other
421 * devices just inherit TT from the hub.
422 *
423 * Roothub must be handled specially.
424 */
425void hcd_setup_device_tt(device_t *dev)
426{
427 if (!dev->hub)
428 return;
429
430 if (dev->hub->speed == USB_SPEED_HIGH && usb_speed_is_11(dev->speed)) {
431 /* For LS devices under HS hub */
432 dev->tt.address = dev->hub->address;
433 dev->tt.port = dev->port;
434 }
435 else {
436 /* Inherit hub's TT */
437 dev->tt = dev->hub->tt;
438 }
439}
[306a36d]440
[820d9bc]441/** Check setup packet data for signs of toggle reset.
442 *
443 * @param[in] requst Setup requst data.
444 *
445 * @retval -1 No endpoints need reset.
446 * @retval 0 All endpoints need reset.
447 * @retval >0 Specified endpoint needs reset.
448 *
449 */
[32fb6bce]450toggle_reset_mode_t hcd_get_request_toggle_reset_mode(
[820d9bc]451 const usb_device_request_setup_packet_t *request)
452{
453 assert(request);
454 switch (request->request)
455 {
456 /* Clear Feature ENPOINT_STALL */
457 case USB_DEVREQ_CLEAR_FEATURE: /*resets only cleared ep */
458 /* 0x2 ( HOST to device | STANDART | TO ENPOINT) */
459 if ((request->request_type == 0x2) &&
460 (request->value == USB_FEATURE_ENDPOINT_HALT))
461 return RESET_EP;
462 break;
463 case USB_DEVREQ_SET_CONFIGURATION:
464 case USB_DEVREQ_SET_INTERFACE:
465 /* Recipient must be device, this resets all endpoints,
466 * In fact there should be no endpoints but EP 0 registered
467 * as different interfaces use different endpoints,
468 * unless you're changing configuration or alternative
469 * interface of an already setup device. */
470 if (!(request->request_type & SETUP_REQUEST_TYPE_DEVICE_TO_HOST))
471 return RESET_ALL;
472 break;
473 default:
474 break;
475 }
476
477 return RESET_NONE;
478}
479
[237df2f]480
[4daee7a]481/**
482 * @}
483 */
Note: See TracBrowser for help on using the repository browser.