Skip to content

tests: bluetooth: classic: Add test suite l2cap. #88703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions tests/bluetooth/classic/l2cap/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
set(NO_QEMU_SERIAL_BT_SERVER 1)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bluetooth)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
74 changes: 74 additions & 0 deletions tests/bluetooth/classic/l2cap/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.. _bluetooth_classic_l2cap_server_tests:

Bluetooth Classic L2cap Server Tests
##################################

Overview
********

This test suite uses ``bumble`` for testing Bluetooth Classic communication between a host
PC (running :ref:`Twister <twister_script>`) and a device under test (DUT) running Zephyr.

Prerequisites
*************

The test suite has the following prerequisites:

* The ``bumble`` library installed on the host PC.
The Bluetooth Classic controller on PC side is required. Refer to getting started of `bumble`_
for details.

The HCI transport for ``bumble`` can be configured as follows:

* A specific configuration context can be provided along with the ``usb_hci`` fixture separated by
a ``:`` (i.e. specify fixture ``usb_hci:usb:0`` to use the ``usb:0`` as hci transport for
``bumble``).
* The configuration context can be overridden using the `hci transport`_ can be provided using the
``--hci-transport`` test suite argument (i.e. run ``twister`` with the
``--pytest-args=--hci-transport=usb:0`` argument to use the ``usb:0`` as hci transport for
``bumble``).

Building and Running
********************

Running on mimxrt1170_evk@B/mimxrt1176/cm7
==========================================

Running the test suite on :ref:`mimxrt1170_evk` relies on configuration of ``bumble``.

On the host PC, a HCI transport needs to be required. Refer to `bumble platforms`_ page of
``bumble`` for details.

For example, on windows, a PTS dongle is used. After `WinUSB driver`_ has been installed,
the HCI transport would be USB transport interface ``usb:<index>``.

If the HCI transport is ``usb:0`` and debug console port is ``COM4``, the test suite can be
launched using Twister:

.. code-block:: shell
west twister -v -p mimxrt1170_evk@B/mimxrt1176/cm7 --device-testing --device-serial COM4 -T tests/bluetooth/classic/l2cap -O l2cap_s --force-platform --west-flash --west-runner=jlink -X usb_hci:usb:0
Running on Hardware
===================

Running the test suite on hardware requires a HCI transport connected to the host PC.

The test suite can be launched using Twister. Below is an example for running on the
:zephyr:board:`mimxrt1170_evk@B/mimxrt1176/cm7`:

.. code-block:: shell
west twister -v -p mimxrt1170_evk@B/mimxrt1176/cm7 --device-testing --device-serial COM4 -T tests/bluetooth/classic/l2cap -O l2cap_s --force-platform --west-flash --west-runner=jlink -X usb_hci:usb:0
.. _bumble:
https://google.github.io/bumble/getting_started.html

.. _hci transport:
https://google.github.io/bumble/transports/index.html

.. _bumble platforms:
https://google.github.io/bumble/platforms/index.html

.. _WinUSB driver:
https://google.github.io/bumble/platforms/windows.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#select NXP NW612 Chipset
CONFIG_BT_NXP_NW612=y

CONFIG_BT_SETTINGS=n
CONFIG_FLASH=n
CONFIG_FLASH_MAP=n
CONFIG_NVS=n
CONFIG_SETTINGS=n

CONFIG_ENTROPY_GENERATOR=y
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/

/ {
chosen {
zephyr,sram = &dtcm;
};
};
17 changes: 17 additions & 0 deletions tests/bluetooth/classic/l2cap/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CONFIG_BT=y
CONFIG_BT_CLASSIC=y
CONFIG_BT_SHELL=y
CONFIG_LOG=y
CONFIG_DEBUG=y
CONFIG_ZTEST=y
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
CONFIG_BT_L2CAP_RET=y
CONFIG_BT_L2CAP_FC=y
CONFIG_BT_L2CAP_ENH_RET=y
CONFIG_BT_L2CAP_STREAM=y
CONFIG_BT_L2CAP_FCS=y
CONFIG_BT_L2CAP_EXT_WIN_SIZE=y
CONFIG_BT_L2CAP_MAX_WINDOW_SIZE=5
CONFIG_BT_DEVICE_NAME="L2CAP_BR"
CONFIG_BT_CREATE_CONN_TIMEOUT=30
CONFIG_BT_PAGE_TIMEOUT=0xFFFF
57 changes: 57 additions & 0 deletions tests/bluetooth/classic/l2cap/pytest/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2024 NXP
#
# SPDX-License-Identifier: Apache-2.0
import re

import pytest
from test_l2cap_common import L2CAP_SERVER_PSM, MODE, logger
from twister_harness import DeviceAdapter, Shell


def pytest_addoption(parser) -> None:
"""Add local parser options to pytest."""
parser.addoption('--hci-transport', default=None, help='Configuration HCI transport for bumble')


@pytest.fixture(name='initialize', scope='session')
def fixture_initialize(request, shell: Shell, dut: DeviceAdapter):
"""Session initializtion"""
# Get HCI transport for bumble
hci = request.config.getoption('--hci-transport')

if hci is None:
for fixture in dut.device_config.fixtures:
if fixture.startswith('usb_hci:'):
hci = fixture.split(sep=':', maxsplit=1)[1]
break

assert hci is not None

lines = shell.exec_command("bt init")
lines = dut.readlines_until("Bluetooth initialized")
regex = r"Identity: (?P<bd_addr>(.*\s\(.*\)))"
bd_addr = None
for line in lines:
logger.info(f"Shell log {line}")
m = re.search(regex, line)
if m:
bd_addr = m.group('bd_addr')

if bd_addr is None:
logger.error('Fail to get IUT BD address')
raise AssertionError

lines = shell.exec_command("br pscan on")
lines = shell.exec_command("br iscan on")
logger.info('initialized')

lines = shell.exec_command(f"l2cap_br register {format(L2CAP_SERVER_PSM, 'x')} {MODE}")
logger.info("l2cap server register")
return hci, bd_addr


@pytest.fixture
def l2cap_br_dut(initialize):
logger.info('Start running testcase')
yield initialize
logger.info('Done')
115 changes: 115 additions & 0 deletions tests/bluetooth/classic/l2cap/pytest/test_l2cap_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright 2025 NXP
#
# SPDX-License-Identifier: Apache-2.0

import asyncio
import logging

from bumble.core import BT_BR_EDR_TRANSPORT
from bumble.hci import (
HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR,
)
from bumble.pairing import PairingDelegate

logger = logging.getLogger(__name__)

L2CAP_SERVER_PSM = 0x1001
MODE = "basic"
L2CAP_CHAN_IUT_ID = 0
MAX_MTU = 0xFFFF
MIN_MTU = 0x30
STRESS_TEST_MAX_COUNT = 50


class Delegate(PairingDelegate):
def __init__(
self,
dut,
io_capability,
):
super().__init__(
io_capability,
)
self.dut = dut


async def device_power_on(device) -> None:
while True:
try:
await device.power_on()
break
except Exception:
continue


async def wait_for_shell_response(dut, message):
found = False
lines = []
try:
while found is False:
read_lines = dut.readlines()
for line in read_lines:
if message in line:
found = True
break
lines = lines + read_lines
await asyncio.sleep(0.1)
except Exception as e:
logger.error(f'{e}!', exc_info=True)
raise e
return found, lines


async def send_cmd_to_iut(shell, dut, cmd, parse=None, wait=True):
found = False
lines = shell.exec_command(cmd)
if wait:
if parse is not None:
found, lines = await wait_for_shell_response(dut, parse)
else:
found = True
else:
found = False
if parse is not None:
for line in lines:
if parse in line:
found = True
break
else:
found = True
logger.info(f'{lines}')
assert found is True
return lines


async def bumble_acl_connect(shell, dut, device, target_address):
connection = None
try:
connection = await device.connect(target_address, transport=BT_BR_EDR_TRANSPORT)
logger.info(f'=== Connected to {connection.peer_address}!')
except Exception as e:
logger.error(f'Fail to connect to {target_address}!')
raise e
return connection


async def bumble_acl_disconnect(shell, dut, device, connection):
await device.disconnect(
connection, reason=HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR
)
found, lines = await wait_for_shell_response(dut, "Disconnected:")
logger.info(f'lines : {lines}')
assert found is True
return found, lines


async def bumble_l2cap_disconnect(shell, dut, incoming_channel, iut_id=L2CAP_CHAN_IUT_ID):
try:
await incoming_channel.disconnect()
except Exception as e:
logger.error('Fail to send l2cap disconnect command!')
raise e
assert incoming_channel.disconnection_result is None

found, _ = await wait_for_shell_response(dut, f"Channel {iut_id} disconnected")
assert found is True
Loading