Changeset df6ded8 in mainline for uspace/lib/usbhost/src/hcd.c


Ignore:
Timestamp:
2018-02-28T16:37:50Z (6 years ago)
Author:
Jakub Jermar <jakub@…>
Branches:
lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
Children:
1b20da0
Parents:
f5e5f73 (diff), b2dca8de (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
Jakub Jermar <jakub@…> (2018-02-28 16:06:42)
git-committer:
Jakub Jermar <jakub@…> (2018-02-28 16:37:50)
Message:

Merge github.com:helenos-xhci-team/helenos

This commit merges support for USB 3 and generally refactors, fixes,
extends and cleans up the existing USB framework.

Notable additions and features:

  • new host controller driver has been implemented to control various xHC models (among others, NEC Renesas uPD720200)
  • isochronous data transfer mode
  • support for explicit USB device removal
  • USB tablet driver
File:
1 edited

Legend:

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

    rf5e5f73 rdf6ded8  
    11/*
    22 * Copyright (c) 2011 Jan Vesely
     3 * Copyright (c) 2018 Ondrej Hlavaty
    34 * All rights reserved.
    45 *
     
    3233/** @file
    3334 *
    34  */
    35 
    36 #include <usb/debug.h>
    37 #include <usb/request.h>
     35 * Host controller driver framework. Encapsulates DDF device of HC to an
     36 * hc_device_t, which is passed to driver implementing hc_driver_t.
     37 */
    3838
    3939#include <assert.h>
    4040#include <async.h>
     41#include <ddf/interrupt.h>
    4142#include <errno.h>
     43#include <macros.h>
     44#include <str_error.h>
     45#include <usb/debug.h>
     46#include <usb/descriptor.h>
     47#include <usb/request.h>
    4248#include <usb_iface.h>
    4349
     50#include "bus.h"
     51#include "ddf_helpers.h"
     52#include "endpoint.h"
     53#include "usb_transfer_batch.h"
     54
    4455#include "hcd.h"
    4556
    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);
     57int hc_dev_add(ddf_dev_t *);
     58int hc_dev_remove(ddf_dev_t *);
     59int hc_dev_gone(ddf_dev_t *);
     60int hc_fun_online(ddf_fun_t *);
     61int hc_fun_offline(ddf_fun_t *);
     62
     63static driver_ops_t hc_driver_ops = {
     64        .dev_add = hc_dev_add,
     65        .dev_remove = hc_dev_remove,
     66        .dev_gone = hc_dev_gone,
     67        .fun_online = hc_fun_online,
     68        .fun_offline = hc_fun_offline,
     69};
     70
     71static const hc_driver_t *hc_driver;
     72
     73/**
     74 * The main HC driver routine.
     75 */
     76int hc_driver_main(const hc_driver_t *driver)
     77{
     78        driver_t ddf_driver = {
     79                .name = driver->name,
     80                .driver_ops = &hc_driver_ops,
     81        };
     82
     83        /* Remember ops to call. */
     84        hc_driver = driver;
     85
     86        return ddf_driver_main(&ddf_driver);
     87}
     88
     89/**
     90 * IRQ handling callback. Call the bus operation.
     91 *
     92 * Currently, there is a bus ops lookup to find the interrupt handler. So far,
     93 * the mechanism is too flexible, as it allows different instances of HC to
     94 * have different IRQ handlers, disallowing us to optimize the lookup here.
     95 * TODO: Make the bus mechanism less flexible in irq handling and remove the
     96 * lookup.
     97 */
     98static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
     99{
     100        assert(dev);
     101        hc_device_t *hcd = dev_to_hcd(dev);
     102
     103        const uint32_t status = IPC_GET_ARG1(*call);
     104        hcd->bus->ops->interrupt(hcd->bus, status);
     105}
     106
     107/**
     108 * Worker for the HW interrupt replacement fibril.
     109 */
     110static errno_t interrupt_polling(void *arg)
     111{
     112        bus_t *bus = arg;
     113        assert(bus);
     114
     115        if (!bus->ops->interrupt || !bus->ops->status)
     116                return ENOTSUP;
     117
     118        uint32_t status = 0;
     119        while (bus->ops->status(bus, &status) == EOK) {
     120                bus->ops->interrupt(bus, status);
     121                status = 0;
     122                /* We should wait 1 frame - 1ms here, but this polling is a
     123                 * lame crutch anyway so don't hog the system. 10ms is still
     124                 * good enough for emergency mode */
     125                async_usleep(10000);
     126        }
     127        return EOK;
     128}
     129
     130/**
     131 * Clean the IRQ code bottom-half.
     132 */
     133static inline void irq_code_clean(irq_code_t *code)
     134{
     135        if (code) {
     136                free(code->ranges);
     137                free(code->cmds);
     138                code->ranges = NULL;
     139                code->cmds = NULL;
     140                code->rangecount = 0;
     141                code->cmdcount = 0;
     142        }
     143}
     144
     145/**
     146 * Register an interrupt handler. If there is a callback to setup the bottom half,
     147 * invoke it and register it. Register for notifications.
     148 *
     149 * If this method fails, a polling fibril is started instead.
     150 *
     151 * @param[in] hcd Host controller device.
     152 * @param[in] hw_res Resources to be used.
     153 *
     154 * @return IRQ capability handle on success.
     155 * @return Negative error code.
     156 */
     157static errno_t hcd_ddf_setup_interrupts(hc_device_t *hcd,
     158    const hw_res_list_parsed_t *hw_res)
     159{
    55160        assert(hcd);
    56         if (hcd->ops.ep_add_hook)
    57                 return hcd->ops.ep_add_hook(hcd, ep);
     161        irq_code_t irq_code = { 0 };
     162
     163        if (!hc_driver->irq_code_gen)
     164                return ENOTSUP;
     165
     166        int irq;
     167        errno_t ret;
     168        ret = hc_driver->irq_code_gen(&irq_code, hcd, hw_res, &irq);
     169        if (ret != EOK) {
     170                usb_log_error("Failed to generate IRQ code: %s.",
     171                    str_error(irq));
     172                return irq;
     173        }
     174
     175        /* Register handler to avoid interrupt lockup */
     176        int irq_cap;
     177        ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler,
     178            &irq_code, &irq_cap);
     179        irq_code_clean(&irq_code);
     180        if (ret != EOK) {
     181                usb_log_error("Failed to register interrupt handler: %s.",
     182                    str_error(irq_cap));
     183                return irq_cap;
     184        }
     185
     186        /* Enable interrupts */
     187        ret = hcd_ddf_enable_interrupt(hcd, irq);
     188        if (ret != EOK) {
     189                usb_log_error("Failed to enable interrupts: %s.",
     190                    str_error(ret));
     191                unregister_interrupt_handler(hcd->ddf_dev, irq_cap);
     192                return ret;
     193        }
     194        return irq_cap;
     195}
     196
     197/**
     198 * Initialize HC in memory of the driver.
     199 *
     200 * This function does all the preparatory work for hc and rh drivers:
     201 *  - gets device's hw resources
     202 *  - attempts to enable interrupts
     203 *  - registers interrupt handler
     204 *  - calls driver specific initialization
     205 *  - registers root hub
     206 *
     207 * @param device DDF instance of the device to use
     208 * @return Error code
     209 */
     210errno_t hc_dev_add(ddf_dev_t *device)
     211{
     212        errno_t ret = EOK;
     213        assert(device);
     214
     215        if (!hc_driver->hc_add) {
     216                usb_log_error("Driver '%s' does not support adding devices.",
     217                    hc_driver->name);
     218                return ENOTSUP;
     219        }
     220
     221        ret = hcd_ddf_setup_hc(device, hc_driver->hc_device_size);
     222        if (ret != EOK) {
     223                usb_log_error("Failed to setup HC device.");
     224                return ret;
     225        }
     226
     227        hc_device_t *hcd = dev_to_hcd(device);
     228
     229        hw_res_list_parsed_t hw_res;
     230        ret = hcd_ddf_get_registers(hcd, &hw_res);
     231        if (ret != EOK) {
     232                usb_log_error("Failed to get register memory addresses "
     233                    "for `%s': %s.", ddf_dev_get_name(device),
     234                    str_error(ret));
     235                goto err_hcd;
     236        }
     237
     238        ret = hc_driver->hc_add(hcd, &hw_res);
     239        if (ret != EOK) {
     240                usb_log_error("Failed to init HCD.");
     241                goto err_hw_res;
     242        }
     243
     244        assert(hcd->bus);
     245
     246        /* Setup interrupts  */
     247        hcd->irq_cap = hcd_ddf_setup_interrupts(hcd, &hw_res);
     248        if (hcd->irq_cap >= 0) {
     249                usb_log_debug("Hw interrupts enabled.");
     250        }
     251
     252        /* Claim the device from BIOS */
     253        if (hc_driver->claim)
     254                ret = hc_driver->claim(hcd);
     255        if (ret != EOK) {
     256                usb_log_error("Failed to claim `%s' for `%s': %s",
     257                    ddf_dev_get_name(device), hc_driver->name, str_error(ret));
     258                goto err_irq;
     259        }
     260
     261        /* Start hw */
     262        if (hc_driver->start)
     263                ret = hc_driver->start(hcd);
     264        if (ret != EOK) {
     265                usb_log_error("Failed to start HCD: %s.", str_error(ret));
     266                goto err_irq;
     267        }
     268
     269        const bus_ops_t *ops = hcd->bus->ops;
     270
     271        /* Need working irq replacement to setup root hub */
     272        if (hcd->irq_cap < 0 && ops->status) {
     273                hcd->polling_fibril = fibril_create(interrupt_polling, hcd->bus);
     274                if (!hcd->polling_fibril) {
     275                        usb_log_error("Failed to create polling fibril");
     276                        ret = ENOMEM;
     277                        goto err_started;
     278                }
     279                fibril_add_ready(hcd->polling_fibril);
     280                usb_log_warning("Failed to enable interrupts: %s."
     281                    " Falling back to polling.", str_error(hcd->irq_cap));
     282        }
     283
     284        /*
     285         * Creating root hub registers a new USB device so HC
     286         * needs to be ready at this time.
     287         */
     288        if (hc_driver->setup_root_hub)
     289                ret = hc_driver->setup_root_hub(hcd);
     290        if (ret != EOK) {
     291                usb_log_error("Failed to setup HC root hub: %s.",
     292                    str_error(ret));
     293                goto err_polling;
     294        }
     295
     296        usb_log_info("Controlling new `%s' device `%s'.",
     297            hc_driver->name, ddf_dev_get_name(device));
    58298        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 
     299
     300err_polling:
     301        // TODO: Stop the polling fibril (refactor the interrupt_polling func)
     302        //
     303err_started:
     304        if (hc_driver->stop)
     305                hc_driver->stop(hcd);
     306err_irq:
     307        unregister_interrupt_handler(device, hcd->irq_cap);
     308        if (hc_driver->hc_remove)
     309                hc_driver->hc_remove(hcd);
     310err_hw_res:
     311        hw_res_list_parsed_clean(&hw_res);
     312err_hcd:
     313        hcd_ddf_clean_hc(hcd);
    241314        return ret;
    242315}
    243316
    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;
     317errno_t hc_dev_remove(ddf_dev_t *dev)
     318{
     319        errno_t err;
     320        hc_device_t *hcd = dev_to_hcd(dev);
     321
     322        if (hc_driver->stop)
     323                if ((err = hc_driver->stop(hcd)))
     324                        return err;
     325
     326        unregister_interrupt_handler(dev, hcd->irq_cap);
     327
     328        if (hc_driver->hc_remove)
     329                if ((err = hc_driver->hc_remove(hcd)))
     330                        return err;
     331
     332        hcd_ddf_clean_hc(hcd);
     333
     334        // TODO probably not complete
     335
     336        return EOK;
     337}
     338
     339errno_t hc_dev_gone(ddf_dev_t *dev)
     340{
     341        errno_t err = ENOTSUP;
     342        hc_device_t *hcd = dev_to_hcd(dev);
     343
     344        if (hc_driver->hc_gone)
     345                err = hc_driver->hc_gone(hcd);
     346
     347        hcd_ddf_clean_hc(hcd);
     348
     349        return err;
     350}
     351
     352errno_t hc_fun_online(ddf_fun_t *fun)
     353{
     354        assert(fun);
     355
     356        device_t *dev = ddf_fun_data_get(fun);
     357        assert(dev);
     358
     359        usb_log_info("Device(%d): Requested to be brought online.", dev->address);
     360        return bus_device_online(dev);
     361}
     362
     363int hc_fun_offline(ddf_fun_t *fun)
     364{
     365        assert(fun);
     366
     367        device_t *dev = ddf_fun_data_get(fun);
     368        assert(dev);
     369
     370        usb_log_info("Device(%d): Requested to be taken offline.", dev->address);
     371        return bus_device_offline(dev);
    288372}
    289373
Note: See TracChangeset for help on using the changeset viewer.