Ignore:
File:
1 edited

Legend:

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

    r296d22fc r68e5406  
    3232/** @file
    3333 *
    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  */
     34 */
     35
     36#include <usb/debug.h>
     37#include <usb/request.h>
    3738
    3839#include <assert.h>
    3940#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>
    4742#include <usb_iface.h>
    4843
    49 #include "bus.h"
    50 #include "ddf_helpers.h"
    51 #include "endpoint.h"
    52 #include "usb_transfer_batch.h"
    53 
    5444#include "hcd.h"
    5545
    56 int hc_dev_add(ddf_dev_t *);
    57 int hc_dev_remove(ddf_dev_t *);
    58 int hc_dev_gone(ddf_dev_t *);
    59 int hc_fun_online(ddf_fun_t *);
    60 int hc_fun_offline(ddf_fun_t *);
    61 
    62 static 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 
    70 static const hc_driver_t *hc_driver;
    71 
    72 /**
    73  * The main HC driver routine.
    74  */
    75 int 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  */
    97 static void irq_handler(ipc_callid_t iid, 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  */
    109 static int interrupt_polling(void *arg)
    110 {
    111         bus_t *bus = arg;
    112         assert(bus);
    113 
    114         if (!bus->ops->interrupt || !bus->ops->status)
     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 */
     51static int register_helper(endpoint_t *ep, void *arg)
     52{
     53        hcd_t *hcd = arg;
     54        assert(ep);
     55        assert(hcd);
     56        if (hcd->ops.ep_add_hook)
     57                return hcd->ops.ep_add_hook(hcd, ep);
     58        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 */
     65static 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 *    */
     78static 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 */
     95void 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
     104int 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
     110int 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
     117int 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
     124int 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
     134int 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
     142typedef 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
     149static void toggle_reset_callback(int 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 */
     174int 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");
    115204                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  */
    132 static 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  */
    156 static int hcd_ddf_setup_interrupts(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
    157 {
    158         assert(hcd);
    159         irq_code_t irq_code = {0};
    160 
    161         if (!hc_driver->irq_code_gen)
    162                 return ENOTSUP;
    163 
    164         const int irq = hc_driver->irq_code_gen(&irq_code, hcd, hw_res);
    165         if (irq < 0) {
    166                 usb_log_error("Failed to generate IRQ code: %s.",
    167                     str_error(irq));
    168                 return irq;
    169         }
    170 
    171         /* Register handler to avoid interrupt lockup */
    172         const int irq_cap = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler, &irq_code);
    173         irq_code_clean(&irq_code);
    174         if (irq_cap < 0) {
    175                 usb_log_error("Failed to register interrupt handler: %s.",
    176                     str_error(irq_cap));
    177                 return irq_cap;
    178         }
    179 
    180         /* Enable interrupts */
    181         int ret = hcd_ddf_enable_interrupt(hcd, irq);
    182         if (ret != EOK) {
    183                 usb_log_error("Failed to enable interrupts: %s.",
    184                     str_error(ret));
    185                 unregister_interrupt_handler(hcd->ddf_dev, irq_cap);
     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 int 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
     241        return ret;
     242}
     243
     244typedef struct {
     245        volatile unsigned done;
     246        int ret;
     247        size_t size;
     248} sync_data_t;
     249
     250static void transfer_in_cb(int 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
     259static void transfer_out_cb(int 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 */
     268int 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 int 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)
    186279                return ret;
    187         }
    188         return irq_cap;
    189 }
    190 
    191 /**
    192  * Initialize HC in memory of the driver.
    193  *
    194  * This function does all the preparatory work for hc and rh drivers:
    195  *  - gets device's hw resources
    196  *  - attempts to enable interrupts
    197  *  - registers interrupt handler
    198  *  - calls driver specific initialization
    199  *  - registers root hub
    200  *
    201  * @param device DDF instance of the device to use
    202  * @return Error code
    203  */
    204 int hc_dev_add(ddf_dev_t *device)
    205 {
    206         int ret = EOK;
    207         assert(device);
    208 
    209         if (!hc_driver->hc_add) {
    210                 usb_log_error("Driver '%s' does not support adding devices.", hc_driver->name);
    211                 return ENOTSUP;
    212         }
    213 
    214         ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
    215         if (ret != EOK) {
    216                 usb_log_error("Failed to setup HC device.");
    217                 return ret;
    218         }
    219 
    220         hc_device_t *hcd = dev_to_hcd(device);
    221 
    222         hw_res_list_parsed_t hw_res;
    223         ret = hcd_ddf_get_registers(hcd, &hw_res);
    224         if (ret != EOK) {
    225                 usb_log_error("Failed to get register memory addresses "
    226                     "for `%s': %s.", ddf_dev_get_name(device),
    227                     str_error(ret));
    228                 goto err_hcd;
    229         }
    230 
    231         ret = hc_driver->hc_add(hcd, &hw_res);
    232         if (ret != EOK) {
    233                 usb_log_error("Failed to init HCD.");
    234                 goto err_hw_res;
    235         }
    236 
    237         assert(hcd->bus);
    238 
    239         /* Setup interrupts  */
    240         hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res);
    241         if (hcd->irq_cap >= 0) {
    242                 usb_log_debug("Hw interrupts enabled.");
    243         }
    244 
    245         /* Claim the device from BIOS */
    246         if (hc_driver->claim)
    247                 ret = hc_driver->claim(hcd);
    248         if (ret != EOK) {
    249                 usb_log_error("Failed to claim `%s' for `%s': %s",
    250                     ddf_dev_get_name(device), hc_driver->name, str_error(ret));
    251                 goto err_irq;
    252         }
    253 
    254         /* Start hw */
    255         if (hc_driver->start)
    256                 ret = hc_driver->start(hcd);
    257         if (ret != EOK) {
    258                 usb_log_error("Failed to start HCD: %s.", str_error(ret));
    259                 goto err_irq;
    260         }
    261 
    262         const bus_ops_t *ops = hcd->bus->ops;
    263 
    264         /* Need working irq replacement to setup root hub */
    265         if (hcd->irq_cap < 0 && ops->status) {
    266                 hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
    267                 if (!hcd->polling_fibril) {
    268                         usb_log_error("Failed to create polling fibril");
    269                         ret = ENOMEM;
    270                         goto err_started;
    271                 }
    272                 fibril_add_ready(hcd->polling_fibril);
    273                 usb_log_warning("Failed to enable interrupts: %s."
    274                     " Falling back to polling.", str_error(hcd->irq_cap));
    275         }
    276 
    277         /*
    278          * Creating root hub registers a new USB device so HC
    279          * needs to be ready at this time.
    280          */
    281         if (hc_driver->setup_root_hub)
    282                 ret = hc_driver->setup_root_hub(hcd);
    283         if (ret != EOK) {
    284                 usb_log_error("Failed to setup HC root hub: %s.",
    285                     str_error(ret));
    286                 goto err_polling;
    287         }
    288 
    289         usb_log_info("Controlling new `%s' device `%s'.",
    290            hc_driver->name, ddf_dev_get_name(device));
    291         return EOK;
    292 
    293 err_polling:
    294         // TODO: Stop the polling fibril (refactor the interrupt_polling func)
    295         //
    296 err_started:
    297         if (hc_driver->stop)
    298                 hc_driver->stop(hcd);
    299 err_irq:
    300         unregister_interrupt_handler(device, hcd->irq_cap);
    301         if (hc_driver->hc_remove)
    302                 hc_driver->hc_remove(hcd);
    303 err_hw_res:
    304         hw_res_list_parsed_clean(&hw_res);
    305 err_hcd:
    306         hcd_ddf_clean_hc(hcd);
    307         return ret;
    308 }
    309 
    310 int hc_dev_remove(ddf_dev_t *dev)
    311 {
    312         int err;
    313         hc_device_t *hcd = dev_to_hcd(dev);
    314 
    315         if (hc_driver->stop)
    316                 if ((err = hc_driver->stop(hcd)))
    317                         return err;
    318 
    319         unregister_interrupt_handler(dev, hcd->irq_cap);
    320 
    321         if (hc_driver->hc_remove)
    322                 if ((err = hc_driver->hc_remove(hcd)))
    323                         return err;
    324 
    325         hcd_ddf_clean_hc(hcd);
    326 
    327         // TODO probably not complete
    328 
    329         return EOK;
    330 }
    331 
    332 int hc_dev_gone(ddf_dev_t *dev)
    333 {
    334         int err = ENOTSUP;
    335         hc_device_t *hcd = dev_to_hcd(dev);
    336 
    337         if (hc_driver->hc_gone)
    338                 err = hc_driver->hc_gone(hcd);
    339 
    340         hcd_ddf_clean_hc(hcd);
    341 
    342         return err;
    343 }
    344 
    345 int hc_fun_online(ddf_fun_t *fun)
    346 {
    347         assert(fun);
    348 
    349         device_t *dev = ddf_fun_data_get(fun);
    350         assert(dev);
    351 
    352         usb_log_info("Device(%d): Requested to be brought online.", dev->address);
    353         return bus_device_online(dev);
    354 }
    355 
    356 int hc_fun_offline(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 taken offline.", dev->address);
    364         return bus_device_offline(dev);
     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;
    365288}
    366289
Note: See TracChangeset for help on using the changeset viewer.