Skip to content

Conversation

@quicjathot
Copy link

Introduce a new recipe bt-qca-set-bdaddr.bb that installs a systemd service and script to configure the Bluetooth Device Address (BDADDR) for uninitialized Qualcomm QCA Bluetooth SoCs at boot time.

Ensures proper BDADDR configuration for Qualcomm Bluetooth chipsets that remain unconfigured when they do not have an OTP (One-Time Programmable) Bluetooth address programmed, preventing Bluetooth initialization failure.

Introduce a new recipe `bt-qca-set-bdaddr.bb` that installs a systemd
service and script to configure the Bluetooth Device Address (BDADDR)
for uninitialized Qualcomm QCA Bluetooth SoCs at boot time.

Ensures proper BDADDR configuration for Qualcomm Bluetooth chipsets that
remain unconfigured when they do not have an OTP (One-Time Programmable)
Bluetooth address programmed, preventing Bluetooth initialization failure.

Signed-off-by: Janaki Ramaiah Thota <janaki.thota@oss.qualcomm.com>
CORE_IMAGE_BASE_INSTALL += " \
kernel-modules \
packagegroup-qcom-utilities-filesystem-utils \
bt-qca-set-bdaddr \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort in alphabetical order.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will follow order and rise as separate commit.

@@ -0,0 +1,172 @@
#!/bin/sh
# Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to use QTI copyright here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, use:

Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.

S = "${UNPACKDIR}"

SYSTEMD_SERVICE:${PN} = "qca_set_bdaddr.service"
SYSTEMD_AUTO_ENABLE:${PN} = "enable"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a default

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure , will remove line SYSTEMD_AUTO_ENABLE:${PN} = "enable"

SYSTEMD_AUTO_ENABLE:${PN} = "enable"

FILES:${PN} += " \
${systemd_unitdir}/system/qca_set_bdaddr.service \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also should be handled by systemd.bbclass

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes will remove this as well, thanks.

${systemd_unitdir}/system/qca_set_bdaddr.service \
${bindir}/qca_set_bdaddr.sh \
"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do_configure[noexec] = "1"
do_compile[noexec] = "1"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will inform bitbake to skip do_configure and do_compile

{
echo "public-addr $BDA"
sleep 1
} | btmgmt >"$TMP_LOG" 2>&1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... You've ignored the previous review. Don't do that (and don't do this too).

btmgmt --index $index  public-addr $BDA

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, we have not ignored the previous comment, we tried as you suggested previously it is not working during every boot time, with current approach it is working as expected every time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly doesn't work? How?
If something, please reply to the comments instead of letting others have an assumption that they were missed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback.

Root Cause:
When running btmgmt public-addr $BDA from service, strace shows:
epoll_ctl(3, EPOLL_CTL_ADD, 0, {events=0, data=0xaaaadcec4560}) = -1 EPERM (Operation not permitted)
This happens because epoll trying to add the stdin (fd 0:/dev/null/) of the current process to epoll to get the notification events but  /dev/null does not support epoll (see epoll_ctl man page). epoll requires file descriptors that implement a .poll handler (e.g., sockets, pipes, tty). /dev/null is "always ready" and lacks this mechanism.

Verification:
When run script manually, stdin is /dev/pts/0 (a tty), which supports poll (tty_io.c),so it is working as expected

Fix:
To fix this, we changed stdin to tty which is epoll-able by adding below in systemd service unit file

StandardInput=tty
StandardOuput=journal
StandardError=journal

This ensures btmgmt has a valid stdin for epoll and works reliably across boots.

so now it is working without any pipe(|) as below
btmgmt public-addr $BDA

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please try the first patch from bluez/bluez#1751 ? If it works for you, please submit it to OE-Core.

CORE_IMAGE_BASE_INSTALL += " \
kernel-modules \
packagegroup-qcom-utilities-filesystem-utils \
bt-qca-set-bdaddr \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate commit

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

debug_log "Target BDA: $BDA"

# Validate BD_ADDR format (must be 6 octets)
if ! echo "$BDA" | grep -Eq '^([0-9A-F]{2}:){5}[0-9A-F]{2}$'; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've spent significant efforts generating the address. Can it now be not properly formatted?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is properly formatted, it is just fail safe check, if you want remove ? we can remove.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to understand, why did you put it here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This final check ensures we only pass an exactly formatted 6‑octet, colon‑delimited address to btmgmt, avoiding failures.
Example:
Properly formatted:
A0:BD:71:7C:AA:E8

Invalid formats blocked by check:
A0:BD:71:30:7C:AA:E8 <-- 7 octets (too many)
A0:BD:71:7C:AA:E8: <-- trailing colon
A0:BD:71:7C:A:E8 <-- one byte has only 1 hex digit
a0:bd:71:7c:aa:e8 <-- lowercase unless you allow [0-9A-Fa-f]
A0-BD-71-7C-AA-E8 <-- hyphens, not colons
A0:BD:71:7C:AA:E8\n <-- extra whitespace/characters

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to check the string you have just generated??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the next patch as suggested hashing serial number, will properly generate string and will remove this check


# Extract exactly 6 hex characters (3 bytes) from the serial number.
# If fewer than 6 digits, pad with leading zero
dev_suffix_hex=$(printf "%06X" "$serial_number" | awk '{print substr($0, length($0)-5)}')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... You are using SoC serial number and then broadcasting it over the air.
The practice which was frowned upon some time ago for the privacy reasons.
I'd suggest at least hashing it.

Also, as you are not using an allocate BT address, it is called 'Random Static Address'. You must set two MSB bits.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the valuable input, will include this in next patch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the inputs. We have now implemented hashing of the SoC serial number for BDA formation. Since the generated BDA remains the same across boots and is fixed for that device, can we consider this as a public address?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between public and random static addresses? Could you please check the docs?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bluetooth Low Energy (BLE) introduced random addresses to enhance privacy by allowing a device's address to change periodically, making long-term tracking more difficult. The significance of the bytes in random addresses depends on their specific type, indicated by the two most significant bits (MSBs) of the address:
Static Random Addresses: These addresses are randomly generated and usually remain the same until the device is powered off and on again. Their two MSBs are set to 0b11 (first hex digit is C, D, E, or F). The remaining 46 bits are a random value.

Setting a static address on the device does not transition it out of the un-configured state, which can impact BLE functionality even after the device has been configured

we can see below print setting public address only brings out the device from un-configured state

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main difference is that the Public address is allocated by the vendor, it should have corresponding OUI prefix and is guaranteed to be unique. As the address you are setting is generated rather than assigned, it can't be called 'Public'.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the inputs. This implementation is specifically for development boards that do not have OTP burnt for BD-Address. Last year, Johan Hovald changed the code for QCOM SoCs without unique BDAs to mark them as unconfigured. This makes devices unusable until a unique BDA is set, which is only possible by opening the mgmt socket and setting the BDA.
Note: This behavior affects only QCOM SoCs.
Bluetooth: qca: generalise device address check The default device address apparently comes from the NVM configuration file and can differ quite a bit between controllers. Store the default address when parsing the configuration file and use it to determine whether the controller has been provisioned with an address. This makes sure that devices without a unique address start as unconfigured unless a valid address has been provided in the devicetree.
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/drivers/bluetooth/btqca.c?id=dd336649ba89789c845618dcbc09867010aec673

We also checked other development boards like Raspberry Pi, which form the BDADDR from the serial number (3 bytes) combined with an OUI (3 bytes) and set the BDA on every boot using a Vendor Specific Command (VSC: write_bda). In their case, the device does not go into the unconfigured state, so they can send VSC successfully.

Reference: <https://github.com/RPi-Distro/pi-bluetooth/blob/master/usr/bin/bthelper>

Key snippet:
sh SERIAL=cat /proc/device-tree/serial-number | cut -c9-
B1=echo $SERIAL | cut -c3-4
B2=echo $SERIAL | cut -c5-6
B3=echo $SERIAL | cut -c7-8
BDADDR=printf '0x%02x 0x%02x 0x%02x 0xeb 0x27 0xb8' $((0x$B3 ^ 0xaa)) $((0x$B2 ^ 0xaa)) $((0x$B1 ^ 0xaa))
/usr/bin/hcitool -i $dev cmd 0x3f 0x001 $BDADDR ```

This shows that generating BDADDR from hardware identifiers is a common approach for boards without OTP, provided privacy measures (like hashing) Our method improves privacy by hashing the serial instead of using it directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't turn that address into a properly allocated Public. Notice the difference in how RPi generates the address and how it's done here. Your approach generates Random Static address. As such, corresponding bits must be set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reach out the QCA team and get input from them if you can't follow corresponding standards.

@ricardosalveti
Copy link
Contributor

checkbashisms recipes-connectivity/qca-set-bdaddr/files/qca_set_bdaddr.sh
possible bashism in recipes-connectivity/qca-set-bdaddr/files/qca_set_bdaddr.sh line 139 (alternative test command ([[ foo ]] should be [ foo ])):
        elif  [[ "$unconfigured" == "DOWN RAW" ]]; then
possible bashism in recipes-connectivity/qca-set-bdaddr/files/qca_set_bdaddr.sh line 139 (should be 'b = a'):
        elif  [[ "$unconfigured" == "DOWN RAW" ]]; then
possible bashism in recipes-connectivity/qca-set-bdaddr/files/qca_set_bdaddr.sh line 141 (alternative test command ([[ foo ]] should be [ foo ])):
        elif [[ "$configured" == "DOWN" ]]; then
possible bashism in recipes-connectivity/qca-set-bdaddr/files/qca_set_bdaddr.sh line 141 (should be 'b = a'):
        elif [[ "$configured" == "DOWN" ]]; then

# Debug mode (set DEBUG=1 to enable)
DEBUG=${DEBUG:-0}

# Choose an official Qualcomm 3-byte Organizationally Unique Identifier(OUI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra whitespace from the end.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

return 1
fi

sleep 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove this sleep, next commit,it is working as expected even without sleep.


validate_and_set_bda() {
attempts=0
max_attempts=10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need 10 attempts here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no particular reason, as you suggest will reduce it to 5 iterations

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need several attempts at all?

Copy link
Author

@quicjathot quicjathot Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we observed hciconfig is not immediately reliable during early boot during Firmware download (e.g., reports 00:00:00:00:00:00) until the controller transitions from DOWN RAW to a stable DOWN state after firmware initialization. To avoid false failures, we use a short, bounded retry loop to wait for a non‑zero stable state before attempting btmgmt public-addr and verification.

we will add comment for better readability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a sysfs uevent for that transition? If not, please add it instead of just waiting and retrying

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now we removed and retries and waiting, it is working fine

DESCRIPTION = "Systemd service and script to generate Device Address (BDADDR) \
for unconfigured Qualcomm QCA BT SoCs and program it using btmgmt at every boot."

inherit systemd
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inherit allarch as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will update architecture independent as well.

install -D -m 0644 ${S}/qca_set_bdaddr.service \
${D}${systemd_unitdir}/system/qca_set_bdaddr.service

# Install script
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove both comments (no need, quite clear what the line is doing), and have both lines together.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will remove

trap 'rm -f "$TMP_LOG"' EXIT

# Debug mode (set DEBUG=1 to enable)
DEBUG=${DEBUG:-0}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just enable debug / verbose by default, logs will go to systemd, and it is always useful to have a more extensive log for the unit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will enable debug logging.

@qualcomm-linux qualcomm-linux deleted a comment from quicjathot Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants