* [PATCH BlueZ v2 0/3] Add CS procedure data aggregation and D-Bus export
@ 2026-07-29 10:30 Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API Naga Bhavani Akella
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Naga Bhavani Akella @ 2026-07-29 10:30 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
This series adds support for collecting, aggregating, and exporting
Channel Sounding procedure data to userspace.
The first patch introduces a new procedure-data aggregation layer in
shared/rap. It tracks local initiator and remote reflector results for
each procedure, combines them once both sides are complete, and provides
a callback-based API for consumers.
The second patch builds on this infrastructure by serializing completed
CS procedure data into a binary blob and exposing it through a new
ProcedureData signal on the CS D-Bus interface. This allows external
ranging estimation daemons to consume raw controller measurement data
without requiring BlueZ to expose or maintain a fixed D-Bus structure
for evolving CS result formats.
The final patch updates the D-Bus API documentation to reflect the new
signal format
Naga Bhavani Akella (2):
profiles/ranging: Emit CS ProcedureData signal over D-Bus
doc: Update ProcedureData signal doc for byte-blob format
Prathibha Madugonde (1):
shared/rap: Add bcs_procedure_data aggregation and procedure data API
doc/org.bluez.ChannelSounding1.rst | 365 ++++++----------
profiles/ranging/rap.c | 285 +++++++++++-
profiles/ranging/rap_hci.c | 59 +++
src/shared/rap.c | 673 +++++++++++++++++++++++++++--
src/shared/rap.h | 50 +++
5 files changed, 1153 insertions(+), 279 deletions(-)
--
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH BlueZ v2 1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API
2026-07-29 10:30 [PATCH BlueZ v2 0/3] Add CS procedure data aggregation and D-Bus export Naga Bhavani Akella
@ 2026-07-29 10:30 ` Naga Bhavani Akella
2026-07-29 12:29 ` Add CS procedure data aggregation and D-Bus export bluez.test.bot
2026-07-29 10:30 ` [PATCH BlueZ v2 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 3/3] doc: Update ProcedureData signal doc for byte-blob format Naga Bhavani Akella
2 siblings, 1 reply; 5+ messages in thread
From: Naga Bhavani Akella @ 2026-07-29 10:30 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Prathibha Madugonde
From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
Define bcs_procedure_data to hold per-procedure CS results from both
the local initiator and remote reflector, including subevent step data,
selected TX powers, procedure enable config, CS config, sw_time values,
and BLE connection interval.
Add cs_proc_state with a 16-entry ring buffer in cstracker to track
procedures keyed by procedure counter, accumulating results
independently from each side until both report all-results-complete.
Update parse_mode_{0,1,2,3}, parse_step, and parse_subevent_steps with
output pointer parameters to capture remote reflector step data.
Introduce parse_cs_local_initiator_data() to store local HCI subevent
results, handling both initial and continuation events. Update
parse_ras_data_segments() to route decoded RAS subevent data into the
matching cs_proc_state as reflector results.
Add bt_rap_set_procedure_data_cb() to register a callback fired once
both local and remote results are complete for a given procedure.
Add bt_rap_set_local_sw_time(), bt_rap_set_remote_sw_time(), and
bt_rap_set_conn_interval() so callers such as rap_hci.c can supply the
CS capability sw_time and connection interval required by upper-layer
distance calculation algorithms.
Remove unwanted function rap_detached , fix for rap profile level
disconnections
---
src/shared/rap.c | 673 ++++++++++++++++++++++++++++++++++++++++++++---
src/shared/rap.h | 50 ++++
2 files changed, 687 insertions(+), 36 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index dfb272d3a..91cb6ec55 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -12,6 +12,8 @@
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
+#include <time.h>
+#include <glib.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
@@ -151,6 +153,16 @@ static inline uint8_t ranging_header_get_antenna_mask(
return hdr->antenna_pct & 0x0F;
}
+static inline uint16_t ranging_header_get_counter(
+ const struct ranging_header *hdr)
+{
+ if (!hdr)
+ return 0;
+
+ return (uint16_t)(hdr->counter_config[0] |
+ ((hdr->counter_config[1] & 0x0F) << 8));
+}
+
static inline uint8_t antenna_mask_count_paths(uint8_t antenna_mask)
{
uint8_t count = 0;
@@ -206,11 +218,23 @@ enum cs_role {
};
#define CS_INVALID_CONFIG_ID 0xFF
+#define CS_RANGING_COUNTER_MASK 0x0FFF
+
/* Minimal enums (align to controller values if needed) */
enum cs_procedure_done_status {
CS_PROC_ALL_RESULTS_COMPLETE = 0x00,
CS_PROC_PARTIAL_RESULTS = 0x01,
- CS_PROC_ABORTED = 0x02
+ CS_PROC_ABORTED = 0x0F
+};
+
+/* Per-procedure tracking — one entry per HCI procedure counter */
+struct cs_proc_state {
+ uint16_t proc_counter;
+ struct bcs_procedure_data bcs_data;
+ enum cs_procedure_done_status local_status;
+ bool local_has_complete_subevent;
+ enum cs_procedure_done_status remote_status;
+ bool remote_has_complete_subevent;
};
/* Main cs_procedure_data */
@@ -253,6 +277,12 @@ struct cstracker {
struct ranging_header ranging_header_;
/* Client - subsequent segments appended using iovec */
struct iovec segment_data;
+
+ /* Session-level config template and per-procedure state */
+ struct bcs_procedure_data bcs_proc_data;
+ struct queue *proc_states;
+ uint8_t procedure_sequence_after_enable;
+ uint64_t proc_start_timestamp_nanos;
};
/* Ranging Service context */
@@ -300,6 +330,15 @@ struct bt_rap {
bt_rap_destroy_func_t debug_destroy;
void *debug_data;
void *user_data;
+
+ bt_rap_procedure_data_func_t procedure_data_cb;
+ bt_rap_destroy_func_t procedure_data_destroy;
+ void *procedure_data_user_data;
+
+ /* CS capabilities sw_time and connection interval for config param */
+ uint8_t local_sw_time;
+ uint8_t remote_sw_time;
+ uint16_t conn_interval;
struct cstracker *resptracker;
struct cstracker *reqtracker;
};
@@ -542,6 +581,178 @@ static bool cs_pd_ras_commit_subevent(struct cs_procedure_data *d,
return true;
}
+/* ---------- bcs_procedure_data helpers ----------------------------------- */
+static uint64_t get_time_nanos(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+
+static void bcs_proc_data_clear(struct bcs_procedure_data *proc)
+{
+ uint32_t i;
+
+ if (!proc)
+ return;
+
+ for (i = 0; i < proc->initiator_subevent_count; i++)
+ free(proc->initiator_subevent_results[i].step_data);
+ free(proc->initiator_subevent_results);
+ proc->initiator_subevent_results = NULL;
+ proc->initiator_subevent_count = 0;
+
+ for (i = 0; i < proc->reflector_subevent_count; i++)
+ free(proc->reflector_subevent_results[i].step_data);
+ free(proc->reflector_subevent_results);
+ proc->reflector_subevent_results = NULL;
+ proc->reflector_subevent_count = 0;
+}
+
+static bool bcs_proc_data_add_initiator_subevent(
+ struct bcs_procedure_data *proc,
+ uint16_t start_acl_evt,
+ uint16_t freq_comp,
+ int8_t ref_pwr,
+ uint8_t num_ant_paths,
+ uint8_t abort_reason,
+ uint64_t timestamp_nanos,
+ struct cs_step_data *steps,
+ uint32_t num_steps)
+{
+ struct cs_subevent_result_data *arr;
+ uint32_t new_count;
+
+ if (!proc)
+ return false;
+
+ new_count = proc->initiator_subevent_count + 1;
+ arr = realloc(proc->initiator_subevent_results,
+ new_count * sizeof(*arr));
+ if (!arr) {
+ free(steps);
+ return false;
+ }
+
+ arr[new_count - 1].start_acl_conn_evt_counter = start_acl_evt;
+ arr[new_count - 1].freq_comp = freq_comp;
+ arr[new_count - 1].ref_pwr_lvl = ref_pwr;
+ arr[new_count - 1].num_ant_paths = num_ant_paths;
+ arr[new_count - 1].subevent_abort_reason = abort_reason;
+ arr[new_count - 1].timestamp_nanos = timestamp_nanos;
+ arr[new_count - 1].num_steps = num_steps;
+ arr[new_count - 1].step_data = steps;
+
+ proc->initiator_subevent_results = arr;
+ proc->initiator_subevent_count = new_count;
+ return true;
+}
+
+static bool bcs_proc_data_add_reflector_subevent(
+ struct bcs_procedure_data *proc,
+ uint16_t start_acl_evt,
+ uint16_t freq_comp,
+ int8_t ref_pwr,
+ uint8_t num_ant_paths,
+ uint8_t abort_reason,
+ uint64_t timestamp_nanos,
+ struct cs_step_data *steps,
+ uint32_t num_steps)
+{
+ struct cs_subevent_result_data *arr;
+ uint32_t new_count;
+
+ if (!proc)
+ return false;
+
+ new_count = proc->reflector_subevent_count + 1;
+ arr = realloc(proc->reflector_subevent_results,
+ new_count * sizeof(*arr));
+ if (!arr) {
+ free(steps);
+ return false;
+ }
+
+ arr[new_count - 1].start_acl_conn_evt_counter = start_acl_evt;
+ arr[new_count - 1].freq_comp = freq_comp;
+ arr[new_count - 1].ref_pwr_lvl = ref_pwr;
+ arr[new_count - 1].num_ant_paths = num_ant_paths;
+ arr[new_count - 1].subevent_abort_reason = abort_reason;
+ arr[new_count - 1].timestamp_nanos = timestamp_nanos;
+ arr[new_count - 1].num_steps = num_steps;
+ arr[new_count - 1].step_data = steps;
+
+ proc->reflector_subevent_results = arr;
+ proc->reflector_subevent_count = new_count;
+ return true;
+}
+
+static bool match_proc_counter(const void *data, const void *match_data)
+{
+ const struct cs_proc_state *s = data;
+ uint16_t proc_counter = PTR_TO_UINT(match_data);
+
+ return s->proc_counter == proc_counter;
+}
+
+static void free_proc_state(void *data)
+{
+ struct cs_proc_state *s = data;
+
+ bcs_proc_data_clear(&s->bcs_data);
+ free(s);
+}
+
+static struct cs_proc_state *find_or_create_proc_state(struct cstracker *t,
+ uint16_t proc_counter)
+{
+ struct cs_proc_state *s;
+
+ s = queue_find(t->proc_states, match_proc_counter,
+ UINT_TO_PTR(proc_counter));
+ if (s)
+ return s;
+
+ s = new0(struct cs_proc_state, 1);
+ s->proc_counter = proc_counter;
+ s->local_status = CS_PROC_PARTIAL_RESULTS;
+ s->remote_status = CS_PROC_PARTIAL_RESULTS;
+ /* Copy session-level config from template */
+ s->bcs_data.proc_enable_config = t->bcs_proc_data.proc_enable_config;
+ s->bcs_data.cs_config = t->bcs_proc_data.cs_config;
+ s->bcs_data.t_sw_time_us_supported_by_local =
+ t->bcs_proc_data.t_sw_time_us_supported_by_local;
+ s->bcs_data.t_sw_time_us_supported_by_remote =
+ t->bcs_proc_data.t_sw_time_us_supported_by_remote;
+ s->bcs_data.ble_conn_interval = t->bcs_proc_data.ble_conn_interval;
+ s->bcs_data.initiator_selected_tx_power =
+ t->bcs_proc_data.initiator_selected_tx_power;
+ s->bcs_data.procedure_counter = proc_counter;
+
+ queue_push_tail(t->proc_states, s);
+
+ return s;
+}
+
+static struct cs_proc_state *find_proc_state_for_ras(struct cstracker *t,
+ uint16_t ranging_counter)
+{
+ const struct queue_entry *entry;
+
+ for (entry = queue_get_entries(t->proc_states); entry;
+ entry = entry->next) {
+ struct cs_proc_state *s = entry->data;
+
+ if ((s->proc_counter & CS_RANGING_COUNTER_MASK) ==
+ ranging_counter)
+ return s;
+ }
+ return NULL;
+}
+
+/* ---------- end bcs helpers ---------------------------------------------- */
+
static struct ras *rap_get_ras(struct bt_rap *rap)
{
if (!rap)
@@ -556,14 +767,6 @@ static struct ras *rap_get_ras(struct bt_rap *rap)
return rap->rrapdb->ras;
}
-static void rap_detached(void *data, void *user_data)
-{
- struct bt_rap_cb *cb = data;
- struct bt_rap *rap = user_data;
-
- cb->detached(rap, cb->user_data);
-}
-
void bt_rap_detach(struct bt_rap *rap)
{
if (!queue_remove(sessions, rap))
@@ -572,8 +775,6 @@ void bt_rap_detach(struct bt_rap *rap)
bt_gatt_client_idle_unregister(rap->client, rap->idle_id);
bt_gatt_client_unref(rap->client);
rap->client = NULL;
-
- queue_foreach(bt_rap_cbs, rap_detached, rap);
}
static void rap_db_free(void *data)
@@ -608,15 +809,21 @@ static void rap_free(void *data)
rap_db_free(rap->rrapdb);
if (rap->resptracker) {
+ free(rap->resptracker->segment_data.iov_base);
free(rap->resptracker);
rap->resptracker = NULL;
}
if (rap->reqtracker) {
+ queue_destroy(rap->reqtracker->proc_states, free_proc_state);
+ free(rap->reqtracker->segment_data.iov_base);
free(rap->reqtracker);
rap->reqtracker = NULL;
}
+ if (rap->procedure_data_destroy)
+ rap->procedure_data_destroy(rap->procedure_data_user_data);
+
queue_destroy(rap->notify, free);
queue_destroy(rap->pending, NULL);
queue_destroy(rap->ready_cbs, rap_ready_free);
@@ -702,6 +909,24 @@ bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func,
return true;
}
+bool bt_rap_set_procedure_data_cb(struct bt_rap *rap,
+ bt_rap_procedure_data_func_t cb,
+ void *user_data,
+ bt_rap_destroy_func_t destroy)
+{
+ if (!rap)
+ return false;
+
+ if (rap->procedure_data_destroy)
+ rap->procedure_data_destroy(rap->procedure_data_user_data);
+
+ rap->procedure_data_cb = cb;
+ rap->procedure_data_destroy = destroy;
+ rap->procedure_data_user_data = user_data;
+
+ return true;
+}
+
static void cs_tracker_init(struct cstracker *t)
{
if (!t)
@@ -716,6 +941,7 @@ static void cs_tracker_init(struct cstracker *t)
t->last_start_acl_conn_evt_counter = 0;
t->last_freq_comp = 0;
t->last_ref_pwr_lvl = 0;
+ t->proc_states = queue_new();
/* Initialize ranging header using helper functions */
memset(&t->ranging_header_, 0, sizeof(t->ranging_header_));
@@ -1804,12 +2030,179 @@ static void form_ras_data_with_cs_subevent_result_cont(struct bt_rap *rap,
cont->step_data);
}
+static void write_procedure_data_to_bcs_algo(struct bt_rap *rap,
+ struct bcs_procedure_data *bcs)
+{
+ if (!rap || !bcs)
+ return;
+
+ if (rap->reqtracker && rap->reqtracker->proc_start_timestamp_nanos) {
+ uint64_t elapsed_nanos = get_time_nanos() -
+ rap->reqtracker->proc_start_timestamp_nanos;
+ uint32_t k;
+
+ DBG(rap, "Procedure elapsed time: %llu nanos",
+ (unsigned long long)elapsed_nanos);
+
+ for (k = 0; k < bcs->initiator_subevent_count; k++)
+ bcs->initiator_subevent_results[k].timestamp_nanos =
+ elapsed_nanos;
+ for (k = 0; k < bcs->reflector_subevent_count; k++)
+ bcs->reflector_subevent_results[k].timestamp_nanos =
+ elapsed_nanos;
+ }
+
+ DBG(rap, "procedure_counter=%u sequence=%u "
+ "init_tx_pwr=%d refl_tx_pwr=%d "
+ "init_abort=%d refl_abort=%d "
+ "init_subevents=%u refl_subevents=%u",
+ bcs->procedure_counter, bcs->procedure_sequence,
+ bcs->initiator_selected_tx_power,
+ bcs->reflector_selected_tx_power,
+ bcs->initiator_procedure_abort_reason,
+ bcs->reflector_procedure_abort_reason,
+ bcs->initiator_subevent_count,
+ bcs->reflector_subevent_count);
+
+ if (rap->procedure_data_cb)
+ rap->procedure_data_cb(rap, bcs,
+ rap->procedure_data_user_data);
+
+ bcs_proc_data_clear(bcs);
+}
+
+static void check_cs_procedure_complete(struct bt_rap *rap,
+ struct cstracker *reqtracker,
+ struct cs_proc_state *state)
+{
+ struct bcs_procedure_data *bcs = &state->bcs_data;
+
+ if (!rap->procedure_data_cb)
+ return;
+
+ if (state->local_status != CS_PROC_ALL_RESULTS_COMPLETE ||
+ state->remote_status != CS_PROC_ALL_RESULTS_COMPLETE) {
+ DBG(rap, "Procedure not complete: local=%d remote=%d",
+ state->local_status, state->remote_status);
+ return;
+ }
+
+ if (!state->local_has_complete_subevent &&
+ !state->remote_has_complete_subevent) {
+ DBG(rap, "No complete subevent available");
+ return;
+ }
+
+ reqtracker->procedure_sequence_after_enable++;
+ bcs->procedure_sequence = reqtracker->procedure_sequence_after_enable;
+ write_procedure_data_to_bcs_algo(rap, bcs);
+
+ queue_remove(reqtracker->proc_states, state);
+ free(state);
+}
+
+static void parse_cs_local_initiator_data(struct bt_rap *rap,
+ bool has_header_fields,
+ uint8_t config_id,
+ uint8_t num_ant_paths,
+ uint16_t proc_counter,
+ uint16_t start_acl_conn_evt_counter,
+ uint16_t freq_comp,
+ int8_t ref_pwr_lvl,
+ uint8_t proc_done_status,
+ uint8_t subevt_done_status,
+ uint8_t abort_reason,
+ uint8_t num_steps_reported,
+ const struct cs_step_data *hci_steps)
+{
+ struct cstracker *reqtracker = rap->reqtracker;
+ uint16_t effective_counter;
+ struct cs_proc_state *state;
+ struct bcs_procedure_data *bcs;
+
+ effective_counter = has_header_fields ? proc_counter
+ : reqtracker->last_proc_counter;
+
+ state = find_or_create_proc_state(reqtracker, effective_counter);
+ if (!state)
+ return;
+
+ bcs = &state->bcs_data;
+
+ bcs->initiator_selected_tx_power = reqtracker->selected_tx_power;
+ bcs->initiator_procedure_abort_reason = abort_reason & 0x0F;
+
+ state->local_status =
+ (enum cs_procedure_done_status)(proc_done_status & 0x0F);
+ if ((subevt_done_status & 0x0F) == 0x00)
+ state->local_has_complete_subevent = true;
+
+ if (has_header_fields) {
+ struct cs_step_data *steps = NULL;
+
+ reqtracker->proc_start_timestamp_nanos = get_time_nanos();
+
+ bcs->procedure_counter = proc_counter;
+ reqtracker->last_proc_counter = proc_counter;
+ reqtracker->last_start_acl_conn_evt_counter =
+ start_acl_conn_evt_counter;
+ reqtracker->last_freq_comp = freq_comp;
+ reqtracker->last_ref_pwr_lvl = ref_pwr_lvl;
+
+ if (num_steps_reported > 0 && hci_steps) {
+ steps = calloc(num_steps_reported, sizeof(*steps));
+ if (!steps)
+ return;
+ memcpy(steps, hci_steps,
+ num_steps_reported * sizeof(*steps));
+ }
+
+ bcs_proc_data_add_initiator_subevent(bcs,
+ start_acl_conn_evt_counter,
+ freq_comp, ref_pwr_lvl,
+ num_ant_paths,
+ (abort_reason >> 4) & 0x0F,
+ 0,
+ steps, num_steps_reported);
+ } else {
+ struct cs_subevent_result_data *last;
+ struct cs_step_data *new_steps;
+ uint32_t old_count, new_count;
+
+ if (!bcs->initiator_subevent_results ||
+ bcs->initiator_subevent_count == 0)
+ return;
+
+ last = &bcs->initiator_subevent_results[
+ bcs->initiator_subevent_count - 1];
+ old_count = last->num_steps;
+ new_count = old_count + num_steps_reported;
+
+ if (num_steps_reported > 0 && hci_steps) {
+ new_steps = realloc(last->step_data,
+ new_count * sizeof(*new_steps));
+ if (!new_steps)
+ return;
+
+ memcpy(&new_steps[old_count], hci_steps,
+ num_steps_reported * sizeof(*new_steps));
+ last->step_data = new_steps;
+ last->num_steps = new_count;
+ }
+
+ last->subevent_abort_reason = (abort_reason >> 4) & 0x0F;
+ }
+
+ check_cs_procedure_complete(rap, reqtracker, state);
+}
+
static void fill_initiator_data_from_cs_subevent_result_cont(struct bt_rap *rap,
const struct rap_ev_cs_subevent_result_cont *cont,
uint16_t length)
{
size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont,
step_data);
+ struct cstracker *reqtracker;
if (!rap || !rap->reqtracker || !cont)
return;
@@ -1819,6 +2212,22 @@ static void fill_initiator_data_from_cs_subevent_result_cont(struct bt_rap *rap,
DBG(rap, "Received CS subevent result continue subevent: len=%u",
length);
+
+ reqtracker = rap->reqtracker;
+
+ parse_cs_local_initiator_data(rap,
+ false,
+ cont->config_id,
+ cont->num_ant_paths,
+ reqtracker->last_proc_counter,
+ reqtracker->last_start_acl_conn_evt_counter,
+ reqtracker->last_freq_comp,
+ reqtracker->last_ref_pwr_lvl,
+ cont->proc_done_status,
+ cont->subevt_done_status,
+ cont->abort_reason,
+ cont->num_steps_reported,
+ cont->step_data);
}
static void fill_initiator_data_from_cs_subevent_result(struct bt_rap *rap,
@@ -1836,7 +2245,20 @@ static void fill_initiator_data_from_cs_subevent_result(struct bt_rap *rap,
return;
DBG(rap, "Received CS subevent result subevent: len=%u", length);
- /* TODO: Store initiator subevent result data */
+
+ parse_cs_local_initiator_data(rap,
+ true,
+ data->config_id,
+ data->num_ant_paths,
+ data->proc_counter,
+ data->start_acl_conn_evt_counter,
+ data->freq_comp,
+ data->ref_pwr_lvl,
+ data->proc_done_status,
+ data->subevt_done_status,
+ data->abort_reason,
+ data->num_steps_reported,
+ data->step_data);
}
void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
@@ -1880,6 +2302,7 @@ void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length,
const struct rap_ev_cs_proc_enable_cmplt *data = param;
struct bt_rap *rap = user_data;
struct cstracker *resptracker;
+ struct cstracker *reqtracker;
DBG(rap, "Received CS procedure enable complete subevent: len=%u",
length);
@@ -1895,6 +2318,21 @@ void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length,
/* Populate responder tracker */
resptracker->config_id = data->config_id;
resptracker->selected_tx_power = data->sel_tx_pwr;
+
+ if (!rap->reqtracker) {
+ reqtracker = new0(struct cstracker, 1);
+ cs_tracker_init(reqtracker);
+ rap->reqtracker = reqtracker;
+ }
+
+ reqtracker = rap->reqtracker;
+ reqtracker->config_id = data->config_id;
+ reqtracker->selected_tx_power = data->sel_tx_pwr;
+ reqtracker->bcs_proc_data.initiator_selected_tx_power =
+ data->sel_tx_pwr;
+
+ /* Store procedure enable config in the session-level template */
+ reqtracker->bcs_proc_data.proc_enable_config = *data;
}
void bt_rap_hci_cs_sec_enable_complete_callback(uint16_t length,
@@ -1945,6 +2383,39 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
reqtracker->config_id = data->config_id;
reqtracker->role = data->role;
reqtracker->rtt_type = data->rtt_type;
+ reqtracker->procedure_sequence_after_enable = 0;
+
+ /* Store cs_config in session-level template for procedure data */
+ reqtracker->bcs_proc_data.cs_config = *data;
+ reqtracker->bcs_proc_data.t_sw_time_us_supported_by_local =
+ rap->local_sw_time;
+ reqtracker->bcs_proc_data.t_sw_time_us_supported_by_remote =
+ rap->remote_sw_time;
+ reqtracker->bcs_proc_data.ble_conn_interval = rap->conn_interval;
+}
+
+void bt_rap_set_local_sw_time(struct bt_rap *rap, uint8_t local_sw_time)
+{
+ if (!rap)
+ return;
+
+ rap->local_sw_time = local_sw_time;
+}
+
+void bt_rap_set_remote_sw_time(struct bt_rap *rap, uint8_t remote_sw_time)
+{
+ if (!rap)
+ return;
+
+ rap->remote_sw_time = remote_sw_time;
+}
+
+void bt_rap_set_conn_interval(struct bt_rap *rap, uint16_t conn_interval)
+{
+ if (!rap)
+ return;
+
+ rap->conn_interval = conn_interval;
}
struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
@@ -2122,7 +2593,8 @@ static size_t get_mode_zero_length(enum cs_role remote_role)
}
static void parse_mode_zero(struct bt_rap *rap, struct iovec *mode_iov,
- enum cs_role remote_role)
+ enum cs_role remote_role,
+ struct cs_mode_zero_data *out)
{
uint8_t packet_quality;
int8_t packet_rssi_dbm;
@@ -2143,7 +2615,12 @@ static void parse_mode_zero(struct bt_rap *rap, struct iovec *mode_iov,
}
}
- /* TODO: Store this data as reflector data */
+ if (out) {
+ out->packet_quality = packet_quality;
+ out->packet_rssi_dbm = (uint8_t)packet_rssi_dbm;
+ out->packet_ant = packet_ant;
+ out->init_measured_freq_offset = init_measured_freq_offset;
+ }
}
static size_t get_mode_one_length(bool include_pct)
@@ -2154,7 +2631,8 @@ static size_t get_mode_one_length(bool include_pct)
}
static void parse_mode_one(struct bt_rap *rap, struct iovec *mode_iov,
- enum cs_role remote_role, bool include_pct)
+ enum cs_role remote_role, bool include_pct,
+ struct cs_mode_one_data *out)
{
uint8_t packet_quality;
uint8_t packet_nadm;
@@ -2178,7 +2656,20 @@ static void parse_mode_one(struct bt_rap *rap, struct iovec *mode_iov,
parse_i_q_sample(mode_iov, &pct2_i, &pct2_q);
}
- /* TODO: Store this data as reflector data */
+ if (out) {
+ out->packet_quality = packet_quality;
+ out->packet_nadm = packet_nadm;
+ out->packet_rssi_dbm = (uint8_t)packet_rssi_dbm;
+ out->packet_ant = packet_ant;
+ if (remote_role == CS_ROLE_REFLECTOR)
+ out->tod_toa_refl = time_value;
+ else
+ out->toa_tod_init = time_value;
+ out->packet_pct1.i_sample = pct1_i;
+ out->packet_pct1.q_sample = pct1_q;
+ out->packet_pct2.i_sample = pct2_i;
+ out->packet_pct2.q_sample = pct2_q;
+ }
}
static size_t get_mode_two_length(uint8_t num_antenna_paths)
@@ -2189,7 +2680,8 @@ static size_t get_mode_two_length(uint8_t num_antenna_paths)
}
static void parse_mode_two(struct bt_rap *rap, struct iovec *mode_iov,
- uint8_t num_antenna_paths)
+ uint8_t num_antenna_paths,
+ struct cs_mode_two_data *out)
{
uint8_t ant_perm_index;
int16_t tone_pct_i[5];
@@ -2228,7 +2720,14 @@ static void parse_mode_two(struct bt_rap *rap, struct iovec *mode_iov,
DBG(rap, " cs_mode_two_data: ant_perm_idx=%u",
ant_perm_index);
- /* TODO: Store this data as reflector data */
+ if (out) {
+ out->ant_perm_index = ant_perm_index;
+ for (k = 0; k < num_paths; k++) {
+ out->tone_pct[k].i_sample = tone_pct_i[k];
+ out->tone_pct[k].q_sample = tone_pct_q[k];
+ out->tone_quality_indicator[k] = tone_quality[k];
+ }
+ }
}
static size_t get_mode_three_length(uint8_t num_antenna_paths, bool include_pct)
@@ -2239,13 +2738,17 @@ static size_t get_mode_three_length(uint8_t num_antenna_paths, bool include_pct)
static void parse_mode_three(struct bt_rap *rap, struct iovec *mode_iov,
enum cs_role remote_role, bool include_pct,
- uint8_t num_antenna_paths)
+ uint8_t num_antenna_paths,
+ struct cs_mode_three_data *out)
{
+ struct cs_mode_one_data *out_m1 = out ? &out->mode_one_data : NULL;
+ struct cs_mode_two_data *out_m2 = out ? &out->mode_two_data : NULL;
+
/* Mode 3 = Mode 1 + Mode 2 */
- parse_mode_one(rap, mode_iov, remote_role, include_pct);
+ parse_mode_one(rap, mode_iov, remote_role, include_pct, out_m1);
if (mode_iov->iov_len > 0)
- parse_mode_two(rap, mode_iov, num_antenna_paths);
+ parse_mode_two(rap, mode_iov, num_antenna_paths, out_m2);
}
static bool parse_subevent_header(struct iovec *iov,
@@ -2278,7 +2781,8 @@ static bool parse_subevent_header(struct iovec *iov,
static bool parse_step(struct bt_rap *rap, struct iovec *iov,
struct cstracker *reqtracker,
- uint8_t num_antenna_paths, uint8_t step_idx)
+ uint8_t num_antenna_paths, uint8_t step_idx,
+ struct cs_step_data *out_step)
{
uint8_t mode_byte, step_mode;
bool include_pct;
@@ -2337,20 +2841,42 @@ static bool parse_step(struct bt_rap *rap, struct iovec *iov,
mode_iov.iov_base = payload;
mode_iov.iov_len = step_payload_len;
+ if (out_step) {
+ out_step->step_mode = step_mode;
+ out_step->step_chnl = 0;
+ out_step->step_data_length = (uint8_t)step_payload_len;
+ }
+
switch (step_mode) {
- case CS_MODE_ZERO:
- parse_mode_zero(rap, &mode_iov, remote_role);
+ case CS_MODE_ZERO: {
+ struct cs_mode_zero_data *out = out_step ?
+ &out_step->step_mode_data.mode_zero_data : NULL;
+
+ parse_mode_zero(rap, &mode_iov, remote_role, out);
break;
- case CS_MODE_ONE:
- parse_mode_one(rap, &mode_iov, remote_role, include_pct);
+ }
+ case CS_MODE_ONE: {
+ struct cs_mode_one_data *out = out_step ?
+ &out_step->step_mode_data.mode_one_data : NULL;
+
+ parse_mode_one(rap, &mode_iov, remote_role, include_pct, out);
break;
- case CS_MODE_TWO:
- parse_mode_two(rap, &mode_iov, num_antenna_paths);
+ }
+ case CS_MODE_TWO: {
+ struct cs_mode_two_data *out = out_step ?
+ &out_step->step_mode_data.mode_two_data : NULL;
+
+ parse_mode_two(rap, &mode_iov, num_antenna_paths, out);
break;
- case CS_MODE_THREE:
+ }
+ case CS_MODE_THREE: {
+ struct cs_mode_three_data *out = out_step ?
+ &out_step->step_mode_data.mode_three_data : NULL;
+
parse_mode_three(rap, &mode_iov, remote_role, include_pct,
- num_antenna_paths);
+ num_antenna_paths, out);
break;
+ }
default:
break;
}
@@ -2360,12 +2886,16 @@ static bool parse_step(struct bt_rap *rap, struct iovec *iov,
static void parse_subevent_steps(struct bt_rap *rap, struct iovec *iov,
struct cstracker *reqtracker,
- uint8_t num_antenna_paths, uint8_t num_steps)
+ uint8_t num_antenna_paths, uint8_t num_steps,
+ struct cs_step_data *out_steps)
{
uint8_t i;
for (i = 0; i < num_steps; i++) {
- if (!parse_step(rap, iov, reqtracker, num_antenna_paths, i))
+ struct cs_step_data *out = out_steps ? &out_steps[i] : NULL;
+
+ if (!parse_step(rap, iov, reqtracker, num_antenna_paths, i,
+ out))
break;
}
}
@@ -2376,6 +2906,10 @@ static void parse_ras_data_segments(struct bt_rap *rap,
struct iovec iov;
uint8_t antenna_mask;
uint8_t num_antenna_paths;
+ uint16_t ranging_counter;
+ struct cs_proc_state *state = NULL;
+ struct bcs_procedure_data *bcs = NULL;
+ bool is_initiator;
if (!rap || !reqtracker)
return;
@@ -2387,10 +2921,30 @@ static void parse_ras_data_segments(struct bt_rap *rap,
ranging_header_get_antenna_mask(&reqtracker->ranging_header_);
num_antenna_paths = antenna_mask_count_paths(antenna_mask);
+ ranging_counter =
+ ranging_header_get_counter(&reqtracker->ranging_header_);
+
+ /* Find the per-procedure state that matches this RAS counter */
+ if (rap->procedure_data_cb) {
+ state = find_proc_state_for_ras(reqtracker, ranging_counter);
+ if (state) {
+ bcs = &state->bcs_data;
+ bcs->reflector_selected_tx_power =
+ reqtracker->ranging_header_.selected_tx_power;
+ }
+ }
+
+ /* When local role=INITIATOR, remote data goes to
+ * reflector_subevent_results. When local role=REFLECTOR,
+ * remote data goes to initiator_subevent_results.
+ */
+ is_initiator = (reqtracker->role == CS_ROLE_INITIATOR);
+
iov = reqtracker->segment_data;
while (iov.iov_len >= RAS_SUBEVENT_HEADER_SIZE) {
struct ras_subevent_header hdr;
+ struct cs_step_data *steps = NULL;
if (!parse_subevent_header(&iov, &hdr))
break;
@@ -2402,19 +2956,66 @@ static void parse_ras_data_segments(struct bt_rap *rap,
hdr.reference_power_level,
hdr.num_steps_reported);
+ if (bcs && hdr.num_steps_reported > 0)
+ steps = calloc(hdr.num_steps_reported, sizeof(*steps));
+
parse_subevent_steps(rap, &iov, reqtracker,
num_antenna_paths,
- hdr.num_steps_reported);
+ hdr.num_steps_reported,
+ steps);
+
+ if (bcs) {
+ uint32_t stored_steps =
+ steps ? hdr.num_steps_reported : 0;
+
+ if (is_initiator)
+ bcs_proc_data_add_reflector_subevent(bcs,
+ hdr.start_acl_conn_event,
+ hdr.frequency_compensation,
+ hdr.reference_power_level,
+ num_antenna_paths,
+ hdr.subevent_abort_reason,
+ 0,
+ steps, stored_steps);
+ else
+ bcs_proc_data_add_initiator_subevent(bcs,
+ hdr.start_acl_conn_event,
+ hdr.frequency_compensation,
+ hdr.reference_power_level,
+ num_antenna_paths,
+ hdr.subevent_abort_reason,
+ 0,
+ steps, stored_steps);
+ steps = NULL; /* ownership transferred */
+ } else {
+ free(steps);
+ steps = NULL;
+ }
- if (hdr.subevent_done_status ==
- SUBEVENT_DONE_ALL_RESULTS_COMPLETE ||
- hdr.ranging_done_status ==
+ if (state) {
+ if (hdr.subevent_done_status ==
+ SUBEVENT_DONE_ALL_RESULTS_COMPLETE)
+ state->remote_has_complete_subevent = true;
+ if (hdr.ranging_done_status ==
+ RANGING_DONE_ALL_RESULTS_COMPLETE)
+ state->remote_status =
+ CS_PROC_ALL_RESULTS_COMPLETE;
+ }
+
+ if (bcs && hdr.ranging_abort_reason)
+ bcs->reflector_procedure_abort_reason =
+ hdr.ranging_abort_reason & 0x0F;
+
+ if (hdr.ranging_done_status ==
RANGING_DONE_ALL_RESULTS_COMPLETE) {
DBG(rap, "Ranging procedure complete");
break;
}
}
+ if (state)
+ check_cs_procedure_complete(rap, reqtracker, state);
+
free(reqtracker->segment_data.iov_base);
reqtracker->segment_data.iov_base = NULL;
reqtracker->segment_data.iov_len = 0;
diff --git a/src/shared/rap.h b/src/shared/rap.h
index 5582635e6..342b504ea 100644
--- a/src/shared/rap.h
+++ b/src/shared/rap.h
@@ -157,10 +157,45 @@ struct rap_ev_cs_subevent_result_cont {
struct cs_step_data step_data[];
};
+struct cs_subevent_result_data {
+ uint16_t start_acl_conn_evt_counter;
+ uint16_t freq_comp;
+ int8_t ref_pwr_lvl;
+ uint8_t num_ant_paths;
+ uint8_t subevent_abort_reason;
+ uint64_t timestamp_nanos;
+ uint32_t num_steps;
+ struct cs_step_data *step_data;
+};
+
+struct bcs_procedure_data {
+ uint16_t procedure_counter;
+ uint16_t procedure_sequence;
+
+ int8_t initiator_selected_tx_power;
+ int8_t reflector_selected_tx_power;
+
+ struct cs_subevent_result_data *initiator_subevent_results;
+ uint32_t initiator_subevent_count;
+ uint8_t initiator_procedure_abort_reason;
+
+ struct cs_subevent_result_data *reflector_subevent_results;
+ uint32_t reflector_subevent_count;
+ uint8_t reflector_procedure_abort_reason;
+
+ struct rap_ev_cs_proc_enable_cmplt proc_enable_config;
+ struct rap_ev_cs_config_cmplt cs_config;
+ uint8_t t_sw_time_us_supported_by_local;
+ uint8_t t_sw_time_us_supported_by_remote;
+ uint16_t ble_conn_interval;
+};
typedef void (*bt_rap_debug_func_t)(const char *str, void *user_data);
typedef void (*bt_rap_ready_func_t)(struct bt_rap *rap, void *user_data);
typedef void (*bt_rap_destroy_func_t)(void *user_data);
typedef void (*bt_rap_func_t)(struct bt_rap *rap, void *user_data);
+typedef void (*bt_rap_procedure_data_func_t)(struct bt_rap *rap,
+ struct bcs_procedure_data *data,
+ void *user_data);
struct bt_rap *bt_rap_ref(struct bt_rap *rap);
void bt_rap_unref(struct bt_rap *rap);
@@ -177,6 +212,10 @@ bool bt_rap_set_user_data(struct bt_rap *rap, void *user_data);
bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func,
void *user_data, bt_rap_destroy_func_t destroy);
+bool bt_rap_set_procedure_data_cb(struct bt_rap *rap,
+ bt_rap_procedure_data_func_t cb,
+ void *user_data,
+ bt_rap_destroy_func_t destroy);
/* session related functions */
unsigned int bt_rap_register(bt_rap_func_t attached, bt_rap_func_t detached,
void *user_data);
@@ -228,6 +267,10 @@ void *bt_rap_attach_hci(struct bt_rap *rap, struct bt_hci *hci,
int8_t max_tx_power);
void bt_rap_detach_hci(struct bt_rap *rap, void *hci_sm);
+bool bt_rap_hci_set_procedure_data_cb(void *hci_sm,
+ bt_rap_procedure_data_func_t cb,
+ void *user_data,
+ bt_rap_destroy_func_t destroy);
/* Connection handle mapping functions */
bool bt_rap_set_conn_hndl(void *hci_sm,
struct bt_rap *rap,
@@ -245,3 +288,10 @@ void bt_rap_set_timeout_cb(void *hci_sm, void (*func)(void *),
void bt_rap_set_proc_active_cb(void *hci_sm, void (*func)(bool, void *),
void *user_data);
+
+/* CS capability sw_time and connection interval setters */
+void bt_rap_set_local_sw_time(struct bt_rap *rap, uint8_t local_sw_time);
+
+void bt_rap_set_remote_sw_time(struct bt_rap *rap, uint8_t remote_sw_time);
+
+void bt_rap_set_conn_interval(struct bt_rap *rap, uint16_t conn_interval);
--
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH BlueZ v2 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus
2026-07-29 10:30 [PATCH BlueZ v2 0/3] Add CS procedure data aggregation and D-Bus export Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API Naga Bhavani Akella
@ 2026-07-29 10:30 ` Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 3/3] doc: Update ProcedureData signal doc for byte-blob format Naga Bhavani Akella
2 siblings, 0 replies; 5+ messages in thread
From: Naga Bhavani Akella @ 2026-07-29 10:30 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Serialize completed BCS procedure data (subevent/step results,
CS config, and procedure-enable parameters) into a byte blob and
emit it via a new ProcedureData D-Bus signal on the CS interface,
so an external ranging estimation daemon can consume raw controller
measurements
---
profiles/ranging/rap.c | 285 ++++++++++++++++++++++++++++++++++++-
profiles/ranging/rap_hci.c | 59 ++++++++
2 files changed, 343 insertions(+), 1 deletion(-)
diff --git a/profiles/ranging/rap.c b/profiles/ranging/rap.c
index 082c35558..1a5efb025 100644
--- a/profiles/ranging/rap.c
+++ b/profiles/ranging/rap.c
@@ -380,6 +380,274 @@ static const struct cs_dict_param_desc *cs_find_dict_param_desc(
return NULL;
}
+/*
+ * ProcedureData is emitted as a single opaque byte blob rather than an
+ * a{sv} dict: every field here is raw controller measurement data with
+ * no standalone meaning, consumed only by an external ranging estimation
+ * daemon that immediately unpacks it again. See
+ * doc/org.bluez.ChannelSounding1.rst for the documented binary layout.
+ *
+ * The blob's size is unbounded (variable subevent/step counts), so every
+ * field is appended with util_iov_append(), which reallocs as needed --
+ * unlike util_iov_push_*(), which assumes a pre-sized buffer.
+ */
+static void blob_push_u8(struct iovec *buf, uint8_t val)
+{
+ util_iov_append(buf, &val, sizeof(val));
+}
+
+static void blob_push_le16(struct iovec *buf, uint16_t val)
+{
+ uint8_t tmp[2];
+
+ put_le16(val, tmp);
+ util_iov_append(buf, tmp, sizeof(tmp));
+}
+
+static void blob_push_le32(struct iovec *buf, uint32_t val)
+{
+ uint8_t tmp[4];
+
+ put_le32(val, tmp);
+ util_iov_append(buf, tmp, sizeof(tmp));
+}
+
+static void blob_push_le64(struct iovec *buf, uint64_t val)
+{
+ uint8_t tmp[8];
+
+ put_le64(val, tmp);
+ util_iov_append(buf, tmp, sizeof(tmp));
+}
+
+static void serialize_mode_zero(struct iovec *buf,
+ const struct cs_mode_zero_data *m0)
+{
+ blob_push_u8(buf, m0->packet_quality);
+ blob_push_u8(buf, m0->packet_rssi_dbm);
+ blob_push_u8(buf, m0->packet_ant);
+ blob_push_le16(buf, m0->init_measured_freq_offset);
+}
+
+static void serialize_mode_one(struct iovec *buf,
+ const struct cs_mode_one_data *m1)
+{
+ blob_push_u8(buf, m1->packet_quality);
+ blob_push_u8(buf, m1->packet_nadm);
+ blob_push_u8(buf, m1->packet_rssi_dbm);
+ blob_push_le16(buf, (uint16_t)m1->toa_tod_init);
+ blob_push_le16(buf, (uint16_t)m1->tod_toa_refl);
+ blob_push_u8(buf, m1->packet_ant);
+ blob_push_le16(buf, (uint16_t)m1->packet_pct1.i_sample);
+ blob_push_le16(buf, (uint16_t)m1->packet_pct1.q_sample);
+ blob_push_le16(buf, (uint16_t)m1->packet_pct2.i_sample);
+ blob_push_le16(buf, (uint16_t)m1->packet_pct2.q_sample);
+}
+
+static void serialize_mode_two(struct iovec *buf,
+ const struct cs_mode_two_data *m2,
+ uint8_t num_ant_paths)
+{
+ int num_paths;
+ int j;
+
+ /*
+ * num_ant_paths is the HCI "number of antenna paths" value
+ * (0-indexed), so actual tone sample count = num_ant_paths + 1,
+ * capped at array size.
+ */
+ num_paths = (num_ant_paths + 1) < CS_MAX_ANT_PATHS ?
+ (num_ant_paths + 1) : CS_MAX_ANT_PATHS;
+
+ blob_push_u8(buf, m2->ant_perm_index);
+
+ for (j = 0; j < num_paths; j++) {
+ blob_push_le16(buf, (uint16_t)m2->tone_pct[j].i_sample);
+ blob_push_le16(buf, (uint16_t)m2->tone_pct[j].q_sample);
+ }
+
+ for (j = 0; j < num_paths; j++)
+ blob_push_u8(buf, m2->tone_quality_indicator[j]);
+}
+
+static void serialize_proc_enable_config(struct iovec *buf,
+ const struct rap_ev_cs_proc_enable_cmplt *cfg)
+{
+ uint32_t sub_evt_len_us;
+
+ sub_evt_len_us = cfg->sub_evt_len[0] |
+ ((uint32_t)cfg->sub_evt_len[1] << 8) |
+ ((uint32_t)cfg->sub_evt_len[2] << 16);
+
+ blob_push_u8(buf, cfg->tone_ant_config_sel);
+ blob_push_le32(buf, sub_evt_len_us);
+ blob_push_u8(buf, cfg->sub_evts_per_evt);
+ blob_push_le16(buf, cfg->sub_evt_intrvl);
+ blob_push_le16(buf, cfg->evt_intrvl);
+ blob_push_le16(buf, cfg->proc_intrvl);
+ blob_push_le16(buf, cfg->proc_counter);
+ blob_push_le16(buf, cfg->max_proc_len);
+}
+
+static void serialize_cs_config_param(struct iovec *buf,
+ const struct bcs_procedure_data *bcs)
+{
+ const struct rap_ev_cs_config_cmplt *cfg = &bcs->cs_config;
+
+ blob_push_u8(buf, cfg->main_mode_type);
+ blob_push_u8(buf, cfg->sub_mode_type);
+ blob_push_u8(buf, cfg->rtt_type);
+ util_iov_append(buf, cfg->channel_map, sizeof(cfg->channel_map));
+ blob_push_u8(buf, cfg->min_main_mode_steps);
+ blob_push_u8(buf, cfg->max_main_mode_steps);
+ blob_push_u8(buf, cfg->main_mode_rep);
+ blob_push_u8(buf, cfg->mode_0_steps);
+ blob_push_u8(buf, cfg->role);
+ blob_push_u8(buf, cfg->cs_sync_phy);
+ blob_push_u8(buf, cfg->channel_sel_type);
+ blob_push_u8(buf, cfg->ch3c_shape);
+ blob_push_u8(buf, cfg->ch3c_jump);
+ blob_push_u8(buf, cfg->channel_map_rep);
+ blob_push_u8(buf, cfg->t_ip1_time);
+ blob_push_u8(buf, cfg->t_ip2_time);
+ blob_push_u8(buf, cfg->t_fcs_time);
+ blob_push_u8(buf, cfg->t_pm_time);
+ blob_push_u8(buf, bcs->t_sw_time_us_supported_by_local);
+ blob_push_u8(buf, bcs->t_sw_time_us_supported_by_remote);
+ blob_push_le16(buf, bcs->ble_conn_interval);
+}
+
+static void serialize_step(struct iovec *buf,
+ const struct cs_step_data *step,
+ uint8_t num_ant_paths)
+{
+ blob_push_u8(buf, step->step_mode);
+ blob_push_u8(buf, step->step_chnl);
+
+ switch (step->step_mode) {
+ case CS_MODE_ZERO:
+ serialize_mode_zero(buf, &step->step_mode_data.mode_zero_data);
+ break;
+
+ case CS_MODE_ONE:
+ serialize_mode_one(buf, &step->step_mode_data.mode_one_data);
+ break;
+
+ case CS_MODE_TWO:
+ serialize_mode_two(buf, &step->step_mode_data.mode_two_data,
+ num_ant_paths);
+ break;
+
+ case CS_MODE_THREE:
+ serialize_mode_one(buf,
+ &step->step_mode_data.mode_three_data.mode_one_data);
+ serialize_mode_two(buf,
+ &step->step_mode_data.mode_three_data.mode_two_data,
+ num_ant_paths);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void serialize_subevent(struct iovec *buf,
+ const struct cs_subevent_result_data *sub)
+{
+ uint32_t i;
+
+ blob_push_le16(buf, sub->start_acl_conn_evt_counter);
+ blob_push_le16(buf, sub->freq_comp);
+ blob_push_u8(buf, (uint8_t)sub->ref_pwr_lvl);
+ blob_push_u8(buf, sub->num_ant_paths);
+ blob_push_u8(buf, sub->subevent_abort_reason);
+ blob_push_le64(buf, sub->timestamp_nanos);
+ blob_push_le32(buf, sub->num_steps);
+
+ if (!sub->step_data)
+ return;
+
+ for (i = 0; i < sub->num_steps; i++)
+ serialize_step(buf, &sub->step_data[i], sub->num_ant_paths);
+}
+
+static void serialize_subevent_array(struct iovec *buf,
+ const struct cs_subevent_result_data *subevents,
+ uint32_t count)
+{
+ uint32_t i;
+
+ for (i = 0; i < count; i++)
+ serialize_subevent(buf, &subevents[i]);
+}
+
+static void rap_emit_procedure_data(struct rap_data *data,
+ const struct bcs_procedure_data *bcs)
+{
+ DBusMessage *signal;
+ DBusMessageIter iter, array;
+ struct iovec blob = { 0 };
+ const uint8_t *ptr;
+
+ signal = dbus_message_new_signal(device_get_path(data->device),
+ CS_INTERFACE, "ProcedureData");
+ if (!signal) {
+ error("Failed to allocate ProcedureData signal");
+ return;
+ }
+
+ blob_push_le16(&blob, bcs->procedure_counter);
+ blob_push_le16(&blob, bcs->procedure_sequence);
+ blob_push_u8(&blob, (uint8_t)bcs->initiator_selected_tx_power);
+ blob_push_u8(&blob, (uint8_t)bcs->reflector_selected_tx_power);
+
+ if (!bcs->initiator_subevent_results) {
+ blob_push_le32(&blob, 0);
+ } else {
+ blob_push_le32(&blob, bcs->initiator_subevent_count);
+ serialize_subevent_array(&blob,
+ bcs->initiator_subevent_results,
+ bcs->initiator_subevent_count);
+ }
+
+ blob_push_u8(&blob, bcs->initiator_procedure_abort_reason);
+
+ if (!bcs->reflector_subevent_results) {
+ blob_push_le32(&blob, 0);
+ } else {
+ blob_push_le32(&blob, bcs->reflector_subevent_count);
+ serialize_subevent_array(&blob,
+ bcs->reflector_subevent_results,
+ bcs->reflector_subevent_count);
+ }
+
+ blob_push_u8(&blob, bcs->reflector_procedure_abort_reason);
+
+ serialize_proc_enable_config(&blob, &bcs->proc_enable_config);
+ serialize_cs_config_param(&blob, bcs);
+
+ dbus_message_iter_init_append(signal, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array);
+ ptr = blob.iov_base;
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &ptr,
+ blob.iov_len);
+ dbus_message_iter_close_container(&iter, &array);
+
+ g_dbus_send_message(btd_get_dbus_connection(), signal);
+
+ free(blob.iov_base);
+}
+
+static void rap_procedure_data(struct bt_rap *rap,
+ struct bcs_procedure_data *bcs,
+ void *user_data)
+{
+ struct rap_data *data = user_data;
+
+ DBG("procedure_counter=%u", bcs->procedure_counter);
+ rap_emit_procedure_data(data, bcs);
+}
+
static DBusMessage *start_measurement(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
@@ -535,6 +803,9 @@ bad_type:
data->active_session.cfg = cfg;
data->active_session.freq = freq;
+ bt_rap_hci_set_procedure_data_cb(data->hci_sm, rap_procedure_data,
+ data, NULL);
+
return dbus_message_new_method_return(msg);
}
@@ -552,6 +823,8 @@ static DBusMessage *stop_measurement(DBusConnection *conn,
return g_dbus_create_error(msg, DBUS_ERROR_FAILED,
"Stop measurement failed");
+ bt_rap_hci_set_procedure_data_cb(data->hci_sm, NULL, NULL, NULL);
+
memset(&data->active_session, 0, sizeof(data->active_session));
g_dbus_emit_property_changed(btd_get_dbus_connection(),
@@ -589,6 +862,11 @@ static const GDBusPropertyTable cs_dbus_properties[] = {
{ }
};
+static const GDBusSignalTable cs_dbus_signals[] = {
+ { GDBUS_SIGNAL("ProcedureData", GDBUS_ARGS({ "data", "ay" })) },
+ { }
+};
+
static void rap_measurement_timeout_cb(void *user_data)
{
struct rap_data *data = user_data;
@@ -753,7 +1031,8 @@ static int rap_accept(struct btd_service *service)
g_dbus_register_interface(btd_get_dbus_connection(),
device_get_path(data->device),
CS_INTERFACE, cs_dbus_methods,
- NULL, cs_dbus_properties, data, NULL);
+ cs_dbus_signals, cs_dbus_properties,
+ data, NULL);
return 0;
}
@@ -775,6 +1054,10 @@ static int rap_disconnect(struct btd_service *service)
data->conn_handle = 0;
}
+ if (data->hci_sm)
+ bt_rap_hci_set_procedure_data_cb(data->hci_sm, NULL, NULL,
+ NULL);
+
memset(&data->active_session, 0, sizeof(data->active_session));
btd_service_disconnecting_complete(service, 0);
diff --git a/profiles/ranging/rap_hci.c b/profiles/ranging/rap_hci.c
index 58ccf87bf..6f00c31fb 100644
--- a/profiles/ranging/rap_hci.c
+++ b/profiles/ranging/rap_hci.c
@@ -443,6 +443,8 @@ static void rap_rd_loc_supp_cap_done_cb(const void *data, uint8_t size,
DBG("Sending read remote capabilities for handle 0x%04X",
sm->active_conn_handle);
bt_rap_read_remote_supported_capabilities(sm, sm->active_conn_handle);
+
+ bt_rap_set_local_sw_time(sm->rap, rsp->t_sw_time_supported);
}
static void rap_send_hci_cs_create_config_command(struct cs_state_machine *sm,
@@ -879,6 +881,8 @@ static void rap_rd_rmt_supp_cap_cmplt_evt(const void *data, uint8_t size,
cs_set_state(sm, CS_STATE_INIT);
rap_send_hci_def_settings_command(sm, evt);
}
+
+ bt_rap_set_remote_sw_time(sm->rap, evt->t_sw_time_supported);
}
static void rap_cs_config_cmplt_evt(const void *data, uint8_t size,
@@ -1169,6 +1173,46 @@ static void rap_cs_proc_enable_cmplt_evt(const void *data, uint8_t size,
&rap_ev, sm->rap);
}
+static void rap_le_conn_update_complete_evt(const void *data, uint8_t size,
+ void *user_data)
+{
+ struct cs_state_machine *sm = user_data;
+ const struct bt_hci_evt_le_conn_update_complete *evt;
+ struct rap_conn_mapping *mapping;
+ struct bt_rap *rap;
+ struct iovec iov;
+
+ if (!sm || !data ||
+ size < sizeof(struct bt_hci_evt_le_conn_update_complete))
+ return;
+
+ iov.iov_base = (void *)data;
+ iov.iov_len = size;
+
+ evt = util_iov_pull_mem(&iov, sizeof(*evt));
+ if (!evt) {
+ error("Failed to pull LE conn update complete struct");
+ return;
+ }
+
+ DBG("status=0x%02X handle=0x%04X interval=%u",
+ evt->status, evt->handle, evt->interval);
+
+ if (evt->status != 0)
+ return;
+
+ mapping = find_mapping_by_handle(sm, evt->handle);
+ if (mapping && mapping->rap) {
+ DBG("Found handle 0x%04X in mapping cache", evt->handle);
+ rap = mapping->rap;
+ } else {
+ DBG("No RAP mapping for handle 0x%04X, ignoring", evt->handle);
+ return;
+ }
+
+ bt_rap_set_conn_interval(rap, evt->interval);
+}
+
static void parse_i_q_sample(struct iovec *iov, int16_t *i_sample,
int16_t *q_sample)
{
@@ -1669,6 +1713,8 @@ void *bt_rap_attach_hci(struct bt_rap *rap, struct bt_hci *hci,
rap_cs_subevt_result_evt },
{ BT_HCI_EVT_LE_CS_SUBEVENT_RESULT_CONTINUE,
rap_cs_subevt_result_cont_evt },
+ { BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE,
+ rap_le_conn_update_complete_evt },
};
struct cs_state_machine *sm;
unsigned int i;
@@ -1788,6 +1834,19 @@ bool bt_rap_stop_measurement(void *hci_sm)
false);
}
+bool bt_rap_hci_set_procedure_data_cb(void *hci_sm,
+ bt_rap_procedure_data_func_t cb,
+ void *user_data,
+ bt_rap_destroy_func_t destroy)
+{
+ struct cs_state_machine *sm = hci_sm;
+
+ if (!sm || !sm->rap)
+ return false;
+
+ return bt_rap_set_procedure_data_cb(sm->rap, cb, user_data, destroy);
+}
+
bool bt_rap_set_conn_hndl(void *hci_sm, struct bt_rap *rap,
uint16_t handle, const uint8_t *bdaddr, uint8_t bdaddr_type,
bool is_central)
--
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH BlueZ v2 3/3] doc: Update ProcedureData signal doc for byte-blob format
2026-07-29 10:30 [PATCH BlueZ v2 0/3] Add CS procedure data aggregation and D-Bus export Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus Naga Bhavani Akella
@ 2026-07-29 10:30 ` Naga Bhavani Akella
2 siblings, 0 replies; 5+ messages in thread
From: Naga Bhavani Akella @ 2026-07-29 10:30 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
Document the create_context CS configuration parameter added
alongside the CS Create Config command.
Update the ProcedureData signal signature from dict to array{byte},
matching the byte-blob serialization now used in rap.c, and drop
the per-field dict documentation (procedureCounter, subevent
results, csConfigParam, etc.) that no longer applies since the
signal now carries an opaque binary blob instead of a structured
dict.
---
doc/org.bluez.ChannelSounding1.rst | 365 ++++++++++-------------------
1 file changed, 123 insertions(+), 242 deletions(-)
diff --git a/doc/org.bluez.ChannelSounding1.rst b/doc/org.bluez.ChannelSounding1.rst
index c06e1d728..95d6bc7b2 100644
--- a/doc/org.bluez.ChannelSounding1.rst
+++ b/doc/org.bluez.ChannelSounding1.rst
@@ -82,6 +82,13 @@ Supported dictionary keys:
Maximum TX power in dBm, treated as a signed value. Valid
range is -127 to +20 dBm.
+:byte create_context (Default: 0x01):
+
+ Controls where the CS configuration is written. Set to 0x00 to
+ write the configuration only to the local Controller. Set to
+ 0x01 to write it to both the local and remote Controllers using
+ the CS Configuration procedure.
+
:byte config_id:
CS configuration identifier.
@@ -254,254 +261,128 @@ Examples:
Signals
-------
-void ProcedureData(dict data)
-``````````````````````````````
+void ProcedureData(array{byte} data)
+`````````````````````````````````````
Emitted when a Channel Sounding measurement procedure completes on this
device, carrying the raw CS procedure results as reported by the
controller. Consumers such as an external ranging estimation daemon
subscribe to this signal to compute distance estimates.
-:dict data:
-
- :int32 procedureCounter:
-
- Procedure counter value from the controller.
-
- :int32 procedureSequence:
-
- Sequence number of this procedure.
-
- :byte initiatorSelectedTxPower:
-
- TX power selected by the Initiator, treated as a signed
- value.
-
- :byte reflectorSelectedTxPower:
-
- TX power selected by the Reflector, treated as a signed
- value.
-
- :uint32 initiatorSubeventCount:
-
- Number of subevent results reported by the Initiator.
-
- :array{dict} initiatorSubeventResults:
-
- Present only when ``initiatorSubeventCount`` is greater
- than 0. One entry per Initiator subevent, each with the
- fields described in `Subevent Result`_ below.
-
- :byte initiatorProcedureAbortReason:
-
- Reason the Initiator's procedure was aborted, 0 if not
- aborted.
-
- :uint32 reflectorSubeventCount:
-
- Number of subevent results reported by the Reflector.
-
- :array{dict} reflectorSubeventResults:
-
- Present only when ``reflectorSubeventCount`` is greater
- than 0. One entry per Reflector subevent, each with the
- fields described in `Subevent Result`_ below.
-
- :byte reflectorProcedureAbortReason:
-
- Reason the Reflector's procedure was aborted, 0 if not
- aborted.
-
- :dict procedureEnableConfig:
-
- :byte toneAntennaConfigSelection:
-
- Antenna configuration used for CS tone exchanges.
-
- :uint32 subeventLenUs:
-
- Subevent length in microseconds.
-
- :byte subeventsPerEvent:
-
- Number of subevents per event.
-
- :uint32 subeventInterval:
-
- Interval between subevents.
-
- :uint32 eventInterval:
-
- Interval between events.
-
- :uint32 procedureInterval:
-
- Interval between procedures.
-
- :uint32 procedureCount:
-
- Number of procedures configured.
-
- :uint32 maxProcedureLen:
-
- Maximum procedure length.
-
- :dict csConfigParam:
-
- :byte modeType:
-
- Main CS mode used in the procedure.
-
- :byte subModeType:
-
- Sub-mode within the main mode.
-
- :byte rttType:
-
- Round Trip Time measurement type.
-
- :array{byte} channelMap:
-
- 10-byte channel map bitmap.
-
- :byte minMainModeSteps:
- :byte maxMainModeSteps:
- :byte mainModeRepetition:
- :byte mode0Steps:
-
- :byte role:
-
- CS role in effect for the procedure (Initiator,
- Reflector, or Both).
-
- :byte csSyncPhyType:
-
- PHY used for CS sync packets.
-
- :byte channelSelectionType:
- :byte ch3cShapeType:
- :byte ch3cJump:
- :byte channelMapRepetition:
- :byte tIp1TimeUs:
- :byte tIp2TimeUs:
- :byte tFcsTimeUs:
- :byte tPmTimeUs:
- :byte tSwTimeUsSupportedByLocal:
- :byte tSwTimeUsSupportedByRemote:
-
- :uint32 bleConnInterval:
-
- BLE connection interval in effect during the
- procedure.
-
-Subevent Result
-~~~~~~~~~~~~~~~~
-
-Each element of ``initiatorSubeventResults`` and
-``reflectorSubeventResults`` is a dict with the following fields:
-
-:int32 startAclConnEvtCounter:
-
- ACL connection event counter at the start of the subevent.
-
-:int32 freqComp:
-
- Frequency compensation value.
-
-:byte refPwrLvl:
-
- Reference power level, treated as a signed value.
-
-:byte numAntPaths:
-
- Number of antenna paths used.
-
-:byte subeventAbortReason:
-
- Reason the subevent was aborted, 0 if not aborted.
-
-:uint64 timestampNanos:
-
- Timestamp of the subevent result, in nanoseconds.
-
-:uint32 numSteps:
-
- Number of steps reported in this subevent.
-
-:array{dict} stepData:
-
- One entry per step. Each entry has:
-
- :byte stepMode:
-
- CS step mode (0-3).
-
- :byte stepChannel:
-
- Channel used for the step.
-
- :dict modeZeroData:
-
- Present when ``stepMode`` is 0.
-
- :byte packetQuality:
- :byte packetRssiDbm:
- :byte packetAntenna:
-
- :int32 initiatorMeasuredFreqOffset:
-
- Frequency offset measured by the Initiator.
-
- :dict modeOneData:
-
- Present when ``stepMode`` is 1.
-
- :byte packetQuality:
- :byte packetNadm:
- :byte packetRssiDbm:
-
- :int32 toaTodInitiator:
-
- Time of Arrival / Time of Departure at the
- Initiator.
-
- :int32 todToaReflector:
-
- Time of Departure / Time of Arrival at the
- Reflector.
-
- :byte packetAntenna:
-
- :array{int32} packetPct1:
-
- In-phase/quadrature sample pair, as
- ``[i_sample, q_sample]``.
-
- :array{int32} packetPct2:
-
- In-phase/quadrature sample pair, as
- ``[i_sample, q_sample]``.
-
- :dict modeTwoData:
-
- Present when ``stepMode`` is 2.
-
- :byte antennaPermutationIndex:
-
- :array{int32} tonePctIQSamples:
-
- Interleaved in-phase/quadrature tone samples, as
- ``[i_sample, q_sample, ...]`` — one pair per
- antenna path.
-
- :array{byte} toneQualityIndicators:
-
- One quality indicator byte per antenna path.
-
- :dict modeThreeData:
-
- Present when ``stepMode`` is 3. Contains the combined
- fields of both **modeOneData** and **modeTwoData**.
+``data`` is an opaque binary blob rather than an introspectable D-Bus
+dict: every field is raw controller measurement data with no standalone
+meaning outside of the ranging algorithm that consumes it, so there is no
+debugging value in exposing it field-by-field at the D-Bus level.
+Consumers must decode it according to the fixed layout below.
+
+All multi-byte integer fields are little-endian. Signed fields are noted
+explicitly; all others are unsigned.
+
+ProcedureData blob::
+
+ u16 procedureCounter
+ u16 procedureSequence
+ s8 initiatorSelectedTxPower
+ s8 reflectorSelectedTxPower
+ u32 initiatorSubeventCount
+ <initiatorSubeventCount> x SubeventBlob
+ u8 initiatorProcedureAbortReason
+ u32 reflectorSubeventCount
+ <reflectorSubeventCount> x SubeventBlob
+ u8 reflectorProcedureAbortReason
+ ProcEnableConfigBlob
+ CsConfigParamBlob
+
+SubeventBlob::
+
+ u16 startAclConnEvtCounter
+ u16 freqComp
+ s8 refPwrLvl
+ u8 numAntPaths
+ u8 subeventAbortReason
+ u64 timestampNanos
+ u32 numSteps
+ <numSteps> x StepBlob
+
+StepBlob::
+
+ u8 stepMode # 0-3, selects the payload below
+ u8 stepChannel
+ <mode payload, shape per stepMode -- see Mode*Blob below>
+
+ModeZeroBlob (5 bytes, present when stepMode is 0)::
+
+ u8 packetQuality
+ u8 packetRssiDbm
+ u8 packetAntenna
+ u16 initiatorMeasuredFreqOffset
+
+ModeOneBlob (16 bytes, present when stepMode is 1)::
+
+ u8 packetQuality
+ u8 packetNadm
+ u8 packetRssiDbm
+ s16 toaTodInitiator
+ s16 todToaReflector
+ u8 packetAntenna
+ s16 packetPct1_i
+ s16 packetPct1_q
+ s16 packetPct2_i
+ s16 packetPct2_q
+
+ModeTwoBlob (1 + 5*numPaths bytes, present when stepMode is 2)::
+
+ u8 antennaPermutationIndex
+ <numPaths> x { s16 toneI, s16 toneQ }
+ <numPaths> x u8 toneQualityIndicator
+
+ # numPaths = min(numAntPaths + 1, 5), where numAntPaths comes from
+ # the enclosing SubeventBlob.
+
+ModeThreeBlob (present when stepMode is 3)::
+
+ ModeOneBlob
+ ModeTwoBlob
+
+ProcEnableConfigBlob (16 bytes)::
+
+ u8 toneAntennaConfigSelection
+ u32 subeventLenUs
+ u8 subeventsPerEvent
+ u16 subeventInterval
+ u16 eventInterval
+ u16 procedureInterval
+ u16 procedureCount
+ u16 maxProcedureLen
+
+CsConfigParamBlob (30 bytes)::
+
+ u8 modeType
+ u8 subModeType
+ u8 rttType
+ u8[10] channelMap
+ u8 minMainModeSteps
+ u8 maxMainModeSteps
+ u8 mainModeRepetition
+ u8 mode0Steps
+ u8 role
+ u8 csSyncPhyType
+ u8 channelSelectionType
+ u8 ch3cShapeType
+ u8 ch3cJump
+ u8 channelMapRepetition
+ u8 tIp1TimeUs
+ u8 tIp2TimeUs
+ u8 tFcsTimeUs
+ u8 tPmTimeUs
+ u8 tSwTimeUsSupportedByLocal
+ u8 tSwTimeUsSupportedByRemote
+ u16 bleConnInterval
+
+The total blob length is fully determined by the counts embedded in the
+blob itself (``initiatorSubeventCount``, ``reflectorSubeventCount``, each
+subevent's ``numSteps``, and each mode-two step's ``numAntPaths``) — there
+is no separate length table to consult.
Properties
----------
--
^ permalink raw reply related [flat|nested] 5+ messages in thread
* RE: Add CS procedure data aggregation and D-Bus export
2026-07-29 10:30 ` [PATCH BlueZ v2 1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API Naga Bhavani Akella
@ 2026-07-29 12:29 ` bluez.test.bot
0 siblings, 0 replies; 5+ messages in thread
From: bluez.test.bot @ 2026-07-29 12:29 UTC (permalink / raw)
To: linux-bluetooth, naga.akella
[-- Attachment #1: Type: text/plain, Size: 1288 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1136563
---Test result---
Test Summary:
CheckPatch PASS 1.79 seconds
GitLint FAIL 0.76 seconds
BuildEll PASS 20.35 seconds
BluezMake PASS 540.34 seconds
MakeCheck PASS 19.00 seconds
MakeDistcheck PASS 160.12 seconds
CheckValgrind PASS 229.19 seconds
CheckSmatch PASS 313.06 seconds
bluezmakeextell PASS 103.83 seconds
IncrementalBuild PASS 546.37 seconds
ScanBuild PASS 980.56 seconds
Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v2,1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API
1: T1 Title exceeds max length (84>80): "[BlueZ,v2,1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API"
https://github.com/bluez/bluez/pull/2360
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-29 12:29 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 10:30 [PATCH BlueZ v2 0/3] Add CS procedure data aggregation and D-Bus export Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 1/3] shared/rap: Add bcs_procedure_data aggregation and procedure data API Naga Bhavani Akella
2026-07-29 12:29 ` Add CS procedure data aggregation and D-Bus export bluez.test.bot
2026-07-29 10:30 ` [PATCH BlueZ v2 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus Naga Bhavani Akella
2026-07-29 10:30 ` [PATCH BlueZ v2 3/3] doc: Update ProcedureData signal doc for byte-blob format Naga Bhavani Akella
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.