Skip to content

[platform] Add RTL87X2G platform port with RCP supportDev#193

Open
yushuo-ko wants to merge 21 commits into
openthread:mainfrom
yushuo-ko:dev
Open

[platform] Add RTL87X2G platform port with RCP supportDev#193
yushuo-ko wants to merge 21 commits into
openthread:mainfrom
yushuo-ko:dev

Conversation

@yushuo-ko
Copy link
Copy Markdown
Contributor

Summary

This PR adds the OpenThread platform port for the Realtek RTL87X2G SoC family
(RTL8777G EVB and RTL8771GUV/GTV USB dongles), along with a number of bug fixes
identified during code review.

Platform support

  • Rename internal bee4 platform to rtl87x2g and bee3plus to rtl8752h
    to align with public chip naming conventions
  • Add ot-rcp build target for RTL8771GUV and RTL8771GTV USB dongles
    (RCP image is also compatible with RTL8777G EVB)
  • Guard internal-only sources (CFU, rtk_sign) with if(EXISTS) so the public
    SDK builds cleanly without them

Bug fixes

  • arm-none-eabi.cmake: replace -march=armv8.1-m.main+dsp+mve+fp (Cortex-M55
    only) with -mcpu=cortex-m33; the incorrect flag risks generating unsupported
    MVE instructions causing HardFault
  • app.ld (rtl8771guv/gtv): fix ER_IRAM_NS section to reference
    *libopenthread-rtl87x2g.a instead of the stale *libopenthread-bee4.a /
    *libbee4-patch.a names
  • radio.c: restore volatile qualifier on tx_backoff_tmo, which is written
    from BT timer interrupt context and read in task context
  • zb_main.c: fix __wrap__realloc_r buffer over-read when newsize exceeds
    the original allocation
  • uart.c: add missing return OT_ERROR_NONE in otPlatUartSend (BUILD_NCP
    path), fixing undefined behavior and -Werror=return-type build failure
  • script/build: split concatenated CMake -D options into separate array
    elements so they are not silently ignored
  • example_vendor_hook.cpp: replace sprintf with snprintf

Build

  • Fix build script paths for public SDK layout (vendor/src/,
    Realtek/script/)
  • Update rtl87x2g_sdk submodule with board-specific libraries for
    RTL8777G, RTL8771GUV, and RTL8771GTV

yushuo-ko added 18 commits May 26, 2026 14:56
- Update rtl87x2g_sdk submodule to origin/main (41de116), which includes
  the official bee4->rtl87x2g rename, Matter BLE/color control updates,
  and CAN/ADC driver lib updates
- Remove third_party/Realtek/bee4/ headers that are no longer referenced
The ot-rcp target failed to build against the public rtl87x2g_sdk submodule
due to references to internal SDK symbols.  Fix all build errors:

- Disable FEATURE_SUPPORT_CFU in openthread-core-rtl8777g-rcp-config.h;
  cfu.h is not present in the public rtl87x2g_sdk submodule.
- Disable FEATURE_SUPPORT_RTK_SIGN in openthread-core-rtl8777g-rcp-config.h;
  the RTK sign algorithm is a private security component not distributed
  in the public SDK.
- Fix format specifier mismatch in example_vendor_hook.cpp: use %u/%X for
  uint32_t values (vid/pid and img_sub_version bitfields).
- Remove mac_read_reg/mac_write_reg calls from example_vendor_hook.cpp and
  their declarations from vendor_hook.h; these internal debug register
  functions were already removed from misc.c and are not in the public SDK.
- Remove config_param runtime configuration references from
  example_vendor_hook.cpp (SPINEL_PROP_VENDOR_RTK_CONFIG_READ/WRITE) and
  vendor_hook.h; config_param and related functions (rtk_write_config_param,
  rtk_enable_flow_control, mac_LoadConfigParam) are internal SDK symbols
  not available in the public submodule.
