Skip to content

Commit

Permalink
Merge pull request #630 from google/gbg/iso-packet-queue
Browse files Browse the repository at this point in the history
add support for ACL and ISO HCI packet queues
  • Loading branch information
barbibulle authored Jan 24, 2025
2 parents af466c2 + 6fe7931 commit afee659
Show file tree
Hide file tree
Showing 9 changed files with 460 additions and 118 deletions.
16 changes: 15 additions & 1 deletion apps/auracast.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 Google LLC
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -825,10 +825,24 @@ async def run_broadcast(
),
)
print('Setup ISO Data Path')

def on_flow(packet_queue):
print(
f'\rPACKETS: pending={packet_queue.pending}, '
f'queued={packet_queue.queued}, completed={packet_queue.completed}',
end='',
)

packet_queue = None
for bis_link in big.bis_links:
await bis_link.setup_data_path(
direction=bis_link.Direction.HOST_TO_CONTROLLER
)
if packet_queue is None:
packet_queue = bis_link.data_packet_queue

if packet_queue:
packet_queue.on('flow', lambda: on_flow(packet_queue))

for frame in itertools.cycle(frames):
mid = len(frame) // 2
Expand Down
28 changes: 22 additions & 6 deletions apps/controller_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
HCI_Command_Status_Event,
HCI_READ_BUFFER_SIZE_COMMAND,
HCI_Read_Buffer_Size_Command,
HCI_LE_READ_BUFFER_SIZE_V2_COMMAND,
HCI_LE_Read_Buffer_Size_V2_Command,
HCI_READ_BD_ADDR_COMMAND,
HCI_Read_BD_ADDR_Command,
HCI_READ_LOCAL_NAME_COMMAND,
Expand Down Expand Up @@ -147,7 +149,7 @@ async def get_le_info(host: Host) -> None:


# -----------------------------------------------------------------------------
async def get_acl_flow_control_info(host: Host) -> None:
async def get_flow_control_info(host: Host) -> None:
print()

if host.supports_command(HCI_READ_BUFFER_SIZE_COMMAND):
Expand All @@ -160,14 +162,28 @@ async def get_acl_flow_control_info(host: Host) -> None:
f'packets of size {response.return_parameters.hc_acl_data_packet_length}',
)

if host.supports_command(HCI_LE_READ_BUFFER_SIZE_COMMAND):
if host.supports_command(HCI_LE_READ_BUFFER_SIZE_V2_COMMAND):
response = await host.send_command(
HCI_LE_Read_Buffer_Size_V2_Command(), check_result=True
)
print(
color('LE ACL Flow Control:', 'yellow'),
f'{response.return_parameters.total_num_le_acl_data_packets} '
f'packets of size {response.return_parameters.le_acl_data_packet_length}',
)
print(
color('LE ISO Flow Control:', 'yellow'),
f'{response.return_parameters.total_num_iso_data_packets} '
f'packets of size {response.return_parameters.iso_data_packet_length}',
)
elif host.supports_command(HCI_LE_READ_BUFFER_SIZE_COMMAND):
response = await host.send_command(
HCI_LE_Read_Buffer_Size_Command(), check_result=True
)
print(
color('LE ACL Flow Control:', 'yellow'),
f'{response.return_parameters.hc_total_num_le_acl_data_packets} '
f'packets of size {response.return_parameters.hc_le_acl_data_packet_length}',
f'{response.return_parameters.total_num_le_acl_data_packets} '
f'packets of size {response.return_parameters.le_acl_data_packet_length}',
)


Expand Down Expand Up @@ -274,8 +290,8 @@ async def async_main(latency_probes, transport):
# Get the LE info
await get_le_info(host)

# Print the ACL flow control info
await get_acl_flow_control_info(host)
# Print the flow control info
await get_flow_control_info(host)

# Get codec info
await get_codecs_info(host)
Expand Down
33 changes: 24 additions & 9 deletions bumble/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,17 @@ def __init__(
'0000000060000000'
) # BR/EDR Not Supported, LE Supported (Controller)
self.manufacturer_name = 0xFFFF
self.hc_data_packet_length = 27
self.hc_total_num_data_packets = 64
self.hc_le_data_packet_length = 27
self.hc_total_num_le_data_packets = 64
self.acl_data_packet_length = 27
self.total_num_acl_data_packets = 64
self.le_acl_data_packet_length = 27
self.total_num_le_acl_data_packets = 64
self.iso_data_packet_length = 960
self.total_num_iso_data_packets = 64
self.event_mask = 0
self.event_mask_page_2 = 0
self.supported_commands = bytes.fromhex(
'2000800000c000000000e4000000a822000000000000040000f7ffff7f000000'
'30f0f9ff01008004000000000000000000000000000000000000000000000000'
'30f0f9ff01008004002000000000000000000000000000000000000000000000'
)
self.le_event_mask = 0
self.advertising_parameters = None
Expand Down Expand Up @@ -1181,9 +1183,9 @@ def on_hci_read_buffer_size_command(self, _command):
return struct.pack(
'<BHBHH',
HCI_SUCCESS,
self.hc_data_packet_length,
self.acl_data_packet_length,
0,
self.hc_total_num_data_packets,
self.total_num_acl_data_packets,
0,
)

Expand Down Expand Up @@ -1212,8 +1214,21 @@ def on_hci_le_read_buffer_size_command(self, _command):
return struct.pack(
'<BHB',
HCI_SUCCESS,
self.hc_le_data_packet_length,
self.hc_total_num_le_data_packets,
self.le_acl_data_packet_length,
self.total_num_le_acl_data_packets,
)

def on_hci_le_read_buffer_size_v2_command(self, _command):
'''
See Bluetooth spec Vol 4, Part E - 7.8.2 LE Read Buffer Size Command
'''
return struct.pack(
'<BHBHB',
HCI_SUCCESS,
self.le_acl_data_packet_length,
self.total_num_le_acl_data_packets,
self.iso_data_packet_length,
self.total_num_iso_data_packets,
)

def on_hci_le_read_local_supported_features_command(self, _command):
Expand Down
29 changes: 10 additions & 19 deletions bumble/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from .colors import color
from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
from .gatt import Characteristic, Descriptor, Service
from .host import Host
from .host import DataPacketQueue, Host
from .profiles.gap import GenericAccessService
from .core import (
BT_BR_EDR_TRANSPORT,
Expand Down Expand Up @@ -1329,7 +1329,6 @@ async def disconnect(
class _IsoLink:
handle: int
device: Device
packet_sequence_number: int
sink: Callable[[hci.HCI_IsoDataPacket], Any] | None = None

class Direction(IntEnum):
Expand Down Expand Up @@ -1391,22 +1390,12 @@ async def remove_data_path(self, direction: _IsoLink.Direction) -> int:
return response.return_parameters.status

def write(self, sdu: bytes) -> None:
"""Write an ISO SDU.
"""Write an ISO SDU."""
self.device.host.send_iso_sdu(connection_handle=self.handle, sdu=sdu)

This will automatically increase the packet sequence number.
"""
self.device.host.send_hci_packet(
hci.HCI_IsoDataPacket(
connection_handle=self.handle,
data_total_length=len(sdu) + 4,
packet_sequence_number=self.packet_sequence_number,
pb_flag=0b10,
packet_status_flag=0,
iso_sdu_length=len(sdu),
iso_sdu_fragment=sdu,
)
)
self.packet_sequence_number = (self.packet_sequence_number + 1) % 0x10000
@property
def data_packet_queue(self) -> DataPacketQueue | None:
return self.device.host.get_data_packet_queue(self.handle)


# -----------------------------------------------------------------------------
Expand All @@ -1426,7 +1415,6 @@ class State(IntEnum):

def __post_init__(self) -> None:
super().__init__()
self.packet_sequence_number = 0

async def disconnect(
self, reason: int = hci.HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
Expand All @@ -1443,7 +1431,6 @@ class BisLink(_IsoLink):

def __post_init__(self) -> None:
self.device = self.big.device
self.packet_sequence_number = 0


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -1691,6 +1678,10 @@ async def get_remote_le_features(self) -> hci.LeFeatureMask:
self.peer_le_features = await self.device.get_remote_le_features(self)
return self.peer_le_features

@property
def data_packet_queue(self) -> DataPacketQueue | None:
return self.device.host.get_data_packet_queue(self.handle)

async def __aenter__(self):
return self

Expand Down
27 changes: 22 additions & 5 deletions bumble/hci.py
Original file line number Diff line number Diff line change
Expand Up @@ -3585,8 +3585,8 @@ def mask(event_codes: Iterable[int]) -> bytes:
@HCI_Command.command(
return_parameters_fields=[
('status', STATUS_SPEC),
('hc_le_acl_data_packet_length', 2),
('hc_total_num_le_acl_data_packets', 1),
('le_acl_data_packet_length', 2),
('total_num_le_acl_data_packets', 1),
]
)
class HCI_LE_Read_Buffer_Size_Command(HCI_Command):
Expand All @@ -3595,6 +3595,22 @@ class HCI_LE_Read_Buffer_Size_Command(HCI_Command):
'''


# -----------------------------------------------------------------------------
@HCI_Command.command(
return_parameters_fields=[
('status', STATUS_SPEC),
('le_acl_data_packet_length', 2),
('total_num_le_acl_data_packets', 1),
('iso_data_packet_length', 2),
('total_num_iso_data_packets', 1),
]
)
class HCI_LE_Read_Buffer_Size_V2_Command(HCI_Command):
'''
See Bluetooth spec @ 7.8.2 LE Read Buffer Size V2 Command
'''


# -----------------------------------------------------------------------------
@HCI_Command.command(
return_parameters_fields=[('status', STATUS_SPEC), ('le_features', 8)]
Expand Down Expand Up @@ -7555,7 +7571,7 @@ def from_bytes(packet: bytes) -> HCI_IsoDataPacket:
if should_include_sdu_info:
packet_sequence_number, sdu_info = struct.unpack_from('<HH', packet, pos)
iso_sdu_length = sdu_info & 0xFFF
packet_status_flag = sdu_info >> 14
packet_status_flag = (sdu_info >> 15) & 1
pos += 4

iso_sdu_fragment = packet[pos:]
Expand Down Expand Up @@ -7589,17 +7605,18 @@ def __bytes__(self) -> bytes:
fmt += 'HH'
args += [
self.packet_sequence_number,
self.iso_sdu_length | self.packet_status_flag << 14,
self.iso_sdu_length | self.packet_status_flag << 15,
]
return struct.pack(fmt, *args) + self.iso_sdu_fragment

def __str__(self) -> str:
return (
f'{color("ISO", "blue")}: '
f'handle=0x{self.connection_handle:04x}, '
f'pb={self.pb_flag}, '
f'ps={self.packet_status_flag}, '
f'data_total_length={self.data_total_length}, '
f'sdu={self.iso_sdu_fragment.hex()}'
f'sdu_fragment={self.iso_sdu_fragment.hex()}'
)


Expand Down
Loading

0 comments on commit afee659

Please sign in to comment.