// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021-2022 Martin Whitaker.

#include "keyboard.h"
#include "memrw32.h"
#include "pci.h"
#include "screen.h"
#include "usb.h"
#include "vmem.h"

#include "ehci.h"
#include "ohci.h"
#include "uhci.h"
#include "xhci.h"

#include "print.h"
#include "unistd.h"

#include "usbhcd.h"

//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------

#define MAX_HCI                 16      // an arbitrary limit - only affects stack usage

#define MAX_HCD                 8       // an arbitrary limit - must match the initialisation of hcd_list

#define PAUSE_IF_NONE_TIME      10      // seconds

#define MILLISEC                1000    // in microseconds

//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------

typedef enum {
    NOT_HCI         = -1,
    UHCI            = 0,
    OHCI            = 1,
    EHCI            = 2,
    XHCI            = 3,
    MAX_HCI_TYPE    = 4
} hci_type_t;

typedef struct {
    hci_type_t      type;
    uint8_t         bus;
    uint8_t         dev;
    uint8_t         func;
    uintptr_t       pm_base_addr;
    uintptr_t       vm_base_addr;
} hci_info_t;

//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------

static const char *hci_name[MAX_HCI_TYPE] = { "UHCI", "OHCI", "EHCI", "XHCI" };

static const hcd_methods_t methods = {
    .reset_root_hub_port = NULL,
    .allocate_slot       = NULL,
    .release_slot        = NULL,
    .assign_address      = NULL,
    .configure_hub_ep    = NULL,
    .configure_kbd_ep    = NULL,
    .setup_request       = NULL,
    .get_data_request    = NULL,
    .poll_keyboards      = NULL
};

// All entries in this array must be initialised in order to generate the necessary relocation records.
static usb_hcd_t hcd_list[MAX_HCD] = {
    { &methods, NULL },
    { &methods, NULL },
    { &methods, NULL },
    { &methods, NULL },
    { &methods, NULL },
    { &methods, NULL },
    { &methods, NULL },
    { &methods, NULL }
};

static int num_hcd = 0;

static int print_row = 0;
static int print_col = 0;

//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------

usb_init_options_t usb_init_options = USB_DEFAULT_INIT;

//------------------------------------------------------------------------------
// Macro Functions
//------------------------------------------------------------------------------

#define MIN(a, b)   (((a) < (b)) ? (a) : (b))

//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------

static usb_endpoint_desc_t *find_hub_endpoint_descriptor(const uint8_t *desc_buffer, int desc_length)
{
    const uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t);
    const uint8_t *tail_ptr = desc_buffer + desc_length;
    while (curr_ptr < tail_ptr) {
        const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr;
        const uint8_t *next_ptr = curr_ptr + header->length;

        // Basic checks for validity.
        if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break;

        if (header->type == USB_DESC_ENDPOINT && header->length == sizeof(usb_endpoint_desc_t)) {
            usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr;
#if 0
            print_usb_info("endpoint addr 0x%02x attr 0x%02x",
                           (uintptr_t)endpoint->address, (uintptr_t)endpoint->attributes);
#endif
            if ((endpoint->address & 0x80) && (endpoint->attributes & 0x3) == 0x3) {
                return endpoint;
            }
        }
        curr_ptr = next_ptr;
    }
    return NULL;
}

