[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [cdi-devel] [PATCH v3 09/10] usb: Add USB 2.0 bus driver



On Mon, May 11, 2015 at 09:59:29PM +0200, Max Reitz wrote:
> + This adds a USB 2.0 bus driver. It is missing support for USB 3.x
>   (obviously), and also interrupt and isochronous transfers (and other
>   things, too). Also, it is probably bad (e.g. there is only a single
>   counter for device IDs instead of keeping track of which IDs are used
>   and which are not).
> 
> Signed-off-by: Max Reitz <max@xxxxxxxxxx>

> diff --git a/usb/usb.c b/usb/usb.c
> new file mode 100644
> index 0000000..b9fc89f
> --- /dev/null
> +++ b/usb/usb.c
> @@ -0,0 +1,871 @@
> +/******************************************************************************
> + * Copyright (c) 2015 Max Reitz                                               *
> + *                                                                            *
> + * Permission is hereby granted,  free of charge,  to any person  obtaining a *
> + * copy of this software and associated documentation files (the "Software"), *
> + * to deal in the Software without restriction,  including without limitation *
> + * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
> + * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
> + * Software is furnished to do so, subject to the following conditions:       *
> + *                                                                            *
> + * The above copyright notice and this permission notice shall be included in *
> + * all copies or substantial portions of the Software.                        *
> + *                                                                            *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
> + * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
> + * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
> + * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
> + * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
> + * DEALINGS IN THE SOFTWARE.                                                  *
> + ******************************************************************************/
> +
> +#include <assert.h>
> +#include <errno.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include <cdi.h>
> +#include <cdi/misc.h>
> +#include <cdi/usb.h>
> +#include <cdi/usb_hcd.h>
> +
> +#include "usb.h"
> +#include "usb-hubs.h"
> +
> +
> +// #define DEBUG
> +
> +#ifdef DEBUG
> +#define DPRINTF(...) printf(__VA_ARGS__)
> +#else
> +#define DPRINTF(...)
> +#endif
> +
> +
> +static int current_usb_addr = 1;
> +
> +
> +static cdi_usb_transmission_result_t
> +    do_control_transfer(struct cdi_usb_hc *hc, int dev, cdi_usb_speed_t speed,
> +                        int tt_addr, int tt_port, const usb_endpoint_t *ep,
> +                        const struct cdi_usb_setup_packet *setup, void *data)
> +{
> +    struct cdi_usb_hcd *hcd = CDI_UPCAST(hc->dev.driver, struct cdi_usb_hcd,
> +                                         drv);
> +    cdi_usb_hc_transaction_t ta;
> +    cdi_usb_transmission_result_t r1, r2, r3;
> +
> +    ta = hcd->create_transaction(hc, &(struct cdi_usb_hc_ep_info){
> +            .dev        = dev,
> +            .ep         = ep->ep,
> +            .ep_type    = ep->type,
> +            .speed      = speed,
> +            .mps        = ep->mps,
> +            .tt_addr    = tt_addr,
> +            .tt_port    = tt_port
> +        });
> +
> +    hcd->enqueue(hc, &(struct cdi_usb_hc_transmission){
> +            .ta     = ta,
> +            .token  = CDI_USB_SETUP,
> +            .buffer = (void *)setup,
> +            .size   = sizeof(*setup),
> +            .toggle = 0,
> +            .result = &r1
> +        });
> +
> +    if (data) {
> +        hcd->enqueue(hc, &(struct cdi_usb_hc_transmission){
> +                .ta     = ta,
> +                .token  = setup->bm_request_type & CDI_USB_CREQ_IN
> +                        ? CDI_USB_IN : CDI_USB_OUT,
> +                .buffer = data,
> +                .size   = setup->w_length,
> +                .toggle = 1,
> +                .result = &r2
> +            });
> +    } else {
> +        r2 = CDI_USB_OK;
> +    }
> +
> +    hcd->enqueue(hc, &(struct cdi_usb_hc_transmission){
> +            .ta     = ta,
> +            .token  = setup->bm_request_type & CDI_USB_CREQ_IN ? CDI_USB_OUT
> +                                                               : CDI_USB_IN,
> +            .toggle = 1,
> +            .result = &r3
> +        });
> +
> +    hcd->wait_transaction(hc, ta);
> +    hcd->destroy_transaction(hc, ta);
> +
> +    return r1 ? r1 : r2 ? r2 : r3;
> +}
> +
> +static cdi_usb_transmission_result_t
> +    control_transfer(const usb_device_t *dev, const usb_endpoint_t *ep,
> +                     const struct cdi_usb_setup_packet *setup, void *data)
> +{
> +    return do_control_transfer(dev->hc, dev->id, dev->speed, dev->tt_addr,
> +                               dev->tt_port, ep, setup, data);
> +}
> +
> +
> +static int get_mps0(usb_hub_t *hub, int hub_port, cdi_usb_speed_t speed)
> +{
> +    cdi_usb_transmission_result_t res;
> +    struct cdi_usb_device_descriptor device_descriptor;
> +    int tt_addr = 0, tt_port = 0;
> +
> +    if (speed < CDI_USB_HIGH_SPEED && hub->ldev) {
> +        if (hub->ldev->dev->speed < CDI_USB_HIGH_SPEED) {
> +            tt_addr = hub->ldev->dev->tt_addr;
> +            tt_port = hub->ldev->dev->tt_port;
> +        } else {
> +            tt_addr = hub->ldev->dev->id;
> +            tt_port = hub_port;
> +        }
> +    }
> +
> +    res = do_control_transfer(hub->hc, 0, speed, tt_addr, tt_port,
> +        &(usb_endpoint_t){
> +            .type       = CDI_USB_CONTROL,
> +            .mps        = 8
> +        }, &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +            .w_value            = (CDI_USB_DESC_DEVICE << 8) | 0,
> +            .w_length           = 8
> +        }, &device_descriptor);
> +
> +    if (res != CDI_USB_OK) {
> +        return -EIO;
> +    }
> +
> +    return device_descriptor.b_max_packet_size_0;
> +}
> +
> +
> +static usb_device_t *enumerate(usb_hub_t *hub, int hub_port,
> +                               cdi_usb_speed_t speed, int mps0)
> +{
> +    cdi_usb_transmission_result_t res;
> +    usb_device_t *dev = calloc(1, sizeof(*dev));
> +
> +    int new_id = current_usb_addr++;

I guess not having proper ID management is okay for now, but should we
at least check if new_id > 127 and print an error and fail then? The
spec says that in this case the behaviour of SET ADDRESS would be
unspecified.

> +    dev->hc = hub->hc;
> +    dev->speed = speed;
> +
> +    if (speed < CDI_USB_HIGH_SPEED && hub->ldev) {
> +        if (hub->ldev->dev->speed < CDI_USB_HIGH_SPEED) {
> +            dev->tt_addr = hub->ldev->dev->tt_addr;
> +            dev->tt_port = hub->ldev->dev->tt_port;
> +        } else {
> +            dev->tt_addr = hub->ldev->dev->id;
> +            dev->tt_port = hub_port;
> +        }
> +    }
> +
> +    dev->eps[0][0].type = CDI_USB_CONTROL;
> +    dev->eps[0][0].mps  = mps0;
> +
> +    res = control_transfer(dev, &dev->eps[0][0], &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_OUT,
> +            .b_request          = CDI_USB_CREQ_SET_ADDRESS,
> +            .w_value            = new_id
> +        }, NULL);
> +    if (res != CDI_USB_OK) {
> +        free(dev);
> +        return NULL;
> +    }
> +
> +    dev->id = new_id;
> +
> +    res = control_transfer(dev, &dev->eps[0][0], &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +            .w_value            = (CDI_USB_DESC_DEVICE << 8) | 0,
> +            .w_length           = sizeof(dev->dev_desc)
> +        }, &dev->dev_desc);
> +    if (res != CDI_USB_OK) {
> +        free(dev);
> +        return NULL;
> +    }
> +
> +    return dev;
> +}
> +
> +
> +#ifdef DEBUG
> +static void get_string_descriptor(usb_device_t *dev, int index,
> +                                  struct cdi_usb_string_descriptor *dst)
> +{
> +    cdi_usb_transmission_result_t res;
> +
> +    res = control_transfer(dev, &dev->eps[0][0], &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +            .w_value            = (CDI_USB_DESC_STRING << 8) | index,
> +            .w_length           = 1
> +        }, dst);
> +    if (res != CDI_USB_OK || !dst->b_length) {
> +        dst->b_length = 0;
> +        return;
> +    }
> +
> +    res = control_transfer(dev, &dev->eps[0][0], &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +            .w_value            = (CDI_USB_DESC_STRING << 8) | index,
> +            .w_length           = dst->b_length
> +        }, dst);
> +    if (res != CDI_USB_OK) {
> +        dst->b_length = 0;
> +        return;
> +    }
> +}
> +#endif
> +
> +
> +static int get_configurations(usb_device_t *dev)
> +{
> +    cdi_usb_transmission_result_t res;
> +    struct cdi_usb_configuration_descriptor config;
> +
> +    dev->configs = calloc(1, dev->dev_desc.b_num_configurations
> +                             * sizeof(*dev->configs));

I think the idea with calloc() was that you don't multiply manually. ;-)

> +
> +    for (int i = 0; i < (int)dev->dev_desc.b_num_configurations; i++) {
> +        res = control_transfer(dev, &dev->eps[0][0],
> +            &(struct cdi_usb_setup_packet){
> +                .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_IN,
> +                .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +                .w_value            = (CDI_USB_DESC_CONFIGURATION << 8) | i,
> +                .w_length           = sizeof(config)
> +            }, &config);
> +        if (res != CDI_USB_OK) {
> +            goto fail;
> +        }
> +
> +        dev->configs[i] = malloc(config.w_total_length);
> +        res = control_transfer(dev, &dev->eps[0][0],
> +            &(struct cdi_usb_setup_packet){
> +                .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_IN,
> +                .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +                .w_value            = (CDI_USB_DESC_CONFIGURATION << 8) | i,
> +                .w_length           = config.w_total_length
> +            }, dev->configs[i]);
> +        if (res != CDI_USB_OK) {
> +            goto fail;
> +        }
> +    }
> +
> +    return 0;
> +
> +fail:
> +    for (int i = 0; i < (int)dev->dev_desc.b_num_configurations; i++) {
> +        free(dev->configs[i]);
> +    }
> +    free(dev->configs);
> +    return -EIO;
> +}
> +
> +
> +static bool compare_configurations(usb_device_t *dev, int i, int j)
> +{
> +    // TODO: Prefer self-powered
> +    if (dev->configs[i]->b_max_power != dev->configs[j]->b_max_power) {
> +        return dev->configs[i]->b_max_power > dev->configs[j]->b_max_power;
> +    }
> +
> +    if (dev->configs[i]->b_num_interfaces != dev->configs[j]->b_num_interfaces)
> +    {
> +        return dev->configs[i]->b_num_interfaces
> +               > dev->configs[j]->b_num_interfaces;
> +    }
> +
> +    return false;
> +}
> +
> +
> +static int choose_configuration(usb_device_t *dev)
> +{
> +    cdi_usb_transmission_result_t res;
> +    int best_config = 0;
> +
> +    for (int i = 1; i < (int)dev->dev_desc.b_num_configurations; i++) {
> +        if (compare_configurations(dev, i, best_config)) {
> +            best_config = i;
> +        }
> +    }
> +
> +    dev->config = dev->configs[best_config];
> +
> +    res = control_transfer(dev, &dev->eps[0][0], &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_OUT,
> +            .b_request          = CDI_USB_CREQ_SET_CONFIGURATION,
> +            .w_value            = dev->config->b_configuration_value,
> +        }, NULL);
> +    if (res != CDI_USB_OK) {
> +        dev->config = NULL;
> +        return -EIO;
> +    }
> +
> +    return 0;
> +}
> +
> +
> +#ifdef DEBUG
> +static void debug_device(usb_device_t *dev)
> +{
> +    printf("[usb] new %s device %04x:%04x (%x.%x.%x) ",
> +           dev->speed == CDI_USB_LOW_SPEED  ?  "low speed" :
> +           dev->speed == CDI_USB_FULL_SPEED ? "full speed" :
> +           dev->speed == CDI_USB_HIGH_SPEED ? "high speed" :
> +           dev->speed == CDI_USB_SUPERSPEED ? "superspeed" :
> +                                              "futurespeed",
> +           dev->dev_desc.id_vendor, dev->dev_desc.id_product,
> +           dev->dev_desc.b_device_class,
> +           dev->dev_desc.b_device_sub_class,
> +           dev->dev_desc.b_device_protocol);
> +
> +    struct cdi_usb_string_descriptor str;
> +
> +    if (dev->dev_desc.i_manufacturer) {
> +        get_string_descriptor(dev, dev->dev_desc.i_manufacturer, &str);
> +        for (int i = 0; i < (str.b_length - 2) / 2; i++) {
> +            putchar(str.b_string[i] > 0x7f ? '?' : str.b_string[i]);
> +        }
> +        putchar(' ');
> +    }
> +
> +    if (dev->dev_desc.i_product) {
> +        get_string_descriptor(dev, dev->dev_desc.i_product, &str);
> +        for (int i = 0; i < (str.b_length - 2) / 2; i++) {
> +            putchar(str.b_string[i] > 0x7f ? '?' : str.b_string[i]);
> +        }
> +        putchar(' ');
> +    }
> +
> +    putchar('\n');
> +}
> +
> +static void debug_configuration(usb_device_t *dev)
> +{
> +    printf("[usb] chose configuration %i ", dev->config->b_configuration_value);
> +
> +    struct cdi_usb_string_descriptor str;
> +
> +    if (dev->config->i_configuration) {
> +        get_string_descriptor(dev, dev->config->i_configuration, &str);
> +        putchar('(');
> +        for (int i = 0; i < (str.b_length - 2) / 2; i++) {
> +            putchar(str.b_string[i] > 0x7f ? '?' : str.b_string[i]);
> +        }
> +        putchar(')');
> +        putchar(' ');
> +    }
> +
> +    printf("with %i interface(s):\n", dev->config->b_num_interfaces);
> +
> +    struct cdi_usb_interface_descriptor *ifc = (void *)(dev->config + 1);
> +    for (int i = 0; i < (int)dev->config->b_num_interfaces; i++) {
> +        printf("[usb] ");
> +        if (ifc->i_interface) {
> +            get_string_descriptor(dev, ifc->i_interface, &str);
> +            for (int j = 0; j < (int)dev->config->b_num_interfaces; j++) {
> +                putchar(str.b_string[j] > 0x7f ? '?' : str.b_string[j]);
> +            }
> +            putchar(' ');
> +        }
> +        printf("%x.%x.%x\n", ifc->b_interface_class,
> +               ifc->b_interface_sub_class, ifc->b_interface_protocol);
> +
> +        ifc = (void *)((struct cdi_usb_endpoint_descriptor *)(ifc + 1)
> +                + ifc->b_num_endpoints);
> +    }
> +}
> +#endif
> +
> +
> +static void new_hub_device(usb_logical_device_t *ldev);
> +
> +static void new_hub(usb_hub_t *hub)
> +{
> +    for (int i = 0; i < hub->cdi->ports; i++) {
> +        hub->drv->port_disable(hub->cdi, i);
> +    }
> +
> +    DPRINTF("[usb] new hub with %i ports detected\n", hub->cdi->ports);
> +
> +    for (int i = 0; i < hub->cdi->ports; i++) {
> +        hub->drv->port_reset_enable(hub->cdi, i);
> +        cdi_sleep_ms(5);
> +
> +        cdi_usb_port_status_t status = hub->drv->port_status(hub->cdi, i);
> +        if (!(status & CDI_USB_PORT_CONNECTED)) {
> +            hub->drv->port_disable(hub->cdi, i);
> +            continue;
> +        }
> +
> +        int mps0 = get_mps0(hub, i, status & CDI_USB_PORT_SPEED_MASK);
> +        if (mps0 < 0) {
> +            fprintf(stderr, "[usb] failed to inquire MPS0\n");
> +            hub->drv->port_disable(hub->cdi, i);
> +            continue;
> +        }
> +
> +        hub->drv->port_reset_enable(hub->cdi, i);
> +        cdi_sleep_ms(5);
> +
> +        status = hub->drv->port_status(hub->cdi, i);
> +        if (!(status & CDI_USB_PORT_CONNECTED)) {
> +            hub->drv->port_disable(hub->cdi, i);
> +            continue;
> +        }

I'm sure there is a reason for this second reset, but I couldn't find it
in the spec and the procedure looks a bit odd. If you still remember the
reason, a comment might be nice.

> +
> +        usb_device_t *dev = enumerate(hub, i, status & CDI_USB_PORT_SPEED_MASK,
> +                                      mps0);
> +        if (!dev) {
> +            fprintf(stderr, "[usb] failed to enumerate device\n");
> +            hub->drv->port_disable(hub->cdi, i);
> +            continue;
> +        }
> +
> +#ifdef DEBUG
> +        debug_device(dev);
> +#endif
> +
> +        usb_logical_device_t *ldev = NULL;
> +        int ldev_ep_idx = 0;
> +        bool interface_level;
> +
> +        interface_level = dev->dev_desc.b_device_class == CDI_USB_DEVCLS_NONE;
> +        if (!interface_level) {
> +            ldev = calloc(1, sizeof(*ldev));
> +            ldev->cdi.bus_data.bus_type = CDI_USB;
> +            ldev->cdi.interface         = -1;
> +            ldev->cdi.endpoint_count    = 1; // EP0
> +            ldev->cdi.vendor_id         = dev->dev_desc.id_vendor;
> +            ldev->cdi.product_id        = dev->dev_desc.id_product;
> +            ldev->cdi.class_id          = dev->dev_desc.b_device_class;
> +            ldev->cdi.subclass_id       = dev->dev_desc.b_device_sub_class;
> +            ldev->cdi.protocol_id       = dev->dev_desc.b_device_protocol;
> +
> +            ldev->dev = dev;
> +
> +            ldev->endpoints = malloc(1 * sizeof(ldev->endpoints[0]));
> +            ldev->endpoints[0] = 0; // EP0
> +            ldev_ep_idx = 1;
> +        }
> +
> +        if (get_configurations(dev) < 0) {
> +            fprintf(stderr, "[usb] failed to get device configurations\n");
> +            free(ldev->endpoints);
> +            free(ldev);
> +            free(dev);
> +            hub->drv->port_disable(hub->cdi, i);
> +            continue;
> +        }
> +        if (choose_configuration(dev) < 0) {
> +            fprintf(stderr, "[usb] failed to select device configuration\n");
> +            free(ldev->endpoints);
> +            free(ldev);
> +            for (int j = 0; j < (int)dev->dev_desc.b_num_configurations; j++) {
> +                free(dev->configs[j]);
> +            }
> +            free(dev->config);
> +            free(dev);
> +            hub->drv->port_disable(hub->cdi, i);
> +            continue;
> +        }
> +
> +#ifdef DEBUG
> +        debug_configuration(dev);
> +#endif
> +
> +        struct cdi_usb_interface_descriptor *ifc = (void *)(dev->config + 1);
> +        for (int j = 0; j < (int)dev->config->b_num_interfaces; j++) {
> +            if (interface_level) {
> +                ldev = calloc(1, sizeof(*ldev));
> +                ldev->cdi.bus_data.bus_type = CDI_USB;
> +                ldev->cdi.interface         = j;
> +                // Include EP0
> +                ldev->cdi.endpoint_count    = ifc->b_num_endpoints + 1;
> +                ldev->cdi.vendor_id         = dev->dev_desc.id_vendor;
> +                ldev->cdi.product_id        = dev->dev_desc.id_product;
> +                ldev->cdi.class_id          = ifc->b_interface_class;
> +                ldev->cdi.subclass_id       = ifc->b_interface_sub_class;
> +                ldev->cdi.protocol_id       = ifc->b_interface_protocol;
> +
> +                ldev->dev = dev;
> +
> +                ldev->endpoints = malloc(ldev->cdi.endpoint_count
> +                                         * sizeof(ldev->endpoints[0]));
> +                ldev->endpoints[0] = 0; // EP0
> +                ldev_ep_idx = 1;
> +            } else {
> +                ldev->cdi.endpoint_count += ifc->b_num_endpoints;
> +                ldev->endpoints = realloc(ldev->endpoints,
> +                                          ldev->cdi.endpoint_count
> +                                          * sizeof(ldev->endpoints[0]));
> +            }
> +
> +            struct cdi_usb_endpoint_descriptor *ep = (void *)(ifc + 1);
> +            for (int k = 0; k < (int)ifc->b_num_endpoints; k++, ep++) {
> +                bool in = ep->b_endpoint_address & CDI_USB_EPDSC_EPADR_DIR;
> +                int id  = CDI_USB_EPDSC_EPADR_ID(ep->b_endpoint_address);
> +
> +                dev->eps[in][id].ep     = id;
> +                dev->eps[in][id].mps
> +                    = CDI_USB_EPDSC_MPS_MPS(ep->w_max_packet_size);
> +                dev->eps[in][id].desc   = ep;
> +
> +                switch (ep->bm_attributes & CDI_USB_EPDSC_ATTR_XFER_TYPE_MASK) {
> +                    case CDI_USB_EPDSC_ATTR_CONTROL:
> +                        dev->eps[in][id].type = CDI_USB_CONTROL;
> +                        break;
> +                    case CDI_USB_EPDSC_ATTR_ISOCHRONOUS:
> +                        dev->eps[in][id].type = CDI_USB_ISOCHRONOUS;
> +                        break;
> +                    case CDI_USB_EPDSC_ATTR_BULK:
> +                        dev->eps[in][id].type = CDI_USB_BULK;
> +                        break;
> +                    case CDI_USB_EPDSC_ATTR_INTERRUPT:
> +                        dev->eps[in][id].type = CDI_USB_INTERRUPT;
> +                        break;
> +                }
> +
> +                ldev->endpoints[ldev_ep_idx++] = ((int)in << 4) | id;
> +            }
> +
> +            if (interface_level) {
> +                if (ldev->cdi.class_id == CDI_USB_DEVCLS_HUB) {
> +                    new_hub_device(ldev);
> +                } else {
> +                    cdi_provide_device(&ldev->cdi.bus_data);
> +                }
> +            }
> +
> +            ifc = (void *)ep;
> +        }
> +
> +        if (!interface_level) {
> +            if (ldev->cdi.class_id == CDI_USB_DEVCLS_HUB) {
> +                new_hub_device(ldev);
> +            } else {
> +                cdi_provide_device(&ldev->cdi.bus_data);
> +            }
> +        }
> +    }
> +}
> +
> +struct cdi_device *usb_init_hc(struct cdi_bus_data *bus_data)
> +{
> +    struct cdi_usb_hc_bus *hc_bus = CDI_UPCAST(bus_data, struct cdi_usb_hc_bus,
> +                                               bus_data);
> +    struct cdi_usb_hc *hc = hc_bus->hc;
> +    struct cdi_usb_hcd *hcd = CDI_UPCAST(hc->dev.driver, struct cdi_usb_hcd,
> +                                         drv);
> +
> +    usb_hub_t *hub = calloc(1, sizeof(*hub));
> +    hub->cdi = &hc->rh;
> +    hub->hc = hc;
> +    hub->drv = &hcd->rh_drv;
> +
> +    new_hub(hub);
> +
> +    return NULL;
> +}
> +
> +
> +void usb_get_endpoint_descriptor(struct cdi_usb_device *dev, int ep,
> +                                 struct cdi_usb_endpoint_descriptor *desc)
> +{
> +    usb_logical_device_t *ldev = CDI_UPCAST(dev, usb_logical_device_t, cdi);
> +
> +    if (ep < 0 || ep >= dev->endpoint_count) {
> +        return;
> +    }
> +
> +    int ep_idx = ldev->endpoints[ep];
> +    usb_endpoint_t *ep_info = &ldev->dev->eps[ep_idx >> 4][ep_idx & 0xf];
> +
> +    if (ep_idx) {
> +        assert(ep_info->desc);
> +        memcpy(desc, ep_info->desc, sizeof(*desc));
> +    } else {
> +        *desc = (struct cdi_usb_endpoint_descriptor){
> +            .b_length           = sizeof(*desc),
> +            .b_descriptor_type  = CDI_USB_DESC_ENDPOINT,
> +            .b_endpoint_address = 0,
> +            .bm_attributes      = 0x00,
> +            .w_max_packet_size  = ep_info->mps,
> +            .b_interval         = 0
> +        };
> +    }
> +}
> +
> +
> +cdi_usb_transmission_result_t
> +    usb_control_transfer(struct cdi_usb_device *dev, int ep,
> +                         const struct cdi_usb_setup_packet *setup, void *data)
> +{
> +    usb_logical_device_t *ldev = CDI_UPCAST(dev, usb_logical_device_t, cdi);
> +
> +    if (ep < 0 || ep >= dev->endpoint_count) {
> +        return CDI_USB_DRIVER_ERROR;
> +    }
> +
> +    int ep_idx = ldev->endpoints[ep];
> +    usb_endpoint_t *ep_info = &ldev->dev->eps[ep_idx >> 4][ep_idx & 0xf];
> +
> +    if (ep_info->type != CDI_USB_CONTROL) {
> +        return CDI_USB_DRIVER_ERROR;
> +    }
> +
> +    return control_transfer(ldev->dev, ep_info, setup, data);
> +}
> +
> +
> +cdi_usb_transmission_result_t usb_bulk_transfer(struct cdi_usb_device *dev,
> +                                                int ep, void *data, size_t size)
> +{
> +    usb_logical_device_t *ldev = CDI_UPCAST(dev, usb_logical_device_t, cdi);
> +
> +    if (ep < 0 || ep >= dev->endpoint_count) {
> +        return CDI_USB_DRIVER_ERROR;
> +    }
> +
> +    int ep_idx = ldev->endpoints[ep];
> +    usb_endpoint_t *ep_info = &ldev->dev->eps[ep_idx >> 4][ep_idx & 0xf];
> +
> +    if (ep_info->type != CDI_USB_BULK) {
> +        return CDI_USB_DRIVER_ERROR;
> +    }
> +
> +    struct cdi_usb_hcd *hcd = CDI_UPCAST(ldev->dev->hc->dev.driver,
> +                                         struct cdi_usb_hcd, drv);
> +    cdi_usb_hc_transaction_t ta;
> +    cdi_usb_transmission_result_t res;
> +
> +    ta = hcd->create_transaction(ldev->dev->hc, &(struct cdi_usb_hc_ep_info){
> +            .dev        = ldev->dev->id,
> +            .ep         = ep_info->ep,
> +            .ep_type    = ep_info->type,
> +            .speed      = ldev->dev->speed,
> +            .mps        = ep_info->mps,
> +            .tt_addr    = ldev->dev->tt_addr,
> +            .tt_port    = ldev->dev->tt_port
> +        });
> +
> +    hcd->enqueue(ldev->dev->hc, &(struct cdi_usb_hc_transmission){
> +            .ta     = ta,
> +            .token  = ep_idx >> 4 ? CDI_USB_IN : CDI_USB_OUT,
> +            .buffer = data,
> +            .size   = size,
> +            .toggle = ep_info->toggle,
> +            .result = &res
> +        });
> +    ep_info->toggle ^= (((size + ep_info->mps - 1) / ep_info->mps) & 1);
> +
> +    hcd->wait_transaction(ldev->dev->hc, ta);
> +    hcd->destroy_transaction(ldev->dev->hc, ta);
> +
> +    return res;
> +}
> +
> +
> +static struct cdi_usb_hub_driver hub_drv;
> +
> +
> +static void new_hub_device(usb_logical_device_t *ldev)
> +{
> +    cdi_usb_transmission_result_t res;
> +    usb_hub_device_t *hub;
> +    struct usb_hub_descriptor *hub_desc = NULL;
> +
> +    assert(ldev->cdi.interface < 0);
> +
> +    hub = calloc(1, sizeof(*hub));
> +
> +    hub->hub = calloc(1, sizeof(*hub->hub));
> +    hub->hub->cdi = &hub->cdi;
> +    hub->hub->drv = &hub_drv;
> +    hub->hub->hc = ldev->dev->hc;
> +    hub->hub->ldev = ldev;
> +
> +    uint8_t hub_desc_length;
> +    res = usb_control_transfer(&ldev->cdi, 0, &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +            .w_value            = (USB_DESC_HUB << 8) | 0,
> +            .w_length           = 1
> +        }, &hub_desc_length);
> +    if (res != CDI_USB_OK ||
> +        hub_desc_length < sizeof(struct usb_hub_descriptor))
> +    {
> +        goto fail;
> +    }
> +
> +    hub_desc = malloc(hub_desc_length);
> +    res = usb_control_transfer(&ldev->cdi, 0, &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_DEVICE | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_DESCRIPTOR,
> +            .w_value            = (USB_DESC_HUB << 8) | 0,
> +            .w_length           = hub_desc_length
> +        }, hub_desc);
> +    if (res != CDI_USB_OK) {
> +        goto fail;
> +    }
> +
> +    int ports = hub_desc->b_nbr_ports;
> +    hub->cdi.ports = ports;
> +
> +    // Enable port power
> +    if (!(hub_desc->w_hub_characteristics & (1 << 1))) {
> +        // Hub supports power switching, enable global power
> +        res = usb_control_transfer(&ldev->cdi, 0,
> +            &(struct cdi_usb_setup_packet){
> +                .bm_request_type    = CDI_USB_CREQ_DEVICE
> +                                    | CDI_USB_CREQ_CLASS | CDI_USB_CREQ_OUT,
> +                .b_request          = CDI_USB_CREQ_SET_FEATURE,
> +                .w_value            = C_HUB_LOCAL_POWER
> +            }, NULL);
> +        if (res != CDI_USB_OK) {
> +            goto fail;
> +        }
> +    }
> +
> +    if ((hub_desc->w_hub_characteristics & 0x3) == 1) {
> +        // Hub supports per-port power switching, enable power for each port
> +        for (int port = 0; port < ports; port++) {
> +            res = usb_control_transfer(&ldev->cdi, 0,
> +                &(struct cdi_usb_setup_packet){
> +                    .bm_request_type    = CDI_USB_CREQ_OTHER
> +                                        | CDI_USB_CREQ_CLASS
> +                                        | CDI_USB_CREQ_OUT,
> +                    .b_request          = CDI_USB_CREQ_SET_FEATURE,
> +                    .w_value            = PORT_POWER,
> +                    .w_index            = port + 1
> +                }, NULL);
> +            if (res != CDI_USB_OK) {
> +                goto fail;
> +            }
> +        }
> +    }
> +
> +    cdi_sleep_ms(hub_desc->b_pwr_on_2_pwr_good * 2);
> +
> +    free(hub_desc);
> +
> +    new_hub(hub->hub);
> +
> +    return;
> +
> +fail:
> +    free(hub_desc);
> +    free(hub->hub);
> +    free(hub);
> +    return;
> +}
> +
> +
> +static void hub_port_down(struct cdi_usb_hub *cdi_hub, int index)
> +{
> +    usb_hub_device_t *hub = CDI_UPCAST(cdi_hub, usb_hub_device_t, cdi);
> +
> +    usb_control_transfer(&hub->hub->ldev->cdi, 0,
> +        &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_OTHER | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_OUT,
> +            .b_request          = CDI_USB_CREQ_CLEAR_FEATURE,
> +            .w_value            = PORT_ENABLE,
> +            .w_index            = index + 1
> +        }, NULL);
> +}
> +
> +
> +static void hub_port_up(struct cdi_usb_hub *cdi_hub, int index)
> +{
> +    usb_hub_device_t *hub = CDI_UPCAST(cdi_hub, usb_hub_device_t, cdi);
> +
> +    usb_control_transfer(&hub->hub->ldev->cdi, 0,
> +        &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_OTHER | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_OUT,
> +            .b_request          = CDI_USB_CREQ_SET_FEATURE,
> +            .w_value            = PORT_ENABLE,
> +            .w_index            = index + 1
> +        }, NULL);

