// SPDX-License-Identifier: GPL-2.0
#ifndef USBHCD_H
#define USBHCD_H
/**
 * \file
 *
 * Provides the base USB host controller driver for USB keyboard support.
 *
 * This is an object-oriented design. The hcd_methods_t structure defines
 * a set of virtual methods that will be implemented by the subclasses.
 * The hcd_workspace_t structure defines the base class properties. The
 * usb_hcd_t structure represents a driver object. The non-virtual and
 * default base class methods are defined as separate functions, taking
 * a usb_hcd_t pointer as their first parameter.
 *
 * The find_usb_keyboards function instantiates a driver object of the
 * appropriate subclass for each USB controller it finds and stores it
 * in the private usb_controllers table, where it can subsequently used
 * to poll the keyboards for key presses.
 *
 *//*
 * Copyright (C) 2021-2022 Martin Whitaker.
 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "usb.h"

/**
 * The size of the data transfer buffer in a host controller driver workspace.
 */
#define HCD_DATA_BUFFER_SIZE    512  // bytes

/**
 * The size of the key code buffer in a host controller driver workspace.
 */
#define HCD_KC_BUFFER_SIZE      8  // keycodes

/**
 * A USB device speed (used internally by the various HCI drivers).
 */
typedef enum  __attribute__ ((packed)) {
    USB_SPEED_UNKNOWN   = 0,
    USB_SPEED_LOW       = 1,
    USB_SPEED_FULL      = 2,
    USB_SPEED_HIGH      = 3
} usb_speed_t;

/**
 * A USB endpoint descriptor (used internally by the various HCI drivers).
 */
typedef struct __attribute__ ((packed)) {
    uintptr_t       driver_data;
    usb_speed_t     device_speed;
    uint8_t         device_id;
    uint8_t         interface_num;
    uint8_t         endpoint_num;
    uint16_t        max_packet_size;
    uint8_t         interval;
    uint8_t         reserved;
} usb_ep_t;

/**
 * A USB parent device descriptor (used internally by the various HCI drivers).
 */
typedef struct __attribute__ ((packed)) {
    uint8_t         device_id;
    uint8_t         port_num;
} usb_parent_t;

/**
 * A USB hub descriptor (used internally by the various HCI drivers).
 */
typedef struct __attribute__ ((packed)) {
    const usb_ep_t  *ep0;
    uint32_t        route;
    uint8_t         level;
    uint8_t         num_ports;
    uint8_t         tt_think_time;
    uint8_t         power_up_delay;
    usb_parent_t    hs_parent;
    uint16_t        reserved;
} usb_hub_t;

/**
 * A USB host controller driver object reference.
 */
typedef const struct usb_hcd_s *usb_hcd_r;

/**
 * A USB host controller driver method table.
 */
typedef struct {
    bool    (*reset_root_hub_port)  (usb_hcd_r, int);
    int     (*allocate_slot)        (usb_hcd_r);
    bool    (*release_slot)         (usb_hcd_r, int);
    bool    (*assign_address)       (usb_hcd_r, const usb_hub_t *, int, usb_speed_t, int, usb_ep_t *);
    bool    (*configure_hub_ep)     (usb_hcd_r, const usb_ep_t *, const usb_hub_t *);
    bool    (*configure_kbd_ep)     (usb_hcd_r, const usb_ep_t *, int);
    bool    (*setup_request)        (usb_hcd_r, const usb_ep_t *, const usb_setup_pkt_t *);
    bool    (*get_data_request)     (usb_hcd_r, const usb_ep_t *, const usb_setup_pkt_t *, const void *, size_t);
    void    (*poll_keyboards)       (usb_hcd_r);
} hcd_methods_t;

/**
 * A USB host controller driver workspace. This is extended by each HCI driver
 * to append its private data.
 */
typedef struct __attribute__((packed)) {
    uint8_t         data_buffer[HCD_DATA_BUFFER_SIZE];
    size_t          data_length;
    uint8_t         kc_buffer[HCD_KC_BUFFER_SIZE];
    int8_t          kc_index_i;
    int8_t          kc_index_o;
} hcd_workspace_t;

/**
 * A USB host controller driver object.
 */
typedef struct usb_hcd_s {
    const hcd_methods_t *methods;
    hcd_workspace_t     *ws;
} usb_hcd_t;

/**
 * A set of USB device initialisation options.
 */
typedef enum {
    USB_DEFAULT_INIT    = 0,
    USB_EXTRA_RESET     = 1 << 0,
    USB_IGNORE_EHCI     = 1 << 1,
    USB_2_STEP_INIT     = 1 << 2,
    USB_DEBUG           = 1 << 3
} usb_init_options_t;

/**
 * The selected USB device initialisation options.
 *
 * Used internally by the various HCI drivers.
 */
extern usb_init_options_t usb_init_options;

/**
 * Constructs a USB setup packet in buffer using the provided values.
 *
 * Used internally by the various HCI drivers.
 */
static inline void build_setup_packet(usb_setup_pkt_t *pkt, int type, int request, int value, int index, int length)
{
    pkt->type    = type;
    pkt->request = request;
    pkt->value   = value;
    pkt->index   = index;
    pkt->length  = length;
}

