From: Naga Bhavani Akella <naga.akella@oss.qualcomm.com>
To: linux-bluetooth@vger.kernel.org
Cc: luiz.dentz@gmail.com, quic_mohamull@quicinc.com,
quic_hbandi@quicinc.com, quic_anubhavg@quicinc.com,
Naga Bhavani Akella <naga.akella@oss.qualcomm.com>
Subject: [PATCH BlueZ v2 2/3] profiles/ranging: Emit CS ProcedureData signal over D-Bus
Date: Wed, 29 Jul 2026 16:00:18 +0530 [thread overview]
Message-ID: <20260729103019.4178720-3-naga.akella@oss.qualcomm.com> (raw)
In-Reply-To: <20260729103019.4178720-1-naga.akella@oss.qualcomm.com>
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)
--
next prev parent reply other threads:[~2026-07-29 10:30 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Naga Bhavani Akella [this message]
2026-07-29 10:30 ` [PATCH BlueZ v2 3/3] doc: Update ProcedureData signal doc for byte-blob format Naga Bhavani Akella
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260729103019.4178720-3-naga.akella@oss.qualcomm.com \
--to=naga.akella@oss.qualcomm.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=quic_anubhavg@quicinc.com \
--cc=quic_hbandi@quicinc.com \
--cc=quic_mohamull@quicinc.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox