What is Link Manager Protocol & Host Controller Interface?
hello students welcome to this free bluetooth development course in c, in this lecture we will discuss about Link Manager Protocol & Host Controller Interface In the previous part we covered the Baseband — the raw radio scheduling layer. The Link Manager (LM) sits right above it and acts as the “operations manager” for a Bluetooth connection. It decides how to set up a link, how to keep it alive, how to tear it down, and how to secure it — all by exchanging special control messages called LMP PDUs.
Think of it this way: the Baseband is the road, and the Link Manager is the traffic controller standing in the middle — deciding who goes, how fast, and whether vehicles need an identity check before entering.
Key Terms in This Post
The Link Manager lives inside the controller. It talks to the Baseband below using hardware registers and talks to the Host above through the HCI layer. Two Link Managers — one on each device — communicate peer-to-peer by exchanging LMP PDUs over the air.
Connection control covers everything that happens from the moment you say “connect” to the moment you say “disconnect”. Let’s walk through each piece.
Connection Establishment
When device A wants to connect to device B, the Link Manager on A sends an LMP_host_connection_req PDU. Device B’s LM accepts it. From this point the initiating device becomes the Master and the other becomes the Slave.
If you want it the other way around (your device is the initiator but you still want to be the Slave), the LM can do a role switch right during setup.
You can observe this in real-time using btmon:
## Start btmon before pairing/connecting
$ sudo btmon
## Sample output — LMP connection packets visible in HCI trace
< HCI Command: Create Connection (0x01|0x0005) plen 13
Address: AA:BB:CC:DD:EE:FF (Unknown)
Packet type: 0xcc18
Page scan repetition mode: R1 (0x01)
Page scan mode: Mandatory (0x00)
Clock offset: 0x0000
Role switch: Allow slave (0x01)
> HCI Event: Command Status (0x0f) plen 4
Create Connection (0x01|0x0005) ncmd 1
Status: Success (0x00)
> HCI Event: Connection Complete (0x03) plen 11
Status: Success (0x00)
Handle: 0x0001
Address: AA:BB:CC:DD:EE:FF (Unknown)
Link type: ACL (0x01)
Encryption: Disabled (0x00)
Link Supervision — The Heartbeat Timer
Once connected, both devices run a supervision timer. The Baseband keeps sending POLL/NULL packets as heartbeats. If no heartbeat is received for the negotiated timeout period, the Link Manager declares the link dead and reports a link supervision timeout to the Host.
Established
heartbeats
in T_supervison?
Report to Host
/* Reading link supervision timeout via BlueZ / HCI socket */
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
void read_link_supervision_timeout(int hci_sock, uint16_t handle)
{
struct hci_request req;
read_link_supervision_timeout_rp rp;
read_link_supervision_timeout_cp cp;
memset(&cp, 0, sizeof(cp));
cp.handle = htobs(handle);
memset(&req, 0, sizeof(req));
req.ogf = OGF_HOST_CTL;
req.ocf = OCF_READ_LINK_SUPERVISION_TIMEOUT;
req.cparam = &cp;
req.clen = sizeof(cp);
req.rparam = &rp;
req.rlen = sizeof(rp);
if (hci_send_req(hci_sock, &req, 1000) < 0) {
perror("hci_send_req");
return;
}
/* timeout is in units of 0.625 ms */
printf("Link supervision timeout: %u slots = %.1f ms\n",
btohs(rp.timeout),
btohs(rp.timeout) * 0.625);
}
Adaptive Frequency Hopping (AFH)
Bluetooth hops across 79 channels. If Wi-Fi is active nearby it pollutes several of those channels. AFH lets the Master tell every Slave: “skip these bad channels”. The channel map is a 79-bit bitmap — each bit says whether that channel is good or bad.
/*
* Send SET_AFH (LMP_set_AFH) via HCI to enable AFH on a connection.
* In BlueZ this is handled internally, but the HCI command looks like:
*/
/* HCI_Write_AFH_Channel_Assessment_Mode */
uint8_t mode = 0x01; /* enable automatic channel assessment */
/* 10-byte channel map: 79 bits, bit N = channel N quality */
/* 0xFF = good, 0x00 = bad/unused */
uint8_t channel_map[10] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* channels 0..39 — all good */
0xFF, 0xFF, 0xFF, 0xFF, 0x7F /* channels 40..78 */
/* To mark channels 3-7 bad: clear those bits in byte 0 */
};
/* BlueZ command line: check AFH status */
// $ hciconfig hci0 afh
// $ btmgmt info
Security in Classic Bluetooth operates at the Link Manager layer. It has four stages — and they build on each other. You can’t do encryption without first doing authentication, and you can’t do authentication without a shared key from pairing.
Better UX + ECDH key agreement
Requires authentication first
Requires a shared link key
Creates the shared link key
3.4.2.1 — Pairing (Legacy)
Pairing is a one-time ceremony. The user enters a PIN on both devices (like “1234”). The Link Managers on both sides take that PIN, mix it with a random number and each device’s BD_ADDR, and derive a Link Key. This key is stored persistently — so the next time these same two devices meet, no PIN is needed.
Algorithm
(128-bit, stored)
/* Monitoring pairing in BlueZ using btmon */
$ sudo btmon
/* You will see something like: */
> HCI Event: Link Key Request (0x17) plen 6
Address: AA:BB:CC:DD:EE:FF
/* If key not found, LM initiates pairing: */
< HCI Command: PIN Code Request Reply (0x01|0x000d) plen 23
Address: AA:BB:CC:DD:EE:FF
PIN length: 4
PIN code: 31 32 33 34 "1234"
> HCI Event: Link Key Notification (0x18) plen 23
Address: AA:BB:CC:DD:EE:FF
Link key: a1 b2 c3 d4 e5 f6 ...
Key type: Combination key (0x00)
3.4.2.2 — Authentication (Challenge-Response)
Every time two previously paired devices reconnect, the LM runs a challenge-response check. Device A (the verifier) sends a random number. Device B computes a response using the random number, its own BD_ADDR, and the stored Link Key. Device A does the same calculation independently. If both answers match — identity confirmed.
3.4.2.3 — Encryption
Encryption is optional, but if both sides agree to use it, every byte on the air gets encrypted using a session key derived from the Link Key. The encryption key size can range from 8 to 128 bits — negotiated by the two LMs during connection.
(prerequisite)
key size
Encryption Key
encrypted
/* Enable encryption via HCI — BlueZ example */
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
int enable_encryption(int dd, uint16_t handle)
{
struct hci_request rq;
set_conn_encrypt_cp cp;
set_conn_encrypt_rp rp;
cp.handle = htobs(handle);
cp.encrypt = 0x01; /* 0x01 = enable encryption */
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LINK_CTL;
rq.ocf = OCF_SET_CONN_ENCRYPT;
rq.cparam = &cp;
rq.clen = SET_CONN_ENCRYPT_CP_SIZE;
rq.rparam = &rp;
rq.rlen = SET_CONN_ENCRYPT_RP_SIZE;
rq.event = EVT_ENCRYPT_CHANGE;
if (hci_send_req(dd, &rq, 2000) < 0) {
perror("hci_send_req");
return -1;
}
if (rp.status) {
fprintf(stderr, "Encryption failed: %s\n",
strerror(EIO));
return -1;
}
printf("Encryption enabled on handle 0x%04x\n", handle);
return 0;
}
3.4.2.4 — Secure Simple Pairing (SSP)
Legacy pairing had two problems: the PIN was often “0000” or “1234” (weak), and the user experience was clunky. Secure Simple Pairing, introduced in Bluetooth 2.1 + EDR, fixes both.
SSP uses Elliptic Curve Diffie-Hellman (ECDH) key exchange. The devices generate public/private key pairs and agree on a shared secret without ever transmitting the secret itself. This protects against passive eavesdropping even if someone records the entire pairing exchange.
/* SSP ECDH key exchange — conceptual pseudocode */
/* Each device generates an EC key pair */
ec_key_t my_key = ec_generate_keypair(P192_CURVE);
uint8_t my_pub[48]; /* 24-byte X + 24-byte Y coordinates */
ec_get_public_key(&my_key, my_pub);
/* Exchange public keys over the air (LMP_public_key_exchange_req/res) */
send_lmp_pdu(LMP_IO_CAPABILITY_REQ, io_cap, oob_present, auth_req);
recv_lmp_pdu(LMP_IO_CAPABILITY_RES, &peer_io_cap);
/* Both sides derive the same Diffie-Hellman key */
uint8_t dh_key[24];
ec_compute_dhkey(&my_key, peer_pub, dh_key);
/* dh_key is now identical on both devices without being transmitted */
/* Numeric comparison: derive 6-digit code from dh_key + nonces */
uint32_t code = ssp_numeric_code(dh_key, na, nb);
printf("Confirm code on both devices: %06u\n", code);
/* BlueZ command to see SSP pairing events in btmon */
// $ sudo btmon 2>&1 | grep -i "simple pairing\|io cap\|public key"
The HCI is the boundary line between software (Host) and hardware (Controller). Everything above HCI — L2CAP, RFCOMM, profiles, your application — runs on the main processor. Everything below — Link Manager, Baseband, Radio — runs inside the Bluetooth chip.
This separation is what lets you plug any Bluetooth USB dongle into any PC and have it just work. The Host speaks a standardised HCI language; the Controller understands that same language regardless of who made it.
All communication over HCI happens through four distinct packet types. Each packet starts with a 1-byte indicator that tells the transport layer which type it is.
Tells the chip what to do:
scan, connect, reset, etc.
Asynchronous notifications:
connection complete, error, etc.
asynchronous user data:
L2CAP frames, file transfers, etc.
synchronous audio:
voice calls (SCO/eSCO links).
HCI Command Packet Structure
Every HCI Command carries a 2-byte OpCode which is split into two sub-fields: the OGF (6 bits, group) and the OCF (10 bits, specific command within that group).
For example, HCI_Reset has OGF = 0x03 (Host Control) and OCF = 0x0003. Combining them: OpCode = (0x03 << 10) | 0x003 = 0x0C03.
/*
* HCI Command packet layout in memory:
*
* Byte 0: Packet indicator (0x01 for Command)
* Bytes 1-2: OpCode (OCF in lower 10 bits, OGF in upper 6 bits)
* Byte 3: Parameter Total Length
* Bytes 4+: Parameters
*
* Example: HCI_Reset (no parameters)
* Indicator : 0x01
* OpCode : 0x03 0x0C (little-endian: OCF=0x003, OGF=0x03)
* Param Len : 0x00
*/
/* Send HCI Reset using raw socket */
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
int hci_reset(int dev_id)
{
int dd = hci_open_dev(dev_id);
if (dd < 0) {
perror("hci_open_dev");
return -1;
}
/* hci_send_cmd wraps the packet framing for us */
/* OGF = 0x03 (Host_CTL), OCF = 0x0003 (Reset) */
if (hci_send_cmd(dd, OGF_HOST_CTL, OCF_RESET, 0, NULL) < 0) {
perror("hci_send_cmd");
hci_close_dev(dd);
return -1;
}
/* Wait for Command Complete event */
struct hci_filter nf;
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_CMD_COMPLETE, &nf);
setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));
unsigned char buf[HCI_MAX_EVENT_SIZE];
int len = read(dd, buf, sizeof(buf));
if (len < 0) {
perror("read");
hci_close_dev(dd);
return -1;
}
printf("HCI Reset complete. Status: 0x%02x\n", buf[6]);
hci_close_dev(dd);
return 0;
}
/* Quick command-line way */
// $ hciconfig hci0 reset
// $ btmgmt power off && btmgmt power on
Controller Types Supported Over HCI
The best way to see LMP and HCI in action is to run btmon while connecting a Bluetooth device. Here is what a full connect + authenticate + encrypt sequence looks like in btmon output:
$ sudo btmon
# ── STEP 1: Host sends HCI Create Connection command ──────────────────
< HCI Command: Create Connection (0x01|0x0005)
Address: 11:22:33:44:55:66
# ── STEP 2: Controller acknowledges ────────────────────────────────────
> HCI Event: Command Status (0x0f)
Create Connection (0x01|0x0005) ncmd 1
Status: Success (0x00)
# ── STEP 3: Baseband completes paging, LMP exchange done ───────────────
> HCI Event: Connection Complete (0x03)
Status: Success (0x00)
Handle: 0x0001
Address: 11:22:33:44:55:66
Link type: ACL (0x01)
Encryption: Disabled (0x00) <-- not yet encrypted
# ── STEP 4: LM requests authentication ─────────────────────────────────
> HCI Event: Link Key Request (0x17)
Address: 11:22:33:44:55:66
< HCI Command: Link Key Request Reply (0x01|0x000b)
Address: 11:22:33:44:55:66
Link key: a1 b2 c3 d4 e5 f6 a7 b8 c9 d0 e1 f2 a3 b4 c5 d6
# ── STEP 5: Auth done, now enable encryption ───────────────────────────
< HCI Command: Set Connection Encryption (0x01|0x0013)
Handle: 0x0001
Encryption: Enabled (0x01)
> HCI Event: Encryption Change (0x08)
Status: Success (0x00)
Handle: 0x0001
Encryption: Enabled with E0 (0x01) <-- link is now encrypted
# ── Everything above this line was LMP under the hood ──────────────────
# ── From here on all ACL data is ciphertext ────────────────────────────
/* Useful btmon filters for LMP/HCI study */
# Show only connection events
$ sudo btmon 2>&1 | grep -i "connection\|disconnect\|handle"
# Show only security events
$ sudo btmon 2>&1 | grep -i "encrypt\|link key\|pin\|pairing\|auth"
# Dump to file for later analysis
$ sudo btmon -w /tmp/capture.btsnoop
# Open saved btsnoop in Wireshark
$ wireshark /tmp/capture.btsnoop
# Filter: bthci_cmd || bthci_evt || btl2cap
| LMP PDU / HCI Cmd | Direction | Purpose |
|---|---|---|
| LMP_host_connection_req | Master → Slave (over air) | Initiate ACL connection |
| LMP_detach | Either → Either | Gracefully disconnect |
| LMP_au_rand | Verifier → Claimant | Authentication challenge |
| LMP_sres | Claimant → Verifier | Authentication response |
| LMP_encryption_mode_req | Either | Request encryption enable/disable |
| LMP_set_AFH | Master → Slave | Set AFH channel map |
| HCI_Create_Connection | Host → Controller | Trigger connection to remote device |
| HCI_Disconnect | Host → Controller | Disconnect a specific handle |
| HCI_Set_Connection_Encrypt | Host → Controller | Enable or disable encryption |
| EVT_Connection_Complete | Controller → Host | Notify connection is up |
| EVT_Encryption_Change | Controller → Host | Notify encryption state changed |
| EVT_Link_Key_Notification | Controller → Host | New link key generated, store it |
Summary
The Link Manager is the brains of a Classic Bluetooth connection. It handles everything from dialling up the initial link, keeping it alive with supervision timers, adapting the frequency hopping pattern to avoid interference, and locking the link down with pairing, authentication, and encryption.
The HCI is the clean interface that lets a Host talk to a Controller regardless of the chip vendor. Commands flow down, events flow back up, and ACL/SCO data flows in both directions — all over the same physical channel (USB, UART, or SDIO on embedded boards).
In the next part we will move up the stack into L2CAP — the layer that multiplexes many logical channels over a single ACL link and handles segmentation and reassembly of large payloads.
Next Up: L2CAP — The Multiplexing Layer
We move up one more layer and look at how L2CAP channels, PSMs, MTU negotiation, and segmentation work in Classic Bluetooth.
