Course: Jetson ESP-Hosted Host Code guide · Phase 2 — Embedded Linux
Previous: Lecture 04
Wi-Fi and Bluetooth ride over the same host transport, but Linux does not treat them as the same thing.
For BLE, the important file is:
esp_hosted_ng/host/esp_bt.c
This file integrates with the Linux HCI subsystem instead of cfg80211.
That is the key Embedded Linux lesson:
- one transport
- multiple Linux subsystem personalities
This lecture connects most directly to:
- OS Lecture 1 — Modern OS Architecture & the Linux Kernel
- OS Lecture 17 — Linux Device Driver Model & Device Tree
Lecture 1 is useful here because HCI is another example of the kernel exposing a standard abstraction over unusual hardware. The ESP is still remote and still behind SPI, but once hci_register_dev() succeeds Linux Bluetooth tools can treat it like a normal controller.
Lecture 17 matters because the Bluetooth side also follows the same object-registration pattern as the Wi-Fi side, just in a different subsystem. The important shift is from “a transport packet arrived” to “the kernel accepted that packet as HCI traffic and handed it to the Bluetooth stack.”
The HCI abstraction matters because Bluetooth in Linux is built around a host/controller split. The controller may be on USB, UART, SDIO, or in this case SPI, but once the driver feeds packets into the HCI layer, BlueZ can manage discovery and connections without caring about the underlying bus details.
That is why BLE validation cannot stop at “hci0 exists.” A visible controller only proves registration; scanning successfully proves that HCI commands, events, and data packets are flowing correctly across the transport and back into the Linux Bluetooth stack.
The kernel interfaces to focus on are:
hci_alloc_dev()hci_register_dev()hci_recv_frame(...)- HCI bus typing with
HCI_SPI
Read these functions:
esp_init_bt(...)esp_deinit_bt(...)esp_bt_send_frame(...)
In esp_init_bt(...), the driver:
- allocates an HCI device
- associates it with the adapter
- sets the HCI bus type
- registers the HCI device
On the SPI path, the bus becomes:
HCI_SPI
That is why the validated Jetson logs and tools showed:
hci0Bus: SPI
This is not cosmetic. It means Linux Bluetooth tooling now sees a standard controller interface.
The key block in esp_bt.c is short and very literal:
int esp_init_bt(struct esp_adapter *adapter)
{
...
hdev = hci_alloc_dev();
...
adapter->hcidev = hdev;
hci_set_drvdata(hdev, adapter);
hdev->bus = INVALID_HDEV_BUS;
...
else if (adapter->if_type == ESP_IF_TYPE_SPI)
hdev->bus = HCI_SPI;
hdev->open = esp_bt_open;
hdev->close = esp_bt_close;
hdev->flush = esp_bt_flush;
hdev->send = esp_bt_send_frame;
...
ret = hci_register_dev(hdev);
...
}That is the precise moment when “Bluetooth packets from a remote ESP” become “a normal Linux HCI controller.”
Now return to main.c and re-read part of process_rx_packet(...).
That function checks the incoming payload type.
When the packet is for Bluetooth:
payload_header->if_type == ESP_HCI_IF
the code forwards the data to the HCI stack using:
hci_recv_frame(...)
This is the crucial handoff:
- transport packet from ESP
- becomes HCI traffic in Linux
That is exactly how a custom hardware path gets translated into a standard subsystem view.
The handoff in main.c is direct:
} else if (payload_header->if_type == ESP_HCI_IF) {
if (hdev) {
type = skb->data;
hci_skb_pkt_type(skb) = *type;
skb_pull(skb, 1);
if (hci_recv_frame(hdev, skb)) {
hdev->stat.err_rx++;
} else {
esp_hci_update_rx_counter(hdev, *type, skb->len);
}
}
}The transport does not expose a custom user API for BLE. It converts remote packets into the standard HCI receive path that BlueZ already understands.
In main.c, the capability printout tells you what the ESP claims to support.
On the validated Jetson path, the logs showed:
BT/BLEHCI over SPIBLE only
That last point matters for ESP32-C6:
- this path is BLE-only
- do not expect classic Bluetooth audio profiles from this configuration
That is a practical product constraint, not just a code detail.
The capability reporting comes from main.c and is worth scanning once in real code:
if (cap & ESP_BT_SPI_SUPPORT)
esp_info("\t - HCI over SPI\n");
if ((cap & ESP_BLE_ONLY_SUPPORT) && (cap & ESP_BR_EDR_ONLY_SUPPORT))
esp_info("\t - BT/BLE dual mode\n");
else if (cap & ESP_BLE_ONLY_SUPPORT)
esp_info("\t - BLE only\n");
else if (cap & ESP_BR_EDR_ONLY_SUPPORT)
esp_info("\t - BR EDR only\n");Those log lines are not decoration. They are the driver telling you how the remote firmware wants Linux to treat the radio.
On real hardware, the validation path was:
hciconfig -abluetoothctl listbluetoothctlscan on
And the proof points were:
hci0existed- BlueZ recognized the controller
- the controller bus reported
SPI - BLE scan discovered nearby devices
That means the BLE path was not just registered. It was functional.
This is important:
hci0appearing is goodscan onfinding devices is much better
That is the same lesson you saw with Wi-Fi:
- appearance is not the same as validated behavior
For this Jetson case study, full success means:
- transport up
- boot-up event received
- chipset validated
wlan0created- Wi-Fi scan works
hci0created- BLE scan works
That is the right Embedded Linux mindset:
- validate at the subsystem level the operating system actually cares about
- not just at the signal or module-insert level
Answer these:
- Where is the HCI device allocated?
- Where is the HCI bus type set to SPI?
- Where do incoming Bluetooth packets get handed to Linux?
- Why is
scan onstronger evidence thanbluetoothctl listalone? - Why does this repo make a good Embedded Linux case study for subsystem integration?
Optional extension:
- write a short comparison of:
cfg80211integration for Wi-Fi- HCI integration for BLE
- identify what is shared between them and what is subsystem-specific
Previous: Lecture 04 · Back to: Course hub