From 7dc9c9c732ccb425c4d71f2af1daf677953eacf5 Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Tue, 5 Dec 2023 21:30:05 +0000 Subject: [PATCH 1/3] driver: Implement driver for Intel Ax210 * Implement driver * Add utility script into tools --- bumble/drivers/__init__.py | 8 +- bumble/drivers/intel.py | 452 +++++++++++++++++++++++++++++++++++++ tools/intel_util.py | 144 ++++++++++++ 3 files changed, 602 insertions(+), 2 deletions(-) create mode 100644 bumble/drivers/intel.py create mode 100644 tools/intel_util.py diff --git a/bumble/drivers/__init__.py b/bumble/drivers/__init__.py index b5712e66..59625deb 100644 --- a/bumble/drivers/__init__.py +++ b/bumble/drivers/__init__.py @@ -25,7 +25,7 @@ import platform from typing import Dict, Iterable, Optional, Type, TYPE_CHECKING -from . import rtk +from . import rtk, intel from .common import Driver if TYPE_CHECKING: @@ -45,7 +45,7 @@ async def get_driver_for_host(host: Host) -> Optional[Driver]: found. If a "driver" HCI metadata entry is present, only that driver class will be probed. """ - driver_classes: Dict[str, Type[Driver]] = {"rtk": rtk.Driver} + driver_classes: Dict[str, Type[Driver]] = {"rtk": rtk.Driver, "intel": intel.Driver} probe_list: Iterable[str] if driver_name := host.hci_metadata.get("driver"): # Only probe a single driver @@ -63,6 +63,10 @@ async def get_driver_for_host(host: Host) -> Optional[Driver]: else: logger.debug(f"Skipping unknown driver class: {driver_name}") + if driver := await intel.Driver.for_host(host): + logger.debug("Instantiated Intel driver") + return driver + return None diff --git a/bumble/drivers/intel.py b/bumble/drivers/intel.py new file mode 100644 index 00000000..047f3365 --- /dev/null +++ b/bumble/drivers/intel.py @@ -0,0 +1,452 @@ +# Copyright 2021-2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Support for Intel AX210 controllers. +Based on the Linux kernel implementation. +(see `drivers/bluetooth/btintel.c`) +""" + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +import asyncio +from dataclasses import dataclass +from enum import IntEnum +import logging +import os +import pathlib +import platform +from typing import Optional, Tuple + +from bumble.hci import ( + hci_vendor_command_op_code, # type: ignore + HCI_Command, + HCI_Reset_Command, + STATUS_SPEC, # type: ignore + HCI_SUCCESS, +) +from bumble.drivers import common + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + +# ----------------------------------------------------------------------------- +# Constants +# ----------------------------------------------------------------------------- +CSS_HEADER_OFFSET = 8 +ECDSA_OFFSET = 644 +OPERATIONAL_FW = 0x03 +INTEL_VENDOR_ID = 0x0000 +HW_PLATFORM = 0x37 +MAX_FRAGMENT_PAYLOAD = 252 +RSA_HEADER_LEN = 644 +ECDSA_HEADER_LEN = 320 + +HCI_CMD_WRITE_BOOT_PARAMS = 0xFC0E + +INTEL_FIRMWARE_DIR_ENV = "BUMBLE_INTEL_FIRMWARE_DIR" +LINUX_FIRMWARE_PATH = "/lib/firmware/intel/" + + +class TLV(IntEnum): + CNVI_TOP = 0x10 + CNVR_TOP = 0x11 + CNVI_BT = 0x12 + IMG_TYPE = 0x1C + SBE_TYPE = 0x2F + + +@dataclass +class IntelVersionTLV: + cnvi_top: Optional[bytes] = None + cnvr_top: Optional[bytes] = None + cnvi_bt: Optional[bytes] = None + img_type: Optional[bytes] = None + sbe_type: Optional[bytes] = None + + +class TLVType(IntEnum): + CNVI_TOP = 0x10 + CNVR_TOP = 0x11 + CNVI_BT = 0x12 + IMG_TYPE = 0x1C + SBE_TYPE = 0x2F + + +def parse_intel_version_tlvs(data: bytes, offset: int) -> IntelVersionTLV: + new_offset = len(data) + data = data[offset:] + + if data[0] == 0x37: + raise ValueError("Legacy Intel version tlv") + + tlvs = {} + while len(data) > 0: + try: + tlv_type = data[0] + tlv_length = data[1] + tlv_value = data[2 : 2 + tlv_length] + + try: + tlvs[TLVType(tlv_type).name.lower()] = tlv_value + except ValueError: + pass # unknown tlv + + data = data[2 + tlv_length :] + except IndexError: + logging.error("TLV parse error") + continue + + return new_offset, IntelVersionTLV(**tlvs) # type: ignore + + +# ----------------------------------------------------------------------------- +# HCI Commands +# ----------------------------------------------------------------------------- +HCI_INTEL_READ_VERSION_COMMAND = hci_vendor_command_op_code(0xFC05) # type: ignore +HCI_INTEL_SECURE_SEND_COMMAND = hci_vendor_command_op_code(0xFC09) # type: ignore +HCI_INTEL_RESET_COMMAND = hci_vendor_command_op_code(0xFC01) # type: ignore +HCI_Command.register_commands(globals()) + + +@HCI_Command.command( # type: ignore + fields=[("param", 1)], + return_parameters_fields=[ + ("status", STATUS_SPEC), + ("version", parse_intel_version_tlvs), + ], +) +class HCI_Intel_Read_Version_Command(HCI_Command): + pass + + +@HCI_Command.command( # type: ignore + fields=[("param", "*")], + return_parameters_fields=[ + ("status", STATUS_SPEC), + ], +) +class Hci_Intel_Secure_Send_Command(HCI_Command): + pass + + +# Please see linux/drivers/bluetooth/btintel.c for more informations. +# Intel Reset parameter description: +# reset_type : 0x00 (Soft reset), 0x01 (Hard reset) +# patch_enable: 0x00 (Do not enable), 0x01 (Enable) +# ddc_reload : 0x00 (Do not reload), 0x01 (Reload) +# boot_option: 0x00 (Current image), 0x01 (Specified boot address) +# boot_param: Boot address +@HCI_Command.command( # type: ignore + fields=[ + ("reset_type", 1), + ("patch_enable", 1), + ("ddc_reload", 1), + ("boot_option", 1), + ("boot_param", 4), + ], + return_parameters_fields=[ + ("data", "*"), + ], +) +class Hci_Intel_Reset_Command(HCI_Command): + pass + + +# ----------------------------------------------------------------------------- + + +async def secure_send(host, fragment_type: int, plen: int, param: bytes): + while plen > 0: + fragment_len = MAX_FRAGMENT_PAYLOAD if plen > MAX_FRAGMENT_PAYLOAD else plen + cmd_param = bytes([fragment_type]) + param[:fragment_len] + + # await host.send_command(Hci_Intel_Secure_Send_Command(param=cmd_param)) # type: ignore + host.send_hci_packet(Hci_Intel_Secure_Send_Command(param=cmd_param)) # type: ignore + await asyncio.sleep(0.002) + + plen -= fragment_len + param = param[fragment_len:] + + +async def sfi_ecdsa_header_secure_send(host, fw: bytes): + try: + # Start the firmware download transaction with the Init fragment + # represented by the 128 bytes of CSS header. + await secure_send(host, 0x00, 128, fw[ECDSA_OFFSET:]) + except IOError as e: + logging.error(f"Failed to send fw header: {e}") + return + + try: + # Send the 256 bytes of public key information from the fw + pkey_offset = ECDSA_OFFSET + 128 + await secure_send(host, 0x03, 96, fw[pkey_offset:]) + except IOError as e: + logger.error(f"Failed to send firmware pkey: {e}") + return + + try: + sign_offset = ECDSA_OFFSET + 224 + await secure_send(host, 0x02, 96, fw[sign_offset:]) + except IOError as e: + logger.error(f"Failed to send firmware signature: {e}") + return + + +def fetch_boot_addr(fw: bytes) -> Tuple[int, str]: # tuple[boot_addr, fw_version] + while len(fw) > 0: + length = 3 + fw[2] + op_code = int.from_bytes(fw[:2], byteorder='little') + if op_code == HCI_CMD_WRITE_BOOT_PARAMS: + boot_addr = int.from_bytes(fw[3:7], byteorder='little') + fw_build_num = fw[7] + fw_build_week = fw[8] + fw_build_year = fw[9] + fw_version = f"{fw_build_num}-{fw_build_week}.{fw_build_year}" + return (boot_addr, fw_version) + fw = fw[length:] + return (0, "") # todo: handle error + + +async def download_fw_payload(host, fw: bytes, header_offset: int): + payload_data = fw[header_offset:] # possiblement boot_data est dans le header + frag_len = 0 + + while len(payload_data) > 0: + frag_len += 3 + payload_data[frag_len + 2] + + if frag_len % 4 == 0: + await secure_send(host, 0x01, frag_len, payload_data) + payload_data = payload_data[frag_len:] + frag_len = 0 + + +async def reboot_bootloader(host): # type: ignore + host.send_command_sync( # type: ignore + Hci_Intel_Reset_Command( + reset_type=0x01, + patch_enable=0x01, + ddc_reload=0x01, + boot_option=0x00, + boot_param=0x00000000, + ) + ) + + await asyncio.sleep(200 / 1000) + + +class Driver(common.Driver): + def __init__(self, host, version: IntelVersionTLV, firmware: bytes, fw_name: str): + self.host = host + self.version = version + self.firmware = firmware + self.fw_name = fw_name + + @classmethod + async def for_host(cls, host, force=False): # type: ignore + try: + if not force and not cls.check(host): + return None + + version = await fetch_intel_version(host) # type: ignore + fw, fw_name = prepare_firmware(version) + return cls(host, version, fw, fw_name) + except Exception: + logging.exception("Error preparing the firmware") + return None + + async def init_controller(self): + try: + await download_firmware(self.host, self.version, self.firmware) + await self.host.send_command(HCI_Reset_Command(), check_result=True) + logger.info(f"Firmware loaded, image: {self.fw_name}") + except Exception: + logging.exception("Failed to download the firmware") + return None + + @staticmethod + def check(host): + if host.hci_metadata.get('driver') == 'intel': + # Forced driver + return True + + @staticmethod + def find_binary_path(file_name: str) -> Optional[pathlib.Path]: + # First check if an environment variable is set + if INTEL_FIRMWARE_DIR_ENV in os.environ: + if ( + path := pathlib.Path(os.environ[INTEL_FIRMWARE_DIR_ENV]) / file_name + ).is_file(): + logger.debug(f"{file_name} found in env dir") + return path + + # When the environment variable is set, don't look elsewhere + return None + + # Then, look where the firmware download tool writes by default + if (path := intel_firmware_dir() / file_name).is_file(): + logger.debug(f"{file_name} found in project data dir") + return path + + # Then, look in the package's driver directory + if (path := pathlib.Path(__file__).parent / "intel_fw" / file_name).is_file(): + logger.debug(f"{file_name} found in package dir") + return path + + # On Linux, check the system's FW directory + if ( + platform.system() == "Linux" + and (path := pathlib.Path(LINUX_FIRMWARE_PATH) / file_name).is_file() + ): + logger.debug(f"{file_name} found in Linux system FW dir") + return path + + # Finally look in the current directory + if (path := pathlib.Path.cwd() / file_name).is_file(): + logger.debug(f"{file_name} found in CWD") + return path + + return None + + @classmethod + async def driver_info_for_host(cls, host) -> str: + version = await fetch_intel_version(host) + fw_name = fetch_firmware_name(version) + return fw_name + + +async def fetch_intel_version(host) -> IntelVersionTLV: # type: ignore + host.ready = True # Needed to let the host know the controller is ready. + response = await host.send_command(HCI_Intel_Read_Version_Command(param=0xFF), check_result=True) # type: ignore + if response.return_parameters.status != HCI_SUCCESS: # type: ignore + raise ValueError("This controller is not an intel device") + + intel_version_tlvs = response.return_parameters.version # type: ignore + + assert isinstance(intel_version_tlvs, IntelVersionTLV) + + if intel_version_tlvs.cnvi_bt is None: + raise ValueError("CNVI_BT cannot be None") + + intel_hw_platform = intel_version_tlvs.cnvi_bt[1] + if intel_hw_platform != HW_PLATFORM: + raise ValueError("Unsupported Intel hardware platform") + + intel_hw_variant = ( + int.from_bytes(intel_version_tlvs.cnvi_bt, 'little') & 0x003F0000 + ) >> 16 + if intel_hw_variant in [0x17, 0x18, 0x19, 0x1B, 0x1C]: + return intel_version_tlvs + else: + raise ValueError("Unsupported Intel hardware variant") + + +def intel_cnvx_top_pack_swab(top: int, step: int) -> int: + combined: int = ((top << 4) | step) & 0xFFFF + + return ((combined >> 8) & 0xFF) | ((combined & 0xFF) << 8) + + +def fetch_firmware_name(version: IntelVersionTLV) -> str: + if version.cnvi_top is None or version.cnvr_top is None: + raise ValueError("cnvi_top and cnvr_top cannot be None") + + cnvi_top = int.from_bytes(version.cnvi_top, byteorder='little') + cnvi_top_step = (cnvi_top & 0x0F000000) >> 24 # type: ignore + cnvi_top_type = cnvi_top & 0x00000FFF + + cnvr_top = int.from_bytes(version.cnvr_top, byteorder='little') + cnvr_top_step = (cnvr_top & 0x0F000000) >> 24 + cnvr_top_type = cnvr_top & 0x00000FFF + + upper_name = intel_cnvx_top_pack_swab(cnvi_top_type, cnvi_top_step) + lower_name = intel_cnvx_top_pack_swab(cnvr_top_type, cnvr_top_step) + + return f"ibt-{upper_name:04x}-{lower_name:04x}.sfi" + + +def prepare_firmware(version: IntelVersionTLV) -> Tuple[bytes, str]: + fw_name = fetch_firmware_name(version) + logging.debug(f"Firmware: {fw_name}") + fw_path = Driver.find_binary_path(fw_name) + if not fw_path: + raise FileNotFoundError(f"Firmware file {fw_name} not found") + with open(fw_path, 'rb') as fw_file: + fw = fw_file.read() + if len(fw) < 644: + raise ValueError( + "Firmware size is less then the minimum required size of 644 bytes" + ) + return (fw, fw_name) + + +async def download_firmware(host, version: IntelVersionTLV, fw: bytes): + if version.img_type is None: + raise ValueError("IMG_TYPE cannot be NONE") + + if version.img_type[0] == OPERATIONAL_FW: + raise RuntimeError( + "Device needs to be reset to bootloader. See tools/intel_utils.py --help." + ) + + if version.cnvi_bt is None: + raise ValueError("CNVI Bluetooth verion cannot be None") + hw_variant = (int.from_bytes(version.cnvi_bt, 'little') & 0x003F0000) >> 16 + + if hw_variant >= 0x17: + if fw[ECDSA_OFFSET] != 0x06: + raise ValueError("Invalid CSS header") + css_header_version = int.from_bytes( + fw[ECDSA_OFFSET + CSS_HEADER_OFFSET :][:4], byteorder='little' + ) + if css_header_version != 0x00020000: + raise ValueError("Invalid CSS Header version") + + if version.sbe_type is None: + raise ValueError("SBE_TYPE cannot be none") + sbe_type = int.from_bytes(version.sbe_type, byteorder='little') + (boot_addr, fw_version) = fetch_boot_addr(fw) + logging.info(f"Boot addr: {hex(boot_addr)}") + logging.info(f"Firmware version: {fw_version}") + if sbe_type == 0x01: + await sfi_ecdsa_header_secure_send(host, fw) + await download_fw_payload(host, fw, RSA_HEADER_LEN + ECDSA_HEADER_LEN) + await host.send_command( # type: ignore + Hci_Intel_Reset_Command( + reset_type=0x00, + patch_enable=0x01, + ddc_reload=0x00, + boot_option=0x01, + boot_param=boot_addr, + ) + ) + await asyncio.sleep(2) + else: + raise ValueError("SBE_TYPE != 0x01 is unsupported") + + +def intel_firmware_dir() -> pathlib.Path: + """ + Returns: + A path to a subdir of the project data dir for Realtek firmware. + The directory is created if it doesn't exist. + """ + from bumble.drivers import project_data_dir + + p = project_data_dir() / "firmware" / "intel" + p.mkdir(parents=True, exist_ok=True) + return p diff --git a/tools/intel_util.py b/tools/intel_util.py new file mode 100644 index 00000000..7602ad2e --- /dev/null +++ b/tools/intel_util.py @@ -0,0 +1,144 @@ +# Copyright 2021-2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +import asyncio +import logging +import click +import os + +from bumble import transport as bumbleTransport +from bumble.host import Host +from bumble.drivers import intel + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + + +# ----------------------------------------------------------------------------- +async def do_load(transport: str, force: bool): + async with await bumbleTransport.open_transport(transport) as ( + hci_source, + hci_sink, + ): + # Create a host to communicate with the device + host = Host(hci_source, hci_sink) + + driver = await intel.Driver.for_host(host, force) + if not driver: + print("Firmware already loaded or no supported driver for this device.") + return + + try: + await driver.init_controller() + except Exception as e: + print(f"Unable to load firmware: {e}") + return + + +# ----------------------------------------------------------------------------- +async def do_info(transport: str, force: bool): + async with await bumbleTransport.open_transport(transport) as ( + hci_source, + hci_sink, + ): + # Create a host to communicate with the device + host = Host(hci_source, hci_sink) # type: ignore + if not force and not intel.Driver.check(host): # type: ignore + print("Device not supported by this Intel driver") + return + + version = await intel.fetch_intel_version(host) # type: ignore + if not version: + print("Device not supported by this Intel driver") + return + try: + (fw, fw_name) = intel.prepare_firmware(version) + fw_path = intel.Driver.find_binary_path(fw_name) + (boot_addr, fw_version) = intel.fetch_boot_addr(fw) + print("Driver:") + print(f"Firmware image: {fw_name}") + print(f"Firmware path: {fw_path}") + print(f"Firmware version: {fw_version}") + print(f"Firmware boot address: {hex(boot_addr)}") + except Exception as e: + print( + f"Firmware already loaded or no supported driver for this device: {e}" + ) + + +# ----------------------------------------------------------------------------- +async def do_reboot_bootloader(transport: str, force: bool): + async with await bumbleTransport.open_transport(transport) as ( + hci_source, + hci_sink, + ): + # Create a host to communicate with the device + host = Host(hci_source, hci_sink) # type: ignore + if not force and not intel.Driver.check(host): # type: ignore + print("Device not supported by this Intel driver") + return + + await intel.reboot_bootloader(host) # type: ignore + + +# ----------------------------------------------------------------------------- +@click.group() +def main(): + logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper()) + + +@main.command +@click.argument("transport") +@click.option( + "--force", + is_flag=True, + default=False, + help="Load the firmware even if the device info doesn't match", +) +def load(transport: str, force: bool): + """Load a firmware image into the Bluetooth dongle""" + asyncio.run(do_load(transport, force)) + + +@main.command +@click.argument("transport") +@click.option( + "--force", + is_flag=True, + default=False, + help="Try to get the device info even if the USB info doesn't match", +) +def info(transport: str, force: bool): + """Get the firmware info from a transport""" + asyncio.run(do_info(transport, force)) + + +@main.command +@click.argument("transport") +@click.option( + "--force", is_flag=True, default=False, help="Force the reset in bootloader state" +) +def reboot_bootloader(transport: str, force: bool): + """Reboot the device in bootloader state""" + asyncio.run(do_reboot_bootloader(transport, force)) + + +# ----------------------------------------------------------------------------- +if __name__ == '__main__': + main() From 18560ec56db81a868b48949a952f542bb028d623 Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Tue, 20 Feb 2024 18:57:28 +0000 Subject: [PATCH 2/3] Send Intel VSC (DDC Write Config) after HCI reset Enable host-initiated role-switching after connection. --- bumble/drivers/intel.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bumble/drivers/intel.py b/bumble/drivers/intel.py index 047f3365..aee51e39 100644 --- a/bumble/drivers/intel.py +++ b/bumble/drivers/intel.py @@ -119,8 +119,11 @@ def parse_intel_version_tlvs(data: bytes, offset: int) -> IntelVersionTLV: HCI_INTEL_READ_VERSION_COMMAND = hci_vendor_command_op_code(0xFC05) # type: ignore HCI_INTEL_SECURE_SEND_COMMAND = hci_vendor_command_op_code(0xFC09) # type: ignore HCI_INTEL_RESET_COMMAND = hci_vendor_command_op_code(0xFC01) # type: ignore +HCI_INTEL_DDC_CONFIG_WRITE_COMMAND = hci_vendor_command_op_code(0xFC8B) # type: ignore HCI_Command.register_commands(globals()) +HCI_INTEL_DDC_CONFIG_WRITE_PAYLOAD = [0x03, 0xE4, 0x02, 0x00] + @HCI_Command.command( # type: ignore fields=[("param", 1)], @@ -143,6 +146,16 @@ class Hci_Intel_Secure_Send_Command(HCI_Command): pass +@HCI_Command.command( # type: ignore + fields=[("params", "*")], + return_parameters_fields=[ + ("params", "*"), + ], +) +class Hci_Intel_DDC_Config_Write_Command(HCI_Command): + pass + + # Please see linux/drivers/bluetooth/btintel.c for more informations. # Intel Reset parameter description: # reset_type : 0x00 (Soft reset), 0x01 (Hard reset) @@ -273,6 +286,12 @@ async def init_controller(self): try: await download_firmware(self.host, self.version, self.firmware) await self.host.send_command(HCI_Reset_Command(), check_result=True) + # Enable host-initiated role-switching + await self.host.send_command( + Hci_Intel_DDC_Config_Write_Command( + params=HCI_INTEL_DDC_CONFIG_WRITE_PAYLOAD + ) + ) logger.info(f"Firmware loaded, image: {self.fw_name}") except Exception: logging.exception("Failed to download the firmware") From c4cfbbda8ad69307ffcf5208d8ed351146b7ada3 Mon Sep 17 00:00:00 2001 From: Charlie Boutier Date: Wed, 13 Dec 2023 18:22:53 +0000 Subject: [PATCH 3/3] driver: add documentation for the intel driver --- docs/mkdocs/mkdocs.yml | 1 + docs/mkdocs/src/drivers/index.md | 3 ++- docs/mkdocs/src/drivers/intel.md | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 docs/mkdocs/src/drivers/intel.md diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 6590d124..860b9bd2 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -39,6 +39,7 @@ nav: - Drivers: - drivers/index.md - Realtek: drivers/realtek.md + - Intel: drivers/intel.md - API: - Guide: api/guide.md - Examples: api/examples.md diff --git a/docs/mkdocs/src/drivers/index.md b/docs/mkdocs/src/drivers/index.md index cb0a981e..2fab786a 100644 --- a/docs/mkdocs/src/drivers/index.md +++ b/docs/mkdocs/src/drivers/index.md @@ -16,4 +16,5 @@ USB vendor ID and product ID. Drivers included in the module are: - * [Realtek](realtek.md): Loading of Firmware and Config for Realtek USB dongles. \ No newline at end of file + * [Realtek](realtek.md): Loading of Firmware and Config for Realtek USB dongles. + * [Intel](intel.md): Loading of Firmware for Intel dongles. \ No newline at end of file diff --git a/docs/mkdocs/src/drivers/intel.md b/docs/mkdocs/src/drivers/intel.md new file mode 100644 index 00000000..0b64e25d --- /dev/null +++ b/docs/mkdocs/src/drivers/intel.md @@ -0,0 +1,44 @@ +INTEL DRIVER +============== + +This driver supports loading firmware images for dongles with an Intel chipset. At present, it is specifically designed for the Intel AX210 model. + +The Intel AX210 relies solely on HCI Vendor Commands in bootloader mode, which limits the ability to programmatically identify the correct dongle when connected. To use Bumble with the Intel AX210, it's necessary to explicitly specify the intel driver in the transport command. For example: + +```shell +python3 examples/.py examples/classic1.json tcp-client:[driver=intel]127.0.0.1:6211 +``` + +The driver uses particular Intel HCI vendor commands to ascertain the appropriate firmware image for the dongle in use. If a matching image is found, the driver proceeds to load it. The firmware files are sought in the following sequence: + + * The search begins in the directory specified by the `BUMBLE_INTEL_FIRMWARE_DIR` environment variable, if it has been set. + * Next, the driver looks in the directory `/drivers/intel_fw`, where `` is the directory where the bumble package is installed. + * On Linux, the system's firmware directory at `lib/firmware/intel/` is also checked. + * Lastly, the driver searches in the current directory. + + +Obtaining Firmware Images and Config Data +----------------------------------------- + +To determine the required firmware version for your dongle, you can utilize the utility scripts. Once you know the version needed, the firmware can be sourced directly from the Intel firmware repository in the Linux kernel. You can find the appropriate version at the following link: [Intel Firmware Repository](https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/intel). + +To aid in identifying the correct firmware version, the intel_util utility program can be used. This tool provides commands to retrieve firmware information and load firmware images into the Bluetooth dongle. + +Usage of the intel_util program is as follows: + +``` +Usage: intel_util.py [OPTIONS] COMMAND [ARGS]... + +Options: + --help Show this message and exit. + +Commands: + info Get the firmware info from a transport + load Load a firmware image into the Bluetooth dongle +``` + +An example command to get firmware info: + +```shell +python3 tools/intel_util.py info tcp-client:[driver=intel]127.0.0.1:6211 +``` \ No newline at end of file