Skip to content

File syn_usb_host_cdc.c

File List > drivers > syn_usb_host_cdc.c

Go to the documentation of this file

#include "syntropic/drivers/syn_usb_host_cdc.h"

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

#include <string.h>

static SYN_Status host_cdc_probe(void *ctx, uint8_t dev_addr, const uint8_t *iface_desc,
                                 uint16_t len)
{
    SYN_USB_HostCDC *hcdc = (SYN_USB_HostCDC *)ctx;
    if (!hcdc || !iface_desc || len < 9U) {
        return SYN_INVALID_PARAM;
    }

    hcdc->dev_addr = dev_addr;

    /* Parse endpoints from subordinate descriptors */
    uint16_t pos = 0;
    while (pos + 2U <= len) {
        uint8_t bLen = iface_desc[pos];
        uint8_t bType = iface_desc[pos + 1U];
        if (bLen < 2U || (pos + bLen) > len) {
            break;
        }

        if (bType == SYN_USB_DESC_TYPE_ENDPOINT && bLen >= 7U) {
            uint8_t ep_addr = iface_desc[pos + 2U];
            uint8_t ep_attr = iface_desc[pos + 3U];
            uint16_t max_pkt =
                (uint16_t)iface_desc[pos + 4U] | ((uint16_t)iface_desc[pos + 5U] << 8);

            /* Bulk Endpoint */
            if ((ep_attr & 0x03U) == SYN_USB_EP_TYPE_BULK) {
                if ((ep_addr & 0x80U) != 0U) { /* Bulk IN */
                    hcdc->ep_bulk_in = ep_addr;
                    hcdc->pipe_bulk_in = 1U;
                    syn_port_usb_host_pipe_open(hcdc->pipe_bulk_in, dev_addr, ep_addr,
                                                SYN_USB_EP_TYPE_BULK, max_pkt);
                } else { /* Bulk OUT */
                    hcdc->ep_bulk_out = ep_addr;
                    hcdc->pipe_bulk_out = 2U;
                    syn_port_usb_host_pipe_open(hcdc->pipe_bulk_out, dev_addr, ep_addr,
                                                SYN_USB_EP_TYPE_BULK, max_pkt);
                }
            }
        }
        pos += bLen;
    }

    hcdc->connected = true;
    return SYN_OK;
}

static void host_cdc_disconnected(void *ctx)
{
    SYN_USB_HostCDC *hcdc = (SYN_USB_HostCDC *)ctx;
    if (hcdc) {
        if (hcdc->pipe_bulk_in != 0U) {
            syn_port_usb_host_pipe_close(hcdc->pipe_bulk_in);
        }
        if (hcdc->pipe_bulk_out != 0U) {
            syn_port_usb_host_pipe_close(hcdc->pipe_bulk_out);
        }
        hcdc->connected = false;
        hcdc->rx_len = 0;
        hcdc->tx_len = 0;
    }
}

static void host_cdc_process(void *ctx)
{
    SYN_USB_HostCDC *hcdc = (SYN_USB_HostCDC *)ctx;
    if (!hcdc || !hcdc->connected) {
        return;
    }

    /* Process pending TX transfers if any */
    if (hcdc->tx_len > 0U && hcdc->pipe_bulk_out != 0U) {
        if (!syn_port_usb_host_xfer_done(hcdc->pipe_bulk_out)) {
            /* Transfer ongoing */
        } else {
            /* Submit TX data */
            syn_port_usb_host_submit_data(hcdc->pipe_bulk_out, hcdc->tx_buf, hcdc->tx_len, false);
            hcdc->tx_len = 0;
        }
    }

    /* Process RX transfers if buffer has space */
    if (hcdc->rx_len == 0U && hcdc->pipe_bulk_in != 0U) {
        if (syn_port_usb_host_xfer_done(hcdc->pipe_bulk_in)) {
            uint16_t read_bytes = 0;
            if (syn_port_usb_host_xfer_result(hcdc->pipe_bulk_in, &read_bytes) == SYN_OK &&
                read_bytes > 0U) {
                hcdc->rx_len = (read_bytes < sizeof(hcdc->rx_buf)) ? read_bytes
                                                                   : (uint16_t)sizeof(hcdc->rx_buf);
            } else {
                /* Trigger non-blocking IN submit */
                syn_port_usb_host_submit_data(hcdc->pipe_bulk_in, hcdc->rx_buf,
                                              sizeof(hcdc->rx_buf), true);
            }
        }
    }
}

SYN_Status syn_usb_host_cdc_init(SYN_USB_HostCDC *hcdc)
{
    if (!hcdc) {
        return SYN_INVALID_PARAM;
    }
    memset(hcdc, 0, sizeof(*hcdc));
    hcdc->line_coding.baudrate = 115200;
    hcdc->line_coding.stop_bits = 0;
    hcdc->line_coding.parity = 0;
    hcdc->line_coding.data_bits = 8;
    return SYN_OK;
}

SYN_Status syn_usb_host_cdc_register(SYN_USB_Host *host, SYN_USB_HostCDC *hcdc)
{
    if (!host || !hcdc) {
        return SYN_INVALID_PARAM;
    }

    SYN_USB_HostClassDriver cls;
    memset(&cls, 0, sizeof(cls));
    cls.class_code = 0x02U;    /* CDC Communication Class (or Data class 0x0A) */
    cls.subclass_code = 0xFFU; /* Any subclass */
    cls.protocol_code = 0xFFU; /* Any protocol */
    cls.ctx = hcdc;
    cls.probe = host_cdc_probe;
    cls.disconnected = host_cdc_disconnected;
    cls.process = host_cdc_process;

    return syn_usb_host_register_class(host, &cls);
}

SYN_Status syn_usb_host_cdc_write(SYN_USB_HostCDC *hcdc, const void *data, size_t len)
{
    if (!hcdc || !data || len == 0U) {
        return SYN_INVALID_PARAM;
    }

    if (len > sizeof(hcdc->tx_buf)) {
        len = sizeof(hcdc->tx_buf);
    }

    memcpy(hcdc->tx_buf, data, len);
    hcdc->tx_len = (uint16_t)len;

    return SYN_OK;
}

SYN_Status syn_usb_host_cdc_read(SYN_USB_HostCDC *hcdc, void *buf, size_t max_len, size_t *out_len)
{
    if (!hcdc || !buf || !out_len) {
        return SYN_INVALID_PARAM;
    }

    uint16_t available = hcdc->rx_len;
    if (available == 0U) {
        *out_len = 0U;
        return SYN_OK;
    }

    if (max_len < available) {
        available = (uint16_t)max_len;
    }

    memcpy(buf, hcdc->rx_buf, available);
    *out_len = available;

    if (available < hcdc->rx_len) {
        memmove(hcdc->rx_buf, &hcdc->rx_buf[available], hcdc->rx_len - available);
        hcdc->rx_len -= available;
    } else {
        hcdc->rx_len = 0U;
    }

    return SYN_OK;
}

bool syn_usb_host_cdc_rx_available(const SYN_USB_HostCDC *hcdc)
{
    return (hcdc != NULL) && (hcdc->rx_len > 0U);
}

bool syn_usb_host_cdc_tx_ready(const SYN_USB_HostCDC *hcdc)
{
    return (hcdc != NULL) && (hcdc->tx_len == 0U);
}