feat(cubered): re-use device::Serial class in primary

Drops the duplicated ArchPX4IOSerial register-level + custom-DMA
transport in favour of device::Serial over NuttX's lower-half UART
driver (DMA-backed via CONFIG_UART7_RXDMA/TXDMA=y). Removes the
~600-line parallel transport stack that had to be kept in sync with
src/drivers/px4io/.

NuttX file descriptors aren't shared across task contexts, so the
fd opened during the bridge's init() (caller task) cannot be used
from Run() (work queue task). The interface releases the fd at the
end of init() and the work queue lazy-opens its own on first use.

Read requests now transmit PKT_SIZE bytes (header + count reg slots
of padding) so the secondary's CRC accounting matches the response
shape — the previous header-only request was rejected by the secondary's
PKT_SIZE/CRC validation.

Renames the internal class and exposed factory/release symbols and
the board macros to drop the misleading PX4IO prefix; the file header
points to the original source. Protocol-level PX4IO_* constants stay
since this driver speaks the PX4IO register protocol.

Signed-off-by: Julian Oes <julian@oes.ch>
This commit is contained in:
Julian Oes
2026-05-04 14:08:30 +12:00
parent d437188333
commit e08064f826
6 changed files with 212 additions and 119 deletions
@@ -291,6 +291,8 @@ CONFIG_UART4_SERIAL_CONSOLE=y
CONFIG_UART7_BAUD=57600
CONFIG_UART7_RXBUFSIZE=600
CONFIG_UART7_TXBUFSIZE=1500
CONFIG_UART7_RXDMA=y
CONFIG_UART7_TXDMA=y
CONFIG_UART8_BAUD=57600
CONFIG_UART8_RXBUFSIZE=600
CONFIG_UART8_TXBUFSIZE=1500
@@ -54,20 +54,10 @@
#endif
/* PX4IO connection configuration */
/* CubeRed bridge link to the secondary MCU (UART7) */
#define BOARD_USES_PX4IO_VERSION 2
#define PX4IO_SERIAL_DEVICE "/dev/ttyS4"
#define PX4IO_SERIAL_TX_GPIO GPIO_UART7_TX
#define PX4IO_SERIAL_RX_GPIO GPIO_UART7_RX
#define PX4IO_SERIAL_BASE STM32_UART7_BASE
#define PX4IO_SERIAL_VECTOR STM32_IRQ_UART7
#define PX4IO_SERIAL_TX_DMAMAP DMAMAP_UART7_TX
#define PX4IO_SERIAL_RX_DMAMAP DMAMAP_UART7_RX
#define PX4IO_SERIAL_RCC_REG STM32_RCC_APB1ENR
#define PX4IO_SERIAL_RCC_EN RCC_APB1LENR_UART7EN
#define PX4IO_SERIAL_CLOCK STM32_PCLK1_FREQUENCY
#define PX4IO_SERIAL_BITRATE 1500000 /* 1.5Mbps -> max rate for IO */
#define PX4IO_SERIAL_SWAPPED
#define CUBERED_BRIDGE_PRIMARY_DEVICE "/dev/ttyS4"
#define CUBERED_BRIDGE_PRIMARY_BITRATE 1500000 /* 1.5 Mbps */
/* LEDs */
@@ -42,7 +42,6 @@ px4_add_module(
module.yaml
cubered_bridge_primary_params.yaml
DEPENDS
arch_px4io_serial
button_publisher
circuit_breaker
mixer_module
@@ -342,7 +342,7 @@ ModuleBase::Descriptor CuberedBridgePrimary::desc{task_spawn, custom_command, pr
CuberedBridgePrimary::CuberedBridgePrimary(device::Device *interface) :
CDev(PX4IO_DEVICE_PATH),
OutputModuleInterface(MODULE_NAME, px4::serial_port_to_wq(PX4IO_SERIAL_DEVICE)),
OutputModuleInterface(MODULE_NAME, px4::serial_port_to_wq(CUBERED_BRIDGE_PRIMARY_DEVICE)),
_interface(interface)
{
_mixing_output.setLowrateSchedulingInterval(20_ms);
@@ -492,6 +492,11 @@ int CuberedBridgePrimary::init()
update_params();
// Release the serial fd opened in this task context. Run() will re-open
// it in the work queue's task context — NuttX file descriptors aren't
// shared across tasks.
cubered_bridge_primary_release(_interface);
ScheduleNow();
return OK;
@@ -1394,7 +1399,7 @@ int CuberedBridgePrimary::ioctl(file *filep, int cmd, unsigned long arg)
static device::Device *get_interface()
{
device::Device *interface = PX4IO_serial_interface();
device::Device *interface = cubered_bridge_primary_interface();
if (interface != nullptr) {
if (interface->init() != OK) {
@@ -41,8 +41,13 @@
#include <board_config.h>
#ifdef PX4IO_SERIAL_BASE
#include <drivers/device/device.h>
device::Device *PX4IO_serial_interface();
#endif
device::Device *cubered_bridge_primary_interface();
/**
* Close the serial fd. Must be called once after the bridge's instance->init()
* completes (and before Run() starts) so the work queue can re-open the fd in
* its own task context — NuttX file descriptors aren't shared across tasks.
*/
void cubered_bridge_primary_release(device::Device *interface);
@@ -1,6 +1,6 @@
/****************************************************************************
*
* Copyright (c) 2013-2015 PX4 Development Team. All rights reserved.
* Copyright (c) 2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -32,41 +32,74 @@
****************************************************************************/
/**
* @file px4io_serial.cpp
* @file cubered_bridge_primary_serial.cpp
*
* Serial interface for PX4IO
* Serial transport for the CubeRed primary bridge. Talks to the
* secondary MCU over UART7 using device::Serial (NuttX lower-half
* driver, DMA-backed via CONFIG_UART7_RXDMA/TXDMA=y).
*
* Originally derived from src/drivers/px4io/px4io_serial.cpp.
*/
#include "cubered_bridge_primary_driver.h"
#include <px4_arch/px4io_serial.h>
#include <drivers/device/device.h>
#include <lib/perf/perf_counter.h>
#include <modules/px4iofirmware/protocol.h>
#include <px4_platform_common/Serial.hpp>
#include <px4_platform_common/log.h>
#include <px4_platform_common/sem.h>
static PX4IO_serial *g_interface;
#include <string.h>
device::Device
*PX4IO_serial_interface()
namespace
{
return new ArchPX4IOSerial();
class CuberedSerial : public device::Device
{
public:
CuberedSerial();
~CuberedSerial() override;
int init() override;
int read(unsigned address, void *data, unsigned count) override;
int write(unsigned address, void *data, unsigned count) override;
private:
static constexpr uint32_t TRANSACTION_TIMEOUT_MS = 10;
static constexpr unsigned MAX_RETRIES = 3;
int ensure_open();
int exchange(size_t request_size, size_t expected_response_size);
public:
void release();
private:
device::Serial _uart{};
IOPacket _io_buffer{};
px4_sem_t _bus_semaphore;
perf_counter_t _pc_txns{perf_alloc(PC_ELAPSED, MODULE_NAME": txns")};
perf_counter_t _pc_retries{perf_alloc(PC_COUNT, MODULE_NAME": retries")};
perf_counter_t _pc_timeouts{perf_alloc(PC_COUNT, MODULE_NAME": timeouts")};
perf_counter_t _pc_crcerrs{perf_alloc(PC_COUNT, MODULE_NAME": crcerrs")};
perf_counter_t _pc_protoerrs{perf_alloc(PC_COUNT, MODULE_NAME": protoerrs")};
};
CuberedSerial::CuberedSerial() :
Device("cubered_bridge_primary")
{
px4_sem_init(&_bus_semaphore, 0, 1);
}
PX4IO_serial::PX4IO_serial() :
Device("PX4IO_serial"),
_pc_txns(perf_alloc(PC_ELAPSED, MODULE_NAME": txns")),
_pc_retries(perf_alloc(PC_COUNT, MODULE_NAME": retries")),
_pc_timeouts(perf_alloc(PC_COUNT, MODULE_NAME": timeouts")),
_pc_crcerrs(perf_alloc(PC_COUNT, MODULE_NAME": crcerrs")),
_pc_protoerrs(perf_alloc(PC_COUNT, MODULE_NAME": protoerrs")),
_pc_uerrs(perf_alloc(PC_COUNT, MODULE_NAME": uarterrs")),
_pc_idle(perf_alloc(PC_COUNT, MODULE_NAME": idle")),
_pc_badidle(perf_alloc(PC_COUNT, MODULE_NAME": badidle")),
_bus_semaphore(SEM_INITIALIZER(0))
CuberedSerial::~CuberedSerial()
{
g_interface = this;
}
if (_uart.isOpen()) {
_uart.close();
}
PX4IO_serial::~PX4IO_serial()
{
/* kill our semaphores */
px4_sem_destroy(&_bus_semaphore);
perf_free(_pc_txns);
@@ -74,31 +107,87 @@ PX4IO_serial::~PX4IO_serial()
perf_free(_pc_timeouts);
perf_free(_pc_crcerrs);
perf_free(_pc_protoerrs);
perf_free(_pc_uerrs);
perf_free(_pc_idle);
perf_free(_pc_badidle);
if (g_interface == this) {
g_interface = nullptr;
}
}
int
PX4IO_serial::init(IOPacket *io_buffer)
int CuberedSerial::init()
{
_io_buffer_ptr = io_buffer;
/* create semaphores */
// in case the sub-class impl fails, the semaphore is cleaned up by destructor.
px4_sem_init(&_bus_semaphore, 0, 1);
if (!_uart.setPort(CUBERED_BRIDGE_PRIMARY_DEVICE)) {
PX4_ERR("setPort %s failed", CUBERED_BRIDGE_PRIMARY_DEVICE);
return -1;
}
if (!_uart.setBaudrate(CUBERED_BRIDGE_PRIMARY_BITRATE)) {
PX4_ERR("setBaudrate %u failed", CUBERED_BRIDGE_PRIMARY_BITRATE);
return -1;
}
return ensure_open();
}
int CuberedSerial::ensure_open()
{
if (_uart.isOpen()) {
return 0;
}
if (!_uart.open()) {
PX4_ERR("open %s failed", CUBERED_BRIDGE_PRIMARY_DEVICE);
return -1;
}
// UART7 TX/RX are wired swapped between the primary and secondary MCUs.
if (!_uart.setSwapRxTxMode()) {
PX4_ERR("setSwapRxTxMode failed");
_uart.close();
return -1;
}
return 0;
}
int
PX4IO_serial::write(unsigned address, void *data, unsigned count)
void CuberedSerial::release()
{
uint8_t page = address >> 8;
uint8_t offset = address & 0xff;
if (_uart.isOpen()) {
_uart.close();
}
}
int CuberedSerial::exchange(size_t request_size, size_t expected_response_size)
{
perf_begin(_pc_txns);
if (_uart.write(&_io_buffer, request_size) != (ssize_t)request_size) {
perf_cancel(_pc_txns);
return -EIO;
}
uint8_t *buf = reinterpret_cast<uint8_t *>(&_io_buffer);
ssize_t got = _uart.readAtLeast(buf, sizeof(IOPacket),
expected_response_size, TRANSACTION_TIMEOUT_MS);
if (got < (ssize_t)expected_response_size) {
perf_count(_pc_timeouts);
perf_cancel(_pc_txns);
return -ETIMEDOUT;
}
const uint8_t received_crc = _io_buffer.crc;
_io_buffer.crc = 0;
if (crc_packet(&_io_buffer) != received_crc) {
perf_count(_pc_crcerrs);
perf_cancel(_pc_txns);
return -EIO;
}
perf_end(_pc_txns);
return 0;
}
int CuberedSerial::write(unsigned address, void *data, unsigned count)
{
const uint8_t page = address >> 8;
const uint8_t offset = address & 0xff;
const uint16_t *values = reinterpret_cast<const uint16_t *>(data);
if (count > PKT_MAX_REGS) {
@@ -107,31 +196,35 @@ PX4IO_serial::write(unsigned address, void *data, unsigned count)
px4_sem_wait(&_bus_semaphore);
int result;
int result = -EIO;
for (unsigned retries = 0; retries < 3; retries++) {
_io_buffer_ptr->count_code = count | PKT_CODE_WRITE;
_io_buffer_ptr->page = page;
_io_buffer_ptr->offset = offset;
memcpy((void *)&_io_buffer_ptr->regs[0], (void *)values, (2 * count));
if (ensure_open() != 0) {
px4_sem_post(&_bus_semaphore);
return result;
}
for (unsigned retries = 0; retries < MAX_RETRIES; retries++) {
_io_buffer.count_code = count | PKT_CODE_WRITE;
_io_buffer.page = page;
_io_buffer.offset = offset;
memcpy(&_io_buffer.regs[0], values, 2 * count);
for (unsigned i = count; i < PKT_MAX_REGS; i++) {
_io_buffer_ptr->regs[i] = 0x55aa;
_io_buffer.regs[i] = 0x55aa;
}
_io_buffer_ptr->crc = 0;
_io_buffer_ptr->crc = crc_packet(_io_buffer_ptr);
_io_buffer.crc = 0;
_io_buffer.crc = crc_packet(&_io_buffer);
/* start the transaction and wait for it to complete */
result = _bus_exchange(_io_buffer_ptr);
const size_t request_size = PKT_SIZE(_io_buffer);
// Write ack response is just the 4-byte header.
const size_t response_size = sizeof(IOPacket) - sizeof(_io_buffer.regs);
/* successful transaction? */
if (result == OK) {
result = exchange(request_size, response_size);
/* check result in packet */
if (PKT_CODE(*_io_buffer_ptr) == PKT_CODE_ERROR) {
/* IO didn't like it - no point retrying */
if (result == 0) {
if (PKT_CODE(_io_buffer) == PKT_CODE_ERROR) {
// Secondary rejected the request — retrying won't help.
result = -EINVAL;
perf_count(_pc_protoerrs);
}
@@ -144,18 +237,13 @@ PX4IO_serial::write(unsigned address, void *data, unsigned count)
px4_sem_post(&_bus_semaphore);
if (result == OK) {
result = count;
}
return result;
return (result == 0) ? (int)count : result;
}
int
PX4IO_serial::read(unsigned address, void *data, unsigned count)
int CuberedSerial::read(unsigned address, void *data, unsigned count)
{
uint8_t page = address >> 8;
uint8_t offset = address & 0xff;
const uint8_t page = address >> 8;
const uint8_t offset = address & 0xff;
uint16_t *values = reinterpret_cast<uint16_t *>(data);
if (count > PKT_MAX_REGS) {
@@ -164,47 +252,41 @@ PX4IO_serial::read(unsigned address, void *data, unsigned count)
px4_sem_wait(&_bus_semaphore);
int result;
int result = -EIO;
for (unsigned retries = 0; retries < 3; retries++) {
if (ensure_open() != 0) {
px4_sem_post(&_bus_semaphore);
return result;
}
/* Clear the entire packet to ensure no stale data */
memset(_io_buffer_ptr, 0, sizeof(IOPacket));
for (unsigned retries = 0; retries < MAX_RETRIES; retries++) {
memset(&_io_buffer, 0, sizeof(IOPacket));
_io_buffer_ptr->count_code = count | PKT_CODE_READ;
_io_buffer_ptr->page = page;
_io_buffer_ptr->offset = offset;
_io_buffer.count_code = count | PKT_CODE_READ;
_io_buffer.page = page;
_io_buffer.offset = offset;
_io_buffer.crc = 0;
_io_buffer.crc = crc_packet(&_io_buffer);
_io_buffer_ptr->crc = 0;
_io_buffer_ptr->crc = crc_packet(_io_buffer_ptr);
// Read requests carry `count` reg slots of padding so that the
// secondary's PKT_SIZE / CRC accounting matches what the response
// will look like.
const size_t request_size = PKT_SIZE(_io_buffer);
const size_t response_size = request_size;
/* start the transaction and wait for it to complete */
result = _bus_exchange(_io_buffer_ptr);
result = exchange(request_size, response_size);
/* successful transaction? */
if (result == OK) {
/* check result in packet */
if (PKT_CODE(*_io_buffer_ptr) == PKT_CODE_ERROR) {
/* IO didn't like it - no point retrying */
if (result == 0) {
if (PKT_CODE(_io_buffer) == PKT_CODE_ERROR) {
result = -EINVAL;
perf_count(_pc_protoerrs);
/* compare the received count with the expected count */
} else if (PKT_COUNT(*_io_buffer_ptr) != count) {
/* IO returned the wrong number of registers - no point retrying */
} else if (PKT_COUNT(_io_buffer) != count) {
result = -EIO;
perf_count(_pc_protoerrs);
/* successful read */
} else {
/* copy back the result */
memcpy(values, &_io_buffer_ptr->regs[0], (2 * count));
memcpy(values, &_io_buffer.regs[0], 2 * count);
}
break;
@@ -215,9 +297,19 @@ PX4IO_serial::read(unsigned address, void *data, unsigned count)
px4_sem_post(&_bus_semaphore);
if (result == OK) {
result = count;
}
return result;
return (result == 0) ? (int)count : result;
}
} // namespace
device::Device *cubered_bridge_primary_interface()
{
return new CuberedSerial();
}
void cubered_bridge_primary_release(device::Device *interface)
{
if (interface != nullptr) {
static_cast<CuberedSerial *>(interface)->release();
}
}