"This bit may be set only as a result of a SetPortFeature(PORT_RESET)
request. [...]
The hub response to a SetPortFeature(PORT_ENABLE) request is not
specified. The preferred behavior is that the hub respond with a Request
Error. This may not be used by the USB System Software."

Good that you don't check the result. ;-)

> +    usb_control_transfer(&hub->hub->ldev->cdi, 0,
> +        &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_OTHER | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_OUT,
> +            .b_request          = CDI_USB_CREQ_SET_FEATURE,
> +            .w_value            = PORT_RESET,
> +            .w_index            = index + 1
> +        }, NULL);
> +
> +    cdi_sleep_ms(50);
> +
> +    usb_control_transfer(&hub->hub->ldev->cdi, 0,
> +        &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_OTHER | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_OUT,
> +            .b_request          = CDI_USB_CREQ_CLEAR_FEATURE,
> +            .w_value            = PORT_RESET,
> +            .w_index            = index + 1
> +        }, NULL);

"The ClearPortFeature(PORT_RESET) request shall not be used by the USB
System Software and may be treated as a no-operation request by hubs."

If I understand the procedure correctly, C_PORT_RESET should be checked
to see whether the reset was complete, and then C_PORT_RESET can be
cleared.

> +
> +    cdi_sleep_ms(5);
> +}
> +
> +
> +static cdi_usb_port_status_t hub_port_status(struct cdi_usb_hub *cdi_hub,
> +                                             int index)
> +{
> +    cdi_usb_transmission_result_t res;
> +    usb_hub_device_t *hub = CDI_UPCAST(cdi_hub, usb_hub_device_t, cdi);
> +
> +    uint32_t portsc;
> +    res = usb_control_transfer(&hub->hub->ldev->cdi, 0,
> +        &(struct cdi_usb_setup_packet){
> +            .bm_request_type    = CDI_USB_CREQ_OTHER | CDI_USB_CREQ_CLASS
> +                                | CDI_USB_CREQ_IN,
> +            .b_request          = CDI_USB_CREQ_GET_STATUS,
> +            .w_index            = index + 1,
> +            .w_length           = sizeof(portsc)
> +        }, &portsc);
> +    if (res != CDI_USB_OK) {
> +        return 0;
> +    }
> +
> +    cdi_usb_port_status_t status = 0;
> +
> +    if (portsc & ((1 << PORT_ENABLE) | (1 << PORT_CONNECTION))) {
> +        status |= CDI_USB_PORT_CONNECTED;
> +        if (portsc & (1 << PORT_LOW_SPEED)) {
> +            status |= CDI_USB_LOW_SPEED;
> +        } else if (portsc & (1 << PORT_HIGH_SPEED)) {
> +            status |= CDI_USB_HIGH_SPEED;
> +        } else {
> +            status |= CDI_USB_FULL_SPEED;
> +        }
> +    }
> +
> +    return status;
> +}
> +
> +
> +static struct cdi_usb_hub_driver hub_drv = {
> +    .port_disable       = hub_port_down,
> +    .port_reset_enable  = hub_port_up,
> +    .port_status        = hub_port_status,
> +};

Kevin