/* Returns the default maximum packet size for a USB device running at the
 * given speed.
 *
 * Used internally by the various HCI drivers.
 */
static inline int default_max_packet_size(usb_speed_t device_speed)
{
    switch (device_speed) {
      case USB_SPEED_LOW:
        return 8;
      case USB_SPEED_FULL:
        return 64;
      case USB_SPEED_HIGH:
        return 64;
      default:
        return 0;
    }
}

/**
 * Returns true if size is a valid value for the maximum packet size for a
 * USB device running at the given speed.
 *
 * Used internally by the various HCI drivers.
 */
static inline bool valid_usb_max_packet_size(int size, usb_speed_t speed)
{
    return (size == 8) || ((speed != USB_SPEED_LOW) && (size == 16 || size == 32 || size == 64));
}

/**
 * Returns true if buffer appears to contain a valid USB device descriptor.
 *
 * Used internally by the various HCI drivers.
 */
static inline bool valid_usb_device_descriptor(const uint8_t *buffer)
{
    usb_desc_header_t *desc = (usb_desc_header_t *)buffer;

    return desc->length == sizeof(usb_device_desc_t) && desc->type == USB_DESC_DEVICE;
}

/**
 * Returns true if buffer appears to contain a valid USB configuration
 * descriptor.
 *
 * Used internally by the various HCI drivers.
 */
static inline bool valid_usb_config_descriptor(const uint8_t *buffer)
{
    usb_desc_header_t *desc = (usb_desc_header_t *)buffer;

    return desc->length == sizeof(usb_config_desc_t) && desc->type == USB_DESC_CONFIGURATION;
}

/**
 * Returns the USB route to the device attached to the hub port specified by
 * hub and port_num. The top 8 bits of the returned value contain the root
 * port number and the bottom 20 bits contain the USB3 route string.
 *
 * Used internally by the various HCI drivers.
 */
uint32_t usb_route(const usb_hub_t *hub, int port_num);

/**
 * Returns the high-speed parent device ID and port number (as defined by
 * the EHCI and XHCI specifications) for the device attached to the hub
 * port specified by hub and port_num. Returns zero values if the device
 * is operating at high speed (as specified by device_speed) or is directly
 * attached to a root hub port.
 *
 * Used internally by the various HCI drivers.
 */
usb_parent_t usb_hs_parent(const usb_hub_t *hub, int port_num, usb_speed_t device_speed);

/**
 * Waits for all the bits set in bit_mask to be cleared in the register pointed
 * to by reg or for max_time microseconds to elapse.
 *
 * Used internally by the various HCI drivers.
 */
bool wait_until_clr(const volatile uint32_t *reg, uint32_t bit_mask, int max_time);

/**
 * Waits for all the bits set in bit_mask to also be set in the register pointed
 * to by reg or for max_time microseconds to elapse.
 *
 * Used internally by the various HCI drivers.
 */
bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_time);

/**
 * Displays an informational message, scrolling the screen if necessary.
 * Takes the same arguments as the printf function.
 *
 * Used internally by the various HCI drivers.
 */
void print_usb_info(const char *fmt, ...);

/**
 * Resets the specified USB hub port.
 *
 * Used internally by the various HCI drivers.
 */
bool reset_usb_hub_port(const usb_hcd_t *hcd, const usb_hub_t *hub, int port_num);

/**
 * Sets the device address for the device attached to the specified hub port
 * (thus moving the device to Address state), fills in the descriptor for the
 * device's default control endpoint (ep0), and leaves the device descriptor
 * in the driver's data transfer buffer. Returns true if all actions are
 * successfully completed.
 *
 * This is the default implementation of the HCD assign_address method.
 */
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);

/**
 * Scans the specified USB device to detect whether it has any HID keyboards
 * attached to it (directly or indirectly). If so, the keyboard device(s)
 * are initialised and configured, as are any intermediate USB hubs, and the
 * table defined by keyboards and max_keyboards is updated accordingly and
 * num_keyboards is updated to match. Returns true if any keyboards were found
 * and added to the table.
 *
 * Used internally by the various HCI drivers.
 */
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);

/**
 * Scans the latest keyboard report from a HID keyboard for key presses that
 * weren't present in the previous report from that keyboard. Appends the HID
 * key code for each new key press to the driver's key code buffer. Returns
 * false if the report signals the phantom condition, otherwise returns true.
 *
 * Used internally by the various HCI drivers.
 */
bool process_usb_keyboard_report(const usb_hcd_t *hcd, const hid_kbd_rpt_t *report, const hid_kbd_rpt_t *prev_report);

/**
 * Scans the attached USB devices and initialises all HID keyboard devices
 * it finds (subject to implementation limits on the number of devices).
 * Records the information needed to subsequently poll those devices for
 * key presses.
 *
 * Used internally by keyboard.c.
 */
void find_usb_keyboards(bool pause_if_none);

/**
 * Polls the keyboards discovered by find_usb_keyboards. Consumes and returns
 * the HID key code for the first key press it detects. Returns zero if no key
 * has been pressed.
 *
 * Used internally by keyboard.c.
 */
uint8_t get_usb_keycode(void);

#endif // USBHCD_H
