Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/lib/usbhost/src/hcd.c

    rb7fd2a0 r5a6cc679  
    3232/** @file
    3333 *
    34  */
    35 
    36 #include <usb/debug.h>
    37 #include <usb/request.h>
     34 * Host controller driver framework. Encapsulates DDF device of HC to an
     35 * hc_device_t, which is passed to driver implementing hc_driver_t.
     36 */
    3837
    3938#include <assert.h>
    4039#include <async.h>
     40#include <ddf/interrupt.h>
    4141#include <errno.h>
     42#include <macros.h>
     43#include <str_error.h>
     44#include <usb/debug.h>
     45#include <usb/descriptor.h>
     46#include <usb/request.h>
    4247#include <usb_iface.h>
    4348
     49#include "bus.h"
     50#include "ddf_helpers.h"
     51#include "endpoint.h"
     52#include "usb_transfer_batch.h"
     53
    4454#include "hcd.h"
    4555
    46 /** Calls ep_add_hook upon endpoint registration.
    47  * @param ep Endpoint to be registered.
    48  * @param arg hcd_t in disguise.
    49  * @return Error code.
    50  */
    51 static errno_t register_helper(endpoint_t *ep, void *arg)
    52 {
    53         hcd_t *hcd = arg;
    54         assert(ep);
     56int hc_dev_add(ddf_dev_t *);
     57int hc_dev_remove(ddf_dev_t *);
     58int hc_dev_gone(ddf_dev_t *);
     59int hc_fun_online(ddf_fun_t *);
     60int hc_fun_offline(ddf_fun_t *);
     61
     62static driver_ops_t hc_driver_ops = {
     63        .dev_add = hc_dev_add,
     64        .dev_remove = hc_dev_remove,
     65        .dev_gone = hc_dev_gone,
     66        .fun_online = hc_fun_online,
     67        .fun_offline = hc_fun_offline,
     68};
     69
     70static const hc_driver_t *hc_driver;
     71
     72/**
     73 * The main HC driver routine.
     74 */
     75int hc_driver_main(const hc_driver_t *driver)
     76{
     77        driver_t ddf_driver = {
     78                .name = driver->name,
     79                .driver_ops = &hc_driver_ops,
     80        };
     81
     82        /* Remember ops to call. */
     83        hc_driver = driver;
     84
     85        return ddf_driver_main(&ddf_driver);
     86}
     87
     88/**
     89 * IRQ handling callback. Call the bus operation.
     90 *
     91 * Currently, there is a bus ops lookup to find the interrupt handler. So far,
     92 * the mechanism is too flexible, as it allows different instances of HC to
     93 * have different IRQ handlers, disallowing us to optimize the lookup here.
     94 * TODO: Make the bus mechanism less flexible in irq handling and remove the
     95 * lookup.
     96 */
     97static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
     98{
     99        assert(dev);
     100        hc_device_t *hcd = dev_to_hcd(dev);
     101
     102        const uint32_t status = IPC_GET_ARG1(*call);
     103        hcd->bus->ops->interrupt(hcd->bus, status);
     104}
     105
     106/**
     107 * Worker for the HW interrupt replacement fibril.
     108 */
     109static errno_t interrupt_polling(void *arg)
     110{
     111        bus_t *bus = arg;
     112        assert(bus);
     113
     114        if (!bus->ops->interrupt || !bus->ops->status)
     115                return ENOTSUP;
     116
     117        uint32_t status = 0;
     118        while (bus->ops->status(bus, &status) == EOK) {
     119                bus->ops->interrupt(bus, status);
     120                status = 0;
     121                /* We should wait 1 frame - 1ms here, but this polling is a
     122                 * lame crutch anyway so don't hog the system. 10ms is still
     123                 * good enough for emergency mode */
     124                async_usleep(10000);
     125        }
     126        return EOK;
     127}
     128
     129/**
     130 * Clean the IRQ code bottom-half.
     131 */
     132static inline void irq_code_clean(irq_code_t *code)
     133{
     134        if (code) {
     135                free(code->ranges);
     136                free(code->cmds);
     137                code->ranges = NULL;
     138                code->cmds = NULL;
     139                code->rangecount = 0;
     140                code->cmdcount = 0;
     141        }
     142}
     143
     144/**
     145 * Register an interrupt handler. If there is a callback to setup the bottom half,
     146 * invoke it and register it. Register for notifications.
     147 *
     148 * If this method fails, a polling fibril is started instead.
     149 *
     150 * @param[in] hcd Host controller device.
     151 * @param[in] hw_res Resources to be used.
     152 *
     153 * @return IRQ capability handle on success.
     154 * @return Negative error code.
     155 */
     156static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
     157{
    55158        assert(hcd);
    56         if (hcd->ops.ep_add_hook)
    57                 return hcd->ops.ep_add_hook(hcd, ep);
     159        irq_code_t irq_code = {0};
     160
     161        if (!hc_driver->irq_code_gen)
     162                return ENOTSUP;
     163
     164        int irq;
     165        errno_t ret;
     166        ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq);
     167        if (ret != EOK) {
     168                usb_log_error("Failed to generate IRQ code: %s.",
     169                    str_error(irq));
     170                return irq;
     171        }
     172
     173        /* Register handler to avoid interrupt lockup */
     174        int irq_cap;
     175        ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, &irq_code, &irq_cap);
     176        irq_code_clean(&irq_code);
     177        if (ret != EOK) {
     178                usb_log_error("Failed to register interrupt handler: %s.",
     179                    str_error(irq_cap));
     180                return irq_cap;
     181        }
     182
     183        /* Enable interrupts */
     184        ret = hcd_ddf_enable_interrupt(hcd, irq);
     185        if (ret != EOK) {
     186                usb_log_error("Failed to enable interrupts: %s.",
     187                    str_error(ret));
     188                unregister_interrupt_handler(hcd->ddf_dev, irq_cap);
     189                return ret;
     190        }
     191        return irq_cap;
     192}
     193
     194/**
     195 * Initialize HC in memory of the driver.
     196 *
     197 * This function does all the preparatory work for hc and rh drivers:
     198 *  - gets device's hw resources
     199 *  - attempts to enable interrupts
     200 *  - registers interrupt handler
     201 *  - calls driver specific initialization
     202 *  - registers root hub
     203 *
     204 * @param device DDF instance of the device to use
     205 * @return Error code
     206 */
     207errno_t hc_dev_add(ddf_dev_t *device)
     208{
     209        errno_t ret = EOK;
     210        assert(device);
     211
     212        if (!hc_driver->hc_add) {
     213                usb_log_error("Driver '%s' does not support adding devices.", hc_driver->name);
     214                return ENOTSUP;
     215        }
     216
     217        ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
     218        if (ret != EOK) {
     219                usb_log_error("Failed to setup HC device.");
     220                return ret;
     221        }
     222
     223        hc_device_t *hcd = dev_to_hcd(device);
     224
     225        hw_res_list_parsed_t hw_res;
     226        ret = hcd_ddf_get_registers(hcd, &hw_res);
     227        if (ret != EOK) {
     228                usb_log_error("Failed to get register memory addresses "
     229                    "for `%s': %s.", ddf_dev_get_name(device),
     230                    str_error(ret));
     231                goto err_hcd;
     232        }
     233
     234        ret = hc_driver->hc_add(hcd, &hw_res);
     235        if (ret != EOK) {
     236                usb_log_error("Failed to init HCD.");
     237                goto err_hw_res;
     238        }
     239
     240        assert(hcd->bus);
     241
     242        /* Setup interrupts  */
     243        hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res);
     244        if (hcd->irq_cap >= 0) {
     245                usb_log_debug("Hw interrupts enabled.");
     246        }
     247
     248        /* Claim the device from BIOS */
     249        if (hc_driver->claim)
     250                ret = hc_driver->claim(hcd);
     251        if (ret != EOK) {
     252                usb_log_error("Failed to claim `%s' for `%s': %s",
     253                    ddf_dev_get_name(device), hc_driver->name, str_error(ret));
     254                goto err_irq;
     255        }
     256
     257        /* Start hw */
     258        if (hc_driver->start)
     259                ret = hc_driver->start(hcd);
     260        if (ret != EOK) {
     261                usb_log_error("Failed to start HCD: %s.", str_error(ret));
     262                goto err_irq;
     263        }
     264
     265        const bus_ops_t *ops = hcd->bus->ops;
     266
     267        /* Need working irq replacement to setup root hub */
     268        if (hcd->irq_cap < 0 && ops->status) {
     269                hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
     270                if (!hcd->polling_fibril) {
     271                        usb_log_error("Failed to create polling fibril");
     272                        ret = ENOMEM;
     273                        goto err_started;
     274                }
     275                fibril_add_ready(hcd->polling_fibril);
     276                usb_log_warning("Failed to enable interrupts: %s."
     277                    " Falling back to polling.", str_error(hcd->irq_cap));
     278        }
     279
     280        /*
     281         * Creating root hub registers a new USB device so HC
     282         * needs to be ready at this time.
     283         */
     284        if (hc_driver->setup_root_hub)
     285                ret = hc_driver->setup_root_hub(hcd);
     286        if (ret != EOK) {
     287                usb_log_error("Failed to setup HC root hub: %s.",
     288                    str_error(ret));
     289                goto err_polling;
     290        }
     291
     292        usb_log_info("Controlling new `%s' device `%s'.",
     293           hc_driver->name, ddf_dev_get_name(device));
    58294        return EOK;
    59 }
    60 
    61 /** Calls ep_remove_hook upon endpoint removal.
    62  * @param ep Endpoint to be unregistered.
    63  * @param arg hcd_t in disguise.
    64  */
    65 static void unregister_helper(endpoint_t *ep, void *arg)
    66 {
    67         hcd_t *hcd = arg;
    68         assert(ep);
    69         assert(hcd);
    70         if (hcd->ops.ep_remove_hook)
    71                 hcd->ops.ep_remove_hook(hcd, ep);
    72 }
    73 
    74 /** Calls ep_remove_hook upon endpoint removal. Prints warning.
    75  *  * @param ep Endpoint to be unregistered.
    76  *   * @param arg hcd_t in disguise.
    77  *    */
    78 static void unregister_helper_warn(endpoint_t *ep, void *arg)
    79 {
    80         assert(ep);
    81         usb_log_warning("Endpoint %d:%d %s was left behind, removing.\n",
    82             ep->address, ep->endpoint, usb_str_direction(ep->direction));
    83         unregister_helper(ep, arg);
    84 }
    85 
    86 
    87 /** Initialize hcd_t structure.
    88  * Initializes device and endpoint managers. Sets data and hook pointer to NULL.
    89  *
    90  * @param hcd hcd_t structure to initialize, non-null.
    91  * @param max_speed Maximum supported USB speed (full, high).
    92  * @param bandwidth Available bandwidth, passed to endpoint manager.
    93  * @param bw_count Bandwidth compute function, passed to endpoint manager.
    94  */
    95 void hcd_init(hcd_t *hcd, usb_speed_t max_speed, size_t bandwidth,
    96     bw_count_func_t bw_count)
    97 {
    98         assert(hcd);
    99         usb_bus_init(&hcd->bus, bandwidth, bw_count, max_speed);
    100 
    101         hcd_set_implementation(hcd, NULL, NULL);
    102 }
    103 
    104 errno_t hcd_request_address(hcd_t *hcd, usb_speed_t speed, usb_address_t *address)
    105 {
    106         assert(hcd);
    107         return usb_bus_request_address(&hcd->bus, address, false, speed);
    108 }
    109 
    110 errno_t hcd_release_address(hcd_t *hcd, usb_address_t address)
    111 {
    112         assert(hcd);
    113         return usb_bus_remove_address(&hcd->bus, address,
    114             unregister_helper_warn, hcd);
    115 }
    116 
    117 errno_t hcd_reserve_default_address(hcd_t *hcd, usb_speed_t speed)
    118 {
    119         assert(hcd);
    120         usb_address_t address = 0;
    121         return usb_bus_request_address(&hcd->bus, &address, true, speed);
    122 }
    123 
    124 errno_t hcd_add_ep(hcd_t *hcd, usb_target_t target, usb_direction_t dir,
    125     usb_transfer_type_t type, size_t max_packet_size, unsigned packets,
    126     size_t size, usb_address_t tt_address, unsigned tt_port)
    127 {
    128         assert(hcd);
    129         return usb_bus_add_ep(&hcd->bus, target.address,
    130             target.endpoint, dir, type, max_packet_size, packets, size,
    131             register_helper, hcd, tt_address, tt_port);
    132 }
    133 
    134 errno_t hcd_remove_ep(hcd_t *hcd, usb_target_t target, usb_direction_t dir)
    135 {
    136         assert(hcd);
    137         return usb_bus_remove_ep(&hcd->bus, target.address,
    138             target.endpoint, dir, unregister_helper, hcd);
    139 }
    140 
    141 
    142 typedef struct {
    143         void *original_data;
    144         usbhc_iface_transfer_out_callback_t original_callback;
    145         usb_target_t target;
    146         hcd_t *hcd;
    147 } toggle_t;
    148 
    149 static void toggle_reset_callback(errno_t retval, void *arg)
    150 {
    151         assert(arg);
    152         toggle_t *toggle = arg;
    153         if (retval == EOK) {
    154                 usb_log_debug2("Reseting toggle on %d:%d.\n",
    155                     toggle->target.address, toggle->target.endpoint);
    156                 usb_bus_reset_toggle(&toggle->hcd->bus,
    157                     toggle->target, toggle->target.endpoint == 0);
    158         }
    159 
    160         toggle->original_callback(retval, toggle->original_data);
    161 }
    162 
    163 /** Prepare generic usb_transfer_batch and schedule it.
    164  * @param hcd Host controller driver.
    165  * @param fun DDF fun
    166  * @param target address and endpoint number.
    167  * @param setup_data Data to use in setup stage (Control communication type)
    168  * @param in Callback for device to host communication.
    169  * @param out Callback for host to device communication.
    170  * @param arg Callback parameter.
    171  * @param name Communication identifier (for nicer output).
    172  * @return Error code.
    173  */
    174 errno_t hcd_send_batch(
    175     hcd_t *hcd, usb_target_t target, usb_direction_t direction,
    176     void *data, size_t size, uint64_t setup_data,
    177     usbhc_iface_transfer_in_callback_t in,
    178     usbhc_iface_transfer_out_callback_t out, void *arg, const char* name)
    179 {
    180         assert(hcd);
    181 
    182         endpoint_t *ep = usb_bus_find_ep(&hcd->bus,
    183             target.address, target.endpoint, direction);
    184         if (ep == NULL) {
    185                 usb_log_error("Endpoint(%d:%d) not registered for %s.\n",
    186                     target.address, target.endpoint, name);
    187                 return ENOENT;
    188         }
    189 
    190         usb_log_debug2("%s %d:%d %zu(%zu).\n",
    191             name, target.address, target.endpoint, size, ep->max_packet_size);
    192 
    193         const size_t bw = bandwidth_count_usb11(
    194             ep->speed, ep->transfer_type, size, ep->max_packet_size);
    195         /* Check if we have enough bandwidth reserved */
    196         if (ep->bandwidth < bw) {
    197                 usb_log_error("Endpoint(%d:%d) %s needs %zu bw "
    198                     "but only %zu is reserved.\n",
    199                     ep->address, ep->endpoint, name, bw, ep->bandwidth);
    200                 return ENOSPC;
    201         }
    202         if (!hcd->ops.schedule) {
    203                 usb_log_error("HCD does not implement scheduler.\n");
    204                 return ENOTSUP;
    205         }
    206 
    207         /* Check for commands that reset toggle bit */
    208         if (ep->transfer_type == USB_TRANSFER_CONTROL) {
    209                 const int reset_toggle = usb_request_needs_toggle_reset(
    210                     (usb_device_request_setup_packet_t *) &setup_data);
    211                 if (reset_toggle >= 0) {
    212                         assert(out);
    213                         toggle_t *toggle = malloc(sizeof(toggle_t));
    214                         if (!toggle)
    215                                 return ENOMEM;
    216                         toggle->target.address = target.address;
    217                         toggle->target.endpoint = reset_toggle;
    218                         toggle->original_callback = out;
    219                         toggle->original_data = arg;
    220                         toggle->hcd = hcd;
    221 
    222                         arg = toggle;
    223                         out = toggle_reset_callback;
    224                 }
    225         }
    226 
    227         usb_transfer_batch_t *batch = usb_transfer_batch_create(
    228             ep, data, size, setup_data, in, out, arg);
    229         if (!batch) {
    230                 usb_log_error("Failed to create transfer batch.\n");
    231                 return ENOMEM;
    232         }
    233 
    234         const errno_t ret = hcd->ops.schedule(hcd, batch);
    235         if (ret != EOK)
    236                 usb_transfer_batch_destroy(batch);
    237 
    238         /* Drop our own reference to ep. */
    239         endpoint_del_ref(ep);
    240 
     295
     296err_polling:
     297        // TODO: Stop the polling fibril (refactor the interrupt_polling func)
     298        //
     299err_started:
     300        if (hc_driver->stop)
     301                hc_driver->stop(hcd);
     302err_irq:
     303        unregister_interrupt_handler(device, hcd->irq_cap);
     304        if (hc_driver->hc_remove)
     305                hc_driver->hc_remove(hcd);
     306err_hw_res:
     307        hw_res_list_parsed_clean(&hw_res);
     308err_hcd:
     309        hcd_ddf_clean_hc(hcd);
    241310        return ret;
    242311}
    243312
    244 typedef struct {
    245         volatile unsigned done;
    246         errno_t ret;
    247         size_t size;
    248 } sync_data_t;
    249 
    250 static void transfer_in_cb(errno_t ret, size_t size, void* data)
    251 {
    252         sync_data_t *d = data;
    253         assert(d);
    254         d->ret = ret;
    255         d->done = 1;
    256         d->size = size;
    257 }
    258 
    259 static void transfer_out_cb(errno_t ret, void* data)
    260 {
    261         sync_data_t *d = data;
    262         assert(data);
    263         d->ret = ret;
    264         d->done = 1;
    265 }
    266 
    267 /** this is really ugly version of sync usb communication */
    268 errno_t hcd_send_batch_sync(
    269     hcd_t *hcd, usb_target_t target, usb_direction_t dir,
    270     void *data, size_t size, uint64_t setup_data, const char* name, size_t *out_size)
    271 {
    272         assert(hcd);
    273         sync_data_t sd = { .done = 0, .ret = EBUSY, .size = size };
    274 
    275         const errno_t ret = hcd_send_batch(hcd, target, dir, data, size, setup_data,
    276             dir == USB_DIRECTION_IN ? transfer_in_cb : NULL,
    277             dir == USB_DIRECTION_OUT ? transfer_out_cb : NULL, &sd, name);
    278         if (ret != EOK)
    279                 return ret;
    280 
    281         while (!sd.done) {
    282                 async_usleep(1000);
    283         }
    284 
    285         if (sd.ret == EOK)
    286                 *out_size = sd.size;
    287         return sd.ret;
     313errno_t hc_dev_remove(ddf_dev_t *dev)
     314{
     315        errno_t err;
     316        hc_device_t *hcd = dev_to_hcd(dev);
     317
     318        if (hc_driver->stop)
     319                if ((err = hc_driver->stop(hcd)))
     320                        return err;
     321
     322        unregister_interrupt_handler(dev, hcd->irq_cap);
     323
     324        if (hc_driver->hc_remove)
     325                if ((err = hc_driver->hc_remove(hcd)))
     326                        return err;
     327
     328        hcd_ddf_clean_hc(hcd);
     329
     330        // TODO probably not complete
     331
     332        return EOK;
     333}
     334
     335errno_t hc_dev_gone(ddf_dev_t *dev)
     336{
     337        errno_t err = ENOTSUP;
     338        hc_device_t *hcd = dev_to_hcd(dev);
     339
     340        if (hc_driver->hc_gone)
     341                err = hc_driver->hc_gone(hcd);
     342
     343        hcd_ddf_clean_hc(hcd);
     344
     345        return err;
     346}
     347
     348errno_t hc_fun_online(ddf_fun_t *fun)
     349{
     350        assert(fun);
     351
     352        device_t *dev = ddf_fun_data_get(fun);
     353        assert(dev);
     354
     355        usb_log_info("Device(%d): Requested to be brought online.", dev->address);
     356        return bus_device_online(dev);
     357}
     358
     359int hc_fun_offline(ddf_fun_t *fun)
     360{
     361        assert(fun);
     362
     363        device_t *dev = ddf_fun_data_get(fun);
     364        assert(dev);
     365
     366        usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
     367        return bus_device_offline(dev);
    288368}
    289369
Note: See TracChangeset for help on using the changeset viewer.