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

Last change on this file since d92060f was eadaeae8, checked in by Jakub Jermar <jakub@…>, 8 years ago

Make capability handles type-safe

Define distinct pointer types for the handles of the supported
capability types and use them instead of integer handles. This makes it
virtually impossible to pass a non-handle or a handle of different type
instead of the proper handle. Also turn cap_handle_t into an "untyped"
capability handle that can be assigned to and from the "typed" handles.

This commit also fixes a bug in msim-con driver, which wrongly used the
IRQ number instead of the IRQ capability handle to unregister the IRQ.

This commit also fixes the wrong use of the capability handle instead
of error code in libusbhost.

  • Property mode set to 100644
File size: 9.3 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
3 * Copyright (c) 2018 Ondrej Hlavaty
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 *
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.
37 */
38
39#include <assert.h>
40#include <async.h>
41#include <ddf/interrupt.h>
42#include <errno.h>
43#include <macros.h>
44#include <str_error.h>
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"
51#include "ddf_helpers.h"
52#include "endpoint.h"
53#include "usb_transfer_batch.h"
54
55#include "hcd.h"
56
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 *);
62
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,
67 .fun_online = hc_fun_online,
68 .fun_offline = hc_fun_offline,
69};
70
71static const hc_driver_t *hc_driver;
72
73/**
74 * The main HC driver routine.
75 */
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
89/**
90 * IRQ handling callback. Call the bus operation.
91 *
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.
97 */
98static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
99{
100 assert(dev);
101 hc_device_t *hcd = dev_to_hcd(dev);
102
103 const uint32_t status = IPC_GET_ARG1(*call);
104 hcd->bus->ops->interrupt(hcd->bus, status);
105}
106
107/**
108 * Worker for the HW interrupt replacement fibril.
109 */
110static errno_t interrupt_polling(void *arg)
111{
112 bus_t *bus = arg;
113 assert(bus);
114
115 if (!bus->ops->interrupt || !bus->ops->status)
116 return ENOTSUP;
117
118 uint32_t status = 0;
119 while (bus->ops->status(bus, &status) == EOK) {
120 bus->ops->interrupt(bus, status);
121 status = 0;
122 /* We should wait 1 frame - 1ms here, but this polling is a
123 * lame crutch anyway so don't hog the system. 10ms is still
124 * good enough for emergency mode */
125 async_usleep(10000);
126 }
127 return EOK;
128}
129
130/**
131 * Clean the IRQ code bottom-half.
132 */
133static inline void irq_code_clean(irq_code_t *code)
134{
135 if (code) {
136 free(code->ranges);
137 free(code->cmds);
138 code->ranges = NULL;
139 code->cmds = NULL;
140 code->rangecount = 0;
141 code->cmdcount = 0;
142 }
143}
144
145/**
146 * Register an interrupt handler. If there is a callback to setup the bottom half,
147 * invoke it and register it. Register for notifications.
148 *
149 * If this method fails, a polling fibril is started instead.
150 *
151 * @param[in] hcd Host controller device.
152 * @param[in] hw_res Resources to be used.
153 * @param[out] irq_handle Storage for the returned IRQ handle
154 *
155 * @return Error code.
156 */
157static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd,
158 const hw_res_list_parsed_t *hw_res, cap_irq_handle_t *irq_handle)
159{
160 assert(hcd);
161 irq_code_t irq_code = { 0 };
162
163 if (!hc_driver->irq_code_gen)
164 return ENOTSUP;
165
166 int irq;
167 errno_t ret;
168 ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq);
169 if (ret != EOK) {
170 usb_log_error("Failed to generate IRQ code: %s.",
171 str_error(ret));
172 return ret;
173 }
174
175 /* Register handler to avoid interrupt lockup */
176 cap_irq_handle_t ihandle;
177 ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler,
178 &irq_code, &ihandle);
179 irq_code_clean(&irq_code);
180 if (ret != EOK) {
181 usb_log_error("Failed to register interrupt handler: %s.",
182 str_error(ret));
183 return ret;
184 }
185
186 /* Enable interrupts */
187 ret = hcd_ddf_enable_interrupt(hcd, irq);
188 if (ret != EOK) {
189 usb_log_error("Failed to enable interrupts: %s.",
190 str_error(ret));
191 unregister_interrupt_handler(hcd->ddf_dev, ihandle);
192 return ret;
193 }
194
195 *irq_handle = ihandle;
196 return EOK;
197}
198
199/**
200 * Initialize HC in memory of the driver.
201 *
202 * This function does all the preparatory work for hc and rh drivers:
203 * - gets device's hw resources
204 * - attempts to enable interrupts
205 * - registers interrupt handler
206 * - calls driver specific initialization
207 * - registers root hub
208 *
209 * @param device DDF instance of the device to use
210 * @return Error code
211 */
212errno_t hc_dev_add(ddf_dev_t *device)
213{
214 errno_t ret = EOK;
215 assert(device);
216
217 if (!hc_driver->hc_add) {
218 usb_log_error("Driver '%s' does not support adding devices.",
219 hc_driver->name);
220 return ENOTSUP;
221 }
222
223 ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
224 if (ret != EOK) {
225 usb_log_error("Failed to setup HC device.");
226 return ret;
227 }
228
229 hc_device_t *hcd = dev_to_hcd(device);
230
231 hw_res_list_parsed_t hw_res;
232 ret = hcd_ddf_get_registers(hcd, &hw_res);
233 if (ret != EOK) {
234 usb_log_error("Failed to get register memory addresses "
235 "for `%s': %s.", ddf_dev_get_name(device),
236 str_error(ret));
237 goto err_hcd;
238 }
239
240 ret = hc_driver->hc_add(hcd, &hw_res);
241 if (ret != EOK) {
242 usb_log_error("Failed to init HCD.");
243 goto err_hw_res;
244 }
245
246 assert(hcd->bus);
247
248 /* Setup interrupts */
249 hcd->irq_handle = CAP_NIL;
250 errno_t irqerr = hcd_ddf_setup_interrupts(hcd, &hw_res,
251 &hcd->irq_handle);
252 if (irqerr == EOK) {
253 usb_log_debug("Hw interrupts enabled.");
254 }
255
256 /* Claim the device from BIOS */
257 if (hc_driver->claim)
258 ret = hc_driver->claim(hcd);
259 if (ret != EOK) {
260 usb_log_error("Failed to claim `%s' for `%s': %s",
261 ddf_dev_get_name(device), hc_driver->name, str_error(ret));
262 goto err_irq;
263 }
264
265 /* Start hw */
266 if (hc_driver->start)
267 ret = hc_driver->start(hcd);
268 if (ret != EOK) {
269 usb_log_error("Failed to start HCD: %s.", str_error(ret));
270 goto err_irq;
271 }
272
273 const bus_ops_t *ops = hcd->bus->ops;
274
275 /* Need working irq replacement to setup root hub */
276 if (irqerr != EOK && ops->status) {
277 hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
278 if (!hcd->polling_fibril) {
279 usb_log_error("Failed to create polling fibril");
280 ret = ENOMEM;
281 goto err_started;
282 }
283 fibril_add_ready(hcd->polling_fibril);
284 usb_log_warning("Failed to enable interrupts: %s."
285 " Falling back to polling.", str_error(irqerr));
286 }
287
288 /*
289 * Creating root hub registers a new USB device so HC
290 * needs to be ready at this time.
291 */
292 if (hc_driver->setup_root_hub)
293 ret = hc_driver->setup_root_hub(hcd);
294 if (ret != EOK) {
295 usb_log_error("Failed to setup HC root hub: %s.",
296 str_error(ret));
297 goto err_polling;
298 }
299
300 usb_log_info("Controlling new `%s' device `%s'.",
301 hc_driver->name, ddf_dev_get_name(device));
302 return EOK;
303
304err_polling:
305 // TODO: Stop the polling fibril (refactor the interrupt_polling func)
306 //
307err_started:
308 if (hc_driver->stop)
309 hc_driver->stop(hcd);
310err_irq:
311 unregister_interrupt_handler(device, hcd->irq_handle);
312 if (hc_driver->hc_remove)
313 hc_driver->hc_remove(hcd);
314err_hw_res:
315 hw_res_list_parsed_clean(&hw_res);
316err_hcd:
317 hcd_ddf_clean_hc(hcd);
318 return ret;
319}
320
321errno_t hc_dev_remove(ddf_dev_t *dev)
322{
323 errno_t err;
324 hc_device_t *hcd = dev_to_hcd(dev);
325
326 if (hc_driver->stop)
327 if ((err = hc_driver->stop(hcd)))
328 return err;
329
330 unregister_interrupt_handler(dev, hcd->irq_handle);
331
332 if (hc_driver->hc_remove)
333 if ((err = hc_driver->hc_remove(hcd)))
334 return err;
335
336 hcd_ddf_clean_hc(hcd);
337
338 // TODO probably not complete
339
340 return EOK;
341}
342
343errno_t hc_dev_gone(ddf_dev_t *dev)
344{
345 errno_t err = ENOTSUP;
346 hc_device_t *hcd = dev_to_hcd(dev);
347
348 if (hc_driver->hc_gone)
349 err = hc_driver->hc_gone(hcd);
350
351 hcd_ddf_clean_hc(hcd);
352
353 return err;
354}
355
356errno_t hc_fun_online(ddf_fun_t *fun)
357{
358 assert(fun);
359
360 device_t *dev = ddf_fun_data_get(fun);
361 assert(dev);
362
363 usb_log_info("Device(%d): Requested to be brought online.", dev->address);
364 return bus_device_online(dev);
365}
366
367int hc_fun_offline(ddf_fun_t *fun)
368{
369 assert(fun);
370
371 device_t *dev = ddf_fun_data_get(fun);
372 assert(dev);
373
374 usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
375 return bus_device_offline(dev);
376}
377
378
379/**
380 * @}
381 */
Note: See TracBrowser for help on using the repository browser.