bumble 0.0.203__py3-none-any.whl → 0.0.204__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/apps/bench.py +2 -1
- bumble/apps/pair.py +13 -8
- bumble/apps/show.py +6 -6
- bumble/att.py +10 -11
- bumble/drivers/common.py +2 -0
- bumble/drivers/intel.py +593 -24
- bumble/gatt.py +32 -6
- bumble/gatt_server.py +12 -1
- bumble/hci.py +46 -23
- bumble/host.py +4 -1
- bumble/pairing.py +3 -0
- bumble/profiles/aics.py +34 -50
- bumble/profiles/bass.py +4 -7
- bumble/profiles/device_information_service.py +4 -1
- bumble/profiles/heart_rate_service.py +5 -6
- bumble/profiles/vocs.py +330 -0
- bumble/smp.py +7 -2
- bumble/tools/intel_fw_download.py +130 -0
- bumble/tools/intel_util.py +154 -0
- bumble/transport/usb.py +8 -2
- bumble/utils.py +20 -5
- bumble/vendor/android/hci.py +29 -4
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/METADATA +1 -1
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/RECORD +29 -26
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/entry_points.txt +2 -0
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/LICENSE +0 -0
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/WHEEL +0 -0
- {bumble-0.0.203.dist-info → bumble-0.0.204.dist-info}/top_level.txt +0 -0
bumble/drivers/intel.py
CHANGED
|
@@ -11,18 +11,33 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Support for Intel USB controllers.
|
|
16
|
+
Loosely based on the Fuchsia OS implementation.
|
|
17
|
+
"""
|
|
14
18
|
|
|
15
19
|
# -----------------------------------------------------------------------------
|
|
16
20
|
# Imports
|
|
17
21
|
# -----------------------------------------------------------------------------
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
import asyncio
|
|
24
|
+
import collections
|
|
25
|
+
import dataclasses
|
|
18
26
|
import logging
|
|
27
|
+
import os
|
|
28
|
+
import pathlib
|
|
29
|
+
import platform
|
|
30
|
+
import struct
|
|
31
|
+
from typing import Any, Deque, Optional, TYPE_CHECKING
|
|
19
32
|
|
|
33
|
+
from bumble import core
|
|
20
34
|
from bumble.drivers import common
|
|
21
|
-
from bumble
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
from bumble import hci
|
|
36
|
+
from bumble import utils
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from bumble.host import Host
|
|
40
|
+
|
|
26
41
|
|
|
27
42
|
# -----------------------------------------------------------------------------
|
|
28
43
|
# Logging
|
|
@@ -34,39 +49,328 @@ logger = logging.getLogger(__name__)
|
|
|
34
49
|
# -----------------------------------------------------------------------------
|
|
35
50
|
|
|
36
51
|
INTEL_USB_PRODUCTS = {
|
|
37
|
-
#
|
|
38
|
-
(0x8087,
|
|
39
|
-
# Intel BE200
|
|
40
|
-
(0x8087, 0x0036),
|
|
52
|
+
(0x8087, 0x0032), # AX210
|
|
53
|
+
(0x8087, 0x0036), # BE200
|
|
41
54
|
}
|
|
42
55
|
|
|
56
|
+
INTEL_FW_IMAGE_NAMES = [
|
|
57
|
+
"ibt-0040-0041",
|
|
58
|
+
"ibt-0040-1020",
|
|
59
|
+
"ibt-0040-1050",
|
|
60
|
+
"ibt-0040-2120",
|
|
61
|
+
"ibt-0040-4150",
|
|
62
|
+
"ibt-0041-0041",
|
|
63
|
+
"ibt-0180-0041",
|
|
64
|
+
"ibt-0180-1050",
|
|
65
|
+
"ibt-0180-4150",
|
|
66
|
+
"ibt-0291-0291",
|
|
67
|
+
"ibt-1040-0041",
|
|
68
|
+
"ibt-1040-1020",
|
|
69
|
+
"ibt-1040-1050",
|
|
70
|
+
"ibt-1040-2120",
|
|
71
|
+
"ibt-1040-4150",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
INTEL_FIRMWARE_DIR_ENV = "BUMBLE_INTEL_FIRMWARE_DIR"
|
|
75
|
+
INTEL_LINUX_FIRMWARE_DIR = "/lib/firmware/intel"
|
|
76
|
+
|
|
77
|
+
_MAX_FRAGMENT_SIZE = 252
|
|
78
|
+
_POST_RESET_DELAY = 0.2
|
|
79
|
+
|
|
43
80
|
# -----------------------------------------------------------------------------
|
|
44
81
|
# HCI Commands
|
|
45
82
|
# -----------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
|
|
83
|
+
HCI_INTEL_WRITE_DEVICE_CONFIG_COMMAND = hci.hci_vendor_command_op_code(0x008B)
|
|
84
|
+
HCI_INTEL_READ_VERSION_COMMAND = hci.hci_vendor_command_op_code(0x0005)
|
|
85
|
+
HCI_INTEL_RESET_COMMAND = hci.hci_vendor_command_op_code(0x0001)
|
|
86
|
+
HCI_INTEL_SECURE_SEND_COMMAND = hci.hci_vendor_command_op_code(0x0009)
|
|
87
|
+
HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND = hci.hci_vendor_command_op_code(0x000E)
|
|
88
|
+
|
|
89
|
+
hci.HCI_Command.register_commands(globals())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@hci.HCI_Command.command(
|
|
93
|
+
fields=[
|
|
94
|
+
("param0", 1),
|
|
95
|
+
],
|
|
96
|
+
return_parameters_fields=[
|
|
97
|
+
("status", hci.STATUS_SPEC),
|
|
98
|
+
("tlv", "*"),
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
class HCI_Intel_Read_Version_Command(hci.HCI_Command):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@hci.HCI_Command.command(
|
|
106
|
+
fields=[("data_type", 1), ("data", "*")],
|
|
107
|
+
return_parameters_fields=[
|
|
108
|
+
("status", 1),
|
|
109
|
+
],
|
|
110
|
+
)
|
|
111
|
+
class Hci_Intel_Secure_Send_Command(hci.HCI_Command):
|
|
112
|
+
pass
|
|
48
113
|
|
|
49
|
-
|
|
114
|
+
|
|
115
|
+
@hci.HCI_Command.command(
|
|
116
|
+
fields=[
|
|
117
|
+
("reset_type", 1),
|
|
118
|
+
("patch_enable", 1),
|
|
119
|
+
("ddc_reload", 1),
|
|
120
|
+
("boot_option", 1),
|
|
121
|
+
("boot_address", 4),
|
|
122
|
+
],
|
|
123
|
+
return_parameters_fields=[
|
|
124
|
+
("data", "*"),
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
class HCI_Intel_Reset_Command(hci.HCI_Command):
|
|
128
|
+
pass
|
|
50
129
|
|
|
51
130
|
|
|
52
|
-
@HCI_Command.command(
|
|
53
|
-
fields=[("
|
|
131
|
+
@hci.HCI_Command.command(
|
|
132
|
+
fields=[("data", "*")],
|
|
54
133
|
return_parameters_fields=[
|
|
134
|
+
("status", hci.STATUS_SPEC),
|
|
55
135
|
("params", "*"),
|
|
56
136
|
],
|
|
57
137
|
)
|
|
58
|
-
class
|
|
138
|
+
class Hci_Intel_Write_Device_Config_Command(hci.HCI_Command):
|
|
59
139
|
pass
|
|
60
140
|
|
|
61
141
|
|
|
142
|
+
# -----------------------------------------------------------------------------
|
|
143
|
+
# Functions
|
|
144
|
+
# -----------------------------------------------------------------------------
|
|
145
|
+
def intel_firmware_dir() -> pathlib.Path:
|
|
146
|
+
"""
|
|
147
|
+
Returns:
|
|
148
|
+
A path to a subdir of the project data dir for Intel firmware.
|
|
149
|
+
The directory is created if it doesn't exist.
|
|
150
|
+
"""
|
|
151
|
+
from bumble.drivers import project_data_dir
|
|
152
|
+
|
|
153
|
+
p = project_data_dir() / "firmware" / "intel"
|
|
154
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
return p
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _find_binary_path(file_name: str) -> pathlib.Path | None:
|
|
159
|
+
# First check if an environment variable is set
|
|
160
|
+
if INTEL_FIRMWARE_DIR_ENV in os.environ:
|
|
161
|
+
if (
|
|
162
|
+
path := pathlib.Path(os.environ[INTEL_FIRMWARE_DIR_ENV]) / file_name
|
|
163
|
+
).is_file():
|
|
164
|
+
logger.debug(f"{file_name} found in env dir")
|
|
165
|
+
return path
|
|
166
|
+
|
|
167
|
+
# When the environment variable is set, don't look elsewhere
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
# Then, look where the firmware download tool writes by default
|
|
171
|
+
if (path := intel_firmware_dir() / file_name).is_file():
|
|
172
|
+
logger.debug(f"{file_name} found in project data dir")
|
|
173
|
+
return path
|
|
174
|
+
|
|
175
|
+
# Then, look in the package's driver directory
|
|
176
|
+
if (path := pathlib.Path(__file__).parent / "intel_fw" / file_name).is_file():
|
|
177
|
+
logger.debug(f"{file_name} found in package dir")
|
|
178
|
+
return path
|
|
179
|
+
|
|
180
|
+
# On Linux, check the system's FW directory
|
|
181
|
+
if (
|
|
182
|
+
platform.system() == "Linux"
|
|
183
|
+
and (path := pathlib.Path(INTEL_LINUX_FIRMWARE_DIR) / file_name).is_file()
|
|
184
|
+
):
|
|
185
|
+
logger.debug(f"{file_name} found in Linux system FW dir")
|
|
186
|
+
return path
|
|
187
|
+
|
|
188
|
+
# Finally look in the current directory
|
|
189
|
+
if (path := pathlib.Path.cwd() / file_name).is_file():
|
|
190
|
+
logger.debug(f"{file_name} found in CWD")
|
|
191
|
+
return path
|
|
192
|
+
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _parse_tlv(data: bytes) -> list[tuple[ValueType, Any]]:
|
|
197
|
+
result: list[tuple[ValueType, Any]] = []
|
|
198
|
+
while len(data) >= 2:
|
|
199
|
+
value_type = ValueType(data[0])
|
|
200
|
+
value_length = data[1]
|
|
201
|
+
value = data[2 : 2 + value_length]
|
|
202
|
+
typed_value: Any
|
|
203
|
+
|
|
204
|
+
if value_type == ValueType.END:
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
if value_type in (ValueType.CNVI, ValueType.CNVR):
|
|
208
|
+
(v,) = struct.unpack("<I", value)
|
|
209
|
+
typed_value = (
|
|
210
|
+
(((v >> 0) & 0xF) << 12)
|
|
211
|
+
| (((v >> 4) & 0xF) << 0)
|
|
212
|
+
| (((v >> 8) & 0xF) << 4)
|
|
213
|
+
| (((v >> 24) & 0xF) << 8)
|
|
214
|
+
)
|
|
215
|
+
elif value_type == ValueType.HARDWARE_INFO:
|
|
216
|
+
(v,) = struct.unpack("<I", value)
|
|
217
|
+
typed_value = HardwareInfo(
|
|
218
|
+
HardwarePlatform((v >> 8) & 0xFF), HardwareVariant((v >> 16) & 0x3F)
|
|
219
|
+
)
|
|
220
|
+
elif value_type in (
|
|
221
|
+
ValueType.USB_VENDOR_ID,
|
|
222
|
+
ValueType.USB_PRODUCT_ID,
|
|
223
|
+
ValueType.DEVICE_REVISION,
|
|
224
|
+
):
|
|
225
|
+
(typed_value,) = struct.unpack("<H", value)
|
|
226
|
+
elif value_type == ValueType.CURRENT_MODE_OF_OPERATION:
|
|
227
|
+
typed_value = ModeOfOperation(value[0])
|
|
228
|
+
elif value_type in (
|
|
229
|
+
ValueType.BUILD_TYPE,
|
|
230
|
+
ValueType.BUILD_NUMBER,
|
|
231
|
+
ValueType.SECURE_BOOT,
|
|
232
|
+
ValueType.OTP_LOCK,
|
|
233
|
+
ValueType.API_LOCK,
|
|
234
|
+
ValueType.DEBUG_LOCK,
|
|
235
|
+
ValueType.SECURE_BOOT_ENGINE_TYPE,
|
|
236
|
+
):
|
|
237
|
+
typed_value = value[0]
|
|
238
|
+
elif value_type == ValueType.TIMESTAMP:
|
|
239
|
+
typed_value = Timestamp(value[0], value[1])
|
|
240
|
+
elif value_type == ValueType.FIRMWARE_BUILD:
|
|
241
|
+
typed_value = FirmwareBuild(value[0], Timestamp(value[1], value[2]))
|
|
242
|
+
elif value_type == ValueType.BLUETOOTH_ADDRESS:
|
|
243
|
+
typed_value = hci.Address(
|
|
244
|
+
value, address_type=hci.Address.PUBLIC_DEVICE_ADDRESS
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
typed_value = value
|
|
248
|
+
|
|
249
|
+
result.append((value_type, typed_value))
|
|
250
|
+
data = data[2 + value_length :]
|
|
251
|
+
|
|
252
|
+
return result
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# -----------------------------------------------------------------------------
|
|
256
|
+
# Classes
|
|
257
|
+
# -----------------------------------------------------------------------------
|
|
258
|
+
class DriverError(core.BaseBumbleError):
|
|
259
|
+
def __init__(self, message: str) -> None:
|
|
260
|
+
super().__init__(message)
|
|
261
|
+
self.message = message
|
|
262
|
+
|
|
263
|
+
def __str__(self) -> str:
|
|
264
|
+
return f"IntelDriverError({self.message})"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class ValueType(utils.OpenIntEnum):
|
|
268
|
+
END = 0x00
|
|
269
|
+
CNVI = 0x10
|
|
270
|
+
CNVR = 0x11
|
|
271
|
+
HARDWARE_INFO = 0x12
|
|
272
|
+
DEVICE_REVISION = 0x16
|
|
273
|
+
CURRENT_MODE_OF_OPERATION = 0x1C
|
|
274
|
+
USB_VENDOR_ID = 0x17
|
|
275
|
+
USB_PRODUCT_ID = 0x18
|
|
276
|
+
TIMESTAMP = 0x1D
|
|
277
|
+
BUILD_TYPE = 0x1E
|
|
278
|
+
BUILD_NUMBER = 0x1F
|
|
279
|
+
SECURE_BOOT = 0x28
|
|
280
|
+
OTP_LOCK = 0x2A
|
|
281
|
+
API_LOCK = 0x2B
|
|
282
|
+
DEBUG_LOCK = 0x2C
|
|
283
|
+
FIRMWARE_BUILD = 0x2D
|
|
284
|
+
SECURE_BOOT_ENGINE_TYPE = 0x2F
|
|
285
|
+
BLUETOOTH_ADDRESS = 0x30
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class HardwarePlatform(utils.OpenIntEnum):
|
|
289
|
+
INTEL_37 = 0x37
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class HardwareVariant(utils.OpenIntEnum):
|
|
293
|
+
# This is a just a partial list.
|
|
294
|
+
# Add other constants here as new hardware is encountered and tested.
|
|
295
|
+
TYPHOON_PEAK = 0x17
|
|
296
|
+
GALE_PEAK = 0x1C
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@dataclasses.dataclass
|
|
300
|
+
class HardwareInfo:
|
|
301
|
+
platform: HardwarePlatform
|
|
302
|
+
variant: HardwareVariant
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclasses.dataclass
|
|
306
|
+
class Timestamp:
|
|
307
|
+
week: int
|
|
308
|
+
year: int
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclasses.dataclass
|
|
312
|
+
class FirmwareBuild:
|
|
313
|
+
build_number: int
|
|
314
|
+
timestamp: Timestamp
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class ModeOfOperation(utils.OpenIntEnum):
|
|
318
|
+
BOOTLOADER = 0x01
|
|
319
|
+
INTERMEDIATE = 0x02
|
|
320
|
+
OPERATIONAL = 0x03
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class SecureBootEngineType(utils.OpenIntEnum):
|
|
324
|
+
RSA = 0x00
|
|
325
|
+
ECDSA = 0x01
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@dataclasses.dataclass
|
|
329
|
+
class BootParams:
|
|
330
|
+
css_header_offset: int
|
|
331
|
+
css_header_size: int
|
|
332
|
+
pki_offset: int
|
|
333
|
+
pki_size: int
|
|
334
|
+
sig_offset: int
|
|
335
|
+
sig_size: int
|
|
336
|
+
write_offset: int
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
_BOOT_PARAMS = {
|
|
340
|
+
SecureBootEngineType.RSA: BootParams(0, 128, 128, 256, 388, 256, 964),
|
|
341
|
+
SecureBootEngineType.ECDSA: BootParams(644, 128, 772, 96, 868, 96, 964),
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
62
345
|
class Driver(common.Driver):
|
|
63
|
-
def __init__(self, host):
|
|
346
|
+
def __init__(self, host: Host) -> None:
|
|
64
347
|
self.host = host
|
|
348
|
+
self.max_in_flight_firmware_load_commands = 1
|
|
349
|
+
self.pending_firmware_load_commands: Deque[hci.HCI_Command] = (
|
|
350
|
+
collections.deque()
|
|
351
|
+
)
|
|
352
|
+
self.can_send_firmware_load_command = asyncio.Event()
|
|
353
|
+
self.can_send_firmware_load_command.set()
|
|
354
|
+
self.firmware_load_complete = asyncio.Event()
|
|
355
|
+
self.reset_complete = asyncio.Event()
|
|
356
|
+
|
|
357
|
+
# Parse configuration options from the driver name.
|
|
358
|
+
self.ddc_addon: Optional[bytes] = None
|
|
359
|
+
self.ddc_override: Optional[bytes] = None
|
|
360
|
+
driver = host.hci_metadata.get("driver")
|
|
361
|
+
if driver is not None and driver.startswith("intel/"):
|
|
362
|
+
for key, value in [
|
|
363
|
+
key_eq_value.split(":") for key_eq_value in driver[6:].split("+")
|
|
364
|
+
]:
|
|
365
|
+
if key == "ddc_addon":
|
|
366
|
+
self.ddc_addon = bytes.fromhex(value)
|
|
367
|
+
elif key == "ddc_override":
|
|
368
|
+
self.ddc_override = bytes.fromhex(value)
|
|
65
369
|
|
|
66
370
|
@staticmethod
|
|
67
|
-
def check(host):
|
|
371
|
+
def check(host: Host) -> bool:
|
|
68
372
|
driver = host.hci_metadata.get("driver")
|
|
69
|
-
if driver == "intel":
|
|
373
|
+
if driver == "intel" or driver is not None and driver.startswith("intel/"):
|
|
70
374
|
return True
|
|
71
375
|
|
|
72
376
|
vendor_id = host.hci_metadata.get("vendor_id")
|
|
@@ -85,18 +389,283 @@ class Driver(common.Driver):
|
|
|
85
389
|
return True
|
|
86
390
|
|
|
87
391
|
@classmethod
|
|
88
|
-
async def for_host(cls, host, force=False):
|
|
392
|
+
async def for_host(cls, host: Host, force: bool = False):
|
|
89
393
|
# Only instantiate this driver if explicitly selected
|
|
90
394
|
if not force and not cls.check(host):
|
|
91
395
|
return None
|
|
92
396
|
|
|
93
397
|
return cls(host)
|
|
94
398
|
|
|
95
|
-
|
|
399
|
+
def on_packet(self, packet: bytes) -> None:
|
|
400
|
+
"""Handler for event packets that are received from an ACL channel"""
|
|
401
|
+
event = hci.HCI_Event.from_bytes(packet)
|
|
402
|
+
|
|
403
|
+
if not isinstance(event, hci.HCI_Command_Complete_Event):
|
|
404
|
+
self.host.on_hci_event_packet(event)
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
if not event.return_parameters == hci.HCI_SUCCESS:
|
|
408
|
+
raise DriverError("HCI_Command_Complete_Event error")
|
|
409
|
+
|
|
410
|
+
if self.max_in_flight_firmware_load_commands != event.num_hci_command_packets:
|
|
411
|
+
logger.debug(
|
|
412
|
+
"max_in_flight_firmware_load_commands update: "
|
|
413
|
+
f"{event.num_hci_command_packets}"
|
|
414
|
+
)
|
|
415
|
+
self.max_in_flight_firmware_load_commands = event.num_hci_command_packets
|
|
416
|
+
logger.debug(f"event: {event}")
|
|
417
|
+
self.pending_firmware_load_commands.popleft()
|
|
418
|
+
in_flight = len(self.pending_firmware_load_commands)
|
|
419
|
+
logger.debug(f"event received, {in_flight} still in flight")
|
|
420
|
+
if in_flight < self.max_in_flight_firmware_load_commands:
|
|
421
|
+
self.can_send_firmware_load_command.set()
|
|
422
|
+
|
|
423
|
+
async def send_firmware_load_command(self, command: hci.HCI_Command) -> None:
|
|
424
|
+
# Wait until we can send.
|
|
425
|
+
await self.can_send_firmware_load_command.wait()
|
|
426
|
+
|
|
427
|
+
# Send the command and adjust counters.
|
|
428
|
+
self.host.send_hci_packet(command)
|
|
429
|
+
self.pending_firmware_load_commands.append(command)
|
|
430
|
+
in_flight = len(self.pending_firmware_load_commands)
|
|
431
|
+
if in_flight >= self.max_in_flight_firmware_load_commands:
|
|
432
|
+
logger.debug(f"max commands in flight reached [{in_flight}]")
|
|
433
|
+
self.can_send_firmware_load_command.clear()
|
|
434
|
+
|
|
435
|
+
async def send_firmware_data(self, data_type: int, data: bytes) -> None:
|
|
436
|
+
while data:
|
|
437
|
+
fragment_size = min(len(data), _MAX_FRAGMENT_SIZE)
|
|
438
|
+
fragment = data[:fragment_size]
|
|
439
|
+
data = data[fragment_size:]
|
|
440
|
+
|
|
441
|
+
await self.send_firmware_load_command(
|
|
442
|
+
Hci_Intel_Secure_Send_Command(data_type=data_type, data=fragment)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
async def load_firmware(self) -> None:
|
|
96
446
|
self.host.ready = True
|
|
97
|
-
await self.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
447
|
+
device_info = await self.read_device_info()
|
|
448
|
+
logger.debug(
|
|
449
|
+
"device info: \n%s",
|
|
450
|
+
"\n".join(
|
|
451
|
+
[
|
|
452
|
+
f" {value_type.name}: {value}"
|
|
453
|
+
for value_type, value in device_info.items()
|
|
454
|
+
]
|
|
455
|
+
),
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
# Check if the firmware is already loaded.
|
|
459
|
+
if (
|
|
460
|
+
device_info.get(ValueType.CURRENT_MODE_OF_OPERATION)
|
|
461
|
+
== ModeOfOperation.OPERATIONAL
|
|
462
|
+
):
|
|
463
|
+
logger.debug("firmware already loaded")
|
|
464
|
+
return
|
|
465
|
+
|
|
466
|
+
# We only support some platforms and variants.
|
|
467
|
+
hardware_info = device_info.get(ValueType.HARDWARE_INFO)
|
|
468
|
+
if hardware_info is None:
|
|
469
|
+
raise DriverError("hardware info missing")
|
|
470
|
+
if hardware_info.platform != HardwarePlatform.INTEL_37:
|
|
471
|
+
raise DriverError("hardware platform not supported")
|
|
472
|
+
if hardware_info.variant not in (
|
|
473
|
+
HardwareVariant.TYPHOON_PEAK,
|
|
474
|
+
HardwareVariant.GALE_PEAK,
|
|
475
|
+
):
|
|
476
|
+
raise DriverError("hardware variant not supported")
|
|
477
|
+
|
|
478
|
+
# Compute the firmware name.
|
|
479
|
+
if ValueType.CNVI not in device_info or ValueType.CNVR not in device_info:
|
|
480
|
+
raise DriverError("insufficient device info, missing CNVI or CNVR")
|
|
481
|
+
|
|
482
|
+
firmware_base_name = (
|
|
483
|
+
"ibt-"
|
|
484
|
+
f"{device_info[ValueType.CNVI]:04X}-"
|
|
485
|
+
f"{device_info[ValueType.CNVR]:04X}"
|
|
486
|
+
)
|
|
487
|
+
logger.debug(f"FW base name: {firmware_base_name}")
|
|
488
|
+
|
|
489
|
+
firmware_name = f"{firmware_base_name}.sfi"
|
|
490
|
+
firmware_path = _find_binary_path(firmware_name)
|
|
491
|
+
if not firmware_path:
|
|
492
|
+
logger.warning(f"Firmware file {firmware_name} not found")
|
|
493
|
+
logger.warning("See https://google.github.io/bumble/drivers/intel.html")
|
|
494
|
+
return None
|
|
495
|
+
logger.debug(f"loading firmware from {firmware_path}")
|
|
496
|
+
firmware_image = firmware_path.read_bytes()
|
|
497
|
+
|
|
498
|
+
engine_type = device_info.get(ValueType.SECURE_BOOT_ENGINE_TYPE)
|
|
499
|
+
if engine_type is None:
|
|
500
|
+
raise DriverError("secure boot engine type missing")
|
|
501
|
+
if engine_type not in _BOOT_PARAMS:
|
|
502
|
+
raise DriverError("secure boot engine type not supported")
|
|
503
|
+
|
|
504
|
+
boot_params = _BOOT_PARAMS[engine_type]
|
|
505
|
+
if len(firmware_image) < boot_params.write_offset:
|
|
506
|
+
raise DriverError("firmware image too small")
|
|
507
|
+
|
|
508
|
+
# Register to receive vendor events.
|
|
509
|
+
def on_vendor_event(event: hci.HCI_Vendor_Event):
|
|
510
|
+
logger.debug(f"vendor event: {event}")
|
|
511
|
+
event_type = event.parameters[0]
|
|
512
|
+
if event_type == 0x02:
|
|
513
|
+
# Boot event
|
|
514
|
+
logger.debug("boot complete")
|
|
515
|
+
self.reset_complete.set()
|
|
516
|
+
elif event_type == 0x06:
|
|
517
|
+
# Firmware load event
|
|
518
|
+
logger.debug("download complete")
|
|
519
|
+
self.firmware_load_complete.set()
|
|
520
|
+
else:
|
|
521
|
+
logger.debug(f"ignoring vendor event type {event_type}")
|
|
522
|
+
|
|
523
|
+
self.host.on("vendor_event", on_vendor_event)
|
|
524
|
+
|
|
525
|
+
# We need to temporarily intercept packets from the controller,
|
|
526
|
+
# because they are formatted as HCI event packets but are received
|
|
527
|
+
# on the ACL channel, so the host parser would get confused.
|
|
528
|
+
saved_on_packet = self.host.on_packet
|
|
529
|
+
self.host.on_packet = self.on_packet # type: ignore
|
|
530
|
+
self.firmware_load_complete.clear()
|
|
531
|
+
|
|
532
|
+
# Send the CSS header
|
|
533
|
+
data = firmware_image[
|
|
534
|
+
boot_params.css_header_offset : boot_params.css_header_offset
|
|
535
|
+
+ boot_params.css_header_size
|
|
536
|
+
]
|
|
537
|
+
await self.send_firmware_data(0x00, data)
|
|
538
|
+
|
|
539
|
+
# Send the PKI header
|
|
540
|
+
data = firmware_image[
|
|
541
|
+
boot_params.pki_offset : boot_params.pki_offset + boot_params.pki_size
|
|
542
|
+
]
|
|
543
|
+
await self.send_firmware_data(0x03, data)
|
|
544
|
+
|
|
545
|
+
# Send the Signature header
|
|
546
|
+
data = firmware_image[
|
|
547
|
+
boot_params.sig_offset : boot_params.sig_offset + boot_params.sig_size
|
|
548
|
+
]
|
|
549
|
+
await self.send_firmware_data(0x02, data)
|
|
550
|
+
|
|
551
|
+
# Send the rest of the image.
|
|
552
|
+
# The payload consists of command objects, which are sent when they add up
|
|
553
|
+
# to a multiple of 4 bytes.
|
|
554
|
+
boot_address = 0
|
|
555
|
+
offset = boot_params.write_offset
|
|
556
|
+
fragment_size = 0
|
|
557
|
+
while offset + 3 < len(firmware_image):
|
|
558
|
+
(command_opcode,) = struct.unpack_from(
|
|
559
|
+
"<H", firmware_image, offset + fragment_size
|
|
560
|
+
)
|
|
561
|
+
command_size = firmware_image[offset + fragment_size + 2]
|
|
562
|
+
if command_opcode == HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND:
|
|
563
|
+
(boot_address,) = struct.unpack_from(
|
|
564
|
+
"<I", firmware_image, offset + fragment_size + 3
|
|
565
|
+
)
|
|
566
|
+
logger.debug(
|
|
567
|
+
"found HCI_INTEL_WRITE_BOOT_PARAMS_COMMAND, "
|
|
568
|
+
f"boot_address={boot_address}"
|
|
569
|
+
)
|
|
570
|
+
fragment_size += 3 + command_size
|
|
571
|
+
if fragment_size % 4 == 0:
|
|
572
|
+
await self.send_firmware_data(
|
|
573
|
+
0x01, firmware_image[offset : offset + fragment_size]
|
|
574
|
+
)
|
|
575
|
+
logger.debug(f"sent {fragment_size} bytes")
|
|
576
|
+
offset += fragment_size
|
|
577
|
+
fragment_size = 0
|
|
578
|
+
|
|
579
|
+
# Wait for the firmware loading to be complete.
|
|
580
|
+
logger.debug("waiting for firmware to be loaded")
|
|
581
|
+
await self.firmware_load_complete.wait()
|
|
582
|
+
logger.debug("firmware loaded")
|
|
583
|
+
|
|
584
|
+
# Restore the original packet handler.
|
|
585
|
+
self.host.on_packet = saved_on_packet # type: ignore
|
|
586
|
+
|
|
587
|
+
# Reset
|
|
588
|
+
self.reset_complete.clear()
|
|
589
|
+
self.host.send_hci_packet(
|
|
590
|
+
HCI_Intel_Reset_Command(
|
|
591
|
+
reset_type=0x00,
|
|
592
|
+
patch_enable=0x01,
|
|
593
|
+
ddc_reload=0x00,
|
|
594
|
+
boot_option=0x01,
|
|
595
|
+
boot_address=boot_address,
|
|
101
596
|
)
|
|
102
597
|
)
|
|
598
|
+
logger.debug("waiting for reset completion")
|
|
599
|
+
await self.reset_complete.wait()
|
|
600
|
+
logger.debug("reset complete")
|
|
601
|
+
|
|
602
|
+
# Load the device config if there is one.
|
|
603
|
+
if self.ddc_override:
|
|
604
|
+
logger.debug("loading overridden DDC")
|
|
605
|
+
await self.load_device_config(self.ddc_override)
|
|
606
|
+
else:
|
|
607
|
+
ddc_name = f"{firmware_base_name}.ddc"
|
|
608
|
+
ddc_path = _find_binary_path(ddc_name)
|
|
609
|
+
if ddc_path:
|
|
610
|
+
logger.debug(f"loading DDC from {ddc_path}")
|
|
611
|
+
ddc_data = ddc_path.read_bytes()
|
|
612
|
+
await self.load_device_config(ddc_data)
|
|
613
|
+
if self.ddc_addon:
|
|
614
|
+
logger.debug("loading DDC addon")
|
|
615
|
+
await self.load_device_config(self.ddc_addon)
|
|
616
|
+
|
|
617
|
+
async def load_device_config(self, ddc_data: bytes) -> None:
|
|
618
|
+
while ddc_data:
|
|
619
|
+
ddc_len = 1 + ddc_data[0]
|
|
620
|
+
ddc_payload = ddc_data[:ddc_len]
|
|
621
|
+
await self.host.send_command(
|
|
622
|
+
Hci_Intel_Write_Device_Config_Command(data=ddc_payload)
|
|
623
|
+
)
|
|
624
|
+
ddc_data = ddc_data[ddc_len:]
|
|
625
|
+
|
|
626
|
+
async def reboot_bootloader(self) -> None:
|
|
627
|
+
self.host.send_hci_packet(
|
|
628
|
+
HCI_Intel_Reset_Command(
|
|
629
|
+
reset_type=0x01,
|
|
630
|
+
patch_enable=0x01,
|
|
631
|
+
ddc_reload=0x01,
|
|
632
|
+
boot_option=0x00,
|
|
633
|
+
boot_address=0,
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
await asyncio.sleep(_POST_RESET_DELAY)
|
|
637
|
+
|
|
638
|
+
async def read_device_info(self) -> dict[ValueType, Any]:
|
|
639
|
+
self.host.ready = True
|
|
640
|
+
response = await self.host.send_command(hci.HCI_Reset_Command())
|
|
641
|
+
if not (
|
|
642
|
+
isinstance(response, hci.HCI_Command_Complete_Event)
|
|
643
|
+
and response.return_parameters
|
|
644
|
+
in (hci.HCI_UNKNOWN_HCI_COMMAND_ERROR, hci.HCI_SUCCESS)
|
|
645
|
+
):
|
|
646
|
+
# When the controller is in operational mode, the response is a
|
|
647
|
+
# successful response.
|
|
648
|
+
# When the controller is in bootloader mode,
|
|
649
|
+
# HCI_UNKNOWN_HCI_COMMAND_ERROR is the expected response. Anything
|
|
650
|
+
# else is a failure.
|
|
651
|
+
logger.warning(f"unexpected response: {response}")
|
|
652
|
+
raise DriverError("unexpected HCI response")
|
|
653
|
+
|
|
654
|
+
# Read the firmware version.
|
|
655
|
+
response = await self.host.send_command(
|
|
656
|
+
HCI_Intel_Read_Version_Command(param0=0xFF)
|
|
657
|
+
)
|
|
658
|
+
if not isinstance(response, hci.HCI_Command_Complete_Event):
|
|
659
|
+
raise DriverError("unexpected HCI response")
|
|
660
|
+
|
|
661
|
+
if response.return_parameters.status != 0: # type: ignore
|
|
662
|
+
raise DriverError("HCI_Intel_Read_Version_Command error")
|
|
663
|
+
|
|
664
|
+
tlvs = _parse_tlv(response.return_parameters.tlv) # type: ignore
|
|
665
|
+
|
|
666
|
+
# Convert the list to a dict. That's Ok here because we only expect each type
|
|
667
|
+
# to appear just once.
|
|
668
|
+
return dict(tlvs)
|
|
669
|
+
|
|
670
|
+
async def init_controller(self):
|
|
671
|
+
await self.load_firmware()
|