155 lines
5.5 KiB
C
155 lines
5.5 KiB
C
/* Mirror application printf calls to plain-text USB CDC without waiting
|
|
* for the USB host in producers. UART still uses the SDK's original printf. */
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include "ql_rtos.h"
|
|
#include "ql_uart.h"
|
|
#include "field_log.h"
|
|
/* This module is also compiled with -Dprintf=field_log_printf. */
|
|
#undef printf
|
|
extern int printf(const char *format, ...);
|
|
|
|
#define LOG_RECORD_BYTES 512
|
|
#define LOG_QUEUE_DEPTH 16
|
|
struct log_record { unsigned int length; char text[LOG_RECORD_BYTES]; };
|
|
/* Queue only a 32-bit slot index; full records exceed this SDK's queue
|
|
* message-size limit. Slots remain owned until the worker finishes writing. */
|
|
static struct log_record log_pool[LOG_QUEUE_DEPTH];
|
|
static unsigned char slot_used[LOG_QUEUE_DEPTH];
|
|
static ql_queue_t usb_queue;
|
|
static ql_task_t usb_task;
|
|
static volatile int ready;
|
|
static unsigned long dropped;
|
|
|
|
static void note_drop(void)
|
|
{
|
|
ql_rtos_task_sched_lock();
|
|
++dropped;
|
|
ql_rtos_task_sched_unlock();
|
|
}
|
|
|
|
static unsigned int reserve_slot(void)
|
|
{
|
|
unsigned int slot;
|
|
ql_rtos_task_sched_lock();
|
|
for (slot = 0; slot < LOG_QUEUE_DEPTH; ++slot) {
|
|
if (!slot_used[slot]) { slot_used[slot] = 1; break; }
|
|
}
|
|
ql_rtos_task_sched_unlock();
|
|
return slot;
|
|
}
|
|
|
|
static void release_slot(unsigned int slot)
|
|
{
|
|
ql_rtos_task_sched_lock();
|
|
slot_used[slot] = 0;
|
|
ql_rtos_task_sched_unlock();
|
|
}
|
|
|
|
int field_log_printf(const char *format, ...)
|
|
{
|
|
struct log_record record;
|
|
va_list args;
|
|
int n, ret;
|
|
va_start(args, format);
|
|
n = vsnprintf(record.text, sizeof(record.text), format, args);
|
|
va_end(args);
|
|
if (n < 0) return n;
|
|
if (n >= (int)sizeof(record.text)) {
|
|
const char suffix[] = " [TRUNCATED]\r\n";
|
|
size_t cut = sizeof(record.text) - sizeof(suffix);
|
|
/* Keep the UTF-8 prefix valid if a multi-byte character crosses cut. */
|
|
while (cut && ((unsigned char)record.text[cut] & 0xc0) == 0x80) --cut;
|
|
memcpy(record.text + cut, suffix, sizeof(suffix));
|
|
}
|
|
record.length = (unsigned int)strlen(record.text);
|
|
ret = printf("%s", record.text);
|
|
if (ready && record.length) {
|
|
unsigned int slot = reserve_slot();
|
|
if (slot == LOG_QUEUE_DEPTH) {
|
|
note_drop();
|
|
} else {
|
|
log_pool[slot] = record;
|
|
if (ql_rtos_queue_release(usb_queue, sizeof(slot), (u8 *)&slot, QL_NO_WAIT) != 0) {
|
|
release_slot(slot);
|
|
note_drop();
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* SDK variants return zero on success or a positive byte count. */
|
|
static int usb_write_all(const char *text, int length)
|
|
{
|
|
int offset = 0;
|
|
while (offset < length) {
|
|
int n = ql_uart_write(QL_USB_CDC_PORT, (unsigned char *)text + offset, length - offset);
|
|
if (n == 0) return 0;
|
|
if (n < 0 || n > length - offset) return -1;
|
|
offset += n;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void usb_worker(void *arg)
|
|
{
|
|
unsigned int slot;
|
|
int opened = 0;
|
|
unsigned long last_notice = 0, reported = 0;
|
|
(void)arg;
|
|
for (;;) {
|
|
if (!opened) {
|
|
int open_result = ql_uart_open(QL_USB_CDC_PORT, QL_UART_BAUD_115200, QL_FC_NONE);
|
|
if (open_result == QL_UART_SUCCESS || open_result == QL_UART_ALREAD_OPEN) {
|
|
const char banner[] = "\r\n[USB日志] 板板现场日志 CDC 已开启;UTF-8 / 115200 8N1;短按原报警键开始网络诊断\r\n";
|
|
opened = usb_write_all(banner, sizeof(banner) - 1) == 0;
|
|
if (!opened) ql_uart_close(QL_USB_CDC_PORT);
|
|
}
|
|
if (!opened) { ql_rtos_task_sleep_ms(1000); continue; }
|
|
}
|
|
if (ql_rtos_queue_wait(usb_queue, (u8 *)&slot, sizeof(slot), 1000) == 0 &&
|
|
slot < LOG_QUEUE_DEPTH) {
|
|
int result = usb_write_all(log_pool[slot].text, log_pool[slot].length);
|
|
release_slot(slot);
|
|
if (result != 0) {
|
|
note_drop();
|
|
ql_uart_close(QL_USB_CDC_PORT);
|
|
opened = 0;
|
|
ql_rtos_task_sleep_ms(1000);
|
|
}
|
|
}
|
|
/* Beacons identify the correct serial interface even when app is idle.
|
|
* Drop totals are never reset: concurrent producers cannot lose them. */
|
|
if (opened && (u32)(ql_rtos_get_systicks() - last_notice) >= 1000) {
|
|
char status[160];
|
|
unsigned long total;
|
|
ql_rtos_task_sched_lock(); total = dropped; ql_rtos_task_sched_unlock();
|
|
snprintf(status, sizeof(status), "\r\n[USB日志] 在线;累计丢弃=%lu 条%s\r\n", total,
|
|
total != reported ? ";USB拥塞或未连接,记录可能不完整" : "");
|
|
if (usb_write_all(status, (int)strlen(status)) != 0) {
|
|
ql_uart_close(QL_USB_CDC_PORT); opened = 0;
|
|
} else reported = total;
|
|
last_notice = ql_rtos_get_systicks();
|
|
}
|
|
}
|
|
}
|
|
|
|
int field_log_init(void)
|
|
{
|
|
int ret;
|
|
if (ready) return 0;
|
|
ret = ql_rtos_queue_create(&usb_queue, sizeof(unsigned int), LOG_QUEUE_DEPTH);
|
|
if (ret != 0) { printf("[USB日志] 队列创建失败=%d;仅保留UART日志\r\n", ret); return ret; }
|
|
ret = ql_rtos_task_create(&usb_task, 4096, 130, "usb_log", usb_worker, NULL);
|
|
if (ret != 0) {
|
|
ql_rtos_queue_delete(usb_queue); usb_queue = NULL;
|
|
printf("[USB日志] 转发任务创建失败=%d;仅保留UART日志\r\n", ret);
|
|
return ret;
|
|
}
|
|
ready = 1;
|
|
field_log_printf("[USB日志] 应用日志已同步到USB CDC;内核/基带日志不在此转发范围\r\n");
|
|
return 0;
|
|
}
|