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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 4e2d387 was 7c3fb9b, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix block comment formatting (ccheck).

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