static bool build_hub_info(const usb_hcd_t *hcd, const usb_hub_t *parent, int port_num, const usb_ep_t *ep0,
                           usb_hub_t *hub, usb_ep_t *ep1)
{
    usb_setup_pkt_t setup_pkt;
    usb_hub_desc_t  hub_desc;

    build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE | USB_REQ_CLASS, HUB_GET_DESCRIPTOR,
                                   HUB_DESC_DEVICE << 8, 0, sizeof(hub_desc));
    if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, &hub_desc, sizeof(hub_desc))) {
        return false;
    }

    hub->ep0            = ep0;
    hub->level          = parent->level + 1;
    hub->route          = usb_route(parent, port_num);
    hub->num_ports      = hub_desc.num_ports;
    hub->tt_think_time  = hub_desc.characteristics & 0x0060 >> 5;
    hub->power_up_delay = hub_desc.power_up_delay;
    hub->hs_parent      = usb_hs_parent(parent, port_num, ep0->device_speed);

    usb_endpoint_desc_t *ep1_desc = find_hub_endpoint_descriptor(hcd->ws->data_buffer, hcd->ws->data_length);
    if (ep1_desc == NULL) {
        return false;
    }

    ep1->driver_data     = ep0->driver_data;
    ep1->device_speed    = ep0->device_speed;
    ep1->device_id       = ep0->device_id;
    ep1->interface_num   = 0;
    ep1->endpoint_num    = ep1_desc->address & 0xf;
    ep1->max_packet_size = ep1_desc->max_packet_size;
    ep1->interval        = ep1_desc->interval;

    return true;
}

static bool get_hub_port_status(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num, uint32_t *port_status)
{
    usb_setup_pkt_t setup_pkt;

    build_setup_packet(&setup_pkt, USB_REQ_FROM_HUB_PORT | USB_REQ_CLASS, HUB_GET_STATUS,
                                   0, port_num, sizeof(uint32_t));
    return hcd->methods->get_data_request(hcd, hub->ep0, &setup_pkt, port_status, sizeof(uint32_t));
}

static int get_configuration_descriptors(const usb_hcd_t *hcd, const usb_ep_t *ep0, int config_idx)
{
    // Fetch the descriptors for the specified configuration. Start by requesting just the configuration descriptor.
    // Then read the descriptor to determine how much more data we need to fetch.

    usb_setup_pkt_t setup_pkt;

    uint8_t *data_buffer = hcd->ws->data_buffer;

    size_t fetch_length = sizeof(usb_config_desc_t);

  get_descriptor:
    build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR,
                                   USB_DESC_CONFIGURATION << 8 | config_idx, 0, fetch_length);
    if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, data_buffer, fetch_length)
    ||  !valid_usb_config_descriptor(data_buffer)) {
        return 0;
    }
    usb_config_desc_t *config = (usb_config_desc_t *)data_buffer;
    size_t total_length = MIN(config->total_length, HCD_DATA_BUFFER_SIZE);
    if (total_length > fetch_length) {
        fetch_length = total_length;
        goto get_descriptor;
    }

    hcd->ws->data_length = fetch_length;

    return config->config_num;
}

static void get_keyboard_info_from_descriptors(const uint8_t *desc_buffer, int desc_length, usb_ep_t keyboards[],
                                               int max_keyboards, int *num_keyboards)
{
    usb_ep_t *kbd = NULL;
    const uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t);
    const uint8_t *tail_ptr = desc_buffer + desc_length;
    while (curr_ptr < tail_ptr) {
        // If we've filled the keyboard info table, abort now.
        if (*num_keyboards >= max_keyboards) break;

        const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr;
        const uint8_t *next_ptr = curr_ptr + header->length;

        // Basic checks for validity.
        if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break;

        if (header->type == USB_DESC_INTERFACE && header->length == sizeof(usb_interface_desc_t)) {
            const usb_interface_desc_t *ifc = (const usb_interface_desc_t *)curr_ptr;
#if 0
            print_usb_info("interface %i class %i subclass %i protocol %i",
                           ifc->interface_num, ifc->class, ifc->subclass, ifc->protocol);
#endif
            if (ifc->class == 3 && ifc->subclass == 1 && ifc->protocol == 1) {
                kbd = &keyboards[*num_keyboards];
                kbd->interface_num = ifc->interface_num;
            } else {
                kbd = NULL;
            }
        } else if (header->type == USB_DESC_ENDPOINT && header->length == sizeof(usb_endpoint_desc_t)) {
            usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr;
#if 0
            print_usb_info("endpoint addr 0x%02x attr 0x%02x",
                           (uintptr_t)endpoint->address, (uintptr_t)endpoint->attributes);
#endif
            if (kbd && (endpoint->address & 0x80) && (endpoint->attributes & 0x3) == 0x3) {
                kbd->endpoint_num    = endpoint->address & 0xf;
                kbd->max_packet_size = endpoint->max_packet_size;
                kbd->interval        = endpoint->interval;
                kbd = NULL;

                *num_keyboards += 1;
            }
        }
        curr_ptr = next_ptr;
    }
}

