bumble 0.0.202__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/drivers/common.py CHANGED
@@ -20,6 +20,8 @@ Common types for drivers.
20
20
  # -----------------------------------------------------------------------------
21
21
  import abc
22
22
 
23
+ from bumble import core
24
+
23
25
 
24
26
  # -----------------------------------------------------------------------------
25
27
  # Classes
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.hci import (
22
- hci_vendor_command_op_code, # type: ignore
23
- HCI_Command,
24
- HCI_Reset_Command,
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
- # Intel AX210
38
- (0x8087, 0x0032),
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
- HCI_INTEL_DDC_CONFIG_WRITE_COMMAND = hci_vendor_command_op_code(0xFC8B) # type: ignore
47
- HCI_INTEL_DDC_CONFIG_WRITE_PAYLOAD = [0x03, 0xE4, 0x02, 0x00]
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
- HCI_Command.register_commands(globals())
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( # type: ignore
53
- fields=[("params", "*")],
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 Hci_Intel_DDC_Config_Write_Command(HCI_Command):
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): # type: ignore
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
- async def init_controller(self):
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.host.send_command(HCI_Reset_Command(), check_result=True)
98
- await self.host.send_command(
99
- Hci_Intel_DDC_Config_Write_Command(
100
- params=HCI_INTEL_DDC_CONFIG_WRITE_PAYLOAD
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()