Skip to content

File syn_usb_host.c

File List > drivers > syn_usb_host.c

Go to the documentation of this file

#include "syntropic/drivers/syn_usb_host.h"

#include "port/syn_port_usb.h"
#include "port/syn_port_usb_host.h"

#include <string.h>

/* ── Internal helpers ──────────────────────────────────────────────────── */

static void build_setup(SYN_USB_SetupPacket *pkt, uint8_t bmRequestType, uint8_t bRequest,
                        uint16_t wValue, uint16_t wIndex, uint16_t wLength)
{
    pkt->bmRequestType = bmRequestType;
    pkt->bRequest = bRequest;
    pkt->wValue = wValue;
    pkt->wIndex = wIndex;
    pkt->wLength = wLength;
}

static void probe_class_drivers(SYN_USB_Host *host)
{
    const uint8_t *buf = host->enum_buf;
    uint16_t total = host->enum_buf_len;
    uint16_t pos = 0;

    while (pos + 2U <= total) {
        uint8_t bLength = buf[pos];
        uint8_t bDescType = buf[pos + 1U];

        if (bLength < 2U || (pos + bLength) > total) {
            break;
        }

        /* Interface Descriptor (bDescriptorType == 0x04, bLength >= 9) */
        if (bDescType == SYN_USB_DESC_TYPE_INTERFACE && bLength >= 9U) {
            uint8_t iface_class = buf[pos + 5U];
            uint8_t iface_sub = buf[pos + 6U];
            uint8_t iface_proto = buf[pos + 7U];

            /* Calculate length of this interface + subordinate descriptors */
            uint16_t iface_start = pos;
            uint16_t next = pos + bLength;
            while (next + 2U <= total) {
                uint8_t nLen = buf[next];
                uint8_t nType = buf[next + 1U];
                if (nLen < 2U || (next + nLen) > total || nType == SYN_USB_DESC_TYPE_INTERFACE) {
                    break;
                }
                next += nLen;
            }
            uint16_t iface_total_len = next - iface_start;

            /* Match against registered class drivers */
            for (uint8_t i = 0; i < host->class_count; i++) {
                SYN_USB_HostClassDriver *cls = &host->classes[i];
                if (cls->matched) {
                    continue;
                }
                bool class_match = (cls->class_code == 0xFFU) || (cls->class_code == iface_class);
                bool sub_match = (cls->subclass_code == 0xFFU) || (cls->subclass_code == iface_sub);
                bool proto_match =
                    (cls->protocol_code == 0xFFU) || (cls->protocol_code == iface_proto);

                if (class_match && sub_match && proto_match && cls->probe) {
                    if (cls->probe(cls->ctx, host->dev_info.dev_addr, &buf[iface_start],
                                   iface_total_len) == SYN_OK) {
                        cls->matched = true;
                        break; /* One driver per interface */
                    }
                }
            }
        }

        pos += bLength;
    }
}

/* ── Enumeration state machine ─────────────────────────────────────────── */

static SYN_Status enumerate_step(SYN_USB_Host *host)
{
    SYN_USB_SetupPacket pkt;

    /* If a transfer is pending, poll for completion */
    if (host->xfer_pending) {
        if (!syn_port_usb_host_xfer_done(0U)) {
            return SYN_BUSY;
        }
        host->xfer_pending = false;

        uint16_t actual = 0;
        SYN_Status xfer_st = syn_port_usb_host_xfer_result(0U, &actual);
        if (xfer_st != SYN_OK) {
            return SYN_ERROR;
        }
        host->enum_buf_len = actual;
    }

    switch (host->enum_step) {
    case SYN_USB_HOST_ENUM_GET_DEV8:
        /* Open pipe 0 to address 0, max_pkt 8 (minimum guaranteed) */
        syn_port_usb_host_pipe_open(0U, 0U, 0x00U, SYN_USB_EP_TYPE_CONTROL, 8U);
        /* GET_DESCRIPTOR(Device, 8 bytes) */
        build_setup(&pkt, 0x80U, SYN_USB_REQ_GET_DESCRIPTOR,
                    (uint16_t)(SYN_USB_DESC_TYPE_DEVICE << 8), 0U, 8U);
        syn_port_usb_host_submit_setup(0U, &pkt);
        syn_port_usb_host_submit_data(0U, host->enum_buf, 8U, true);
        host->xfer_pending = true;
        host->enum_step = SYN_USB_HOST_ENUM_SET_ADDR;
        return SYN_BUSY;

    case SYN_USB_HOST_ENUM_SET_ADDR: {
        /* Parse bMaxPacketSize0 from byte 7 of device descriptor */
        uint8_t max_pkt0 = host->enum_buf[7];
        if (max_pkt0 == 0U) {
            max_pkt0 = 8U;
        }
        host->dev_info.max_pkt_ep0 = max_pkt0;

        /* Re-open pipe 0 with actual max packet size */
        syn_port_usb_host_pipe_close(0U);
        syn_port_usb_host_pipe_open(0U, 0U, 0x00U, SYN_USB_EP_TYPE_CONTROL, max_pkt0);

        /* SET_ADDRESS */
        uint8_t addr = host->next_addr;
        if (addr > 127U) {
            addr = 1U;
        }
        host->next_addr = addr + 1U;
        host->dev_info.dev_addr = addr;

        build_setup(&pkt, 0x00U, SYN_USB_REQ_SET_ADDRESS, addr, 0U, 0U);
        syn_port_usb_host_submit_setup(0U, &pkt);
        host->xfer_pending = true;
        host->enum_step = SYN_USB_HOST_ENUM_GET_DEV_FULL;
        return SYN_BUSY;
    }

    case SYN_USB_HOST_ENUM_GET_DEV_FULL:
        /* Re-open pipe 0 to new address */
        syn_port_usb_host_pipe_close(0U);
        syn_port_usb_host_pipe_open(0U, host->dev_info.dev_addr, 0x00U, SYN_USB_EP_TYPE_CONTROL,
                                    host->dev_info.max_pkt_ep0);

        /* GET_DESCRIPTOR(Device, 18 bytes) */
        build_setup(&pkt, 0x80U, SYN_USB_REQ_GET_DESCRIPTOR,
                    (uint16_t)(SYN_USB_DESC_TYPE_DEVICE << 8), 0U, 18U);
        syn_port_usb_host_submit_setup(0U, &pkt);
        syn_port_usb_host_submit_data(0U, host->enum_buf, 18U, true);
        host->xfer_pending = true;
        host->enum_step = SYN_USB_HOST_ENUM_GET_CFG;
        return SYN_BUSY;

    case SYN_USB_HOST_ENUM_GET_CFG: {
        /* Parse full device descriptor */
        const uint8_t *d = host->enum_buf;
        host->dev_info.vid = (uint16_t)((uint16_t)d[9] << 8) | d[8];
        host->dev_info.pid = (uint16_t)((uint16_t)d[11] << 8) | d[10];
        host->dev_info.dev_class = d[4];
        host->dev_info.dev_subclass = d[5];
        host->dev_info.dev_protocol = d[6];
        host->dev_info.num_configs = d[17];

        /* GET_DESCRIPTOR(Configuration 0, up to enum_buf size) */
        build_setup(&pkt, 0x80U, SYN_USB_REQ_GET_DESCRIPTOR,
                    (uint16_t)(SYN_USB_DESC_TYPE_CONFIGURATION << 8), 0U,
                    SYN_USB_HOST_ENUM_BUF_SIZE);
        syn_port_usb_host_submit_setup(0U, &pkt);
        syn_port_usb_host_submit_data(0U, host->enum_buf, SYN_USB_HOST_ENUM_BUF_SIZE, true);
        host->xfer_pending = true;
        host->enum_step = SYN_USB_HOST_ENUM_SET_CFG;
        return SYN_BUSY;
    }

    case SYN_USB_HOST_ENUM_SET_CFG: {
        /* Config descriptor received. Parse bConfigurationValue (byte 5) */
        uint8_t cfg_val = 1U;
        if (host->enum_buf_len >= 6U) {
            cfg_val = host->enum_buf[5];
        }

        /* SET_CONFIGURATION */
        build_setup(&pkt, 0x00U, SYN_USB_REQ_SET_CONFIGURATION, cfg_val, 0U, 0U);
        syn_port_usb_host_submit_setup(0U, &pkt);
        host->xfer_pending = true;
        host->enum_step = SYN_USB_HOST_ENUM_CLASS_PROBE;
        return SYN_BUSY;
    }

    case SYN_USB_HOST_ENUM_CLASS_PROBE:
        /* Probe class drivers against interface descriptors */
        probe_class_drivers(host);
        host->enum_step = SYN_USB_HOST_ENUM_DONE;
        return SYN_OK;

    case SYN_USB_HOST_ENUM_DONE:
        return SYN_OK;

    default:
        return SYN_ERROR;
    }
}

