Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: l2cap: fix UAF race in l2cap_sock_cleanup_listen
From: Safa Karakuş @ 2026-04-28 23:30 UTC (permalink / raw)
  To: linux-bluetooth@vger.kernel.org
  Cc: marcel@holtmann.org, luiz.dentz@gmail.com, stable@vger.kernel.org

l2cap_sock_cleanup_listen() dequeues child sockets via
bt_accept_dequeue() without holding a reference on the returned sk.
A concurrent HCI disconnect can trigger l2cap_conn_del() on CPU1
which, while holding chan->lock, calls:

  teardown_cb  -> sock_set_flag(sk, SOCK_ZAPPED)
  close_cb     -> l2cap_sock_kill(sk) -> sock_put(sk) -> kfree(sk)

all before CPU0 has a chance to acquire chan->lock.  CPU0 then calls
l2cap_chan_lock() on the now-freed sk's chan (already safe because
l2cap_chan_hold() was called first) but subsequently passes the freed
sk pointer to l2cap_sock_kill(), causing a use-after-free read on
sk->sk_flags and sk->sk_socket.

Fix by calling sock_hold() immediately after bt_accept_dequeue() to
prevent kfree(sk) from racing with our traversal.  After acquiring
chan->lock, check SOCK_DEAD: if l2cap_conn_del() already invoked
l2cap_sock_kill() (which sets SOCK_DEAD), skip the duplicate call to
avoid a double sock_put().  Drop the extra reference with sock_put()
at the end of each loop iteration.

Fixes: 15f02b910562 ("Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode")
Cc: stable@vger.kernel.org
Signed-off-by: Safa Karakus<safa.karakus@secunnix.com>
---
 net/bluetooth/l2cap_sock.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 71e8c1b45..4475d3377 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1477,7 +1477,15 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)

 	/* Close not yet accepted channels */
 	while ((sk = bt_accept_dequeue(parent, NULL))) {
-		struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+		struct l2cap_chan *chan;
+
+		/* Hold sk across the chan->lock acquisition window.
+		 * A concurrent l2cap_conn_del() can call l2cap_sock_kill(sk)
+		 * -> kfree(sk) inside chan->lock before we acquire it,
+		 * leaving a dangling pointer.
+		 */
+		sock_hold(sk);
+		chan = l2cap_pi(sk)->chan;

 		BT_DBG("child chan %p state %s", chan,
 		       state_to_string(chan->state));
@@ -1487,10 +1495,16 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)

 		__clear_chan_timer(chan);
 		l2cap_chan_close(chan, ECONNRESET);
-		l2cap_sock_kill(sk);
+		/* l2cap_conn_del() may have already called l2cap_sock_kill()
+		 * (setting SOCK_DEAD); skip the duplicate to avoid a
+		 * double sock_put().
+		 */
+		if (!sock_flag(sk, SOCK_DEAD))
+			l2cap_sock_kill(sk);

 		l2cap_chan_unlock(chan);
 		l2cap_chan_put(chan);
+		sock_put(sk);
 	}
 }

--
2.34.1

^ permalink raw reply related

* Re: [PATCH] sysfs: return -ENOENT from move/rename when kobj->sd is NULL
From: Danilo Krummrich @ 2026-04-28 22:31 UTC (permalink / raw)
  To: Conor Kotwasinski
  Cc: Greg Kroah-Hartman, Rafael J . Wysocki, driver-core, linux-kernel,
	linux-bluetooth, syzbot+d1db96f72a452dc9cbd2,
	syzbot+faeac5b54ba997a96278
In-Reply-To: <20260416150600.2148935-1-conorkotwasinski2024@u.northwestern.edu>

On Thu Apr 16, 2026 at 5:06 PM CEST, Conor Kotwasinski wrote:
> sysfs_move_dir_ns() and sysfs_rename_dir_ns() pass kobj->sd to
> kernfs_rename_ns() unconditionally. If sysfs_remove_dir() has already
> cleared kobj->sd, the NULL flows through and kernfs_rename_ns()
> dereferences it via rcu_access_pointer(kn->__parent), which KASAN
> surfaces as a stack-segment fault on the shadow lookup:
>
>   Oops: stack segment: 0000 [#1] SMP KASAN PTI
>   RIP: 0010:kernfs_rename_ns+0x3a/0x7a0 fs/kernfs/dir.c:1752
>   Call Trace:
>    kobject_move+0x525/0x6e0 lib/kobject.c:569
>    device_move+0xe0/0x730 drivers/base/core.c:4606
>    hci_conn_del_sysfs+0xb8/0x1a0 net/bluetooth/hci_sysfs.c:75
>    hci_conn_cleanup net/bluetooth/hci_conn.c:173 [inline]
>    hci_conn_del+0xc36/0x1240 net/bluetooth/hci_conn.c:1234
>    hci_conn_hash_flush+0x191/0x260 net/bluetooth/hci_conn.c:2638
>    hci_dev_close_sync+0x821/0x1100 net/bluetooth/hci_sync.c:5327
>    hci_dev_do_close net/bluetooth/hci_core.c:501 [inline]
>    hci_unregister_dev+0x21a/0x5b0 net/bluetooth/hci_core.c:2715
>
> syzbot has reported 35 hits with this signature across net, net-next
> and linux-next between July 2025 and January 2026, via both vhci
> release and HCIDEVRESET ioctl.
>
> Return -ENOENT in that case, consistent with sysfs_create_dir_ns().
> The underlying ordering problem in bluetooth -- device_move() called
> after the target's sysfs has been torn down -- is a separate issue.
>
> Reported-by: syzbot+d1db96f72a452dc9cbd2@syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/all/687c6966.a70a0220.693ce.00a5.GAE@google.com/
> Reported-by: syzbot+faeac5b54ba997a96278@syzkaller.appspotmail.com
>

No Closes: tag for the second report?

Also, this should have a Fixes: tag and Cc: stable.

^ permalink raw reply

* Re: [PATCH BlueZ v5 1/3] src/shared: Add RAS packet format and notification support
From: Luiz Augusto von Dentz @ 2026-04-28 19:43 UTC (permalink / raw)
  To: Prathibha Madugonde
  Cc: linux-bluetooth, quic_mohamull, quic_hbandi, quic_anubhavg
In-Reply-To: <20260428023111.1640377-2-prathm@qti.qualcomm.com>

Hi Prathibha,

On Mon, Apr 27, 2026 at 10:31 PM Prathibha Madugonde
<prathibha.madugonde@oss.qualcomm.com> wrote:
>
> From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
>
> Implement complete RAS data pipeline:
>  - Handle HCI CS subevent result/continuation events
>  - Serialize ranging/subevent headers per spec
>  - GATT notifications for real-time ranging data
> ---
>  src/shared/rap.c | 1376 +++++++++++++++++++++++++++++++++++++++++++++-
>  src/shared/rap.h |    4 +-
>  2 files changed, 1366 insertions(+), 14 deletions(-)
>
> diff --git a/src/shared/rap.c b/src/shared/rap.c
> index ac6de04e0..db924b0e9 100644
> --- a/src/shared/rap.c
> +++ b/src/shared/rap.c
> @@ -33,6 +33,170 @@
>  /* Total number of attribute handles reserved for the RAS service */
>  #define RAS_TOTAL_NUM_HANDLES          18
>
> +/* 2(rc+cfg) + 1(tx_pwr) + 1(4 bits antenna_mask, 2 bits reserved,
> + * 2 bits pct_format)
> + */
> +#define RAS_RANGING_HEADER_SIZE 4
> +#define TOTAL_RAS_RANGING_HEADER_SIZE 5
> +#define ATT_OVERHEAD 3 /* 1(opcode) + 2(char handle) */
> +#define RAS_STEP_ABORTED_BIT   0x80/* set step aborted */
> +#define RAS_SUBEVENT_HEADER_SIZE 8
> +
> +enum pct_format {
> +       IQ = 0,
> +       PHASE = 1,
> +};
> +
> +enum ranging_done_status {
> +       RANGING_DONE_ALL_RESULTS_COMPLETE = 0x0,
> +       RANGING_DONE_PARTIAL_RESULTS = 0x1,
> +       RANGING_DONE_ABORTED = 0xF,
> +};
> +
> +enum subevent_done_status {
> +       SUBEVENT_DONE_ALL_RESULTS_COMPLETE = 0x0,
> +       SUBEVENT_DONE_PARTIAL_RESULTS = 0x1,
> +       SUBEVENT_DONE_ABORTED = 0xF,
> +};
> +
> +enum ranging_abort_reason {
> +       RANGING_ABORT_NO_ABORT = 0x0,
> +       RANGING_ABORT_LOCAL_HOST_OR_REMOTE = 0x1,
> +       RANGING_ABORT_INSUFFICIENT_FILTERED_CHANNELS = 0x2,
> +       RANGING_ABORT_INSTANT_HAS_PASSED = 0x3,
> +       RANGING_ABORT_UNSPECIFIED = 0xF,
> +};
> +
> +enum subevent_abort_reason {
> +       SUBEVENT_ABORT_NO_ABORT = 0x0,
> +       SUBEVENT_ABORT_LOCAL_HOST_OR_REMOTE = 0x1,
> +       SUBEVENT_ABORT_NO_CS_SYNC_RECEIVED = 0x2,
> +       SUBEVENT_ABORT_SCHEDULING_CONFLICTS_OR_LIMITED_RESOURCES = 0x3,
> +       SUBEVENT_ABORT_UNSPECIFIED = 0xF,
> +};
> +
> +/* Segmentation header: 1 byte
> + * bit 0: first_segment
> + * bit 1: last_segment
> + * bits 2-7: rolling_segment_counter (6 bits)
> + */
> +struct segmentation_header {
> +       uint8_t first_segment;
> +       uint8_t last_segment;
> +       uint8_t rolling_segment_counter;
> +};
> +
> +/* Macros to pack/unpack segmentation header */
> +#define SEG_HDR_PACK(first, last, counter) \
> +       ((uint8_t)(((first) ? 0x01 : 0x00) | \
> +               ((last) ? 0x02 : 0x00) | \
> +               (((counter) & 0x3F) << 2)))
> +
> +#define SEG_HDR_UNPACK_FIRST(byte)    ((byte) & 0x01)
> +#define SEG_HDR_UNPACK_LAST(byte)     (((byte) >> 1) & 0x01)
> +#define SEG_HDR_UNPACK_COUNTER(byte)  (((byte) >> 2) & 0x3F)
> +
> +/* Ranging header: 4 bytes
> + * 0-1: ranging_counter (12 bits) | configuration_id (4 bits)
> + *      [little-endian]
> + * byte 2: selected_tx_power (signed)
> + * byte 3: antenna_paths_mask (4 bits) | reserved (2 bits) |
> + *         pct_format (2 bits)
> + */
> +struct ranging_header {
> +       uint16_t ranging_counter;
> +       uint8_t configuration_id;
> +       int8_t selected_tx_power;
> +       uint8_t antenna_paths_mask;
> +       uint8_t pct_format;
> +} __packed;
> +
> +/* Macros to pack/unpack ranging header */
> +#define RANGING_HDR_PACK_BYTE0_1(rc, cfg) \
> +       ((uint16_t)(((rc) & 0x0FFF) | (((cfg) & 0x0F) << 12)))
> +
> +#define RANGING_HDR_PACK_BYTE3(ant_mask, pct_fmt) \
> +       ((uint8_t)(((ant_mask) & 0x0F) | (((pct_fmt) & 0x03) << 6)))
> +
> +#define RANGING_HDR_UNPACK_RC(byte0_1) \
> +       ((uint16_t)((byte0_1) & 0x0FFF))
> +
> +#define RANGING_HDR_UNPACK_CFG(byte0_1) \
> +       ((uint8_t)(((byte0_1) >> 12) & 0x0F))
> +
> +#define RANGING_HDR_UNPACK_ANT_MASK(byte3) \
> +       ((uint8_t)((byte3) & 0x0F))
> +
> +#define RANGING_HDR_UNPACK_PCT_FMT(byte3) \
> +       ((uint8_t)(((byte3) >> 6) & 0x03))
> +
> +struct ras_subevent_header {
> +       uint16_t start_acl_conn_event;
> +       uint16_t frequency_compensation;
> +       uint8_t ranging_done_status;
> +       uint8_t subevent_done_status;
> +       uint8_t ranging_abort_reason;
> +       uint8_t subevent_abort_reason;
> +       int8_t reference_power_level;
> +       uint8_t num_steps_reported;
> +};
> +
> +struct ras_subevent {
> +       struct ras_subevent_header subevent_header;
> +       uint8_t subevent_data[];
> +};
> +
> +/* Role maps to Core CS roles (initiator/reflector) */
> +enum cs_role {
> +       CS_ROLE_INITIATOR = 0x00,
> +       CS_ROLE_REFLECTOR = 0x01,
> +};
> +
> +#define CS_INVALID_CONFIG_ID   0xFF
> +/* 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
> +};
> +
> +/* Main cs_procedure_data  */
> +struct cs_procedure_data {
> +       /* Identity and counters */
> +       uint16_t counter;
> +       uint8_t  num_antenna_paths;
> +       /* Flags and status */
> +       enum cs_procedure_done_status local_status;
> +       enum cs_procedure_done_status remote_status;
> +       bool contains_complete_subevent_;
> +       /* RAS aggregation */
> +       struct segmentation_header segmentation_header_;
> +       struct ranging_header      ranging_header_;
> +       struct iovec       ras_raw_data_;        /* raw concatenated */
> +       uint16_t           ras_raw_data_index_;
> +       struct ras_subevent_header  ras_subevent_header_;
> +       struct iovec       ras_subevent_data_;   /* buffer per subevent */
> +       uint8_t            ras_subevent_counter_;
> +       /* Reference power levels */
> +       int8_t initiator_reference_power_level;
> +       int8_t reflector_reference_power_level;
> +       bool ranging_header_prepended_;
> +       bool ras_subevent_header_emitted;
> +};
> +
> +struct cstracker {
> +       enum cs_role        role;                 /* INITIATOR/REFLECTOR */
> +       uint8_t             config_id;            /* CS_INVALID_CONFIG_ID */
> +       uint8_t             selected_tx_power;    /* PROC_ENABLE_COMPLETE */
> +       uint8_t             rtt_type;             /* RTT type */
> +       struct cs_procedure_data *current_proc;
> +       /* Cached header values for CONT events (per-connection state) */
> +       uint16_t last_proc_counter;
> +       uint16_t last_start_acl_conn_evt_counter;
> +       uint16_t last_freq_comp;
> +       int8_t last_ref_pwr_lvl;
> +};
> +
>  /* Ranging Service context */
>  struct ras {
>         struct bt_rap_db *rapdb;
> @@ -43,9 +207,17 @@ struct ras {
>         struct gatt_db_attribute *realtime_chrc;
>         struct gatt_db_attribute *realtime_chrc_ccc;
>         struct gatt_db_attribute *ondemand_chrc;
> +       struct gatt_db_attribute *ondemand_ccc;
>         struct gatt_db_attribute *cp_chrc;
> +       struct gatt_db_attribute *cp_ccc;
>         struct gatt_db_attribute *ready_chrc;
> +       struct gatt_db_attribute *ready_ccc;
>         struct gatt_db_attribute *overwritten_chrc;
> +       struct gatt_db_attribute *overwritten_ccc;
> +
> +       /* CCC state tracking for mutual exclusivity */
> +       uint16_t realtime_ccc_value;
> +       uint16_t ondemand_ccc_value;
>  };
>
>  struct bt_rap_db {
> @@ -70,6 +242,7 @@ struct bt_rap {
>         bt_rap_destroy_func_t debug_destroy;
>         void *debug_data;
>         void *user_data;
> +       struct cstracker *resptracker;
>  };
>
>  static struct queue *rap_db;
> @@ -90,6 +263,209 @@ struct bt_rap_ready {
>         void *data;
>  };
>
> +uint16_t default_ras_mtu = 247; /*Section 3.1.2 of RAP 1.0*/
> +uint8_t ras_segment_header_size = 1;
> +
> +static struct cs_procedure_data *cs_procedure_data_create(
> +                                       uint16_t procedure_counter,
> +                                       uint8_t num_antenna_paths,
> +                                       uint8_t configuration_id,
> +                                       uint8_t selected_tx_power)
> +{
> +       struct cs_procedure_data *d;
> +       uint8_t i;
> +
> +       d = calloc(1, sizeof(struct cs_procedure_data));
> +
> +       if (!d)
> +               return NULL;
> +
> +       d->counter = procedure_counter;
> +       d->num_antenna_paths = num_antenna_paths;
> +       d->local_status = CS_PROC_PARTIAL_RESULTS;
> +       d->remote_status = CS_PROC_PARTIAL_RESULTS;
> +       d->contains_complete_subevent_ = false;
> +       d->segmentation_header_.first_segment = 1;
> +       d->segmentation_header_.last_segment = 0;
> +       d->segmentation_header_.rolling_segment_counter = 0;
> +       d->ranging_header_.ranging_counter = procedure_counter;
> +       d->ranging_header_.configuration_id = configuration_id;
> +       d->ranging_header_.selected_tx_power = selected_tx_power;
> +       d->ranging_header_.antenna_paths_mask = 0;
> +
> +       for (i = 0; i < num_antenna_paths; i++)
> +               d->ranging_header_.antenna_paths_mask |= (1u << i);
> +
> +       d->ranging_header_.pct_format = IQ;
> +       memset(&d->ras_raw_data_, 0, sizeof(d->ras_raw_data_));
> +       d->ras_raw_data_index_ = 0;
> +       memset(&d->ras_subevent_data_, 0, sizeof(d->ras_subevent_data_));
> +       d->ras_subevent_counter_ = 0;
> +       d->initiator_reference_power_level = 0;
> +       d->reflector_reference_power_level = 0;
> +       d->ranging_header_prepended_ = false;
> +       d->ras_subevent_header_emitted = false;
> +
> +       return d;
> +}
> +
> +static void cs_procedure_data_destroy(struct cs_procedure_data *d)
> +{
> +       if (!d)
> +               return;
> +
> +       free(d->ras_raw_data_.iov_base);
> +       free(d->ras_subevent_data_.iov_base);
> +       free(d);
> +}
> +
> +static void cs_pd_set_local_status(struct cs_procedure_data *d,
> +                               enum cs_procedure_done_status s)
> +{
> +       if (d)
> +               d->local_status = s;
> +}
> +
> +static void cs_pd_set_remote_status(struct cs_procedure_data *d,
> +                               enum cs_procedure_done_status s)
> +{
> +       if (d)
> +               d->remote_status = s;
> +}
> +
> +static void cs_pd_set_reference_power_levels(struct cs_procedure_data *d,
> +                                       int8_t init_lvl, int8_t ref_lvl)
> +{
> +       if (!d)
> +               return;
> +
> +       d->initiator_reference_power_level = init_lvl;
> +       d->reflector_reference_power_level = ref_lvl;
> +}
> +
> +static void cs_pd_ras_begin_subevent(struct cs_procedure_data *d,
> +                               uint16_t start_acl_conn_event,
> +                               uint16_t frequency_compensation,
> +                               int8_t reference_power_level)
> +{
> +       if (!d)
> +               return;
> +
> +       d->ras_subevent_counter_++;
> +       d->ras_subevent_header_.start_acl_conn_event = start_acl_conn_event;
> +       d->ras_subevent_header_.frequency_compensation =
> +               frequency_compensation;
> +       d->ras_subevent_header_.reference_power_level = reference_power_level;
> +       d->ras_subevent_header_.num_steps_reported = 0;
> +       d->ras_subevent_header_emitted = false;
> +       d->ras_subevent_data_.iov_len = 0;
> +}
> +
> +static bool cs_pd_ras_append_subevent_bytes(struct cs_procedure_data *d,
> +                                       const uint8_t *bytes, size_t len)
> +{
> +       if (!d || !bytes || len == 0)
> +               return false;
> +
> +       return util_iov_append(&d->ras_subevent_data_, bytes, len) != NULL;
> +}
> +
> +static inline size_t serialize_ras_subevent_header(
> +                               const struct ras_subevent_header *h,
> +                               uint8_t *out, size_t out_len)
> +{
> +       uint16_t start_le;
> +       uint16_t freq_le;
> +
> +       if (!h || !out || out_len < RAS_SUBEVENT_HEADER_SIZE)
> +               return 0;
> +
> +       start_le = (uint16_t)h->start_acl_conn_event;
> +       out[0] = (uint8_t)(start_le & 0xFF);
> +       out[1] = (uint8_t)((start_le >> 8) & 0xFF);

What is the struct we want to produce here? And why aren't we using
acl_handle_pack? Or perhaps

handle = cpu_to_le16(acl_handle_pack(handle, flags)); /* if flags
should be carried over otherwise use acl_handle */

> +
> +       freq_le = (uint16_t)h->frequency_compensation;
> +       out[2] = (uint8_t)(freq_le & 0xFF);
> +       out[3] = (uint8_t)((freq_le >> 8) & 0xFF);

cpu_to_le16?

> +
> +       out[4] = (uint8_t)((h->ranging_done_status & 0x0F) |
> +                       ((h->subevent_done_status & 0x0F) << 4));

done_status_pack(ranging, subevent);

> +
> +       out[5] = (uint8_t)((h->ranging_abort_reason & 0x0F) |
> +                       ((h->subevent_abort_reason & 0x0F) << 4));

abort_reason_pack(ranging, subevent);

> +       out[6] = (uint8_t)h->reference_power_level;
> +       out[7] = (uint8_t)h->num_steps_reported;

Not sure what is up with casting above?

> +       return RAS_SUBEVENT_HEADER_SIZE;
> +}
> +
> +static bool cs_pd_ras_commit_subevent(struct cs_procedure_data *d,
> +                               uint8_t num_steps_reported,
> +                               uint8_t ranging_done_status,
> +                               uint8_t subevent_done_status,
> +                               uint8_t ranging_abort_reason,
> +                               uint8_t subevent_abort_reason)
> +{
> +       size_t hdr_sz;
> +       size_t payload_sz;
> +       size_t total;
> +       uint8_t *buf;
> +       size_t w;
> +       bool ok;
> +
> +       if (!d)
> +               return false;
> +
> +       d->ras_subevent_header_.num_steps_reported =
> +               (uint8_t)(d->ras_subevent_header_.num_steps_reported +
> +                       num_steps_reported);
> +       d->ras_subevent_header_.ranging_done_status = ranging_done_status;
> +       d->ras_subevent_header_.subevent_done_status = subevent_done_status;
> +       d->ras_subevent_header_.ranging_abort_reason = ranging_abort_reason;
> +       d->ras_subevent_header_.subevent_abort_reason = subevent_abort_reason;
> +
> +       if (subevent_done_status == SUBEVENT_DONE_ALL_RESULTS_COMPLETE)
> +               d->contains_complete_subevent_ = true;
> +
> +       if (subevent_done_status == SUBEVENT_DONE_PARTIAL_RESULTS)
> +               return true;
> +
> +       if (!d->ras_subevent_header_emitted) {
> +               hdr_sz = RAS_SUBEVENT_HEADER_SIZE;
> +               payload_sz = d->ras_subevent_data_.iov_len;
> +               total = hdr_sz + payload_sz;
> +               buf = (uint8_t *)malloc(total);
> +
> +               if (!buf)
> +                       return false;
> +
> +               w = serialize_ras_subevent_header(&d->ras_subevent_header_,
> +                                               buf, total);
> +
> +               if (w != hdr_sz) {
> +                       free(buf);
> +                       return false;
> +               }
> +
> +               if (payload_sz > 0)
> +                       memcpy(buf + hdr_sz,
> +                               (const uint8_t *)d->ras_subevent_data_.iov_base,
> +                               payload_sz);
> +
> +               ok = util_iov_append(&d->ras_raw_data_, buf, total) != NULL;
> +               free(buf);
> +
> +               if (!ok)
> +                       return false;
> +
> +               d->ras_subevent_data_.iov_len = 0;
> +               d->ras_subevent_header_emitted = true;
> +       }
> +
> +       return true;
> +}
> +
>  static struct ras *rap_get_ras(struct bt_rap *rap)
>  {
>         if (!rap)
> @@ -155,6 +531,11 @@ static void rap_free(void *data)
>
>         rap_db_free(rap->rrapdb);
>
> +       if (rap->resptracker) {
> +               free(rap->resptracker);
> +               rap->resptracker = NULL;
> +       }
> +
>         queue_destroy(rap->notify, free);
>         queue_destroy(rap->pending, NULL);
>         queue_destroy(rap->ready_cbs, rap_ready_free);
> @@ -240,6 +621,22 @@ bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func,
>         return true;
>  }
>
> +static void cs_tracker_init(struct cstracker *t)
> +{
> +       if (!t)
> +               return;
> +
> +       memset(t, 0, sizeof(*t));
> +       t->role = CS_ROLE_REFLECTOR;
> +       t->config_id = CS_INVALID_CONFIG_ID;
> +       t->rtt_type = 0;
> +       t->selected_tx_power = 0;
> +       t->last_proc_counter = 0;
> +       t->last_start_acl_conn_evt_counter = 0;
> +       t->last_freq_comp = 0;
> +       t->last_ref_pwr_lvl = 0;
> +}
> +
>  static void ras_features_read_cb(struct gatt_db_attribute *attrib,
>                                  unsigned int id, uint16_t offset,
>                                  uint8_t opcode, struct bt_att *att,
> @@ -304,6 +701,225 @@ static void ras_data_overwritten_read_cb(struct gatt_db_attribute *attrib,
>         gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
>  }
>
> +/* CCC write callbacks for custom handling */
> +static void ras_realtime_ccc_write_cb(struct gatt_db_attribute *attrib,
> +                                       unsigned int id, uint16_t offset,
> +                                       const uint8_t *value, size_t len,
> +                                       uint8_t opcode, struct bt_att *att,
> +                                       void *user_data)
> +{
> +       struct ras *ras = user_data;
> +       uint16_t ccc_value;
> +
> +       if (!ras) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_UNLIKELY);
> +               return;
> +       }
> +
> +       if (offset) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_INVALID_OFFSET);
> +               return;
> +       }
> +
> +       if (len != 2) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                       BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
> +               return;
> +       }
> +
> +       ccc_value = get_le16(value);
> +
> +       if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
> +           ccc_value != 0x0002 && ccc_value != 0x0003) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_WRITE_REQUEST_REJECTED);
> +               return;
> +       }
> +
> +       /* Check mutual exclusivity: reject if trying to enable realtime
> +        * while ondemand is already enabled
> +        */

It is probably worth mentioning if there is a test case that requires
this behavior.

> +       if (ccc_value != 0x0000 && ras->ondemand_ccc_value != 0x0000) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_CCC_IMPROPERLY_CONFIGURED);
> +               return;
> +       }
> +
> +       /* Update state */
> +       ras->realtime_ccc_value = ccc_value;
> +
> +       gatt_db_attribute_write_result(attrib, id, 0);
> +}
> +
> +static void ras_ondemand_ccc_write_cb(struct gatt_db_attribute *attrib,
> +                                       unsigned int id, uint16_t offset,
> +                                       const uint8_t *value, size_t len,
> +                                       uint8_t opcode, struct bt_att *att,
> +                                       void *user_data)
> +{
> +       struct ras *ras = user_data;
> +       uint16_t ccc_value;
> +
> +       if (!ras) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_UNLIKELY);
> +               return;
> +       }
> +
> +       if (offset) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_INVALID_OFFSET);
> +               return;
> +       }
> +
> +       if (len != 2) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                               BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
> +               return;
> +       }
> +
> +       ccc_value = get_le16(value);
> +
> +       if (ccc_value != 0x0000 && ccc_value != 0x0001 && ccc_value != 0x0002 &&
> +           ccc_value != 0x0003) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_WRITE_REQUEST_REJECTED);
> +               return;
> +       }
> +
> +       /* Check mutual exclusivity: reject if trying to enable ondemand
> +        * while realtime is already enabled
> +        */
> +       if (ccc_value != 0x0000 && ras->realtime_ccc_value != 0x0000) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_CCC_IMPROPERLY_CONFIGURED);
> +               return;
> +       }
> +
> +       /* Update state */
> +       ras->ondemand_ccc_value = ccc_value;
> +
> +       gatt_db_attribute_write_result(attrib, id, 0);

This looks duplicated, can we just use the same callback for both?

> +}
> +
> +static void ras_cp_ccc_write_cb(struct gatt_db_attribute *attrib,
> +                                       unsigned int id, uint16_t offset,
> +                                       const uint8_t *value, size_t len,
> +                                       uint8_t opcode, struct bt_att *att,
> +                                       void *user_data)
> +{
> +       struct ras *ras = user_data;
> +       uint16_t ccc_value;
> +
> +       if (!ras) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_UNLIKELY);
> +               return;
> +       }
> +
> +       if (offset) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_INVALID_OFFSET);
> +               return;
> +       }
> +
> +       if (len != 2) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                       BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
> +               return;
> +       }
> +
> +       ccc_value = get_le16(value);
> +
> +       if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
> +           ccc_value != 0x0002) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_WRITE_REQUEST_REJECTED);
> +               return;
> +       }
> +
> +       gatt_db_attribute_write_result(attrib, id, 0);
> +}
> +
> +static void ras_ready_ccc_write_cb(struct gatt_db_attribute *attrib,
> +                                       unsigned int id, uint16_t offset,
> +                                       const uint8_t *value, size_t len,
> +                                       uint8_t opcode, struct bt_att *att,
> +                                       void *user_data)
> +{
> +       struct ras *ras = user_data;
> +       uint16_t ccc_value;
> +
> +       if (!ras) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_UNLIKELY);
> +               return;
> +       }
> +
> +       if (offset) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_INVALID_OFFSET);
> +               return;
> +       }
> +
> +       if (len != 2) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                       BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
> +               return;
> +       }
> +
> +       ccc_value = get_le16(value);
> +
> +       if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
> +           ccc_value != 0x0002 && ccc_value != 0x0003) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_WRITE_REQUEST_REJECTED);
> +               return;
> +       }
> +
> +       gatt_db_attribute_write_result(attrib, id, 0);
> +}
> +
> +static void ras_overwritten_ccc_write_cb(struct gatt_db_attribute *attrib,
> +                                       unsigned int id, uint16_t offset,
> +                                       const uint8_t *value, size_t len,
> +                                       uint8_t opcode, struct bt_att *att,
> +                                       void *user_data)
> +{
> +       struct ras *ras = user_data;
> +       uint16_t ccc_value;
> +
> +       if (!ras) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_UNLIKELY);
> +               return;
> +       }
> +
> +       if (offset) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ATT_ERROR_INVALID_OFFSET);
> +               return;
> +       }
> +
> +       if (len != 2) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                       BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
> +               return;
> +       }
> +
> +       ccc_value = get_le16(value);
> +
> +       if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
> +           ccc_value != 0x0002 && ccc_value != 0x0003) {
> +               gatt_db_attribute_write_result(attrib, id,
> +                                       BT_ERROR_WRITE_REQUEST_REJECTED);
> +               return;
> +       }
> +
> +       gatt_db_attribute_write_result(attrib, id, 0);
> +}
>  /* Service registration – store attribute pointers */
>  static struct ras *register_ras_service(struct gatt_db *db)
>  {
> @@ -349,9 +965,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
>                                                   NULL, NULL, ras);
>
>         ras->realtime_chrc_ccc =
> -               gatt_db_service_add_ccc(ras->svc,
> -                                       BT_ATT_PERM_READ |
> -                                       BT_ATT_PERM_WRITE);
> +               gatt_db_service_add_ccc_custom(ras->svc,
> +                                       BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
> +                                       ras_realtime_ccc_write_cb, ras);
>
>         /* On-demand Ranging Data */
>         bt_uuid16_create(&uuid, RAS_ONDEMAND_DATA_UUID);
> @@ -364,8 +980,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
>                                                   ras_ondemand_read_cb, NULL,
>                                                   ras);
>
> -       gatt_db_service_add_ccc(ras->svc,
> -                               BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
> +       ras->ondemand_ccc = gatt_db_service_add_ccc_custom(ras->svc,
> +                                       BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
> +                                       ras_ondemand_ccc_write_cb, ras);
>
>         /* RAS Control Point */
>         bt_uuid16_create(&uuid, RAS_CONTROL_POINT_UUID);
> @@ -379,8 +996,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
>                                                   ras_control_point_write_cb,
>                                                   ras);
>
> -       gatt_db_service_add_ccc(ras->svc,
> -                               BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
> +       ras->cp_ccc = gatt_db_service_add_ccc_custom(ras->svc,
> +                                       BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
> +                                       ras_cp_ccc_write_cb, ras);
>
>         /* RAS Data Ready */
>         bt_uuid16_create(&uuid, RAS_DATA_READY_UUID);
> @@ -394,8 +1012,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
>                                                   ras_data_ready_read_cb, NULL,
>                                                   ras);
>
> -       gatt_db_service_add_ccc(ras->svc,
> -                               BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
> +       ras->ready_ccc = gatt_db_service_add_ccc_custom(ras->svc,
> +                                       BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
> +                                       ras_ready_ccc_write_cb, ras);
>
>         /* RAS Data Overwritten */
>         bt_uuid16_create(&uuid, RAS_DATA_OVERWRITTEN_UUID);
> @@ -409,8 +1028,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
>                                                   ras_data_overwritten_read_cb,
>                                                   NULL, ras);
>
> -       gatt_db_service_add_ccc(ras->svc,
> -                               BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
> +       ras->overwritten_ccc = gatt_db_service_add_ccc_custom(ras->svc,
> +                                       BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
> +                                       ras_overwritten_ccc_write_cb, ras);

Do we really need custom callbacks for every single CCC here? I though
that was just for those that cannot be enabled together, otherwise
they just identical/duplicated.

>         /* Activate the service */
>         gatt_db_service_set_active(ras->svc, true);
> @@ -503,32 +1123,746 @@ bool bt_rap_unregister(unsigned int id)
>         return true;
>  }
>
> +static inline size_t serialize_segmentation_header(
> +                               const struct segmentation_header *s,
> +                               uint8_t *out, size_t out_len)
> +{
> +       if (!s || !out || out_len < 1)
> +               return 0;
> +
> +       /* [0] bit0: first, bit1: last, bits2..7: rolling counter */
> +       out[0] = (s->first_segment ? 0x01 : 0x00) |
> +               (s->last_segment ? 0x02 : 0x00) |
> +               ((s->rolling_segment_counter & 0x3F) << 2);
> +
> +       return 1;
> +}
> +
> +static inline size_t serialize_ranging_header(const struct ranging_header *r,
> +                                               uint8_t *out, size_t out_len)
> +{
> +       uint16_t rcid;
> +
> +       if (!r || !out || out_len < RAS_RANGING_HEADER_SIZE)
> +               return 0;
> +
> +       /* Little-endian pack: [rc (12 bits) | cfg_id (4 bits)] */
> +       rcid = RANGING_HDR_PACK_BYTE0_1(r->ranging_counter,
> +                                       r->configuration_id);
> +
> +       put_le16(rcid, out + 0);
> +       out[2] = (uint8_t)r->selected_tx_power;
> +       /* Byte 3: antenna_paths_mask | reserved | pct_format */
> +       out[3] = RANGING_HDR_PACK_BYTE3(r->antenna_paths_mask,
> +                                       r->pct_format);

Can't we define packed structs instead of serializing to a byte array
like this? That way we can actually use sizeof, etc.

> +       return RAS_RANGING_HEADER_SIZE;
> +}
> +
> +static inline uint16_t ras_att_value_payload_max(struct bt_rap *rap)
> +{
> +       struct bt_att *att = bt_rap_get_att(rap);
> +       uint16_t mtu = att ? bt_att_get_mtu(att) : default_ras_mtu;

Still don't get it how this can be called with a NULL att?

> +
> +       return (uint16_t)(mtu > ATT_OVERHEAD ?
> +               (mtu - ATT_OVERHEAD - TOTAL_RAS_RANGING_HEADER_SIZE -
> +               ras_segment_header_size) : 0);
> +}
> +
> +/* Prepend data to an iovec */
> +static bool iov_prepend_bytes(struct iovec *iov, const uint8_t *bytes,
> +                               size_t len)
> +{
> +       size_t new_len;
> +       void *new_base;
> +
> +       if (!iov || !bytes || len == 0)
> +               return false;
> +
> +       new_len = iov->iov_len + len;
> +       new_base = malloc(new_len);

This wouldn't be too bad if we could avoid it, since this is
apparently done inline, allowing us to avoid copying each segment on a
new memory chunk or in the worst case, we can use multiple iovs to
track each segment independently and add an API to notify using writev
semantics.

> +       if (!new_base)
> +               return false;
> +
> +       memcpy(new_base, bytes, len);
> +
> +       if (iov->iov_len > 0)
> +               memcpy((uint8_t *)new_base + len, iov->iov_base,
> +                       iov->iov_len);
> +
> +       free(iov->iov_base);
> +       iov->iov_base = new_base;
> +       iov->iov_len = new_len;
> +
> +       return true;
> +}
> +
> +/* Append the 4-byte RangingHeader to ras_raw_data_ on first segment */
> +static bool ras_maybe_prepend_ranging_header(struct cs_procedure_data *d)
> +{
> +       uint8_t hdr[RAS_RANGING_HEADER_SIZE];
> +       size_t w;
> +       bool ok;
> +
> +       if (!d)
> +               return false;
> +
> +       if (d->ranging_header_prepended_)
> +               return false;
> +
> +       if (!d->segmentation_header_.first_segment)
> +               return false;
> +
> +       if (d->ras_raw_data_index_ != 0)
> +               return false;
> +
> +       w = serialize_ranging_header(&d->ranging_header_, hdr, sizeof(hdr));
> +
> +       if (w != RAS_RANGING_HEADER_SIZE)
> +               return false;
> +
> +       ok = iov_prepend_bytes(&d->ras_raw_data_, hdr, w);
> +
> +       if (ok)
> +               d->ranging_header_prepended_ = true;
> +
> +       return ok;
> +}
> +
> +static void send_ras_segment_data(struct bt_rap *rap,
> +                               struct cs_procedure_data *proc)
> +{
> +       struct ras *ras;
> +       uint16_t value_max;
> +       const uint16_t header_len = ras_segment_header_size;
> +       uint16_t raw_payload_size;
> +       bool ok;
> +
> +       if (!rap || !proc)
> +               return;
> +
> +       if (!rap->lrapdb || !rap->lrapdb->ras)
> +               return;

Empty line after the if statement.

> +       ras = rap->lrapdb->ras;
> +       value_max = ras_att_value_payload_max(rap);
> +
> +       if (value_max == 0) {
> +               DBG(rap, "value_max=0 (MTU not available?)");
> +               return;
> +       }
> +
> +       if (value_max <= header_len) {
> +               DBG(rap, "value_max(%u) too small for header", value_max);
> +               return;
> +       }
> +
> +       raw_payload_size = (uint16_t)(value_max - header_len);
> +
> +       /* Convert tail recursion to loop */
> +       while (true) {
> +               size_t total_len = proc->ras_raw_data_.iov_len;
> +               size_t index = proc->ras_raw_data_index_;
> +               size_t unsent_data_size;
> +               uint16_t copy_size;
> +               uint16_t seg_len;
> +               uint8_t *seg;
> +               uint16_t wr = 0;
> +
> +               if (index > total_len)
> +                       index = total_len;
> +
> +               unsent_data_size = total_len - index;
> +
> +               if (unsent_data_size == 0)
> +                       return;
> +
> +               /* Set last_segment if procedure complete or fits in segment */
> +               if ((proc->local_status != CS_PROC_PARTIAL_RESULTS &&
> +                               unsent_data_size <= raw_payload_size) ||
> +                       (proc->contains_complete_subevent_ &&
> +                               unsent_data_size <= raw_payload_size)) {
> +                       proc->segmentation_header_.last_segment = 1;
> +               } else {
> +                       proc->segmentation_header_.last_segment = 0;
> +               }
> +
> +               /* Wait for more data if needed and not last segment */
> +               if (unsent_data_size < raw_payload_size &&
> +                               proc->segmentation_header_.last_segment == 0) {
> +                       DBG(rap, "waiting for more data (unsent=%zu < "
> +                               "payload=%u)", unsent_data_size,
> +                               raw_payload_size);
> +                       return;
> +               }
> +
> +               copy_size = (uint16_t)((unsent_data_size < raw_payload_size) ?
> +                               unsent_data_size : raw_payload_size);
> +               seg_len = (uint16_t)(header_len + copy_size);
> +               seg = (uint8_t *)malloc(seg_len);
> +
> +               if (!seg) {
> +                       DBG(rap, "OOM (%u)", seg_len);
> +                       return;
> +               }
> +
> +               wr += (uint16_t)serialize_segmentation_header(
> +                               &proc->segmentation_header_, seg + wr,
> +                               seg_len - wr);
> +               memcpy(seg + wr,
> +                       (const uint8_t *)proc->ras_raw_data_.iov_base + index,
> +                       copy_size);
> +               wr += copy_size;
> +
> +               /* Try sending to real-time characteristic */
> +               if (ras->realtime_chrc)
> +                       ok = gatt_db_attribute_notify(ras->realtime_chrc, seg,
> +                                               wr, bt_rap_get_att(rap));
> +
> +               /* Try sending to on-demand characteristic */
> +               if (ras->ondemand_chrc)
> +                       ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg,
> +                                               wr, bt_rap_get_att(rap));
> +
> +               free(seg);
> +
> +               if (!ok) {
> +                       DBG(rap, "Failed to send RAS notification");
> +                       return;
> +               }
> +
> +               /* Advance read cursor and update segmentation state */
> +               proc->ras_raw_data_index_ += copy_size;
> +               proc->segmentation_header_.first_segment = 0;
> +               proc->segmentation_header_.rolling_segment_counter =
> +                       (uint8_t)((proc->segmentation_header_
> +                               .rolling_segment_counter + 1) & 0x3F);
> +
> +               if (proc->segmentation_header_.last_segment ||
> +                               proc->ras_raw_data_index_ >=
> +                               proc->ras_raw_data_.iov_len) {
> +                       DBG(rap, "RAS clear ras buffers");
> +                       proc->ras_raw_data_.iov_len = 0;
> +                       proc->ras_raw_data_index_ = 0;
> +                       proc->ranging_header_prepended_ = false;
> +                       return;
> +               }
> +       }
> +}
> +
> +static inline void resptracker_reset_current_proc(struct cstracker *t)
> +{
> +       if (!t)
> +               return;
> +
> +       if (t->current_proc) {
> +               cs_procedure_data_destroy(t->current_proc);
> +               t->current_proc = NULL;
> +       }
> +}
> +/* Unified local subevent handler */
> +static void handle_local_subevent_result(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 void *step_bytes)
> +{
> +       struct cstracker *resptracker;
> +       struct cs_procedure_data *proc;
> +       const struct cs_step_data *steps;
> +       uint8_t idx;
> +
> +       if (!rap || !rap->resptracker || !step_bytes)
> +               return;
> +
> +       resptracker = rap->resptracker;
> +
> +       if (resptracker->current_proc) {
> +               struct cs_procedure_data *cur = resptracker->current_proc;
> +
> +               if (has_header_fields && cur->counter != proc_counter) {
> +                       /* Safety: a new procedure; destroy the previous one */
> +                       resptracker_reset_current_proc(resptracker);
> +               }
> +       }
> +
> +       proc = resptracker->current_proc;
> +       /* Cache header info from a RESULT event for later CONT usage */
> +       if (has_header_fields) {
> +               resptracker->last_proc_counter = proc_counter;
> +               resptracker->last_start_acl_conn_evt_counter =
> +                       start_acl_conn_evt_counter;
> +               resptracker->last_freq_comp = freq_comp;
> +               resptracker->last_ref_pwr_lvl = ref_pwr_lvl;
> +       }
> +
> +       /* Create the procedure on first use */
> +       if (!proc) {
> +               uint16_t create_counter = has_header_fields ? proc_counter :
> +                                       resptracker->last_proc_counter;
> +
> +               proc = cs_procedure_data_create(create_counter,
> +                                               num_ant_paths,
> +                                               config_id,
> +                                               resptracker->selected_tx_power);
> +               if (!proc)
> +                       return;
> +
> +               resptracker->current_proc = proc;
> +
> +               /* Reference power levels and status defaults */
> +               cs_pd_set_reference_power_levels(proc,
> +                       has_header_fields ? ref_pwr_lvl :
> +                               resptracker->last_ref_pwr_lvl,
> +                       has_header_fields ? ref_pwr_lvl :
> +                               resptracker->last_ref_pwr_lvl);
> +               cs_pd_set_local_status(proc,
> +                       (enum cs_procedure_done_status)proc_done_status);
> +               cs_pd_set_remote_status(proc,
> +                       (enum cs_procedure_done_status)subevt_done_status);
> +       }
> +
> +       /* Begin a new RAS subevent only when we have header fields */
> +       if (has_header_fields) {
> +               cs_pd_ras_begin_subevent(proc,
> +                                       start_acl_conn_evt_counter,
> +                                       freq_comp,
> +                                       ref_pwr_lvl);
> +       }
> +
> +       /* step_bytes points to an array of struct cs_step_data */
> +       steps = (const struct cs_step_data *)step_bytes;
> +
> +       /* Process each step */
> +       for (idx = 0; idx < num_steps_reported; idx++) {
> +               const struct cs_step_data *step = &steps[idx];
> +               const uint8_t mode = step->step_mode;
> +               const uint8_t channel = step->step_chnl;
> +               const uint8_t payload_len = step->step_data_length;
> +               uint8_t mode_byte;
> +               uint8_t mode_type;
> +               const uint8_t *payload;
> +               uint8_t plen;
> +               bool step_aborted;
> +
> +               /* Check if step is aborted: bit 7 of step_mode or
> +                * 0 payload len
> +                */
> +               step_aborted = (mode & RAS_STEP_ABORTED_BIT) ||
> +                       (payload_len == 0);
> +
> +               DBG(rap, "step[%u]: mode=0x%02x channel=%u payload_len=%u "
> +                       "aborted=%s", idx, mode, channel, payload_len,
> +                       step_aborted ? "YES" : "NO");
> +
> +               /* Slim-step serialization for RAS:
> +                * - 1 byte: mode (bit7 set if aborted)
> +                * - payload: exactly step_data_length bytes (raw mode data)
> +                */
> +               mode_byte = step->step_mode;
> +               payload = (const uint8_t *)&step->step_mode_data;
> +               plen = step->step_data_length;
> +
> +               if (step_aborted) {
> +                       /* Ensure abort bit is set */
> +                       mode_byte |= RAS_STEP_ABORTED_BIT;
> +                       cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1);
> +                       /* No payload when aborted - per RAS spec Table 3.8 */
> +                       DBG(rap, "step[%u]: mode=0x%02x aborted, "
> +                               "no payload sent", idx, mode_byte);
> +               } else {
> +                       mode_type = mode & 0x03;
> +
> +                       /* Mode byte first (without abort bit) */
> +                       cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1);
> +
> +                       switch (mode_type) {
> +                       case CS_MODE_ZERO: {
> +                               /* Mode 0: use raw structure bytes */
> +                               payload =
> +                                       (const uint8_t *)&step->step_mode_data;
> +                               plen = step->step_data_length;
> +                               cs_pd_ras_append_subevent_bytes(proc,
> +                                                               payload, plen);
> +                               DBG(rap, "step[%u]: mode=0x%02x Mode0 "
> +                                       "payload_len=%u sent",
> +                                       idx, mode_byte, (unsigned int)plen);
> +                               break;
> +                       }
> +                       case CS_MODE_ONE: {
> +                               const struct cs_mode_one_data *m1 =
> +                                       &step->step_mode_data.mode_one_data;
> +                               uint8_t buf[16];  /* Max Mode 1 size */
> +                               uint8_t *p = buf;
> +                               uint16_t time_val;
> +                               uint32_t pct1;
> +                               uint32_t pct2;
> +                               enum cs_role cs_role = resptracker->role;
> +                               uint8_t cs_rtt_type = resptracker->rtt_type;
> +
> +                               *p++ = m1->packet_quality;
> +                               *p++ = m1->packet_nadm;
> +                               *p++ = m1->packet_rssi_dbm;
> +
> +                               /* Time value (2 bytes LE) - use the
> +                                * appropriate field based on role
> +                                */
> +                               if (cs_role == CS_ROLE_REFLECTOR)
> +                                       time_val = m1->tod_toa_refl;
> +                               else
> +                                       time_val = m1->toa_tod_init;
> +
> +                               *p++ = (uint8_t)(time_val & 0xFF);
> +                               *p++ = (uint8_t)((time_val >> 8) & 0xFF);
> +
> +                               *p++ = m1->packet_ant;
> +
> +                               /* PCT samples if RTT type contains
> +                                * sounding sequence
> +                                */
> +                               if (cs_rtt_type == 0x01 ||
> +                                               cs_rtt_type == 0x02) {
> +                                       pct1 = ((uint32_t)(m1->packet_pct1
> +                                                       .i_sample &
> +                                                       0x0FFF)) |
> +                                               (((uint32_t)(m1->packet_pct1
> +                                                       .q_sample &
> +                                                       0x0FFF)) << 12);
> +                                       *p++ = (uint8_t)(pct1 & 0xFF);
> +                                       *p++ = (uint8_t)((pct1 >> 8) & 0xFF);
> +                                       *p++ = (uint8_t)((pct1 >> 16) & 0xFF);
> +
> +                                       /* PCT2 (3 bytes) */
> +                                       pct2 = ((uint32_t)(m1->packet_pct2
> +                                                       .i_sample &
> +                                                       0x0FFF)) |
> +                                               (((uint32_t)(m1->packet_pct2
> +                                                       .q_sample &
> +                                                       0x0FFF)) << 12);
> +                                       *p++ = (uint8_t)(pct2 & 0xFF);
> +                                       *p++ = (uint8_t)((pct2 >> 8) & 0xFF);
> +                                       *p++ = (uint8_t)((pct2 >> 16) & 0xFF);
> +                               }
> +
> +                               plen = (uint8_t)(p - buf);
> +                               cs_pd_ras_append_subevent_bytes(proc, buf,
> +                                                               plen);
> +                               DBG(rap, "step[%u]: mode=0x%02x Mode1 "
> +                                       "serialized payload_len=%u role=%s "
> +                                       "rtt_type=0x%02x",
> +                                       idx, mode_byte, (unsigned int)plen,
> +                                       cs_role == CS_ROLE_INITIATOR ?
> +                                               "INIT" : "REFL", cs_rtt_type);
> +                               break;
> +                       }
> +                       case CS_MODE_TWO: {
> +                               const struct cs_mode_two_data *m2 =
> +                                       &step->step_mode_data.mode_two_data;
> +                               /* Max Mode 2 size: 1 + 5*(3+1) = 21 bytes */
> +                               uint8_t buf[64];
> +                               uint8_t *p = buf;
> +                               uint8_t k;
> +                               uint8_t num_paths = (num_ant_paths + 1) < 5 ?
> +                                       (num_ant_paths + 1) : 5;
> +
> +                               *p++ = m2->ant_perm_index;
> +
> +                               /* Serialize each path: PCT (3 bytes) +
> +                                * quality (1 byte)
> +                                */
> +                               for (k = 0; k < num_paths; k++) {
> +                                       /* Convert 4-byte structure PCT to
> +                                        * 3-byte wire format
> +                                        */
> +                                       uint32_t pct =
> +                                               ((uint32_t)(m2->tone_pct[k]
> +                                                       .i_sample &
> +                                                       0x0FFF)) |
> +                                               (((uint32_t)(m2->tone_pct[k]
> +                                                       .q_sample &
> +                                                       0x0FFF)) << 12);
> +                                       *p++ = (uint8_t)(pct & 0xFF);
> +                                       *p++ = (uint8_t)((pct >> 8) & 0xFF);
> +                                       *p++ = (uint8_t)((pct >> 16) & 0xFF);
> +
> +                                       *p++ = m2->tone_quality_indicator[k];
> +                               }
> +
> +                               plen = (uint8_t)(p - buf);
> +                               cs_pd_ras_append_subevent_bytes(proc, buf,
> +                                                               plen);
> +                               DBG(rap, "step[%u]: mode=0x%02x Mode2 "
> +                                       "serialized payload_len=%u paths=%u",
> +                                       idx, mode_byte, (unsigned int)plen,
> +                                       num_paths);
> +                               break;
> +                       }
> +                       case CS_MODE_THREE: {
> +                               const struct cs_mode_three_data *m3 =
> +                                       &step->step_mode_data.mode_three_data;
> +                               const struct cs_mode_one_data *m1 =
> +                                       &m3->mode_one_data;
> +                               const struct cs_mode_two_data *m2 =
> +                                       &m3->mode_two_data;
> +                               uint8_t buf[80];  /* Max Mode 3 size */
> +                               uint8_t *p = buf;
> +                               uint8_t k;
> +                               uint32_t pct1;
> +                               uint32_t pct2;
> +                               uint8_t num_paths = (num_ant_paths + 1) < 5 ?
> +                                       (num_ant_paths + 1) : 5;
> +                               uint8_t cs_rtt_type = resptracker->rtt_type;
> +                               enum cs_role cs_role = resptracker->role;
> +                               uint16_t time_val;
> +                               bool include_pct;
> +
> +                               /* Determine if PCT samples should be
> +                                * included
> +                                */
> +                               include_pct = (cs_rtt_type == 0x01 ||
> +                                               cs_rtt_type == 0x02) &&
> +                                       (cs_role == CS_ROLE_INITIATOR ||
> +                                               cs_role == CS_ROLE_REFLECTOR);
> +
> +                               /* Mode 1 portion - basic fields */
> +                               *p++ = m1->packet_quality;
> +                               *p++ = m1->packet_nadm;
> +                               *p++ = m1->packet_rssi_dbm;
> +
> +                               /* Time value based on role */
> +                               if (cs_role == CS_ROLE_REFLECTOR)
> +                                       time_val = m1->tod_toa_refl;
> +                               else
> +                                       time_val = m1->toa_tod_init;
> +
> +                               *p++ = (uint8_t)(time_val & 0xFF);
> +                               *p++ = (uint8_t)((time_val >> 8) & 0xFF);
> +
> +                               *p++ = m1->packet_ant;
> +
> +                               /* PCT samples if conditions met */
> +                               if (include_pct) {
> +                                       pct1 = ((uint32_t)(m1->packet_pct1
> +                                                       .i_sample &
> +                                                       0x0FFF)) |
> +                                               (((uint32_t)(m1->packet_pct1
> +                                                       .q_sample &
> +                                                       0x0FFF)) << 12);
> +                                       *p++ = (uint8_t)(pct1 & 0xFF);
> +                                       *p++ = (uint8_t)((pct1 >> 8) & 0xFF);
> +                                       *p++ = (uint8_t)((pct1 >> 16) & 0xFF);
> +
> +                                       pct2 = ((uint32_t)(m1->packet_pct2
> +                                                       .i_sample &
> +                                                       0x0FFF)) |
> +                                               (((uint32_t)(m1->packet_pct2
> +                                                       .q_sample &
> +                                                       0x0FFF)) << 12);
> +                                       *p++ = (uint8_t)(pct2 & 0xFF);
> +                                       *p++ = (uint8_t)((pct2 >> 8) & 0xFF);
> +                                       *p++ = (uint8_t)((pct2 >> 16) & 0xFF);
> +                               }
> +
> +                               /* Mode 2 portion */
> +                               *p++ = m2->ant_perm_index;
> +
> +                               for (k = 0; k < num_paths; k++) {
> +                                       uint32_t pct =
> +                                               ((uint32_t)(m2->tone_pct[k]
> +                                                       .i_sample &
> +                                                       0x0FFF)) |
> +                                               (((uint32_t)(m2->tone_pct[k]
> +                                                       .q_sample &
> +                                                       0x0FFF)) << 12);
> +                                       *p++ = (uint8_t)(pct & 0xFF);
> +                                       *p++ = (uint8_t)((pct >> 8) & 0xFF);
> +                                       *p++ = (uint8_t)((pct >> 16) & 0xFF);
> +
> +                                       *p++ = m2->tone_quality_indicator[k];
> +                               }
> +
> +                               plen = (uint8_t)(p - buf);
> +                               cs_pd_ras_append_subevent_bytes(proc, buf,
> +                                                               plen);
> +                               DBG(rap, "step[%u]: mode=0x%02x Mode3 "
> +                                       "serialized payload_len=%u paths=%u "
> +                                       "role=%s rtt_type=0x%02x pct=%s",
> +                                       idx, mode_byte, (unsigned int)plen,
> +                                       num_paths,
> +                                       cs_role == CS_ROLE_INITIATOR ?
> +                                               "INIT" : "REFL",
> +                                       cs_rtt_type,
> +                                       include_pct ? "YES" : "NO");
> +                               break;
> +                       }

Make each case call a helper function to perform the operation.

> +                       default:
> +                               /* Unknown mode: use raw structure bytes */
> +                               payload =
> +                                       (const uint8_t *)&step->step_mode_data;
> +                               plen = step->step_data_length;
> +                               cs_pd_ras_append_subevent_bytes(proc, payload,
> +                                                               plen);
> +                               DBG(rap, "step[%u]: mode=0x%02x unknown mode, "
> +                                       "payload_len=%u sent",
> +                                       idx, mode_byte, (unsigned int)plen);
> +                               break;
> +                       }
> +               }
> +       }
> +
> +       /* Update status for this chunk */
> +       cs_pd_set_local_status(proc,
> +                       (enum cs_procedure_done_status)proc_done_status);
> +       cs_pd_set_remote_status(proc,
> +                       (enum cs_procedure_done_status)subevt_done_status);
> +
> +       /* Commit subevent chunk (RESULT or CONT) */
> +       cs_pd_ras_commit_subevent(proc,
> +               num_steps_reported,
> +               proc_done_status,
> +               subevt_done_status,
> +               abort_reason & 0x0F,
> +               (abort_reason >> 4) & 0x0F);
> +
> +       /* Ensure first segment body starts with the 4-byte RangingHeader */
> +       ras_maybe_prepend_ranging_header(proc);
> +
> +       if (subevt_done_status != SUBEVENT_DONE_PARTIAL_RESULTS)
> +               /* Send RAS raw segment data */
> +               send_ras_segment_data(rap, proc);
> +
> +       /* Procedure complete? Clean up */
> +       if (proc_done_status == CS_PROC_ALL_RESULTS_COMPLETE) {
> +               DBG(rap, "Destroying CsProcedureData counter=%u and "
> +                       "clearing current_proc", proc->counter);
> +               resptracker_reset_current_proc(resptracker);
> +               /* Reset cached header values for next procedure */
> +               resptracker->last_proc_counter = 0;
> +               resptracker->last_start_acl_conn_evt_counter = 0;
> +               resptracker->last_freq_comp = 0;
> +               resptracker->last_ref_pwr_lvl = 0;
> +       }
> +}
> +
> +static void form_ras_data_with_cs_subevent_result(struct bt_rap *rap,
> +               const struct rap_ev_cs_subevent_result *data,
> +               uint16_t length)
> +{
> +       size_t base_len = offsetof(struct rap_ev_cs_subevent_result,
> +                                       step_data);
> +
> +       if (!rap || !rap->resptracker || !data)
> +               return;
> +
> +       /* Defensive check: base header must be present */
> +       if (length < base_len)
> +               return;
> +
> +       DBG(rap, "Received CS subevent result subevent: len=%d", length);
> +
> +       handle_local_subevent_result(rap,
> +               true,                   /* has header fields */
> +               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);       /* start of steps */
> +}
> +static void form_ras_data_with_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 *resptracker;
> +
> +       if (!rap || !rap->resptracker || !cont)
> +               return;
> +
> +       if (length < base_len)
> +               return;
> +
> +       DBG(rap, "Received CS subevent result continue subevent: len=%d",
> +               length);
> +
> +       resptracker = rap->resptracker;
> +
> +       /* Use cached header values captured from the last RESULT event */
> +       handle_local_subevent_result(rap,
> +               false,                  /* CONT has no header fields */
> +               cont->config_id,
> +               cont->num_ant_paths,
> +               resptracker->last_proc_counter,
> +               resptracker->last_start_acl_conn_evt_counter,
> +               resptracker->last_freq_comp,
> +               resptracker->last_ref_pwr_lvl,
> +               cont->proc_done_status,
> +               cont->subevt_done_status,
> +               cont->abort_reason,
> +               cont->num_steps_reported,
> +               cont->step_data);
> +}
>  void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
>                                                 const void *param,
>                                                 void *user_data)
>  {
> +       const struct rap_ev_cs_subevent_result_cont *cont = param;
>         struct bt_rap *rap = user_data;
>
>         DBG(rap, "Received CS subevent CONT: len=%d", length);
> +
> +       form_ras_data_with_cs_subevent_result_cont(rap, cont, length);
>  }
>
>  void bt_rap_hci_cs_subevent_result_callback(uint16_t length,
>                                         const void *param,
>                                         void *user_data)
>  {
> +       const struct rap_ev_cs_subevent_result *data = param;
>         struct bt_rap *rap = user_data;
>
>         DBG(rap, "Received CS subevent: len=%d", length);
> +
> +       /* Populate CsProcedureData and send RAS payload */
> +       form_ras_data_with_cs_subevent_result(rap, data, length);
>  }
>
>  void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length,
>                                                 const void *param,
>                                                 void *user_data)
>  {
> +       const struct rap_ev_cs_proc_enable_cmplt *data = param;
>         struct bt_rap *rap = user_data;
> +       struct cstracker *resptracker;
>
>         DBG(rap, "Received CS procedure enable complete subevent: len=%d",
>             length);
> +
> +       if (!rap->resptracker) {
> +               resptracker = new0(struct cstracker, 1);
> +               cs_tracker_init(resptracker);
> +               rap->resptracker = resptracker;
> +       }
> +
> +       resptracker = rap->resptracker;
> +
> +       /* Populate responder tracker */
> +       resptracker->config_id = data->config_id;
> +       resptracker->selected_tx_power = data->sel_tx_pwr;
>  }
>
>  void bt_rap_hci_cs_sec_enable_complete_callback(uint16_t length,
> @@ -544,9 +1878,27 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
>                                         const void *param,
>                                         void *user_data)
>  {
> +       const struct rap_ev_cs_config_cmplt *data = param;
>         struct bt_rap *rap = user_data;
> +       struct cstracker *resptracker;
> +
> +       if (!rap)
> +               return;
>
>         DBG(rap, "Received CS config complete subevent: len=%d", length);
> +
> +       if (!rap->resptracker) {
> +               resptracker = new0(struct cstracker, 1);
> +               cs_tracker_init(resptracker);
> +               rap->resptracker = resptracker;
> +       }
> +
> +       resptracker = rap->resptracker;
> +
> +       /* Basic fields */
> +       resptracker->config_id = data->config_id;
> +       resptracker->role = data->role;
> +       resptracker->rtt_type = data->rtt_type;
>  }
>
>  struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
> @@ -786,7 +2138,7 @@ bool bt_rap_attach(struct bt_rap *rap, struct bt_gatt_client *client)
>
>         bt_uuid16_create(&uuid, RAS_UUID16);
>
> -       gatt_db_foreach_service(rap->lrapdb->db, &uuid,
> +       gatt_db_foreach_service(rap->rrapdb->db, &uuid,
>                                 foreach_rap_service, rap);
>
>         return true;
> diff --git a/src/shared/rap.h b/src/shared/rap.h
> index 15ddea295..c9431aecd 100644
> --- a/src/shared/rap.h
> +++ b/src/shared/rap.h
> @@ -97,11 +97,11 @@ struct cs_mode_zero_data {
>
>  struct cs_mode_one_data {
>         uint8_t packet_quality;
> -       uint8_t packet_rssi_dbm;
> -       uint8_t packet_ant;
>         uint8_t packet_nadm;
> +       uint8_t packet_rssi_dbm;
>         int16_t toa_tod_init;
>         int16_t tod_toa_refl;
> +       uint8_t packet_ant;
>         struct pct_iq_sample packet_pct1;
>         struct pct_iq_sample packet_pct2;
>  };
> --
> 2.34.1
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* RE: Support for block device NVMEM providers
From: bluez.test.bot @ 2026-04-28 17:44 UTC (permalink / raw)
  To: linux-bluetooth, loic.poulain
In-Reply-To: <20260428-block-as-nvmem-v1-1-6ad23e75190a@oss.qualcomm.com>

[-- Attachment #1: Type: text/plain, Size: 5167 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=1086909

---Test result---

Test Summary:
CheckPatch                    FAIL      4.55 seconds
GitLint                       PASS      1.73 seconds
SubjectPrefix                 FAIL      0.59 seconds
BuildKernel                   PASS      21.22 seconds
CheckAllWarning               PASS      22.13 seconds
CheckSparse                   PASS      22.41 seconds
BuildKernel32                 PASS      20.10 seconds
TestRunnerSetup               PASS      418.01 seconds
TestRunner_l2cap-tester       PASS      23.43 seconds
TestRunner_iso-tester         PASS      28.84 seconds
TestRunner_bnep-tester        PASS      5.01 seconds
TestRunner_mgmt-tester        FAIL      96.51 seconds
TestRunner_rfcomm-tester      PASS      7.54 seconds
TestRunner_sco-tester         PASS      11.75 seconds
TestRunner_ioctl-tester       PASS      8.01 seconds
TestRunner_mesh-tester        FAIL      9.97 seconds
TestRunner_smp-tester         PASS      7.00 seconds
TestRunner_userchan-tester    PASS      5.36 seconds
TestRunner_6lowpan-tester     FAIL      6.83 seconds
IncrementalBuild              PASS      43.58 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[3/9] block: implement NVMEM provider
WARNING: please write a help paragraph that fully describes the config symbol with at least 4 lines
#194: FILE: block/Kconfig:212:
+config BLK_NVMEM
+	bool "Block device NVMEM provider"
+	depends on OF
+	depends on NVMEM
+	help
+	  Allow block devices (or partitions) to act as NVMEM providers,
+	  typically used with eMMC to store MAC addresses or Wi-Fi
+	  calibration data on embedded devices.
+

WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#216: 
new file mode 100644

total: 0 errors, 2 warnings, 183 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/patch/14544435.patch has style problems, please review.

NOTE: Ignored message types: UNKNOWN_COMMIT_ID

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: SubjectPrefix - FAIL
Desc: Check subject contains "Bluetooth" prefix
Output:
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 494, Passed: 489 (99.0%), Failed: 1, Not Run: 4

Failed Test Cases
Read Exp Feature - Success                           Failed       0.095 seconds
##############################
Test: TestRunner_mesh-tester - FAIL
Desc: Run mesh-tester with test-runner
Output:
Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0

Failed Test Cases
Mesh - Send cancel - 1                               Timed out    2.054 seconds
Mesh - Send cancel - 2                               Timed out    1.997 seconds
##############################
Test: TestRunner_6lowpan-tester - FAIL
Desc: Run 6lowpan-tester with test-runner
Output:
WARNING: possible circular locking dependency detected
7.0.0-rc7-g6ce4c12d8e26 #1 Not tainted
------------------------------------------------------
kworker/0:1/11 is trying to acquire lock:
ffff888002735940 ((wq_completion)hci0#2){+.+.}-{0:0}, at: touch_wq_lockdep_map+0x75/0x180

but task is already holding lock:
ffffffffb204faa0 (rtnl_mutex){+.+.}-{4:4}, at: lowpan_unregister_netdev+0xd/0x30

which lock already depends on the new lock.


the existing dependency chain (in reverse order) is:

-> #4 (rtnl_mutex){+.+.}-{4:4}:
       lock_acquire+0xf7/0x2c0
       __mutex_lock+0x16b/0x1fc0
       lowpan_register_netdev+0x11/0x30
       chan_ready_cb+0x836/0xd00
       l2cap_recv_frame+0x61bb/0x88e0
       l2cap_recv_acldata+0x790/0xdf0
       hci_rx_work+0x500/0xd00
       process_scheduled_works+0xba7/0x1a90
       worker_thread+0x514/0xbb0
       kthread+0x368/0x490
       ret_from_fork+0x498/0x7e0
       ret_from_fork_asm+0x19/0x30

-> #3 (&chan->lock#3/1){+.+.}-{4:4}:
       lock_acquire+0xf7/0x2c0
       __mutex_lock+0x16b/0x1fc0
       l2cap_chan_connect+0x74e/0x1980
       lowpan_control_write+0x523/0x660
       full_proxy_write+0x10b/0x190
       vfs_write+0x1c0/0xf60
       ksys_write+0xf1/0x1d0
       do_syscall_64+0xa0/0x570
       entry_SYSCALL_64_after_hwframe+0x74/0x7c

-> #2 (&conn->lock){+.+.}-{4:4}:
...
Total: 8, Passed: 8 (100.0%), Failed: 0, Not Run: 0


https://github.com/bluez/bluetooth-next/pull/128

---
Regards,
Linux Bluetooth


^ permalink raw reply

* RE: client: Add public broadcast advertising support
From: bluez.test.bot @ 2026-04-28 16:27 UTC (permalink / raw)
  To: linux-bluetooth, raghavendra.rao
In-Reply-To: <20260428144008.7487-2-raghavendra.rao@collabora.com>

[-- Attachment #1: Type: text/plain, Size: 1964 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=1086916

---Test result---

Test Summary:
CheckPatch                    FAIL      1.10 seconds
GitLint                       PASS      0.67 seconds
BuildEll                      PASS      20.51 seconds
BluezMake                     PASS      620.88 seconds
CheckSmatch                   PASS      338.52 seconds
bluezmakeextell               PASS      174.71 seconds
IncrementalBuild              PASS      622.72 seconds
ScanBuild                     PASS      975.91 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[BlueZ,v3,1/2] client: add public-broadcast advertising command
WARNING:LONG_LINE_STRING: line length of 84 exceeds 80 columns
#158: FILE: client/advertising.c:1066:
+		bt_shell_printf("Invalid argument: accepted values are sq or hq\n");

WARNING:STATIC_CONST_CHAR_ARRAY: static const char * array should probably be static const char * const
#197: FILE: client/main.c:2923:
+static const char *public_broadcast_arguments[] = {

/github/workspace/src/patch/14544460.patch total: 0 errors, 2 warnings, 136 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/patch/14544460.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.




https://github.com/bluez/bluez/pull/2081

---
Regards,
Linux Bluetooth


^ permalink raw reply

* [bluez/bluez]
From: BluezTestBot @ 2026-04-28 15:40 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1081265
  Home:   https://github.com/bluez/bluez

To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* [bluez/bluez]
From: BluezTestBot @ 2026-04-28 15:40 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1085227
  Home:   https://github.com/bluez/bluez

To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* [bluez/bluez] 505db9: client: add public-broadcast advertising command
From: raghava447 @ 2026-04-28 15:40 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1086916
  Home:   https://github.com/bluez/bluez
  Commit: 505db9ca976bd619e41a116129caeaf1ea35f4cd
      https://github.com/bluez/bluez/commit/505db9ca976bd619e41a116129caeaf1ea35f4cd
  Author: raghavendra <raghavendra.rao@collabora.com>
  Date:   2026-04-28 (Tue, 28 Apr 2026)

  Changed paths:
    M client/advertising.c
    M client/advertising.h
    M client/main.c

  Log Message:
  -----------
  client: add public-broadcast advertising command


  Commit: 1b866ee4f1ec0bd7dd0df2b4e39cca47bf1f80a5
      https://github.com/bluez/bluez/commit/1b866ee4f1ec0bd7dd0df2b4e39cca47bf1f80a5
  Author: raghavendra <raghavendra.rao@collabora.com>
  Date:   2026-04-28 (Tue, 28 Apr 2026)

  Changed paths:
    M client/advertising.c

  Log Message:
  -----------
  client: make advertise.name use public broadcast name


Compare: https://github.com/bluez/bluez/compare/505db9ca976b%5E...1b866ee4f1ec

To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* [bluez/bluez] b3729e: bass: Fix crashing on BT_BASS_MOD_SRC
From: Luiz Augusto von Dentz @ 2026-04-28 15:22 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/master
  Home:   https://github.com/bluez/bluez
  Commit: b3729e26cc42fb1477f325c994b2a51b34f929d7
      https://github.com/bluez/bluez/commit/b3729e26cc42fb1477f325c994b2a51b34f929d7
  Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
  Date:   2026-04-28 (Tue, 28 Apr 2026)

  Changed paths:
    M profiles/audio/bass.c

  Log Message:
  -----------
  bass: Fix crashing on BT_BASS_MOD_SRC

If assistant attempt o modify source the code would attempt to iterate
over all valid range of BIS indexes which may lead to the following
trace since the delegator maybe freed in the process:

 #0  queue_find (queue=<optimized out>, function=function@entry=0x58b8761109c0 <setup_match_bis>, match_data=match_data@entry=0x3) at src/shared/queue.c:230
 #1  0x000058b8761127fb in bass_update_bis_sync (bcast_src=<optimized out>, dg=<optimized out>) at profiles/audio/bass.c:1824
 #2  handle_mod_src_req (data=<optimized out>, params=<optimized out>, bcast_src=0x58b894661be0) at profiles/audio/bass.c:1862
 #3  cp_handler (bcast_src=0x58b894661be0, op=<optimized out>, params=<optimized out>, user_data=<optimized out>) at profiles/audio/bass.c:1910
 #4  0x000058b8761bc978 in bass_handle_mod_src_op (bass=<optimized out>, attrib=<optimized out>, opcode=<optimized out>, id=<optimized out>, iov=<optimized out>, att=<optimized out>)
    at src/shared/bass.c:1069

To fix the code will now just interate at existing setups checking if
they match the BIS index then adding/removing the stream so it is no
longer possible to free the delegator before all setups are processed.



To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* Re: [PATCH BlueZ v2] bass: Fix crashing on BT_BASS_MOD_SRC
From: patchwork-bot+bluetooth @ 2026-04-28 14:50 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
In-Reply-To: <20260414190459.161947-1-luiz.dentz@gmail.com>

Hello:

This patch was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Tue, 14 Apr 2026 15:04:59 -0400 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> 
> If assistant attempt o modify source the code would attempt to iterate
> over all valid range of BIS indexes which may lead to the following
> trace since the delegator maybe freed in the process:
> 
>  #0  queue_find (queue=<optimized out>, function=function@entry=0x58b8761109c0 <setup_match_bis>, match_data=match_data@entry=0x3) at src/shared/queue.c:230
>  #1  0x000058b8761127fb in bass_update_bis_sync (bcast_src=<optimized out>, dg=<optimized out>) at profiles/audio/bass.c:1824
>  #2  handle_mod_src_req (data=<optimized out>, params=<optimized out>, bcast_src=0x58b894661be0) at profiles/audio/bass.c:1862
>  #3  cp_handler (bcast_src=0x58b894661be0, op=<optimized out>, params=<optimized out>, user_data=<optimized out>) at profiles/audio/bass.c:1910
>  #4  0x000058b8761bc978 in bass_handle_mod_src_op (bass=<optimized out>, attrib=<optimized out>, opcode=<optimized out>, id=<optimized out>, iov=<optimized out>, att=<optimized out>)
>     at src/shared/bass.c:1069
> 
> [...]

Here is the summary with links:
  - [BlueZ,v2] bass: Fix crashing on BT_BASS_MOD_SRC
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=b3729e26cc42

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v2 1/2] client: add public-broadcast advertising command
From: Luiz Augusto von Dentz @ 2026-04-28 14:42 UTC (permalink / raw)
  To: Raghavendra Rao; +Cc: linux-bluetooth
In-Reply-To: <19dd4843cc9.16a62f4c253537.8494966934612526917@collabora.com>

Hi Raghavendra,

On Tue, Apr 28, 2026 at 10:35 AM Raghavendra Rao
<raghavendra.rao@collabora.com> wrote:
>
> Hello Luiz,
>
> I agree that adding a decoder for UUID 0x1856 would be useful.
> I would like to work on that and send it as a follow-up patch.

Too late, Ive already send a patch to address it:

https://patchwork.kernel.org/project/bluetooth/patch/20260427192223.2875358-1-luiz.dentz@gmail.com/

>
> Thanks & Regards,
> Raghavendra
>
>
>
> From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
> To: "raghu447"<raghavendra.rao@collabora.com>
> Cc: <linux-bluetooth@vger.kernel.org>
> Date: Fri, 24 Apr 2026 20:14:00 +0530
> Subject: Re: [PATCH v2 1/2] client: add public-broadcast advertising command
>
> Hi,
>
> On Fri, Apr 24, 2026 at 10:19 AM raghu447 <raghavendra.rao@collabora.com> wrote:
> >
> > From: raghavendra <raghavendra.rao@collabora.com>
> >
> > ---
> > client/advertising.c | 71 ++++++++++++++++++++++++++++++++++++++++++++
> > client/advertising.h | 2 ++
> > client/main.c | 19 ++++++++++++
> > 3 files changed, 92 insertions(+)
> >
> > diff --git a/client/advertising.c b/client/advertising.c
> > index f9df1b855..fc51f8193 100644
> > --- a/client/advertising.c
> > +++ b/client/advertising.c
> > @@ -20,6 +20,9 @@
> > #include <string.h>
> > #include <errno.h>
> >
> > +#include "bluetooth/bluetooth.h"
> > +#include "bluetooth/uuid.h"
> > +
> > #include "gdbus/gdbus.h"
> > #include "src/shared/util.h"
> > #include "src/shared/shell.h"
> > @@ -27,6 +30,7 @@
> >
> > #define AD_PATH "/org/bluez/advertising"
> > #define AD_IFACE "org.bluez.LEAdvertisement1"
> > +#define AD_PUBLIC_BROADCAST_UUID "0x1856"
>
> Forgot to mention, we don't seem to have a decoder for 0x1856, would
> you like to create one so we can actually see what is being programmed
> on btmon? That will probably require extending the
> monitor/packet.c:service_data_decoders
>
> > struct ad_data {
> > uint8_t data[245];
> > @@ -1005,6 +1009,73 @@ static void ad_clear_data(int type)
> > memset(&ad.data[type], 0, sizeof(ad.data[type]));
> > }
> >
> > +static bool ad_is_public_broadcast_uuid(const char *uuid)
> > +{
> > + return uuid && !bt_uuid_strcmp(uuid, AD_PUBLIC_BROADCAST_UUID);
> > +}
> > +
> > +static const char *ad_public_broadcast_state(void)
> > +{
> > + if (!ad_is_public_broadcast_uuid(ad.service[AD_TYPE_AD].uuid))
> > + return NULL;
> > +
> > + if (ad.service[AD_TYPE_AD].data.len != 2)
> > + return NULL;
> > +
> > + if (ad.service[AD_TYPE_AD].data.data[0] == 0x02 &&
> > + ad.service[AD_TYPE_AD].data.data[1] == 0x00)
> > + return "sq";
> > +
> > + if (ad.service[AD_TYPE_AD].data.data[0] == 0x04 &&
> > + ad.service[AD_TYPE_AD].data.data[1] == 0x00)
> > + return "hq";
> > +
> > + return NULL;
> > +}
> > +
> > +void ad_advertise_public_broadcast(DBusConnection *conn, int argc, char *argv[])
> > +{
> > + struct ad_data data = {
> > + .data = { 0x00, 0x00 },
> > + .len = 2,
> > + };
> > + const char *state;
> > +
> > + if (argc < 2) {
> > + state = ad_public_broadcast_state();
> > + if (state)
> > + bt_shell_printf("Public Broadcast: %s\n", state);
> > + else
> > + bt_shell_printf("Public Broadcast not set\n");
> > +
> > + return bt_shell_noninteractive_quit(EXIT_SUCCESS);
> > + }
> > +
> > + if (!strlen(argv[1])) {
> > + bt_shell_printf("Public broadcast value cannot be empty\n");
> > + return bt_shell_noninteractive_quit(EXIT_FAILURE);
> > + }
> > +
> > + if (!strcasecmp(argv[1], "sq"))
> > + data.data[0] = 0x02;
> > + else if (!strcasecmp(argv[1], "hq"))
> > + data.data[0] = 0x04;
> > + else {
> > + bt_shell_printf("Invalid argument: accepted values are sq or hq\n");
> > + return bt_shell_noninteractive_quit(EXIT_FAILURE);
> > + }
> > +
> > + ad_clear_service(AD_TYPE_AD);
> > +
> > + ad.service[AD_TYPE_AD].uuid = g_strdup(AD_PUBLIC_BROADCAST_UUID);
> > + ad.service[AD_TYPE_AD].data = data;
> > +
> > + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE,
> > + prop_names.service[AD_TYPE_AD]);
> > +
> > + return bt_shell_noninteractive_quit(EXIT_SUCCESS);
> > +}
> > +
> > void ad_advertise_data(DBusConnection *conn, int type, int argc, char *argv[])
> > {
> > char *endptr = NULL;
> > diff --git a/client/advertising.h b/client/advertising.h
> > index 9d124c7af..98f1bc524 100644
> > --- a/client/advertising.h
> > +++ b/client/advertising.h
> > @@ -34,6 +34,8 @@ void ad_advertise_local_appearance(DBusConnection *conn, long int *value);
> > void ad_advertise_duration(DBusConnection *conn, long int *value);
> > void ad_advertise_timeout(DBusConnection *conn, long int *value);
> > void ad_advertise_data(DBusConnection *conn, int type, int argc, char *argv[]);
> > +void ad_advertise_public_broadcast(DBusConnection *conn, int argc,
> > + char *argv[]);
> > void ad_disable_data(DBusConnection *conn, int type);
> > void ad_advertise_discoverable(DBusConnection *conn, dbus_bool_t *value);
> > void ad_advertise_discoverable_timeout(DBusConnection *conn, long int *value);
> > diff --git a/client/main.c b/client/main.c
> > index 57fa13888..5fd21c80d 100644
> > --- a/client/main.c
> > +++ b/client/main.c
> > @@ -2920,6 +2920,17 @@ static char *scan_generator(const char *text, int state)
> > return argument_generator(text, state, scan_arguments);
> > }
> >
> > +static const char *public_broadcast_arguments[] = {
> > + "sq",
> > + "hq",
> > + NULL
> > +};
> > +
> > +static char *public_broadcast_generator(const char *text, int state)
> > +{
> > + return argument_generator(text, state, public_broadcast_arguments);
> > +}
> > +
> > static void cmd_advertise(int argc, char *argv[])
> > {
> > dbus_bool_t enable;
> > @@ -2970,6 +2981,11 @@ static void cmd_advertise_data(int argc, char *argv[])
> > ad_advertise_data(dbus_conn, AD_TYPE_AD, argc, argv);
> > }
> >
> > +static void cmd_advertise_public_broadcast(int argc, char *argv[])
> > +{
> > + ad_advertise_public_broadcast(dbus_conn, argc, argv);
> > +}
> > +
> > static void cmd_advertise_sr_uuids(int argc, char *argv[])
> > {
> > ad_advertise_uuids(dbus_conn, AD_TYPE_SRD, argc, argv);
> > @@ -3653,6 +3669,9 @@ static const struct bt_shell_menu advertise_menu = {
> > "Set/Get advertise manufacturer data" },
> > { "data", "[type] [data=xx xx ...]", cmd_advertise_data,
> > "Set/Get advertise data" },
> > + { "public-broadcast", "[sq/hq]", cmd_advertise_public_broadcast,
> > + "Set/Get BLE Audio Public Broadcast Announcement",
> > + public_broadcast_generator },
> > { "sr-uuids", "[uuid1 uuid2 ...]", cmd_advertise_sr_uuids,
> > "Set/Get scan response uuids" },
> > { "sr-solicit", "[uuid1 uuid2 ...]", cmd_advertise_sr_solicit,
> > --
> > 2.53.0
> >
> >
>
>
> --
> Luiz Augusto von Dentz
>
>
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* [PATCH BlueZ v3 2/2] client: make advertise.name use public broadcast name
From: raghu447 @ 2026-04-28 14:40 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: raghavendra
In-Reply-To: <20260428144008.7487-1-raghavendra.rao@collabora.com>

From: raghavendra <raghavendra.rao@collabora.com>

---
 client/advertising.c | 74 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/client/advertising.c b/client/advertising.c
index 8ffbb085b..98e3895c3 100644
--- a/client/advertising.c
+++ b/client/advertising.c
@@ -31,6 +31,7 @@
 #define AD_PATH "/org/bluez/advertising"
 #define AD_IFACE "org.bluez.LEAdvertisement1"
 #define AD_PUBLIC_BROADCAST_UUID "0x1856"
+#define AD_TYPE_PUBLIC_BROADCAST_NAME 0x30
 #define AD_PUBLIC_BROADCAST_SQ BIT(1)
 #define AD_PUBLIC_BROADCAST_HQ BIT(2)
 
@@ -1016,6 +1017,62 @@ static bool ad_is_public_broadcast_uuid(const char *uuid)
 	return uuid && !bt_uuid_strcmp(uuid, AD_PUBLIC_BROADCAST_UUID);
 }
 
+static bool ad_broadcast_in_use(void)
+{
+	return ad_is_public_broadcast_uuid(ad.service[AD_TYPE_AD].uuid);
+}
+
+static bool ad_has_public_broadcast_name(void)
+{
+	return ad.data[AD_TYPE_AD].valid &&
+		ad.data[AD_TYPE_AD].type == AD_TYPE_PUBLIC_BROADCAST_NAME &&
+		ad.data[AD_TYPE_AD].data.len > 0;
+}
+
+static void ad_print_public_broadcast_name(void)
+{
+	if (!ad_has_public_broadcast_name()) {
+		bt_shell_printf("Public Broadcast Name not set\n");
+		return;
+	}
+
+	bt_shell_printf("Public Broadcast Name: %.*s\n",
+			(int) ad.data[AD_TYPE_AD].data.len,
+			(char *) ad.data[AD_TYPE_AD].data.data);
+}
+
+static bool ad_set_public_broadcast_name(DBusConnection *conn,
+						const char *name)
+{
+	struct ad_data data;
+	size_t len;
+
+	if (!name || !strlen(name)) {
+		bt_shell_printf("Public Broadcast Name cannot be empty\n");
+		return false;
+	}
+
+	len = strlen(name);
+	if (len > sizeof(data.data)) {
+		bt_shell_printf("Public Broadcast Name too long\n");
+		return false;
+	}
+
+	memset(&data, 0, sizeof(data));
+	memcpy(data.data, name, len);
+	data.len = len;
+
+	ad_clear_data(AD_TYPE_AD);
+	ad.data[AD_TYPE_AD].valid = true;
+	ad.data[AD_TYPE_AD].type = AD_TYPE_PUBLIC_BROADCAST_NAME;
+	ad.data[AD_TYPE_AD].data = data;
+
+	g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE,
+					prop_names.data[AD_TYPE_AD]);
+
+	return true;
+}
+
 static const char *ad_public_broadcast_state(void)
 {
 	if (!ad_is_public_broadcast_uuid(ad.service[AD_TYPE_AD].uuid))
@@ -1202,6 +1259,23 @@ void ad_advertise_name(DBusConnection *conn, bool value)
 
 void ad_advertise_local_name(DBusConnection *conn, const char *name)
 {
+	if (ad_broadcast_in_use()) {
+		if (!name) {
+			ad_print_public_broadcast_name();
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+		}
+
+		if (ad_has_public_broadcast_name() &&
+		    ad.data[AD_TYPE_AD].data.len == strlen(name) &&
+		    !memcmp(ad.data[AD_TYPE_AD].data.data, name, strlen(name)))
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+
+		if (!ad_set_public_broadcast_name(conn, name))
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+	}
+
 	if (!name) {
 		if (ad.local_name)
 			bt_shell_printf("LocalName: %s\n", ad.local_name);
-- 
2.43.0


^ permalink raw reply related

* [PATCH BlueZ v3 1/2] client: add public-broadcast advertising command
From: raghu447 @ 2026-04-28 14:40 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: raghavendra
In-Reply-To: <20260428144008.7487-1-raghavendra.rao@collabora.com>

From: raghavendra <raghavendra.rao@collabora.com>

---
 client/advertising.c | 73 ++++++++++++++++++++++++++++++++++++++++++++
 client/advertising.h |  2 ++
 client/main.c        | 19 ++++++++++++
 3 files changed, 94 insertions(+)

diff --git a/client/advertising.c b/client/advertising.c
index f9df1b855..8ffbb085b 100644
--- a/client/advertising.c
+++ b/client/advertising.c
@@ -20,6 +20,9 @@
 #include <string.h>
 #include <errno.h>
 
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/uuid.h"
+
 #include "gdbus/gdbus.h"
 #include "src/shared/util.h"
 #include "src/shared/shell.h"
@@ -27,6 +30,9 @@
 
 #define AD_PATH "/org/bluez/advertising"
 #define AD_IFACE "org.bluez.LEAdvertisement1"
+#define AD_PUBLIC_BROADCAST_UUID "0x1856"
+#define AD_PUBLIC_BROADCAST_SQ BIT(1)
+#define AD_PUBLIC_BROADCAST_HQ BIT(2)
 
 struct ad_data {
 	uint8_t data[245];
@@ -1005,6 +1011,73 @@ static void ad_clear_data(int type)
 	memset(&ad.data[type], 0, sizeof(ad.data[type]));
 }
 
+static bool ad_is_public_broadcast_uuid(const char *uuid)
+{
+	return uuid && !bt_uuid_strcmp(uuid, AD_PUBLIC_BROADCAST_UUID);
+}
+
+static const char *ad_public_broadcast_state(void)
+{
+	if (!ad_is_public_broadcast_uuid(ad.service[AD_TYPE_AD].uuid))
+		return NULL;
+
+	if (ad.service[AD_TYPE_AD].data.len != 2)
+		return NULL;
+
+	if (ad.service[AD_TYPE_AD].data.data[0] == AD_PUBLIC_BROADCAST_SQ &&
+			ad.service[AD_TYPE_AD].data.data[1] == 0x00)
+		return "sq";
+
+	if (ad.service[AD_TYPE_AD].data.data[0] == AD_PUBLIC_BROADCAST_HQ &&
+			ad.service[AD_TYPE_AD].data.data[1] == 0x00)
+		return "hq";
+
+	return NULL;
+}
+
+void ad_advertise_public_broadcast(DBusConnection *conn, int argc, char *argv[])
+{
+	struct ad_data data = {
+		.data = { 0x00, 0x00 },
+		.len = 2,
+	};
+	const char *state;
+
+	if (argc < 2) {
+		state = ad_public_broadcast_state();
+		if (state)
+			bt_shell_printf("Public Broadcast: %s\n", state);
+		else
+			bt_shell_printf("Public Broadcast not set\n");
+
+		return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+	}
+
+	if (!strlen(argv[1])) {
+		bt_shell_printf("Public broadcast value cannot be empty\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	if (!strcasecmp(argv[1], "sq"))
+		data.data[0] = AD_PUBLIC_BROADCAST_SQ;
+	else if (!strcasecmp(argv[1], "hq"))
+		data.data[0] = AD_PUBLIC_BROADCAST_HQ;
+	else {
+		bt_shell_printf("Invalid argument: accepted values are sq or hq\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	ad_clear_service(AD_TYPE_AD);
+
+	ad.service[AD_TYPE_AD].uuid = g_strdup(AD_PUBLIC_BROADCAST_UUID);
+	ad.service[AD_TYPE_AD].data = data;
+
+	g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE,
+					prop_names.service[AD_TYPE_AD]);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
 void ad_advertise_data(DBusConnection *conn, int type, int argc, char *argv[])
 {
 	char *endptr = NULL;
diff --git a/client/advertising.h b/client/advertising.h
index 9d124c7af..8c3fd041b 100644
--- a/client/advertising.h
+++ b/client/advertising.h
@@ -34,6 +34,8 @@ void ad_advertise_local_appearance(DBusConnection *conn, long int *value);
 void ad_advertise_duration(DBusConnection *conn, long int *value);
 void ad_advertise_timeout(DBusConnection *conn, long int *value);
 void ad_advertise_data(DBusConnection *conn, int type, int argc, char *argv[]);
+void ad_advertise_public_broadcast(DBusConnection *conn, int argc,
+							char *argv[]);
 void ad_disable_data(DBusConnection *conn, int type);
 void ad_advertise_discoverable(DBusConnection *conn, dbus_bool_t *value);
 void ad_advertise_discoverable_timeout(DBusConnection *conn, long int *value);
diff --git a/client/main.c b/client/main.c
index 57fa13888..6fb399277 100644
--- a/client/main.c
+++ b/client/main.c
@@ -2920,6 +2920,17 @@ static char *scan_generator(const char *text, int state)
 	return argument_generator(text, state, scan_arguments);
 }
 
+static const char *public_broadcast_arguments[] = {
+	"sq",
+	"hq",
+	NULL
+};
+
+static char *public_broadcast_generator(const char *text, int state)
+{
+	return argument_generator(text, state, public_broadcast_arguments);
+}
+
 static void cmd_advertise(int argc, char *argv[])
 {
 	dbus_bool_t enable;
@@ -2970,6 +2981,11 @@ static void cmd_advertise_data(int argc, char *argv[])
 	ad_advertise_data(dbus_conn, AD_TYPE_AD, argc, argv);
 }
 
+static void cmd_advertise_public_broadcast(int argc, char *argv[])
+{
+	ad_advertise_public_broadcast(dbus_conn, argc, argv);
+}
+
 static void cmd_advertise_sr_uuids(int argc, char *argv[])
 {
 	ad_advertise_uuids(dbus_conn, AD_TYPE_SRD, argc, argv);
@@ -3653,6 +3669,9 @@ static const struct bt_shell_menu advertise_menu = {
 			"Set/Get advertise manufacturer data" },
 	{ "data", "[type] [data=xx xx ...]", cmd_advertise_data,
 			"Set/Get advertise data" },
+	{ "public-broadcast", "[sq/hq]", cmd_advertise_public_broadcast,
+			"Set/Get BLE Audio Public Broadcast Announcement",
+			public_broadcast_generator },
 	{ "sr-uuids", "[uuid1 uuid2 ...]", cmd_advertise_sr_uuids,
 			"Set/Get scan response uuids" },
 	{ "sr-solicit", "[uuid1 uuid2 ...]", cmd_advertise_sr_solicit,
-- 
2.43.0


^ permalink raw reply related

* [PATCH BlueZ v3 0/2] client: Add public broadcast advertising support
From: raghu447 @ 2026-04-28 14:40 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: raghu447
In-Reply-To: <20260424141831.9172-3-raghavendra.rao@collabora.com>

This series adds bluetoothctl support for staging Public Broadcast
Announcement advertising data used during LE Audio public broadcast
qualification.

Patch 1 adds a public-broadcast command to the advertise submenu. The
command stages Public Broadcast Announcement ServiceData using UUID
0x1856 and supports SQ and HQ presets.

Patch 2 makes advertise.name broadcast-aware. When Public Broadcast
Announcement ServiceData is staged, advertise.name sets and gets the
Public Broadcast Name using AD type 0x30.

raghavendra (2):
  client: add public-broadcast advertising command
  client: make advertise.name use public broadcast name

 client/advertising.c | 147 +++++++++++++++++++++++++++++++++++++++++++
 client/advertising.h |   2 +
 client/main.c        |  19 ++++++
 3 files changed, 168 insertions(+)

-- 
2.43.0


^ permalink raw reply

* [PATCH 9/9] arm64: dts: qcom: arduino-imola: Get Bluetooth BD address from NVMEM
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

On Arduino Uno-Q, the Bluetooth Device address is stored in the eMMC
boot1 partition. Point to the appropriate NVMEM cell to retrieve it.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
index 35a30cd6f47d6d2e018f6841a05fe929fec15738..109fa76e05625461935e321e15dbfe6c7d452e78 100644
--- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
+++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
@@ -536,6 +536,9 @@ bluetooth {
 		vddch0-supply = <&pm4125_l22>;
 		enable-gpios = <&tlmm 87 GPIO_ACTIVE_HIGH>;
 		max-speed = <3000000>;
+
+		nvmem-cells = <&bd_addr>;
+		nvmem-cell-names = "local-bd-address";
 	};
 };
 

-- 
2.34.1


^ permalink raw reply related

* [PATCH 8/9] Bluetooth: qca: Set NVMEM BD address quirks when address is invalid
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

When the controller BD address is invalid (zero or default),
set the NVMEM quirks to allow retrieving the address from a
'local-bd-address' NVMEM cell. The BD address is often stored
alongside the WiFi MAC address in big-endian format, so also
set the big-endian quirk.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 drivers/bluetooth/btqca.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c
index dda76365726f0bfe0e80e05fe04859fa4f0592e1..df33eacfd29fa680f393f90215150743e6001d5b 100644
--- a/drivers/bluetooth/btqca.c
+++ b/drivers/bluetooth/btqca.c
@@ -721,8 +721,11 @@ static int qca_check_bdaddr(struct hci_dev *hdev, const struct qca_fw_config *co
 	}
 
 	bda = (struct hci_rp_read_bd_addr *)skb->data;
-	if (!bacmp(&bda->bdaddr, &config->bdaddr))
+	if (!bacmp(&bda->bdaddr, &config->bdaddr)) {
 		hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY);
+		hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM);
+		hci_set_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE);
+	}
 
 	kfree_skb(skb);
 

-- 
2.34.1


^ permalink raw reply related

* [PATCH 7/9] Bluetooth: hci_sync: Add NVMEM-backed BD address retrieval
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

Some devices store the Bluetooth BD address in non-volatile
memory, which can be accessed through the NVMEM framework.
Similar to Ethernet or WiFi MAC addresses, add support for
reading the BD address from a 'local-bd-address' NVMEM cell.

As with the device-tree provided BD address, add a quirk to
indicate whether a device or platform should attempt to read
the address from NVMEM when no valid in-chip address is present.
Also add a quirk to indicate if the address is stored in
big-endian byte order.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 include/net/bluetooth/hci.h | 18 +++++++++++++++
 net/bluetooth/hci_sync.c    | 56 ++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 572b1c620c5d653a1fe10b26c1b0ba33e8f4968f..7686466d1109253b0d75edeb5f6a99fb98ce4cc6 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -164,6 +164,24 @@ enum {
 	 */
 	HCI_QUIRK_BDADDR_PROPERTY_BROKEN,
 
+	/* When this quirk is set, the public Bluetooth address
+	 * initially reported by HCI Read BD Address command
+	 * is considered invalid. The public BD Address can be
+	 * retrieved via a 'local-bd-address' NVMEM cell.
+	 *
+	 * This quirk can be set before hci_register_dev is called or
+	 * during the hdev->setup vendor callback.
+	 */
+	HCI_QUIRK_USE_BDADDR_NVMEM,
+
+	/* When this quirk is set, the Bluetooth Device Address provided by
+	 * the 'local-bd-address' NVMEM is stored in big-endian order.
+	 *
+	 * This quirk can be set before hci_register_dev is called or
+	 * during the hdev->setup vendor callback.
+	 */
+	HCI_QUIRK_BDADDR_NVMEM_BE,
+
 	/* When this quirk is set, the duplicate filtering during
 	 * scanning is based on Bluetooth devices addresses. To allow
 	 * RSSI based updates, restart scanning if needed.
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index fd3aacdea512a37c22b9a2be90c89ddca4b4d99f..f87cb6ae85c3a5754fe79f415ba05dd177f75fad 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -6,6 +6,7 @@
  * Copyright 2023 NXP
  */
 
+#include <linux/nvmem-consumer.h>
 #include <linux/property.h>
 
 #include <net/bluetooth/bluetooth.h>
@@ -3588,6 +3589,54 @@ int hci_powered_update_sync(struct hci_dev *hdev)
 	return 0;
 }
 
+/**
+ * hci_dev_get_bd_addr_from_nvmem - Get the Bluetooth Device Address
+ *				    (BD_ADDR) for a HCI device from
+ *				    an NVMEM cell.
+ * @hdev:	The HCI device
+ *
+ * Search for 'local-bd-address' NVMEM cell.
+ *
+ * All-zero BD addresses are rejected (unprovisioned).
+ */
+static int hci_dev_get_bd_addr_from_nvmem(struct hci_dev *hdev)
+{
+	struct device *dev = hdev->dev.parent;
+	struct nvmem_cell *cell;
+	const void *ba;
+	int err = 0;
+	size_t len;
+
+	cell = nvmem_cell_get(dev, "local-bd-address");
+	if (IS_ERR(cell))
+		return PTR_ERR(cell);
+
+	ba = nvmem_cell_read(cell, &len);
+	nvmem_cell_put(cell);
+
+	if (IS_ERR(ba)) {
+		bt_dev_warn(hdev, "Error reading BD address from NVMEM (%ld)\n",
+			    PTR_ERR(ba));
+		err = PTR_ERR(ba);
+		goto done;
+	}
+
+	if (len != sizeof(bdaddr_t) || !bacmp(ba, BDADDR_ANY)) {
+		bt_dev_warn(hdev, "NVMEM BD address has incorrect format\n");
+		err = -EINVAL;
+		goto done;
+	}
+
+	if (hci_test_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE))
+		baswap(&hdev->public_addr, (bdaddr_t *)ba);
+	else
+		bacpy(&hdev->public_addr, (bdaddr_t *)ba);
+
+done:
+	kfree(ba);
+	return err;
+}
+
 /**
  * hci_dev_get_bd_addr_from_property - Get the Bluetooth Device Address
  *				       (BD_ADDR) for a HCI device from
@@ -5042,12 +5091,17 @@ static int hci_dev_setup_sync(struct hci_dev *hdev)
 	 * its setup callback.
 	 */
 	invalid_bdaddr = hci_test_quirk(hdev, HCI_QUIRK_INVALID_BDADDR) ||
-			 hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY);
+			 hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY) ||
+			 hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM);
 	if (!ret) {
 		if (hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY) &&
 		    !bacmp(&hdev->public_addr, BDADDR_ANY))
 			hci_dev_get_bd_addr_from_property(hdev);
 
+		if (hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM) &&
+		    !bacmp(&hdev->public_addr, BDADDR_ANY))
+			hci_dev_get_bd_addr_from_nvmem(hdev);
+
 		if (invalid_bdaddr && bacmp(&hdev->public_addr, BDADDR_ANY) &&
 		    hdev->set_bdaddr) {
 			ret = hdev->set_bdaddr(hdev, &hdev->public_addr);

-- 
2.34.1


^ permalink raw reply related

* [PATCH 6/9] dt-bindings: bluetooth: qcom: Add NVMEM BD address cell
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

Add support for an NVMEM cell provider for "local-bd-address",
allowing the Bluetooth stack to retrieve controller's BD address
from non-volatile storage such as an EEPROM or an eMMC partition.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 .../bindings/net/bluetooth/qcom,bluetooth-common.yaml          | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml
index c8e9c55c1afb4c8e05ba2dae41ce2db4194b4a0f..ecb3de65506f7f0f1fc1d0b9bbd316163b7c26e8 100644
--- a/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml
+++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml
@@ -22,4 +22,14 @@ properties:
     description:
       boot firmware is incorrectly passing the address in big-endian order
 
+  nvmem-cells:
+    maxItems: 1
+    description:
+      Nvmem data cell that contains a 6 byte BD address with the most
+      significant byte first (big-endian).
+
+  nvmem-cell-names:
+    items:
+      - const: local-bd-address
+
 additionalProperties: true

-- 
2.34.1


^ permalink raw reply related

* [PATCH 5/9] arm64: dts: qcom: arduino-imola: Get WiFi MAC from NVMEM
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

On Arduino Uno-Q, the WiFi MAC address is stored in the eMMC
boot1 partition. Point to the appropriate NVMEM cell to
retrieve it.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
index dc85cf94f71cac8666cab30ccf37cc2d2f8fd941..35a30cd6f47d6d2e018f6841a05fe929fec15738 100644
--- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
+++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
@@ -581,6 +581,9 @@ &wifi {
 	qcom,ath10k-calibration-variant = "ArduinoImola";
 	firmware-name = "qcm2290";
 
+	nvmem-cells = <&wifi_mac_addr>;
+	nvmem-cell-names = "mac-address";
+
 	status = "okay";
 };
 

-- 
2.34.1


^ permalink raw reply related

* [PATCH 4/9] dt-bindings: net: wireless: qcom,ath10k: Add NVMEM MAC address cell
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

Add support for an NVMEM cell provider with the standard "mac-address"
cell name. This allows the ath10k device to retrieve its MAC address
from non-volatile storage such as an EEPROM or an eMMC partition.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 .../devicetree/bindings/net/wireless/qcom,ath10k.yaml          | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
index c21d66c7cd558ab792524be9afec8b79272d1c87..7155d8b15cc145c3a7d703db0c9c3e056a54c07e 100644
--- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
+++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
@@ -92,6 +92,16 @@ properties:
 
   ieee80211-freq-limit: true
 
+  nvmem-cells:
+    maxItems: 1
+    description:
+      Nvmem data cell that contains a 6 byte MAC address with the most
+      significant byte first (big-endian).
+
+  nvmem-cell-names:
+    items:
+      - const: mac-address
+
   qcom,calibration-data:
     $ref: /schemas/types.yaml#/definitions/uint8-array
     description:

-- 
2.34.1


^ permalink raw reply related

* [PATCH 3/9] block: implement NVMEM provider
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

From: Daniel Golle <daniel@makrotopia.org>

On embedded devices using an eMMC it is common that one or more partitions
on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM
data. Allow referencing the partition in device tree for the kernel and
Wi-Fi drivers accessing it via the NVMEM layer.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 block/Kconfig     |   9 +++
 block/Makefile    |   1 +
 block/blk-nvmem.c | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 174 insertions(+)

diff --git a/block/Kconfig b/block/Kconfig
index 15027963472d7b40e27b9097a5993c457b5b3054..0b33747e16dc33473683706f75c92bdf8b648f7c 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -209,6 +209,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
 	  by falling back to the kernel crypto API when inline
 	  encryption hardware is not present.
 
+config BLK_NVMEM
+	bool "Block device NVMEM provider"
+	depends on OF
+	depends on NVMEM
+	help
+	  Allow block devices (or partitions) to act as NVMEM providers,
+	  typically used with eMMC to store MAC addresses or Wi-Fi
+	  calibration data on embedded devices.
+
 source "block/partitions/Kconfig"
 
 config BLK_PM
diff --git a/block/Makefile b/block/Makefile
index 7dce2e44276c4274c11a0a61121c83d9c43d6e0c..d7ac389e71902bc091a8800ea266190a43b3e63d 100644
--- a/block/Makefile
+++ b/block/Makefile
@@ -36,3 +36,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION)	+= blk-crypto.o blk-crypto-profile.o \
 					   blk-crypto-sysfs.o
 obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK)	+= blk-crypto-fallback.o
 obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED)	+= holder.o
+obj-$(CONFIG_BLK_NVMEM)                += blk-nvmem.o
diff --git a/block/blk-nvmem.c b/block/blk-nvmem.c
new file mode 100644
index 0000000000000000000000000000000000000000..01b67c638a6dfd1393043024b6a7f3ebb947a57c
--- /dev/null
+++ b/block/blk-nvmem.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * block device NVMEM provider
+ *
+ * Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
+ *
+ * Useful on devices using a partition on an eMMC for MAC addresses or
+ * Wi-Fi calibration EEPROM data.
+ */
+
+#include "blk.h"
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/pagemap.h>
+#include <linux/property.h>
+
+/* List of all NVMEM devices */
+static LIST_HEAD(nvmem_devices);
+static DEFINE_MUTEX(devices_mutex);
+
+struct blk_nvmem {
+	struct nvmem_device	*nvmem;
+	struct device		*dev;
+	struct list_head	list;
+};
+
+static int blk_nvmem_reg_read(void *priv, unsigned int from,
+			      void *val, size_t bytes)
+{
+	blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES;
+	unsigned long offs = from & ~PAGE_MASK, to_read;
+	pgoff_t f_index = from >> PAGE_SHIFT;
+	struct blk_nvmem *bnv = priv;
+	size_t bytes_left = bytes;
+	struct file *bdev_file;
+	struct folio *folio;
+	void *p;
+	int ret = 0;
+
+	bdev_file = bdev_file_open_by_dev(bnv->dev->devt, mode, priv, NULL);
+	if (!bdev_file)
+		return -ENODEV;
+
+	if (IS_ERR(bdev_file))
+		return PTR_ERR(bdev_file);
+
+	while (bytes_left) {
+		folio = read_mapping_folio(bdev_file->f_mapping, f_index++, NULL);
+		if (IS_ERR(folio)) {
+			ret = PTR_ERR(folio);
+			goto err_release_bdev;
+		}
+		to_read = min_t(unsigned long, bytes_left, PAGE_SIZE - offs);
+		p = folio_address(folio) + offset_in_folio(folio, offs);
+		memcpy(val, p, to_read);
+		offs = 0;
+		bytes_left -= to_read;
+		val += to_read;
+		folio_put(folio);
+	}
+
+err_release_bdev:
+	fput(bdev_file);
+
+	return ret;
+}
+
+static int blk_nvmem_register(struct device *dev)
+{
+	struct device_node *np = dev_of_node(dev);
+	struct block_device *bdev = dev_to_bdev(dev);
+	struct nvmem_config config = {};
+	struct blk_nvmem *bnv;
+
+	/* skip devices which do not have a device tree node */
+	if (!np)
+		return 0;
+
+	/* skip devices without an nvmem layout defined */
+	if (!of_get_child_by_name(np, "nvmem-layout"))
+		return 0;
+
+	/*
+	 * skip block device too large to be represented as NVMEM devices
+	 * which are using an 'int' as address
+	 */
+	if (bdev_nr_bytes(bdev) > INT_MAX)
+		return -EFBIG;
+
+	bnv = kzalloc_obj(*bnv);
+	if (!bnv)
+		return -ENOMEM;
+
+	config.id = NVMEM_DEVID_NONE;
+	config.dev = &bdev->bd_device;
+	config.name = dev_name(&bdev->bd_device);
+	config.owner = THIS_MODULE;
+	config.priv = bnv;
+	config.reg_read = blk_nvmem_reg_read;
+	config.size = bdev_nr_bytes(bdev);
+	config.word_size = 1;
+	config.stride = 1;
+	config.read_only = true;
+	config.root_only = true;
+	config.ignore_wp = true;
+	config.of_node = to_of_node(dev->fwnode);
+
+	bnv->dev = &bdev->bd_device;
+	bnv->nvmem = nvmem_register(&config);
+	if (IS_ERR(bnv->nvmem)) {
+		dev_err_probe(&bdev->bd_device, PTR_ERR(bnv->nvmem),
+			      "Failed to register NVMEM device\n");
+
+		kfree(bnv);
+		return PTR_ERR(bnv->nvmem);
+	}
+
+	mutex_lock(&devices_mutex);
+	list_add_tail(&bnv->list, &nvmem_devices);
+	mutex_unlock(&devices_mutex);
+
+	return 0;
+}
+
+static void blk_nvmem_unregister(struct device *dev)
+{
+	struct blk_nvmem *bnv_c, *bnv = NULL;
+
+	mutex_lock(&devices_mutex);
+	list_for_each_entry(bnv_c, &nvmem_devices, list) {
+		if (bnv_c->dev == dev) {
+			bnv = bnv_c;
+			break;
+		}
+	}
+
+	if (!bnv) {
+		mutex_unlock(&devices_mutex);
+		return;
+	}
+
+	list_del(&bnv->list);
+	mutex_unlock(&devices_mutex);
+	nvmem_unregister(bnv->nvmem);
+	kfree(bnv);
+}
+
+static struct class_interface blk_nvmem_bus_interface __refdata = {
+	.class = &block_class,
+	.add_dev = &blk_nvmem_register,
+	.remove_dev = &blk_nvmem_unregister,
+};
+
+static int __init blk_nvmem_init(void)
+{
+	int ret;
+
+	ret = class_interface_register(&blk_nvmem_bus_interface);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+device_initcall(blk_nvmem_init);

-- 
2.34.1


^ permalink raw reply related

* [PATCH 2/9] arm64: dts: qcom: arduino-imola: Describe boot1 NVMEM layout
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

On Arduino Uno-Q, the eMMC boot1 partition is factory provisioned
with device-specific information such as the WiFi MAC address
and the Bluetooth BD address. This partition can serve as an
alternative to additional non-volatile memory, such as a
dedicated EEPROM.

The eMMC boot partitions are typically good candidates, as they
are realively small, read-only by default (and can be enforced
as hardware read-only), and are not affected by board reflashing
procedures, which generally target the eMMC user or GP partitions.

Describe the corresponding nvmem-layout for the WiFi and
Bluetooth addresses.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
index bf088fa9807f040f0c8f405f9111b01790b09377..dc85cf94f71cac8666cab30ccf37cc2d2f8fd941 100644
--- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
+++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts
@@ -409,7 +409,31 @@ &sdhc_1 {
 	no-sdio;
 	no-sd;
 
+	#address-cells = <1>;
+	#size-cells = <0>;
+
 	status = "okay";
+
+	card@0 {
+		compatible = "mmc-card";
+		reg = <0>;
+
+		partitions-boot1 {
+			nvmem-layout {
+				compatible = "fixed-layout";
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				wifi_mac_addr: mac-addr@4400 {
+					reg = <0x4400 0x6>;
+				};
+
+				bd_addr: bd-addr@5400 {
+					reg = <0x5400 0x6>;
+				};
+			};
+		};
+	};
 };
 
 &spi5 {

-- 
2.34.1


^ permalink raw reply related

* [PATCH 1/9] dt-bindings: mmc: Document support for nvmem-layout
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain
In-Reply-To: <20260428-block-as-nvmem-v1-0-6ad23e75190a@oss.qualcomm.com>

Add support for an nvmem-layout subnode under an eMMC hardware
partition. This allows the partition to be exposed as an NVMEM
provider and its internal layout to be described. For example,
an eMMC boot partition can be used to store device-specific
information such as a WiFi MAC address.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
 Documentation/devicetree/bindings/mmc/mmc-card.yaml | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/Documentation/devicetree/bindings/mmc/mmc-card.yaml b/Documentation/devicetree/bindings/mmc/mmc-card.yaml
index a61d6c96df759102f9c1fbfd548b026a77921cae..e01fc82ab8520a31196475b18acb5e839e1bf71f 100644
--- a/Documentation/devicetree/bindings/mmc/mmc-card.yaml
+++ b/Documentation/devicetree/bindings/mmc/mmc-card.yaml
@@ -40,6 +40,9 @@ patternProperties:
         contains:
           const: fixed-partitions
 
+      nvmem-layout:
+        $ref: /schemas/nvmem/layouts/nvmem-layout.yaml
+
 required:
   - compatible
   - reg
@@ -86,6 +89,23 @@ examples:
                     read-only;
                 };
             };
+
+            partitions-boot2 {
+                nvmem-layout {
+                    compatible = "fixed-layout";
+
+                    #address-cells = <1>;
+                    #size-cells = <1>;
+
+                    mac-addr@4400 {
+                        reg = <0x4400 0x6>;
+                    };
+
+                    bd-addr@5400 {
+                        reg = <0x5400 0x6>;
+                    };
+                };
+            };
         };
     };
 

-- 
2.34.1


^ permalink raw reply related

* [PATCH 0/9] Support for block device NVMEM providers
From: Loic Poulain @ 2026-04-28 14:23 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Loic Poulain

On embedded devices, it is common for factory provisioning to store
device-specific information, such as Ethernet or WiFi MAC addresses,
in a dedicated area of an eMMC partition. This avoids the need for
and additional EEPROM/OTP and leverages the persistence of eMMC.

One example is the Arduino UNO-Q, where the WiFi MAC address and the
Bluetooth Device address are stored in the eMMC Boot1 partition.

Until now, accessing this information required a custom bootloader
to read the data and inject it into the Device Tree before handing
control over to the kernel. This approach is fragile and leads to
device-specific workarounds.

Rather than adding a new NVMEM provider specifically to the eMMC
subsystem, the new support operates at the block layer, allowing any
block device to behave like other non-volatile memories such as EEPROM
or OTP.

This series builds on earlier work by Daniel Golle that enables block
devices to act as NVMEM providers:
https://lore.kernel.org/all/6061aa4201030b9bb2f8d03ef32a564fdb786ed1.1709667858.git.daniel@makrotopia.org/

It also introduces an NVMEM layout description for the Arduino UNO-Q,
allowing device-specific data stored in the eMMC Boot1 partition to
be accessed in a standard way.

WiFi and Ethernet already support retrieving MAC addresses from NVMEM.
Bluetooth requires similar support, which is also addressed.

Note that this is currently limited to eMMC-backed block devices, as
only the eMMC core associates a firmware node with the block device
(add_disk_fwnode). This can be easily extended in the future to
support additional block drivers.

Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
---
Daniel Golle (1):
      block: implement NVMEM provider

Loic Poulain (8):
      dt-bindings: mmc: Document support for nvmem-layout
      arm64: dts: qcom: arduino-imola: Describe boot1 NVMEM layout
      dt-bindings: net: wireless: qcom,ath10k: Add NVMEM MAC address cell
      arm64: dts: qcom: arduino-imola: Get WiFi MAC from NVMEM
      dt-bindings: bluetooth: qcom: Add NVMEM BD address cell
      Bluetooth: hci_sync: Add NVMEM-backed BD address retrieval
      Bluetooth: qca: Set NVMEM BD address quirks when address is invalid
      arm64: dts: qcom: arduino-imola: Get Bluetooth BD address from NVMEM

 .../devicetree/bindings/mmc/mmc-card.yaml          |  20 +++
 .../net/bluetooth/qcom,bluetooth-common.yaml       |  10 ++
 .../bindings/net/wireless/qcom,ath10k.yaml         |  10 ++
 arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts |  30 ++++
 block/Kconfig                                      |   9 ++
 block/Makefile                                     |   1 +
 block/blk-nvmem.c                                  | 164 +++++++++++++++++++++
 drivers/bluetooth/btqca.c                          |   5 +-
 include/net/bluetooth/hci.h                        |  18 +++
 net/bluetooth/hci_sync.c                           |  56 ++++++-
 10 files changed, 321 insertions(+), 2 deletions(-)
---
base-commit: 47c4835fc0fed583d01d90387b67633950eba2b2
change-id: 20260428-block-as-nvmem-4b308e8bda9a

Best regards,
-- 
Loic Poulain <loic.poulain@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH v1] arm64: dts: monac-arduino-monza: Add Bluetooth UART node
From: Dmitry Baryshkov @ 2026-04-28 11:26 UTC (permalink / raw)
  To: Shuai Zhang
  Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-arm-msm, devicetree, linux-kernel,
	linux-bluetooth, cheng.jiang, quic_chezhou, wei.deng, jinwang.li,
	mengshi.wu, Loic Poulain
In-Reply-To: <20260428025652.662502-1-shuai.zhang@oss.qualcomm.com>

On Tue, Apr 28, 2026 at 10:56:52AM +0800, Shuai Zhang wrote:
> enable bt on monac-arduino-monza
> 
> Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> Signed-off-by: Shuai Zhang <shuai.zhang@oss.qualcomm.com>
> ---
>  arch/arm64/boot/dts/qcom/monaco-arduino-monza.dts | 11 +++++++++++
>  1 file changed, 11 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/qcom/monaco-arduino-monza.dts b/arch/arm64/boot/dts/qcom/monaco-arduino-monza.dts
> index ca14f0ea4..092ca0b59 100644
> --- a/arch/arm64/boot/dts/qcom/monaco-arduino-monza.dts
> +++ b/arch/arm64/boot/dts/qcom/monaco-arduino-monza.dts
> @@ -21,6 +21,7 @@ aliases {
>  		ethernet0 = &ethernet0;
>  		i2c1 = &i2c1;
>  		serial0 = &uart7;
> +		serial1 = &uart10;
>  	};
>  
>  	chosen {
> @@ -454,6 +455,16 @@ &uart7 {
>  	status = "okay";
>  };
>  
> +&uart10 {
> +	status = "okay";
> +	bluetooth: bluetooth {
> +		compatible = "qcom,qca2066-bt";

What powers on this BT device?

> +		max-speed = <3200000>;
> +		enable-gpios = <&tlmm 55 GPIO_ACTIVE_HIGH>;
> +		status = "okay";
> +	};
> +};
> +
>  &usb_1 {
>  	status = "okay";
>  };
> -- 
> 2.34.1
> 

-- 
With best wishes
Dmitry

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox