HelenOS sources
This source file includes following definitions.
- hc_driver_main
- irq_handler
- interrupt_polling
- irq_code_clean
- hcd_ddf_setup_interrupts
- hc_dev_add
- hc_dev_remove
- hc_dev_gone
- hc_fun_online
- hc_fun_offline
#include <assert.h>
#include <async.h>
#include <ddf/interrupt.h>
#include <errno.h>
#include <macros.h>
#include <str_error.h>
#include <usb/debug.h>
#include <usb/descriptor.h>
#include <usb/request.h>
#include <usb_iface.h>
#include "bus.h"
#include "ddf_helpers.h"
#include "endpoint.h"
#include "usb_transfer_batch.h"
#include "hcd.h"
int hc_dev_add(ddf_dev_t *);
int hc_dev_remove(ddf_dev_t *);
int hc_dev_gone(ddf_dev_t *);
int hc_fun_online(ddf_fun_t *);
int hc_fun_offline(ddf_fun_t *);
static driver_ops_t hc_driver_ops = {
.dev_add = hc_dev_add,
.dev_remove = hc_dev_remove,
.dev_gone = hc_dev_gone,
.fun_online = hc_fun_online,
.fun_offline = hc_fun_offline,
};
static const hc_driver_t *hc_driver;
int hc_driver_main(const hc_driver_t *driver)
{
driver_t ddf_driver = {
.name = driver->name,
.driver_ops = &hc_driver_ops,
};
hc_driver = driver;
return ddf_driver_main(&ddf_driver);
}
static void irq_handler(ipc_call_t *call, void *arg)
{
hc_device_t *hcd = (hc_device_t *)arg;
const uint32_t status = ipc_get_arg1(call);
hcd->bus->ops->interrupt(hcd->bus, status);
}
static errno_t interrupt_polling(void *arg)
{
bus_t *bus = arg;
assert(bus);
if (!bus->ops->interrupt || !bus->ops->status)
return ENOTSUP;
uint32_t status = 0;
while (bus->ops->status(bus, &status) == EOK) {
bus->ops->interrupt(bus, status);
status = 0;
fibril_usleep(10000);
}
return EOK;
}
static inline void irq_code_clean(irq_code_t *code)
{
if (code) {
free(code->ranges);
free(code->cmds);
code->ranges = NULL;
code->cmds = NULL;
code->rangecount = 0;
code->cmdcount = 0;
}
}
static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd,
const hw_res_list_parsed_t *hw_res, cap_irq_handle_t *irq_handle)
{
assert(hcd);
irq_code_t irq_code = { 0 };
if (!hc_driver->irq_code_gen)
return ENOTSUP;
int irq;
errno_t ret;
ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq);
if (ret != EOK) {
usb_log_error("Failed to generate IRQ code: %s.",
str_error(ret));
return ret;
}
cap_irq_handle_t ihandle;
ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler,
(void *)hcd, &irq_code, &ihandle);
irq_code_clean(&irq_code);
if (ret != EOK) {
usb_log_error("Failed to register interrupt handler: %s.",
str_error(ret));
return ret;
}
ret = hcd_ddf_enable_interrupt(hcd, irq);
if (ret != EOK) {
usb_log_error("Failed to enable interrupts: %s.",
str_error(ret));
unregister_interrupt_handler(hcd->ddf_dev, ihandle);
return ret;
}
*irq_handle = ihandle;
return EOK;
}
errno_t hc_dev_add(ddf_dev_t *device)
{
errno_t ret = EOK;
assert(device);
if (!hc_driver->hc_add) {
usb_log_error("Driver '%s' does not support adding devices.",
hc_driver->name);
return ENOTSUP;
}
ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
if (ret != EOK) {
usb_log_error("Failed to setup HC device.");
return ret;
}
hc_device_t *hcd = dev_to_hcd(device);
hw_res_list_parsed_t hw_res;
ret = hcd_ddf_get_registers(hcd, &hw_res);
if (ret != EOK) {
usb_log_error("Failed to get register memory addresses "
"for `%s': %s.", ddf_dev_get_name(device),
str_error(ret));
goto err_hcd;
}
ret = hc_driver->hc_add(hcd, &hw_res);
if (ret != EOK) {
usb_log_error("Failed to init HCD.");
goto err_hw_res;
}
assert(hcd->bus);
hcd->irq_handle = CAP_NIL;
errno_t irqerr = hcd_ddf_setup_interrupts(hcd, &hw_res,
&hcd->irq_handle);
if (irqerr == EOK) {
usb_log_debug("Hw interrupts enabled.");
}
if (hc_driver->claim)
ret = hc_driver->claim(hcd);
if (ret != EOK) {
usb_log_error("Failed to claim `%s' for `%s': %s",
ddf_dev_get_name(device), hc_driver->name, str_error(ret));
goto err_irq;
}
if (hc_driver->start)
ret = hc_driver->start(hcd);
if (ret != EOK) {
usb_log_error("Failed to start HCD: %s.", str_error(ret));
goto err_irq;
}
const bus_ops_t *ops = hcd->bus->ops;
if (irqerr != EOK && ops->status) {
hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
if (!hcd->polling_fibril) {
usb_log_error("Failed to create polling fibril");
ret = ENOMEM;
goto err_started;
}
fibril_add_ready(hcd->polling_fibril);
usb_log_warning("Failed to enable interrupts: %s."
" Falling back to polling.", str_error(irqerr));
}
if (hc_driver->setup_root_hub)
ret = hc_driver->setup_root_hub(hcd);
if (ret != EOK) {
usb_log_error("Failed to setup HC root hub: %s.",
str_error(ret));
goto err_polling;
}
usb_log_info("Controlling new `%s' device `%s'.",
hc_driver->name, ddf_dev_get_name(device));
return EOK;
err_polling:
err_started:
if (hc_driver->stop)
hc_driver->stop(hcd);
err_irq:
unregister_interrupt_handler(device, hcd->irq_handle);
if (hc_driver->hc_remove)
hc_driver->hc_remove(hcd);
err_hw_res:
hw_res_list_parsed_clean(&hw_res);
err_hcd:
hcd_ddf_clean_hc(hcd);
return ret;
}
errno_t hc_dev_remove(ddf_dev_t *dev)
{
errno_t err;
hc_device_t *hcd = dev_to_hcd(dev);
if (hc_driver->stop)
if ((err = hc_driver->stop(hcd)))
return err;
unregister_interrupt_handler(dev, hcd->irq_handle);
if (hc_driver->hc_remove)
if ((err = hc_driver->hc_remove(hcd)))
return err;
hcd_ddf_clean_hc(hcd);
return EOK;
}
errno_t hc_dev_gone(ddf_dev_t *dev)
{
errno_t err = ENOTSUP;
hc_device_t *hcd = dev_to_hcd(dev);
if (hc_driver->hc_gone)
err = hc_driver->hc_gone(hcd);
hcd_ddf_clean_hc(hcd);
return err;
}
errno_t hc_fun_online(ddf_fun_t *fun)
{
assert(fun);
device_t *dev = ddf_fun_data_get(fun);
assert(dev);
usb_log_info("Device(%d): Requested to be brought online.", dev->address);
return bus_device_online(dev);
}
int hc_fun_offline(ddf_fun_t *fun)
{
assert(fun);
device_t *dev = ddf_fun_data_get(fun);
assert(dev);
usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
return bus_device_offline(dev);
}
HelenOS homepage, sources at GitHub