Changeset c4e84ed6 in mainline


Ignore:
Timestamp:
2018-01-16T03:45:38Z (6 years ago)
Author:
Ondřej Hlavatý <aearsis@…>
Branches:
lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
Children:
c952abc4
Parents:
d2c3dcd
git-author:
Ondřej Hlavatý <aearsis@…> (2018-01-15 20:49:15)
git-committer:
Ondřej Hlavatý <aearsis@…> (2018-01-16 03:45:38)
Message:

usbhub: rewrite port handling

The state space of a usb hub port is a bit more complex than what was
there originally. Got rid of the active operations counting, and
replaced that with finite state machine. Fixes a lot of race conditions
and lack of synchronization when connect and disconnect events come very
fast.

Location:
uspace/drv/bus/usb/usbhub
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • uspace/drv/bus/usb/usbhub/port.c

    rd2c3dcd rc4e84ed6  
    4848#include "status.h"
    4949
    50 /** Information for fibril for device discovery. */
    51 struct add_device_phase1 {
    52         usb_hub_dev_t *hub;
    53         usb_hub_port_t *port;
    54         usb_speed_t speed;
    55 };
    56 
    57 static int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub);
    58 static void usb_hub_port_reset_completed(usb_hub_port_t *port,
    59     usb_hub_dev_t *hub, usb_port_status_t status);
    60 static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status);
    61 static int add_device_phase1_worker_fibril(void *arg);
    62 static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
    63     usb_speed_t speed);
    64 
    65 int usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub)
     50#define port_log(lvl, port, fmt, ...) do { usb_log_##lvl("(%p-%u): " fmt, (port->hub), (port->port_number), ##__VA_ARGS__); } while (0)
     51
     52/** Initialize hub port information.
     53 *
     54 * @param port Port to be initialized.
     55 */
     56void usb_hub_port_init(usb_hub_port_t *port, usb_hub_dev_t *hub, unsigned int port_number)
    6657{
    6758        assert(port);
    68         if (port->device_attached)
    69                 return usb_hub_port_device_gone(port, hub);
    70         return EOK;
    71 }
    72 
    73 /**
    74  * Clear feature on hub port.
    75  *
    76  * @param port Port structure.
    77  * @param feature Feature selector.
    78  * @return Operation result
    79  */
    80 int usb_hub_port_clear_feature(
    81     usb_hub_port_t *port, usb_hub_class_feature_t feature)
     59        memset(port, 0, sizeof(*port));
     60        fibril_mutex_initialize(&port->guard);
     61        fibril_condvar_initialize(&port->state_cv);
     62        port->hub = hub;
     63        port->port_number = port_number;
     64}
     65
     66/**
     67 * Utility method to change current port state and notify waiters.
     68 */
     69static void port_change_state(usb_hub_port_t *port, port_state_t state)
     70{
     71        assert(fibril_mutex_is_locked(&port->guard));
     72#define S(STATE) [PORT_##STATE] = #STATE
     73        static const char *state_names [] = {
     74            S(DISABLED), S(CONNECTED), S(ERROR), S(IN_RESET), S(ENABLED),
     75        };
     76#undef S
     77        port_log(debug, port, "%s ->%s", state_names[port->state], state_names[state]);
     78        port->state = state;
     79        fibril_condvar_broadcast(&port->state_cv);
     80}
     81
     82/**
     83 * Utility method to wait for a particular state.
     84 *
     85 * @warning Some states might not be reached because of an external error
     86 * condition (PORT_ENABLED).
     87 */
     88static void port_wait_state(usb_hub_port_t *port, port_state_t state)
     89{
     90        assert(fibril_mutex_is_locked(&port->guard));
     91        while (port->state != state)
     92                fibril_condvar_wait(&port->state_cv, &port->guard);
     93}
     94
     95/**
     96 * Inform the HC that the device on port is gone.
     97 */
     98static int usb_hub_port_device_gone(usb_hub_port_t *port)
    8299{
    83100        assert(port);
    84         const usb_device_request_setup_packet_t clear_request = {
    85                 .request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
    86                 .request = USB_DEVREQ_CLEAR_FEATURE,
    87                 .value = feature,
    88                 .index = port->port_number,
    89                 .length = 0,
    90         };
    91         return usb_pipe_control_write(port->control_pipe, &clear_request,
    92             sizeof(clear_request), NULL, 0);
    93 }
    94 
    95 /**
    96  * Set feature on hub port.
    97  *
    98  * @param port Port structure.
    99  * @param feature Feature selector.
    100  * @return Operation result
    101  */
    102 int usb_hub_port_set_feature(
    103     usb_hub_port_t *port, usb_hub_class_feature_t feature)
    104 {
    105         assert(port);
    106         const usb_device_request_setup_packet_t clear_request = {
    107                 .request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
    108                 .request = USB_DEVREQ_SET_FEATURE,
    109                 .index = port->port_number,
    110                 .value = feature,
    111                 .length = 0,
    112         };
    113         return usb_pipe_control_write(port->control_pipe, &clear_request,
    114             sizeof(clear_request), NULL, 0);
    115 }
    116 
    117 /**
    118  * Mark reset process as failed due to external reasons
    119  *
    120  * @param port Port structure
    121  */
    122 void usb_hub_port_reset_fail(usb_hub_port_t *port)
    123 {
    124         assert(port);
    125         fibril_mutex_lock(&port->mutex);
    126         if (port->reset_status == IN_RESET)
    127                 port->reset_status = RESET_FAIL;
    128         fibril_condvar_broadcast(&port->reset_cv);
    129         fibril_mutex_unlock(&port->mutex);
    130 }
    131 
    132 /**
    133  * Process interrupts on given port
    134  *
    135  * Accepts connection, over current and port reset change.
    136  * @param port port structure
    137  * @param hub hub representation
    138  */
    139 void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
    140 {
    141         assert(port);
    142         assert(hub);
    143         usb_log_debug2("(%p-%u): Interrupt.", hub, port->port_number);
    144 
    145         usb_port_status_t status = 0;
    146         const int opResult = get_port_status(port, &status);
    147         if (opResult != EOK) {
    148                 usb_log_error("(%p-%u): Failed to get port status: %s.", hub,
    149                     port->port_number, str_error(opResult));
    150                 return;
    151         }
    152 
    153         /* Connection change */
    154         if (status & USB_HUB_PORT_C_STATUS_CONNECTION) {
    155                 const bool connected =
    156                     (status & USB_HUB_PORT_STATUS_CONNECTION) != 0;
    157                 usb_log_debug("(%p-%u): Connection change: device %s.", hub,
    158                     port->port_number, connected ? "attached" : "removed");
    159 
    160                 /* ACK the change */
    161                 const int opResult = usb_hub_port_clear_feature(port,
    162                     USB_HUB_FEATURE_C_PORT_CONNECTION);
    163                 if (opResult != EOK) {
    164                         usb_log_warning("(%p-%u): Failed to clear "
    165                             "port-change-connection flag: %s.\n", hub,
    166                             port->port_number, str_error(opResult));
    167                 }
    168 
    169                 if (connected) {
    170                         const int opResult = create_add_device_fibril(port, hub,
    171                             usb_port_speed(status));
    172                         if (opResult != EOK) {
    173                                 usb_log_error("(%p-%u): Cannot handle change on"
    174                                    " port: %s.\n", hub, port->port_number,
    175                                    str_error(opResult));
    176                         }
    177                 } else {
    178                         /* Handle the case we were in reset */
    179                         // FIXME: usb_hub_port_reset_fail(port);
    180                         /* If enabled change was reported leave the removal
    181                          * to that handler, it shall ACK the change too. */
    182                         if (!(status & USB_HUB_PORT_C_STATUS_ENABLED)) {
    183                                 usb_hub_port_device_gone(port, hub);
    184                         }
    185                 }
    186         }
    187 
    188         /* Enable change, ports are automatically disabled on errors. */
    189         if (status & USB_HUB_PORT_C_STATUS_ENABLED) {
    190                 // TODO: maybe HS reset failed?
    191                 usb_log_info("(%p-%u): Port disabled because of errors.", hub,
    192                    port->port_number);
    193                 usb_hub_port_device_gone(port, hub);
    194                 const int rc = usb_hub_port_clear_feature(port,
    195                         USB_HUB_FEATURE_C_PORT_ENABLE);
    196                 if (rc != EOK) {
    197                         usb_log_error("(%p-%u): Failed to clear port enable "
    198                             "change feature: %s.", hub, port->port_number,
    199                             str_error(rc));
    200                 }
    201 
    202         }
    203 
    204         /* Suspend change */
    205         if (status & USB_HUB_PORT_C_STATUS_SUSPEND) {
    206                 usb_log_error("(%p-%u): Port went to suspend state, this should"
    207                     " NOT happen as we do not support suspend state!", hub,
    208                     port->port_number);
    209                 const int rc = usb_hub_port_clear_feature(port,
    210                         USB_HUB_FEATURE_C_PORT_SUSPEND);
    211                 if (rc != EOK) {
    212                         usb_log_error("(%p-%u): Failed to clear port suspend "
    213                             "change feature: %s.", hub, port->port_number,
    214                             str_error(rc));
    215                 }
    216         }
    217 
    218         /* Over current */
    219         if (status & USB_HUB_PORT_C_STATUS_OC) {
    220                 usb_log_debug("(%p-%u): Port OC reported!.", hub,
    221                     port->port_number);
    222                 /* According to the USB specs:
    223                  * 11.13.5 Over-current Reporting and Recovery
    224                  * Hub device is responsible for putting port in power off
    225                  * mode. USB system software is responsible for powering port
    226                  * back on when the over-current condition is gone */
    227                 const int rc = usb_hub_port_clear_feature(port,
    228                     USB_HUB_FEATURE_C_PORT_OVER_CURRENT);
    229                 if (rc != EOK) {
    230                         usb_log_error("(%p-%u): Failed to clear port OC change "
    231                             "feature: %s.\n", hub, port->port_number,
    232                             str_error(rc));
    233                 }
    234                 if (!(status & ~USB_HUB_PORT_STATUS_OC)) {
    235                         const int rc = usb_hub_port_set_feature(
    236                             port, USB_HUB_FEATURE_PORT_POWER);
    237                         if (rc != EOK) {
    238                                 usb_log_error("(%p-%u): Failed to set port "
    239                                     "power after OC: %s.", hub,
    240                                     port->port_number, str_error(rc));
    241                         }
    242                 }
    243         }
    244 
    245         /* Port reset, set on port reset complete. */
    246         if (status & USB_HUB_PORT_C_STATUS_RESET) {
    247                 usb_hub_port_reset_completed(port, hub, status);
    248         }
    249 
    250         usb_log_debug2("(%p-%u): Port status %#08" PRIx32, hub,
    251             port->port_number, status);
    252 }
    253 
    254 /**
    255  * routine called when a device on port has been removed
    256  *
    257  * If the device on port had default address, it releases default address.
    258  * Otherwise does not do anything, because DDF does not allow to remove device
    259  * from it`s device tree.
    260  * @param port port structure
    261  * @param hub hub representation
    262  */
    263 int usb_hub_port_device_gone(usb_hub_port_t *port, usb_hub_dev_t *hub)
    264 {
    265         assert(port);
    266         assert(hub);
    267         async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
     101        assert(fibril_mutex_is_locked(&port->guard));
     102        assert(port->state == PORT_ENABLED);
     103
     104        async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
    268105        if (!exch)
    269106                return ENOMEM;
    270107        const int rc = usbhc_device_remove(exch, port->port_number);
    271108        usb_device_bus_exchange_end(exch);
    272         if (rc == EOK)
    273                 port->device_attached = false;
    274109        return rc;
    275 
    276 }
    277 
    278 /**
    279  * Process port reset change
    280  *
    281  * After this change port should be enabled, unless some problem occurred.
    282  * This functions triggers second phase of enabling new device.
    283  * @param port Port structure
    284  * @param status Port status mask
    285  */
    286 void usb_hub_port_reset_completed(usb_hub_port_t *port, usb_hub_dev_t *hub,
    287     usb_port_status_t status)
     110}
     111
     112/**
     113 * Teardown a device on port, no matter which state it was in.
     114 */
     115static void port_make_disabled(usb_hub_port_t *port)
     116{
     117        assert(fibril_mutex_is_locked(&port->guard));
     118
     119        port_log(debug, port, "Making device offline.");
     120
     121        switch (port->state) {
     122        case PORT_ENABLED:
     123                port_log(debug, port, "Enabled ->");
     124                if (usb_hub_port_device_gone(port))
     125                        port_log(error, port, "Failed to remove the device node from HC. Continuing anyway.");
     126                port_change_state(port, PORT_DISABLED);
     127                break;
     128
     129        case PORT_CONNECTED:
     130                port_log(debug, port, "Connected ->");
     131                /* fallthrough */
     132        case PORT_IN_RESET:
     133                port_log(debug, port, "In reset ->");
     134                port_change_state(port, PORT_ERROR);
     135                /* fallthrough */
     136        case PORT_ERROR:
     137                port_log(debug, port, "Error ->");
     138                port_wait_state(port, PORT_DISABLED);
     139                /* fallthrough */
     140        case PORT_DISABLED:
     141                port_log(debug, port, "Disabled.");
     142                break;
     143        }
     144
     145        assert(port->state == PORT_DISABLED);
     146}
     147
     148/**
     149 * Finalize a port. Make sure no fibril is managing its state,
     150 * and that the HC is aware the device is no longer there.
     151 */
     152void usb_hub_port_fini(usb_hub_port_t *port)
    288153{
    289154        assert(port);
    290         fibril_mutex_lock(&port->mutex);
    291         const bool enabled = (status & USB_HUB_PORT_STATUS_ENABLED) != 0;
    292         /* Finalize device adding. */
    293 
    294         if (enabled) {
    295                 port->reset_status = RESET_OK;
    296                 usb_log_debug("(%p-%u): Port reset complete.", hub,
    297                     port->port_number);
    298         } else {
    299                 port->reset_status = RESET_FAIL;
    300                 usb_log_warning("(%p-%u): Port reset complete but port not "
    301                     "enabled.", hub, port->port_number);
    302         }
    303         fibril_condvar_broadcast(&port->reset_cv);
    304         fibril_mutex_unlock(&port->mutex);
    305 
    306         /* Clear the port reset change. */
    307         int rc = usb_hub_port_clear_feature(port, USB_HUB_FEATURE_C_PORT_RESET);
     155        fibril_mutex_lock(&port->guard);
     156        port_make_disabled(port);
     157        port_log(debug, port, "Finalized.");
     158        fibril_mutex_unlock(&port->guard);
     159}
     160
     161static int port_reset_sync(usb_hub_port_t *port)
     162{
     163        assert(fibril_mutex_is_locked(&port->guard));
     164        assert(port->state == PORT_CONNECTED);
     165
     166        port_change_state(port, PORT_IN_RESET);
     167        port_log(debug2, port, "Issuing reset.");
     168        int rc = usb_hub_set_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_RESET);
    308169        if (rc != EOK) {
    309                 usb_log_error("(%p-%u): Failed to clear port reset change: %s.",
    310                     hub, port->port_number, str_error(rc));
    311         }
    312 }
    313 
    314 /** Retrieve port status.
    315  *
    316  * @param[in] port Port structure
    317  * @param[out] status Where to store the port status.
    318  * @return Error code.
    319  */
    320 static int get_port_status(usb_hub_port_t *port, usb_port_status_t *status)
    321 {
    322         assert(port);
    323         /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
    324          * Generic GET_STATUS request cannot be used because of the difference
    325          * in status data size (2B vs. 4B)*/
    326         const usb_device_request_setup_packet_t request = {
    327                 .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
    328                 .request = USB_HUB_REQUEST_GET_STATUS,
    329                 .value = 0,
    330                 .index = uint16_host2usb(port->port_number),
    331                 .length = sizeof(usb_port_status_t),
    332         };
    333         size_t recv_size;
    334         usb_port_status_t status_tmp;
    335 
    336         const int rc = usb_pipe_control_read(port->control_pipe,
    337             &request, sizeof(usb_device_request_setup_packet_t),
    338             &status_tmp, sizeof(status_tmp), &recv_size);
    339         if (rc != EOK) {
     170                port_log(warning, port, "Port reset request failed: %s", str_error(rc));
    340171                return rc;
    341172        }
    342173
    343         if (recv_size != sizeof (status_tmp)) {
    344                 return ELIMIT;
    345         }
    346 
    347         if (status != NULL) {
    348                 *status = status_tmp;
    349         }
    350 
     174        fibril_condvar_wait_timeout(&port->state_cv, &port->guard, 2000000);
     175        return port->state == PORT_ENABLED ? EOK : ESTALL;
     176}
     177
     178/**
     179 * Routine for adding a new device.
     180 *
     181 * Separate fibril is needed because the operation blocks on waiting for
     182 * requesting default address and resetting port, and we must not block the
     183 * control pipe.
     184 */
     185static void setup_device(usb_hub_port_t *port)
     186{
     187        int err;
     188
     189        fibril_mutex_lock(&port->guard);
     190
     191        if (port->state == PORT_ERROR) {
     192                /*
     193                 * The device was removed faster than this fibril acquired the
     194                 * mutex.
     195                 */
     196                port_change_state(port, PORT_DISABLED);
     197                goto out;
     198        }
     199
     200        if (port->state != PORT_CONNECTED) {
     201                /*
     202                 * Another fibril already took care of the device.
     203                 * This may happen for example when the connection is unstable
     204                 * and a sequence of connect, disconnect and connect come
     205                 * faster the first fibril manages to request the default
     206                 * address.
     207                 */
     208                goto out;
     209        }
     210
     211        port_log(debug, port, "Setting up new device.");
     212
     213        async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
     214        if (!exch) {
     215                port_log(error, port, "Failed to create exchange.");
     216                goto out;
     217        }
     218
     219        /* Reserve default address
     220         * TODO: Make the request synchronous.
     221         */
     222        while ((err = usbhc_reserve_default_address(exch, port->speed)) == EAGAIN) {
     223                fibril_condvar_wait_timeout(&port->state_cv, &port->guard, 500000);
     224                if (port->state != PORT_CONNECTED) {
     225                        assert(port->state == PORT_ERROR);
     226                        port_change_state(port, PORT_DISABLED);
     227                        goto out_exch;
     228                }
     229        }
     230        if (err != EOK) {
     231                port_log(error, port, "Failed to reserve default address: %s", str_error(err));
     232                goto out_exch;
     233        }
     234
     235        port_log(debug, port, "Got default address. Resetting port.");
     236
     237        if ((err = port_reset_sync(port))) {
     238                port_log(error, port, "Failed to reset port.");
     239                port_change_state(port, PORT_DISABLED);
     240                goto out_address;
     241        }
     242
     243        assert(port->state == PORT_ENABLED);
     244
     245        port_log(debug, port, "Port reset, enumerating device.");
     246
     247        if ((err = usbhc_device_enumerate(exch, port->port_number))) {
     248                port_log(error, port, "Failed to enumerate device: %s", str_error(err));
     249                port_change_state(port, PORT_DISABLED);
     250                goto out_port;
     251        }
     252
     253        port_log(debug, port, "Device enumerated");
     254
     255out_port:
     256        if (port->state != PORT_ENABLED)
     257                usb_hub_clear_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_C_PORT_ENABLE);
     258
     259out_address:
     260        if ((err = usbhc_release_default_address(exch)))
     261                port_log(error, port, "Failed to release default address: %s", str_error(err));
     262
     263out_exch:
     264        usb_device_bus_exchange_end(exch);
     265
     266out:
     267        assert(port->state == PORT_ENABLED || port->state == PORT_DISABLED);
     268        fibril_mutex_unlock(&port->guard);
     269}
     270
     271static int setup_device_worker(void *arg)
     272{
     273        setup_device(arg);
    351274        return EOK;
    352 }
    353 
    354 static int port_enable(usb_hub_port_t *port, usb_hub_dev_t *hub, bool enable)
    355 {
    356         if (enable) {
    357                 int rc =
    358                     usb_hub_port_set_feature(port, USB_HUB_FEATURE_PORT_RESET);
    359                 if (rc != EOK) {
    360                         usb_log_error("(%p-%u): Port reset request failed: %s.",
    361                             hub, port->port_number, str_error(rc));
    362                         return rc;
    363                 }
    364                 /* Wait until reset completes. */
    365                 fibril_mutex_lock(&port->mutex);
    366                 port->reset_status = IN_RESET;
    367                 while (port->reset_status == IN_RESET)
    368                         fibril_condvar_wait(&port->reset_cv, &port->mutex);
    369                 rc = port->reset_status == RESET_OK ? EOK : ESTALL;
    370                 fibril_mutex_unlock(&port->mutex);
    371                 return rc;
    372         } else {
    373                 return usb_hub_port_clear_feature(port,
    374                                 USB_HUB_FEATURE_PORT_ENABLE);
    375         }
    376 }
    377 
    378 /** Fibril for adding a new device.
    379  *
    380  * Separate fibril is needed because the port reset completion is announced
    381  * via interrupt pipe and thus we cannot block here.
    382  *
    383  * @param arg Pointer to struct add_device_phase1.
    384  * @return 0 Always.
    385  */
    386 int add_device_phase1_worker_fibril(void *arg)
    387 {
    388         struct add_device_phase1 *params = arg;
    389         assert(params);
    390 
    391         bool release_default_address = false;
    392 
    393         int ret = EOK;
    394         usb_hub_dev_t *hub = params->hub;
    395         usb_hub_port_t *port = params->port;
    396         const usb_speed_t speed = params->speed;
    397         free(arg);
    398 
    399         usb_log_debug("(%p-%u): New device sequence.", hub, port->port_number);
    400 
    401         async_exch_t *exch = usb_device_bus_exchange_begin(hub->usb_device);
    402         if (!exch) {
    403                 usb_log_error("(%p-%u): Failed to begin bus exchange.", hub,
    404                     port->port_number);
    405                 ret = ENOMEM;
    406                 goto out;
    407         }
    408 
    409         /* Reserve default address */
    410         while ((ret = usbhc_reserve_default_address(exch, speed)) == EAGAIN) {
    411                 async_usleep(1000000);
    412         }
    413         if (ret != EOK) {
    414                 usb_log_error("(%p-%u): Failed to reserve default address: %s", hub, port->port_number, str_error(ret));
    415                 goto out;
    416         }
    417         release_default_address = true;
    418 
    419         usb_log_debug("(%p-%u): Got default address. Reseting port.", hub, port->port_number);
    420 
    421         /* Reset port */
    422         if ((ret = port_enable(port, hub, true))) {
    423                 usb_log_error("(%p-%u): Failed to reset port.", hub, port->port_number);
    424                 ret = EIO;
    425                 goto out_address;
    426         }
    427 
    428         usb_log_debug("(%p-%u): Port reset, enumerating device", hub, port->port_number);
    429 
    430         if ((ret = usbhc_device_enumerate(exch, port->port_number))) {
    431                 usb_log_error("(%p-%u): Failed to enumerate device: %s", hub, port->port_number, str_error(ret));
    432                 goto out_port;
    433         }
    434 
    435         usb_log_debug("(%p-%u): Device enumerated", hub, port->port_number);
    436         port->device_attached = true;
    437 
    438 out_port:
    439         if (!port->device_attached && (ret = port_enable(port, hub, false))) {
    440                 usb_log_warning("(%p-%u)Failed to disable port (%s), NOT releasing default address.", hub, port->port_number, str_error(ret));
    441                 release_default_address = false;
    442         }
    443 
    444 out_address:
    445         if (release_default_address && (ret = usbhc_release_default_address(exch)))
    446                 usb_log_warning("(%p-%u): Failed to release default address: %s", hub, port->port_number, str_error(ret));
    447 
    448 out:
    449         usb_device_bus_exchange_end(exch);
    450 
    451         fibril_mutex_lock(&hub->pending_ops_mutex);
    452         assert(hub->pending_ops_count > 0);
    453         --hub->pending_ops_count;
    454         fibril_condvar_signal(&hub->pending_ops_cv);
    455         fibril_mutex_unlock(&hub->pending_ops_mutex);
    456 
    457         return ret;
    458275}
    459276
     
    467284 * @return Error code.
    468285 */
    469 static int create_add_device_fibril(usb_hub_port_t *port, usb_hub_dev_t *hub,
    470     usb_speed_t speed)
    471 {
     286static int create_setup_device_fibril(usb_hub_port_t *port)
     287{
     288        assert(port);
     289
     290        fid_t fibril = fibril_create(setup_device_worker, port);
     291        if (!fibril)
     292                return ENOMEM;
     293
     294        fibril_add_ready(fibril);
     295        return EOK;
     296}
     297
     298static void port_changed_connection(usb_hub_port_t *port, usb_port_status_t status)
     299{
     300        const bool connected = !!(status & USB_HUB_PORT_STATUS_CONNECTION);
     301        port_log(debug, port, "Connection change: device %s.", connected ? "attached" : "removed");
     302
     303        if (connected) {
     304                if (port->state == PORT_ENABLED)
     305                        port_log(warning, port, "Connection detected on port that is currently enabled. Resetting.");
     306
     307                port_make_disabled(port);
     308                port_change_state(port, PORT_CONNECTED);
     309                port->speed = usb_port_speed(status);
     310                create_setup_device_fibril(port);
     311        } else {
     312                port_make_disabled(port);
     313        }
     314}
     315
     316static void port_changed_enabled(usb_hub_port_t *port, usb_port_status_t status)
     317{
     318        const bool enabled = !!(status & USB_HUB_PORT_STATUS_ENABLED);
     319        if (enabled) {
     320                port_log(warning, port, "Port unexpectedly changed to enabled.");
     321        } else {
     322                port_make_disabled(port);
     323        }
     324}
     325
     326static void port_changed_suspend(usb_hub_port_t *port, usb_port_status_t status)
     327{
     328        port_log(error, port, "Port unexpectedly suspend. Weird, we do not support suspending!");
     329}
     330
     331static void port_changed_overcurrent(usb_hub_port_t *port, usb_port_status_t status)
     332{
     333        const bool overcurrent = !!(status & USB_HUB_PORT_STATUS_OC);
     334
     335        /* According to the USB specs:
     336         * 11.13.5 Over-current Reporting and Recovery
     337         * Hub device is responsible for putting port in power off
     338         * mode. USB system software is responsible for powering port
     339         * back on when the over-current condition is gone */
     340
     341        port_make_disabled(port);
     342
     343        if (!overcurrent) {
     344                const int err = usb_hub_set_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_POWER);
     345                if (err)
     346                        port_log(error, port, "Failed to set port power after OC: %s.", str_error(err));
     347        }
     348}
     349
     350static void port_changed_reset(usb_hub_port_t *port, usb_port_status_t status)
     351{
     352        const bool enabled = !!(status & USB_HUB_PORT_STATUS_ENABLED);
     353
     354        /* Check if someone is waiting for the result */
     355        if (port->state != PORT_IN_RESET)
     356                return;
     357
     358        port_change_state(port, enabled ? PORT_ENABLED : PORT_ERROR);
     359}
     360
     361typedef void (*change_handler_t)(usb_hub_port_t *, usb_port_status_t);
     362
     363static const change_handler_t port_change_handlers [] = {
     364        [USB_HUB_FEATURE_C_PORT_CONNECTION] = &port_changed_connection,
     365        [USB_HUB_FEATURE_C_PORT_ENABLE] = &port_changed_enabled,
     366        [USB_HUB_FEATURE_C_PORT_SUSPEND] = &port_changed_suspend,
     367        [USB_HUB_FEATURE_C_PORT_OVER_CURRENT] = &port_changed_overcurrent,
     368        [USB_HUB_FEATURE_C_PORT_RESET] = &port_changed_reset,
     369        [sizeof(usb_port_status_t) * 8] = NULL,
     370};
     371
     372/**
     373 * Process interrupts on given port
     374 *
     375 * Accepts connection, over current and port reset change.
     376 * @param port port structure
     377 * @param hub hub representation
     378 */
     379void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
     380{
     381        assert(port);
    472382        assert(hub);
    473         assert(port);
    474         struct add_device_phase1 *data
    475             = malloc(sizeof(struct add_device_phase1));
    476         if (data == NULL) {
    477                 return ENOMEM;
    478         }
    479         data->hub = hub;
    480         data->port = port;
    481         data->speed = speed;
    482 
    483         fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
    484         if (fibril == 0) {
    485                 free(data);
    486                 return ENOMEM;
    487         }
    488         fibril_mutex_lock(&hub->pending_ops_mutex);
    489         ++hub->pending_ops_count;
    490         fibril_mutex_unlock(&hub->pending_ops_mutex);
    491         fibril_add_ready(fibril);
    492 
    493         return EOK;
    494 }
     383        port_log(debug2, port, "Interrupt.");
     384
     385        usb_port_status_t status = 0;
     386        const int err = usb_hub_get_port_status(port->hub, port->port_number, &status);
     387        if (err != EOK) {
     388                port_log(error, port, "Failed to get port status: %s.", str_error(err));
     389                return;
     390        }
     391
     392        fibril_mutex_lock(&port->guard);
     393
     394        for (uint32_t feature = 0; feature < sizeof(usb_port_status_t) * 8; ++feature) {
     395                uint32_t mask = 1 << feature;
     396
     397                if ((status & mask) == 0)
     398                        continue;
     399
     400                if (!port_change_handlers[feature])
     401                        continue;
     402
     403                /* ACK this change */
     404                status &= ~mask;
     405                usb_hub_clear_port_feature(port->hub, port->port_number, feature);
     406
     407                port_change_handlers[feature](port, status);
     408        }
     409
     410        fibril_mutex_unlock(&port->guard);
     411
     412        port_log(debug2, port, "Port status after handling: %#08" PRIx32, status);
     413}
     414
    495415
    496416/**
  • uspace/drv/bus/usb/usbhub/port.h

    rd2c3dcd rc4e84ed6  
    22 * Copyright (c) 2011 Vojtech Horky
    33 * Copyright (c) 2011 Jan Vesely
     4 * Copyright (c) 2017 Ondra Hlavaty
    45 * All rights reserved.
    56 *
     
    3233 */
    3334/** @file
    34  * Hub ports related functions.
     35 * Hub port state machine.
    3536 */
    3637
     
    4344typedef struct usb_hub_dev usb_hub_dev_t;
    4445
     46typedef enum {
     47        PORT_DISABLED,  /* No device connected. */
     48        PORT_CONNECTED, /* A device connected, not yet initialized. */
     49        PORT_IN_RESET,  /* An initial port reset in progress. */
     50        PORT_ENABLED,   /* Port reset complete, port enabled. Device announced to the HC. */
     51        PORT_ERROR,     /* An error occured. There is still a fibril that needs to know it. */
     52} port_state_t;
     53
    4554/** Information about single port on a hub. */
    4655typedef struct {
     56        /* Parenting hub */
     57        usb_hub_dev_t *hub;
     58        /** Guarding all fields */
     59        fibril_mutex_t guard;
     60        /** Current state of the port */
     61        port_state_t state;
     62        /** A speed of the device connected (if any). Valid unless state == PORT_DISABLED. */
     63        usb_speed_t speed;
    4764        /** Port number as reported in descriptors. */
    4865        unsigned int port_number;
    49         /** Device communication pipe. */
    50         usb_pipe_t *control_pipe;
    51         /** Mutex needed not only by CV for checking port reset. */
    52         fibril_mutex_t mutex;
    5366        /** CV for waiting to port reset completion. */
    54         fibril_condvar_t reset_cv;
    55         /** Port reset status.
    56          * Guarded by @c reset_mutex.
    57          */
    58         enum {
    59                 NO_RESET,
    60                 IN_RESET,
    61                 RESET_OK,
    62                 RESET_FAIL,
    63         } reset_status;
    64         /** Device reported to USB bus driver */
    65         bool device_attached;
     67        fibril_condvar_t state_cv;
    6668} usb_hub_port_t;
    6769
    68 /** Initialize hub port information.
    69  *
    70  * @param port Port to be initialized.
    71  */
    72 static inline void usb_hub_port_init(usb_hub_port_t *port,
    73     unsigned int port_number, usb_pipe_t *control_pipe)
    74 {
    75         assert(port);
    76         port->port_number = port_number;
    77         port->control_pipe = control_pipe;
    78         port->reset_status = NO_RESET;
    79         port->device_attached = false;
    80         fibril_mutex_initialize(&port->mutex);
    81         fibril_condvar_initialize(&port->reset_cv);
    82 }
     70void usb_hub_port_init(usb_hub_port_t *, usb_hub_dev_t *, unsigned int);
     71void usb_hub_port_fini(usb_hub_port_t *);
    8372
    84 int usb_hub_port_fini(usb_hub_port_t *port, usb_hub_dev_t *hub);
    85 int usb_hub_port_clear_feature(
    86     usb_hub_port_t *port, usb_hub_class_feature_t feature);
    87 int usb_hub_port_set_feature(
    88     usb_hub_port_t *port, usb_hub_class_feature_t feature);
    89 void usb_hub_port_reset_fail(usb_hub_port_t *port);
    9073void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub);
    9174
  • uspace/drv/bus/usb/usbhub/usbhub.c

    rd2c3dcd rc4e84ed6  
    8686    usb_hub_status_t status);
    8787static void usb_hub_global_interrupt(const usb_hub_dev_t *hub_dev);
    88 static void usb_hub_polling_terminated_callback(usb_device_t *device,
    89     bool was_error, void *data);
    9088
    9189static bool usb_hub_polling_error_callback(usb_device_t *dev, int err_code, void *arg)
     
    9391        assert(dev);
    9492        assert(arg);
    95         usb_hub_dev_t *hub = arg;
    96 
    97         usb_log_error("Device %s polling error: %s", usb_device_get_name(dev),
    98             str_error(err_code));
    99 
    100         /* Continue polling until the device is about to be removed. */
    101         return hub->running;
     93
     94        usb_log_error("Device %s polling error: %s", usb_device_get_name(dev), str_error(err_code));
     95
     96        return true;
    10297}
    10398
     
    121116        }
    122117        hub_dev->usb_device = usb_dev;
    123         hub_dev->pending_ops_count = 0;
    124         hub_dev->running = false;
    125         fibril_mutex_initialize(&hub_dev->pending_ops_mutex);
    126         fibril_condvar_initialize(&hub_dev->pending_ops_cv);
    127118
    128119        /* Set hub's first configuration. (There should be only one) */
     
    178169        polling->buffer = malloc(polling->request_size);
    179170        polling->on_data = hub_port_changes_callback;
    180         polling->on_polling_end = usb_hub_polling_terminated_callback;
    181171        polling->on_error = usb_hub_polling_error_callback;
    182172        polling->arg = hub_dev;
     
    194184        }
    195185
    196         hub_dev->running = true;
    197186        usb_log_info("Controlling hub '%s' (%p: %zu ports).",
    198187            usb_device_get_name(hub_dev->usb_device), hub_dev,
     
    204193static int usb_hub_cleanup(usb_hub_dev_t *hub)
    205194{
    206         assert(!hub->running);
    207 
    208195        free(hub->polling.buffer);
    209196        usb_polling_fini(&hub->polling);
    210197
    211198        for (size_t port = 0; port < hub->port_count; ++port) {
    212                 const int ret = usb_hub_port_fini(&hub->ports[port], hub);
    213                 if (ret != EOK)
    214                         return ret;
     199                usb_hub_port_fini(&hub->ports[port]);
    215200        }
    216201        free(hub->ports);
     
    343328            descriptor.port_count);
    344329        hub_dev->port_count = descriptor.port_count;
     330        hub_dev->control_pipe = control_pipe;
    345331
    346332        hub_dev->ports = calloc(hub_dev->port_count, sizeof(usb_hub_port_t));
     
    350336
    351337        for (size_t port = 0; port < hub_dev->port_count; ++port) {
    352                 usb_hub_port_init(
    353                     &hub_dev->ports[port], port + 1, control_pipe);
     338                usb_hub_port_init(&hub_dev->ports[port], hub_dev, port + 1);
    354339        }
    355340
     
    370355        for (unsigned int port = 0; port < hub_dev->port_count; ++port) {
    371356                usb_log_debug("(%p): Powering port %u.", hub_dev, port);
    372                 const int ret = usb_hub_port_set_feature(
    373                     &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
     357                const int ret = usb_hub_set_port_feature(hub_dev, port, USB_HUB_FEATURE_PORT_POWER);
    374358
    375359                if (ret != EOK) {
     
    459443        /* Over-current condition is gone, it is safe to turn the ports on. */
    460444        for (size_t port = 0; port < hub_dev->port_count; ++port) {
    461                 const int ret = usb_hub_port_set_feature(
    462                     &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER);
     445                const int ret = usb_hub_set_port_feature(hub_dev, port, USB_HUB_FEATURE_PORT_POWER);
    463446                if (ret != EOK) {
    464447                        usb_log_warning("(%p-%u): HUB OVER-CURRENT GONE: Cannot"
     
    470453                }
    471454        }
    472 
     455}
     456
     457/**
     458 * Set feature on the real hub port.
     459 *
     460 * @param port Port structure.
     461 * @param feature Feature selector.
     462 */
     463int usb_hub_set_port_feature(const usb_hub_dev_t *hub, size_t port_number, usb_hub_class_feature_t feature)
     464{
     465        assert(hub);
     466        const usb_device_request_setup_packet_t clear_request = {
     467                .request_type = USB_HUB_REQ_TYPE_SET_PORT_FEATURE,
     468                .request = USB_DEVREQ_SET_FEATURE,
     469                .index = uint16_host2usb(port_number),
     470                .value = feature,
     471                .length = 0,
     472        };
     473        return usb_pipe_control_write(hub->control_pipe, &clear_request,
     474            sizeof(clear_request), NULL, 0);
     475}
     476
     477/**
     478 * Clear feature on the real hub port.
     479 *
     480 * @param port Port structure.
     481 * @param feature Feature selector.
     482 */
     483int usb_hub_clear_port_feature(const usb_hub_dev_t *hub, size_t port_number, usb_hub_class_feature_t feature)
     484{
     485        assert(hub);
     486        const usb_device_request_setup_packet_t clear_request = {
     487                .request_type = USB_HUB_REQ_TYPE_CLEAR_PORT_FEATURE,
     488                .request = USB_DEVREQ_CLEAR_FEATURE,
     489                .value = feature,
     490                .index = uint16_host2usb(port_number),
     491                .length = 0,
     492        };
     493        return usb_pipe_control_write(hub->control_pipe,
     494            &clear_request, sizeof(clear_request), NULL, 0);
     495}
     496
     497/**
     498 * Retrieve port status.
     499 *
     500 * @param[in] port Port structure
     501 * @param[out] status Where to store the port status.
     502 * @return Error code.
     503 */
     504int usb_hub_get_port_status(const usb_hub_dev_t *hub, size_t port_number, usb_port_status_t *status)
     505{
     506        assert(hub);
     507        assert(status);
     508
     509        /* USB hub specific GET_PORT_STATUS request. See USB Spec 11.16.2.6
     510         * Generic GET_STATUS request cannot be used because of the difference
     511         * in status data size (2B vs. 4B)*/
     512        const usb_device_request_setup_packet_t request = {
     513                .request_type = USB_HUB_REQ_TYPE_GET_PORT_STATUS,
     514                .request = USB_HUB_REQUEST_GET_STATUS,
     515                .value = 0,
     516                .index = uint16_host2usb(port_number),
     517                .length = sizeof(usb_port_status_t),
     518        };
     519        size_t recv_size;
     520
     521        const int rc = usb_pipe_control_read(hub->control_pipe,
     522            &request, sizeof(usb_device_request_setup_packet_t),
     523            status, sizeof(*status), &recv_size);
     524        if (rc != EOK)
     525                return rc;
     526
     527        if (recv_size != sizeof(*status))
     528                return ELIMIT;
     529
     530        return EOK;
    473531}
    474532
     
    545603
    546604/**
    547  * callback called from hub polling fibril when the fibril terminates
    548  *
    549  * Does not perform cleanup, just marks the hub as not running.
    550  * @param device usb device afected
    551  * @param was_error indicates that the fibril is stoped due to an error
    552  * @param data pointer to usb_hub_dev_t structure
    553  */
    554 static void usb_hub_polling_terminated_callback(usb_device_t *device,
    555     bool was_error, void *data)
    556 {
    557         usb_hub_dev_t *hub = data;
    558         assert(hub);
    559 
    560         fibril_mutex_lock(&hub->pending_ops_mutex);
    561 
    562         /* The device is dead. However there might be some pending operations
    563          * that we need to wait for.
    564          * One of them is device adding in progress.
    565          * The respective fibril is probably waiting for status change
    566          * in port reset (port enable) callback.
    567          * Such change would never come (otherwise we would not be here).
    568          * Thus, we would flush all pending port resets.
    569          */
    570         if (hub->pending_ops_count > 0) {
    571                 for (size_t port = 0; port < hub->port_count; ++port) {
    572                         usb_hub_port_reset_fail(&hub->ports[port]);
    573                 }
    574         }
    575         /* And now wait for them. */
    576         while (hub->pending_ops_count > 0) {
    577                 fibril_condvar_wait(&hub->pending_ops_cv,
    578                     &hub->pending_ops_mutex);
    579         }
    580         fibril_mutex_unlock(&hub->pending_ops_mutex);
    581         hub->running = false;
    582 }
    583 
    584 /**
    585605 * @}
    586606 */
  • uspace/drv/bus/usb/usbhub/usbhub.h

    rd2c3dcd rc4e84ed6  
    5050
    5151#include "port.h"
     52#include "status.h"
    5253
    5354/** Information about attached hub. */
     
    6162        /** Data polling handle. */
    6263        usb_polling_t polling;
    63         /** Number of pending operations on the mutex to prevent shooting
    64          * ourselves in the foot.
    65          * When the hub is disconnected but we are in the middle of some
    66          * operation, we cannot destroy this structure right away because
    67          * the pending operation might use it.
    68          */
    69         size_t pending_ops_count;
    70         /** Guard for pending_ops_count. */
    71         fibril_mutex_t pending_ops_mutex;
    72         /** Condition variable for pending_ops_count. */
    73         fibril_condvar_t pending_ops_cv;
    7464        /** Pointer to usbhub function. */
    7565        ddf_fun_t *hub_fun;
    76         /** Status indicator */
    77         volatile bool running;
     66        /** Device communication pipe. */
     67        usb_pipe_t *control_pipe;
    7868        /** Hub supports port power switching. */
    7969        bool power_switched;
     
    8474extern const usb_endpoint_description_t hub_status_change_endpoint_description;
    8575
    86 extern int usb_hub_device_add(usb_device_t *);
    87 extern int usb_hub_device_remove(usb_device_t *);
    88 extern int usb_hub_device_gone(usb_device_t *);
     76int usb_hub_device_add(usb_device_t *);
     77int usb_hub_device_remove(usb_device_t *);
     78int usb_hub_device_gone(usb_device_t *);
    8979
    90 extern bool hub_port_changes_callback(usb_device_t *, uint8_t *, size_t,
    91     void *);
     80int usb_hub_get_port_status(const usb_hub_dev_t *, size_t, usb_port_status_t *);
     81int usb_hub_set_port_feature(const usb_hub_dev_t *, size_t, usb_hub_class_feature_t);
     82int usb_hub_clear_port_feature(const usb_hub_dev_t *, size_t, usb_hub_class_feature_t);
     83
     84bool hub_port_changes_callback(usb_device_t *, uint8_t *, size_t, void *);
    9285
    9386#endif
Note: See TracChangeset for help on using the changeset viewer.