Both were defined inline in the header, so the List add/remove bodies
were duplicated into the constructor and destructor of every one of the
150+ ModuleParams-derived classes. Construction and destruction are cold
paths; a call is smaller at each site.
Two consumers reach ModuleParams without px4_platform on the link line
and now need the definitions explicitly: health_and_arming_checks (the
functional-ModeManagement test only pulls it in transitively through
modules__commander, after px4_platform) and the failsafe_web emscripten
build, which compiles module_params.cpp directly.
The smaller constructors let the compiler fully inline the defaulted
FlightTaskDescend constructor on the ITCM boards, so its entry is
dropped from their linker scripts.
Saves 3968 B of .text on px4_fmu-v6x_default.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Balduin <balduin@auterion.com>
board_query_manifest walked the list for mft[ndx].id == id then
returned &mft[id]. On a sparse list that is the wrong row, and for
PX4_MFT_T100_ETH (id 7) it is out of bounds.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Make px4::atomic's value constructor and ModuleBase::Descriptor
constexpr so the ~40 per-module 'desc' statics are constant-initialised
instead of each emitting static-init code, and apply the same to the
mixer_module FunctionProvider table.
Signed-off-by: Balduin <balduin@auterion.com>
The ~PublicationBase() destructor was header-inline, so its null-check
plus the orb_get_queue_size()/unadvertise() branch was emitted at every
publication destruction site (PX4 links with bfd ld, no ICF, no LTO).
Move it into Publication.cpp alongside the already out-lined
advertise()/publish(), mirroring #27581.
Saves 2.4 kB of flash on px4_fmu-v6x_default.
publish(), advertise() and get_instance() were instantiated per topic
type and the advertise-check in publish() was additionally inlined at
every call site. Move them into a type-independent PublicationMultiBase
compiled once.
Saves 5.5 kB of flash on px4_fmu-v6x_default.
process_add_subscription() (CONFIG_ORB_COMMUNICATOR only) read _data_valid and
the topic buffer contents while passing a pointer into _data straight to
send_message(), with no synchronization against a concurrent publisher. A
publisher's write() memcpy's into _data and sets _data_valid under ATOMIC_ENTER,
so the remote-init send can read a torn/partially-updated sample - most clearly
for o_queue == 1, where the read slot ((gen - 1) % 1) is exactly the slot being
written.
This is a pre-existing latent race (the previous px4::atomic<uint8_t*> _data only
guarded the pointer, never the bytes) that neither ASan nor TSan can catch because
CONFIG_ORB_COMMUNICATOR is not built in those configs. On VOXL2 it runs on the
muorb RX thread concurrently with publishers.
Snapshot the most recent sample into a temporary buffer under ATOMIC_ENTER, then
send it outside the critical section - mirroring copy(). The buffer is allocated
before the critical section (it cannot be allocated under ATOMIC_ENTER on NuttX)
and send_message() must run outside it anyway, since it can be slow and may call
back into DeviceNode. This only affects the rare subscription-add path, not the
publish hot path.
Signed-off-by: Julian Oes <julian@oes.ch>
_data (the lazily-allocated topic buffer) was a px4::atomic only to make the
double-checked-locking publish safe against concurrent readers. It is allocated
under the node lock() and written exactly once, so reading it under ATOMIC_ENTER
- which is that same lock() on POSIX - serializes the subscriber side against the
allocation without needing an atomic. On NuttX it is a set-once, word-aligned
pointer. The copy() guard is moved inside ATOMIC_ENTER accordingly. (Suggested in
review.)
DeviceNode::open() took the cdev lock only to call mark_as_advertised(), but
_advertised is a px4::atomic, so the single store needs no lock. (Suggested in
review.)
_interval_us and _last_update are read in SubscriptionCallbackWorkItem::call()
(and SubscriptionBlocking::call()) on the publishing thread, while the subscriber
sets them - e.g. from mixer_module's setMaxTopicUpdateRate() on a reconfigure.
That is a genuine cross-thread access that ThreadSanitizer flags.
Instead of making these fields atomic in the base SubscriptionInterval - which is
used single-threaded by 100+ subscriptions - parameterize the class on whether the
interval / last-update storage is atomic and expose two aliases:
SubscriptionInterval plain, single-threaded (the common case)
SubscriptionIntervalAtomic atomic, for callback subscriptions
SubscriptionCallback - and hence all callback subscriptions
(SubscriptionCallbackWorkItem, SubscriptionBlocking, CallbackHandler) - uses the
atomic variant; every other subscription stays plain with no synchronization
overhead.
The updated()/update()/copy() bodies stay out-of-line with explicit instantiation
of both variants, so they are not inlined at every call site (flash size).
The publisher fires callbacks via SubscriptionCallbackWorkItem::call(), which read
the subscriber's _last_generation (and called updated(), which reads it again) to
decide whether to schedule the work item. That made the subscriber's read cursor a
cross-thread field - publisher reads it while the subscriber writes it in copy() -
which TSan flagged and which we had wrapped in an atomic.
Instead, hand the publishing node's generation into call() and let the callback keep
its own _last_scheduled_generation cursor for the count gate (_required_updates).
call() runs under the node lock, so that cursor is only ever touched there and is
serialized - no atomic needed, and using the real generation (not a plain counter)
means a coalesced/missed call() does not drift the batch. The redundant updated()
generation check is dropped (call() only runs right after a publish, so it is always
true); the interval gate (_last_update / _interval_us) is kept unchanged.
With the publisher no longer reading it, _last_generation reverts to a plain unsigned
(subscriber-thread only). Behaviour is unchanged: the count and interval throttles
fire at the same rates (verified via work_queue status - gyro_fft 62.5 Hz,
flight_mode_manager 50 Hz) and TSan stays clean.
GCC does not provide __has_feature, and its preprocessor tokenizes both
operands of && before evaluating the operator, so
#if defined(__SANITIZE_ADDRESS__) || defined(__has_feature) && __has_feature(address_sanitizer)
failed to compile with 'missing binary operator before token "("' on
toolchains such as the voxl2 linaro GCC. Add the standard __has_feature
fallback shim so the check uses __SANITIZE_ADDRESS__ on GCC and the real
__has_feature on Clang. Behavior is unchanged.
Signed-off-by: Julian Oes <julian@oes.ch>
system_exit() (= _exit()) on Linux skips C++ destructors and atexit
hooks, including ASan's leak-and-error report. Under sanitizer builds
we want that report. Switch to plain exit() when __SANITIZE_ADDRESS__
is defined; native posix shutdown still uses _exit().
Two related fixes for HRT callout queue corruption under lockstep SIH:
1. WorkQueue::Run() popped a WorkItem, released the work-queue lock,
then called work->Run(). If another thread called Deinit on the
item in that window, wq->Remove was a no-op (item already popped)
and Deinit returned while Run() was still executing. Run() could
then call ScheduleDelayed → hrt_call_after on an object that the
caller of Deinit has since destructed and reused via placement new
— the hrt_call metadata ends up living inside another object's
fields, which later overwrite the flink pointer and tear the hrt
callout queue.
Fix: WorkItem carries a _run_in_progress atomic flag set by
WorkQueue::Run around work->Run(). Deinit spins on that flag after
wq->Remove so it cannot return while a Run() is still executing.
WorkQueue captures its worker tid in the constructor (the ctor
runs on the worker thread) so Deinit can skip the self-wait when
called from inside Run() itself (e.g. should_exit() paths that
invoke ScheduleClear).
2. _hrt_lock was a px4_sem_t and hrt_call_invoke unlocked around the
callback so callbacks could re-enter hrt_call_*. That also exposed
the queue to other threads mid-invocation. Switch _hrt_lock to a
PTHREAD_MUTEX_RECURSIVE pthread_mutex and hold it across the
callback — matches NuttX's enter_critical_section() nesting
semantics, lets callbacks reschedule themselves safely, and
prevents concurrent queue manipulation.
Together these eliminate the HRT queue tearing observed in the
SIH-at-20x MAVSDK integration soak (torn flink chains with the tail
unreachable from the head; orphan nodes pointing into reused
memory).
Move the helper from the header into WorkQueue.cpp. Drop the
SignalWorkerThread() call from request_stop() — request_stop sets
_should_exit but the worker is woken up via the existing exit path
in Detach already, so the extra signal here is redundant.
A seq_cst px4::atomic emits a hardware `dmb` on ARM. That barrier only orders
accesses as seen by a second observer (another CPU core or DMA); for inter-thread
synchronisation on a single-core (uniprocessor) target there is no second core, so
the `dmb` is dead weight - only the compiler ordering is required.
On a NuttX build without CONFIG_SMP, keep full seq_cst semantics but emit a
compiler-only fence (__atomic_signal_fence, zero instructions) instead of the
`dmb`. This mirrors Linux, where smp_mb()/smp_rmb()/smp_wmb() collapse to a
compiler barrier on uniprocessor builds. SMP NuttX and POSIX are unchanged (real
barriers, so SITL under ThreadSanitizer keeps full ordering). 64-bit types (not
lock-free on a 32-bit core) keep the existing interrupts-off critical section,
which already provides both atomicity and ordering.
The public API is unchanged - no per-call ordering knob. Verified on
arm-none-eabi Cortex-M7 that the single-core path emits no `dmb` while keeping
atomicity, and that SMP/POSIX is unchanged:
load (single-core): ldr (signal_fence: 0 instr)
store (single-core): str
fadd (single-core): ldrex/strex loop, no dmb
load (seq_cst/SMP): dmb ish; ldr; dmb ish
store (seq_cst/SMP): dmb ish; str; dmb ish
fadd (seq_cst/SMP): dmb ish; ldrex/strex; dmb ish
Shrinks fmu-v6x by ~2.8 KB (dead barriers removed from atomics already in use),
with no behaviour change. Note: inter-thread ordering only - DMA/device sync
still needs explicit barriers.
* fix(i2c_spi): prevent use-after-free when stopping driver instances
module_stop() deleted each instance before unlinking it from the global
i2c_spi_module_instances list. The instance *is* the intrusive list node,
so removeInstance()/List::remove() then dereferenced the freed node to fix
up the links. If another thread reused that heap block in the window, the
list got corrupted, sporadically leaving instances linked (a later start
reports "already running") or hard-faulting. This regressed in the 2020
array->linked-list refactor: the old array version only nulled an array
slot after delete, which was safe.
Unlink each instance before freeing it. Also only delete/unlink when the
task actually exited: if request_stop_and_wait() times out the work queue
may still reference the object, so leave it allocated and listed rather
than freeing it. module_stop() now returns -1 when an instance failed to
stop.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* Update platforms/common/i2c_spi_buses.cpp
* Update platforms/common/i2c_spi_buses.cpp
---------
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* platforms/common/uORB/uORB.h: Add definition for orb_sub_t and handle check functions
Signed-off-by: Jukka Laitinen <jukkax@ssrc.tii.ae>
* uORB: Change subscriber id:s from int to orb_sub_t
Signed-off-by: Jukka Laitinen <jukkax@ssrc.tii.ae>
* uxrce_dds_client: Change polling of transport device from px4_poll to poll
Use posix poll directly, there is no need to use px4_poll unless uORBs are
being polled.
The one used here is a normal filesystem/device poll, so we can use normal "poll",
this is the common pattern in the codebase.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* Fix linking for protected build
This fixes errors for memalign not linking on some configurations.
Memalign exists in nuttx kernel-side mm library and it may fail in configurations
where kernel and userspace are separated. This has no effect on other than
"CONFIG_BUILD_PROTECTED" or "CONFIG_BUILD_KERNEL" NuttX builds.
Signed-off-by: Jukka Laitinen <jukkax@ssrc.tii.ae>
---------
Signed-off-by: Jukka Laitinen <jukkax@ssrc.tii.ae>
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
Co-authored-by: Jukka Laitinen <jukkax@ssrc.tii.ae>
* refactor(uORB): out-line PublicationBase/Subscription methods to save flash
Move the type-independent PublicationBase::advertise()/publish() and
Subscription::copy()/update() bodies out of the headers into
Publication.cpp / Subscription.cpp, leaving only declarations inline.
PX4 links with bfd ld (no identical-code folding) and without LTO, so
these header-inline methods were emitted per translation unit: GCC both
.isra-cloned them (~39 publish, 25 advertise, 17 copy, 12 update copies)
and fully inlined them into many callers. Out-lining collapses all of
that to a single shared definition each.
This is the out-of-line follow-up to #27526, which hoisted advertise/
publish from the Publication<T> template into the non-template
PublicationBase but kept them inline in the header. The methods were
already emitted as separate clones, so callers already pay a bl and the
publish/read hot paths are unchanged -- this is pure code motion.
Saves 10.4 KB .text on auterion_fmu-v6x (1,949,568 -> 1,939,168).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(uORB): out-line SubscriptionCallback register/unregister to save flash
Move SubscriptionCallback::registerCallback()/unregisterCallback() out of the
header into a new SubscriptionCallback.cpp. Like the Publication/Subscription
methods, these were emitted per translation unit (PX4 links with bfd ld, no
ICF, no LTO): GCC .isra-cloned them and inlined them into every WorkItem-based
subscriber's setup path.
registerCallback() runs only at subscription/callback setup (cold path), so
out-lining is pure code motion with no hot-path change.
Saves 4.9 KB .text on auterion_fmu-v6x (1,939,168 -> 1,934,128).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(uORB): out-line remaining Subscription methods to save flash
Extends this PR's pattern to the rest of Subscription: move the type-independent methods still defined in the header (constructors, destructor, copy/move assignment, updated(), advertised()) into Subscription.cpp so they are emitted once instead of inlined into every translation unit.
updated()/advertised() inline the Manager::updates_available / is_advertised (DeviceNode) chain, and the constructors were inlined at every subscription site, so most of the remaining duplication lived there. ~9.4 KB .text saved on ark_fmu-v6x_default.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
* src/drivers/cdcacm_autostart: Include posix.h for px4_close
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* platforms/nuttx/CMakeLists.txt: Fix linking of nuttx libaries for memalign
This fixes memalign not found in linking step for some boards
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* Add "flock" to macos.sh setup script
"flock" is not standard on macOS, and a dependency was missing from the
macOS setup path.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* Fix clang-tidy errors in Bitset.hpp and in src/lib/matrix
Fix the "bugprone-dynamic-static-initializers" linter errors.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* systemlib/hardfault_log: Fix clang-diagnostics error
Fix for
"[error] clang-diagnostic-error [error]
use of undeclared identifier XCPTCONTEXT_REGS".
XCPTCONTEXT_REGS is defined in nuttx irq.h, so include that.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* uORB: Fix clang-tidy error "bugprone-dynamic-static-initializers"
Fix the clang-tidy error appearing on uORBManager _Instance variable by
adding a getter for the reference to the _Instance and using that instead.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
* CI: Add default ubuntu mirrors as fallback
In case of specified aws mirror doesn't have the package idicated by the
metadata, a the default ubuntu mirror as a backup.
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
---------
Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
I2CBusIterator::external() was returning px4_i2c_bus_external(_bus),
where _bus is the constructor filter argument (the user's -b value,
which defaults to -1 when no bus is specified). When a driver was
started with -I and no -b (e.g. iis2mdc -I start, bmp388 -I start),
_bus stayed -1, px4_i2c_bus_external(-1) fell through to its "not
found" fallback that returns true, and the boot log printed
"on I2C bus 4 (external)" for sensors sitting on an internal bus.
Pass bus().bus instead so the result reflects the bus the iterator
is currently positioned on. This mirrors SPIBusIterator::external()
and restores the pre-8080ca966a8 semantics.
Device::external() (the override used by sensors status and
calibration) already used the device id's bus number, so only the
boot-time print was wrong.
Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
call into a new UORB COMMUNICATOR ICHANNEL shutdown interface if it has been configured, otherwise it
does nothing. This allows ICHANNEL implementations to pass on a shutdown indication to a remote processor.
Implemented the shutdown interface in the muorb module for VOXL flight controllers.
* rt106x: Use platform SPI hal layer
* rt106x: Add romapi support and reboot to isp/bootloader
* bootloader: imxrt_common: Add rt106x support
* NXP MR-Tropic initial commit
* Add missing file for mr-tropic bootloader
* nxp-mr-tropic:Bootloader Alow Assertion debugging & Keep Ram Vectors
* nxp-mr-tropic: Firmware Boot from bootloader
* nxp-mr-tropic:Add Bootloader bin file
* mr-tropic: Update config and linker
Fixes enet issues with write-back and some code cleanup.
Furthermore increase NOR LittleFS to 256kB to reflect on linker
* Update NuttX
* mr-tropic: fix itcm apping and add mr-tropic to itcm check
---------
Co-authored-by: David Sidrane <David.Sidrane@NscDg.com>
* platforms: Serial new dedicated writeBlocking method
* finish writeBlocking()
* add back fsync
* updated posix, added string constant for port not open error
* format
* fix build
* remove fsync
* actually remove fsync
* remove fsync from write
* review feedback
---------
Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com>
Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com>