Changeset 94f8c363 in mainline


Ignore:
Timestamp:
2018-01-18T00:48:27Z (6 years ago)
Author:
Ondřej Hlavatý <aearsis@…>
Branches:
lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
Children:
a9fcd73
Parents:
8ad2b0a
Message:

usbhub: extract the port state machine to the usb library

Location:
uspace
Files:
2 added
5 edited

Legend:

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

    r8ad2b0a r94f8c363  
    5858        assert(port);
    5959        memset(port, 0, sizeof(*port));
    60         fibril_mutex_initialize(&port->guard);
    61         fibril_condvar_initialize(&port->state_cv);
    6260        port->hub = hub;
    6361        port->port_number = port_number;
    64 }
    65 
    66 /**
    67  * Utility method to change current port state and notify waiters.
    68  */
    69 static 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  */
    88 static 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);
     62        usb_port_init(&port->base);
     63}
     64
     65static inline usb_hub_port_t *get_hub_port(usb_port_t *port)
     66{
     67        assert(port);
     68        return (usb_hub_port_t *) port;
    9369}
    9470
     
    9672 * Inform the HC that the device on port is gone.
    9773 */
    98 static int usb_hub_port_device_gone(usb_hub_port_t *port)
    99 {
    100         assert(port);
    101         assert(fibril_mutex_is_locked(&port->guard));
    102         assert(port->state == PORT_ENABLED);
     74static void remove_device(usb_port_t *port_base)
     75{
     76        usb_hub_port_t *port = get_hub_port(port_base);
    10377
    10478        async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
    105         if (!exch)
     79        if (!exch) {
     80                port_log(error, port, "Cannot remove the device, failed creating exchange.");
     81                return;
     82        }
     83       
     84        const int err = usbhc_device_remove(exch, port->port_number);
     85        if (err)
     86                port_log(error, port, "Failed to remove device: %s", str_error(err));
     87
     88        usb_device_bus_exchange_end(exch);
     89}
     90
     91/**
     92 * Routine for adding a new device.
     93 *
     94 * Separate fibril is needed because the operation blocks on waiting for
     95 * requesting default address and resetting port, and we must not block the
     96 * control pipe.
     97 */
     98static int enumerate_device(usb_port_t *port_base)
     99{
     100        int err;
     101        usb_hub_port_t *port = get_hub_port(port_base);
     102
     103        port_log(debug, port, "Setting up new device.");
     104
     105        async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
     106        if (!exch) {
     107                port_log(error, port, "Failed to create exchange.");
    106108                return ENOMEM;
    107         const int rc = usbhc_device_remove(exch, port->port_number);
    108         usb_device_bus_exchange_end(exch);
    109         return rc;
    110 }
    111 
    112 /**
    113  * Teardown a device on port, no matter which state it was in.
    114  */
    115 static 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  */
    152 void usb_hub_port_fini(usb_hub_port_t *port)
    153 {
    154         assert(port);
    155 
    156         fibril_mutex_lock(&port->guard);
    157         switch (port->state) {
    158         case PORT_ENABLED:
    159                 /*
    160                  * We shall inform the HC that the device is gone.
    161                  * However, we can't wait for it, because if the device is hub,
    162                  * it would have to use the same IPC handling fibril as we do.
    163                  * But we cannot even defer it to another fibril, because then
    164                  * the HC would assume our driver didn't cleanup properly, and
    165                  * will remove those devices by himself.
    166                  *
    167                  * So the solutions seems to behave like a bad driver and leave
    168                  * the work for HC.
    169                  */
    170                 port_change_state(port, PORT_DISABLED);
    171                 break;
    172 
    173         case PORT_CONNECTED:
    174         case PORT_IN_RESET:
    175                 port_change_state(port, PORT_ERROR);
    176                 /* fallthrough */
    177         case PORT_ERROR:
    178                 port_wait_state(port, PORT_DISABLED);
    179                 /* fallthrough */
    180         case PORT_DISABLED:
    181                 break;
    182         }
    183         port_log(debug, port, "Finalized.");
    184         fibril_mutex_unlock(&port->guard);
    185 }
    186 
    187 static int port_reset_sync(usb_hub_port_t *port)
    188 {
    189         assert(fibril_mutex_is_locked(&port->guard));
    190         assert(port->state == PORT_CONNECTED);
    191 
    192         port_change_state(port, PORT_IN_RESET);
    193         port_log(debug2, port, "Issuing reset.");
     109        }
     110
     111        /* Reserve default address */
     112        err = usb_hub_reserve_default_address(port->hub, exch, &port->base);
     113        if (err != EOK) {
     114                port_log(error, port, "Failed to reserve default address: %s", str_error(err));
     115                goto out_exch;
     116        }
     117
     118        /* Reservation of default address could have blocked */
     119        if (port->base.state != PORT_CONNECTING)
     120                goto out_address;
     121
     122        port_log(debug, port, "Got default address. Resetting port.");
    194123        int rc = usb_hub_set_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_RESET);
    195124        if (rc != EOK) {
    196125                port_log(warning, port, "Port reset request failed: %s", str_error(rc));
    197                 return rc;
    198         }
    199 
    200         fibril_condvar_wait_timeout(&port->state_cv, &port->guard, 2000000);
    201         return port->state == PORT_ENABLED ? EOK : ESTALL;
    202 }
    203 
    204 /**
    205  * Routine for adding a new device.
    206  *
    207  * Separate fibril is needed because the operation blocks on waiting for
    208  * requesting default address and resetting port, and we must not block the
    209  * control pipe.
    210  */
    211 static void setup_device(usb_hub_port_t *port)
    212 {
    213         int err;
    214 
    215         fibril_mutex_lock(&port->guard);
    216 
    217         if (port->state == PORT_ERROR) {
    218                 /*
    219                  * The device was removed faster than this fibril acquired the
    220                  * mutex.
    221                  */
    222                 port_change_state(port, PORT_DISABLED);
    223                 goto out;
    224         }
    225 
    226         if (port->state != PORT_CONNECTED) {
    227                 /*
    228                  * Another fibril already took care of the device.
    229                  * This may happen for example when the connection is unstable
    230                  * and a sequence of connect, disconnect and connect come
    231                  * faster the first fibril manages to request the default
    232                  * address.
    233                  */
    234                 goto out;
    235         }
    236 
    237         port_log(debug, port, "Setting up new device.");
    238 
    239         async_exch_t *exch = usb_device_bus_exchange_begin(port->hub->usb_device);
    240         if (!exch) {
    241                 port_log(error, port, "Failed to create exchange.");
    242                 goto out;
    243         }
    244 
    245         /* Reserve default address */
    246         err = usb_hub_reserve_default_address(port->hub, exch, &port->guard);
    247         if (err != EOK) {
    248                 port_log(error, port, "Failed to reserve default address: %s", str_error(err));
    249                 port_change_state(port, PORT_DISABLED);
    250                 goto out_exch;
    251         }
    252 
    253         /* Reservation of default address could have blocked */
    254         if (port->state != PORT_CONNECTED) {
    255                 assert(port->state == PORT_ERROR);
    256                 port_change_state(port, PORT_DISABLED);
    257                 goto out_exch;
    258         }
    259 
    260         port_log(debug, port, "Got default address. Resetting port.");
    261 
    262         if ((err = port_reset_sync(port))) {
    263                 port_log(error, port, "Failed to reset port.");
    264                 port_change_state(port, PORT_DISABLED);
    265                 goto out_address;
    266         }
    267 
    268         assert(port->state == PORT_ENABLED);
     126                goto out_address;
     127        }
     128
     129        if ((err = usb_port_wait_for_enabled(&port->base))) {
     130                port_log(error, port, "Failed to reset port: %s", str_error(err));
     131                goto out_address;
     132        }
    269133
    270134        port_log(debug, port, "Port reset, enumerating device.");
    271135
    272         if ((err = usbhc_device_enumerate(exch, port->port_number, port->speed))) {
     136        if ((err = usbhc_device_enumerate(exch, port->port_number, port->base.speed))) {
    273137                port_log(error, port, "Failed to enumerate device: %s", str_error(err));
    274                 port_change_state(port, PORT_DISABLED);
    275                 goto out_port;
     138                /* Disable the port */
     139                usb_hub_clear_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_PORT_ENABLE);
     140                goto out_address;
    276141        }
    277142
    278143        port_log(debug, port, "Device enumerated");
    279144
    280 out_port:
    281         if (port->state != PORT_ENABLED)
    282                 usb_hub_clear_port_feature(port->hub, port->port_number, USB_HUB_FEATURE_C_PORT_ENABLE);
    283145out_address:
    284146        usb_hub_release_default_address(port->hub, exch);
    285147out_exch:
    286148        usb_device_bus_exchange_end(exch);
    287 out:
    288         assert(port->state == PORT_ENABLED || port->state == PORT_DISABLED);
    289         fibril_mutex_unlock(&port->guard);
    290 }
    291 
    292 static int setup_device_worker(void *arg)
    293 {
    294         setup_device(arg);
    295         return EOK;
    296 }
    297 
    298 /** Start device adding when connection change is detected.
    299  *
    300  * This fires a new fibril to complete the device addition.
    301  *
    302  * @param hub Hub where the change occured.
    303  * @param port Port index (starting at 1).
    304  * @param speed Speed of the device.
    305  * @return Error code.
    306  */
    307 static int create_setup_device_fibril(usb_hub_port_t *port)
    308 {
    309         assert(port);
    310 
    311         fid_t fibril = fibril_create(setup_device_worker, port);
    312         if (!fibril)
    313                 return ENOMEM;
    314 
    315         fibril_add_ready(fibril);
    316         return EOK;
     149        return err;
    317150}
    318151
     
    323156
    324157        if (connected) {
    325                 if (port->state == PORT_ENABLED)
    326                         port_log(warning, port, "Connection detected on port that is currently enabled. Resetting.");
    327 
    328                 port_make_disabled(port);
    329                 port_change_state(port, PORT_CONNECTED);
    330                 port->speed = usb_port_speed(status);
    331                 create_setup_device_fibril(port);
     158                usb_port_connected(&port->base, &enumerate_device);
    332159        } else {
    333                 port_make_disabled(port);
     160                usb_port_disabled(&port->base, &remove_device);
    334161        }
    335162}
     
    341168                port_log(warning, port, "Port unexpectedly changed to enabled.");
    342169        } else {
    343                 port_make_disabled(port);
     170                usb_port_disabled(&port->base, &remove_device);
    344171        }
    345172}
     
    360187         * back on when the over-current condition is gone */
    361188
    362         port_make_disabled(port);
     189        usb_port_disabled(&port->base, &remove_device);
    363190
    364191        if (!overcurrent) {
     
    373200        const bool enabled = !!(status & USB_HUB_PORT_STATUS_ENABLED);
    374201
    375         /* Check if someone is waiting for the result */
    376         if (port->state != PORT_IN_RESET)
    377                 return;
    378 
    379         port_change_state(port, enabled ? PORT_ENABLED : PORT_ERROR);
     202        if (enabled)
     203                usb_port_enabled(&port->base, usb_port_speed(status));
     204        else
     205                usb_port_disabled(&port->base, &remove_device);
    380206}
    381207
     
    398224 * @param hub hub representation
    399225 */
    400 void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub)
     226void usb_hub_port_process_interrupt(usb_hub_port_t *port)
    401227{
    402228        assert(port);
    403         assert(hub);
    404229        port_log(debug2, port, "Interrupt.");
    405230
     
    411236        }
    412237
    413         fibril_mutex_lock(&port->guard);
    414 
    415238        for (uint32_t feature = 0; feature < sizeof(usb_port_status_t) * 8; ++feature) {
    416239                uint32_t mask = 1 << feature;
     
    429252        }
    430253
    431         fibril_mutex_unlock(&port->guard);
    432 
    433254        port_log(debug2, port, "Port status after handling: %#08" PRIx32, status);
    434255}
  • uspace/drv/bus/usb/usbhub/port.h

    r8ad2b0a r94f8c363  
    3333 */
    3434/** @file
    35  * Hub port state machine.
     35 * Hub port handling.
    3636 */
    3737
     
    4141#include <usb/dev/driver.h>
    4242#include <usb/classes/hub.h>
     43#include <usb/port.h>
    4344
    4445typedef struct usb_hub_dev usb_hub_dev_t;
    4546
    46 typedef 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 
    5447/** Information about single port on a hub. */
    5548typedef struct {
     49        usb_port_t base;
    5650        /* Parenting hub */
    5751        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;
    6452        /** Port number as reported in descriptors. */
    6553        unsigned int port_number;
    66         /** CV for waiting to port reset completion. */
    67         fibril_condvar_t state_cv;
    6854} usb_hub_port_t;
    6955
    7056void usb_hub_port_init(usb_hub_port_t *, usb_hub_dev_t *, unsigned int);
    71 void usb_hub_port_fini(usb_hub_port_t *);
    7257
    73 void usb_hub_port_process_interrupt(usb_hub_port_t *port, usb_hub_dev_t *hub);
     58void usb_hub_port_process_interrupt(usb_hub_port_t *port);
    7459
    7560#endif
  • uspace/drv/bus/usb/usbhub/usbhub.c

    r8ad2b0a r94f8c363  
    200200
    201201        for (size_t port = 0; port < hub->port_count; ++port) {
    202                 usb_hub_port_fini(&hub->ports[port]);
     202                usb_port_fini(&hub->ports[port].base);
    203203        }
    204204        free(hub->ports);
     
    291291                const bool change = (change_bitmap[bit / 8] >> (bit % 8)) & 1;
    292292                if (change) {
    293                         usb_hub_port_process_interrupt(&hub->ports[port], hub);
     293                        usb_hub_port_process_interrupt(&hub->ports[port]);
    294294                }
    295295        }
     
    605605}
    606606
     607static FIBRIL_CONDVAR_INITIALIZE(global_hub_default_address_cv);
     608
    607609/**
    608610 * Reserve a default address for a port across all other devices connected to
     
    611613 * is connected with already attached devices.
    612614 */
    613 int usb_hub_reserve_default_address(usb_hub_dev_t *hub, async_exch_t *exch, fibril_mutex_t *guard)
     615int usb_hub_reserve_default_address(usb_hub_dev_t *hub, async_exch_t *exch, usb_port_t *port)
    614616{
    615617        assert(hub);
    616618        assert(exch);
    617         assert(guard);
    618         assert(fibril_mutex_is_locked(guard));
     619        assert(port);
     620        assert(fibril_mutex_is_locked(&port->guard));
    619621
    620622        fibril_mutex_lock(&hub->default_address_guard);
     
    624626                int err;
    625627                while ((err = usbhc_reserve_default_address(exch)) == EAGAIN) {
    626                         fibril_mutex_unlock(guard);
    627                         async_usleep(500000);
    628                         fibril_mutex_lock(guard);
     628                        // We ignore the return value here, as we cannot give up now.
     629                        usb_port_condvar_wait_timeout(port, &global_hub_default_address_cv, 500000);
    629630                }
    630631                return err;
    631632        } else {
    632633                /* Drop the port guard, we're going to wait */
    633                 fibril_mutex_unlock(guard);
     634                fibril_mutex_unlock(&port->guard);
    634635
    635636                /* Wait for a signal */
     
    638639                /* Remember ABBA, first drop the hub guard */
    639640                fibril_mutex_unlock(&hub->default_address_guard);
    640                 fibril_mutex_lock(guard);
     641                fibril_mutex_lock(&port->guard);
    641642                return EOK;
    642643        }
     
    655656                // from requesting the address before we release
    656657                ret = usbhc_release_default_address(exch);
     658                // This is optimistic optimization - it may wake one hub from polling sleep
     659                fibril_condvar_signal(&global_hub_default_address_cv);
    657660        } else {
    658661                fibril_condvar_signal(&hub->default_address_cv);
  • uspace/drv/bus/usb/usbhub/usbhub.h

    r8ad2b0a r94f8c363  
    8989bool hub_port_changes_callback(usb_device_t *, uint8_t *, size_t, void *);
    9090
    91 int usb_hub_reserve_default_address(usb_hub_dev_t *, async_exch_t *, fibril_mutex_t *);
     91int usb_hub_reserve_default_address(usb_hub_dev_t *, async_exch_t *, usb_port_t *);
    9292int usb_hub_release_default_address(usb_hub_dev_t *, async_exch_t *);
    9393
  • uspace/lib/usb/Makefile

    r8ad2b0a r94f8c363  
    3636        src/debug.c \
    3737        src/dump.c \
     38        src/port.c \
    3839        src/usb.c
    3940
Note: See TracChangeset for help on using the changeset viewer.