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

Last change on this file was 8300c72, checked in by Jiri Svoboda <jiri@…>, 4 months ago

Quiesce devices before proceeding with shutdown.

Only implemented for e1k, uhci and xhci.

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