static bool configure_device(const usb_hcd_t *hcd, const usb_ep_t *ep0, int config_num)
{
    usb_setup_pkt_t setup_pkt;

    build_setup_packet(&setup_pkt, USB_REQ_TO_DEVICE, USB_SET_CONFIGURATION, config_num, 0, 0);
    return hcd->methods->setup_request(hcd, ep0, &setup_pkt);
}

static bool configure_keyboard(const usb_hcd_t *hcd, const usb_ep_t *ep0, int interface_num)
{
    usb_setup_pkt_t setup_pkt;

    // Set the idle duration to infinite.
    build_setup_packet(&setup_pkt, USB_REQ_TO_INTERFACE | USB_REQ_CLASS, HID_SET_IDLE, 0, interface_num, 0);
    if (!hcd->methods->setup_request(hcd, ep0, &setup_pkt)) {
        return false;
    }

    // Select the boot protocol.
    build_setup_packet(&setup_pkt, USB_REQ_TO_INTERFACE | USB_REQ_CLASS, HID_SET_PROTOCOL, 0, interface_num, 0);
    if (!hcd->methods->setup_request(hcd, ep0, &setup_pkt)) {
        return false;
    }

    return true;
}

static bool scan_hub_ports(const usb_hcd_t *hcd, const usb_hub_t *hub, int *num_devices,
                           usb_ep_t keyboards[], int max_keyboards, int *num_keyboards)
{
    bool keyboard_found = false;

    usb_setup_pkt_t setup_pkt;

    // Power up all the ports.
    build_setup_packet(&setup_pkt, USB_REQ_TO_HUB_PORT | USB_REQ_CLASS, HUB_SET_FEATURE, HUB_PORT_POWER, 0, 0);
    for (int port_num = 1; port_num <= hub->num_ports; port_num++) {
        setup_pkt.index = port_num;
        if (!hcd->methods->setup_request(hcd, hub->ep0, &setup_pkt)) {
            return false;
        }
    }
    usleep(hub->power_up_delay * 2 * MILLISEC);

    usleep(100*MILLISEC);  // USB maximum device attach time.

    // Scan the ports, looking for hubs and keyboards.
    for (int port_num = 1; port_num <= hub->num_ports; port_num++) {
        // If we've filled the keyboard info table, abort now.
        if (*num_keyboards >= max_keyboards) break;

        uint32_t port_status;

        get_hub_port_status(hcd, hub, port_num, &port_status);

        // Check the port is powered up.
        if (~port_status & HUB_PORT_POWERED) continue;

        // Check if anything is connected to this port.
        if (~port_status & HUB_PORT_CONNECTED) continue;

        if (!reset_usb_hub_port(hcd, hub, port_num)) continue;

        get_hub_port_status(hcd, hub, port_num, &port_status);

        // Check the port is active.
        if (~port_status & HUB_PORT_CONNECTED) continue;
        if (~port_status & HUB_PORT_ENABLED)   continue;

        // Now the port has been enabled, we can determine the device speed.
        usb_speed_t device_speed;
        if        (port_status & HUB_PORT_LOW_SPEED) {
            device_speed = USB_SPEED_LOW;
        } else if (port_status & HUB_PORT_HIGH_SPEED) {
            device_speed = USB_SPEED_HIGH;
        } else {
            device_speed = USB_SPEED_FULL;
        }

        *num_devices += 1;

        // By default, using the incrementing count of devices as the device ID.
        int device_id = *num_devices;

        // Allocate a controller slot for this device (only needed for some controllers).
        if (hcd->methods->allocate_slot) {
            device_id = hcd->methods->allocate_slot(hcd);
            if (device_id == 0) break;
        }

        // Look for keyboards attached directly or indirectly to this port.
        if (find_attached_usb_keyboards(hcd, hub, port_num, device_speed, device_id, num_devices,
                                        keyboards, max_keyboards, num_keyboards)) {
            keyboard_found = true;
            continue;
        }

        // If we didn't find any keyboards, we can disable the port and release the slot.
        build_setup_packet(&setup_pkt, USB_REQ_TO_HUB_PORT | USB_REQ_CLASS, HUB_CLR_FEATURE, HUB_PORT_ENABLE, port_num, 0);
        (void)hcd->methods->setup_request(hcd, hub->ep0, &setup_pkt);

        if (hcd->methods->release_slot) {
            (void)hcd->methods->release_slot(hcd, device_id);
        }
    }

    return keyboard_found;
}

