This commit is contained in:
liquidraver
2026-10-06 20:17:53 +02:00
parent 9bb7ccfe53
commit 42eea4f906
4 changed files with 113 additions and 22 deletions
+51 -10
View File
@@ -100,9 +100,16 @@ static const struct companion_link_cbs *s_link;
K_MSGQ_DEFINE(usb_recv_queue, sizeof(struct frame), USB_RECV_QUEUE_DEPTH, 4);
/* A session starts on the first traffic after open (a binary frame or a CLI
* line) and ends when the host drops DTR. */
* line) and ends when the host drops DTR or goes away without one (VBUS loss,
* bus reset). */
static bool usb_session_active;
/* The session can carry frames: it is open and the bus is not suspended (host
* asleep with the port open). Main hears each edge as a connect or a
* disconnect, so a suspended session is not a client: nothing is queued for
* it, and it does not hold up the last-client cleanup. */
static bool usb_link_up;
/* CLI text line callback — fired when a complete line arrives in text mode. */
static void (*s_cli_line_cb)(const char *line);
static char usb_text_line[USB_TEXT_LINE_MAX];
@@ -189,6 +196,31 @@ static void usb_uart_isr(const struct device *dev, void *user_data)
}
}
/* Recompute usb_link_up and tell main about an edge. Arduino shows "connected"
* for serial transports too, so a text session raises the edges as well. */
static void usb_link_update(void)
{
bool up = usb_session_active;
#if COMPANION_HAS_DTR
up = up && !zephcore_usbd_is_suspended();
#endif
if (up == usb_link_up) {
return;
}
usb_link_up = up;
if (!s_link) {
return;
}
if (up) {
if (s_link->on_connected) {
s_link->on_connected();
}
} else if (s_link->on_disconnected) {
s_link->on_disconnected();
}
}
/* Start a session on its first inbound traffic — a binary frame or a complete
* CLI line. The official client opens with CMD_DEVICE_QUERY (0x16), not
* CMD_APP_START, so any first traffic starts it. Other transports (BLE, WiFi)
@@ -202,10 +234,7 @@ static void usb_session_begin(uint8_t log_tag, bool is_text)
usb_session_is_text = is_text;
LOG_INF("usb_rx: first traffic 0x%02x, session started (%s)", log_tag,
is_text ? "text" : "binary");
/* Arduino shows "connected" for serial transports too */
if (s_link && s_link->on_connected) {
s_link->on_connected();
}
usb_link_update();
}
/* USB RX work - parses V3 frames from ring buffer */
@@ -347,7 +376,8 @@ static void usb_rx_work_fn(struct k_work *work)
#if COMPANION_HAS_DTR
/* DTR-transition callback from the shared ZephyrUSBCDC module.
* On drop: host closed the port → reset parser, hand control back to BLE.
* On drop (the host closed the port, or went away: VBUS loss, bus reset):
* reset the parser and end the session.
* CDC-only — a plain-UART backend has no DTR and never registers this. */
static void on_dtr_change(bool dtr_active)
{
@@ -355,7 +385,6 @@ static void on_dtr_change(bool dtr_active)
return;
}
LOG_INF("usb_dtr: DTR dropped, USB disconnected");
bool was_active = usb_session_active;
usb_session_active = false;
ring_buf_reset(&usb_ring_buf);
@@ -375,8 +404,19 @@ static void on_dtr_change(bool dtr_active)
/* The session is over: main runs its per-session cleanup (contact dump,
* sync, sign buffer) once no other transport is still connected. */
if (was_active && s_link && s_link->on_disconnected) {
s_link->on_disconnected();
usb_link_update();
}
/* Bus suspend, resume or reset, from the shared ZephyrUSBCDC module. */
static void on_bus_change(void)
{
bool was_up = usb_link_up;
usb_link_update();
if (usb_link_up && !was_up) {
/* Resumed. The class does not restart TX by itself: frames queued
* before the suspend wait for this kick. */
uart_irq_tx_enable(usb_dev);
}
}
#endif /* COMPANION_HAS_DTR */
@@ -455,7 +495,7 @@ size_t zephcore_usb_companion_recv(uint8_t *dest)
bool zephcore_usb_companion_is_connected(void)
{
return usb_session_active && !usb_session_is_text;
return usb_link_up && !usb_session_is_text;
}
/* Only a connected session is ever busy: MultiSerialInterface asks every
@@ -516,6 +556,7 @@ void zephcore_usb_companion_init(const struct companion_link_cbs *link)
* shared usbd_msg_callback — no polling work needed. Plain-UART
* backends have no DTR; their session reset is protocol-driven. */
zephcore_usbd_set_dtr_cb(on_dtr_change);
zephcore_usbd_set_bus_cb(on_bus_change);
#endif
} else {
LOG_WRN("Companion UART not ready");
+5 -3
View File
@@ -21,8 +21,9 @@ extern "C" {
/**
* Initialize the transport: ring buffers, UART ISR, DTR tracking.
* @param link frame waiting / TX drained / session start / session end
* (DTR drop). Raised from the ISR and sysworkq: post events only.
* @param link frame waiting / TX drained / session start (also a bus resume)
* / session end (DTR drop, VBUS loss, bus reset; also a bus
* suspend). Raised from the ISR and sysworkq: post events only.
*/
void zephcore_usb_companion_init(const struct companion_link_cbs *link);
@@ -36,7 +37,8 @@ size_t zephcore_usb_companion_write_frame(const uint8_t *src, size_t len);
/** Take the next received binary frame into dest (MAX_FRAME_SIZE). Returns its length, 0 if none. */
size_t zephcore_usb_companion_recv(uint8_t *dest);
/** A binary companion session is open (a text-CLI session is not a client). */
/** A binary companion session is open and the bus is not suspended (a
* text-CLI session is not a client). */
bool zephcore_usb_companion_is_connected(void);
/** The TX ring cannot take another full-size frame. */
+43 -7
View File
@@ -8,8 +8,9 @@
* 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)
* - Fires a user callback on every DTR transition (companion: end the
* session on drop), and reports VBUS loss and a bus reset as a drop
* - Fires a user callback on bus suspend, resume and reset
* - Handles the Arduino-style 1200-baud touch → reboot-to-bootloader flow
* for nRF52 boards
*/
@@ -71,6 +72,7 @@ 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;
static zephcore_usbd_cdc_bus_cb_t s_bus_cb;
/* ZephyrBoard for bootloader-magic write before reset. The class is stateless
* (only static methods over GPREGRET + sys_reboot), so a local instance here
@@ -86,6 +88,17 @@ static void enter_bootloader(void)
CODE_UNREACHABLE;
}
/* The host went away without a DTR=0: report it as a DTR drop. */
static void host_gone(void)
{
if (s_dtr_active) {
s_dtr_active = false;
if (s_dtr_cb) {
s_dtr_cb(false);
}
}
}
/* ===== USBD message callback ===== */
static void usbd_msg_callback(struct usbd_context *const ctx,
const struct usbd_msg *msg)
@@ -142,11 +155,24 @@ static void usbd_msg_callback(struct usbd_context *const ctx,
* connection until reboot). Treat VBUS loss as a DTR drop so the
* interface is handed back to BLE. */
LOG_INF("CDC ACM: VBUS removed (device unplug)");
if (s_dtr_active) {
s_dtr_active = false;
if (s_dtr_cb) {
s_dtr_cb(false);
}
host_gone();
} else if (msg->type == USBD_MSG_RESET) {
/* Bus reset. At enumeration no port is open and this does nothing.
* Later it means the host is enumerating us again (a hub upstream
* was reset, or the host lost the device): the port it had open is
* gone, and no DTR=0 says so, since the class keeps the line state
* of the old session. VBUS can stay up throughout, so treat it as
* a DTR drop too. A reset also ends a suspend, with no RESUME. */
LOG_INF("CDC ACM: bus reset");
host_gone();
if (s_bus_cb) {
s_bus_cb();
}
} else if (msg->type == USBD_MSG_SUSPEND || msg->type == USBD_MSG_RESUME) {
LOG_INF("CDC ACM: bus %s",
msg->type == USBD_MSG_SUSPEND ? "suspended" : "resumed");
if (s_bus_cb) {
s_bus_cb();
}
}
}
@@ -268,6 +294,16 @@ extern "C" void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb)
s_dtr_cb = cb;
}
extern "C" bool zephcore_usbd_is_suspended(void)
{
return s_initialized && usbd_is_suspended(&zephcore_usbd);
}
extern "C" void zephcore_usbd_set_bus_cb(zephcore_usbd_cdc_bus_cb_t cb)
{
s_bus_cb = cb;
}
extern "C" void zephcore_usbd_detach(void)
{
if (!s_initialized) {
+14 -2
View File
@@ -20,9 +20,14 @@ extern "C" {
#endif
/* Per-transition DTR callback. Fires on every host CONTROL_LINE_STATE change,
* value = current DTR level. NULL is allowed (cb cleared). */
* value = current DTR level, and with false when the host goes away without
* one (VBUS loss, bus reset). NULL is allowed (cb cleared). */
typedef void (*zephcore_usbd_cdc_dtr_cb_t)(bool dtr_active);
/* Bus state callback: the bus was suspended, resumed or reset. Read the state
* with zephcore_usbd_is_suspended(). NULL is allowed (cb cleared). */
typedef void (*zephcore_usbd_cdc_bus_cb_t)(void);
/* Initialize USB device + CDC ACM class and enumerate.
* Idempotent; subsequent calls are no-ops.
* Returns 0 on success or a negative errno. */
@@ -37,9 +42,16 @@ int zephcore_usbd_wait_dtr(uint32_t timeout_ms);
bool zephcore_usbd_is_dtr_active(void);
/* Register a callback fired on every DTR transition.
* Companion uses this to reset its RX state and flip active_iface on drop. */
* Companion uses this to end its session on a drop. */
void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb);
/* The host has suspended the bus (asleep with the port open): nothing we send
* leaves the device until it resumes. The USB stack's own state. */
bool zephcore_usbd_is_suspended(void);
/* Register a callback fired on a bus suspend, resume or reset. */
void zephcore_usbd_set_bus_cb(zephcore_usbd_cdc_bus_cb_t cb);
/* Detach from the USB bus (drop the D+ pull-up) so the host sees an unplug.
* Call before a reset: a soft reset leaves the PHY attached on some SoCs, and
* the host then keeps a stale endpoint open across the reboot. No-op if the