HCI: 호스트와 컨트롤러 사이의 바이트
휴대폰의 블루투스는 두 칩, 혹은 한 칩 안의 두 세계다. 운영체제 쪽 호스트와 무선 쪽 컨트롤러(1권 1장). 둘 사이에 오가는 모든 것이 HCI 패킷이고, 안드로이드의 btsnoop 로그나 리눅스의 btmon이 보여 주는 것도 이것이다. 이 장은 그 바이트를 읽는 법을 익힌다. 사양 6장 암호화 샘플(이 책 6장)에 실린 실제 HCI 바이트를 해독기로 하나씩 풀어 본다.
- HCI 패킷 다섯 가지의 머리말과 UART(H4) 표시 바이트를 알아보고 바이트열을 패킷으로 나눌 수 있다.
- opcode를 OGF·OCF로 나누고, Command Complete와 Command Status를 구별할 수 있다.
- 명령 크레딧과 데이터 버퍼 크레딧(Number Of Completed Packets)이 왜 필요하고, 모자라면 무엇이 멈추는지 설명할 수 있다.
- 이벤트 마스크와 [v2] 명령으로 6.3에서 비트를 늘린 이유, 흔한 초기화 순서를 안다.
패킷 다섯 가지와 표시 바이트
HCI 패킷은 다섯 가지다. 호스트가 보내는 명령, 컨트롤러가 보내는 이벤트, 양방향의 ACL 데이터(L2CAP이 실린다), 클래식 음성용 동기 데이터, LE 오디오용 ISO 데이터. 모든 값은 작은 바이트부터(리틀 엔디언)다. UART 하나로 다섯 가지를 섞어 보내려면 패킷 앞에 1바이트 표시 바이트를 붙인다. 흔히 “H4”라 부르는 방식이고, btsnoop 로그도 이 표시 바이트를 기록한다.
| 패킷 | 표시 | 머리말 | 최대 길이 |
|---|---|---|---|
| 명령 | 0x01 | opcode 2바이트(OGF 6비트 ‖ OCF 10비트) + 길이 1바이트 | 매개변수 255바이트 |
| ACL 데이터 | 0x02 | 핸들 12비트 + PB 2비트 + BC 2비트, 길이 2바이트 | 컨트롤러 버퍼 크기(LE는 최소 27) |
| 동기 데이터 | 0x03 | 핸들 12비트 + 상태 비트, 길이 1바이트 | — |
| 이벤트 | 0x04 | 이벤트 코드 1바이트 + 길이 1바이트 | 매개변수 255바이트 |
| ISO 데이터 | 0x05 | 핸들 12비트 + PB 2비트 + TS 1비트, 길이 14비트 | ISO 버퍼 크기 |
명령의 opcode는 위 6비트 OGF(명령 묶음)와 아래 10비트 OCF로 나뉜다. LE 명령은 모두 OGF 0x08이라 opcode가 0x20xx이고, 컨트롤러·기저대역 명령(OGF 0x03)은 0x0Cxx, 정보 명령(OGF 0x04)은 0x10xx다. 예를 들어 HCI_Reset = 0x0C03, HCI_LE_Encrypt = 0x2017. LE 이벤트는 모두 코드 0x3E(LE Meta) 하나에 첫 바이트 서브이벤트 코드로 구별한다.
호스트는 사장, 컨트롤러는 무전기를 쥔 비서다. 사장은 정해진 양식의 지시 메모(명령)를 건네고, 비서는 “받았습니다”(Command Status)나 “다 했습니다, 결과는 이렇습니다”(Command Complete)로 답하며, 무전으로 일어난 일은 따로 보고 메모(이벤트)로 올린다. 보낼 편지(ACL 데이터)는 비서의 책상 서랍 칸 수만큼만 맡길 수 있고, 비서가 “두 칸 비었습니다”(Number Of Completed Packets)라고 알려야 더 맡긴다.
UART 표시 바이트(Table 2.1): [Vol 4] Part A §2 · 명령 패킷과 OGF·OCF: [Vol 4] Part E §5.4.1 · ACL: §5.4.2 · 이벤트: §5.4.4 · ISO: §5.4.5 · 리틀 엔디언: §5.2
해독기: 사양 샘플의 HCI 바이트
사양 [Vol 6] Part C §1은 암호화 시작 과정을 HCI 바이트까지 적어 두었다. 아래 해독기의 기본 입력은 그 바이트에 H4 표시와 머리말을 붙인 것이다. 센트럴 호스트가 LE_Enable_Encryption을 보내고, 페리페럴 컨트롤러가 LTK를 요청하고, 페리페럴 호스트가 LTK를 돌려주고, HCI_LE_Encrypt로 세션 키를 확인한 뒤, ACL 데이터를 보낸다. 자기 로그의 바이트를 붙여 넣어도 된다(공백·줄바꿈 무시, # 뒤는 주석).
HCI(H4) 바이트 해독기
샘플 HCI 바이트와 필드 주석: [Vol 6] Part C §1·§1.1 · 명령: LE_Enable_Encryption [Vol 4] Part E §7.8.24 · LE_Long_Term_Key_Request_Reply §7.8.25 · LE_Encrypt §7.8.22 · 이벤트: LE_Long_Term_Key_Request §7.7.65.5 · Command_Complete §7.7.14
크레딧: 컨트롤러가 받을 수 있는 만큼만
컨트롤러의 메모리는 작다. 그래서 HCI는 두 가지 크레딧으로 넘치지 않게 한다.
- 명령 크레딧. 전원을 켜거나 리셋한 뒤 호스트는 한 번에 명령 하나만 보낸다. Command Complete·Status의 Num_HCI_Command_Packets가 “이제 몇 개 더 보내도 된다”를 알려 준다. 0이면 멈추고, 컨트롤러는 opcode 0x0000으로 크레딧만 주는 이벤트를 보낼 수도 있다. 명령은 받은 순서대로 시작하지만 끝나는 순서는 다를 수 있다.
- 데이터 버퍼 크레딧. 호스트는 데이터를 보내기 전에 LE_Read_Buffer_Size로 컨트롤러 버퍼 하나의 크기와 개수를 읽어야 한다. 보낸 만큼 크레딧이 줄고, 컨트롤러가 버퍼를 비우면(전송했거나 버렸을 때) Number_Of_Completed_Packets 이벤트(0x13)로 연결 핸들별 개수를 돌려준다. 이 이벤트가 늦으면 무선은 비어 있는데 호스트가 기다리는 일이 생긴다.
버퍼 크레딧과 처리량
호스트가 연결 하나에 크레딧을 모두 써 버렸는데 그 상대가 멀어져 패킷이 계속 재전송된다. 다른 연결(예: 키보드)의 데이터는?
LE ACL 버퍼는 연결마다 따로인가, 함께 쓰는가?
명령 흐름 제어: [Vol 4] Part E §4.4 · 데이터 흐름 제어·LE_Read_Buffer_Size를 먼저: §4.1·§4.1.1·§7.8.2 · Number_Of_Completed_Packets: §7.7.19 · 연결 끊김 때 버퍼: §4.3
이벤트 마스크, 그리고 비트가 모자랄 때
컨트롤러가 모든 이벤트를 다 올리면 호스트가 모르는 이벤트까지 쏟아진다. 그래서 호스트는 Set_Event_Mask(일반 이벤트)와 LE_Set_Event_Mask(LE 서브이벤트)로 받을 것만 고른다. 비트 하나가 이벤트 하나다. 같은 식으로 컨트롤러는 Read_Local_Supported_Commands로 자기가 아는 명령을 비트로 알려 준다.
문제는 비트 수가 고정이라는 것이다. LE 이벤트 마스크는 8바이트(64개), 지원 명령은 64바이트(512개)였는데, 기능이 늘며 거의 다 찼다. 6.3의 “Running Out of Bits”는 같은 일을 하는 [v2] 명령을 새 opcode로 만들어 길이를 늘렸다. LE_Set_Event_Mask [v2](0x20A4)는 255바이트, Read_Local_Supported_Commands [v2](0x1010)는 251바이트다. 한 명령에 여러 버전이 있으면 컨트롤러는 지원하고 켜져 있는 가장 새 버전의 이벤트를 쓴다.
| 명령 | [v1] | [v2] (6.3) |
|---|---|---|
| LE_Set_Event_Mask | 0x2001, 8옥텟 | 0x20A4, 255옥텟 |
| Read_Local_Supported_Commands | 0x1002, 64옥텟 | 0x1010, 251옥텟 |
흔한 초기화 순서
사양이 순서를 하나로 정하지는 않지만, 공개 스택들은 대체로 이렇게 시작한다. HCI_Reset → Read_Local_Version_Information(코어 버전) → Read_Local_Supported_Commands → LE_Read_Local_Supported_Features → Set_Event_Mask·LE_Set_Event_Mask → LE_Read_Buffer_Size(데이터를 보내기 전 필수) → Read_BD_ADDR. 해독기의 “흔한 초기화 순서”가 이 흐름의 바이트다. 리셋 직후 명령을 하나씩만 보내는 것도 위의 명령 크레딧 규칙 때문이다.
Set_Event_Mask: [Vol 4] Part E §7.3.1 · LE_Set_Event_Mask [v1]·[v2]: §7.8.1 · Read_Local_Supported_Commands [v1]·[v2]: §7.4.2 · 버전 있는 이벤트: §4.8 · 6.3 새 기능: [Vol 1] Part C §17.1 · 버전 정보: [Vol 4] Part E §7.4.1
핵심 정리
- HCI 패킷 다섯 가지: 명령 01, ACL 02, 동기 03, 이벤트 04, ISO 05(UART 표시 바이트). 값은 모두 리틀 엔디언.
- opcode = OGF 6비트 ‖ OCF 10비트. LE 명령은 0x20xx, LE 이벤트는 0x3E + 서브이벤트 코드.
- 명령 크레딧(Num_HCI_Command_Packets), 데이터 크레딧(LE_Read_Buffer_Size → Number_Of_Completed_Packets). 크레딧 보고가 늦으면 무선이 비어도 호스트가 멈춘다.
- LE ACL 버퍼는 연결들이 함께 쓴다. 한 연결이 크레딧을 붙잡으면 다른 연결도 막힌다.
- 6.3 Running Out of Bits: [v2] 명령으로 LE 이벤트 마스크 8 → 255옥텟, 지원 명령 64 → 251옥텟.
확인 퀴즈
opcode 0x0C03의 OGF와 OCF는?
H4 바이트열이 “04 0E 04 01 03 0C 00”이다. 무엇인가?
호스트가 LE 데이터를 보내기 전에 반드시 해야 하는 일은?
LE_Set_Event_Mask [v2]의 마스크 길이는?