mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 23:38:18 +00:00
Single ZephyrUSBCDC module owns the usbd context, 1200-baud DFU detection, and DTR transitions for both roles. The boot banner now blocks on a k_event signalled by the usbd_msg_callback when DTR transitions high — host attached → wakes immediately; no host → bounded timeout (2 s repeater, 1 s companion). Replaces the fixed k_sleep delays in both mains. Deletes the companion's 10 s DTR-polling work — line state changes arrive as events now, same callback handles disconnect (resets V3 parser, flips active_iface) and DFU touch (reboots to bootloader). Side effect: prod companion no longer enumerates a phantom CDC ACM port (CONFIG_LOG=n skips the whole stack instead of auto-initing an unused device).
256 lines
6.9 KiB
C++
256 lines
6.9 KiB
C++
/*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
* ZephCore — Unified USB CDC ACM init (companion + repeater + observer)
|
|
*
|
|
* See ZephyrUSBCDC.h for the contract. This implementation:
|
|
* - Defines a single USBD context + CDC ACM class
|
|
* - Registers usbd_msg_callback so the host's SET_CONTROL_LINE_STATE and
|
|
* SET_LINE_CODING requests reach us as events (no DTR polling)
|
|
* - Emits a k_event when DTR transitions high — the boot path waits on it
|
|
* instead of a fixed sleep
|
|
* - Fires a user callback on every DTR transition (companion: reset RX +
|
|
* flip active_iface on drop)
|
|
* - Handles the Arduino-style 1200-baud touch → reboot-to-bootloader flow
|
|
* for nRF52 boards
|
|
*/
|
|
|
|
#include "ZephyrUSBCDC.h"
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/usb/usbd.h>
|
|
#include <zephyr/drivers/uart.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(zephcore_usbd, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
|
|
|
|
#include <adapters/board/ZephyrBoard.h>
|
|
|
|
/* ===== Per-role descriptor strings ====== */
|
|
#define ZEPHCORE_USB_VID 0x2fe3 /* Zephyr Project VID */
|
|
|
|
#if defined(ZEPHCORE_REPEATER)
|
|
#define ZEPHCORE_USB_PID 0x0004
|
|
#define ZEPHCORE_USB_PRODUCT "ZephCore Repeater"
|
|
#elif defined(ZEPHCORE_OBSERVER)
|
|
#define ZEPHCORE_USB_PID 0x0005
|
|
#define ZEPHCORE_USB_PRODUCT "ZephCore Observer"
|
|
#else
|
|
#define ZEPHCORE_USB_PID 0x0006
|
|
#define ZEPHCORE_USB_PRODUCT "ZephCore Companion"
|
|
#endif
|
|
|
|
#define ZEPHCORE_USB_MAX_POWER 125 /* 250 mA in 2 mA units */
|
|
|
|
/* ===== USBD device + descriptors ===== */
|
|
USBD_DEVICE_DEFINE(zephcore_usbd,
|
|
DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
|
|
ZEPHCORE_USB_VID, ZEPHCORE_USB_PID);
|
|
|
|
USBD_DESC_LANG_DEFINE(zephcore_lang);
|
|
USBD_DESC_MANUFACTURER_DEFINE(zephcore_mfr, "ZephCore");
|
|
USBD_DESC_PRODUCT_DEFINE(zephcore_product, ZEPHCORE_USB_PRODUCT);
|
|
USBD_DESC_SERIAL_NUMBER_DEFINE(zephcore_sn);
|
|
|
|
USBD_DESC_CONFIG_DEFINE(zephcore_fs_cfg_desc, "FS Configuration");
|
|
USBD_DESC_CONFIG_DEFINE(zephcore_hs_cfg_desc, "HS Configuration");
|
|
|
|
USBD_CONFIGURATION_DEFINE(zephcore_fs_config,
|
|
0, ZEPHCORE_USB_MAX_POWER, &zephcore_fs_cfg_desc);
|
|
USBD_CONFIGURATION_DEFINE(zephcore_hs_config,
|
|
0, ZEPHCORE_USB_MAX_POWER, &zephcore_hs_cfg_desc);
|
|
|
|
/* ===== Module state ===== */
|
|
#define USB_CDC_DTR_BIT BIT(0)
|
|
static K_EVENT_DEFINE(usb_cdc_events);
|
|
|
|
static bool s_dtr_active;
|
|
static bool s_initialized;
|
|
static zephcore_usbd_cdc_dtr_cb_t s_dtr_cb;
|
|
|
|
/* ZephyrBoard for bootloader-magic write before reset. The class is stateless
|
|
* (only static methods over GPREGRET + sys_reboot), so a local instance here
|
|
* is fine even when other roles also have their own instance. */
|
|
static mesh::ZephyrBoard usb_board;
|
|
|
|
static void enter_bootloader(void)
|
|
{
|
|
LOG_WRN("Entering bootloader (1200 baud touch)");
|
|
/* Let USB disconnect cleanly before the reset */
|
|
k_msleep(100);
|
|
usb_board.rebootToBootloader();
|
|
CODE_UNREACHABLE;
|
|
}
|
|
|
|
/* ===== USBD message callback ===== */
|
|
static void usbd_msg_callback(struct usbd_context *const ctx,
|
|
const struct usbd_msg *msg)
|
|
{
|
|
ARG_UNUSED(ctx);
|
|
|
|
if (msg->type == USBD_MSG_CDC_ACM_LINE_CODING) {
|
|
uint32_t baudrate = 0;
|
|
if (uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE,
|
|
&baudrate) == 0) {
|
|
LOG_DBG("CDC ACM baud rate: %u", baudrate);
|
|
if (baudrate == 1200) {
|
|
/* Host explicitly set 1200 baud — DFU intent.
|
|
* Adafruit nrfutil --touch 1200 closes the
|
|
* port immediately after; do not wait for
|
|
* the DTR drop to fire. */
|
|
enter_bootloader();
|
|
}
|
|
}
|
|
} else if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) {
|
|
uint32_t dtr = 0;
|
|
uint32_t baudrate = 0;
|
|
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr);
|
|
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE,
|
|
&baudrate);
|
|
LOG_DBG("CDC ACM DTR=%u baud=%u", dtr, baudrate);
|
|
|
|
const bool dtr_now = (dtr != 0);
|
|
|
|
/* Arduino-style DFU: DTR drop while baud is 1200. Some host
|
|
* tools change baud then close (handled by LINE_CODING above),
|
|
* others just toggle DTR with baud still at 1200 — catch both. */
|
|
if (s_dtr_active && !dtr_now && baudrate == 1200) {
|
|
enter_bootloader();
|
|
}
|
|
|
|
s_dtr_active = dtr_now;
|
|
|
|
if (dtr_now) {
|
|
/* Wake the boot-time waiter; subsequent transitions
|
|
* leave the bit set, which is what callers expect
|
|
* (a "have we ever seen DTR?" gate). */
|
|
k_event_set(&usb_cdc_events, USB_CDC_DTR_BIT);
|
|
}
|
|
|
|
if (s_dtr_cb) {
|
|
s_dtr_cb(dtr_now);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ===== Setup helpers ===== */
|
|
static int register_cdc_acm(struct usbd_context *const uds_ctx,
|
|
const enum usbd_speed speed)
|
|
{
|
|
struct usbd_config_node *cfg_nd;
|
|
int err;
|
|
|
|
if (speed == USBD_SPEED_HS) {
|
|
cfg_nd = &zephcore_hs_config;
|
|
} else {
|
|
cfg_nd = &zephcore_fs_config;
|
|
}
|
|
|
|
err = usbd_add_configuration(uds_ctx, speed, cfg_nd);
|
|
if (err) {
|
|
LOG_ERR("Failed to add configuration");
|
|
return err;
|
|
}
|
|
|
|
err = usbd_register_class(&zephcore_usbd, "cdc_acm_0", speed, 1);
|
|
if (err) {
|
|
LOG_ERR("Failed to register CDC ACM class");
|
|
return err;
|
|
}
|
|
|
|
return usbd_device_set_code_triple(uds_ctx, speed,
|
|
USB_BCC_MISCELLANEOUS, 0x02, 0x01);
|
|
}
|
|
|
|
/* ===== Public API ===== */
|
|
extern "C" int zephcore_usbd_init(void)
|
|
{
|
|
if (s_initialized) {
|
|
return 0;
|
|
}
|
|
|
|
int err;
|
|
|
|
LOG_INF("Initializing ZephCore USB CDC ACM (" ZEPHCORE_USB_PRODUCT ")");
|
|
|
|
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_lang);
|
|
if (err) {
|
|
LOG_ERR("usbd_add_descriptor(lang) failed: %d", err);
|
|
return err;
|
|
}
|
|
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_mfr);
|
|
if (err) {
|
|
LOG_ERR("usbd_add_descriptor(mfr) failed: %d", err);
|
|
return err;
|
|
}
|
|
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_product);
|
|
if (err) {
|
|
LOG_ERR("usbd_add_descriptor(product) failed: %d", err);
|
|
return err;
|
|
}
|
|
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_sn);
|
|
if (err) {
|
|
LOG_ERR("usbd_add_descriptor(sn) failed: %d", err);
|
|
return err;
|
|
}
|
|
|
|
if (USBD_SUPPORTS_HIGH_SPEED &&
|
|
usbd_caps_speed(&zephcore_usbd) == USBD_SPEED_HS) {
|
|
err = register_cdc_acm(&zephcore_usbd, USBD_SPEED_HS);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = register_cdc_acm(&zephcore_usbd, USBD_SPEED_FS);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
err = usbd_msg_register_cb(&zephcore_usbd, usbd_msg_callback);
|
|
if (err) {
|
|
LOG_ERR("usbd_msg_register_cb failed: %d", err);
|
|
return err;
|
|
}
|
|
|
|
err = usbd_init(&zephcore_usbd);
|
|
if (err) {
|
|
LOG_ERR("usbd_init failed: %d", err);
|
|
return err;
|
|
}
|
|
|
|
err = usbd_enable(&zephcore_usbd);
|
|
if (err) {
|
|
LOG_ERR("usbd_enable failed: %d", err);
|
|
return err;
|
|
}
|
|
|
|
s_initialized = true;
|
|
LOG_INF("ZephCore USB CDC initialized");
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int zephcore_usbd_wait_dtr(uint32_t timeout_ms)
|
|
{
|
|
if (!s_initialized) {
|
|
return -ENODEV;
|
|
}
|
|
uint32_t got = k_event_wait(&usb_cdc_events, USB_CDC_DTR_BIT,
|
|
false, K_MSEC(timeout_ms));
|
|
return got ? 0 : -EAGAIN;
|
|
}
|
|
|
|
extern "C" bool zephcore_usbd_is_dtr_active(void)
|
|
{
|
|
return s_dtr_active;
|
|
}
|
|
|
|
extern "C" void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb)
|
|
{
|
|
s_dtr_cb = cb;
|
|
}
|