* [PATCH BlueZ v3 1/3] shared: Add bcs_procedure_data aggregation and procedure data API
2026-07-30 6:40 [PATCH BlueZ v3 0/3] Add CS procedure data aggregation and D-Bus export Naga Bhavani Akella
@ 2026-07-30 6:40 ` Naga Bhavani Akella
2026-07-30 9:38 ` Add CS procedure data aggregation and D-Bus export bluez.test.bot
2026-07-30 6:40 ` [PATCH BlueZ v3 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus Naga Bhavani Akella
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Naga Bhavani Akella @ 2026-07-30 6:40 UTC (permalink / raw)
To: linux-bluetooth
Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg,
Naga Bhavani Akella
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 | 679 +++++++++++++++++++++++++++++++++++++++++++++--
src/shared/rap.h | 50 ++++
2 files changed, 703 insertions(+), 26 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index b0c5f8ae7..be80154cd 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"
@@ -150,6 +152,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;
@@ -205,11 +217,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 */
@@ -252,6 +276,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)
@@ -645,15 +856,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);
@@ -767,6 +984,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)
@@ -781,6 +1016,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_));
@@ -1893,12 +2129,186 @@ 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;
+ }
+
+ DBG(rap, "Local initiator data: proc_counter=%u local_status=%d "
+ "remote_status=%d local_has_complete_subevent=%d "
+ "remote_has_complete_subevent=%d",
+ state->proc_counter, state->local_status, state->remote_status,
+ state->local_has_complete_subevent,
+ state->remote_has_complete_subevent);
+
+ 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;
@@ -1908,6 +2318,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,
@@ -1925,7 +2351,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,
@@ -1969,6 +2408,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);
@@ -1984,6 +2424,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,
@@ -2034,6 +2489,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)
@@ -2211,7 +2699,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;
@@ -2232,7 +2721,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)
@@ -2243,7 +2737,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;
@@ -2267,7 +2762,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)
@@ -2278,7 +2786,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];
@@ -2317,7 +2826,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)
@@ -2328,13 +2844,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,
@@ -2367,7 +2887,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;
@@ -2426,20 +2947,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;
}
@@ -2449,12 +2992,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;
}
}
@@ -2465,6 +3012,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;
@@ -2476,10 +3027,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;
@@ -2491,19 +3062,75 @@ 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 (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 (hdr.subevent_done_status ==
- SUBEVENT_DONE_ALL_RESULTS_COMPLETE ||
- hdr.ranging_done_status ==
+ 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) {
+ DBG(rap, "RAS data segments parsed: proc_counter=%u "
+ "local_status=%d remote_status=%d "
+ "local_has_complete_subevent=%d "
+ "remote_has_complete_subevent=%d",
+ state->proc_counter, state->local_status,
+ state->remote_status,
+ state->local_has_complete_subevent,
+ state->remote_has_complete_subevent);
+ 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 a44831e6e..b0cf307c2 100644
--- a/src/shared/rap.h
+++ b/src/shared/rap.h
@@ -158,10 +158,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);
@@ -179,6 +214,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);
@@ -230,6 +269,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,
@@ -247,3 +290,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] 6+ messages in thread* [PATCH BlueZ v3 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus
2026-07-30 6:40 [PATCH BlueZ v3 0/3] Add CS procedure data aggregation and D-Bus export Naga Bhavani Akella
2026-07-30 6:40 ` [PATCH BlueZ v3 1/3] shared: Add bcs_procedure_data aggregation and procedure data API Naga Bhavani Akella
@ 2026-07-30 6:40 ` Naga Bhavani Akella
2026-07-30 6:40 ` [PATCH BlueZ v3 3/3] doc: Update ProcedureData signal doc for byte-blob format Naga Bhavani Akella
2026-07-30 15:40 ` [PATCH BlueZ v3 0/3] Add CS procedure data aggregation and D-Bus export patchwork-bot+bluetooth
3 siblings, 0 replies; 6+ messages in thread
From: Naga Bhavani Akella @ 2026-07-30 6:40 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 | 292 ++++++++++++++++++++++++++++++++++++-
profiles/ranging/rap_hci.c | 59 ++++++++
2 files changed, 348 insertions(+), 3 deletions(-)
diff --git a/profiles/ranging/rap.c b/profiles/ranging/rap.c
index 91f30ac30..cabf8a5de 100644
--- a/profiles/ranging/rap.c
+++ b/profiles/ranging/rap.c
@@ -433,6 +433,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)
{
@@ -588,6 +856,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);
}
@@ -605,6 +876,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(),
@@ -642,6 +915,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;
@@ -720,6 +998,10 @@ static void rap_remove(struct btd_service *service)
return;
}
+ g_dbus_unregister_interface(btd_get_dbus_connection(),
+ device_get_path(device),
+ CS_INTERFACE);
+
rap_data_remove(data);
}
@@ -794,9 +1076,9 @@ static int rap_setup_reflector(struct rap_data *data)
if (!data->dbus_registered) {
if (!g_dbus_register_interface(btd_get_dbus_connection(),
- device_get_path(device),
- CS_INTERFACE, cs_dbus_methods,
- NULL, cs_dbus_properties, data, NULL)) {
+ device_get_path(device),
+ CS_INTERFACE, cs_dbus_methods,
+ cs_dbus_signals, cs_dbus_properties, data, NULL)) {
error("Failed to register %s interface for %s",
CS_INTERFACE, addr);
return -EINVAL;
@@ -854,6 +1136,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..66bc7b5eb 100644
--- a/profiles/ranging/rap_hci.c
+++ b/profiles/ranging/rap_hci.c
@@ -437,6 +437,8 @@ static void rap_rd_loc_supp_cap_done_cb(const void *data, uint8_t size,
DBG(" T_SW Time Supported: %u", rsp->t_sw_time_supported);
DBG(" TX SNR Capability: 0x%02X", rsp->tx_snr_capability);
+ bt_rap_set_local_sw_time(sm->rap, rsp->t_sw_time_supported);
+
/* Transition to INIT state before reading remote capabilities */
cs_set_state(sm, CS_STATE_INIT);
@@ -860,6 +862,8 @@ static void rap_rd_rmt_supp_cap_cmplt_evt(const void *data, uint8_t size,
subfeatures_supported = le16_to_cpu(evt->subfeatures_supported);
DBG("subfeatures_supported=0x%04X", subfeatures_supported);
+ bt_rap_set_remote_sw_time(sm->rap, evt->t_sw_time_supported);
+
/* Check Bit 1 of subfeatures_supported (0x0002) */
if (!(subfeatures_supported & 0x0002)) {
DBG("Bit 1 not set, sending Read Remote FAE Table");
@@ -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] 6+ messages in thread