# 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 Realtek USB dongles. Based on various online bits of information, including the Linux kernel. (see `drivers/bluetooth/btrtl.c`) """ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- from dataclasses import dataclass import asyncio import enum import logging import math import os import pathlib import struct import weakref from bumble.hci import ( hci_command_op_code, STATUS_SPEC, HCI_SUCCESS, HCI_COMMAND_NAMES, HCI_Command, HCI_Reset_Command, HCI_Read_Local_Version_Information_Command, ) # ----------------------------------------------------------------------------- # Logging # ----------------------------------------------------------------------------- logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- # Constants # ----------------------------------------------------------------------------- RTK_ROM_LMP_8723A = 0x1200 RTK_ROM_LMP_8723B = 0x8723 RTK_ROM_LMP_8821A = 0x8821 RTK_ROM_LMP_8761A = 0x8761 RTK_ROM_LMP_8822B = 0x8822 RTK_ROM_LMP_8852A = 0x8852 RTK_CONFIG_MAGIC = 0x8723AB55 RTK_EPATCH_SIGNATURE = b"Realtech" RTK_FRAGMENT_LENGTH = 252 RTK_FIRMWARE_DIR_ENV = "BUMBLE_RTK_FIRMWARE_DIR" class RtlProjectId(enum.IntEnum): PROJECT_ID_8723A = 0 PROJECT_ID_8723B = 1 PROJECT_ID_8821A = 2 PROJECT_ID_8761A = 3 PROJECT_ID_8822B = 8 PROJECT_ID_8723D = 9 PROJECT_ID_8821C = 10 PROJECT_ID_8822C = 13 PROJECT_ID_8761B = 14 PROJECT_ID_8852A = 18 PROJECT_ID_8852B = 20 PROJECT_ID_8852C = 25 RTK_PROJECT_ID_TO_ROM = { 0: RTK_ROM_LMP_8723A, 1: RTK_ROM_LMP_8723B, 2: RTK_ROM_LMP_8821A, 3: RTK_ROM_LMP_8761A, 8: RTK_ROM_LMP_8822B, 9: RTK_ROM_LMP_8723B, 10: RTK_ROM_LMP_8821A, 13: RTK_ROM_LMP_8822B, 14: RTK_ROM_LMP_8761A, 18: RTK_ROM_LMP_8852A, 20: RTK_ROM_LMP_8852A, 25: RTK_ROM_LMP_8852A, } # List of USB (VendorID, ProductID) for Realtek-based devices. RTK_USB_PRODUCTS = { # Realtek 8723AE (0x0930, 0x021D), (0x13D3, 0x3394), # Realtek 8723BE (0x0489, 0xE085), (0x0489, 0xE08B), (0x04F2, 0xB49F), (0x13D3, 0x3410), (0x13D3, 0x3416), (0x13D3, 0x3459), (0x13D3, 0x3494), # Realtek 8723BU (0x7392, 0xA611), # Realtek 8723DE (0x0BDA, 0xB009), (0x2FF8, 0xB011), # Realtek 8761BUV (0x0B05, 0x190E), (0x0BDA, 0x8771), (0x2230, 0x0016), (0x2357, 0x0604), (0x2550, 0x8761), (0x2B89, 0x8761), (0x7392, 0xC611), # Realtek 8821AE (0x0B05, 0x17DC), (0x13D3, 0x3414), (0x13D3, 0x3458), (0x13D3, 0x3461), (0x13D3, 0x3462), # Realtek 8821CE (0x0BDA, 0xB00C), (0x0BDA, 0xC822), (0x13D3, 0x3529), # Realtek 8822BE (0x0B05, 0x185C), (0x13D3, 0x3526), # Realtek 8822CE (0x04C5, 0x161F), (0x04CA, 0x4005), (0x0B05, 0x18EF), (0x0BDA, 0xB00C), (0x0BDA, 0xC123), (0x0BDA, 0xC822), (0x0CB5, 0xC547), (0x1358, 0xC123), (0x13D3, 0x3548), (0x13D3, 0x3549), (0x13D3, 0x3553), (0x13D3, 0x3555), (0x2FF8, 0x3051), # Realtek 8822CU (0x13D3, 0x3549), # Realtek 8852AE (0x04C5, 0x165C), (0x04CA, 0x4006), (0x0BDA, 0x2852), (0x0BDA, 0x385A), (0x0BDA, 0x4852), (0x0BDA, 0xC852), (0x0CB8, 0xC549), # Realtek 8852BE (0x0BDA, 0x887B), (0x0CB8, 0xC559), (0x13D3, 0x3571), # Realtek 8852CE (0x04C5, 0x1675), (0x04CA, 0x4007), (0x0CB8, 0xC558), (0x13D3, 0x3586), (0x13D3, 0x3587), (0x13D3, 0x3592), } # ----------------------------------------------------------------------------- # HCI Commands # ----------------------------------------------------------------------------- HCI_RTK_READ_ROM_VERSION_COMMAND = hci_command_op_code(0x3F, 0x6D) HCI_COMMAND_NAMES[HCI_RTK_READ_ROM_VERSION_COMMAND] = "HCI_RTK_READ_ROM_VERSION_COMMAND" @HCI_Command.command(return_parameters_fields=[("status", STATUS_SPEC), ("version", 1)]) class HCI_RTK_Read_ROM_Version_Command(HCI_Command): pass HCI_RTK_DOWNLOAD_COMMAND = hci_command_op_code(0x3F, 0x20) HCI_COMMAND_NAMES[HCI_RTK_DOWNLOAD_COMMAND] = "HCI_RTK_DOWNLOAD_COMMAND" @HCI_Command.command( fields=[("index", 1), ("payload", RTK_FRAGMENT_LENGTH)], return_parameters_fields=[("status", STATUS_SPEC), ("index", 1)], ) class HCI_RTK_Download_Command(HCI_Command): pass HCI_RTK_DROP_FIRMWARE_COMMAND = hci_command_op_code(0x3F, 0x66) HCI_COMMAND_NAMES[HCI_RTK_DROP_FIRMWARE_COMMAND] = "HCI_RTK_DROP_FIRMWARE_COMMAND" @HCI_Command.command() class HCI_RTK_Drop_Firmware_Command(HCI_Command): pass # ----------------------------------------------------------------------------- class Firmware: def __init__(self, firmware): extension_sig = bytes([0x51, 0x04, 0xFD, 0x77]) if not firmware.startswith(RTK_EPATCH_SIGNATURE): raise ValueError("Firmware does not start with epatch signature") if not firmware.endswith(extension_sig): raise ValueError("Firmware does not end with extension sig") # The firmware should start with a 14 byte header. epatch_header_size = 14 if len(firmware) < epatch_header_size: raise ValueError("Firmware too short") # Look for the "project ID", starting from the end. offset = len(firmware) - len(extension_sig) project_id = -1 while offset >= epatch_header_size: length, opcode = firmware[offset - 2 : offset] offset -= 2 if opcode == 0xFF: # End break if length == 0: raise ValueError("Invalid 0-length instruction") if opcode == 0 and length == 1: project_id = firmware[offset - 1] break offset -= length if project_id < 0: raise ValueError("Project ID not found") self.project_id = project_id # Read the patch tables info. self.version, num_patches = struct.unpack("... (16 bits each) # ... (16 bits each) # ... (32 bits each) if epatch_header_size + 8 * num_patches > len(firmware): raise ValueError("Firmware too short") chip_id_table_offset = epatch_header_size patch_length_table_offset = chip_id_table_offset + 2 * num_patches patch_offset_table_offset = chip_id_table_offset + 4 * num_patches for patch_index in range(num_patches): chip_id_offset = chip_id_table_offset + 2 * patch_index (chip_id,) = struct.unpack_from(" len(firmware): raise ValueError("Firmware too short") # Get the SVN version for the patch (svn_version,) = struct.unpack_from( "= 0x80: download_index += 1 if fragment_index == fragment_count - 1: download_index |= 0x80 # End marker. fragment_offset = fragment_index * RTK_FRAGMENT_LENGTH fragment = payload[fragment_offset : fragment_offset + RTK_FRAGMENT_LENGTH] logger.debug(f"downloading fragment {fragment_index}") await self.host.send_command( HCI_RTK_Download_Command( index=download_index, payload=fragment, check_result=True ) ) logger.debug("download complete!") # Read the version again response = await self.host.send_command( HCI_RTK_Read_ROM_Version_Command(), check_result=True ) if response.return_parameters.status != HCI_SUCCESS: logger.warning("can't get ROM version") else: rom_version = response.return_parameters.version logger.debug(f"ROM version after download: {rom_version:04X}") async def download_firmware(self): if self.driver_info.rom == RTK_ROM_LMP_8723A: return await self.download_for_rtl8723a() if self.driver_info.rom in ( RTK_ROM_LMP_8723B, RTK_ROM_LMP_8821A, RTK_ROM_LMP_8761A, RTK_ROM_LMP_8822B, RTK_ROM_LMP_8852A, ): return await self.download_for_rtl8723b() raise ValueError("ROM not supported") async def init_controller(self): await self.download_firmware() await self.host.send_command(HCI_Reset_Command(), check_result=True) logger.info(f"loaded FW image {self.driver_info.fw_name}")