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

Last change on this file since 09398589 was 60744cb, checked in by Jiri Svoboda <jiri@…>, 15 months ago

Let driver specify any argument to IRQ handler

This allows the driver to register a single handler for multiple
interrupts and still distinguish between them. It also removes
the extra step of having to get softstate from ddf_dev_t.

  • Property mode set to 100644
File size: 9.4 KB
RevLine 
[4daee7a]1/*
2 * Copyright (c) 2011 Jan Vesely
[e0a5d4c]3 * Copyright (c) 2018 Ondrej Hlavaty
[4daee7a]4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup libusbhost
31 * @{
32 */
33/** @file
34 *
[1102eca]35 * Host controller driver framework. Encapsulates DDF device of HC to an
36 * hc_device_t, which is passed to driver implementing hc_driver_t.
[4daee7a]37 */
38
[8d2e251]39#include <assert.h>
40#include <async.h>
[32fb6bce]41#include <ddf/interrupt.h>
[8d2e251]42#include <errno.h>
[306a36d]43#include <macros.h>
[2b61945]44#include <str_error.h>
[64fea02]45#include <usb/debug.h>
46#include <usb/descriptor.h>
47#include <usb/request.h>
48#include <usb_iface.h>
49
50#include "bus.h"
[32fb6bce]51#include "ddf_helpers.h"
[64fea02]52#include "endpoint.h"
53#include "usb_transfer_batch.h"
[8d2e251]54
[b4c1c95]55#include "hcd.h"
56
[32fb6bce]57int hc_dev_add(ddf_dev_t *);
58int hc_dev_remove(ddf_dev_t *);
59int hc_dev_gone(ddf_dev_t *);
60int hc_fun_online(ddf_fun_t *);
61int hc_fun_offline(ddf_fun_t *);
[41924f30]62
[32fb6bce]63static driver_ops_t hc_driver_ops = {
64 .dev_add = hc_dev_add,
65 .dev_remove = hc_dev_remove,
66 .dev_gone = hc_dev_gone,
[8eb7095]67 .fun_online = hc_fun_online,
[32fb6bce]68 .fun_offline = hc_fun_offline,
69};
70
71static const hc_driver_t *hc_driver;
72
[1102eca]73/**
74 * The main HC driver routine.
75 */
[32fb6bce]76int hc_driver_main(const hc_driver_t *driver)
77{
78 driver_t ddf_driver = {
79 .name = driver->name,
80 .driver_ops = &hc_driver_ops,
81 };
82
83 /* Remember ops to call. */
84 hc_driver = driver;
85
86 return ddf_driver_main(&ddf_driver);
87}
88
[1102eca]89/**
90 * IRQ handling callback. Call the bus operation.
[21be46a]91 *
[1102eca]92 * Currently, there is a bus ops lookup to find the interrupt handler. So far,
93 * the mechanism is too flexible, as it allows different instances of HC to
94 * have different IRQ handlers, disallowing us to optimize the lookup here.
95 * TODO: Make the bus mechanism less flexible in irq handling and remove the
96 * lookup.
[60744cb]97 *
98 * @param call Interrupt notification
99 * @param arg Argument (hc_device_t *)
[32fb6bce]100 */
[60744cb]101static void irq_handler(ipc_call_t *call, void *arg)
[32fb6bce]102{
[60744cb]103 hc_device_t *hcd = (hc_device_t *)arg;
[32fb6bce]104
[fafb8e5]105 const uint32_t status = ipc_get_arg1(call);
[296d22fc]106 hcd->bus->ops->interrupt(hcd->bus, status);
[32fb6bce]107}
108
[1102eca]109/**
110 * Worker for the HW interrupt replacement fibril.
[21be46a]111 */
[5a6cc679]112static errno_t interrupt_polling(void *arg)
[32fb6bce]113{
[f98f4b7]114 bus_t *bus = arg;
115 assert(bus);
[32fb6bce]116
[296d22fc]117 if (!bus->ops->interrupt || !bus->ops->status)
[32fb6bce]118 return ENOTSUP;
119
120 uint32_t status = 0;
[296d22fc]121 while (bus->ops->status(bus, &status) == EOK) {
122 bus->ops->interrupt(bus, status);
[32fb6bce]123 status = 0;
[7c3fb9b]124 /*
125 * We should wait 1 frame - 1ms here, but this polling is a
[32fb6bce]126 * lame crutch anyway so don't hog the system. 10ms is still
[7c3fb9b]127 * good enough for emergency mode
128 */
[5f97ef44]129 fibril_usleep(10000);
[32fb6bce]130 }
131 return EOK;
132}
[21be46a]133
[1102eca]134/**
135 * Clean the IRQ code bottom-half.
136 */
[32fb6bce]137static inline void irq_code_clean(irq_code_t *code)
138{
139 if (code) {
140 free(code->ranges);
141 free(code->cmds);
142 code->ranges = NULL;
143 code->cmds = NULL;
144 code->rangecount = 0;
145 code->cmdcount = 0;
146 }
147}
148
[1102eca]149/**
150 * Register an interrupt handler. If there is a callback to setup the bottom half,
151 * invoke it and register it. Register for notifications.
152 *
153 * If this method fails, a polling fibril is started instead.
[32fb6bce]154 *
[eadaeae8]155 * @param[in] hcd Host controller device.
156 * @param[in] hw_res Resources to be used.
157 * @param[out] irq_handle Storage for the returned IRQ handle
[32fb6bce]158 *
[eadaeae8]159 * @return Error code.
[32fb6bce]160 */
[ae3a941]161static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd,
[eadaeae8]162 const hw_res_list_parsed_t *hw_res, cap_irq_handle_t *irq_handle)
[32fb6bce]163{
164 assert(hcd);
[ae3a941]165 irq_code_t irq_code = { 0 };
[32fb6bce]166
167 if (!hc_driver->irq_code_gen)
168 return ENOTSUP;
169
[132ab5d1]170 int irq;
[5a6cc679]171 errno_t ret;
[132ab5d1]172 ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq);
173 if (ret != EOK) {
[a1732929]174 usb_log_error("Failed to generate IRQ code: %s.",
[eadaeae8]175 str_error(ret));
176 return ret;
[32fb6bce]177 }
178
179 /* Register handler to avoid interrupt lockup */
[eadaeae8]180 cap_irq_handle_t ihandle;
[ae3a941]181 ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler,
[60744cb]182 (void *)hcd, &irq_code, &ihandle);
[32fb6bce]183 irq_code_clean(&irq_code);
[132ab5d1]184 if (ret != EOK) {
[a1732929]185 usb_log_error("Failed to register interrupt handler: %s.",
[eadaeae8]186 str_error(ret));
187 return ret;
[32fb6bce]188 }
189
190 /* Enable interrupts */
[132ab5d1]191 ret = hcd_ddf_enable_interrupt(hcd, irq);
[32fb6bce]192 if (ret != EOK) {
[a1732929]193 usb_log_error("Failed to enable interrupts: %s.",
[32fb6bce]194 str_error(ret));
[eadaeae8]195 unregister_interrupt_handler(hcd->ddf_dev, ihandle);
[32fb6bce]196 return ret;
197 }
[eadaeae8]198
199 *irq_handle = ihandle;
200 return EOK;
[32fb6bce]201}
202
[1102eca]203/**
204 * Initialize HC in memory of the driver.
[32fb6bce]205 *
206 * This function does all the preparatory work for hc and rh drivers:
207 * - gets device's hw resources
208 * - attempts to enable interrupts
209 * - registers interrupt handler
210 * - calls driver specific initialization
211 * - registers root hub
[1102eca]212 *
213 * @param device DDF instance of the device to use
214 * @return Error code
[32fb6bce]215 */
[5a6cc679]216errno_t hc_dev_add(ddf_dev_t *device)
[32fb6bce]217{
[5a6cc679]218 errno_t ret = EOK;
[32fb6bce]219 assert(device);
220
221 if (!hc_driver->hc_add) {
[ae3a941]222 usb_log_error("Driver '%s' does not support adding devices.",
223 hc_driver->name);
[32fb6bce]224 return ENOTSUP;
225 }
226
227 ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
228 if (ret != EOK) {
[a1732929]229 usb_log_error("Failed to setup HC device.");
[32fb6bce]230 return ret;
231 }
232
233 hc_device_t *hcd = dev_to_hcd(device);
234
235 hw_res_list_parsed_t hw_res;
236 ret = hcd_ddf_get_registers(hcd, &hw_res);
237 if (ret != EOK) {
238 usb_log_error("Failed to get register memory addresses "
[a1732929]239 "for `%s': %s.", ddf_dev_get_name(device),
[32fb6bce]240 str_error(ret));
241 goto err_hcd;
242 }
243
244 ret = hc_driver->hc_add(hcd, &hw_res);
245 if (ret != EOK) {
[a1732929]246 usb_log_error("Failed to init HCD.");
[32fb6bce]247 goto err_hw_res;
248 }
249
250 assert(hcd->bus);
251
252 /* Setup interrupts */
[eadaeae8]253 hcd->irq_handle = CAP_NIL;
254 errno_t irqerr = hcd_ddf_setup_interrupts(hcd, &hw_res,
255 &hcd->irq_handle);
256 if (irqerr == EOK) {
[a1732929]257 usb_log_debug("Hw interrupts enabled.");
[32fb6bce]258 }
259
260 /* Claim the device from BIOS */
261 if (hc_driver->claim)
262 ret = hc_driver->claim(hcd);
263 if (ret != EOK) {
264 usb_log_error("Failed to claim `%s' for `%s': %s",
265 ddf_dev_get_name(device), hc_driver->name, str_error(ret));
266 goto err_irq;
267 }
268
269 /* Start hw */
270 if (hc_driver->start)
271 ret = hc_driver->start(hcd);
272 if (ret != EOK) {
[a1732929]273 usb_log_error("Failed to start HCD: %s.", str_error(ret));
[32fb6bce]274 goto err_irq;
275 }
276
[296d22fc]277 const bus_ops_t *ops = hcd->bus->ops;
[32fb6bce]278
279 /* Need working irq replacement to setup root hub */
[eadaeae8]280 if (irqerr != EOK && ops->status) {
[32fb6bce]281 hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
282 if (!hcd->polling_fibril) {
[a1732929]283 usb_log_error("Failed to create polling fibril");
[32fb6bce]284 ret = ENOMEM;
285 goto err_started;
286 }
287 fibril_add_ready(hcd->polling_fibril);
288 usb_log_warning("Failed to enable interrupts: %s."
[eadaeae8]289 " Falling back to polling.", str_error(irqerr));
[32fb6bce]290 }
291
292 /*
293 * Creating root hub registers a new USB device so HC
294 * needs to be ready at this time.
295 */
296 if (hc_driver->setup_root_hub)
297 ret = hc_driver->setup_root_hub(hcd);
298 if (ret != EOK) {
[a1732929]299 usb_log_error("Failed to setup HC root hub: %s.",
[32fb6bce]300 str_error(ret));
301 goto err_polling;
302 }
303
[a1732929]304 usb_log_info("Controlling new `%s' device `%s'.",
[ae3a941]305 hc_driver->name, ddf_dev_get_name(device));
[32fb6bce]306 return EOK;
307
308err_polling:
309 // TODO: Stop the polling fibril (refactor the interrupt_polling func)
310 //
311err_started:
312 if (hc_driver->stop)
313 hc_driver->stop(hcd);
314err_irq:
[eadaeae8]315 unregister_interrupt_handler(device, hcd->irq_handle);
[32fb6bce]316 if (hc_driver->hc_remove)
317 hc_driver->hc_remove(hcd);
318err_hw_res:
319 hw_res_list_parsed_clean(&hw_res);
320err_hcd:
321 hcd_ddf_clean_hc(hcd);
322 return ret;
323}
324
[5a6cc679]325errno_t hc_dev_remove(ddf_dev_t *dev)
[32fb6bce]326{
[5a6cc679]327 errno_t err;
[32fb6bce]328 hc_device_t *hcd = dev_to_hcd(dev);
329
330 if (hc_driver->stop)
331 if ((err = hc_driver->stop(hcd)))
332 return err;
333
[eadaeae8]334 unregister_interrupt_handler(dev, hcd->irq_handle);
[32fb6bce]335
336 if (hc_driver->hc_remove)
337 if ((err = hc_driver->hc_remove(hcd)))
338 return err;
339
340 hcd_ddf_clean_hc(hcd);
341
342 // TODO probably not complete
343
344 return EOK;
345}
346
[5a6cc679]347errno_t hc_dev_gone(ddf_dev_t *dev)
[32fb6bce]348{
[5a6cc679]349 errno_t err = ENOTSUP;
[32fb6bce]350 hc_device_t *hcd = dev_to_hcd(dev);
351
352 if (hc_driver->hc_gone)
353 err = hc_driver->hc_gone(hcd);
354
355 hcd_ddf_clean_hc(hcd);
356
357 return err;
358}
359
[5a6cc679]360errno_t hc_fun_online(ddf_fun_t *fun)
[32fb6bce]361{
362 assert(fun);
363
364 device_t *dev = ddf_fun_data_get(fun);
365 assert(dev);
366
367 usb_log_info("Device(%d): Requested to be brought online.", dev->address);
368 return bus_device_online(dev);
369}
370
371int hc_fun_offline(ddf_fun_t *fun)
372{
373 assert(fun);
374
375 device_t *dev = ddf_fun_data_get(fun);
376 assert(dev);
377
378 usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
379 return bus_device_offline(dev);
[21be46a]380}
381
[4daee7a]382/**
383 * @}
384 */
Note: See TracBrowser for help on using the repository browser.