From: Cheng Jiang <cheng.jiang@oss.qualcomm.com>
To: linux-bluetooth@vger.kernel.org
Cc: jinwang.li@oss.qualcomm.com, wei.deng@oss.qualcomm.com,
xiuzhuo.shang@oss.qualcomm.com, shuai.zhang@oss.qualcomm.com,
Cheng Jiang <cheng.jiang@oss.qualcomm.com>
Subject: [PATCH 07/11] shared/rap: Add requester ranging data subscription API
Date: Thu, 1 Oct 2026 21:18:53 +0800 [thread overview]
Message-ID: <20261001131900.707485-8-cheng.jiang@oss.qualcomm.com> (raw)
In-Reply-To: <20261001131900.707485-1-cheng.jiang@oss.qualcomm.com>
Give the RAS requester control over how Ranging Data is received from the
peer. bt_rap_set_ranging_data_mode() picks Real-time, On-demand or neither,
together with the CCC value to write on the peer, and
bt_rap_disable_realtime_ranging() drops a Real-time subscription again.
bt_rap_set_ondemand_ranging() selects the mode to use before GATT discovery
has completed.
Setting a mode first unsubscribes whatever is currently subscribed, so the
modes do not stack, and fails when the RAS Features read has not completed
or the peer does not advertise the mode being asked for.
The requested transport applies to the RAS Control Point, Ranging Data
Ready and Ranging Data Overwritten characteristics. On-demand Ranging Data
itself stays on notifications whatever is asked for: it carries the
segmented transfer, and gating each segment on a confirmation would slow a
large one down for no benefit on the receiving side. On-demand mode also
reads Ranging Data Ready once subscribed, to pick up a counter published
before the subscription existed.
Real-time reception is guarded by the deadlines RAP 4.4.1.1 sets for the
Requester: the CCCD is disabled again if no first segment arrives within
5 seconds, or if more than 1 second passes between continuation segments,
so a peer that stops sending does not leave the subscription open.
Assisted-by: Claude:claude-opus-4.8
---
src/shared/rap.c | 270 ++++++++++++++++++++++++++++++++++++++---------
src/shared/rap.h | 21 ++++
2 files changed, 244 insertions(+), 47 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index b1108ad68..c0305a9e3 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -34,6 +34,7 @@
*/
#define RAS_CP_RESPONSE_TIMEOUT (5 * 1000)
#define RAS_SEGMENT_TIMEOUT (1 * 1000)
+#define RAS_REALTIME_FIRST_SEGMENT_TIMEOUT (5 * 1000)
/* Delay between On-demand ranging-data notification segments, see
* send_ondemand_segment_data_cb().
*/
@@ -470,6 +471,15 @@ struct bt_rap {
* bt_rap_attach() to take effect.
*/
bool ondemand_mode;
+ uint16_t realtime_ccc_value;
+ uint16_t ondemand_ccc_value;
+ unsigned int realtime_notify_id;
+ unsigned int realtime_timeout_id;
+ unsigned int ondemand_notify_id;
+ unsigned int cp_notify_id;
+ unsigned int ready_notify_id;
+ unsigned int overwritten_notify_id;
+ uint32_t ras_features;
/* Responder role: this connection's on-demand ranging data cache.
* Per-connection (not on the shared struct ras) so that concurrent
@@ -957,6 +967,15 @@ 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;
+ if (rap->realtime_timeout_id) {
+ timeout_remove(rap->realtime_timeout_id);
+ rap->realtime_timeout_id = 0;
+ }
+ rap->realtime_notify_id = 0;
+ rap->ondemand_notify_id = 0;
+ rap->cp_notify_id = 0;
+ rap->ready_notify_id = 0;
+ rap->overwritten_notify_id = 0;
bt_att_unregister_disconnect(rap->att, rap->disconn_id);
rap->att = NULL;
@@ -3397,6 +3416,7 @@ static void rap_notify_destroy(void *data)
static unsigned int bt_rap_register_notify(struct bt_rap *rap,
uint16_t value_handle,
+ uint16_t ccc_value,
rap_notify_t func,
void *user_data)
{
@@ -3409,9 +3429,13 @@ static unsigned int bt_rap_register_notify(struct bt_rap *rap,
DBG(rap, "register for notifications");
- notify->id = bt_gatt_client_register_notify(rap->client,
- value_handle, ras_register,
- rap_notify, notify,
+ if (ccc_value)
+ notify->id = bt_gatt_client_register_notify_with_ccc(rap->client,
+ value_handle, ccc_value, ras_register,
+ rap_notify, notify, rap_notify_destroy);
+ else
+ notify->id = bt_gatt_client_register_notify(rap->client,
+ value_handle, ras_register, rap_notify, notify,
rap_notify_destroy);
if (!notify->id) {
DBG(rap, "Unable to register for notifications");
@@ -3424,6 +3448,85 @@ static unsigned int bt_rap_register_notify(struct bt_rap *rap,
return notify->id;
}
+bool bt_rap_disable_realtime_ranging(struct bt_rap *rap)
+{
+ unsigned int notify_id;
+
+ if (!rap || !rap->client)
+ return false;
+
+ if (rap->realtime_timeout_id) {
+ timeout_remove(rap->realtime_timeout_id);
+ rap->realtime_timeout_id = 0;
+ }
+
+ if (!rap->realtime_notify_id)
+ return true;
+
+ notify_id = rap->realtime_notify_id;
+ rap->realtime_notify_id = 0;
+
+ return bt_gatt_client_unregister_notify(rap->client, notify_id);
+}
+
+static bool ras_realtime_timeout_cb(void *user_data)
+{
+ struct bt_rap *rap = user_data;
+
+ rap->realtime_timeout_id = 0;
+ DBG(rap, "Real-time Ranging Data segment timeout; disabling CCCD");
+ bt_rap_disable_realtime_ranging(rap);
+
+ return false;
+}
+
+static void ras_realtime_arm_timeout(struct bt_rap *rap, unsigned int timeout)
+{
+ if (rap->realtime_timeout_id)
+ timeout_remove(rap->realtime_timeout_id);
+
+ rap->realtime_timeout_id = timeout_add(timeout, ras_realtime_timeout_cb,
+ rap, NULL);
+}
+
+static void ras_realtime_cancel_timeout(struct bt_rap *rap)
+{
+ if (!rap->realtime_timeout_id)
+ return;
+
+ timeout_remove(rap->realtime_timeout_id);
+ rap->realtime_timeout_id = 0;
+}
+
+static bool bt_rap_unregister_notify(struct bt_rap *rap, unsigned int *id)
+{
+ unsigned int notify_id;
+
+ if (!*id)
+ return true;
+
+ notify_id = *id;
+ *id = 0;
+ return bt_gatt_client_unregister_notify(rap->client, notify_id);
+}
+
+static bool bt_rap_disable_ranging_data(struct bt_rap *rap)
+{
+ bool success = true;
+
+ if (!rap || !rap->client)
+ return false;
+
+ ras_realtime_cancel_timeout(rap);
+
+ success &= bt_rap_unregister_notify(rap, &rap->realtime_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->ondemand_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->cp_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->ready_notify_id);
+ success &= bt_rap_unregister_notify(rap, &rap->overwritten_notify_id);
+ return success;
+}
+
static void ras_ondemand_client_reset(struct ras *ras)
{
if (!ras)
@@ -4160,12 +4263,22 @@ static void ras_segment_notify_cb(struct bt_rap *rap, uint16_t value_handle,
{
const char *label = user_data ? user_data : "real-time";
struct ras *ras = rap_get_ras(rap);
+ struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
+ struct segmentation_header seg_hdr;
+ bool realtime = !strcmp(label, "real-time");
DBG(rap, "Received %s notification: handle=0x%04x len=%u",
label, value_handle, length);
ras_reassemble_segment_notify(rap, value, length);
+ if (realtime && parse_segmentation_header(&iov, &seg_hdr)) {
+ if (seg_hdr.last_segment)
+ ras_realtime_cancel_timeout(rap);
+ else
+ ras_realtime_arm_timeout(rap, RAS_SEGMENT_TIMEOUT);
+ }
+
if (ras && ras->client.pending_op == RAS_CP_PENDING_GET)
ras_cp_arm_segment_timeout(rap, ras);
}
@@ -4309,7 +4422,8 @@ static void ras_cp_response_notify_cb(struct bt_rap *rap,
*/
static void rap_subscribe_ondemand_chrc(struct bt_rap *rap,
struct gatt_db_attribute *chrc,
- rap_notify_t func, const char *name)
+ uint16_t ccc_value, rap_notify_t func, const char *name,
+ unsigned int *notify_id_out)
{
uint16_t value_handle;
unsigned int notify_id;
@@ -4325,13 +4439,112 @@ static void rap_subscribe_ondemand_chrc(struct bt_rap *rap,
return;
}
- notify_id = bt_rap_register_notify(rap, value_handle, func,
+ notify_id = bt_rap_register_notify(rap, value_handle, ccc_value, func,
(void *)name);
if (!notify_id)
DBG(rap, "Failed to register for %s notifications", name);
else
DBG(rap, "Registered for %s notifications: id=%u", name,
notify_id);
+
+ if (notify_id_out)
+ *notify_id_out = notify_id;
+}
+
+static void ras_data_ready_read_complete(struct bt_rap *rap, bool success,
+ uint8_t att_ecode, const uint8_t *value,
+ uint16_t length, void *user_data)
+{
+ if (!success) {
+ DBG(rap, "Unable to read Ranging Data Ready: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ /* This read only synchronizes the characteristic as required by RAS.
+ * A Ready notification, not its current value, starts a retrieval. */
+ DBG(rap, "Read Ranging Data Ready (%u bytes)", length);
+}
+
+static bool rap_subscribe_ranging_data(struct bt_rap *rap,
+ enum bt_rap_ranging_data_mode mode)
+{
+ struct ras *ras = rap_get_ras(rap);
+
+ if (!ras || !rap->client)
+ return false;
+
+ if (mode == BT_RAP_RANGING_DATA_REALTIME) {
+ uint16_t value_handle;
+
+ if (!(rap->ras_features & 0x01) || !ras->realtime_chrc ||
+ !gatt_db_attribute_get_char_data(ras->realtime_chrc, NULL,
+ &value_handle, NULL, NULL, NULL))
+ return false;
+
+ rap->realtime_notify_id = bt_rap_register_notify(rap, value_handle,
+ rap->realtime_ccc_value, ras_segment_notify_cb,
+ "real-time");
+ if (rap->realtime_notify_id)
+ ras_realtime_arm_timeout(rap,
+ RAS_REALTIME_FIRST_SEGMENT_TIMEOUT);
+ return rap->realtime_notify_id != 0;
+ }
+
+ if (mode == BT_RAP_RANGING_DATA_ONDEMAND) {
+ uint16_t value_handle;
+
+ /* Data is segmented and can be large; keep it notification-based.
+ * The control/status characteristics use the requested transport. */
+ rap_subscribe_ondemand_chrc(rap, ras->ondemand_chrc,
+ BT_RAP_RANGING_NOTIFY,
+ ras_segment_notify_cb, "on-demand", &rap->ondemand_notify_id);
+ rap_subscribe_ondemand_chrc(rap, ras->cp_chrc,
+ rap->ondemand_ccc_value,
+ ras_cp_response_notify_cb, "RAS Control Point", &rap->cp_notify_id);
+ rap_subscribe_ondemand_chrc(rap, ras->ready_chrc,
+ rap->ondemand_ccc_value,
+ ras_data_ready_notify_cb, "Ranging Data Ready", &rap->ready_notify_id);
+ rap_subscribe_ondemand_chrc(rap, ras->overwritten_chrc,
+ rap->ondemand_ccc_value,
+ ras_data_overwritten_notify_cb, "Ranging Data Overwritten",
+ &rap->overwritten_notify_id);
+ if (ras->ready_chrc &&
+ gatt_db_attribute_get_char_data(ras->ready_chrc, NULL,
+ &value_handle, NULL, NULL, NULL))
+ rap_read_value(rap, value_handle, ras_data_ready_read_complete,
+ NULL);
+ return rap->ondemand_notify_id || rap->cp_notify_id ||
+ rap->ready_notify_id || rap->overwritten_notify_id;
+ }
+
+ return true;
+}
+
+bool bt_rap_set_ranging_data_mode(struct bt_rap *rap,
+ enum bt_rap_ranging_data_mode mode,
+ enum bt_rap_ranging_transport transport)
+{
+ if (!rap || !rap->client || !rap->ras_features)
+ return false;
+
+ if (mode == BT_RAP_RANGING_DATA_REALTIME &&
+ transport != BT_RAP_RANGING_NOTIFY &&
+ transport != BT_RAP_RANGING_INDICATE)
+ return false;
+
+ if (mode == BT_RAP_RANGING_DATA_ONDEMAND &&
+ transport != BT_RAP_RANGING_NOTIFY &&
+ transport != BT_RAP_RANGING_INDICATE)
+ return false;
+
+ if (!bt_rap_disable_ranging_data(rap))
+ return false;
+
+ rap->realtime_ccc_value = transport;
+ rap->ondemand_ccc_value = transport;
+
+ return rap_subscribe_ranging_data(rap, mode);
}
static void read_ras_features(struct bt_rap *rap, bool success,
@@ -4379,6 +4592,7 @@ static void read_ras_features(struct bt_rap *rap, bool success,
abort_operation = (features & 0x04) != 0;
if (ras)
ras->client.abort_operation = abort_operation;
+ rap->ras_features = features;
DBG(rap, "RAS Features - Real-time: %s, Retrieve Lost: %s, Abort: %s",
supports_realtime ? "Yes" : "No",
@@ -4392,48 +4606,10 @@ static void read_ras_features(struct bt_rap *rap, bool success,
return;
}
- if (!rap->ondemand_mode) {
- /* Register for real-time characteristic notifications */
- if (supports_realtime && ras->realtime_chrc) {
- uint16_t value_handle;
-
- if (gatt_db_attribute_get_char_data(ras->realtime_chrc,
- NULL, &value_handle,
- NULL, NULL, NULL)) {
- unsigned int notify_id;
-
- notify_id = bt_rap_register_notify(rap,
- value_handle,
- ras_segment_notify_cb,
- "real-time");
- if (!notify_id)
- DBG(rap, "Failed to register for "
- "real-time notifications");
- else
- DBG(rap, "Registered for real-time "
- "features: id=%u", notify_id);
- }
- }
-
- return;
- }
-
- /* On-demand mode: subscribe to each on-demand characteristic
- * independently so a partially-capable peer doesn't block the
- * others.
- */
- rap_subscribe_ondemand_chrc(rap, ras->ondemand_chrc,
- ras_segment_notify_cb,
- "on-demand");
- rap_subscribe_ondemand_chrc(rap, ras->cp_chrc,
- ras_cp_response_notify_cb,
- "RAS Control Point");
- rap_subscribe_ondemand_chrc(rap, ras->ready_chrc,
- ras_data_ready_notify_cb,
- "Ranging Data Ready");
- rap_subscribe_ondemand_chrc(rap, ras->overwritten_chrc,
- ras_data_overwritten_notify_cb,
- "Ranging Data Overwritten");
+ if (!rap_subscribe_ranging_data(rap, rap->ondemand_mode ?
+ BT_RAP_RANGING_DATA_ONDEMAND :
+ BT_RAP_RANGING_DATA_REALTIME))
+ DBG(rap, "Failed to subscribe to requested Ranging Data mode");
}
static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data)
diff --git a/src/shared/rap.h b/src/shared/rap.h
index 9ab577710..9a721df95 100644
--- a/src/shared/rap.h
+++ b/src/shared/rap.h
@@ -303,3 +303,24 @@ void bt_rap_set_conn_interval(struct bt_rap *rap, uint16_t conn_interval);
* GATT discovery to completion to take effect.
*/
bool bt_rap_set_ondemand_ranging(struct bt_rap *rap, bool enable);
+
+enum bt_rap_ranging_data_mode {
+ BT_RAP_RANGING_DATA_DISABLED,
+ BT_RAP_RANGING_DATA_REALTIME,
+ BT_RAP_RANGING_DATA_ONDEMAND,
+};
+
+enum bt_rap_ranging_transport {
+ BT_RAP_RANGING_NOTIFY = 0x0001,
+ BT_RAP_RANGING_INDICATE = 0x0002,
+};
+
+/* Select the Requester's received Ranging Data subscription. This can only be
+ * called after remote RAS discovery and Features read complete. */
+bool bt_rap_set_ranging_data_mode(struct bt_rap *rap,
+ enum bt_rap_ranging_data_mode mode,
+ enum bt_rap_ranging_transport transport);
+
+/* Initiator role: stop receiving Real-time Ranging Data. The GATT client
+ * disables the characteristic CCCD when this is its final subscription. */
+bool bt_rap_disable_realtime_ranging(struct bt_rap *rap);
--
2.43.0
next prev parent reply other threads:[~2026-10-01 13:19 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-01 13:18 [PATCH 00/11] Ranging Profile and Ranging Service PTS fixes Cheng Jiang
2026-10-01 13:18 ` [PATCH 01/11] shared/gatt-client: Allow registering notify with explicit CCC value Cheng Jiang
2026-10-01 16:03 ` Ranging Profile and Ranging Service PTS fixes bluez.test.bot
2026-10-01 13:18 ` [PATCH 02/11] shared/gatt-db: Add gatt_db_attribute_indicate() Cheng Jiang
2026-10-01 15:48 ` Luiz Augusto von Dentz
2026-10-09 8:45 ` Cheng Jiang
2026-10-01 13:18 ` [PATCH 03/11] shared/rap: Honor CCCD transport for RAS on-demand ranging data Cheng Jiang
2026-10-01 13:18 ` [PATCH 04/11] shared/rap: Add RAS Control Point Abort Operation Cheng Jiang
2026-10-01 13:18 ` [PATCH 05/11] shared/rap: Shorten the requester Control Point response timeout Cheng Jiang
2026-10-01 13:18 ` [PATCH 06/11] shared/rap: Add real-time ranging data delivery on the responder Cheng Jiang
2026-10-01 13:18 ` Cheng Jiang [this message]
2026-10-01 13:18 ` [PATCH 08/11] profiles/ranging: Expose requester ranging data mode over D-Bus Cheng Jiang
2026-10-01 13:18 ` [PATCH 09/11] client: Add Channel Sounding ranging data mode control Cheng Jiang
2026-10-01 13:18 ` [PATCH 10/11] doc: Document " Cheng Jiang
2026-10-01 13:18 ` [PATCH 11/11] doc/qualification: Add RAP/RAS PTS qualification results Cheng Jiang
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=20261001131900.707485-8-cheng.jiang@oss.qualcomm.com \
--to=cheng.jiang@oss.qualcomm.com \
--cc=jinwang.li@oss.qualcomm.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=shuai.zhang@oss.qualcomm.com \
--cc=wei.deng@oss.qualcomm.com \
--cc=xiuzhuo.shang@oss.qualcomm.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