Bluetooth: LE: Add PA support#527
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds LE Periodic Advertising (PA) sync support across the stack (Zephyr SAL → service layer → IPC/socket → framework API) and introduces an Auracast sink bttool command that leverages PA scan parsing.
Changes:
- Add PA sync service + Zephyr SAL implementation and wire it into adapter lifecycle.
- Extend LE scan result plumbing to carry PA-related fields (SID/interval/tx power/flags) and update IPC scan callback codes.
- Add PA sync IPC messages + socket client/server handling and expose a framework API, plus an Auracast sink tool command.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/utils.h | Add BTTOOL_STRCAT helper macro for safe-ish log concatenation. |
| tools/bt_tools.h | Declare Auracast sink command entrypoints. |
| tools/bt_tools.c | Register/init/uninit Auracast sink command behind config. |
| tools/auracast_sink.c | New bttool command to scan for Auracast sources and (eventually) sync. |
| service/stacks/zephyr/sal_pa_sync_interface.c | New Zephyr SAL implementation for PA sync creation and callbacks. |
| service/stacks/zephyr/sal_le_scan_interface.c | Switch to Zephyr scan recv callback + propagate SID/interval/tx power. |
| service/stacks/include/sal_pa_sync_interface.h | New SAL PA sync API definitions/options and prototypes. |
| service/src/scan_manager.h | Update scan result data update signature to const uint8_t*. |
| service/src/scan_manager.c | Mark scan results as periodic advertising based on interval. |
| service/src/pa_sync_service.h | New PA sync service API and SAL-to-service callback hooks. |
| service/src/pa_sync_service.c | New PA sync service implementation + event dispatch. |
| service/src/pa_sync_event.h | Define PA sync event types and payload structs. |
| service/src/adapter_service.c | Start/stop PA sync service with LE enable/disable. |
| service/ipc/socket/src/bt_socket_server.c | Route PA sync IPC commands to new handler. |
| service/ipc/socket/src/bt_socket_scan.c | Adjust scan callback handling for new IPC subcode scheme. |
| service/ipc/socket/src/bt_socket_pa_sync.c | New PA sync socket IPC server/client callback implementation. |
| service/ipc/socket/src/bt_socket_client.c | Register PA sync callback range dispatch. |
| service/ipc/socket/include/bt_socket.h | Expose PA sync socket process/callback APIs. |
| service/ipc/socket/include/bt_message_scan.h | Keep old enum stable; add new IPC subcodes/codes for scan result callback. |
| service/ipc/socket/include/bt_message_pa_sync.h | New PA sync IPC codes and message/callback payloads. |
| service/ipc/socket/include/bt_message.h | Add PA sync payload unions to the common packet. |
| service/ipc/socket/include/bt_ipc_code.h | Add new IPC code groups for PA / PA sync. |
| framework/socket/bt_pa_sync.c | New socket-based framework wrapper for bt_pa_sync_create. |
| framework/include/bt_uuid.h | Add Broadcast Audio Announcement UUID (0x1852). |
| framework/include/bt_pa_sync.h | New public PA sync API and parsed info struct. |
| framework/include/bt_le_scan.h | Extend scan result struct with SID/interval/tx power/flags and constants. |
| framework/include/bluetooth.h | Add broadcast ID invalid constant and broadcast name max length. |
| framework/include/advertiser_data.h | Add generic advertiser_data_parse() callback type + API. |
| framework/common/advertiser_data_helper.c | Implement advertiser_data_parse() and PA-sync advertising parsing helper. |
| framework/api/bt_pa_sync.c | Add direct API binding to pa_sync_create() (non-socket path). |
| Makefile | Build integration for PA sync and Auracast sink tool. |
| Kconfig | Add BLUETOOTH_PA_SYNC and BLUETOOTH_AURACAST_SINK configs. |
| CMakeLists.txt | CMake build integration for PA sync and Auracast sink tool. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| bt_addr_set(&prev->addr, result->addr.addr); | ||
| prev->life = BTTOOL_PA_SYNC_PA_REPORT_LIFE; | ||
| prev->type = result->addr_type; | ||
| prev->sid = result->sid; |
There was a problem hiding this comment.
Yes, the previous one is not valid when the new value arrives.
| switch (data->type) { | ||
| case BT_AD_NAME_SHORT: | ||
| case BT_AD_NAME_COMPLETE: | ||
| strlcpy(info->name, (char*)data->data, MIN(sizeof(info->name), data->len)); | ||
| break; | ||
|
|
||
| #ifdef CONFIG_BLUETOOTH_AURACAST_SINK | ||
| case BT_AD_BROADCAST_NAME: | ||
| strlcpy(info->broadcast_name, (char*)data->data, | ||
| MIN(sizeof(info->broadcast_name), data->len)); | ||
| break; |
There was a problem hiding this comment.
Both sizeof(info->broadcast_name) and data->len are length of the string + 1
| device = zalloc(sizeof(pa_sync_device_t)); | ||
| if (device == NULL) | ||
| goto error; | ||
|
|
||
| memcpy(&sal_params.addr, &msg->addr, sizeof(bt_le_address_t)); |
There was a problem hiding this comment.
Already fixed in the next PR.
The final version is:
error:
sync_terminated_callback(params->cbs, &msg->addr, msg->sid, params->context);
free(device);
| if (!cbs || sid > BLE_SCAN_SID_MAX) | ||
| return BT_STATUS_PARM_INVALID; | ||
|
|
||
| if (!params) | ||
| params = &default_params; | ||
|
|
||
| if (params->skip > BT_PA_SYNC_SKIP_MAX) | ||
| return BT_STATUS_PARM_INVALID; | ||
|
|
||
| if (params->timeout > BT_PA_SYNC_TIMEOUT_MAX) | ||
| return BT_STATUS_PARM_INVALID; | ||
|
|
||
| msg = zalloc(sizeof(pa_sync_event_t) + sizeof(pa_sync_event_create_sync_t)); | ||
| if (!msg) { | ||
| BT_LOGE("%s, malloc failed", __func__); | ||
| return BT_STATUS_NOMEM; | ||
| } | ||
|
|
||
| param = (pa_sync_event_create_sync_t*)msg->data; | ||
| memcpy(&msg->addr, addr, sizeof(bt_le_address_t)); | ||
| msg->event = CREATE_SYNC; |
There was a problem hiding this comment.
Same as the other services/profiles, we do not check if address is empty.
huangyulong3
left a comment
There was a problem hiding this comment.
General PR Feedback:
-
The PR description is empty. For a feature of this size (2000+ lines, 33 files), please add a description covering: what PA sync is, the design approach, and any known limitations.
-
Copyright year inconsistency:
bt_message_pa_sync.handframework/api/bt_pa_sync.cuse 2026, while all other new files use 2025. Please align. -
sal_pa_sync_interface.his missing a trailing newline at end of file.
| sal_pa_sync_device_t* device = (sal_pa_sync_device_t*)data; | ||
| struct bt_le_per_adv_sync* sync = (struct bt_le_per_adv_sync*)context; | ||
|
|
||
| if (!device) |
There was a problem hiding this comment.
device_delete() calls pa_sync_on_terminated() then free(device). But in create_sync() error path, if bt_le_per_adv_sync_cb_register fails, device_delete(device) is called which triggers pa_sync_on_terminated — this is incorrect because the sync was never established. Consider separating the cleanup logic from the termination notification. A simple free() would suffice in the error path of create_sync().
There was a problem hiding this comment.
When create_sync failed, there should be a terminated callback even if it's not established. Since we don't really have a "establish failed" callback.
|
|
||
| memcpy(device->info, info, sizeof(struct bt_le_per_adv_sync_synced_info)); | ||
|
|
||
| pa_sync_on_established(device->id, &device->addr, device->sid); |
There was a problem hiding this comment.
on_synced(): The info pointer from Zephyr is likely stack-allocated or transient. You are doing a shallow memcpy of struct bt_le_per_adv_sync_synced_info, but this struct may contain pointers (e.g., addr) that become dangling after the callback returns. Verify that all fields in the copied struct are value types, or deep-copy any pointer members.
There was a problem hiding this comment.
info->addr is not used, we use device->addr instead.
info->conn is not used too.
| err = bt_le_per_adv_sync_create(z_param, &device->sync); | ||
| if (err) | ||
| goto error; | ||
|
|
There was a problem hiding this comment.
bt_sal_pa_sync_cleanup(): The TODO comment says "add unregisteration for stack callbacks" — this should be addressed before merging. Without calling bt_le_per_adv_sync_cb_unregister(&sal_pa_sync_cbs), the Zephyr stack will still invoke on_synced after cleanup, leading to use-after-free on g_sal_pa_sync_info. Also, typo: "unregisteration" → "unregistration".
There was a problem hiding this comment.
Zephyr do not really have a bt_le_per_adv_sync_cb_unregister
| break; | ||
| } | ||
| } | ||
| #endif |
There was a problem hiding this comment.
Bug: In bt_socket_client_pa_sync_callback, the SYNC_TERMINATED_CALLBACK and SYNC_REPORT_CALLBACK cases both read from _on_sync_established instead of their respective union members (_on_sync_terminated and _on_sync_report). While the struct layout happens to be identical today, this is fragile and will break silently if the structs diverge. Please use the correct union member for each case:
case PA_SYNC_SUBCODE_SYNC_TERMINATED_CALLBACK:
if (cbs->on_sync_terminated) {
cbs->on_sync_terminated(&packet->pa_sync_cb._on_sync_terminated.addr,
packet->pa_sync_cb._on_sync_terminated.sid, context);
}
break;
case PA_SYNC_SUBCODE_SYNC_REPORT_CALLBACK:
if (cbs->on_sync_report) {
cbs->on_sync_report(&packet->pa_sync_cb._on_sync_report.addr,
packet->pa_sync_cb._on_sync_report.sid, context);
}
break;There was a problem hiding this comment.
Already fixed in the next PR.
The final version is:
case PA_SYNC_SUBCODE_SYNC_TERMINATED_CALLBACK:
if (cbs->on_sync_terminated) {
cbs->on_sync_terminated(&packet->pa_sync_cb._on_sync_terminated.addr,
packet->pa_sync_cb._on_sync_terminated.sid, context);
}
break;
case PA_SYNC_SUBCODE_SYNC_REPORT_CALLBACK:
if (cbs->on_sync_report) {
bt_pa_sync_report_t report = { 0 };
report.tx_power = packet->pa_sync_cb._on_sync_report.tx_power;
report.rssi = packet->pa_sync_cb._on_sync_report.rssi;
report.cnt = packet->pa_sync_cb._on_sync_report.cnt;
report.subevent = packet->pa_sync_cb._on_sync_report.subevent;
report.adv_data_len = packet->pa_sync_cb._on_sync_report.adv_data_len;
if (report.adv_data_len)
report.data = packet->pa_sync_cb._on_sync_report.data;
cbs->on_sync_report(&packet->pa_sync_cb._on_sync_report.addr,
packet->pa_sync_cb._on_sync_report.sid, &report, context);
}
break;
| { | ||
| switch (type) { | ||
| case BT_LE_ADDR_TYPE_PUBLIC: | ||
| return "Public"; |
There was a problem hiding this comment.
Typo: update_neaby_pa → update_nearby_pa. This function name is also referenced in the call site.
| { | ||
| if (!g_auracast_sink) { | ||
| PRINT("Not initialized"); | ||
| return CMD_INVALID_OPT; |
There was a problem hiding this comment.
sync_create_cmd(): All option cases are empty — the parsed values from optarg are never stored or used. This means the sync command accepts arguments but silently ignores them. Either implement the option handling or add TODO comments explaining the plan. As-is, this is dead code that misleads users.
There was a problem hiding this comment.
Implemented in the next PR.
| sal_params.options &= ~BT_SAL_PA_SYNC_OPTION_USE_LIST; /**< not supported */ | ||
| sal_params.options |= params->params.no_report ? BT_SAL_PA_SYNC_OPTION_REPORTING_DISABLED : 0; | ||
| sal_params.options |= params->params.filter ? BT_SAL_PA_SYNC_OPTION_FILTER_ENABLED : 0; | ||
| sal_params.cte = 0; /**< nothing specified */ |
There was a problem hiding this comment.
create_sync(): sal_params.options &= ~BT_SAL_PA_SYNC_OPTION_USE_LIST on a zero-initialized struct is a no-op. The comment says "not supported" — if the Periodic Advertiser List is truly not supported, consider adding validation in pa_sync_create() to reject such requests early, rather than silently clearing the flag here.
There was a problem hiding this comment.
The accept list mechanism in not used in vela Bluetooth service.
| { | ||
| pa_sync_device_t* device = (pa_sync_device_t*)data; | ||
|
|
||
| BT_LOGD("%s", __func__); |
There was a problem hiding this comment.
process_sync_terminated() removes the device from the list while iterating via bt_list_foreach → func_for_device. Calling bt_list_remove() during iteration can corrupt the list iterator or cause use-after-free depending on the bt_list implementation. Consider collecting devices to remove and doing the removal after iteration completes, or using a safe-iteration pattern.
There was a problem hiding this comment.
This is a standard utilization of the finalizing hook function.
|
|
||
| static void create_sync(const pa_sync_event_t* msg) | ||
| { | ||
| pa_sync_event_create_sync_t* params = (pa_sync_event_create_sync_t*)msg->data; |
There was a problem hiding this comment.
create_sync(): The cbs pointer is stored directly in pa_sync_device_t without any lifetime guarantee. If the caller frees or invalidates the callback struct after pa_sync_create() returns, the stored pointer becomes dangling. Consider either: (1) documenting that cbs must remain valid for the lifetime of the sync, or (2) copying the callbacks struct into the device.
There was a problem hiding this comment.
It is assumed that cbs must be valid until the periodic connection is terminated.
There was a problem hiding this comment.
They're typically const static variables.
| return BT_LE_EXT_ADV_SCAN_IND; | ||
| if (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) | ||
| return BT_LE_EXT_ADV_IND; | ||
|
|
There was a problem hiding this comment.
scan_recv_cb is invoked from the Zephyr BT RX thread context. The scan_on_result_data_update() call does a malloc + memcpy + do_in_service_loop, which is fine. However, scan_cbs is a file-scope static — if bt_le_scan_cb_register is called multiple times (e.g., rapid start/stop), the -EEXIST check in start_scan handles it, but stop_scan unconditionally calls bt_le_scan_cb_unregister. If another scan start races with stop, the callback could be unregistered while still in use. Consider adding a reference count or ensuring serialization.
There was a problem hiding this comment.
This case is handled in adapter service. And is not considered in this PR.
|
|
||
| STREAM_TO_UINT16(uuid_16, p); | ||
| adv_data_parse_uuid_16(info, uuid_16, data); | ||
| break; |
There was a problem hiding this comment.
adv_data_parsed() for BT_AD_NAME_SHORT/BT_AD_NAME_COMPLETE: strlcpy with MIN(sizeof(info->name), data->len) — note that data->len includes the AD Type byte (1 octet), so the actual name data length is data->len - 1. The data pointer data->data already skips the type byte, but the length passed should be data->len - 1 (or data->len since len field = total_length which includes type). Please double-check the adv_data_t struct semantics: if len = length_byte (which includes the type octet), then the payload is len - 1 bytes, and you should use MIN(sizeof(info->name), data->len - 1 + 1) to account for the null terminator. The current code may copy one extra byte.
There was a problem hiding this comment.
The "one extra byte" is reserved for the \0
|
|
||
| #include "bluetooth.h" | ||
|
|
||
| #define BTTOOL_STRCAT(dst, size, src, ...) \ |
There was a problem hiding this comment.
utils.h is missing an include guard (#ifndef __UTILS_H__ / #define __UTILS_H__). Multiple inclusion will cause redefinition errors for the BTTOOL_STRCAT macro and the function declarations.
| static struct bt_le_per_adv_sync_cb sal_pa_sync_cbs = { | ||
| .synced = on_synced, | ||
| .term = NULL, | ||
| .recv = NULL, |
There was a problem hiding this comment.
sal_pa_sync_cbs: The term and recv callbacks are set to NULL. Without a term callback, the SAL layer will never know when Zephyr terminates a sync (e.g., due to timeout or remote side stopping). This means pa_sync_on_terminated() will only be called from device_delete() during cleanup, but never during normal operation when the controller loses sync. This seems like a significant missing piece — at minimum, a term callback should be implemented to properly notify the service layer.
There was a problem hiding this comment.
Implemanted in the next PR.
| */ | ||
| typedef void (*on_sync_report_callback)(const bt_le_address_t* addr, uint8_t sid, void* context); | ||
|
|
||
| typedef struct { |
There was a problem hiding this comment.
on_sync_report_callback signature only passes addr, sid, and context — but no actual report data (tx_power, rssi, cte, adv_data, etc.). The pa_sync_event_report_data_t struct in pa_sync_event.h defines all these fields, but they are never surfaced to the API consumer. This makes the report callback unusable in practice. Consider adding a report data parameter to the callback signature.
There was a problem hiding this comment.
Implemented in the next PR
typedef void (*on_pa_sync_report_callback)(const bt_le_address_t* addr, uint8_t sid,
const bt_pa_sync_report_t* report, void* context);
| { | ||
| pa_sync_event_t* msg = (pa_sync_event_t*)data; | ||
|
|
||
| BT_LOGD("%s, event = %s(%d)", __func__, pa_sync_event_to_string(msg->event), msg->event); |
There was a problem hiding this comment.
pa_sync_process_message(): The TERMINATE_SYNC and SYNC_REPORT events are defined in the enum but not handled in the switch. TERMINATE_SYNC falls through to default silently. At minimum, add a log warning for unhandled events, or implement the terminate path since pa_sync_terminate() currently returns BT_STATUS_UNSUPPORTED.
There was a problem hiding this comment.
Implemented in the next PR.
switch (msg->event) {
case CREATE_SYNC:
create_sync(msg);
break;
case TERMINATE_SYNC:
terminate_sync(msg);
break;
case SYNC_ESTABLISHED:
sync_established(msg);
break;
case SYNC_TERMINATED:
sync_terminated(msg);
break;
case SYNC_REPORT:
sync_report(msg);
break;
case BIGINFO_RECEIVED:
biginfo_received(msg);
break;
default:
break;
}
| break; | ||
| } | ||
| case BLE_SCAN_SUBCODE_SCAN_CALLBACK: { | ||
| bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_cb._on_scan_result_cb.scanner; |
There was a problem hiding this comment.
The new BLE_SCAN_SUBCODE_SCAN_CALLBACK handler does not check if tmp is NULL after malloc. If allocation fails, the subsequent memcpy(tmp, ...) will crash. Add a NULL check:
ble_scan_result_t* tmp = malloc(sizeof(ble_scan_result_t) + result->length);
if (!tmp)
break;There was a problem hiding this comment.
This keeps the code exactly same as it was before.
| return BT_STATUS_FAIL; | ||
| } | ||
|
|
||
| bt_status_t bt_sal_pa_sync_cleanup(void) |
There was a problem hiding this comment.
bt_sal_pa_create_sync(): In the error path, both z_param and req are freed. However, req->context points to z_param. If sal_send_req fails after the request has been partially processed (unlikely but possible in edge cases), z_param could be double-freed — once here and once in create_sync(). Consider setting req->context = NULL before freeing, or restructuring ownership so only one path frees z_param.
There was a problem hiding this comment.
If sal_send_req failed, create_sync will not be invoked.
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, |
There was a problem hiding this comment.
bt_pa_sync_create() in the direct API path does not validate ins or addr for NULL. The socket path (framework/socket/bt_pa_sync.c) does validate with BT_SOCKET_INS_VALID and BT_SOCKET_PTR_VALID. Add similar NULL checks here for consistency and safety.
There was a problem hiding this comment.
Same as all the other profiles, we do not check instance for local apis. Because they're never used.
| @@ -0,0 +1,79 @@ | |||
| /**************************************************************************** | |||
| * Copyright (C) 2025 Xiaomi Corporation | |||
There was a problem hiding this comment.
This is developed in 2025.
| @@ -0,0 +1,95 @@ | |||
| /**************************************************************************** | |||
| * Copyright (C) 2025 Xiaomi Corporation | |||
There was a problem hiding this comment.
This is developped in 2025
| #include "bluetooth.h" | ||
|
|
||
| #ifndef SAL_BIT | ||
| #define SAL_BIT(x) (1UL << (x)) |
There was a problem hiding this comment.
Used the BIT is better.
There was a problem hiding this comment.
BIT() is not provided now.
We will have BIT() since #473
bug: v/82379 Periodic advertising often comes from extended scanning, so we add an parser here. An alternative is Periodic Advertising Sync Transfer (to be implemented later). Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82379 Parse the scan result at the client side, so we can reduce the uplink data. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82275 Add TxPower, Periodic Advertising Interval, and correct the conversion of adv_type. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82380 Initial version of Auracast sink test tool Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82380 Add scan command to search active Auracast sources. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82379 Parse scan result at bttol to check if there is an Auracast source available. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82379 Skip logs that are not available. Add more debug logs on error. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82379 Allow to stop scanning for periodic advertisers. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82820 Not sure the reason but we decided to add an extra flag to indicate whether periodic advertising is present in the scan result. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82820 Modify message codes so its safe when client and server have different version of codes. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82379 Record a nearby advertiser for easier testing. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82379 A command to initiate a sync connection. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82581 A initial version of PA sync API. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82581 A initial version of ipc codes. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82581 Allow to generate a create sync message to socket server. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82581 Extract message from ipc and relay to the service Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82581 All the actions shall be in service loop. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82581 This patch sends the create sync command to SAL. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82275 Allow to transfer to service loop at SAL. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82275 A device list is necessary at SAL to record the sync object from Zephyr. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82275 Generate callback from SAL and post to service loop. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82275 Generate a sync establish or terminated callback when sync success and failed. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82275 Add `new` and `delete` function for sync devices. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82380 Use `pa_sync_send_message` for all pa sync messages. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82380 Allow to generate callback for each applications that watching a specific device. Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82380 Allow to generate ipc message from server to client Signed-off-by: Zihao Gao <gaozihao@xiaomi.com>
bug: v/82380 Fix typo: rename update_neaby_pa to update_nearby_pa. Signed-off-by: liyuheng <liyuheng@xiaomi.com>
PR #527 初步审查 — Bluetooth: LE: Add PA support概述: 33 文件 2084+/36-,添加 LE Periodic Advertising Sync 支持。 ❌ 阻断级问题1. PR body 为空
2. 与 #546 的关联性不明
请补充完整 PR 描述后我进行深入审查。 |
No description provided.