diff --git a/setup.cfg b/setup.cfg index ac13d9c5..a7a09d63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,11 +24,12 @@ url = https://github.com/google/bumble [options] python_requires = >=3.8 -packages = bumble, bumble.transport, bumble.drivers, bumble.profiles, bumble.apps, bumble.apps.link_relay, bumble.pandora +packages = bumble, bumble.transport, bumble.drivers, bumble.profiles, bumble.apps, bumble.apps.link_relay, bumble.pandora, bumble.tools package_dir = bumble = bumble bumble.apps = apps -include-package-data = True + bumble.tools = tools +include_package_data = True install_requires = aiohttp ~= 3.8; platform_system!='Emscripten' appdirs >= 1.4 @@ -64,6 +65,8 @@ console_scripts = bumble-bench = bumble.apps.bench:main bumble-speaker = bumble.apps.speaker.speaker:main bumble-pandora-server = bumble.apps.pandora_server:main + bumble-rtk-util = bumble.tools.rtk_util:main + bumble-rtk-fw-download = bumble.tools.rtk_fw_download:main [options.package_data] * = py.typed, *.pyi diff --git a/utils/generate_company_id_list.py b/tools/generate_company_id_list.py similarity index 100% rename from utils/generate_company_id_list.py rename to tools/generate_company_id_list.py diff --git a/tools/rtk_fw_download.py b/tools/rtk_fw_download.py new file mode 100644 index 00000000..8f539fb1 --- /dev/null +++ b/tools/rtk_fw_download.py @@ -0,0 +1,153 @@ +# 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 logging +import pathlib +import urllib.request +import urllib.error + +import click + +from bumble.colors import color +from bumble.drivers import rtk +from bumble.tools import rtk_util + + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + + +# ----------------------------------------------------------------------------- +# Constants +# ----------------------------------------------------------------------------- +LINUX_KERNEL_GIT_SOURCE = ( + "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/rtl_bt", + False, +) +REALTEK_OPENSOURCE_SOURCE = ( + "https://github.com/Realtek-OpenSource/android_hardware_realtek/raw/rtk1395/bt/rtkbt/Firmware/BT", + True, +) +LINUX_FROM_SCRATCH_SOURCE = ( + "https://anduin.linuxfromscratch.org/sources/linux-firmware/rtl_bt", + False +) + +# ----------------------------------------------------------------------------- +# Functions +# ----------------------------------------------------------------------------- +def download_file(base_url, name, remove_suffix): + if remove_suffix: + name = name.replace(".bin", "") + + url = f"{base_url}/{name}" + with urllib.request.urlopen(url) as file: + data = file.read() + print(f"Downloaded {name}: {len(data)} bytes") + return data + + +# ----------------------------------------------------------------------------- +@click.command +@click.option( + "--output-dir", + default=".", + help="Output directory where the files will be saved", + show_default=True, +) +@click.option( + "--source", + type=click.Choice(["linux-kernel", "realtek-opensource", "linux-from-scratch"]), + default="linux-kernel", + show_default=True, +) +@click.option("--single", help="Only download a single image set, by its base name") +@click.option("--force", is_flag=True, help="Overwrite files if they already exist") +@click.option("--parse", is_flag=True, help="Parse the FW image after saving") +def main(output_dir, source, single, force, parse): + """Download RTK firmware images and configs.""" + + # Check that the output dir exists + output_dir = pathlib.Path(output_dir) + if not output_dir.is_dir(): + print("Output dir does not exist or is not a directory") + return + + base_url, remove_suffix = { + "linux-kernel": LINUX_KERNEL_GIT_SOURCE, + "realtek-opensource": REALTEK_OPENSOURCE_SOURCE, + "linux-from-scratch": LINUX_FROM_SCRATCH_SOURCE + }[source] + + print("Downloading") + print(color("FROM:", "green"), base_url) + print(color("TO:", "green"), output_dir) + + if single: + images = [(f"{single}_fw.bin", f"{single}_config.bin", True)] + else: + images = [ + ( + driver_info.fw_name, + driver_info.config_name, + driver_info.config_needed + ) + for driver_info in rtk.Driver.DRIVER_INFOS + ] + + for (fw_name, config_name, config_needed) in images: + print(color("---", "yellow")) + fw_image_out = output_dir / fw_name + if not force and fw_image_out.exists(): + print(color(f"{fw_image_out} already exists, skipping", "red")) + continue + if config_name: + config_image_out = output_dir / config_name + if not force and config_image_out.exists(): + print(color("f{config_out} already exists, skipping", "red")) + continue + + try: + fw_image = download_file(base_url, fw_name, remove_suffix) + except urllib.error.HTTPError as error: + print(f"Failed to download {fw_name}: {error}") + continue + + config_image = None + if config_name: + try: + config_image = download_file(base_url, config_name, remove_suffix) + except urllib.error.HTTPError as error: + if config_needed: + print(f"Failed to download {config_name}: {error}") + continue + else: + print(f"No config available as {config_name}") + + fw_image_out.write_bytes(fw_image) + if parse and config_name: + print(color("Parsing:", "cyan"), fw_name) + rtk_util.do_parse(fw_image_out) + if config_image: + config_image_out.write_bytes(config_image) + + +# ----------------------------------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/utils/rtk_util.py b/tools/rtk_util.py similarity index 96% rename from utils/rtk_util.py rename to tools/rtk_util.py index bd4f223f..ad2d656a 100644 --- a/utils/rtk_util.py +++ b/tools/rtk_util.py @@ -1,4 +1,4 @@ -# Copyright 2021-2022 Google LLC +# 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. @@ -36,7 +36,10 @@ def do_parse(firmware_path): with open(firmware_path, 'rb') as firmware_file: firmware_data = firmware_file.read() firmware = rtk.Firmware(firmware_data) - print(f'Firmware: version=0x{firmware.version:08X} project_id=0x{firmware.project_id:04X}') + print( + f"Firmware: version=0x{firmware.version:08X} " + f"project_id=0x{firmware.project_id:04X}" + ) for patch in firmware.patches: print( f" Patch: chip_id=0x{patch[0]:04X}, " @@ -110,7 +113,7 @@ async def do_info(usb_transport, force): # ----------------------------------------------------------------------------- @click.group() def main(): - pass + logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper()) @main.command @@ -155,5 +158,4 @@ def info(usb_transport, force): # ----------------------------------------------------------------------------- if __name__ == '__main__': - logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'INFO').upper()) main()