Ignore:
File:
1 edited

Legend:

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

    r5a6cc679 rb7fd2a0  
    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_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 errno_t 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 errno_t 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
     104errno_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
     110errno_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
     117errno_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
     124errno_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
     134errno_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
     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(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 */
     174errno_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");
    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 errno_t 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         int irq;
     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
     241        return ret;
     242}
     243
     244typedef struct {
     245        volatile unsigned done;
    165246        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);
     247        size_t size;
     248} sync_data_t;
     249
     250static 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
     259static 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 */
     268errno_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)
    189279                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  */
    207 errno_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));
    294         return EOK;
    295 
    296 err_polling:
    297         // TODO: Stop the polling fibril (refactor the interrupt_polling func)
    298         //
    299 err_started:
    300         if (hc_driver->stop)
    301                 hc_driver->stop(hcd);
    302 err_irq:
    303         unregister_interrupt_handler(device, hcd->irq_cap);
    304         if (hc_driver->hc_remove)
    305                 hc_driver->hc_remove(hcd);
    306 err_hw_res:
    307         hw_res_list_parsed_clean(&hw_res);
    308 err_hcd:
    309         hcd_ddf_clean_hc(hcd);
    310         return ret;
    311 }
    312 
    313 errno_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 
    335 errno_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 
    348 errno_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 
    359 int 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);
     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;
    368288}
    369289
Note: See TracChangeset for help on using the changeset viewer.