50552 Commits

Author SHA1 Message Date
Julian Oes
b6b2028a80 fix(lockstep_scheduler): wait for threads before advancing time in test (#27798)
test_multiple_semaphores_waiting() spawns a worker thread per TestCase, then
advances virtual time in a loop and completes each case in check(). Since
check() early-returns while !_thread_ready, a slow-to-start worker thread can
miss the whole loop: the loop advances past the case's timeout before the thread
registers, so the case is never completed. ~TestCase() then fails
EXPECT_TRUE(_is_done) and destroys a still-joinable std::thread, which calls
std::terminate() and aborts the test. This was flaky (~50% locally, worse under
CI load).

Wait for every worker thread to be ready before advancing virtual time. Also
re-comment the per-iteration std::cout that was left enabled.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-03 13:09:03 -07:00
PX4BuildBot
cc833b894d docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-07-03 19:53:30 +00:00
saad1551
cf0f76101b fix(navigator): enforce NAV_MIN_LTR_ALT in MC braking loiter
When a multicopter enters Hold (AUTO.LOITER) below NAV_MIN_LTR_ALT the
documented behavior is to first climb to that minimum altitude. The
clamp was added in 2022 to setLoiterItemFromCurrentPosition (the
fixed-wing branch) but never applied to the sibling
setLoiterItemFromCurrentPositionWithBraking, which is the rotary-wing
Hold path selected at loiter.cpp:131. As a result, multicopters
silently held at the entry altitude.

Apply the same min-altitude clamp in the braking variant so the
rotary-wing path matches the fixed-wing one.

Verified in SITL (jMAVSim x500): with NAV_MIN_LTR_ALT=20 and Hold
engaged from a 5m hover, the vehicle now climbs to 20m as documented.

Fixes #27578

Signed-off-by: saad1551 <saad.ashraf.9094@gmail.com>
2026-07-03 12:41:48 -07:00
Beat Küng
5d2cec97f1 chore(commander): remove unused flag_control_termination_enabled flag 2026-07-03 12:33:03 -07:00
Beat Küng
f33b5769fc chore: use setpoint_config in mc_raptor and mc_nn_control 2026-07-03 12:33:03 -07:00
Beat Küng
9fb0f1e43a chore(VehicleControlMode): remove unused flag_control_acceleration_enabled 2026-07-03 12:33:03 -07:00
Beat Küng
343eab2f05 feat(commander): add setpoint types
Switches from using VehicleControlMode to a specific setpoint type message
with reply.
Reasons:
- in case a VehicleControlMode was dropped (e.g. when the mode switched
  setpoint type), no confirmation was returned, and resulting in wrong
  controller flags.
- cleaner interface separation: external modes do not need to configure
  (or know) which controller to run for a certain setpoint
- allows for external modes to check for compatibility: e.g. a mode using
  fixed-wing setpoint types can now be rejected on a multicopter.
- allows for further extensions, like a setpoint timeout

This makes it a bit more effort to add a new setpoint type. Specifically,
setpoint_types.cpp needs to be extended when adding a new setpoint message.

PX4 internal modes also make use of the setpoint types. The information
flow is:
nav_state -> setpoint type -> vehicle control mode flags

It also adds a timeout to the setpoint config, but is not implemented
yet.

This changes the interface for external modes and thus the compatibility
version is increased.
2026-07-03 12:33:03 -07:00
Beat Küng
0280f41867 fix(navigator): resend mode_completed when mode is completed
Important for external modes in case a topic sample gets dropped.
2026-07-03 12:33:03 -07:00
Beat Küng
604fe59ed7 fix(commander): add config_overrides_confirm topic
Allows an external mode to check if the request got processed
2026-07-03 12:33:03 -07:00
Beat Küng
f0fa73bf8d chore(msg): remove unused VehicleAngularAccelerationSetpoint.msg 2026-07-03 12:33:03 -07:00
yannickpoffet
d0f7b7d8fc feat(uxrce_dds_client): drain / unlimited rate options for topic bridging (#27688)
* feat(uxrce_dds_client): add "drain" and "unlimited" rate options for dds_topics bridging

Drain option allows to specify that a queued uORB topic being bridged should
have its queue drained completely at every rate interval, instead of sending
only the latest message. This is useful to prevent jitter and delay being
added on topics subject to burst and irregular publishing patterns.

The "unlimited" rate option allows to specify that a uORB topic being bridged
should be sent as fast as possible, without any rate limiting. This is useful
for reducing latency on topics where no message loss is acceptable and where
the uORB topic is being published at a high rate.

* fix(dds_topics): remove "drain" option and safe guard against inf loop

Drain option is removed to be coupled with rate_limit = unlimited.

The loop is gated to the size of the uorb queue to avoid infinite loops
if publisher is faster that this loop.

* fix(dds_topics): simplification recommended in review
2026-07-03 12:14:40 -07:00
Ramon Roche
f19c9f98c5 fix(tecs): prevent NaN propagation in pitch controller (#27749)
* tecs: Prevent. NaN in pitch integrator

Prevents NaN propagation into _pitch_integ_state when seb_rate.setpoint is non-finite (as seen when exiting FW velocity Offboard). Mirrors the existing throttle integrator finite check. Fixes #25906.

* tecs: Harden the TECS pitch integrator against NaN contamination

* fix(tecs): guard pitch output against non-finite SEB rate

The integrator guard alone does not prevent the reported NaN lockup when
the non-finite value originates on the energy/speed side: the feedforward
and damping terms in _calcPitchControlOutput feed seb_rate straight into
the pitch demand, bypassing the integrator. constrain() does not reject
NaN, so the pitch setpoint latches NaN permanently and the aircraft
becomes uncontrollable.

Fall back to the (now guaranteed finite) integrator state when the
uncorrected pitch setpoint is non-finite, and drop the stray trailing
whitespace introduced by the previous commit.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>

* test(tecs): regression test for NaN pitch integrator and output

Drives TECSControl::update with a non-finite true airspeed setpoint, the
same way seb_rate goes non-finite on the energy/speed side when exiting
offboard velocity mode, and asserts the pitch integrator state and the
pitch setpoint both stay finite, across a single bad frame and sustained
non-finite input. Both assertions fail against unguarded TECS.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>

* test(tecs): cover the corrupted-integrator-state safety reset

Addresses review feedback on #27749: the existing tests inject a
non-finite airspeed setpoint, which the integrator-input guard rejects
before it can reach _pitch_integ_state, so the state-corruption reset
branch in _calcPitchControlUpdate was never actually exercised.

Add a FRIEND_TEST seam so the regression test can pre-corrupt
_pitch_integ_state directly, then confirm a single update() call detects
the non-finite state, resets it, and keeps the pitch setpoint finite.
Verified the test fails against the code with the reset guard removed and
passes with it in place.

---------

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
Co-authored-by: Ryan Johnston <31726584+ryanjAA@users.noreply.github.com>
Co-authored-by: Ryan Johnston <rjohnston@appliedaeronautics.com>
2026-07-03 11:58:33 -07:00
PX4BuildBot
36d0825b6e docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-07-03 17:06:24 +00:00
Alexis Guijarro
f903391f44 feat(drivers): add DPS368 variant support via -8 flag (#27724)
* feat(drivers/dps310): add DPS368 variant support via -8 flag

Register DRV_BARO_DEVTYPE_DPS368 (0x77) and extend the dps310 driver
entry point with a -8 flag to select the DPS368 devtype at startup.

* fix(drivers/dps310): set device type from devid_driver_index after init
2026-07-03 10:00:03 -07:00
cuav-chen2
40671b2a58 refactor(boards/cuav_x25-evo): Remove INS to save flash 2026-07-03 09:53:30 -07:00
Balduin
580e9750fd refactor(sih): use basic Publication + class member
needs less flash
2026-07-03 09:48:22 -07:00
Balduin
d8847067a8 refactor(sih): remove local variable to save stack
A recent SIH log (V6X) was full of:

WARNING: [px4] [load_mon] sih low on stack! (144 bytes left)

Using PublicationData (which holds the esc_status_s in heap rather than
stack) fixes them and adheres to the philosophy from the module
description: "Most of the variables are declared global in the .hpp file
to avoid stack overflow"
2026-07-03 09:48:22 -07:00
Julian Oes
94c12f468c fix(mc_pos_control): allow disabling trajectory time-stretch and fix docs
The trajectory time-stretch is skipped when the max-error param is at or below
FLT_EPSILON, so zero is a valid 'disabled' setting. Lower the minimum of
MPC_XY_ERR_MAX and MPC_Z_ERR_MAX from 0.1 to 0 so users can actually select it,
and document it in both params.

Also fix the setMaxAllowedVerticalError() doc comment, which incorrectly
claimed zero was the default: the MPC_Z_ERR_MAX param default is 1 m.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-03 09:46:48 -07:00
Julian Oes
49535090a8 refactor(motion_planning): remove unused clampToXYNorm/clampToZNorm
These helpers are no longer called from production code after the smoother
was reworked to clamp XY and Z velocity components independently. Their only
remaining references were in TrajectoryConstraintsTest.cpp, which is not
registered in CMake and therefore never compiled. Drop both the functions and
the orphaned tests.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-03 09:46:48 -07:00
Julian Oes
495803a1db feat(mc_pos_control): per-axis time-stretch with new MPC_Z_ERR_MAX
PositionSmoothing already has a horizontal-error gate (MPC_XY_ERR_MAX)
that slows trajectory integration when the smoother's virtual position
runs ahead of the drone in XY. The same time-stretch was applied to
all three axes, but it was only ever computed from XY error. So:

- Z lag never slowed Z down: a noisy altitude reference (e.g. mission
  setpoint jittering as home altitude is refined in flight) could
  walk the commanded z 5+ m above the mission target while horizontal
  tracking stayed fine. The jerk-limited smoother integrates each
  small step into a velocity ramp, and successive ramps superpose
  into multi-meter altitude excursions.

- Z lag dragging XY: when a Z stretch eventually was added, applying
  the same scalar to all axes meant a ±1 m altitude jitter could
  cap forward flight speed during a straight mission leg. That's
  almost never what we want.

Fix: compute time_stretch_xy and time_stretch_z independently using
their own _max_allowed_*_error thresholds and apply per-axis. Default
the new MPC_Z_ERR_MAX to 1 m (tighter than XY's 2 m because altitude
holds are usually crisper). Other PositionSmoothing users (Orbit,
GotoControl) keep zero as the vertical error cap, which is the
explicit "disable Z stretch" sentinel — so behaviour there is
unchanged.
2026-07-03 09:46:48 -07:00
Julian Oes
4211f3d427 fix(payload_deliverer): configure gripper before arming callbacks
ScheduleOnInterval and registerCallback can both invoke Run() on
another thread (the work queue / publisher thread). configure_gripper
sets state Run() reads, so it must complete before the callbacks are
armed — otherwise a fast first iteration can race with the gripper
initialization.
2026-07-03 09:46:48 -07:00
Julian Oes
ab5d7aefa9 fix(flight_mode_manager): init state before registering callback
set_interval_us() and the initial _time_stamp_last_loop write must
happen before the uORB subscription callback is armed — otherwise a
publisher on another thread can invoke Run() concurrently with init().
Run() reads _time_stamp_last_loop and depends on the interval being
set; observing zero-init values briefly leads to either a giant first
dt or an over-rate first iteration.
2026-07-03 09:46:48 -07:00
Julian Oes
1ae8f2383f refactor(motion_planning): split XY/Z velocity components in smoother
PositionSmoothing::_generateVelocitySetpoint computed a single
constrained 3D velocity vector and then ran clampToXYNorm /
clampToZNorm in series. The XY clamp could shorten the Z component
(and vice versa), so a Z speed limit ended up capping forward speed
and a tight altitude error capped horizontal motion. Compute XY and
Z velocity components independently using their own speed limits so
they don't mutually clamp.

Companion to the per-axis time-stretch fix (commit added later in
the chain). Together they decouple the horizontal and vertical
trajectory dynamics in the smoother.
2026-07-03 09:46:48 -07:00
Julian Oes
43131d474c fix(mavlink): don't miss mission current updates
When a mission is finished we need to make sure to send the mission
current message and not drop it. Otherwise, MAVSDK tests might not get
the update that the mission has been finished.
2026-07-03 09:46:48 -07:00
PX4BuildBot
c772b23067 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-07-03 15:39:03 +00:00
Balduin
deb21ae2be fix(fw_latlon_control): add back FW_PSP_OFF after TECS
Before calling _tecs.update in tecs_update_pitch_throttle, all pitch
values are corrected by subtracting trim pitch, so TECS internal pitch
is always relative to trim.

To stay correct we have to add it back when passing the TECS internal
pitch setpoint to the rest of the system.
2026-07-03 17:33:48 +02:00
Balduin
3e9841cbb6 feat(docs): sort dds topics, fix formatting (#27794) 2026-07-03 08:44:15 +10:00
PX4BuildBot
60ed1a27d4 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-07-02 19:21:36 +00:00
Ramon Roche
2f459921f6 docs: add official PX4 developer kits and manufacturer program
Document the Official PX4 Developer Kit program: a new top-level
Developer Kits section listing the certified kits (ModalAI Starling 2
Max, DroneBlocks DEXI 5, Holybro X500 v2), a new DEXI 5 page, and a
manufacturer-facing page describing the program requirements, benefits,
and eligibility.

Restructure the hardware navigation so all certified kits live in one
place instead of being split across Complete Vehicles and Kits, drop
discontinued build guides from the sidebar (still linked from the kit
build guides page), and group the manufacturer guides under a new
Manufacturers section in Development > Hardware.

Signed-off-by: Ramon Roche <mrpollo@gmail.com>
2026-07-02 12:15:03 -07:00
PX4BuildBot
47896d8aa1 docs: auto-sync metadata [skip ci]
Co-Authored-By: PX4 BuildBot <bot@px4.io>
2026-07-02 15:51:29 +00:00
Julian Oes
bca6e3df75 fix(mavlink): give mavlink_channel_send_mutexes internal linkage
The per-channel send mutex array was defined without static, giving it external
linkage in the single-binary firmware even though it is only used within
mavlink_main.cpp (its status/buffer siblings are already static). Make it static
to match and avoid a latent cross-TU name clash.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-02 08:42:49 -07:00
Julian Oes
abc5780776 refactor(mavlink): apply protocol version on channel assignment, drop early buffers
get_status()/get_buffer() returned per-instance _mavlink_status_early/
_mavlink_buffer_early fallbacks because the constructor touches the channel
status (via mavlink_update_parameters() -> setProtocolVersion()) before
set_instance_id() has assigned a channel, and set_instance_id() then copied
those into the static per-channel arrays.

Instead, don't touch channel state before we own a channel: setProtocolVersion()
now only caches _protocol_version until _instance_id is valid, and set_instance_id()
applies the cached version to the freshly-claimed channel's status flags. This lets
get_status()/get_buffer() index the per-channel arrays directly and removes the two
early buffers. The end state is identical (the buffer copy always copied a
zero-initialized buffer, since nothing writes it before assignment).

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-02 08:42:49 -07:00
Julian Oes
f92996f16b test: document why 'sensors status' is disabled in test_imu_filtering
Replace the terse "triggers TSAN" note with a TODO explaining that 'sensors
status' reads sensor module state from the shell thread without synchronization,
so it should be re-enabled once that race is fixed.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-02 08:42:49 -07:00
Julian Oes
f15c796b56 docs(nuttx): document px4_task_join limitations
NuttX tasks are task_create() children, not pthreads, so px4_task_join has no
portable join and instead polls whether the task still exists. Document that it
returns no exit status, can wait on the wrong task if the PID is reused, and is
only safe because its sole on-target caller is WorkQueueManager shutdown (and
NuttX shutdown is a full reboot).

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-02 08:42:49 -07:00
Julian Oes
48a08e1633 fix(posix): guard px4_task_join/delete against negative task ids
px4_task_t is a plain int and callers hold -1 for "no task" (e.g. dataman's
g_task_id after stop). The bounds checks only tested id < PX4_MAX_TASKS, which is
true for negative ids, so px4_task_join(-1) and px4_task_delete(-1) would index
taskmap[-1] out of bounds. Check id >= 0 as well.

Signed-off-by: Julian Oes <julian@oes.ch>
2026-07-02 08:42:49 -07:00
Julian Oes
d828e1b12d fix(uORB): snapshot _data under the node lock before sending to a remote subscriber
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>
2026-07-02 08:42:49 -07:00
Julian Oes
358b090c4e fix(posix): initialize PX4 before starting the daemon server
The daemon server's client-handler thread spawns module tasks (e.g. dataman).
Start px4::init_once()/init() before creating px4_daemon::Server so platform init
- uORB, work queues, logging - completes with a happens-before edge to those
tasks. Otherwise a module task racing the still-running init reads globals like
uORB::Manager's instance pointer or the log message advertisement without
synchronization (ThreadSanitizer flags it) - and an atomic would not make it
correct, since the publication could simply be skipped.
2026-07-02 08:42:49 -07:00
Julian Oes
99c1eb8123 fix(mavlink): bump per-message tstatus counters lock-free instead of under the mutex
tx_message_count (every send) and tx_buffer_overruns took _tstatus_mutex on the
hot path just to increment a counter. Make them px4::atomic, like the existing
_bytes_* counters, bumped lock-free from either thread's send path and folded
into _tstatus (cumulative) under the mutex at the ~1 Hz publish. The mutex is
kept for the genuinely shared low-frequency fields. (Suggested in review.)
2026-07-02 08:42:49 -07:00
Julian Oes
1ab4774e31 fix(parameters): initialize the recursive param mutex in param_init()
The param access mutex must be recursive because a layered param lookup
re-enters the lock on the same thread (Layer::get() -> _parent->get()).

Initialize it in param_init() - which runs once before any thread accesses
parameters - via pthread_mutexattr_settype(PTHREAD_MUTEX_RECURSIVE), replacing
the previous pthread_once lazy init. This avoids the non-portable glibc static
initializer PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, which is unavailable on QURT
and gated behind _GNU_SOURCE elsewhere (broke the voxl2 and SITL builds).
2026-07-02 08:42:49 -07:00
Julian Oes
76742a4e98 refactor(uORB): guard DeviceNode _data with the node lock instead of an atomic
_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.)
2026-07-02 08:42:49 -07:00
Julian Oes
53b138e38c refactor(uORB): drop redundant lock around the atomic _advertised store
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.)
2026-07-02 08:42:49 -07:00
Julian Oes
6aae566ea1 docs: document building SITL with sanitizers (ASan/TSan)
Add a test_and_ci page covering the PX4_ASAN / PX4_TSAN / PX4_MSAN / PX4_UBSAN
build variables, the Linux `sudo sysctl vm.mmap_rnd_bits=28` workaround for the
sanitizer runtime's startup memory mapping, and the TSan suppressions file
(test/sitl_tsan.supp) for the Gazebo simulation transport. Linked from SUMMARY.md
and the test_and_ci index.
2026-07-02 08:42:49 -07:00
Julian Oes
ccd444726e test: add TSan suppressions for Gazebo sim transport
Running SITL with the Gazebo bridge under ThreadSanitizer reports many data races
inside libzmq and gz-transport (and the GZBridge glue) - third-party simulation
transport that is not in the flight path or the SIH-based CI and that we cannot
fix. Add a suppressions file so the TSan signal stays usable for finding real
races. Use via TSAN_OPTIONS="suppressions=test/sitl_tsan.supp".
2026-07-02 08:42:49 -07:00
Julian Oes
aed9c16bf1 fix(flight_mode_manager): set up subscription before registering callback
init() registered the vehicle_local_position callback before calling
set_interval_us() and initialising _time_stamp_last_loop. The moment the callback
is registered it fires (and Run() is scheduled) on the publisher's thread, racing
those writes - ThreadSanitizer flags both. Register the callback last, once the
object is fully initialised.
2026-07-02 08:42:49 -07:00
Julian Oes
65f58b3bfe refactor(uORB): use a dedicated SubscriptionIntervalAtomic for callbacks
_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).
2026-07-02 08:42:49 -07:00
Julian Oes
0aa25de0dd refactor(uORB): pass generation to SubscriptionCallback::call() to drop the _last_generation atomic
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.
2026-07-02 08:42:49 -07:00
Julian Oes
bffa69a554 test(microbench): add perf_counter microbenchmark
Times perf_count and perf_begin/perf_end in tight loops with hrt, plus a
plain non-atomic counter loop as reference, to quantify the overhead of
the atomic perf_counter change. Run with: microbench microbench_perf
2026-07-02 08:42:49 -07:00
Julian Oes
a4e8430407 fix(mavlink): protect mission shared static state with a mutex
MavlinkMissionManager keeps several pieces of mission state as static
class members so all instances share one view of the active mission:
_mission_dataman_id, _fence_dataman_id, _safepoint_dataman_id,
_current_seq, _count[], _crc32[]. Multiple instances' receiver
threads (and their send() ticks) read and write these concurrently,
which TSan flags.

Add a static pthread_mutex_t _shared_state_mutex (recursive, so the
nested call inside update_geofence_count -> update_active_mission is
fine) initialized once via pthread_once, and take it around every
read-modify-write or read-then-publish path:

  - update_active_mission
  - update_geofence_count
  - update_safepoint_count
  - send() (covers _current_seq / _count / _crc32 access)
  - check_active_mission (the publisher of the dataman bump)

Mutex declaration is in the public section so the pthread_once
initializer can reach it; it remains a class-level static.
2026-07-02 08:42:49 -07:00
Julian Oes
65ee670b82 fix(mavlink): fix more TSan races between task_main and receiver
Three closely-related races flagged by TSan with multi-instance
mavlink under SIH:

- _protocol_version is touched by both threads (task_main writes it
  in setProtocolVersion via mavlink_update_parameters; receiver
  reads it via getProtocolVersion and itself writes it on auto-
  upgrade to v2). Make it px4::atomic<uint8_t>.

- setProtocolVersion's get_status()->flags RMW touches the per-
  channel mavlink_channel_statuses[] global, which the receiver
  thread also reads/writes from mavlink_frame_char_buffer. Take
  lock_send() around the body, matching the existing pattern around
  mavlink_parse_char in the receiver.

- get_use_hil_gps() read _param_mav_usehilgps directly from the
  receiver thread while task_main was writing it via updateParams.
  Add an atomic snapshot _use_hil_gps_atomic, refresh it in
  mavlink_update_parameters(), and return the atomic load from the
  getter. Same pattern as _boot_complete and the other cross-thread
  state in this class.

Drive-by: the receiver's "upgrade to v2" check at the top of
MavlinkReceiver::run was reading _mavlink.get_status()->flags
outside lock_send(). mavlink_parse_char() copies the channel-
global flags into the receiver-local _status, so read _status.flags
instead — no lock needed and no race.
2026-07-02 08:42:49 -07:00
Julian Oes
b1a17ac355 fix(mavlink): pthread_once init for command_sender, try_lock in check_timeout
Two fixes for the singleton MavlinkCommandSender used across all
Mavlink instances:

1. initialize() races between instances. Multiple Mavlink::start()
   calls hit it concurrently; the existing nullptr check on _instance
   is not atomic, and re-running px4_sem_init on a semaphore another
   thread is already using is a TSan-flagged data race. Move the
   actual init into do_initialize() and call it through pthread_once,
   which is portable across POSIX and NuttX.

2. check_timeout() is invoked every Mavlink main loop tick on every
   instance. Under load they queue up on the singleton's lock and
   form a convoy that delays heartbeat sending. Switch to try_lock:
   if another instance is already inside, skip this cycle and retry
   on the next tick. The wait was never useful here — the work is
   "scan for timed-out commands", which is fine to defer one tick.
2026-07-02 08:42:49 -07:00