/* ── Public API ────────────────────────────────────────────────────────── */

SYN_Status syn_usb_host_init(SYN_USB_Host *host)
{
    if (!host) {
        return SYN_INVALID_PARAM;
    }
    memset(host, 0, sizeof(*host));
    host->state = SYN_USB_HOST_STATE_DISCONNECTED;
    host->next_addr = 1U;
    return SYN_OK;
}

SYN_Status syn_usb_host_register_class(SYN_USB_Host *host, const SYN_USB_HostClassDriver *cls)
{
    if (!host || !cls) {
        return SYN_INVALID_PARAM;
    }
    if (host->class_count >= SYN_USB_HOST_MAX_CLASSES) {
        return SYN_ERROR;
    }
    host->classes[host->class_count] = *cls;
    host->classes[host->class_count].matched = false;
    host->class_count++;
    return SYN_OK;
}

SYN_Status syn_usb_host_process(SYN_USB_Host *host)
{
    if (!host) {
        return SYN_INVALID_PARAM;
    }

    switch (host->state) {
    case SYN_USB_HOST_STATE_DISCONNECTED:
        if (syn_port_usb_host_device_attached()) {
            syn_port_usb_host_vbus(true);
            host->state = SYN_USB_HOST_STATE_ATTACHED;
        }
        return SYN_OK;

    case SYN_USB_HOST_STATE_ATTACHED:
        syn_port_usb_host_vbus(true);
        syn_port_usb_host_bus_reset();
        host->enum_step = SYN_USB_HOST_ENUM_GET_DEV8;
        host->xfer_pending = false;
        host->enum_buf_len = 0;
        memset(&host->dev_info, 0, sizeof(host->dev_info));
        /* Clear matched flags */
        for (uint8_t i = 0; i < host->class_count; i++) {
            host->classes[i].matched = false;
        }
        host->state = SYN_USB_HOST_STATE_ENUMERATING;
        return SYN_OK;

    case SYN_USB_HOST_STATE_ENUMERATING: {
        SYN_Status st = enumerate_step(host);
        if (st == SYN_OK) {
            host->state = SYN_USB_HOST_STATE_READY;
        } else if (st == SYN_ERROR) {
            host->state = SYN_USB_HOST_STATE_ERROR;
        }
        return st;
    }

    case SYN_USB_HOST_STATE_READY:
        /* Check for detach */
        if (!syn_port_usb_host_device_attached()) {
            for (uint8_t i = 0; i < host->class_count; i++) {
                if (host->classes[i].matched && host->classes[i].disconnected) {
                    host->classes[i].disconnected(host->classes[i].ctx);
                }
                host->classes[i].matched = false;
            }
            syn_port_usb_host_pipe_close(0U);
            syn_port_usb_host_vbus(false);
            host->state = SYN_USB_HOST_STATE_DISCONNECTED;
            return SYN_OK;
        }
        /* Tick class drivers */
        for (uint8_t i = 0; i < host->class_count; i++) {
            if (host->classes[i].matched && host->classes[i].process) {
                host->classes[i].process(host->classes[i].ctx);
            }
        }
        return SYN_OK;

    case SYN_USB_HOST_STATE_ERROR:
        /* On error, check detach to allow re-enumeration */
        if (!syn_port_usb_host_device_attached()) {
            syn_port_usb_host_vbus(false);
            host->state = SYN_USB_HOST_STATE_DISCONNECTED;
        }
        return SYN_ERROR;

    default:
        return SYN_ERROR;
    }
}

bool syn_usb_host_is_ready(const SYN_USB_Host *host)
{
    return (host != NULL) && (host->state == SYN_USB_HOST_STATE_READY);
}

const SYN_USB_HostDevInfo *syn_usb_host_get_dev_info(const SYN_USB_Host *host)
{
    if (!host || host->state < SYN_USB_HOST_STATE_READY) {
        return NULL;
    }
    return &host->dev_info;
}