feat(factory): support USB CDC alongside UART CLI

This commit is contained in:
chenjintao
2026-10-27 15:04:14 +08:00
parent 6116a67d5f
commit 002967b21f
13 changed files with 583 additions and 69 deletions
+4
View File
@@ -77,6 +77,10 @@ set(platform_priv_requires
nvs_flash
)
if(CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE)
list(APPEND platform_priv_requires esp_driver_usb_serial_jtag)
endif()
if(CONFIG_TQ_NETWORK_DIAGNOSTICS_ENABLE)
list(APPEND platform_priv_requires lwip)
endif()
+9 -1
View File
@@ -243,7 +243,8 @@ esp_err_t platform_write_device_identity(const char *device_id, const char *seri
* @brief 工厂 CLI 使用的串口行传输原语。
* Console (UART) line-transport primitives for the factory CLI.
*
* 这些接口不保存命令状态。install 可重复调用;read 返回读取字节数,超时返回0,
* 这些接口不保存命令状态。install 可重复调用,安装锁最多等待100ms;至少一路可用即成功。
* 原有read/write_line操作UART0;read 返回读取字节数,超时返回0,
* 错误返回负数。零超时表示轮询一次,负超时无效。
* Stateless primitives over the console UART. `platform_console_install()` is
* idempotent (a pre-existing console driver is treated as success).
@@ -254,6 +255,13 @@ esp_err_t platform_console_install(void);
int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms);
void platform_console_write_line(const char *line);
/** 独立USB Serial/JTAG CDC端点:read语义同UART,禁用/不可用返回负数。
* write为尽力发送(共250ms入队预算,超时可能已发送部分),不切换或广播到UART。
* 两路输入必须由同一个CLI任务消费,并各自维护行缓冲。驱动常驻,不随CLI stop卸载。
*/
int platform_console_read_cdc_bytes(uint8_t *buf, size_t len, int timeout_ms);
void platform_console_write_cdc_line(const char *line);
/**
* @brief 基于 NVS 的通用持久化键值存储原语。
* Generic persistent key/value blob storage (NVS-backed).
+117 -17
View File
@@ -3,15 +3,24 @@
*
* SPDX-License-Identifier: Apache-2.0
*
* Console UART line-transport primitives for the factory CLI. Line
* UART and USB Serial/JTAG CDC line-transport primitives for the factory CLI. Line
* accumulation and the worker loop live in the control-plane CLI owner.
*/
#include "platform.h"
#include <string.h>
#include <stdio.h>
#include "driver/uart.h"
#include "sdkconfig.h"
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
#include "driver/usb_serial_jtag.h"
#if CONFIG_VFS_SUPPORT_IO
#include "driver/usb_serial_jtag_vfs.h"
#endif
#endif
#include "freertos/semphr.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -22,30 +31,67 @@
static const char *TAG = "platform_console";
/* UART0 驱动安装具有幂等性,已由其他启动路径安装也视为可用。 */
/* 两个驱动是平台常驻资源,CLI stop只停止消费者,不卸载共享控制台驱动。
* 初始化串行化且可重入;USB安装失败仍允许UART工作,下一次install会重试USB。 */
static StaticSemaphore_t s_install_lock_buffer;
static SemaphoreHandle_t s_install_lock;
static portMUX_TYPE s_install_lock_guard = portMUX_INITIALIZER_UNLOCKED;
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE && CONFIG_VFS_SUPPORT_IO
static bool s_usb_vfs_attached;
#endif
esp_err_t platform_console_install(void)
{
esp_err_t err = uart_driver_install(PLATFORM_CONSOLE_UART_PORT,
PLATFORM_CONSOLE_RX_BUFFER,
0,
0,
NULL,
0);
/* The console may have already installed the UART driver; treat that as success. */
if (err == ESP_ERR_INVALID_STATE) {
return ESP_OK;
portENTER_CRITICAL(&s_install_lock_guard);
if (s_install_lock == NULL) {
s_install_lock = xSemaphoreCreateMutexStatic(&s_install_lock_buffer);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to install UART%d driver: %s",
(int)PLATFORM_CONSOLE_UART_PORT, esp_err_to_name(err));
portEXIT_CRITICAL(&s_install_lock_guard);
if (s_install_lock == NULL ||
xSemaphoreTake(s_install_lock, pdMS_TO_TICKS(100)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
return err;
esp_err_t uart_err = ESP_OK;
if (!uart_is_driver_installed(PLATFORM_CONSOLE_UART_PORT)) {
uart_err = uart_driver_install(PLATFORM_CONSOLE_UART_PORT,
PLATFORM_CONSOLE_RX_BUFFER, 0, 0, NULL, 0);
}
if (uart_err != ESP_OK) {
ESP_LOGW(TAG, "Factory UART0 unavailable: %s", esp_err_to_name(uart_err));
}
bool ready = uart_err == ESP_OK;
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
esp_err_t usb_err = ESP_OK;
if (!usb_serial_jtag_is_driver_installed()) {
usb_serial_jtag_driver_config_t config = {
.rx_buffer_size = PLATFORM_CONSOLE_RX_BUFFER,
.tx_buffer_size = 1024,
};
usb_err = usb_serial_jtag_driver_install(&config);
}
if (usb_err != ESP_OK) {
ESP_LOGW(TAG, "Factory USB Serial/JTAG CDC unavailable: %s", esp_err_to_name(usb_err));
} else {
#if CONFIG_VFS_SUPPORT_IO
if (!s_usb_vfs_attached) {
/* 日志的USB第二输出也走同一驱动队列,避免VFS直接写FIFO与ISR竞争。 */
usb_serial_jtag_vfs_use_driver();
s_usb_vfs_attached = true;
}
#endif
ESP_LOGI(TAG, "Factory USB Serial/JTAG CDC ready (GPIO19 D-, GPIO20 D+)");
ready = true;
}
#endif
(void)xSemaphoreGive(s_install_lock);
return ready ? ESP_OK : uart_err;
}
int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms)
{
/* 0 表示单次轮询,负数是无效参数;正数转换为 FreeRTOS 有界等待。 */
if (buf == NULL || len == 0U || timeout_ms < 0) {
if (buf == NULL || len == 0U || timeout_ms < 0 ||
!uart_is_driver_installed(PLATFORM_CONSOLE_UART_PORT)) {
return -1;
}
TickType_t ticks = pdMS_TO_TICKS(timeout_ms);
@@ -55,9 +101,63 @@ int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms)
void platform_console_write_line(const char *line)
{
/* 工厂协议按行发送,统一补充 CRLF,避免调用方重复处理行结束符。 */
if (line == NULL) {
if (line == NULL || !uart_is_driver_installed(PLATFORM_CONSOLE_UART_PORT)) {
return;
}
uart_write_bytes(PLATFORM_CONSOLE_UART_PORT, line, strlen(line));
uart_write_bytes(PLATFORM_CONSOLE_UART_PORT, "\r\n", 2);
}
int platform_console_read_cdc_bytes(uint8_t *buf, size_t len, int timeout_ms)
{
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
if (buf == NULL || len == 0U || len > UINT32_MAX || timeout_ms < 0 ||
!usb_serial_jtag_is_driver_installed()) {
return -1;
}
return usb_serial_jtag_read_bytes(buf, (uint32_t)len, pdMS_TO_TICKS(timeout_ms));
#else
(void)buf;
(void)len;
(void)timeout_ms;
return -1;
#endif
}
void platform_console_write_cdc_line(const char *line)
{
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
if (line == NULL || !usb_serial_jtag_is_driver_installed()) {
return;
}
/* USB未打开/拔出时不能让CLI卡住。使用零等待的小块写入和一条应答共250ms预算;
* 超时停止入队,已经交给驱动的字节不可撤销,主机按完整JSON/OK判定成功。 */
TickType_t started = xTaskGetTickCount();
TickType_t budget = pdMS_TO_TICKS(250);
/* 普通ESP_LOG使用stdout;按行有界获取同一锁,避免JSON被并发日志插入。
* 不使用无界flockfile,未能获得锁也必须让CLI继续处理停止/另一端输入。 */
while (ftrylockfile(stdout) != 0) {
if ((TickType_t)(xTaskGetTickCount() - started) >= budget) {
return;
}
vTaskDelay(1);
}
size_t length = strlen(line);
size_t sent = 0U;
while (sent < length + 2U &&
(TickType_t)(xTaskGetTickCount() - started) < budget) {
const char *data = sent < length ? line + sent : &"\r\n"[sent - length];
size_t remaining = sent < length ? length - sent : length + 2U - sent;
size_t chunk = remaining > 64U ? 64U : remaining;
int written = usb_serial_jtag_write_bytes(data, chunk, 0);
if (written > 0) {
sent += (size_t)written;
} else {
vTaskDelay(1);
}
}
funlockfile(stdout);
#else
(void)line;
#endif
}