- Replace config_param-based dynamic pin configuration in zb_main.c with
  static compile-time pin definitions (ZB_CLI_UART_TX_PIN/RX_PIN) for RCP
  builds, consistent with non-RCP builds that already use static pins.
arm-none-eabi-gcc defines uint32_t as 'unsigned long' while cppcheck
treats it as 'unsigned int', causing conflicting format warnings.

Use explicit (unsigned int) casts with %u/%X to satisfy both tools:
- VID/PID display: cast uint32_t vid/pid to (unsigned int) with %X
- img_sub_version bitfields: cast each field to (unsigned int) with %u
Changes:
- script/build: add apply_patch idempotent helper, replace bare git apply calls
- script/openthread.cmake, sdk.cmake: extend platform and build type support
- src/alarm.c: add BSD-3 license header, document crystal accuracy
- src/radio.c: standardize initialization flow
- src/rtl87x2g/cmake: add BSD-3 license headers, extend mbedtls/peripheral config
- src/rtl87x2g/openthread-core-rtl87x2g-config.h: document CSL_RECEIVE_TIME_AHEAD rationale
- src/rtl87x2g/uart.c: add uart support for usb_cdc_driver_data_pipe_send
- src/rtl87x2g/zb_main.c: update zigbee main task handling
- src/rtl87x2g/misc.c: new file, mac register access helpers
- src/rtl87x2g/arm-none-eabi.cmake, CMakeLists.txt: compiler and build updates
Correct Gerrit-specific paths that were not converted during sync:

- script/build: fix pre_build/post_build calls (Realtek/ -> script/),
  toolchain file reference (vendor/ -> src/), and REALTEK_SDK_PATH
  (../../ -> third_party/Realtek/rtl87x2g_sdk)
- script/sdk.cmake: remove trailing .. from REALTEK_SDK_ROOT
- cmake/rtl87x2g-mbedtls.cmake: fix MBEDTLS_REPO/PORT/COMMON_CONFIG
  paths (../../../mbedtls/ -> rtl87x2g_sdk/subsys/mbedtls/)
- cmake/openthread-rtl87x2g.cmake: link against pre-built
  librtl87x2g-internal.a from SDK instead of cmake target
- cmake/rtl87x2g-init.cmake: add missing file (was blocked by
  NEEDS_REVIEW filter); remove internal/cfu include path
CFU (subsys/cfu/) and rtk_sign (internal/rtk_sign/) source files are not
present in the public rtl87x2g_sdk submodule.  Wrap their cmake blocks with
if(EXISTS ...) so the build skips them when building against the public SDK;
internal builds retain full functionality unchanged.

main_ns.c is unconditionally added for ot-rcp (it was previously inside the
CFU block).
- Add rtl8771guv and rtl8771gtv board config files (flash_map.h,
  mem_config.h, iot_persistent_config.h, app.ld) for RCP builds
- Disable FEATURE_SUPPORT_CFU and FEATURE_SUPPORT_RTK_SIGN in
  rtl8771guv/rtl8771gtv RCP configs (internal-only features)
- Use board-specific librtl87x2g-internal.a via cmake if(EXISTS),
  falling back to the generic lib for unknown targets
- Bump rtl87x2g_sdk with board-specific libraries and mbedtls fixes
- README: document ot-rcp build command for rtl8771guv; note that the
  image is compatible with RTL8777G EVB
- build.yml: add 'Build RCP' step to CI for rtl8771guv alongside
  the existing 'Build CLI' step
- Replace system firmware binaries with v1.0.183 release:
  BANK0/BANK1 boot patch, sys patch, BT stack patch, BT host
- Add new configFile (2025.11.18.19) and bt_host binary
- Update mptoolconfig.json with correct flash addresses per
  flash map layout
- Update README Flash Binaries section with full address table

