1# Copyright 2021-2023 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# Imports 17# ----------------------------------------------------------------------------- 18import logging 19import pathlib 20import urllib.request 21import urllib.error 22 23import click 24 25from bumble.colors import color 26from bumble.drivers import rtk 27from bumble.tools import rtk_util 28 29 30# ----------------------------------------------------------------------------- 31# Logging 32# ----------------------------------------------------------------------------- 33logger = logging.getLogger(__name__) 34 35 36# ----------------------------------------------------------------------------- 37# Constants 38# ----------------------------------------------------------------------------- 39LINUX_KERNEL_GIT_SOURCE = ( 40 "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/rtl_bt", 41 False, 42) 43REALTEK_OPENSOURCE_SOURCE = ( 44 "https://github.com/Realtek-OpenSource/android_hardware_realtek/raw/rtk1395/bt/rtkbt/Firmware/BT", 45 True, 46) 47LINUX_FROM_SCRATCH_SOURCE = ( 48 "https://anduin.linuxfromscratch.org/sources/linux-firmware/rtl_bt", 49 False, 50) 51 52 53# ----------------------------------------------------------------------------- 54# Functions 55# ----------------------------------------------------------------------------- 56def download_file(base_url, name, remove_suffix): 57 if remove_suffix: 58 name = name.replace(".bin", "") 59 60 url = f"{base_url}/{name}" 61 with urllib.request.urlopen(url) as file: 62 data = file.read() 63 print(f"Downloaded {name}: {len(data)} bytes") 64 return data 65 66 67# ----------------------------------------------------------------------------- 68@click.command 69@click.option( 70 "--output-dir", 71 default="", 72 help="Output directory where the files will be saved. Defaults to the OS-specific" 73 "app data dir, which the driver will check when trying to find firmware", 74 show_default=True, 75) 76@click.option( 77 "--source", 78 type=click.Choice(["linux-kernel", "realtek-opensource", "linux-from-scratch"]), 79 default="linux-kernel", 80 show_default=True, 81) 82@click.option("--single", help="Only download a single image set, by its base name") 83@click.option("--force", is_flag=True, help="Overwrite files if they already exist") 84@click.option("--parse", is_flag=True, help="Parse the FW image after saving") 85def main(output_dir, source, single, force, parse): 86 """Download RTK firmware images and configs.""" 87 88 # Check that the output dir exists 89 if output_dir == '': 90 output_dir = rtk.rtk_firmware_dir() 91 else: 92 output_dir = pathlib.Path(output_dir) 93 if not output_dir.is_dir(): 94 print("Output dir does not exist or is not a directory") 95 return 96 97 base_url, remove_suffix = { 98 "linux-kernel": LINUX_KERNEL_GIT_SOURCE, 99 "realtek-opensource": REALTEK_OPENSOURCE_SOURCE, 100 "linux-from-scratch": LINUX_FROM_SCRATCH_SOURCE, 101 }[source] 102 103 print("Downloading") 104 print(color("FROM:", "green"), base_url) 105 print(color("TO:", "green"), output_dir) 106 107 if single: 108 images = [(f"{single}_fw.bin", f"{single}_config.bin", True)] 109 else: 110 images = [ 111 (driver_info.fw_name, driver_info.config_name, driver_info.config_needed) 112 for driver_info in rtk.Driver.DRIVER_INFOS 113 ] 114 115 for fw_name, config_name, config_needed in images: 116 print(color("---", "yellow")) 117 fw_image_out = output_dir / fw_name 118 if not force and fw_image_out.exists(): 119 print(color(f"{fw_image_out} already exists, skipping", "red")) 120 continue 121 if config_name: 122 config_image_out = output_dir / config_name 123 if not force and config_image_out.exists(): 124 print(color("f{config_out} already exists, skipping", "red")) 125 continue 126 127 try: 128 fw_image = download_file(base_url, fw_name, remove_suffix) 129 except urllib.error.HTTPError as error: 130 print(f"Failed to download {fw_name}: {error}") 131 continue 132 133 config_image = None 134 if config_name: 135 try: 136 config_image = download_file(base_url, config_name, remove_suffix) 137 except urllib.error.HTTPError as error: 138 if config_needed: 139 print(f"Failed to download {config_name}: {error}") 140 continue 141 else: 142 print(f"No config available as {config_name}") 143 144 fw_image_out.write_bytes(fw_image) 145 if parse and config_name: 146 print(color("Parsing:", "cyan"), fw_name) 147 rtk_util.do_parse(fw_image_out) 148 if config_image: 149 config_image_out.write_bytes(config_image) 150 151 152# ----------------------------------------------------------------------------- 153if __name__ == '__main__': 154 main() 155