diff --git a/Documents/api-reference.md b/Documents/api-reference.md index 128cc22..4b91078 100644 --- a/Documents/api-reference.md +++ b/Documents/api-reference.md @@ -226,19 +226,23 @@ PID 接口是旧版控制器工具。Motor 新 controller 不依赖该公共 API 头文件:`include/ares/interface/ares_interface.h` -`struct AresInterfaceAPI` 是传输层驱动表: +`struct AresInterfaceAPI` 是传输层能力表。不是每个接口都会实现所有回调,协议层调用前应检查函数 +指针是否为空。`send()` 与 `send_with_callback()` 调用后,`net_buf` 所有权交给接口;接口会在发送 +完成、发送中止或同步入队失败时释放缓冲。 -| 回调 | 说明 | -| --- | --- | -| `send(interface, buf)` | 发送一个 `net_buf`。 | -| `send_with_lock(interface, buf, mutex)` | 在调用者给出的锁保护下发送。 | -| `send_raw(interface, data, len)` | 发送原始字节。 | -| `connect(interface)` | 建立连接。 | -| `disconnect(interface)` | 断开连接。 | -| `is_connected(interface)` | 查询连接状态。 | -| `alloc_buf(interface)` | 分配发送缓冲。 | -| `alloc_buf_with_data(interface, data, size)` | 分配并填充发送缓冲。 | -| `init(interface)` | 初始化接口对象。 | +| 回调 | 说明 | 当前实现 | +| --- | --- | --- | +| `send(interface, buf)` | 发送一个 `net_buf`,不需要完成通知时使用。 | UART、USB bulk | +| `send_with_callback(interface, buf, cb, user_data)` | 发送 `net_buf` 并在 TX 完成、abort 或同步失败时调用 `ares_interface_tx_done_cb_t`。 | UART、USB bulk | +| `send_raw(interface, data, len)` | 发送原始字节,不使用 `net_buf`。 | UART | +| `connect(interface)` | 建立连接。 | 预留,当前 UART/USB 未填 | +| `disconnect(interface)` | 断开连接。 | 预留,当前 UART/USB 未填 | +| `is_connected(interface)` | 查询连接状态。 | 预留,当前 UART/USB 未填 | +| `caps(interface)` | 返回 `AresInterfaceCaps` 能力位。 | UART、USB bulk | +| `mtu(interface)` | 返回接口建议帧长上限。 | UART、USB bulk | +| `alloc_buf(interface)` | 分配发送缓冲。 | UART、USB bulk | +| `alloc_buf_with_data(interface, data, size)` | 分配并携带现有数据块的发送缓冲。 | USB bulk | +| `init(interface)` | 初始化接口对象。 | UART、USB bulk | `struct AresInterface` 包含名称、API、绑定的协议和传输私有数据。 @@ -250,8 +254,11 @@ PID 接口是旧版控制器工具。Motor 新 controller 不依赖该公共 API | --- | --- | | `ares_uart_init(interface)` | 初始化 UART 接口。 | | `ares_uart_send(interface, buf)` | 发送 `net_buf`。 | +| `ares_uart_send_with_callback(interface, buf, cb, user_data)` | 发送 `net_buf` 并在 UART TX 完成或失败时通知调用者。 | | `ares_uart_send_raw(interface, data, len)` | 发送原始字节。 | | `ares_uart_interface_alloc_buf(interface)` | 分配 UART 发送缓冲。 | +| `ares_uart_caps(interface)` | 返回 UART 接口能力位。 | +| `ares_uart_mtu(interface)` | 返回 UART 建议帧长上限。 | | `ares_uart_init_dev(interface, uart_dev)` | 绑定 Zephyr UART 设备。 | | `ARES_UART_INTERFACE_DEFINE(name)` | 定义 UART 接口实例。 | @@ -263,9 +270,11 @@ PID 接口是旧版控制器工具。Motor 新 controller 不依赖该公共 API | --- | --- | | `ares_usbd_init(interface)` | 初始化 USB bulk 接口。 | | `ares_usbd_write(interface, buf)` | 发送 `net_buf`。 | -| `ares_usbd_write_with_lock(interface, buf, mutex)` | 带外部锁发送。 | +| `ares_usbd_write_with_callback(interface, buf, cb, user_data)` | 发送 `net_buf` 并在 USB IN 传输完成或失败时通知调用者。 | | `ares_interface_alloc_buf(interface)` | 分配发送缓冲。 | -| `ares_interface_alloc_buf_with_data(interface, data, len)` | 分配并填充发送缓冲。 | +| `ares_interface_alloc_buf_with_data(interface, data, len)` | 分配并携带现有数据块的发送缓冲。 | +| `ares_usbd_caps(interface)` | 返回 USB bulk 接口能力位。 | +| `ares_usbd_mtu(interface)` | 返回当前 USB bulk 端点最大包长,未初始化时返回 64。 | | `ARES_BULK_INTERFACE_DEFINE(name)` | 定义 USB bulk 接口实例。 | ### 协议层 diff --git a/Documents/communication/interface.md b/Documents/communication/interface.md index b27a272..74b85fd 100644 --- a/Documents/communication/interface.md +++ b/Documents/communication/interface.md @@ -21,17 +21,29 @@ ARES 接口层统一承载底层链路,向协议层暴露稳定的发送、缓 ## `AresInterfaceAPI` -接口实现可以提供以下能力: +接口实现可以提供以下能力。这个结构体是能力表,不是强制每个接口完整实现的 vtable: - `send()`: 发送 `net_buf`。 -- `send_with_lock()`: 带互斥语义的发送。 -- `send_raw()`: 发送裸字节缓冲。 -- `connect()` / `disconnect()` / `is_connected()`: 连接状态接口。 +- `send_with_callback()`: 发送 `net_buf` 并在完成、abort 或同步失败时通知调用者。 +- `send_raw()`: 发送裸字节缓冲,当前由 UART 实现。 +- `connect()` / `disconnect()` / `is_connected()`: 连接状态接口,当前 UART/USB 宏未填,属于预留能力。 +- `caps()`: 返回 `AresInterfaceCaps` 能力位。 +- `mtu()`: 返回接口建议帧长上限。 - `alloc_buf()`: 分配发送缓冲。 -- `alloc_buf_with_data()`: 用现有数据块包装发送缓冲。 +- `alloc_buf_with_data()`: 用现有数据块创建发送缓冲,当前由 USB bulk 实现。 - `init()`: 初始化接口实例。 -并非所有接口都会实现全部入口。协议层在调用前应按空指针能力协商。 +当前宏填充情况: + +| 接口宏 | 已填回调 | +| --- | --- | +| `ARES_UART_INTERFACE_DEFINE()` | `init`、`send`、`send_with_callback`、`send_raw`、`caps`、`mtu`、`alloc_buf` | +| `ARES_BULK_INTERFACE_DEFINE()` | `init`、`send`、`send_with_callback`、`caps`、`mtu`、`alloc_buf`、`alloc_buf_with_data` | + +协议层在调用可选回调前必须按空指针能力协商。调用 `send()` 或 `send_with_callback()` 后, +`net_buf` 所有权交给接口;当前 UART/USB 实现会在发送完成、发送中止或同步入队失败时释放传入 +缓冲,调用者不应再次 `net_buf_unref()`。需要等待 TX 完成的协议应使用 `send_with_callback()`, +并在 `ares_interface_tx_done_cb_t` 里释放自己的在飞状态。 ## 绑定顺序 @@ -62,8 +74,12 @@ UART 接口适合: - `ares_uart_init(struct AresInterface *interface)` - `ares_uart_send(struct AresInterface *interface, struct net_buf *buf)` +- `ares_uart_send_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data)` - `ares_uart_send_raw(struct AresInterface *interface, uint8_t *data, uint16_t len)` - `ares_uart_interface_alloc_buf(struct AresInterface *interface)` +- `ares_uart_caps(struct AresInterface *interface)` +- `ares_uart_mtu(struct AresInterface *interface)` - `ares_uart_init_dev(struct AresInterface *interface, const struct device *uart_dev)` 以及实例定义宏: @@ -125,7 +141,8 @@ UART 异步回调收到 `UART_RX_RDY` 后: 1. 把 `net_buf *` 放入 TX 队列。 2. 由专用 TX 线程串行发送。 -3. 依靠 `UART_TX_DONE` / `UART_TX_ABORTED` 回调释放在飞缓冲。 +3. 依靠 `UART_TX_DONE` / `UART_TX_ABORTED` 回调触发 `send_with_callback()` 的完成通知。 +4. 完成通知返回后释放在飞缓冲。 这一设计保证了单通道串口发送有统一仲裁点,避免多个上下文直接打到底层驱动。 @@ -147,13 +164,13 @@ UART 异步回调收到 `UART_RX_RDY` 后: UART 接口常见失败点如下: -- TX 队列满:`ares_uart_send()` 返回 `-ENOMEM`,并释放缓冲。 +- TX 队列满:`ares_uart_send()` 返回 `-ENOMEM`,触发完成回调并释放缓冲。 - 设备未 ready:`ares_uart_init()` 返回 `-ENODEV`。 - `uart_callback_set()` 失败:初始化直接返回底层错误码。 -- `uart_tx()` 失败:发送线程记录错误并释放当前缓冲。 +- `uart_tx()` 失败:发送线程记录错误,触发完成回调并释放当前缓冲。 -调用者应将 `send()` 返回值视为“接口是否接管了缓冲”的边界;失败时不要再自行释放已经交给接口的 -缓冲,避免双重 `unref`。 +调用者应将 `send()` 或 `send_with_callback()` 调用视为缓冲所有权转移边界;即使发送入口返回错误, +当前实现也已处理完成回调与缓冲释放,调用者不要再自行释放。 ### 最小入口 diff --git a/Documents/communication/protocol.md b/Documents/communication/protocol.md index c6e97f6..ad28e51 100644 --- a/Documents/communication/protocol.md +++ b/Documents/communication/protocol.md @@ -185,9 +185,10 @@ SYNC 帧长度不能仅靠帧头得知,必须在收到 ID 后查同步表长 - 优先使用 `alloc_buf_with_data()` 包装现成帧 - 否则分配 `net_buf` 后复制数据 -- 若接口支持 `send_with_lock()`,则利用互斥保护在飞帧 +- 需要等待 TX 完成的 FUNC/同步帧使用 `send_with_callback()` 清理在飞状态 -这解释了为什么 USB 实现提供了 `send_with_lock()`,而 UART 没有。 +协议层不再依赖 USB 专用锁语义。UART 和 USB bulk 都通过 `ares_interface_tx_done_cb_t` +报告发送完成、abort 或同步入队失败。 ### CRC diff --git a/Documents/updates/ares_uart_usart1_921600_update.md b/Documents/updates/ares_uart_usart1_921600_update.md new file mode 100644 index 0000000..69dc5cc --- /dev/null +++ b/Documents/updates/ares_uart_usart1_921600_update.md @@ -0,0 +1,159 @@ + + +# ARES UART USART1 921600 Update + +Date: 2026-07-01 +Board: Damiao MC-02, STM32H723 +Test UART: USART1 on PA9/PA10 +Log UART: USART10 at 115200 +Host UART device: `/dev/cu.usbmodem00000000050C1` + +## Summary + +This update brings up the ARES UART interface on MC-02 USART1 and validates it +with real ARES dual-protocol FUNC/REPL frames at 921600 baud. The test path does +not bypass the protocol stack: the host sends FUNC frames, the board runs the +sample `func_cb`, and the board sends REPL frames back through the UART +interface. + +## Bugs Found + +- The communication sample hard-coded `DT_NODELABEL(usart6)`, while MC-02 test + hardware is wired to USART1. +- `CONFIG_UART_ASYNC_API` was not enabled in the UART test build, causing + `uart_callback_set()` to fail with `-ENOSYS`. +- STM32 async UART requires DMA. Without `dmas`/`dma-names`, `uart_tx()` failed + with `-ENODEV`. +- STM32H7 async UART DMA rejects cached buffers. RX and TX initially failed with + `Rx/Tx buffer should be placed in a nocache memory region` and `-EFAULT`. +- The UART driver created threads with `K_NO_WAIT` and then called + `k_thread_start()` again, which can restart an already started thread and led + to an MPU fault during bring-up. +- UART thread stacks were declared as raw `k_thread_stack_t[]` members instead + of Zephyr kernel stack members. + +## Changes + +- The sample now honors `chosen { ares,uart = ...; }` before falling back to + `usart6`, so board-specific UART selection can be done in an overlay. +- Added `samples/communication/ares_communication/boards/dm_mc02_usart1_921600.overlay` + for USART1 PA9/PA10 at 921600 baud with STM32H7 DMAMUX requests 42/41. +- UART RX slab is placed in `__nocache`. +- UART TX copies each outgoing `net_buf` into a dedicated `__nocache` DMA buffer + before calling `uart_tx()`. +- UART exposes TX-complete callback support and interface caps/MTU so the dual + protocol can clear in-flight state when real UART TX completes. +- Removed redundant `k_thread_start()` calls after `k_thread_create(..., K_NO_WAIT)`. +- Changed UART stack members to `K_KERNEL_STACK_MEMBER`. + +## Build Commands + +Functional/log-enabled test: + +```sh +west build -p always -b dm_mc02/stm32h723xx \ + samples/communication/ares_communication \ + -d build_uart_test -- \ + -DDTC_OVERLAY_FILE="samples/communication/ares_communication/boards/dm_mc02.overlay;samples/communication/ares_communication/boards/dm_mc02_usart1_921600.overlay" \ + -DCONFIG_UART_INTERFACE=y \ + -DCONFIG_UART_ASYNC_API=y \ + -DCONFIG_NOCACHE_MEMORY=y +``` + +Transport benchmark without application logging: + +```sh +west build -p always -b dm_mc02/stm32h723xx \ + samples/communication/ares_communication \ + -d build_uart_test -- \ + -DDTC_OVERLAY_FILE="samples/communication/ares_communication/boards/dm_mc02.overlay;samples/communication/ares_communication/boards/dm_mc02_usart1_921600.overlay" \ + -DCONFIG_UART_INTERFACE=y \ + -DCONFIG_UART_ASYNC_API=y \ + -DCONFIG_NOCACHE_MEMORY=y \ + -DCONFIG_LOG=n +``` + +Flash: + +```sh +west flash -d build_uart_test --no-rebuild --runner openocd \ + --config boards/damiao/dm_mc02/support/openocd.cfg +``` + +## Functional Validation + +- Board initialized `uart_protocol` successfully on USART1. +- Board transmitted ARES heartbeat frames on USART1. +- Host heartbeat frames kept `uart_protocol` online. +- Host FUNC frame: + +```text +ca fe 01 00 11 00 00 00 22 00 00 00 33 00 00 00 34 12 +``` + +Board log confirmed: + +```text +func_cb +params: 11,22,33 +``` + +## Performance + +All rows use 921600 baud, FUNC requests are 18 bytes, REPL replies are 10 bytes. +RTT is measured at the host from FUNC write to matching REPL read. At overloaded +rates, REPL request IDs wrap at 8 bits in the current protocol, so tail RTT is +best read as queue/overload indication rather than precise per-frame latency. + +### Log Enabled + +The sample's `func_cb` logs two lines per incoming FUNC. USART10 logging at +115200 saturated around 9.4 KiB/s and became a system-level bottleneck. + +| Target FPS | Sent FPS | Reply FPS | Success | Avg RTT ms | P95 RTT ms | P99 RTT ms | Max RTT ms | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 500 | 500.0 | 500.0 | 100.00% | 1.03 | 1.15 | 1.72 | 3.34 | +| 1000 | 1000.0 | 1000.0 | 100.00% | 1.03 | 1.13 | 1.33 | 3.65 | +| 2000 | 1998.7 | 1989.0 | 99.52% | 8.21 | 128.78 | 129.20 | 131.88 | +| 3000 | 2818.0 | 2777.3 | 98.56% | 16.07 | 86.65 | 170.87 | 343.31 | +| 4000 | 2759.0 | 2721.0 | 98.62% | 23.31 | 103.72 | 198.97 | 331.67 | +| 5000 | 2767.0 | 2733.7 | 98.80% | 17.27 | 97.50 | 169.64 | 271.34 | + +Practical log-enabled operating point: 1000 FUNC/REPL fps with low latency and +no observed loss in the 3 s run. Higher rates still return many frames, but the +log backend is saturated and tail latency rises sharply. + +### Log Disabled + +This removes application log traffic while keeping the real ARES UART +FUNC/REPL path. The wire-rate request-side limit for 18-byte frames at 921600 +8N1 is about 5120 FUNC fps. + +| Target FPS | Sent FPS | Reply FPS | Success | Avg RTT ms | P50 RTT ms | P95 RTT ms | P99 RTT ms | Max RTT ms | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 1000 | 979.7 | 969.3 | 98.95% | 22.93 | 0.93 | 256.85 | 257.17 | 573.82 | +| 2000 | 2000.0 | 1977.3 | 98.87% | 19.65 | 0.89 | 128.93 | 256.88 | 384.85 | +| 3000 | 3000.0 | 2824.0 | 94.13% | 113.50 | 86.08 | 341.76 | 342.50 | 512.89 | +| 4000 | 4000.0 | 2948.0 | 73.70% | 386.33 | 384.53 | 896.55 | 1088.59 | 1345.09 | +| 5000 | 5000.0 | 1898.3 | 37.97% | 952.40 | 914.41 | 1817.98 | 2021.38 | 2328.66 | +| 6000 | 5996.3 | 1464.0 | 24.41% | 997.69 | 928.22 | 2254.06 | 2461.94 | 2803.09 | + +Practical log-disabled operating point: around 2000 FUNC/REPL fps for sustained +testing with about 98.9% replies in this run. The maximum observed reply +throughput was about 2948 REPL fps at a 4000 fps request target, but that mode +is overloaded and drops roughly 26% of replies. + +The stable 2000 fps point corresponds to approximately 36.0 KiB/s of request +frame bytes host-to-board and 19.3 KiB/s of reply frame bytes board-to-host, +or about 55.3 KiB/s total protocol frame bytes across the full-duplex UART. + +## Remaining Bottleneck + +The UART transport is now functional at 921600, but the current dual-protocol +FUNC reply path has only one in-flight reply buffer per function mapping. When +FUNC frames arrive faster than UART TX completion can clear that in-flight bit, +extra replies are intentionally skipped. Raising the loss-free ceiling will +require queuing REPL frames or giving each pending reply its own buffer and +request ID storage. diff --git a/Documents/updates/ares_usb_transport_update.md b/Documents/updates/ares_usb_transport_update.md new file mode 100644 index 0000000..ea810a8 --- /dev/null +++ b/Documents/updates/ares_usb_transport_update.md @@ -0,0 +1,150 @@ + + +# ARES USB Transport Update + +Date: 2026-07-01 +Board under test: dm_mc02 / STM32H723 +USB device: ARES Bulk Interface Async, full-speed USB, VID:PID 1209:0001 + +## Summary + +This update fixes the ARES USB bulk transport and dual protocol reconnect path. The +main symptom was that the USB endpoints could still receive host frames while the +protocol state stayed offline or the reply path stayed permanently busy after a +host disconnect/reconnect. + +No benchmark-only firmware hooks are part of this update. The final validation was +done on the normal `samples/communication/ares_communication` application. + +## Root Cause + +The old USB path treated `USBD_STATE_CONFIGURED` as the main source of truth for +the protocol `CONNECTED` event. In practice, the Zephyr USB class `enable()` +callback is the reliable point where the interface has been enabled and the first +bulk OUT transfer can be submitted. + +There was also a second reconnect bug in the dual protocol TX path. The old +implementation used mutex-style send ownership. After a host disconnect, an IN +transfer could be left without a normal completion path. The board continued to +receive and parse FUNC frames, but the matching reply path stayed marked busy, so +later FUNC replies were suppressed. + +## Old Architecture + +- USB protocol connection was driven mostly by USB device state messages. +- Bulk OUT submission and protocol online state were loosely coupled. +- Reply and sync sends used a lock-like ownership model. +- Disconnect cleanup cleared the backup queue, but did not clear all in-flight + FUNC/SYNC send state. +- Reconnecting the host could leave the device in a split-brain state: + - OUT endpoint and parser were active. + - Protocol online/reply state could remain stale. + - Host could send frames but receive no replies. + +## New Architecture + +- USB class `enable()` first marks the interface enabled, then submits the initial + bulk OUT buffer. +- The protocol receives `ARES_PROTOCOL_EVENT_CONNECTED` only after that OUT + submission succeeds. +- A dedicated USB connected bit deduplicates `CONNECTED` and `DISCONNECTED` + events from class callbacks and USB state messages. +- USB class `disable()`, reset, and suspend all flow through the same disconnect + state helper. +- The ARES interface API now exposes TX completion callbacks and simple transport + capabilities. +- USB bulk stores the TX completion callback in `net_buf` user data and calls it + from IN completion. +- Dual protocol FUNC/SYNC sends use atomic `DUAL_TX_IN_FLIGHT` state that is + cleared by TX completion. +- Offline/reconnect cleanup now clears: + - FUNC in-flight state. + - SYNC in-flight state. + - FUNC reply backup queue. + - FUNC backup count. + +The intended state model is: + +1. USB class enable succeeds. +2. Initial OUT buffer is queued. +3. Protocol is marked connected. +4. Protocol sends use TX completion to release in-flight state. +5. Disconnect/reconnect cleanup always resets stale TX state before going online + again. + +## Files Changed + +- `include/ares/interface/ares_interface.h` +- `lib/ares/interface/ares_interface.h` +- `include/ares/interface/usb/usb_bulk.h` +- `lib/ares/interface/usb/usb_bulk.h` +- `include/ares/protocol/dual/dual_protocol.h` +- `lib/ares/interface/usb/usb_bulk.c` +- `lib/ares/protocol/dual/dual_protocol.c` + +## Validation + +Build: + +```sh +west build -d build +``` + +Flash: + +```sh +west flash -d build --no-rebuild --runner openocd --config mambo/boards/damiao/dm_mc02/support/openocd.cfg +``` + +Startup log after reset showed the expected class-driven connection path: + +```text +ares_usb_bulk_async: Enable ARES Bulk interface +dual_protocol: dual_protocol Connection established due to event. +``` + +Three consecutive host reconnect tests completed successfully. Before this fix, +later runs could drop to `replies=0` while the board still logged incoming +`func_cb` calls. + +```text +Run 1: sent=45949 replies=45449 send_fps=9189.8 reply_fps_over_send=9089.8 +Run 2: sent=46238 replies=45819 send_fps=9247.6 reply_fps_over_send=9163.8 +Run 3: sent=46039 replies=45626 send_fps=9207.8 reply_fps_over_send=9125.2 +``` + +## Performance Data + +These numbers measure the normal ARES protocol path, not raw USB bulk maximum +throughput. The tested application also still emits serial logs and periodic +application traffic. + +Best observed exec/reply rate: + +- Host-to-device exec send rate: 9247.6 frames/s. +- Device-to-host reply rate during send window: 9163.8 frames/s. +- Exec frame payload on the wire: 18 bytes. +- Reply frame payload on the wire: 10 bytes. +- Approximate effective protocol payload throughput: + - OUT: 162.6 KiB/s. + - IN replies: 89.5 KiB/s. + - Combined: 252 KiB/s, about 2.06 Mbit/s. + +Round-trip latency for single `exec -> reply` calls: + +- Typical RTT: 0.28 ms to 0.36 ms. +- Average RTT: 0.30 ms to 0.39 ms. +- p95 RTT: 0.44 ms to 0.64 ms. +- p99 RTT: 0.56 ms to 1.12 ms. +- Observed max RTT: 1.4 ms to 3.8 ms. + +For application budgeting, `1 ms` is a conservative round-trip target for the +current full-speed USB ARES protocol path. + +## Notes + +- The board enumerated as full-speed USB during the test. +- The temporary USB bulk benchmark and UART diagnostic sample directories were + deleted and are not part of this update. +- Raw USB bulk throughput can be higher than the protocol numbers above, but the + protocol numbers are the relevant data for current ARES exec/reply behavior. diff --git a/include/ares/interface/ares_interface.h b/include/ares/interface/ares_interface.h index 0d651cd..b0508ba 100644 --- a/include/ares/interface/ares_interface.h +++ b/include/ares/interface/ares_interface.h @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -13,6 +14,18 @@ extern "C" { struct AresProtocol; struct AresInterface; +typedef void (*ares_interface_tx_done_cb_t)(struct AresInterface *interface, struct net_buf *buf, + int status, void *user_data); + +enum AresInterfaceCaps { + ARES_INTERFACE_CAP_STREAM = BIT(0), + ARES_INTERFACE_CAP_PACKET = BIT(1), + ARES_INTERFACE_CAP_ZERO_COPY = BIT(2), + ARES_INTERFACE_CAP_TX_COMPLETE = BIT(3), + ARES_INTERFACE_CAP_TX_QUEUE = BIT(4), + ARES_INTERFACE_CAP_ETHERNET_FRAME = BIT(5), +}; + /** * @brief API that an interface must implement. * @@ -22,13 +35,15 @@ struct AresInterface; */ struct AresInterfaceAPI { int (*send)(struct AresInterface *interface, struct net_buf *buf); - int (*send_with_lock)(struct AresInterface *interface, struct net_buf *buf, - struct k_mutex *mutex); + int (*send_with_callback)(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data); int (*send_raw)(struct AresInterface *interface, uint8_t *data, uint16_t len); int (*connect)(struct AresInterface *interface); int (*disconnect)(struct AresInterface *interface); bool (*is_connected)(struct AresInterface *interface); + uint32_t (*caps)(struct AresInterface *interface); + size_t (*mtu)(struct AresInterface *interface); struct net_buf *(*alloc_buf)(struct AresInterface *interface); struct net_buf *(*alloc_buf_with_data)(struct AresInterface *interface, void *data, diff --git a/include/ares/interface/uart/uart.h b/include/ares/interface/uart/uart.h index 7b3c0a2..0638310 100644 --- a/include/ares/interface/uart/uart.h +++ b/include/ares/interface/uart/uart.h @@ -16,8 +16,12 @@ int ares_uart_init(struct AresInterface *interface); int ares_uart_send(struct AresInterface *interface, struct net_buf *buf); +int ares_uart_send_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data); int ares_uart_send_raw(struct AresInterface *interface, uint8_t *data, uint16_t len); struct net_buf *ares_uart_interface_alloc_buf(struct AresInterface *interface); +uint32_t ares_uart_caps(struct AresInterface *interface); +size_t ares_uart_mtu(struct AresInterface *interface); void ares_uart_init_dev(struct AresInterface *interface, const struct device *uart_dev); @@ -41,21 +45,24 @@ struct AresUartInterface { struct k_sem sem; struct k_thread thread; - k_thread_stack_t thread_stack[ARES_UART_PROCESSING_THREAD_STACK_SIZE]; + K_KERNEL_STACK_MEMBER(thread_stack, ARES_UART_PROCESSING_THREAD_STACK_SIZE); struct k_msgq tx_msgq; // 发送消息队列 char __aligned(4) tx_msgq_buffer[sizeof(struct net_buf *) * ARES_UART_TX_QUEUE_SIZE]; - struct k_sem tx_sem; // 用于发送流控制的信号量 - struct net_buf *current_tx_buf; // 指向当前正在发送的buf - struct k_thread tx_thread; // 发送线程的句柄 - k_thread_stack_t tx_thread_stack[ARES_UART_TX_THREAD_STACK_SIZE]; // 发送线程的栈 + struct k_sem tx_sem; // 用于发送流控制的信号量 + struct net_buf *current_tx_buf; // 指向当前正在发送的buf + struct k_thread tx_thread; // 发送线程的句柄 + K_KERNEL_STACK_MEMBER(tx_thread_stack, ARES_UART_TX_THREAD_STACK_SIZE); // 发送线程的栈 }; #define ARES_UART_INTERFACE_DEFINE(Interface_name) \ struct AresInterfaceAPI ares_uart_interface_api = { \ .init = ares_uart_init, \ .send = ares_uart_send, \ + .send_with_callback = ares_uart_send_with_callback, \ .send_raw = ares_uart_send_raw, \ + .caps = ares_uart_caps, \ + .mtu = ares_uart_mtu, \ .alloc_buf = ares_uart_interface_alloc_buf, \ }; \ struct AresUartInterface Internal_##Interface_name = {NULL}; \ diff --git a/include/ares/interface/usb/usb_bulk.h b/include/ares/interface/usb/usb_bulk.h index caa557d..925a85b 100644 --- a/include/ares/interface/usb/usb_bulk.h +++ b/include/ares/interface/usb/usb_bulk.h @@ -13,8 +13,10 @@ int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf); struct net_buf *ares_interface_alloc_buf(struct AresInterface *interface); struct net_buf *ares_interface_alloc_buf_with_data(struct AresInterface *interface, void *data, size_t len); -int ares_usbd_write_with_lock(struct AresInterface *interface, struct net_buf *buf, - struct k_mutex *mutex); +int ares_usbd_write_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data); +uint32_t ares_usbd_caps(struct AresInterface *interface); +size_t ares_usbd_mtu(struct AresInterface *interface); struct AresBulkInterface { struct AresInterface *interface; @@ -32,7 +34,9 @@ struct AresBulkInterface { struct AresInterfaceAPI ares_bulk_interface_api = { \ .init = ares_usbd_init, \ .send = ares_usbd_write, \ - .send_with_lock = ares_usbd_write_with_lock, \ + .send_with_callback = ares_usbd_write_with_callback, \ + .caps = ares_usbd_caps, \ + .mtu = ares_usbd_mtu, \ .alloc_buf = ares_interface_alloc_buf, \ .alloc_buf_with_data = ares_interface_alloc_buf_with_data, \ }; \ diff --git a/include/ares/protocol/dual/dual_protocol.h b/include/ares/protocol/dual/dual_protocol.h index f45a63a..4869c0e 100644 --- a/include/ares/protocol/dual/dual_protocol.h +++ b/include/ares/protocol/dual/dual_protocol.h @@ -94,6 +94,8 @@ enum frame_type { #define SYNC_PACK_STATUS_WRITE BIT(1) #define SYNC_PACK_STATUS_DONE BIT(2) +#define DUAL_TX_IN_FLIGHT 0 + #define GET_8BITS(buf, n_byte) (*(uint8_t *)(buf + n_byte)) #define GET_16BITS(buf, n_byte) (*(uint16_t *)(buf + n_byte)) #define GET_32BITS(buf, n_byte) (*(uint32_t *)(buf + n_byte)) @@ -116,7 +118,7 @@ struct sync_pack { dual_trans_cb_t cb; uint8_t *buf; - struct k_mutex mutex; + atomic_t tx_state; }; struct id_mapping { @@ -127,7 +129,7 @@ struct id_mapping { uint32_t arg3; uint16_t req_id; - struct k_mutex mutex; + atomic_t tx_state; __aligned(4) uint8_t buf[REPL_FRAME_LENGTH + 2]; // +2 for potential CRC16 }; @@ -207,19 +209,6 @@ int dual_sync_flush(struct AresProtocol *protocol, sync_table_t *pack); }; \ struct dual_protocol_data Protocol_name##_data = { \ .name = #Protocol_name, \ - .heart_beat_timer = {0}, \ - .err_frame_mutex = {0}, \ - .func_cnt = 0, \ - .sync_cnt = 0, \ - .func_tx_bckup_msgq = {0}, \ - .online = false, \ - .func_tx_bckup_cnt = 0, \ - .state = PARSER_STATE_IDLE, \ - .current_frame_type = FRAME_TYPE_UNKNOWN, \ - .rx_buffer_pos = 0, \ - .expected_frame_length = 0, \ - .header_value = 0, \ - .crc_enabled = false, \ }; \ struct AresProtocol Protocol_name = { \ .name = #Protocol_name, \ @@ -236,18 +225,6 @@ int dual_sync_flush(struct AresProtocol *protocol, sync_table_t *pack); }; \ struct dual_protocol_data Protocol_name##_data = { \ .name = #Protocol_name, \ - .heart_beat_timer = {0}, \ - .err_frame_mutex = {0}, \ - .func_cnt = 0, \ - .sync_cnt = 0, \ - .func_tx_bckup_msgq = {0}, \ - .online = false, \ - .func_tx_bckup_cnt = 0, \ - .state = PARSER_STATE_IDLE, \ - .current_frame_type = FRAME_TYPE_UNKNOWN, \ - .rx_buffer_pos = 0, \ - .expected_frame_length = 0, \ - .header_value = 0, \ .crc_enabled = true, \ }; \ struct AresProtocol Protocol_name = { \ diff --git a/lib/ares/interface/ares_interface.h b/lib/ares/interface/ares_interface.h index 0d651cd..b0508ba 100644 --- a/lib/ares/interface/ares_interface.h +++ b/lib/ares/interface/ares_interface.h @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -13,6 +14,18 @@ extern "C" { struct AresProtocol; struct AresInterface; +typedef void (*ares_interface_tx_done_cb_t)(struct AresInterface *interface, struct net_buf *buf, + int status, void *user_data); + +enum AresInterfaceCaps { + ARES_INTERFACE_CAP_STREAM = BIT(0), + ARES_INTERFACE_CAP_PACKET = BIT(1), + ARES_INTERFACE_CAP_ZERO_COPY = BIT(2), + ARES_INTERFACE_CAP_TX_COMPLETE = BIT(3), + ARES_INTERFACE_CAP_TX_QUEUE = BIT(4), + ARES_INTERFACE_CAP_ETHERNET_FRAME = BIT(5), +}; + /** * @brief API that an interface must implement. * @@ -22,13 +35,15 @@ struct AresInterface; */ struct AresInterfaceAPI { int (*send)(struct AresInterface *interface, struct net_buf *buf); - int (*send_with_lock)(struct AresInterface *interface, struct net_buf *buf, - struct k_mutex *mutex); + int (*send_with_callback)(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data); int (*send_raw)(struct AresInterface *interface, uint8_t *data, uint16_t len); int (*connect)(struct AresInterface *interface); int (*disconnect)(struct AresInterface *interface); bool (*is_connected)(struct AresInterface *interface); + uint32_t (*caps)(struct AresInterface *interface); + size_t (*mtu)(struct AresInterface *interface); struct net_buf *(*alloc_buf)(struct AresInterface *interface); struct net_buf *(*alloc_buf_with_data)(struct AresInterface *interface, void *data, diff --git a/lib/ares/interface/uart/uart.c b/lib/ares/interface/uart/uart.c index f44674d..322df76 100755 --- a/lib/ares/interface/uart/uart.c +++ b/lib/ares/interface/uart/uart.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -13,6 +14,7 @@ #include "ares/interface/uart/uart.h" #include "zephyr/sys/ring_buffer.h" #include +#include LOG_MODULE_REGISTER(ares_uart, LOG_LEVEL_INF); // 日志级别可以按需调整 @@ -21,8 +23,30 @@ LOG_MODULE_REGISTER(ares_uart, LOG_LEVEL_INF); // 日志级别可以按需调整 #define ARES_UART_PROCESSING_THREAD_PRIORITY K_PRIO_PREEMPT(5) #define ARES_UART_RX_INACTIVE_TIMEOUT 10 -NET_BUF_POOL_DEFINE(uart_net_buf_pool, 16, 128, 4, NULL); // 增加缓冲区数量和大小 -K_MEM_SLAB_DEFINE(uart_rx_slab, ARES_UART_BLOCK_SIZE, 8, 4); +struct ares_uart_tx_meta { + ares_interface_tx_done_cb_t tx_done; + void *tx_user_data; +}; + +NET_BUF_POOL_DEFINE(uart_net_buf_pool, 16, 128, sizeof(struct ares_uart_tx_meta), + NULL); // 增加缓冲区数量和大小 +K_MEM_SLAB_DEFINE_IN_SECT(uart_rx_slab, __nocache, ARES_UART_BLOCK_SIZE, 8, 4); + +static uint8_t __nocache __aligned(32) uart_tx_dma_buf[MAX_FRAME_PAYLOAD_SIZE]; + +static void ares_uart_complete_tx(struct AresInterface *interface, struct net_buf *buf, int status) +{ + struct ares_uart_tx_meta *meta; + + if (buf == NULL) { + return; + } + + meta = net_buf_user_data(buf); + if (meta && meta->tx_done) { + meta->tx_done(interface, buf, status, meta->tx_user_data); + } +} /** * @brief 发送一个数据包到队列中 @@ -34,11 +58,24 @@ K_MEM_SLAB_DEFINE(uart_rx_slab, ARES_UART_BLOCK_SIZE, 8, 4); * @return 0 on success, -ENOMEM if the queue is full. */ int ares_uart_send(struct AresInterface *interface, struct net_buf *buf) +{ + return ares_uart_send_with_callback(interface, buf, NULL, NULL); +} + +int ares_uart_send_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data) { struct AresUartInterface *uart_if = interface->priv_data; + struct ares_uart_tx_meta *meta = net_buf_user_data(buf); + + if (meta) { + meta->tx_done = cb; + meta->tx_user_data = user_data; + } if (k_msgq_put(&uart_if->tx_msgq, &buf, K_NO_WAIT) != 0) { LOG_ERR("TX message queue is full. Dropping packet!"); + ares_uart_complete_tx(interface, buf, -ENOMEM); net_buf_unref(buf); return -ENOMEM; } @@ -56,14 +93,37 @@ int ares_uart_send_raw(struct AresInterface *interface, uint8_t *data, uint16_t LOG_ERR("uart_tx failed with error %d", err); return -EIO; } + return 0; } struct net_buf *ares_uart_interface_alloc_buf(struct AresInterface *interface) { struct net_buf *buf = net_buf_alloc(&uart_net_buf_pool, K_NO_WAIT); + if (buf) { + struct ares_uart_tx_meta *meta = net_buf_user_data(buf); + if (meta) { + meta->tx_done = NULL; + meta->tx_user_data = NULL; + } + } return buf; } +uint32_t ares_uart_caps(struct AresInterface *interface) +{ + ARG_UNUSED(interface); + + return ARES_INTERFACE_CAP_STREAM | ARES_INTERFACE_CAP_TX_COMPLETE | + ARES_INTERFACE_CAP_TX_QUEUE; +} + +size_t ares_uart_mtu(struct AresInterface *interface) +{ + ARG_UNUSED(interface); + + return MAX_FRAME_PAYLOAD_SIZE; +} + /** * @brief (新增) 发送线程入口函数 * @@ -80,11 +140,20 @@ static void ares_uart_tx_thread_entry(void *p1, void *p2, void *p3) while (1) { k_msgq_get(&uart_if->tx_msgq, &buf, K_FOREVER); k_sem_take(&uart_if->tx_sem, K_FOREVER); + if (buf->len > sizeof(uart_tx_dma_buf)) { + LOG_ERR("UART frame too large for DMA buffer: %u", buf->len); + ares_uart_complete_tx(interface, buf, -EMSGSIZE); + net_buf_unref(buf); + k_sem_give(&uart_if->tx_sem); + continue; + } + memcpy(uart_tx_dma_buf, buf->data, buf->len); uart_if->current_tx_buf = buf; - err = uart_tx(uart_if->uart_dev, buf->data, buf->len, SYS_FOREVER_US); + err = uart_tx(uart_if->uart_dev, uart_tx_dma_buf, buf->len, SYS_FOREVER_US); if (err != 0) { LOG_ERR("uart_tx failed with error %d, even with flow control!", err); k_sem_give(&uart_if->tx_sem); + ares_uart_complete_tx(interface, uart_if->current_tx_buf, -EIO); net_buf_unref(uart_if->current_tx_buf); uart_if->current_tx_buf = NULL; } @@ -101,6 +170,7 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void /* --- TX 事件处理 --- */ case UART_TX_DONE: if (uart_if->current_tx_buf != NULL) { + ares_uart_complete_tx(uart_if->interface, uart_if->current_tx_buf, 0); net_buf_unref(uart_if->current_tx_buf); uart_if->current_tx_buf = NULL; } @@ -110,6 +180,8 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void case UART_TX_ABORTED: LOG_WRN("UART TX aborted"); if (uart_if->current_tx_buf != NULL) { + ares_uart_complete_tx(uart_if->interface, uart_if->current_tx_buf, + -ECANCELED); net_buf_unref(uart_if->current_tx_buf); uart_if->current_tx_buf = NULL; } @@ -178,6 +250,7 @@ void ares_uart_thread_entry(void *p1, void *p2, void *p3) void ares_uart_init_dev(struct AresInterface *interface, const struct device *uart_dev) { struct AresUartInterface *uart_if = interface->priv_data; + uart_if->interface = interface; uart_if->uart_dev = (struct device *)uart_dev; } @@ -185,6 +258,7 @@ void ares_uart_init_dev(struct AresInterface *interface, const struct device *ua int ares_uart_init(struct AresInterface *interface) { struct AresUartInterface *uart_if = interface->priv_data; + uart_if->interface = interface; if (!device_is_ready(uart_if->uart_dev)) { LOG_ERR("UART device not ready"); @@ -215,7 +289,6 @@ int ares_uart_init(struct AresInterface *interface) (void *)interface, NULL, NULL, ARES_UART_PROCESSING_THREAD_PRIORITY, 0, K_NO_WAIT); k_thread_name_set(&uart_if->thread, "ares_uart_rx"); - k_thread_start(&uart_if->thread); /* --- 新增: 初始化 TX 部分 --- */ uart_if->current_tx_buf = NULL; @@ -228,7 +301,6 @@ int ares_uart_init(struct AresInterface *interface) (void *)interface, NULL, NULL, ARES_UART_PROCESSING_THREAD_PRIORITY, 0, K_NO_WAIT); k_thread_name_set(&uart_if->tx_thread, "ares_uart_tx"); - k_thread_start(&uart_if->tx_thread); LOG_INF("Ares UART Interface initialized with TX queue."); return 0; diff --git a/lib/ares/interface/usb/usb_bulk.c b/lib/ares/interface/usb/usb_bulk.c index e2ca82f..a5d30cc 100644 --- a/lib/ares/interface/usb/usb_bulk.c +++ b/lib/ares/interface/usb/usb_bulk.c @@ -46,6 +46,7 @@ static struct AresInterface *ares_interface; #define ARES_IF_FUNCTION_ENABLED 0 #define ARES_IF_FUNCTION_OUT_ENGAGED 1 #define ARES_IF_FUNCTION_IN_ENGAGED 2 +#define ARES_IF_FUNCTION_CONNECTED 3 /* === USB Device and Descriptor Definitions === */ @@ -68,6 +69,7 @@ USBD_CONFIGURATION_DEFINE(ares_hs_config, attributes, 250, &hs_cfg_desc); // Forward declaration struct usbd_class_data *ares_if_class_data; +static void ares_usbd_protocol_event(enum AresProtocolEvent event); struct ares_if_desc { struct usb_if_descriptor if0; @@ -85,6 +87,38 @@ struct ares_if_data { atomic_t state; }; +struct ares_udc_buf_info { + struct udc_buf_info udc_buf_info; + ares_interface_tx_done_cb_t tx_done; + void *tx_user_data; +}; + +static void ares_usbd_set_connected(struct usbd_class_data *const c_data, bool connected) +{ + struct ares_if_data *data; + + if (c_data == NULL) { + return; + } + + data = usbd_class_get_private(c_data); + if (data == NULL) { + return; + } + + if (connected) { + if (!atomic_test_bit(&data->state, ARES_IF_FUNCTION_ENABLED)) { + return; + } + + if (!atomic_test_and_set_bit(&data->state, ARES_IF_FUNCTION_CONNECTED)) { + ares_usbd_protocol_event(ARES_PROTOCOL_EVENT_CONNECTED); + } + } else if (atomic_test_and_clear_bit(&data->state, ARES_IF_FUNCTION_CONNECTED)) { + ares_usbd_protocol_event(ARES_PROTOCOL_EVENT_DISCONNECTED); + } +} + static uint8_t ares_if_get_bulk_out(struct usbd_class_data *const c_data) { struct ares_if_data *data = usbd_class_get_private(c_data); @@ -167,6 +201,10 @@ static int ares_if_request_handler(struct usbd_class_data *const c_data, struct if (ep == ares_if_get_bulk_in(c_data)) { atomic_clear_bit(&data->state, ARES_IF_FUNCTION_IN_ENGAGED); + struct ares_udc_buf_info *buf_info = net_buf_user_data(buf); + if (buf_info && buf_info->tx_done) { + buf_info->tx_done(ares_interface, buf, err_code, buf_info->tx_user_data); + } net_buf_unref(buf); /* @@ -262,13 +300,19 @@ static void *ares_if_get_desc(struct usbd_class_data *const c_data, const enum u static void ares_if_enable(struct usbd_class_data *const c_data) { struct ares_if_data *data = usbd_class_get_private(c_data); + int err; + LOG_INF("Enable ARES Bulk interface"); if (atomic_test_and_set_bit(&data->state, ARES_IF_FUNCTION_ENABLED)) { return; } - if (ares_if_submit_bulk_out(c_data) != 0) { + err = ares_if_submit_bulk_out(c_data); + if (err != 0) { LOG_ERR("Failed to submit initial bulk OUT request on enable"); + return; } + + ares_usbd_set_connected(c_data, true); } static void ares_if_disable(struct usbd_class_data *const c_data) @@ -277,6 +321,8 @@ static void ares_if_disable(struct usbd_class_data *const c_data) struct net_buf *buf; LOG_INF("Disable ARES Bulk interface"); + ares_usbd_set_connected(c_data, false); + atomic_clear_bit(&data->state, ARES_IF_FUNCTION_ENABLED); atomic_clear_bit(&data->state, ARES_IF_FUNCTION_IN_ENGAGED); atomic_clear_bit(&data->state, ARES_IF_FUNCTION_OUT_ENGAGED); @@ -337,11 +383,6 @@ static void ares_processing_thread_entry(void *p1, void *p2, void *p3) /* === Public API and Setup Function === */ -struct ares_udc_buf_info { - struct udc_buf_info udc_buf_info; - struct k_mutex *mutex; -} __attribute__((packed)); - static const char *const ares_usbd_bulk_first_blocklist[] = { "ares_if_0", NULL, @@ -350,18 +391,14 @@ static const char *const ares_usbd_bulk_first_blocklist[] = { /* To send data, we can allocate from the general-purpose system pool */ void buf_cb_unlock(struct net_buf *buf) { - struct ares_udc_buf_info *buf_info = net_buf_user_data(buf); - if (buf_info->mutex) { - LOG_DBG("Unlocking mutex %p", buf_info->mutex); - k_mutex_unlock(buf_info->mutex); - } LOG_DBG("Destroying buffer %p", buf); net_buf_destroy(buf); } UDC_BUF_POOL_DEFINE(tx_pool, 8, 512, sizeof(struct ares_udc_buf_info), buf_cb_unlock); -int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf) +static int ares_usbd_write_common(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data) { struct usbd_class_data *c_data = ares_if_class_data; struct ares_if_data *data = NULL; @@ -388,7 +425,8 @@ int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf) struct ares_udc_buf_info *buf_info = net_buf_user_data(buf); if (buf_info) { - buf_info->mutex = NULL; + buf_info->tx_done = cb; + buf_info->tx_user_data = user_data; buf_info->udc_buf_info.ep = ares_if_get_bulk_in(c_data); } @@ -397,6 +435,9 @@ int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf) atomic_clear_bit(&data->state, ARES_IF_FUNCTION_IN_ENGAGED); LOG_ERR("Enqueue error %d", err); clear_exit: + if (cb) { + cb(interface, buf, err, user_data); + } net_buf_unref(buf); } else { LOG_DBG("Enqueueing buffer %p", buf); @@ -404,48 +445,35 @@ int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf) return err; } -int ares_usbd_write_with_lock(struct AresInterface *interface, struct net_buf *buf, - struct k_mutex *mutex) +int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf) { - struct usbd_class_data *c_data = ares_if_class_data; - struct ares_if_data *data = NULL; - int err; + return ares_usbd_write_common(interface, buf, NULL, NULL); +} - if (c_data == NULL) { - err = -ENODEV; - goto clear_exit; - } +int ares_usbd_write_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data) +{ + return ares_usbd_write_common(interface, buf, cb, user_data); +} - data = usbd_class_get_private(c_data); - if (data == NULL) { - err = -EINVAL; - goto clear_exit; - } - if (!atomic_test_bit(&data->state, ARES_IF_FUNCTION_ENABLED)) { - err = -EPERM; - goto clear_exit; - } - if (atomic_test_and_set_bit(&data->state, ARES_IF_FUNCTION_IN_ENGAGED)) { - err = -EBUSY; - goto clear_exit; - } +uint32_t ares_usbd_caps(struct AresInterface *interface) +{ + ARG_UNUSED(interface); - struct ares_udc_buf_info *buf_info = net_buf_user_data(buf); - if (buf_info) { - buf_info->mutex = mutex; - buf_info->udc_buf_info.ep = ares_if_get_bulk_in(c_data); - } + return ARES_INTERFACE_CAP_PACKET | ARES_INTERFACE_CAP_ZERO_COPY | + ARES_INTERFACE_CAP_TX_COMPLETE; +} - err = usbd_ep_enqueue(c_data, buf); - if (err) { - atomic_clear_bit(&data->state, ARES_IF_FUNCTION_IN_ENGAGED); - LOG_DBG("locked Enqueue error %d", err); -clear_exit: - net_buf_unref(buf); - } else { - LOG_DBG("locked Enqueueing buffer %p", buf); +size_t ares_usbd_mtu(struct AresInterface *interface) +{ + struct usbd_class_data *c_data = ares_if_class_data; + + ARG_UNUSED(interface); + + if (c_data == NULL) { + return 64U; } - return err; + return ares_if_get_mps(c_data); } struct net_buf *ares_interface_alloc_buf(struct AresInterface *interface) @@ -454,7 +482,8 @@ struct net_buf *ares_interface_alloc_buf(struct AresInterface *interface) if (buf) { struct ares_udc_buf_info *buf_info = net_buf_user_data(buf); if (buf_info) { - buf_info->mutex = NULL; + buf_info->tx_done = NULL; + buf_info->tx_user_data = NULL; } } return buf; @@ -467,7 +496,8 @@ struct net_buf *ares_interface_alloc_buf_with_data(struct AresInterface *interfa if (buf) { struct ares_udc_buf_info *buf_info = net_buf_user_data(buf); if (buf_info) { - buf_info->mutex = NULL; + buf_info->tx_done = NULL; + buf_info->tx_user_data = NULL; } } return buf; @@ -516,16 +546,16 @@ static void ares_usbd_msg_cb(struct usbd_context *const usbd_ctx, const struct u { switch (msg->type) { case USBD_STATE_CONFIGURED: - LOG_INF("USB device configured"); - ares_usbd_protocol_event(ARES_PROTOCOL_EVENT_CONNECTED); + // LOG_INF("USB device configured"); + ares_usbd_set_connected(ares_if_class_data, true); break; case USBD_MSG_RESET: - LOG_INF("USB device reset"); - ares_usbd_protocol_event(ARES_PROTOCOL_EVENT_DISCONNECTED); + // LOG_INF("USB device reset"); + ares_usbd_set_connected(ares_if_class_data, false); break; case USBD_MSG_SUSPEND: LOG_INF("USB device suspended"); - ares_usbd_protocol_event(ARES_PROTOCOL_EVENT_DISCONNECTED); + ares_usbd_set_connected(ares_if_class_data, false); break; default: break; diff --git a/lib/ares/interface/usb/usb_bulk.h b/lib/ares/interface/usb/usb_bulk.h index caa557d..925a85b 100644 --- a/lib/ares/interface/usb/usb_bulk.h +++ b/lib/ares/interface/usb/usb_bulk.h @@ -13,8 +13,10 @@ int ares_usbd_write(struct AresInterface *interface, struct net_buf *buf); struct net_buf *ares_interface_alloc_buf(struct AresInterface *interface); struct net_buf *ares_interface_alloc_buf_with_data(struct AresInterface *interface, void *data, size_t len); -int ares_usbd_write_with_lock(struct AresInterface *interface, struct net_buf *buf, - struct k_mutex *mutex); +int ares_usbd_write_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data); +uint32_t ares_usbd_caps(struct AresInterface *interface); +size_t ares_usbd_mtu(struct AresInterface *interface); struct AresBulkInterface { struct AresInterface *interface; @@ -32,7 +34,9 @@ struct AresBulkInterface { struct AresInterfaceAPI ares_bulk_interface_api = { \ .init = ares_usbd_init, \ .send = ares_usbd_write, \ - .send_with_lock = ares_usbd_write_with_lock, \ + .send_with_callback = ares_usbd_write_with_callback, \ + .caps = ares_usbd_caps, \ + .mtu = ares_usbd_mtu, \ .alloc_buf = ares_interface_alloc_buf, \ .alloc_buf_with_data = ares_interface_alloc_buf_with_data, \ }; \ diff --git a/lib/ares/protocol/dual/dual_protocol.c b/lib/ares/protocol/dual/dual_protocol.c index f5f1a8a..3b39d15 100755 --- a/lib/ares/protocol/dual/dual_protocol.c +++ b/lib/ares/protocol/dual/dual_protocol.c @@ -3,7 +3,7 @@ #include "zephyr/kernel.h" #include "zephyr/net_buf.h" #include -#include +#include #include "ares/interface/ares_interface.h" #include @@ -86,14 +86,40 @@ struct net_buf *alloc_and_add_data(struct AresProtocol *protocol, uint8_t *data, } } -int send_try_lock(struct AresProtocol *protocol, struct net_buf *buf, struct k_mutex *mutex) +static int send_with_tx_done(struct AresProtocol *protocol, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data) { - if (protocol->interface->api->send_with_lock && mutex) { - return protocol->interface->api->send_with_lock(protocol->interface, buf, mutex); - } else { - k_mutex_unlock(mutex); + if (buf == NULL) { + return -ENOMEM; + } + + if (cb != NULL) { + if (protocol->interface->api->send_with_callback == NULL) { + net_buf_unref(buf); + return -ENOTSUP; + } + return protocol->interface->api->send_with_callback(protocol->interface, buf, cb, + user_data); + } + + if (protocol->interface->api->send) { return protocol->interface->api->send(protocol->interface, buf); } + + net_buf_unref(buf); + return -ENOTSUP; +} + +static void dual_clear_tx_in_flight(struct AresInterface *interface, struct net_buf *buf, + int status, void *user_data) +{ + atomic_t *tx_state = user_data; + + ARG_UNUSED(interface); + ARG_UNUSED(buf); + ARG_UNUSED(status); + + atomic_clear_bit(tx_state, DUAL_TX_IN_FLIGHT); } static void error_handle(struct AresProtocol *protocol, uint16_t req_id, uint16_t error) @@ -204,9 +230,19 @@ static void usb_offline_clean(struct AresProtocol *protocol) { struct tx_msg_bck msg; struct dual_protocol_data *data = protocol->priv_data; + + for (uint8_t i = 0; i < data->func_cnt; i++) { + atomic_clear_bit(&data->func_table[i].tx_state, DUAL_TX_IN_FLIGHT); + } + + for (uint8_t i = 0; i < data->sync_cnt; i++) { + atomic_clear_bit(&data->sync_table[i].tx_state, DUAL_TX_IN_FLIGHT); + } + while (k_msgq_num_used_get(&data->func_tx_bckup_msgq) > 0) { k_msgq_get(&data->func_tx_bckup_msgq, &msg, K_NO_WAIT); } + data->func_tx_bckup_cnt = 0; } static void usb_trans_heart_beat(struct k_timer *timer) @@ -312,15 +348,16 @@ static void parse_func(struct AresProtocol *protocol, func_table_t *map) k_msgq_put(&data->func_tx_bckup_msgq, &msg, K_NO_WAIT); data->func_tx_bckup_cnt++; - uint8_t *repl_frame = map->buf; - if (k_mutex_lock(&map->mutex, K_NO_WAIT) != 0) { - LOG_DBG("%s Failed to lock FUNC frame mutex.", data->name); + if (atomic_test_and_set_bit(&map->tx_state, DUAL_TX_IN_FLIGHT)) { + LOG_DBG("%s FUNC reply frame is still in flight.", data->name); return; } + + uint8_t *repl_frame = map->buf; GET_16BITS(repl_frame, REPL_HEAD_IDX) = REPL_FRAME_HEAD; GET_16BITS(repl_frame, REPL_FUNC_ID_IDX) = map->id; GET_32BITS(repl_frame, REPL_RET_IDX) = (uint32_t)ret; - GET_8BITS(repl_frame, REPL_REQ_ID_IDX) = map->req_id; + GET_16BITS(repl_frame, REPL_REQ_ID_IDX) = map->req_id; size_t frame_len = REPL_FRAME_LENGTH; if (data->crc_enabled) { @@ -330,9 +367,10 @@ static void parse_func(struct AresProtocol *protocol, func_table_t *map) int err = 0; struct net_buf *buf = alloc_and_add_data(protocol, repl_frame, frame_len); - err = send_try_lock(protocol, buf, &map->mutex); + err = send_with_tx_done(protocol, buf, dual_clear_tx_in_flight, &map->tx_state); if (err != 0) { + atomic_clear_bit(&map->tx_state, DUAL_TX_IN_FLIGHT); LOG_ERR("%s Failed to send REPLY frame.", data->name); return; } @@ -392,6 +430,9 @@ int dual_sync_flush(struct AresProtocol *protocol, sync_table_t *pack) LOG_ERR("%s Sync pack is NULL.", data->name); return -EINVAL; } + if (atomic_test_and_set_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT)) { + return -EBUSY; + } memcpy(pack->buf + SYNC_DATA_IDX, pack->data, pack->len); size_t frame_len = pack->len + SYNC_FRAME_LENGTH_OFFSET; @@ -403,9 +444,10 @@ int dual_sync_flush(struct AresProtocol *protocol, sync_table_t *pack) // LOG_HEXDUMP_DBG(pack->buf, frame_len, "Sync data to send:"); struct net_buf *buf = alloc_and_add_data(protocol, pack->buf, frame_len); - int ret = send_try_lock(protocol, buf, &pack->mutex); + int ret = send_with_tx_done(protocol, buf, dual_clear_tx_in_flight, &pack->tx_state); if (ret != 0) { + atomic_clear_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT); // LOG_ERR("Failed to send SYNC frame. %d", ret); return -EBUSY; } @@ -463,6 +505,7 @@ sync_table_t *dual_sync_add(struct AresProtocol *protocol, uint16_t ID, uint8_t data->sync_table[data->sync_cnt].ID = ID; data->sync_table[data->sync_cnt].len = len; data->sync_table[data->sync_cnt].cb = cb; + atomic_set(&data->sync_table[data->sync_cnt].tx_state, 0); size_t buf_size = len + SYNC_FRAME_LENGTH_OFFSET; if (data->crc_enabled) { @@ -471,12 +514,12 @@ sync_table_t *dual_sync_add(struct AresProtocol *protocol, uint16_t ID, uint8_t data->sync_table[data->sync_cnt].buf = k_heap_aligned_alloc(&dual_protocol_heap, 4, buf_size, K_NO_WAIT); - memset(data->sync_table[data->sync_cnt].buf, 0, buf_size); if (data->sync_table[data->sync_cnt].buf == NULL) { LOG_ERR("%s Failed to allocate memory for SYNC frame. Count: %d", data->name, data->sync_cnt); return NULL; } + memset(data->sync_table[data->sync_cnt].buf, 0, buf_size); GET_16BITS(data->sync_table[data->sync_cnt].buf, SYNC_HEAD_IDX) = SYNC_FRAME_HEAD; GET_16BITS(data->sync_table[data->sync_cnt].buf, SYNC_ID_IDX) = ID; data->sync_cnt++; @@ -502,6 +545,7 @@ void dual_func_add(struct AresProtocol *protocol, uint16_t id, dual_trans_func_t } data->func_table[data->func_cnt].id = id; data->func_table[data->func_cnt].cb = cb; + atomic_set(&data->func_table[data->func_cnt].tx_state, 0); data->func_cnt++; } @@ -806,12 +850,14 @@ void ares_dual_protocol_event(struct AresProtocol *protocol, enum AresProtocolEv if (event == ARES_PROTOCOL_EVENT_CONNECTED) { LOG_INF("%s Connection established due to event.", data->name); k_msleep(600); + usb_offline_clean(protocol); data->online = true; // 重置状态机状态 reset_parser_state(data); } else if (event == ARES_PROTOCOL_EVENT_DISCONNECTED) { LOG_INF("%s Connection lost due to event.", data->name); data->online = false; + usb_offline_clean(protocol); // 重置状态机状态 reset_parser_state(data); } diff --git a/samples/communication/ares_communication/boards/dm_mc02.conf b/samples/communication/ares_communication/boards/dm_mc02.conf index 2e5458e..b0d5448 100644 --- a/samples/communication/ares_communication/boards/dm_mc02.conf +++ b/samples/communication/ares_communication/boards/dm_mc02.conf @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + # dm_mc02 uses USART10 as the debug console, so keep this sample focused on USB bulk. CONFIG_UART_INTERFACE=n diff --git a/samples/communication/ares_communication/boards/dm_mc02.overlay b/samples/communication/ares_communication/boards/dm_mc02.overlay index c6cd80d..f0abfbc 100644 --- a/samples/communication/ares_communication/boards/dm_mc02.overlay +++ b/samples/communication/ares_communication/boards/dm_mc02.overlay @@ -1,3 +1,7 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + zephyr_udc0: &usbotg_hs { pinctrl-0 = <&usb_otg_hs_dm_pa11 &usb_otg_hs_dp_pa12>; pinctrl-names = "default"; diff --git a/samples/communication/ares_communication/boards/dm_mc02_usart1_921600.overlay b/samples/communication/ares_communication/boards/dm_mc02_usart1_921600.overlay new file mode 100644 index 0000000..14027fa --- /dev/null +++ b/samples/communication/ares_communication/boards/dm_mc02_usart1_921600.overlay @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + ares,uart = &usart1; + }; +}; + +&usart1 { + status = "okay"; + pinctrl-0 = <&usart1_tx_pa9 &usart1_rx_pa10>; + pinctrl-names = "default"; + current-speed = <921600>; + dmas = <&dmamux1 3 42 (STM32_DMA_PERIPH_TX | STM32_DMA_PRIORITY_HIGH)>, + <&dmamux1 4 41 (STM32_DMA_PERIPH_RX | STM32_DMA_PRIORITY_HIGH)>; + dma-names = "tx", "rx"; +}; diff --git a/samples/communication/ares_communication/src/main.c b/samples/communication/ares_communication/src/main.c index 3e803ab..0397235 100755 --- a/samples/communication/ares_communication/src/main.c +++ b/samples/communication/ares_communication/src/main.c @@ -14,7 +14,11 @@ LOG_MODULE_REGISTER(main_app, LOG_LEVEL_INF); #if IS_ENABLED(CONFIG_UART_INTERFACE) +#if DT_HAS_CHOSEN(ares_uart) +#define UART_DEV DT_CHOSEN(ares_uart) +#else #define UART_DEV DT_NODELABEL(usart6) +#endif static const struct device *uart_dev = DEVICE_DT_GET(UART_DEV); #endif diff --git a/tests/native_sim/ares_communication/CMakeLists.txt b/tests/native_sim/ares_communication/CMakeLists.txt new file mode 100644 index 0000000..d55aa21 --- /dev/null +++ b/tests/native_sim/ares_communication/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ares_native_sim_communication) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/native_sim/ares_communication/prj.conf b/tests/native_sim/ares_communication/prj.conf new file mode 100644 index 0000000..4f7aaec --- /dev/null +++ b/tests/native_sim/ares_communication/prj.conf @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_ZTEST=y +CONFIG_NET_BUF=y +CONFIG_ARES_COMM_LIB=y +CONFIG_DUAL_PROPOSE_PROTOCOL=y diff --git a/tests/native_sim/ares_communication/src/main.c b/tests/native_sim/ares_communication/src/main.c new file mode 100644 index 0000000..ebf55d9 --- /dev/null +++ b/tests/native_sim/ares_communication/src/main.c @@ -0,0 +1,698 @@ +/* + * Copyright (c) 2026 ttwards + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +NET_BUF_POOL_DEFINE(fake_buf_pool, 48, 320, 0, NULL); + +struct fake_transport { + struct AresInterface *peer; + bool connected; + bool hold_callback; + bool fail_next_send; + bool deliver_as_bytes; + + int init_calls; + int send_calls; + int send_with_callback_calls; + int callback_calls; + int delivered_frames; + int last_callback_status; + + uint8_t last_frame[320]; + size_t last_len; + + struct net_buf *held_buf; + ares_interface_tx_done_cb_t held_cb; + void *held_user_data; +}; + +struct bind_observer { + int order; + int interface_init_order; + int protocol_init_order; + bool interface_saw_protocol; + bool protocol_saw_interface; +}; + +static struct bind_observer bind_obs; + +static void fake_transport_reset(struct fake_transport *transport) +{ + memset(transport, 0, sizeof(*transport)); +} + +static void fake_record_frame(struct fake_transport *transport, struct net_buf *buf) +{ + zassert_true(buf->len <= sizeof(transport->last_frame), "frame too large"); + memcpy(transport->last_frame, buf->data, buf->len); + transport->last_len = buf->len; +} + +static void fake_deliver_to_peer(struct fake_transport *transport, struct net_buf *buf) +{ + if (transport->peer == NULL || transport->peer->protocol == NULL) { + return; + } + + if (transport->deliver_as_bytes && transport->peer->protocol->api->handle_byte != NULL) { + for (size_t i = 0; i < buf->len; i++) { + transport->peer->protocol->api->handle_byte(transport->peer->protocol, + buf->data[i]); + } + } else if (transport->peer->protocol->api->handle != NULL) { + struct net_buf *rx = net_buf_alloc(&fake_buf_pool, K_NO_WAIT); + + zassert_not_null(rx, "failed to allocate RX clone"); + net_buf_add_mem(rx, buf->data, buf->len); + transport->peer->protocol->api->handle(transport->peer->protocol, rx); + net_buf_unref(rx); + } + + transport->delivered_frames++; +} + +static void fake_complete_held_tx(struct AresInterface *interface, int status) +{ + struct fake_transport *transport = interface->priv_data; + struct net_buf *buf = transport->held_buf; + ares_interface_tx_done_cb_t cb = transport->held_cb; + void *user_data = transport->held_user_data; + + zassert_not_null(buf, "no held TX buffer"); + transport->held_buf = NULL; + transport->held_cb = NULL; + transport->held_user_data = NULL; + + if (cb != NULL) { + cb(interface, buf, status, user_data); + transport->callback_calls++; + transport->last_callback_status = status; + } + + net_buf_unref(buf); +} + +static int fake_interface_init(struct AresInterface *interface) +{ + struct fake_transport *transport = interface->priv_data; + + transport->init_calls++; + transport->connected = true; + return 0; +} + +static int fake_send(struct AresInterface *interface, struct net_buf *buf) +{ + struct fake_transport *transport = interface->priv_data; + + transport->send_calls++; + fake_record_frame(transport, buf); + fake_deliver_to_peer(transport, buf); + net_buf_unref(buf); + return 0; +} + +static int fake_send_with_callback(struct AresInterface *interface, struct net_buf *buf, + ares_interface_tx_done_cb_t cb, void *user_data) +{ + struct fake_transport *transport = interface->priv_data; + + transport->send_with_callback_calls++; + fake_record_frame(transport, buf); + + if (transport->fail_next_send) { + transport->fail_next_send = false; + if (cb != NULL) { + cb(interface, buf, -EIO, user_data); + transport->callback_calls++; + transport->last_callback_status = -EIO; + } + net_buf_unref(buf); + return -EIO; + } + + fake_deliver_to_peer(transport, buf); + + if (transport->hold_callback) { + if (transport->held_buf != NULL) { + if (cb != NULL) { + cb(interface, buf, -EBUSY, user_data); + transport->callback_calls++; + transport->last_callback_status = -EBUSY; + } + net_buf_unref(buf); + return -EBUSY; + } + + transport->held_buf = buf; + transport->held_cb = cb; + transport->held_user_data = user_data; + return 0; + } + + if (cb != NULL) { + cb(interface, buf, 0, user_data); + transport->callback_calls++; + transport->last_callback_status = 0; + } + net_buf_unref(buf); + return 0; +} + +static struct net_buf *fake_alloc_buf(struct AresInterface *interface) +{ + ARG_UNUSED(interface); + + return net_buf_alloc(&fake_buf_pool, K_NO_WAIT); +} + +static struct net_buf *fake_alloc_buf_with_data(struct AresInterface *interface, void *data, + size_t size) +{ + struct net_buf *buf = fake_alloc_buf(interface); + + if (buf != NULL) { + net_buf_add_mem(buf, data, size); + } + + return buf; +} + +static bool fake_is_connected(struct AresInterface *interface) +{ + struct fake_transport *transport = interface->priv_data; + + return transport->connected; +} + +static uint32_t fake_caps(struct AresInterface *interface) +{ + ARG_UNUSED(interface); + + return ARES_INTERFACE_CAP_STREAM | ARES_INTERFACE_CAP_TX_COMPLETE | + ARES_INTERFACE_CAP_TX_QUEUE; +} + +static uint32_t fake_no_callback_caps(struct AresInterface *interface) +{ + ARG_UNUSED(interface); + + return ARES_INTERFACE_CAP_STREAM | ARES_INTERFACE_CAP_TX_QUEUE; +} + +static size_t fake_mtu(struct AresInterface *interface) +{ + ARG_UNUSED(interface); + + return sizeof(((struct fake_transport *)0)->last_frame); +} + +static const struct AresInterfaceAPI fake_interface_api = { + .send = fake_send, + .send_with_callback = fake_send_with_callback, + .is_connected = fake_is_connected, + .caps = fake_caps, + .mtu = fake_mtu, + .alloc_buf = fake_alloc_buf, + .alloc_buf_with_data = fake_alloc_buf_with_data, + .init = fake_interface_init, +}; + +static const struct AresInterfaceAPI fake_no_callback_api = { + .send = fake_send, + .is_connected = fake_is_connected, + .caps = fake_no_callback_caps, + .mtu = fake_mtu, + .alloc_buf = fake_alloc_buf, + .alloc_buf_with_data = fake_alloc_buf_with_data, + .init = fake_interface_init, +}; + +static int observed_interface_init(struct AresInterface *interface) +{ + bind_obs.interface_init_order = ++bind_obs.order; + bind_obs.interface_saw_protocol = interface->protocol != NULL; + return 0; +} + +static int observed_protocol_init(struct AresProtocol *protocol) +{ + bind_obs.protocol_init_order = ++bind_obs.order; + bind_obs.protocol_saw_interface = protocol->interface != NULL; + return 0; +} + +static const struct AresInterfaceAPI observed_interface_api = { + .init = observed_interface_init, +}; + +static const struct AresProtocolAPI observed_protocol_api = { + .init = observed_protocol_init, +}; + +DUAL_PROPOSE_PROTOCOL_DEFINE(sync_protocol); +DUAL_PROPOSE_PROTOCOL_DEFINE(no_callback_protocol); +DUAL_PROPOSE_PROTOCOL_DEFINE(client_protocol); +DUAL_PROPOSE_PROTOCOL_DEFINE(server_protocol); +DUAL_PROPOSE_PROTOCOL_DEFINE(disconnect_protocol); + +static bool sync_protocol_ready; +static bool no_callback_protocol_ready; +static bool client_protocol_ready; +static bool server_protocol_ready; +static bool disconnect_protocol_ready; + +static void reset_dual_protocol(struct AresProtocol *protocol, const char *name, bool *ready) +{ + struct dual_protocol_data *data = protocol->priv_data; + + if (*ready) { + k_timer_stop(&data->heart_beat_timer); + } + + memset(data, 0, sizeof(*data)); + data->name = name; + data->state = PARSER_STATE_IDLE; + data->current_frame_type = FRAME_TYPE_UNKNOWN; + protocol->interface = NULL; + *ready = false; +} + +static void bind_dual_online(struct AresInterface *interface, struct AresProtocol *protocol, + const char *name, bool *ready) +{ + struct dual_protocol_data *data = protocol->priv_data; + + reset_dual_protocol(protocol, name, ready); + zassert_ok(ares_bind_interface(interface, protocol), "bind failed"); + *ready = true; + k_timer_stop(&data->heart_beat_timer); + data->online = true; + data->last_receive = k_uptime_get_32(); + data->last_heart_beat = data->last_receive; +} + +static void put16(uint8_t *buf, size_t offset, uint16_t value) +{ + GET_16BITS(buf, offset) = value; +} + +static void put32(uint8_t *buf, size_t offset, uint32_t value) +{ + GET_32BITS(buf, offset) = value; +} + +static int direct_tx_callback_count; +static int direct_tx_callback_status; + +static void direct_tx_done(struct AresInterface *interface, struct net_buf *buf, int status, + void *user_data) +{ + ARG_UNUSED(interface); + ARG_UNUSED(buf); + + direct_tx_callback_count++; + direct_tx_callback_status = status; + *(int *)user_data = status; +} + +ZTEST(ares_communication, test_bind_initializes_interface_before_protocol) +{ + struct AresInterface interface = { + .name = "observed-interface", + .api = &observed_interface_api, + }; + struct AresProtocol protocol = { + .name = "observed-protocol", + .api = &observed_protocol_api, + }; + + memset(&bind_obs, 0, sizeof(bind_obs)); + + zassert_equal(ares_bind_interface(NULL, &protocol), -EINVAL); + zassert_equal(ares_bind_interface(&interface, NULL), -EINVAL); + zassert_ok(ares_bind_interface(&interface, &protocol)); + + zassert_equal(interface.protocol, &protocol); + zassert_equal(protocol.interface, &interface); + zassert_true(bind_obs.interface_saw_protocol); + zassert_true(bind_obs.protocol_saw_interface); + zassert_equal(bind_obs.interface_init_order, 1); + zassert_equal(bind_obs.protocol_init_order, 2); +} + +ZTEST(ares_communication, test_interface_send_with_callback_reports_success_and_failure) +{ + struct fake_transport transport; + struct AresInterface interface = { + .name = "direct-interface", + .api = &fake_interface_api, + .priv_data = &transport, + }; + int user_status = 123; + struct net_buf *buf; + + fake_transport_reset(&transport); + zassert_ok(interface.api->init(&interface)); + + direct_tx_callback_count = 0; + buf = interface.api->alloc_buf(&interface); + zassert_not_null(buf); + net_buf_add_mem(buf, "abc", 3); + zassert_ok( + interface.api->send_with_callback(&interface, buf, direct_tx_done, &user_status)); + zassert_equal(transport.send_with_callback_calls, 1); + zassert_equal(transport.callback_calls, 1); + zassert_equal(direct_tx_callback_count, 1); + zassert_equal(user_status, 0); + zassert_equal(direct_tx_callback_status, 0); + zassert_equal(transport.last_len, 3); + zassert_mem_equal(transport.last_frame, "abc", 3); + + transport.fail_next_send = true; + user_status = 123; + buf = interface.api->alloc_buf(&interface); + zassert_not_null(buf); + net_buf_add_mem(buf, "err", 3); + zassert_equal( + interface.api->send_with_callback(&interface, buf, direct_tx_done, &user_status), + -EIO); + zassert_equal(transport.callback_calls, 2); + zassert_equal(user_status, -EIO); + zassert_equal(direct_tx_callback_status, -EIO); +} + +static int sync_status_mask; +static int sync_status_count; + +static void sync_status_cb(int status) +{ + sync_status_mask |= status; + sync_status_count++; +} + +ZTEST(ares_communication, test_dual_sync_flush_uses_tx_done_to_release_in_flight) +{ + struct fake_transport transport; + struct AresInterface interface = { + .name = "sync-interface", + .api = &fake_interface_api, + .priv_data = &transport, + }; + uint8_t payload[] = {0x10, 0x20, 0x30}; + sync_table_t *pack; + + fake_transport_reset(&transport); + transport.hold_callback = true; + bind_dual_online(&interface, &sync_protocol, "sync_protocol", &sync_protocol_ready); + + sync_status_mask = 0; + sync_status_count = 0; + pack = dual_sync_add(&sync_protocol, 0x3344, payload, sizeof(payload), sync_status_cb); + zassert_not_null(pack); + zassert_equal(sync_status_mask, SYNC_PACK_STATUS_READ); + zassert_equal(transport.send_with_callback_calls, 1); + zassert_not_null(transport.held_buf); + zassert_true(atomic_test_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT)); + zassert_equal(dual_sync_flush(&sync_protocol, pack), -EBUSY); + + fake_complete_held_tx(&interface, 0); + zassert_false(atomic_test_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT)); + + payload[0] = 0x99; + zassert_ok(dual_sync_flush(&sync_protocol, pack)); + zassert_not_null(transport.held_buf); + zassert_equal(GET_16BITS(transport.last_frame, SYNC_HEAD_IDX), SYNC_FRAME_HEAD); + zassert_equal(GET_16BITS(transport.last_frame, SYNC_ID_IDX), 0x3344); + zassert_equal(transport.last_frame[SYNC_DATA_IDX], 0x99); + fake_complete_held_tx(&interface, 0); +} + +ZTEST(ares_communication, test_dual_sync_requires_callback_capable_interface) +{ + struct fake_transport transport; + struct AresInterface interface = { + .name = "no-callback-interface", + .api = &fake_no_callback_api, + .priv_data = &transport, + }; + uint8_t payload[] = {0x01, 0x02}; + sync_table_t *pack; + + fake_transport_reset(&transport); + bind_dual_online(&interface, &no_callback_protocol, "no_callback_protocol", + &no_callback_protocol_ready); + + pack = dual_sync_add(&no_callback_protocol, 0x1122, payload, sizeof(payload), NULL); + zassert_not_null(pack); + zassert_equal(dual_sync_flush(&no_callback_protocol, pack), -EBUSY); + zassert_false(atomic_test_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT)); + zassert_equal(transport.send_calls, 0); +} + +ZTEST(ares_communication, test_dual_sync_receive_updates_registered_pack) +{ + struct fake_transport transport; + struct AresInterface interface = { + .name = "sync-rx-interface", + .api = &fake_interface_api, + .priv_data = &transport, + }; + uint8_t payload[] = {0xaa, 0xbb, 0xcc}; + uint8_t frame[SYNC_FRAME_LENGTH_OFFSET + sizeof(payload)]; + sync_table_t *pack; + + fake_transport_reset(&transport); + bind_dual_online(&interface, &sync_protocol, "sync_protocol", &sync_protocol_ready); + + pack = dual_sync_add(&sync_protocol, 0x5566, payload, sizeof(payload), sync_status_cb); + zassert_not_null(pack); + + sync_status_mask = 0; + sync_status_count = 0; + put16(frame, SYNC_HEAD_IDX, SYNC_FRAME_HEAD); + put16(frame, SYNC_ID_IDX, 0x5566); + frame[SYNC_DATA_IDX] = 0x01; + frame[SYNC_DATA_IDX + 1] = 0x02; + frame[SYNC_DATA_IDX + 2] = 0x03; + + for (size_t i = 0; i < sizeof(frame); i++) { + sync_protocol.api->handle_byte(&sync_protocol, frame[i]); + } + + zassert_mem_equal(payload, ((uint8_t[]){0x01, 0x02, 0x03}), sizeof(payload)); + zassert_true((sync_status_mask & SYNC_PACK_STATUS_WRITE) != 0); + zassert_true((sync_status_mask & SYNC_PACK_STATUS_DONE) != 0); + zassert_equal(sync_status_count, 2); +} + +static uint32_t server_func_arg1; +static uint32_t server_func_arg2; +static uint32_t server_func_arg3; + +static uint32_t server_func_cb(uint32_t arg1, uint32_t arg2, uint32_t arg3) +{ + server_func_arg1 = arg1; + server_func_arg2 = arg2; + server_func_arg3 = arg3; + return arg1 + arg2 + arg3; +} + +static uint16_t client_ret_id; +static uint16_t client_ret_req_id; +static uint32_t client_ret_value; +static int client_ret_calls; + +static void client_ret_cb(uint16_t id, uint16_t req_id, uint32_t ret) +{ + client_ret_id = id; + client_ret_req_id = req_id; + client_ret_value = ret; + client_ret_calls++; +} + +ZTEST(ares_communication, test_dual_func_request_reply_and_repl_callback) +{ + struct fake_transport client_transport; + struct fake_transport server_transport; + struct AresInterface client_interface = { + .name = "client-interface", + .api = &fake_interface_api, + .priv_data = &client_transport, + }; + struct AresInterface server_interface = { + .name = "server-interface", + .api = &fake_interface_api, + .priv_data = &server_transport, + }; + const uint16_t func_id = 0x1234; + const uint16_t req_id = 0x4a31; + uint8_t func_frame[FUNC_FRAME_LENGTH]; + + fake_transport_reset(&client_transport); + fake_transport_reset(&server_transport); + client_transport.peer = &server_interface; + client_transport.deliver_as_bytes = true; + server_transport.peer = &client_interface; + server_transport.deliver_as_bytes = true; + server_transport.hold_callback = true; + + bind_dual_online(&client_interface, &client_protocol, "client_protocol", + &client_protocol_ready); + bind_dual_online(&server_interface, &server_protocol, "server_protocol", + &server_protocol_ready); + + server_func_arg1 = 0; + server_func_arg2 = 0; + server_func_arg3 = 0; + client_ret_calls = 0; + zassert_ok(dual_ret_cb_set(&client_protocol, client_ret_cb)); + dual_func_add(&server_protocol, func_id, server_func_cb); + + put16(func_frame, FUNC_HEAD_IDX, FUNC_FRAME_HEAD); + put16(func_frame, FUNC_ID_IDX, func_id); + put32(func_frame, FUNC_ARG1_IDX, 7); + put32(func_frame, FUNC_ARG2_IDX, 11); + put32(func_frame, FUNC_ARG3_IDX, 13); + put16(func_frame, FUNC_REQ_IDX, req_id); + + for (size_t i = 0; i < sizeof(func_frame); i++) { + server_protocol.api->handle_byte(&server_protocol, func_frame[i]); + } + + zassert_equal(server_func_arg1, 7); + zassert_equal(server_func_arg2, 11); + zassert_equal(server_func_arg3, 13); + zassert_equal(server_transport.send_with_callback_calls, 1); + zassert_not_null(server_transport.held_buf); + zassert_true( + atomic_test_bit(&server_protocol_data.func_table[0].tx_state, DUAL_TX_IN_FLIGHT)); + + zassert_equal(client_ret_calls, 1); + zassert_equal(client_ret_id, func_id); + zassert_equal(client_ret_req_id, req_id); + zassert_equal(client_ret_value, 31); + zassert_equal(GET_16BITS(server_transport.last_frame, REPL_REQ_ID_IDX), req_id); + + fake_complete_held_tx(&server_interface, 0); + zassert_false( + atomic_test_bit(&server_protocol_data.func_table[0].tx_state, DUAL_TX_IN_FLIGHT)); +} + +ZTEST(ares_communication, test_dual_func_call_serializes_and_delivers_request) +{ + struct fake_transport client_transport; + struct fake_transport server_transport; + struct AresInterface client_interface = { + .name = "client-interface", + .api = &fake_interface_api, + .priv_data = &client_transport, + }; + struct AresInterface server_interface = { + .name = "server-interface", + .api = &fake_interface_api, + .priv_data = &server_transport, + }; + int req_id; + + fake_transport_reset(&client_transport); + fake_transport_reset(&server_transport); + client_transport.peer = &server_interface; + server_transport.peer = &client_interface; + + bind_dual_online(&client_interface, &client_protocol, "client_protocol", + &client_protocol_ready); + bind_dual_online(&server_interface, &server_protocol, "server_protocol", + &server_protocol_ready); + + dual_func_add(&server_protocol, 0x2233, server_func_cb); + req_id = dual_func_call(&client_protocol, 0x2233, 3, 4, 5); + + zassert_true(req_id >= 0); + zassert_equal(client_transport.send_calls, 1); + zassert_equal(server_func_arg1, 3); + zassert_equal(server_func_arg2, 4); + zassert_equal(server_func_arg3, 5); + zassert_equal(server_transport.send_with_callback_calls, 1); + zassert_equal(GET_16BITS(client_transport.last_frame, FUNC_HEAD_IDX), FUNC_FRAME_HEAD); + zassert_equal(GET_16BITS(client_transport.last_frame, FUNC_ID_IDX), 0x2233); +} + +ZTEST(ares_communication, test_disconnect_event_clears_sync_tx_in_flight) +{ + struct fake_transport transport; + struct AresInterface interface = { + .name = "sync-disconnect-interface", + .api = &fake_interface_api, + .priv_data = &transport, + }; + uint8_t payload[] = {0xde, 0xad}; + sync_table_t *pack; + + fake_transport_reset(&transport); + transport.hold_callback = true; + bind_dual_online(&interface, &sync_protocol, "sync_protocol", &sync_protocol_ready); + + pack = dual_sync_add(&sync_protocol, 0x8899, payload, sizeof(payload), NULL); + zassert_not_null(pack); + zassert_true(atomic_test_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT)); + + sync_protocol.api->event(&sync_protocol, ARES_PROTOCOL_EVENT_DISCONNECTED); + zassert_false(sync_protocol_data.online); + zassert_false(atomic_test_bit(&pack->tx_state, DUAL_TX_IN_FLIGHT)); + + fake_complete_held_tx(&interface, 0); +} + +ZTEST(ares_communication, test_disconnect_event_clears_func_tx_in_flight) +{ + struct fake_transport transport; + struct AresInterface interface = { + .name = "disconnect-interface", + .api = &fake_interface_api, + .priv_data = &transport, + }; + const uint16_t func_id = 0x7777; + uint8_t func_frame[FUNC_FRAME_LENGTH]; + + fake_transport_reset(&transport); + transport.hold_callback = true; + bind_dual_online(&interface, &disconnect_protocol, "disconnect_protocol", + &disconnect_protocol_ready); + + dual_func_add(&disconnect_protocol, func_id, server_func_cb); + put16(func_frame, FUNC_HEAD_IDX, FUNC_FRAME_HEAD); + put16(func_frame, FUNC_ID_IDX, func_id); + put32(func_frame, FUNC_ARG1_IDX, 1); + put32(func_frame, FUNC_ARG2_IDX, 2); + put32(func_frame, FUNC_ARG3_IDX, 3); + put16(func_frame, FUNC_REQ_IDX, 0x0102); + + for (size_t i = 0; i < sizeof(func_frame); i++) { + disconnect_protocol.api->handle_byte(&disconnect_protocol, func_frame[i]); + } + + zassert_true(atomic_test_bit(&disconnect_protocol_data.func_table[0].tx_state, + DUAL_TX_IN_FLIGHT)); + disconnect_protocol.api->event(&disconnect_protocol, ARES_PROTOCOL_EVENT_DISCONNECTED); + zassert_false(disconnect_protocol_data.online); + zassert_false(atomic_test_bit(&disconnect_protocol_data.func_table[0].tx_state, + DUAL_TX_IN_FLIGHT)); + + fake_complete_held_tx(&interface, 0); +} + +ZTEST_SUITE(ares_communication, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/native_sim/ares_communication/testcase.yaml b/tests/native_sim/ares_communication/testcase.yaml new file mode 100644 index 0000000..ccb468c --- /dev/null +++ b/tests/native_sim/ares_communication/testcase.yaml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +tests: + ares.native_sim.communication: + tags: + - ares + - native_sim + - communication + platform_allow: + - native_sim + - native_sim/native/64 + integration_platforms: + - native_sim + - native_sim/native/64