App image (ot-cli-ftd) placeholder left for user to fill in.
zb_main.c: fix __wrap__realloc_r to avoid buffer over-read
  The previous implementation allocated newsize bytes then copied newsize
  bytes from the original pointer, causing a buffer over-read when the
  new size exceeds the original allocation size.
  Since the original allocation size is not tracked, return NULL for
  resize attempts (mem != NULL, newsize != 0) rather than risk memory
  corruption or information leakage.  realloc(NULL, size) and
  realloc(ptr, 0) cases are handled correctly.

misc.c: remove mac_read_reg and mac_write_reg
  These debug register access functions allow arbitrary memory reads and
  writes, posing a security risk.  Their declarations were removed from
  vendor_hook.h and calls removed from example_vendor_hook.cpp in a
  previous commit; remove the definitions here to complete the cleanup.
Update EXCLUDE_FILE directives in app.ld for rtl8771guv and rtl8771gtv:
- Remove libbee4-patch.a reference (library no longer exists)
- Rename libopenthread-bee4.a to libopenthread-rtl87x2g.a to match the
  current build output

Without this fix, the EXCLUDE_FILE for the OpenThread RCP libraries is
ineffective, causing their .text/.rodata to land in the wrong section.
tx_backoff_tmo is written from btcmp*_handler (BT timer interrupt
callbacks registered via mpan_RegisterTimer / APP_RAM_TEXT_SECTION)
and read in BEE_RadioBackoffTimeout (task context).  The volatile
qualifier was accidentally dropped when ce9f4d3 re-introduced the
variable; without it the compiler may cache the value and miss
interrupt-driven updates, breaking the TX backoff state machine.
- arm-none-eabi.cmake: replace -march=armv8.1-m.main+dsp+mve+fp with
  -mcpu=cortex-m33; ARMv8.1-M/MVE is Cortex-M55 only, not M33
- rtl8771guv/gtv app.ld: fix ER_IRAM_NS section to use
  *libopenthread-rtl87x2g.a (FLASH_TEXT was already corrected)
- example_vendor_hook.cpp: replace sprintf with snprintf(str, sizeof(str))
- radio.c: extract magic 10000 us sleep threshold into MIN_SLEEP_US
uart.c: add return OT_ERROR_NONE after mutex_give in BUILD_NCP
  otPlatUartSend had no return on the usb_uart_ready path, causing
  undefined behavior and a -Werror=return-type build failure

script/build: split concatenated cmake -D options into separate array
  elements; CMake treats the entire string as one argument when multiple
  -D flags are passed as a single string, silently ignoring the extras
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the platform codebase by renaming the bee4 platform to rtl87x2g and bee3plus to rtl8752h to align with public chip naming conventions. It also fixes several RCP build errors against the public SDK submodule, updates the build system and CMake configurations, and introduces support for NCP and OTBR builds. The review feedback highlights critical concurrency and deadlock issues in the USB UART callback where a mutex is taken and a busy-wait loop is executed in an ISR context. Additionally, there are concerns regarding unsafe NULL pointers passed to otTaskletsSignalPending in reset functions, commented-out code, and incomplete reset reason and host-wake implementations.

Comment thread src/rtl87x2g/uart.c Outdated
Comment on lines +375 to +395
os_mutex_take(uart_tx_mutex, 0xffffffff);
if (sRemainLength > out_mtu_size)
{
mac_memcpy(usb_uart_tx_buf, &sTransmitBuffer[sTransmitLength], out_mtu_size);
do
{
ret = usb_cdc_driver_data_pipe_send(cdc_in_handle, usb_uart_tx_buf, out_mtu_size);
}
while (ret > 0);
}
else
{
mac_memcpy(usb_uart_tx_buf, &sTransmitBuffer[sTransmitLength], sRemainLength);
do
{
ret = usb_cdc_driver_data_pipe_send(cdc_in_handle, usb_uart_tx_buf, sRemainLength);
}
while (ret > 0);
}
os_mutex_give(uart_tx_mutex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Taking a mutex (os_mutex_take) with a block time from a USB callback (CdcSendDataCb), which typically executes in an interrupt service routine (ISR) context, is a critical bug. In FreeRTOS, mutexes cannot be used in ISRs because they support priority inheritance and can block, which will trigger an assertion or cause a system crash.

Since OpenThread's UART API guarantees that otPlatUartSend is not called again until otPlatUartSendDone is invoked, there is no concurrent access to the transmit buffer from the task context during an active transmission. Therefore, the mutex is unnecessary in CdcSendDataCb. If mutual exclusion is required for other reasons, a critical section or basic semaphore (without blocking) should be used instead of a mutex.

Comment thread src/rtl87x2g/misc.c
Comment thread src/rtl87x2g/misc.c
Comment thread src/rtl87x2g/uart.c Outdated
Comment on lines +379 to +383
do
{
ret = usb_cdc_driver_data_pipe_send(cdc_in_handle, usb_uart_tx_buf, out_mtu_size);
}
while (ret > 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Spinning in a do-while loop while usb_cdc_driver_data_pipe_send returns a positive value (e.g., when the pipe is full or busy) inside CdcSendDataCb (which runs in USB interrupt/callback context) can lead to a deadlock. The USB driver may need the interrupt context to clear to process the previous transfer and free up buffer space, but the CPU is stuck spinning in the callback, preventing any progress.

Instead of busy-waiting, the driver should handle flow control or buffer full states asynchronously, or at least avoid spinning infinitely in interrupt/callback context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concern is noted, but in practice usb_cdc_driver_data_pipe_send (which wraps usb_pipe_send in the Realtek USB HAL) only returns 0 on success or a negative error code on failure — it never returns a positive value. Therefore the while (ret > 0) condition is always false and the loop body executes exactly once per call, with no spinning. The do-while structure is a defensive pattern inherited from the original driver and carries no runtime risk. We will leave it in place to avoid unnecessary churn, but can refactor it to a plain call in a follow-up if preferred.

Comment thread src/rtl87x2g/thread_task.c Outdated
Comment on lines +95 to +99
//#if BUILD_NCP
//#else
mbedtls_threading_alt_init();
crypto_hw_locks_init();
//#endif
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Commented-out code should be removed to keep the codebase clean and maintainable.

    mbedtls_threading_alt_init();
    crypto_hw_locks_init();

Comment thread src/rtl87x2g/misc.c
Comment thread src/rtl87x2g/misc.c
yushuo-ko added 3 commits May 26, 2026 16:12
uart.c: remove mutex from CdcSendDataCb (BUILD_NCP); the callback
  runs in USB ISR context where os_mutex_take is not safe; per
  OpenThread API contract otPlatUartSend is not re-entered before
  otPlatUartSendDone so no mutual exclusion is needed

misc.c: pass aInstance to otTaskletsSignalPending instead of NULL
  in both __wrap_otInstanceResetRadioStack and otPlatReset; NULL is
  only safe in single-instance builds

thread_task.c: remove dead commented-out #if BUILD_NCP guards
zb_main.c: fix __wrap__realloc_r resize path; previous fix returned
NULL for resize (non-NULL mem), breaking Matter pb_decode; use
FreeRTOS BlockLink_t header (xBlockSize at mem[-1] bits[27:0])
to determine old size and copy min(old_data, newsize) bytes safely
Remove local system_rtl.c and the four __wrap__malloc_r/__wrap__free_r/
__wrap__realloc_r/__wrap__calloc_r wrappers from zb_main.c. The SDK
(rtl87x2g_sdk 708600d) now provides these via its updated system_rtl.c,
which includes freertos_realloc() for correct resize behavior needed by
Matter protobuf decoding. Update cmake files to reference the SDK copy.
@yushuo-ko
Copy link
Copy Markdown
Contributor Author

@jwhui Hi, this PR is ready for review. Please take a look when you get a chance. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant