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