static int find_usb_controllers(hci_info_t hci_list[])
{
    int num_hci = 0;
    for (int bus = 0; bus < PCI_MAX_BUS; bus++) {
        for (int dev = 0; dev < PCI_MAX_DEV; dev++) {
            for (int func = 0; func < PCI_MAX_FUNC; func++) {
                // Test for device/function present.
                uint16_t vendor_id = pci_config_read16(bus, dev, func, 0x00);
                uint8_t  hdr_type  = pci_config_read8 (bus, dev, func, 0x0e);
                if (vendor_id != 0xffff) {
                    // Test for a USB controller.
                    uint16_t class_code = pci_config_read16(bus, dev, func, 0x0a);
                    if (class_code == 0x0c03) {
                        hci_type_t controller_type = pci_config_read8(bus, dev, func, 0x09) >> 4;
                        if (controller_type < MAX_HCI_TYPE) {
                            hci_list[num_hci].type = controller_type;
                            hci_list[num_hci].bus  = bus;
                            hci_list[num_hci].dev  = dev;
                            hci_list[num_hci].func = func;
                            num_hci++;
                            // If we've filled the table, abort now.
                            if (num_hci == MAX_HCI) {
                                return num_hci;
                            }
                        }
                    }
                    // Break out if this is a single function device.
                    if (func == 0 && (hdr_type & 0x80) == 0) {
                        break;
                    }
                } else {
                    // Break out if no device is present.
                    if (func == 0) {
                        break;
                    }
                }
            }
        }
    }
    return num_hci;
}

static void reset_usb_controller(hci_info_t *hci)
{
    hci_type_t controller_type = hci->type;

    int bus  = hci->bus;
    int dev  = hci->dev;
    int func = hci->func;

    uint16_t vendor_id  = pci_config_read16(bus, dev, func, 0x00);
    uint16_t device_id  = pci_config_read16(bus, dev, func, 0x02);
    uint16_t pci_status = pci_config_read16(bus, dev, func, 0x06);

    // Disable access to the device while we probe it.
    uint16_t pci_command = pci_config_read16(bus, dev, func, 0x04);
    pci_config_write16(bus, dev, func, 0x04, pci_command & ~0x0003);

    int bar = (controller_type == UHCI) ? 0x20 : 0x10;
    uintptr_t base_addr = pci_config_read32(bus, dev, func, bar);
    pci_config_write32(bus, dev, func, bar, 0xffffffff);
    uintptr_t mmio_size = pci_config_read32(bus, dev, func, bar);
    pci_config_write32(bus, dev, func, bar, base_addr);
    bool in_io_space = base_addr & 0x1;
#ifdef __x86_64__
    if (!in_io_space && (base_addr & 0x4)) {
        base_addr += (uintptr_t)pci_config_read32(bus, dev, func, bar + 4) << 32;
        pci_config_write32(bus, dev, func, bar + 4, 0xffffffff);
        mmio_size += (uintptr_t)pci_config_read32(bus, dev, func, bar + 4) << 32;
        pci_config_write32(bus, dev, func, bar + 4, base_addr >> 32);
    } else {
        mmio_size += (uintptr_t)0xffffffff << 32;
    }
#endif
    base_addr &= ~(uintptr_t)0xf;
    mmio_size &= ~(uintptr_t)0xf;
    mmio_size = ~mmio_size + 1;

    // Restore access to the device and set the bus master flag in case the BIOS hasn't.
    pci_config_write16(bus, dev, func, 0x04, pci_command | (in_io_space ? 0x0005 : 0x0006));

    hci->pm_base_addr = base_addr;

    print_usb_info("Found %s controller %04x:%04x at %08x size %08x in %s space", hci_name[controller_type],
                   (uintptr_t)vendor_id, (uintptr_t)device_id, base_addr, mmio_size, in_io_space ? "I/O" : "Mem");

    if (in_io_space) {
        if (controller_type != UHCI) {
            print_usb_info(" Unsupported address mapping for this controller type");
            hci->type = NOT_HCI;  // mark this controller as unusable
            return;
        }
    } else {
        if (controller_type == UHCI) {
            print_usb_info(" Unsupported address mapping for this controller type");
            hci->type = NOT_HCI;  // mark this controller as unusable
            return;
        }
        base_addr = map_region(base_addr, mmio_size, false);
        if (base_addr == 0) {
            print_usb_info(" Failed to map device into virtual memory");
            hci->type = NOT_HCI;  // mark this controller as unusable
            return;
        }
    }

    hci->vm_base_addr = base_addr;

    // Search for power management capability.
    //uint8_t pm_cap_ptr;
    if (pci_status & 0x10) {
        uint8_t cap_ptr = pci_config_read8(bus, dev, func, 0x34) & 0xfe;
        while (cap_ptr != 0) {
            uint8_t cap_id = pci_config_read8(bus, dev, func, cap_ptr);
            if (cap_id == 1) {
                uint16_t pm_status = pci_config_read16(bus, dev, func, cap_ptr+2);
                // Power on if necessary.
                if ((pm_status & 0x3) != 0) {
                    pci_config_write16(bus, dev, func, cap_ptr+2, 0x8000);
                    usleep(10000);
                }
                //pm_cap_ptr = cap_ptr;
                break;
            }
            cap_ptr = pci_config_read8(bus, dev, func, cap_ptr+1) & 0xfe;
        }
    }

    // Reset the device according to its type.
    bool success = false;
    switch (controller_type) {
      case UHCI:
        success = uhci_reset(bus, dev, func, base_addr);
        break;
      case OHCI:
        success = ohci_reset(base_addr);
        break;
      case EHCI:
        success = ehci_reset(bus, dev, func, base_addr);
        break;
      case XHCI:
        success = xhci_reset(base_addr);
        break;
      default:
        break;
    }
    if (!success) {
        hci->type = NOT_HCI;  // mark this controller as unusable
    }
}

static void probe_usb_controller(hci_type_t controller_type, uintptr_t pm_base_addr, uintptr_t vm_base_addr)
{
    print_usb_info("Probing %s controller at %08x", hci_name[controller_type], pm_base_addr);

    // Probe the device according to its type.
    bool keyboards_found = false;
    switch (controller_type) {
      case UHCI:
        keyboards_found = uhci_probe(vm_base_addr, &hcd_list[num_hcd]);
        break;
      case OHCI:
        keyboards_found = ohci_probe(vm_base_addr, &hcd_list[num_hcd]);
        break;
      case EHCI:
        keyboards_found = ehci_probe(vm_base_addr, &hcd_list[num_hcd]);
        break;
      case XHCI:
        keyboards_found = xhci_probe(vm_base_addr, &hcd_list[num_hcd]);
        break;
      default:
        break;
    }
    if (keyboards_found) {
        num_hcd++;
    }
}

//------------------------------------------------------------------------------
// Shared Functions (used by all drivers)
//------------------------------------------------------------------------------

uint32_t usb_route(const usb_hub_t *hub, int port_num)
{
    if (hub->level == 0) {
        return port_num << 24;
    }
    if (hub->level > 5) {
        port_num = 0;
    } else if (port_num > 15) {
        port_num = 15;
    }
    return hub->route | (port_num << (4 * (hub->level - 1)));
}

usb_parent_t usb_hs_parent(const usb_hub_t *hub, int port_num, usb_speed_t device_speed)
{
    usb_parent_t hs_parent = { 0, 0 };
    if (device_speed < USB_SPEED_HIGH && hub->level > 0) {
        if (hub->ep0->device_speed < USB_SPEED_HIGH) {
            hs_parent = hub->hs_parent;
        } else {
            hs_parent.device_id = hub->ep0->device_id;
            hs_parent.port_num  = port_num;
        }
    }
    return hs_parent;
}

bool wait_until_clr(const volatile uint32_t *reg, uint32_t bit_mask, int max_time)
{
    int timer = max_time >> 3;
    while (read32(reg) & bit_mask) {
        if (timer == 0) return false;
        usleep(8);
        timer--;
    }
    return true;
}

bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_time)
{
    int timer = max_time >> 3;
    while (~read32(reg) & bit_mask) {
        if (timer == 0) return false;
        usleep(8);
        timer--;
    }
    return true;
}

void print_usb_info(const char *fmt, ...)
{
    if (print_row == SCREEN_HEIGHT) {
        scroll_screen_region(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 1);
        print_row--;
    }

    va_list args;

    va_start(args, fmt);
    (void)vprintf(print_row++, print_col, fmt, args);
    va_end(args);
}

bool reset_usb_hub_port(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num)
{
    usb_setup_pkt_t setup_pkt;

    if (hub->level > 0) {
        build_setup_packet(&setup_pkt, USB_REQ_TO_HUB_PORT | USB_REQ_CLASS, HUB_SET_FEATURE,
                                       HUB_PORT_RESET, port_num, 0);
        if (!hcd->methods->setup_request(hcd, hub->ep0, &setup_pkt)) {
            return false;
        }
        int timer = 200;
        uint32_t port_status;
        do {
            usleep(1000);
            if (--timer == 0) return false;
            if (!get_hub_port_status(hcd, hub, port_num, &port_status)) {
                return false;
            }
        } while (port_status & HUB_PORT_RESETTING);
    } else {
        if (!hcd->methods->reset_root_hub_port(hcd, port_num)) {
            return false;
        }
    }

    usleep(10*MILLISEC);  // USB reset recovery time

    return true;
}

bool assign_usb_address(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
                        usb_speed_t device_speed, int device_id, usb_ep_t *ep0)
{
    usb_setup_pkt_t setup_pkt;

    uint8_t *data_buffer = hcd->ws->data_buffer;

    // If we've run out of USB addresses, abort now.
    if (device_id > USB_MAX_ADDRESS) {
        return false;
    }

    // Initialise the control endpoint descriptor.

    ep0->device_speed    = device_speed;
    ep0->device_id       = 0;
    ep0->interface_num   = 0;
    ep0->endpoint_num    = 0;
    ep0->max_packet_size = default_max_packet_size(device_speed);
    ep0->interval        = 0;

    // The device should currently be in Default state. For low and full speed devices, we first fetch the first
    // 8 bytes of the device descriptor to discover the maximum packet size for the control endpoint. We then set
    // the device address, which moves the device into Address state, and fetch the full device descriptor.

    size_t fetch_length = sizeof(usb_device_desc_t);
    if (device_speed < USB_SPEED_HIGH || usb_init_options & USB_2_STEP_INIT) {
        fetch_length = 8;
        goto fetch_descriptor;
    }

  set_address:
    build_setup_packet(&setup_pkt, USB_REQ_TO_DEVICE, USB_SET_ADDRESS, device_id, 0, 0);
    if (!hcd->methods->setup_request(hcd, ep0, &setup_pkt)) {
        return false;
    }
    ep0->device_id = device_id;

    usleep(2*MILLISEC + 1*MILLISEC);  // USB set address recovery time (plus a bit).

  fetch_descriptor:
    build_setup_packet(&setup_pkt, USB_REQ_FROM_DEVICE, USB_GET_DESCRIPTOR, USB_DESC_DEVICE << 8, 0, fetch_length);
    if (!hcd->methods->get_data_request(hcd, ep0, &setup_pkt, data_buffer, fetch_length)
    ||  !valid_usb_device_descriptor(data_buffer)) {
        return false;
    }
#if 0
    print_usb_info("%02x %02x %02x %02x %02x %02x %02x %02x",
                   (uintptr_t)data_buffer[0],
                   (uintptr_t)data_buffer[1],
                   (uintptr_t)data_buffer[2],
                   (uintptr_t)data_buffer[3],
                   (uintptr_t)data_buffer[4],
                   (uintptr_t)data_buffer[5],
                   (uintptr_t)data_buffer[6],
                   (uintptr_t)data_buffer[7]);
#endif

    if (fetch_length == 8) {
        usb_device_desc_t *device = (usb_device_desc_t *)data_buffer;
        ep0->max_packet_size = device->max_packet_size;
        if (!valid_usb_max_packet_size(device->max_packet_size, device_speed)) {
            return false;
        }
        if (usb_init_options & USB_EXTRA_RESET) {
            if (!reset_usb_hub_port(hcd, hub, port_num)) {
                return false;
            }
        }
        fetch_length = sizeof(usb_device_desc_t);
        goto set_address;
    }

    hcd->ws->data_length = fetch_length;

    return true;
}

bool find_attached_usb_keyboards(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num,
                                 usb_speed_t device_speed, int device_id, int *num_devices,
                                 usb_ep_t keyboards[], int max_keyboards, int *num_keyboards)
{
    bool keyboard_found = false;

    // Set the USB device address. If successful, this also fills in the descriptor for the default control endpoint
    // (ep0) and leaves the device descriptor in the data transfer buffer.
    usb_ep_t ep0;
    if (!hcd->methods->assign_address(hcd, hub, port_num, device_speed, device_id, &ep0)) {
        return false;
    }
    usb_device_desc_t *device = (usb_device_desc_t *)hcd->ws->data_buffer;
    bool is_hub = (device->class == USB_CLASS_HUB);

    // Fetch the descriptors for the first configuration into the data transfer buffer. In theory a keyboard device
    // may have more than one configuration and may only support the boot protocol in another configuration, but
    // this seems unlikely in practice. A hub should only ever have one configuration.
    int config_num = get_configuration_descriptors(hcd, &ep0, 0);
    if (config_num == 0) {
        return false;
    }

    if (is_hub) {
        usb_hub_t new_hub;
        usb_ep_t  ep1;
        if (!build_hub_info(hcd, hub, port_num, &ep0, &new_hub, &ep1)) {
            return false;
        }
        if (!configure_device(hcd, &ep0, config_num)) {
            return false;
        }
        if (hcd->methods->configure_hub_ep) {
            if (!hcd->methods->configure_hub_ep(hcd, &ep1, &new_hub)) {
                return false;
            }
        }
        print_usb_info(" %i port hub found on port %i", new_hub.num_ports, port_num);
        print_col += 1;
        keyboard_found = scan_hub_ports(hcd, &new_hub, num_devices, keyboards, max_keyboards, num_keyboards);
        print_col -= 1;
    } else {
        // Scan the configuration to see if this device has one or more interfaces that implement the keyboard
        // boot protocol and if so, record that information in the keyboard info table and configure the device.
        int old_num_keyboards = *num_keyboards;
        int new_num_keyboards = *num_keyboards;
        get_keyboard_info_from_descriptors(hcd->ws->data_buffer, hcd->ws->data_length,
                                           keyboards, max_keyboards, &new_num_keyboards);
        if (new_num_keyboards == old_num_keyboards) {
            return false;
        }
        if (!configure_device(hcd, &ep0, config_num)) {
            return false;
        }

        // Complete the new entries in the keyboard info table and configure the keyboard interfaces.
        for (int kbd_idx = old_num_keyboards; kbd_idx < new_num_keyboards; kbd_idx++) {
            usb_ep_t *kbd = &keyboards[kbd_idx];
            kbd->driver_data  = ep0.driver_data;
            kbd->device_speed = device_speed;
            kbd->device_id    = device_id;
            if (hcd->methods->configure_kbd_ep) {
                if (!hcd->methods->configure_kbd_ep(hcd, kbd, kbd_idx)) {
                    return false;
                }
            }
            if (!configure_keyboard(hcd, &ep0, kbd->interface_num)) break;

            print_usb_info(" Keyboard found on port %i interface %i endpoint %i",
                           port_num, kbd->interface_num, kbd->endpoint_num);

            keyboard_found = true;
            *num_keyboards += 1;
        }
    }

    return keyboard_found;
}

bool process_usb_keyboard_report(const usb_hcd_t *hcd, const hid_kbd_rpt_t *report, const hid_kbd_rpt_t *prev_report)
{
    hcd_workspace_t *ws = hcd->ws;

    int error_count = 0;
    for (int i = 0; i < 6; i++) {
        uint8_t key_code = report->key_code[i];
        if (key_code > 0x03) {
            // Check if we've already seen this key press.
            for (int j = 0; j < 6; j++) {
                if (prev_report->key_code[j] == key_code) {
                    key_code = 0;
                    break;
                }
            }
            // If not, put it in the key code buffer.
            if (key_code != 0) {
                int kc_index_i = ws->kc_index_i;
                int kc_index_n = (kc_index_i + 1) % HCD_KC_BUFFER_SIZE;
                if (kc_index_n != ws->kc_index_o) {
                    ws->kc_buffer[kc_index_i] = key_code;
                    ws->kc_index_i = kc_index_n;
                }
            }
        } else if (key_code != 0x00) {
            error_count++;
        }
    }
    return error_count < 6;
}

//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------

void find_usb_keyboards(bool pause_if_none)
{
    clear_screen();
    print_usb_info("Scanning for USB keyboards...");

    hci_info_t hci_list[MAX_HCI];

    int num_hci = find_usb_controllers(hci_list);

    // Take ownership of all controllers and reset them.
    for (int i = 0; i < num_hci; i++) {
        reset_usb_controller(&hci_list[i]);
    }

    num_hcd = 0;

    // As we don't support hot plugging, we need to probe EHCI controllers before
    // probing any of their companion controllers, to ensure any low and full speed
    // devices are routed to the companion controllers before we probe them.
    for (int i = 0; i < num_hci && num_hcd < MAX_HCD; i++) {
        if (hci_list[i].type == EHCI) {
            if (~usb_init_options & USB_IGNORE_EHCI) {
                probe_usb_controller(EHCI, hci_list[i].pm_base_addr, hci_list[i].vm_base_addr);
            }
            hci_list[i].type = NOT_HCI;  // prevent this controller from being scanned again
        }
    }

    // Now probe the other controllers.
    for (int i = 0; i < num_hci && num_hcd < MAX_HCD; i++) {
        if (hci_list[i].type != NOT_HCI) {
            probe_usb_controller(hci_list[i].type, hci_list[i].pm_base_addr, hci_list[i].vm_base_addr);
        }
    }

    if (usb_init_options & USB_DEBUG) {
        print_usb_info("Press any key to continue...");
        while (get_key() == 0) {}
    } else if (pause_if_none && num_hcd == 0) {
        for (int i = PAUSE_IF_NONE_TIME; i > 0; i--) {
            print_usb_info("No USB keyboards found. Continuing in %i second%c ", i, i == 1 ? ' ' : 's');
            sleep(1);
            print_row--; // overwrite message
        }
    }
}

uint8_t get_usb_keycode(void)
{
    for (int i = 0; i < num_hcd; i++) {
        const usb_hcd_t *hcd = &hcd_list[i];

        hcd->methods->poll_keyboards(hcd);

        int kc_index_o = hcd->ws->kc_index_o;
        if (kc_index_o != hcd->ws->kc_index_i) {
            hcd->ws->kc_index_o = (kc_index_o + 1) % HCD_KC_BUFFER_SIZE;
            return hcd->ws->kc_buffer[kc_index_o];
        }
    }
    return 0;
}
