hciconfig是 BlueZ 提供的经典蓝牙适配器配置工具,直接通过 HCI 套接字与内核蓝牙子系统交互,绕过 bluetoothd 守护进程。它的定位类似于网络世界的 ifconfig------操作对象是 HCI 控制器(hci0/hci1...),能完成上电/下电、扫描模式切换、本地名称/设备类设置、LE 地址与广播配置等底层操作。本文基于 BlueZ 5.x 源码,全面整理 hciconfig 实操命令,并深度拆解其源码入口、参数解析、ioctl 系统调用、HCI 指令封装、套接字通信底层实现,最后对比 hciconfig与 bluetoothctl 的功能差异与适配场景。
目录
[一、hciconfig 全套实操命令](#一、hciconfig 全套实操命令)
[四、ioctl 通信层:控制套接字操作](#四、ioctl 通信层:控制套接字操作)
[五、HCI 指令通信层:hci_send_req 机制](#五、HCI 指令通信层:hci_send_req 机制)
[八、hciconfig vs bluetoothctl 功能差异](#八、hciconfig vs bluetoothctl 功能差异)
[十一、核心命令 → 源码函数 → 底层机制速查](#十一、核心命令 → 源码函数 → 底层机制速查)
一、hciconfig 全套实操命令
1.1 基本用法
cpp
# 查看所有适配器(简要)
hciconfig
# 查看所有适配器(详细,含 features/name/class/version)
hciconfig -a
# 查看指定适配器
hciconfig hci0
hciconfig -a hci0
# 帮助
hciconfig -h
典型输出:
cpp
hci0: Type: Primary Bus: USB
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING
RX bytes:1234 acl:0 sco:0 events:56 commands:57 errors:0
TX bytes:4321 acl:0 sco:0 commands:57 errors:0
1.2 适配器 启停与重置
cpp
hciconfig hci0 up # 上电并初始化 HCI 设备
hciconfig hci0 down # 关闭 HCI 设备
hciconfig hci0 reset # 重置(先 down 再 up)
hciconfig hci0 rstat # 重置统计计数器
1.3 扫描模式配置
cpp
hciconfig hci0 piscan # 开启 Page + Inquiry 扫描(可连接 + 可发现)
hciconfig hci0 iscan # 仅开启 Inquiry 扫描(可发现)
hciconfig hci0 pscan # 仅开启 Page 扫描(可连接)
hciconfig hci0 noscan # 关闭所有扫描
1.4 认证与加密
cpp
hciconfig hci0 auth # 启用认证
hciconfig hci0 noauth # 禁用认证
hciconfig hci0 encrypt # 启用加密
hciconfig hci0 noencrypt # 禁用加密
1.5 本地名称与设备类
cpp
# 查看本地名称
hciconfig hci0 name
# 设置本地名称(最长 248 字节)
hciconfig hci0 name "My Bluetooth Device"
# 查看设备类
hciconfig hci0 class
# 输出示例:
# Class: 0x1c010c
# Service Classes: Networking, Object Transfer, Audio
# Device Class: Computer, Laptop
# 设置设备类(3 字节十六进制)
hciconfig hci0 class 0x1c010c
1.6 硬件信息查询
cpp
hciconfig hci0 version # HCI/LMP 版本、制造商
hciconfig hci0 features # LMP 特性页(page 0/1/2...)
hciconfig hci0 commands # 支持的 HCI 命令列表
hciconfig hci0 revision # 硬件修订版本(厂商相关)
hciconfig hci0 lestates # LE 支持的状态组合
version 输出示例:
cpp
hci0: Type: Primary Bus: USB
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
HCI Version: 5.0 (0x9) Revision: 0x100
LMP Version: 5.0 (0x9) Subversion: 0x100
Manufacturer: Cambridge Silicon Radio (10)
1.7 链路参数配置
cpp
# 数据包类型
hciconfig hci0 ptype # 查看
hciconfig hci0 ptype DM1,DH1,DH3 # 设置
# 链路策略
hciconfig hci0 lp # 查看
hciconfig hci0 lp HOLD,SNIFF # 设置
# 链路模式
hciconfig hci0 lm # 查看
hciconfig hci0 lm MASTER,ACCEPT # 设置
# 语音设置
hciconfig hci0 voice # 查看
hciconfig hci0 voice 0x0060 # 设置
# IAC(Inquiry Access Code)
hciconfig hci0 iac # 查看
hciconfig hci0 iac 0x9e8b33 # 设置 GIAC
1.8 查询参数调优
cpp
# 查询发射功率
hciconfig hci0 inqtpl # 查看
hciconfig hci0 inqtpl 10 # 设置
# 查询模式(0=标准, 1=RSSI, =EIR)
hciconfig hci0 inqmode # 查看
hciconfig hci0 inqmode 1 # 设置为 RSSI 模式
# 查询扫描参数(窗口:间隔)
hciconfig hci0 inqparms # 查看
hciconfig hci0 inqparms 18:32 # 设置
# 查询扫描类型(0=标准, 1=交错)
hciconfig hci0 inqtype # 查看
hciconfig hci0 inqtype 1 # 设置
# Page 扫描参数
hciconfig hci0 pageparms # 查看
hciconfig hci0 pageparms 18:32 # 设置
# Page 超时
hciconfig hci0 pageto # 查看
hciconfig hci0 pageto 8192 # 设置(slots)
1.9 高级功能
cpp
# AFH 模式(0=禁用, 1=启用)
hciconfig hci0 afhmode # 查看
hciconfig hci0 afhmode 1 # 启用
# Simple Pairing 模式
hciconfig hci0 sspmode # 查看
hciconfig hci0 sspmode 1 # 启用
# ACL/SCO MTU
hciconfig hci0 aclmtu 800:10 # 设置 ACL MTU:包数
hciconfig hci0 scomtu 64:8 # 设置 SCO MTU:包数
# OOB 数据
hciconfig hci0 oobdata # 读取本地 OOB 数据
# 设备黑名单
hciconfig hci0 block AA:BB:CC:DD:EE:FF # 拉黑设备
hciconfig hci0 unblock AA:BB:CC:DD:EE:FF # 解除拉黑
# 删除链路密钥
hciconfig hci0 delkey AA:BB:CC:DD:EE:FF
1.10 LE 专项配置
cpp
# 设置 LE 随机地址(MAC 修改)
hciconfig hci0 lerandaddr AA:BB:CC:DD:EE:FF
# 开启 LE 广播
hciconfig hci0 leadv # 默认: 可连接非定向广播 (type 0)
hciconfig hci0 leadv 0 # ADV_IND: 可连接非定向
hciconfig hci0 leadv 3 # ADV_NONCONN_IND: 不可连接非定向
# 关闭 LE 广播
hciconfig hci0 noleadv
# 查看 LE 支持的状态
hciconfig hci0 lestates
1.11 实战速查:从零配置一台 BLE 外设
cpp
# 1. 上电
hciconfig hci0 up
# 2. 设置随机地址(修改 MAC)
hciconfig hci0 lerandaddr AA:BB:CC:DD:EE:FF
# 3. 开启 LE 广播
hciconfig hci0 leadv 0
# 4. 验证状态
hciconfig -a hci0
# 5. 停止广播
hciconfig hci0 noleadv
# 6. 下电
hciconfig hci0 down
二、源码总览与工程结构
hciconfig 的全部源码集中在单文件 tools/hciconfig.c(约 2066 行),依赖 BlueZ 的公共库 lib/hci.c(HCI 操作封装)和 lib/hci.h(结构体与 ioctl 定义)。
cpp
tools/hciconfig.c # 全部源码:命令表 + 各 cmd_* 函数 + main()
│
├── lib/hci.h # ioctl 码定义、struct hci_dev_info / hci_dev_req 等
├── lib/hci.c # hci_open_dev / hci_send_req / hci_read_local_version 等
└── lib/bluetooth.h # bdaddr_t、ba2str、str2ba 等基础定义
与 bluetoothctl 不同,hciconfig 不依赖 D-Bus,不连接 bluetoothd,直接通过 socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI) + ioctl() + hci_send_req() 与内核 HCI 子系统交互。
三、程序入口与参数解析
3.1 main() 全貌
入口位于 tools/hciconfig.c第 1999 行:
cpp
int main(int argc, char *argv[])
{
int opt, ctl, i, cmd = 0;
/* 1. 解析全局选项:-a (all), -h (help) */
while ((opt = getopt_long(argc, argv, "ah", main_options, NULL)) != -1) {
switch (opt) {
case 'a':
all = 1; /* 设置全局 all 标志,影响输出详细程度 */
break;
case 'h':
default:
usage();
exit(0);
}
}
argc -= optind;
argv += optind;
optind = 0;
/* 2. 打开 HCI 控制套接字(用于 ioctl 操作) */
if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {
perror("Can't open HCI socket.");
exit(1);
}
/* 3. 无参数 → 列出所有设备 */
if (argc < 1) {
print_dev_list(ctl, 0);
exit(0);
}
/* 4. 解析设备号:从 "hci0" 提取 0 */
di.dev_id = atoi(argv[0] + 3); /* 跳过 "hci" 前缀 */
argc--; argv++;
/* 5. 获取设备信息(ioctl HCIGETDEVINFO) */
if (ioctl(ctl, HCIGETDEVINFO, (void *) &di)) {
perror("Can't get device info");
exit(1);
}
/* 6. 遍历剩余参数,在命令表中查找并执行 */
while (argc > 0) {
for (i = 0; command[i].cmd; i++) {
if (strncmp(command[i].cmd, *argv, strlen(command[i].cmd)))
continue;
/* 有参数的命令消耗下一个 argv */
if (command[i].opt)
argc--, argv++;
command[i].func(ctl, di.dev_id, *argv);
cmd = 1;
break;
}
if (command[i].cmd == 0)
fprintf(stderr, "Warning: unknown command - \"%s\"\n", *argv);
argc--; argv++;
}
/* 7. 无命令 → 打印设备详情 */
if (!cmd)
print_dev_info(ctl, &di);
close(ctl);
return 0;
}
3.2 两套套接字体系
hciconfig 的源码中存在两种套接字使用方式,对应两类操作:
|--------------------|---------------------------------------------|------------------------------|-----------------------------------|
| 方式 | 套接字 | 操作 | 典型命令 |
| 控制套接字 (ctl) | socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI) | ioctl() | up/down/scan/auth/ptype |
| HCI 设备套接字 (dd) | hci_open_dev()→ bind 到具体 hciX | hci_send_req() / hci_read_* | name/class/version/features/leadv |
cpp
/* main() 中创建的是控制套接字 ctl,只用于 ioctl */
ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
/* 各 cmd_* 函数中按需创建 HCI 设备套接字 dd,用于收发 HCI 命令 */
dd = hci_open_dev(hdev); /* 内部: socket + bind(hci_dev=hdev) */
3.3 命令表数据结构
命令表是 hciconfig 的核心数据结构,采用编译期静态初始化:
cpp
static struct {
char *cmd; /* 命令名 */
void (*func)(int ctl, int hdev, char *opt); /* 处理函数 */
char *opt; /* 参数提示(NULL 表示无参数) */
char *doc; /* 描述 */
} command[] = {
{ "up", cmd_up, 0, "Open and initialize HCI device" },
{ "down", cmd_down, 0, "Close HCI device" },
{ "reset", cmd_reset, 0, "Reset HCI device" },
{ "piscan", cmd_scan, 0, "Enable Page and Inquiry scan" },
{ "noscan", cmd_scan, 0, "Disable scan" },
{ "name", cmd_name, "[name]", "Get/Set local name" },
{ "class", cmd_class, "[class]", "Get/Set class of device" },
{ "version", cmd_version, 0, "Display version information" },
{ "features", cmd_features, 0, "Display device features" },
{ "lerandaddr", cmd_le_addr, "<bdaddr>", "Set LE Random Address" },
{ "leadv", cmd_le_adv, "[type]", "Enable LE advertising" },
{ NULL, NULL, 0 } /* 哨兵 */
};
main() 中的查找逻辑是简单的线性匹配:
cpp
for (i = 0; command[i].cmd; i++) {
if (strncmp(command[i].cmd, *argv, strlen(command[i].cmd)))
continue; /* 不匹配,继续 */
/* 匹配成功,执行 */
if (command[i].opt)
argc--, argv++; /* 消耗参数 */
command[i].func(ctl, di.dev_id, *argv);
break;
}
3.4 参数解析流程图
cpp
命令行: hciconfig -a hci0 name "MyBT"
│
├─ getopt_long → all=1
│
├─ socket(AF_BLUETOOTH, ...) → ctl
│
├─ argv[0]="hci0" → di.dev_id = atoi("0") = 0
├─ ioctl(ctl, HCIGETDEVINFO, &di) → 获取设备信息
│
├─ argv[1]="name" → 匹配 command[16] "name"
│ ├─ command[16].opt = "[name]" → 非NULL → 消耗 argv[2]
│ └─ cmd_name(ctl, 0, "MyBT")
│
└─ close(ctl)
四、ioctl 通信层:控制套接字操作
4.1 ioctl 码定义
所有 hciconfig 的 ioctl 码定义在 lib/hci.h:
cpp
#define HCIDEVUP _IOW('H', 201, int)
#define HCIDEVDOWN _IOW('H', 202, int)
#define HCIDEVRESET _IOW('H', 203, int)
#define HCIDEVRESTAT _IOW('H', 204, int)
#define HCIGETDEVLIST _IOR('H', 210, int)
#define HCIGETDEVINFO _IOR('H', 211, int)
#define HCISETSCAN _IOW('H', 221, int)
#define HCISETAUTH _IOW('H', 222, int)
#define HCISETENCRYPT _IOW('H', 223, int)
#define HCISETPTYPE _IOW('H', 224, int)
#define HCISETLINKPOL _IOW('H', 225, int)
#define HCISETLINKMODE _IOW('H', 226, int)
#define HCIBLOCKADDR _IOW('H', 230, int)
这些是标准的 Linux ioctl 码,_IOW 表示写入(用户态→内核),_IOR 表示读取(内核→用户态)。'H' 是蓝牙子系统的魔数。
4.2 设备列表获取:HCIGETDEVLIST
cpp
static void print_dev_list(int ctl, int flags)
{
struct hci_dev_list_req *dl;
struct hci_dev_req *dr;
int i;
/* 分配缓冲区:最多 HCI_MAX_DEV 个设备 */
dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t));
dl->dev_num = HCI_MAX_DEV;
dr = dl->dev_req;
/* ioctl 获取设备列表 */
if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
perror("Can't get device list");
exit(1);
}
/* 遍历每个设备,获取详细信息 */
for (i = 0; i < dl->dev_num; i++) {
di.dev_id = (dr + i)->dev_id;
if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0)
continue;
print_dev_info(ctl, &di);
}
free(dl);
}
相关结构体:
cpp
struct hci_dev_req {
uint16_t dev_id;
uint32_t dev_opt;
};
struct hci_dev_list_req {
uint16_t dev_num;
struct hci_dev_req dev_req[]; /* 柔性数组 */
};
4.3 设备信息获取:HCIGETDEVINFO
cpp
/* main() 中 */
di.dev_id = atoi(argv[0] + 3); /* "hci0" → 0 */
ioctl(ctl, HCIGETDEVINFO, (void *) &di);
struct hci_dev_info 是 hciconfig 最核心的数据结构,一次 ioctl 即可获取控制器的所有静态信息:
cpp
struct hci_dev_info {
uint16_t dev_id; /* 设备 ID */
char name[8]; /* 设备名 "hci0" */
bdaddr_t bdaddr; /* BD 地址(MAC) */
uint32_t flags; /* 状态标志(UP/RUNNING/PSCAN/ISCAN...) */
uint8_t type; /* 类型(高4位: Primary/AMP, 低4位: Bus) */
uint8_t features[8]; /* LMP 特性 page 0 */
uint32_t pkt_type; /* 支持的包类型 */
uint32_t link_policy; /* 链路策略 */
uint32_t link_mode; /* 链路模式 */
uint16_t acl_mtu; /* ACL MTU */
uint16_t acl_pkts; /* ACL 包数 */
uint16_t sco_mtu; /* SCO MTU */
uint16_t sco_pkts; /* SCO 包数 */
struct hci_dev_stats stat;/* 收发统计 */
};
4.4 启停控制:HCIDEVUP / HCIDEVDOWN
cpp
static void cmd_up(int ctl, int hdev, char *opt)
{
if (ioctl(ctl, HCIDEVUP, hdev) < 0) {
if (errno == EALREADY) /* 已经 up,不算错误 */
return;
fprintf(stderr, "Can't init device hci%d: %s (%d)\n",
hdev, strerror(errno), errno);
exit(1);
}
}
static void cmd_down(int ctl, int hdev, char *opt)
{
if (ioctl(ctl, HCIDEVDOWN, hdev) < 0) {
fprintf(stderr, "Can't down device hci%d: %s (%d)\n",
hdev, strerror(errno), errno);
exit(1);
}
}
reset 实现为 down + up 组合:
cpp
static void cmd_reset(int ctl, int hdev, char *opt)
{
cmd_down(ctl, hdev, "down");
cmd_up(ctl, hdev, "up");
}
4.5 扫描模式:HCISETSCAN
cpp
static void cmd_scan(int ctl, int hdev, char *opt)
{
struct hci_dev_req dr;
dr.dev_id = hdev;
dr.dev_opt = SCAN_DISABLED;
if (!strcmp(opt, "iscan"))
dr.dev_opt = SCAN_INQUIRY; /* 0x02 */
else if (!strcmp(opt, "pscan"))
dr.dev_opt = SCAN_PAGE; /* 0x01 */
else if (!strcmp(opt, "piscan"))
dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY; /* 0x03 */
if (ioctl(ctl, HCISETSCAN, (unsigned long) &dr) < 0) {
fprintf(stderr, "Can't set scan mode on hci%d: %s (%d)\n",
hdev, strerror(errno), errno);
exit(1);
}
}
注意 cmd_scan 被四个命令复用:piscan、iscan、pscan、noscan,通过 opt 参数区分具体行为。这是命令表中 auth/noauth、encrypt/noencrypt 同样采用的复用模式。
4.6 ioctl 类命令汇总
|-----------------------------|----------------|--------------|----------------------|
| 命令 | ioctl 码 | 参数结构 | 内核处理 |
| up | HCIDEVUP | dev_id (int) | 开启 HCI 设备,执行初始化序列 |
| down | HCIDEVDOWN | dev_id (int) | 关闭 HCI 设备 |
| rstat | HCIDEVRESTAT | dev_id (int) | 清零收发统计计数器 |
| piscan/iscan/pscan/noscan | HCISETSCAN | hci_dev_req | 设置 Page/Inquiry 扫描使能 |
| auth/noauth | HCISETAUTH | hci_dev_req | 设置认证使能 |
| encrypt/noencrypt | HCISETENCRYPT | hci_dev_req | 设置加密使能 |
| ptype | HCISETPTYPE | hci_dev_req | 设置默认数据包类型 |
| lp | HCISETLINKPOL | hci_dev_req | 设置链路策略 |
| lm | HCISETLINKMODE | hci_dev_req | 设置链路模式 |
| block | HCIBLOCKADDR | bdaddr_t | 添加到拒绝列表 |
| unblock | HCIUNBLOCKADDR | bdaddr_t | 从拒绝列表移除 |
五、HCI 指令通信层:hci_send_req 机制
5.1 两层通信模型

5.2 hci_open_dev() --- 设备套接字创建
lib/hci.c:
cpp
int hci_open_dev(int dev_id)
{
struct sockaddr_hci a;
int dd, err;
if (dev_id < 0) {
errno = ENODEV;
return -1;
}
/* 创建 HCI 原始套接字 */
dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
if (dd < 0)
return dd;
/* 绑定到指定 HCI 设备 */
memset(&a, 0, sizeof(a));
a.hci_family = AF_BLUETOOTH;
a.hci_dev = dev_id; /* 绑定到 hci0/hci1... */
if (bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0)
goto failed;
return dd;
failed:
err = errno;
close(dd);
errno = err;
return -1;
}
与 main() 中的控制套接字不同,这里 bind() 绑定了具体的 hci_dev,使得后续 read()/write() 直接操作该设备的 HCI 数据流。
5.3 hci_send_req() --- 请求-响应核心
lib/hci.c这是 hciconfig 发送 HCI 命令的核心函数:
cpp
int hci_send_req(int dd, struct hci_request *r, int to)
{
unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
uint16_t opcode = htobs(cmd_opcode_pack(r->ogf, r->ocf));
struct hci_filter nf, of;
socklen_t olen;
hci_event_hdr *hdr;
int err, try;
/* 1. 保存旧过滤器,设置新过滤器:只接收目标命令的响应事件 */
olen = sizeof(of);
getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen);
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_CMD_STATUS, &nf);
hci_filter_set_event(EVT_CMD_COMPLETE, &nf);
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
hci_filter_set_event(r->event, &nf);
hci_filter_set_opcode(opcode, &nf);
setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));
/* 2. 发送 HCI 命令包 */
if (hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam) < 0)
goto failed;
/* 3. 轮询等待响应事件(最多重试 10 次) */
try = 10;
while (try--) {
evt_cmd_complete *cc;
evt_cmd_status *cs;
int len;
if (to) {
struct pollfd p;
int n;
p.fd = dd;
p.events = POLLIN;
while ((n = poll(&p, 1, to)) < 0) {
if (errno == EAGAIN || errno == EINTR)
continue;
goto failed;
}
if (!n) { /* 超时 */
errno = ETIMEDOUT;
goto failed;
}
to -= 10;
if (to < 0) to = 0;
}
/* 读取事件包 */
while ((len = read(dd, buf, sizeof(buf))) < 0) {
if (errno == EAGAIN || errno == EINTR)
continue;
goto failed;
}
hdr = (void *) (buf + 1);
ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
len -= (1 + HCI_EVENT_HDR_SIZE);
switch (hdr->evt) {
case EVT_CMD_STATUS:
cs = (void *) ptr;
if (cs->opcode != opcode)
continue;
if (r->event != EVT_CMD_STATUS) {
if (cs->status) {
errno = EIO;
goto failed;
}
break;
}
/* 返回状态 */
if (r->rparam && r->rlen)
memcpy(r->rparam, ptr, r->rlen);
goto done;
case EVT_CMD_COMPLETE:
cc = (void *) ptr;
if (cc->opcode != opcode)
continue;
/* 跳过事件头,拷贝返回参数 */
ptr += EVT_CMD_COMPLETE_SIZE;
len -= EVT_CMD_COMPLETE_SIZE;
if (r->rparam && r->rlen)
memcpy(r->rparam, ptr, r->rlen);
goto done;
}
}
failed:
err = errno;
/* 恢复旧过滤器 */
setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
errno = err;
return -1;
done:
/* 恢复旧过滤器 */
setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
return 0;
}
5.4 hci_request 结构
lib/hci_lib.h:
cpp
struct hci_request {
uint16_t ogf; /* Opcode Group Field(命令组) */
uint16_t ocf; /* Opcode Command Field(命令码) */
int event; /* 期望的响应事件类型 */
void *cparam; /* 命令参数 */
int clen; /* 命令参数长度 */
void *rparam; /* 响应参数缓冲 */
int rlen; /* 响应参数长度 */
};
ogf(Opcode Group Field)和 ocf(Opcode Command Field)组成 16 位 HCI Opcode,是蓝牙规范定义的命令编号。例如:
-
OGF_LE_CTL (0x08)+OCF_LE_SET_RANDOM_ADDRESS (0x0005)→ 设置 LE 随机地址 -
OGF_LE_CTL (0x08)+OCF_LE_SET_ADVERTISING_PARAMETERS (0x0006)→ 设置广播参数 -
OGF_HOST_CTL (0x03)+OCF_WRITE_LOCAL_NAME (0x0013)→ 写本地名称
5.5 hci_send_req 工作时序

六、命令实现详解:两种模式
6.1 模式一:ioctl 直接配置(无需 HCI 命令包)
以 cmd_scan(piscan/iscan/pscan/noscan)为例,通过控制套接字的 ioctl 直接配置内核 HCI Core:
cpp
static void cmd_scan(int ctl, int hdev, char *opt)
{
struct hci_dev_req dr;
dr.dev_id = hdev;
dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY; /* piscan */
/* 直接 ioctl,不发 HCI 命令包 */
ioctl(ctl, HCISETSCAN, (unsigned long) &dr);
}
此类命令的特点:内核 HCI Core 收到 ioctl 后,自行决定是否下发 HCI 命令给控制器。用户态工具只需告诉内核"我要什么",不需要关心 HCI 协议细节。
适用命令:up/down/reset/rstat/piscan/pscan/iscan/noscan/auth/noauth/encrypt/noencrypt/ptype/lp/lm/block/unblock
6.2 模式二:HCI 命令直接收发
以 cmd_le_addr(设置 LE 随机地址)为例,通过设备套接字直接发送 HCI 命令包:
cpp
static void cmd_le_addr(int ctl, int hdev, char *opt)
{
struct hci_request rq;
le_set_random_address_cp cp; /* 命令参数结构 */
uint8_t status; /* 响应参数 */
int dd;
/* 1. 打开设备套接字 */
dd = hci_open_dev(hdev);
/* 2. 填充命令参数 */
memset(&cp, 0, sizeof(cp));
str2ba(opt, &cp.bdaddr); /* "AA:BB:CC:DD:EE:FF" → bdaddr_t */
/* 3. 构造 hci_request */
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LE_CTL; /* LE 控制命令组 */
rq.ocf = OCF_LE_SET_RANDOM_ADDRESS; /* 设置随机地址 */
rq.cparam = &cp; /* 命令参数 */
rq.clen = LE_SET_RANDOM_ADDRESS_CP_SIZE;/* 参数长度 */
rq.rparam = &status; /* 响应缓冲 */
rq.rlen = 1; /* 响应长度(仅 status 字节) */
/* 4. 发送请求并等待响应 */
ret = hci_send_req(dd, &rq, 1000); /* 超时 1000ms */
if (status || ret < 0) {
fprintf(stderr, "Can't set random address for hci%d: %s (%d)\n",
hdev, strerror(errno), errno);
}
/* 5. 关闭设备套接字 */
hci_close_dev(dd);
}
6.3 LE 广播配置:多命令组合
cmd_le_adv 演示了多步 HCI 命令组合:
cpp
static void cmd_le_adv(int ctl, int hdev, char *opt)
{
struct hci_request rq;
le_set_advertise_enable_cp advertise_cp;
le_set_advertising_parameters_cp adv_params_cp;
uint8_t status;
int dd;
dd = hci_open_dev(hdev);
/* 第一步:设置广播参数 */
memset(&adv_params_cp, 0, sizeof(adv_params_cp));
adv_params_cp.min_interval = htobs(0x0800); /* 1.28s */
adv_params_cp.max_interval = htobs(0x0800);
if (opt)
adv_params_cp.advtype = atoi(opt); /* 广播类型 */
adv_params_cp.chan_map = 7; /* 三个广播信道全开 */
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LE_CTL;
rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
rq.cparam = &adv_params_cp;
rq.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
rq.rparam = &status;
rq.rlen = 1;
hci_send_req(dd, &rq, 1000);
/* 第二步:使能广播 */
memset(&advertise_cp, 0, sizeof(advertise_cp));
advertise_cp.enable = 0x01;
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LE_CTL;
rq.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
rq.cparam = &advertise_cp;
rq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
rq.rparam = &status;
rq.rlen = 1;
hci_send_req(dd, &rq, 1000);
hci_close_dev(dd);
}
6.4 封装函数:hci_write_local_name 等
部分命令(name/class/version/features/sspmode)通过 lib/hci.c 提供的封装函数实现,本质上仍是 hci_send_req 的简化:
cpp
/* lib/hci.c --- 封装函数内部实现 */
int hci_write_local_name(int dd, const char *name, int to)
{
change_local_name_cp cp;
memset(&cp, 0, sizeof(cp));
strncpy(cp.name, name, sizeof(cp.name));
/* 内部调用 hci_send_req */
struct hci_request rq = {
.ogf = OGF_HOST_CTL,
.ocf = OCF_WRITE_LOCAL_NAME,
.cparam = &cp,
.clen = CHANGE_LOCAL_NAME_CP_SIZE,
.rparam = NULL,
.rlen = 0,
};
return hci_send_req(dd, &rq, to);
}
cmd_name 调用封装函数:
cpp
static void cmd_name(int ctl, int hdev, char *opt)
{
int dd = hci_open_dev(hdev);
if (opt) {
/* 设置名称 */
hci_write_local_name(dd, opt, 2000);
} else {
/* 读取名称 */
char name[249];
hci_read_local_name(dd, sizeof(name), name, 1000);
printf("\tName: '%s'\n", name);
}
hci_close_dev(dd);
}
6.5 两种模式对比
|----------|--------------------|------------------------------------|
| 特性 | ioctl 模式 | hci_send_req 模式 |
| 套接字 | 控制套接字 ctl(不绑定设备) | 设备套接字 dd(绑定具体 hciX) |
| 通信方式 | ioctl() 系统调用 | write() + read() |
| 协议层级 | 内核 HCI Core 级 | HCI 命令/事件级(直达控制器) |
| 命令封装 | struct hci_dev_req | struct hci_request + HCI Opcode |
| 响应处理 | ioctl 返回值 | 解析 EVT_CMD_COMPLETE/EVT_CMD_STATUS |
| 超时控制 | 无(同步返回) | 有(poll 超时) |
| 典型命令 | up/down/scan/auth | name/class/version/features/leadv |
| 适用场景 | 设备级控制 | 需要读控制器寄存器/特性的操作 |
七、设备信息打印与状态解析
7.1 print_dev_info() --- 完整信息输出
cpp
static void print_dev_info(int ctl, struct hci_dev_info *di)
{
struct hci_dev_stats *st = &di->stat;
char *str;
print_dev_hdr(di);
/* 状态标志(UP/RUNNING/PSCAN/ISCAN/AUTH/ENCRYPT...) */
str = hci_dflagstostr(di->flags);
printf("\t%s\n", str);
bt_free(str);
/* 收发统计 */
printf("\tRX bytes:%d acl:%d sco:%d events:%d errors:%d\n",
st->byte_rx, st->acl_rx, st->sco_rx, st->evt_rx, st->err_rx);
printf("\tTX bytes:%d acl:%d sco:%d commands:%d errors:%d\n",
st->byte_tx, st->acl_tx, st->sco_tx, st->cmd_tx, st->err_tx);
/* -a 详细模式:额外打印 features/ptype/lp/lm/name/class/version */
if (all && !hci_test_bit(HCI_RAW, &di->flags)) {
print_dev_features(di, 0);
if (((di->type & 0x30) >> 4) == HCI_PRIMARY) {
print_pkt_type(di);
print_link_policy(di);
print_link_mode(di);
if (hci_test_bit(HCI_UP, &di->flags)) {
cmd_name(ctl, di->dev_id, NULL); /* 读取名称 */
cmd_class(ctl, di->dev_id, NULL); /* 读取设备类 */
}
}
if (hci_test_bit(HCI_UP, &di->flags))
cmd_version(ctl, di->dev_id, NULL); /* 读取版本 */
}
}
7.2 print_dev_hdr() --- 头部输出
cpp
static void print_dev_hdr(struct hci_dev_info *di)
{
static int hdr = -1;
char addr[18];
/* 同一设备只打印一次头部 */
if (hdr == di->dev_id)
return;
hdr = di->dev_id;
ba2str(&di->bdaddr, addr);
printf("%s:\tType: %s Bus: %s\n", di->name,
hci_typetostr((di->type & 0x30) >> 4), /* Primary / AMP */
hci_bustostr(di->type & 0x0f)); /* USB / UART / SDIO... */
printf("\tBD Address: %s ACL MTU: %d:%d SCO MTU: %d:%d\n",
addr, di->acl_mtu, di->acl_pkts, di->sco_mtu, di->sco_pkts);
}
7.3 设备类型字段解析
cpp
di->type 是一个 uint8_t,编码了两部分信息:
bit 4-5: 控制器类型
0x00 → HCI_PRIMARY(主控制器,BR/EDR + LE)
0x10 → HCI_AMP(AMP 控制器)
bit 0-3: 总线类型
0x00 → HCI_VIRTUAL
0x01 → HCI_USB
0x02 → HCI_PCCARD
0x03 → HCI_UART
0x04 → HCI_RS232
0x05 → HCI_PCI
0x06 → HCI_SDIO
八、hciconfig vs bluetoothctl 功能差异
8.1 架构对比

8.2 功能对比表
|-------------------|---------------------------------------|-------------------------------------|
| 维度 | hciconfig | bluetoothctl |
| 通信方式 | socket + ioctl + hci_send_req | D-Bus → bluetoothd |
| 依赖 bluetoothd | 不依赖 | 强依赖 |
| 权限 | root(CAP_NET_ADMIN) | root 或 policykit 授权 |
| 操作层级 | HCI 控制器级 | Profile/Service 级 |
| 设备管理 | 仅控制器(hciX) | 控制器 + 设备 + GATT + Profile |
| 配对/连接 | 不支持 | 完整支持 |
| GATT 读写 | 不支持 | 完整支持 |
| Agent 交互 | 不支持 | 完整支持 |
| 广播配置 | 基础(leadv/noleadv) | 完整(UUID/Service/Manufacturer/Data) |
| MAC 修改 | 支持(lerandaddr) | 支持(lerandaddr) |
| 硬件调试 | 强(features/commands/version/revision) | 弱(仅 show) |
| 扫描参数 | 基础(piscan/iscan/pscan) | 丰富(UUID/RSSI/Pathloss/Transport 过滤) |
| 运行模式 | 单次执行 | 交互式 shell + 非交互 |
| 维护状态 | 已废弃(维护中,不新增功能) | 活跃开发 |
8.3 使用场景选择

九、常见报错与解决方案
9.1 套接字创建失败
cpp
$ hciconfig
Can't open HCI socket.: Address family not supported by protocol
根因:内核未启用蓝牙支持。
解决:
cpp
# 检查内核配置
zcat /proc/config.gz | grep CONFIG_BT
# 需要 CONFIG_BT=y 或 m
# 加载蓝牙模块
sudo modprobe bluetooth
sudo modprobe hci_uart # UART 接口
sudo modprobe btusb # USB 接口
9.2 设备初始化失败
cpp
$ hciconfig hci0 up
Can't init device hci0: Operation not possible due to RF-kill (132)
根因:蓝牙被 rfkill 软开关禁用。
解决:
cpp
# 查看 rfkill 状态
rfkill list
# 解除蓝牙软阻止
sudo rfkill unblock bluetooth
9.3 权限不足
cpp
$ hciconfig hci0 up
Can't init device hci0: Operation not permitted (1)
根因:非 root 用户,缺少 CAP_NET_ADMIN。
解决:
cpp
sudo hciconfig hci0 up
# 或设置 capability
sudo setcap cap_net_admin+ep /usr/bin/hciconfig
9.4 设备被占用
cpp
$ hciconfig hci0 down
Can't down device hci0: Device or resource busy (16)
根因:bluetoothd 正在使用设备,或有活跃连接。
解决:
cpp
# 停止 bluetoothd
sudo systemctl stop bluetooth
# 再操作
sudo hciconfig hci0 down
9.5 HCI 命令超时
cpp
$ hciconfig hci0 version
Can't read version info hci0: Connection timed out (110)
根因:控制器未响应 HCI 命令(固件异常/USB 接触不良/电源问题)。
解决:
cpp
# 1. 重置设备
sudo hciconfig hci0 reset
# 2. 物理重插 USB 蓝牙适配器
# 3. 检查 dmesg
dmesg | tail -50
# 4. 重新加载驱动
sudo rmmod btusb && sudo modprobe btusb
9.6 设置名称/类无效
cpp
$ hciconfig hci0 name "Test"
# 设置成功,但 hciconfig hci0 name 显示旧名称
根因:bluetoothd 会覆盖 hciconfig 的设置(bluetoothd 管理名称和设备类)。
解决:停止 bluetoothd 后再操作,或使用 bluetoothctl 的 system-alias / advertise 命令。
十、嵌入式调试技巧
10.1 无 bluetoothd 环境下使用 hciconfig
在嵌入式设备上,常需要在没有 bluetoothd 的环境下进行蓝牙调试:
cpp
# 1. 确保内核蓝牙模块已加载
lsmod | grep bluetooth
# 2. 加载 HCI 驱动
# UART 蓝牙模块
sudo hciattach /dev/ttyS1 texas 115200
# 或
sudo hciattach /dev/ttyS1 bcm43xx 921600
# 3. 上电
hciconfig hci0 up
# 4. 配置基本参数
hciconfig hci0 piscan
hciconfig hci0 name "Embedded-BT"
hciconfig hci0 class 0x1c010c
# 5. 验证
hciconfig -a hci0
10.2 硬件能力探测脚本
cpp
#!/bin/bash
# hw_probe.sh --- 蓝牙硬件能力探测
DEV=${1:-hci0}
echo "=== Device Info ==="
hciconfig -a $DEV | head -10
echo "=== Version ==="
hciconfig $DEV version
echo "=== Features ==="
hciconfig $DEV features
echo "=== Supported Commands ==="
hciconfig $DEV commands
echo "=== LE States ==="
hciconfig $DEV lestates
echo "=== SSP Mode ==="
hciconfig $DEV sspmode
echo "=== Revision ==="
hciconfig $DEV revision
10.3 btmon 配合抓包
cpp
# 启动 btmon 监控 HCI 流量
sudo btmon &
# 执行 hciconfig 命令
hciconfig hci0 leadv 0
hciconfig hci0 version
# btmon 输出示例
< HCI Command: LE Set Advertising Parameters (0x08|0x0006) plen 15
> HCI Event: Command Complete (0x0e) plen 4
LE Set Advertising Parameters (0x08|0x0006) ncmd 1
Status: Success (0x00)
10.4 strace 追踪系统调用
cpp
# 追踪 ioctl 和 socket 调用
sudo strace -e trace=socket,ioctl,read,write,close \
-f hciconfig hci0 version
# 关键输出:
# socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI) = 3 # 控制套接字
# ioctl(3, HCIGETDEVINFO, ...) = 0 # 获取设备信息
# socket(AF_BLUETOOTH, SOCK_RAW|SOCK_CLOEXEC, BTPROTO_HCI) = 4 # 设备套接字
# bind(4, {sa_family=AF_BLUETOOTH, ...}, 6) = 0 # 绑定 hci0
# getsockopt(4, SOL_HCI, HCI_FILTER, ...) = 0 # 获取旧过滤器
# setsockopt(4, SOL_HCI, HCI_FILTER, ...) = 0 # 设置新过滤器
# write(4, "\1\4\10", 3) = 3 # 发送 HCI 命令
# poll([{fd=4, events=POLLIN}], 1, 1000) = 1 # 等待响应
# read(4, "\4\16\4\1\1\10", 258) = 6 # 读取事件
10.5 直接读取 /sys 信息
无需 hciconfig,直接从 sysfs 读取部分信息:
cpp
# 设备地址
cat /sys/class/bluetooth/hci0/address
# 设备类型
cat /sys/class/bluetooth/hci0/type
# 设备名
cat /sys/class/bluetooth/hci0/name
# 电源状态
cat /sys/class/bluetooth/hci0/power/control
# 厂商信息
cat /sys/class/bluetooth/hci0/device/manufacturer
cat /sys/class/bluetooth/hci0/device/idVendor
cat /sys/class/bluetooth/hci0/device/idProduct
十一、核心命令 → 源码函数 → 底层机制速查
|-----------------------------|------------------|-----------------------------------------------------------------|-------------|
| hciconfig 命令 | 源码函数 | 底层机制 | 通信层级 |
| up | cmd_up | ioctl(ctl, HCIDEVUP, hdev) | 内核 HCI Core |
| down | cmd_down | ioctl(ctl, HCIDEVDOWN, hdev) | 内核 HCI Core |
| reset | cmd_reset | HCIDEVDOWN + HCIDEVUP | 内核 HCI Core |
| rstat | cmd_rstat | ioctl(ctl, HCIDEVRESTAT, hdev) | 内核 HCI Core |
| piscan/iscan/pscan/noscan | cmd_scan | ioctl(ctl, HCISETSCAN, &dr) | 内核 HCI Core |
| auth/noauth | cmd_auth | ioctl(ctl, HCISETAUTH, &dr) | 内核 HCI Core |
| encrypt/noencrypt | cmd_encrypt | ioctl(ctl, HCISETENCRYPT, &dr) | 内核 HCI Core |
| ptype | cmd_ptype | ioctl(ctl, HCISETPTYPE, &dr) | 内核 HCI Core |
| lp | cmd_lp | ioctl(ctl, HCISETLINKPOL, &dr) | 内核 HCI Core |
| lm | cmd_lm | ioctl(ctl, HCISETLINKMODE, &dr) | 内核 HCI Core |
| block | cmd_block | ioctl(dd, HCIBLOCKADDR, &bdaddr) | 内核 HCI Core |
| name | cmd_name | hci_write_local_name / hci_read_local_name | HCI 命令 |
| class | cmd_class | hci_write_class_of_dev / hci_read_class_of_dev | HCI 命令 |
| version | cmd_version | hci_read_local_version | HCI 命令 |
| features | cmd_features | hci_read_local_ext_features | HCI 命令 |
| commands | cmd_commands | hci_read_local_commands | HCI 命令 |
| sspmode | cmd_ssp_mode | hci_write_simple_pairing_mode | HCI 命令 |
| voice | cmd_voice | hci_write_voice_setting / hci_read_voice_setting | HCI 命令 |
| lerandaddr | cmd_le_addr | hci_send_req(OGF_LE_CTL, OCF_LE_SET_RANDOM_ADDRESS) | HCI 命令 |
| leadv | cmd_le_adv | hci_send_req(OCF_LE_SET_ADV_PARAMS) + OCF_LE_SET_ADV_ENABLE | HCI 命令 |
| noleadv | cmd_no_le_adv | hci_send_req(OCF_LE_SET_ADV_ENABLE, enable=0) | HCI 命令 |
| lestates | cmd_le_states | hci_read_le_host_supported | HCI 命令 |
| revision | cmd_revision | hci_send_req(厂商相关) | HCI 命令 |
| 无命令(默认) | print_dev_info | ioctl(ctl, HCIGETDEVINFO) | 内核 HCI Core |
| 无参数(列表) | print_dev_list | ioctl(ctl, HCIGETDEVLIST) + HCIGETDEVINFO | 内核 HCI Core |
十二、总结
12.1 核心设计要点
-
单文件实现:hciconfig 全部逻辑集中在 tools/hciconfig.c 一个文件中,结构清晰,约 2000 行代码。
-
命令表驱动:所有命令通过静态 command\[\] 数组注册,main() 线性查找匹配后调用对应 cmd_* 函数。这种模式简单、高效,适合命令数量适中的工具。
-
双层通信:
-
ioctl 层(控制套接字):设备级控制(up/down/scan/auth),直接配置内核 HCI Core 状态。
-
HCI 命令层(设备套接字):控制器级操作(name/class/version/features),通过 hci_send_req 收发 HCI 命令/事件包。
-
无 D-Bus 依赖:hciconfig 直接与内核交互,不需要 bluetoothd 守护进程,适合嵌入式环境和底层调试。
-
函数复用:cmd_scan 被 piscan/iscan/pscan/noscan 复用,cmd_auth 被 auth/noauth 复用,通过 opt 参数区分行为。
12.2 与 bluetoothctl 的定位差异
-
hciconfig = 蓝牙世界的 ifconfig:操作网络接口(HCI 设备)的底层工具,关注硬件级配置。
-
bluetoothctl = 蓝牙世界的网络管理器:通过 bluetoothd 管理完整的蓝牙服务栈,关注应用级交互。
两者互补而非替代:hciconfig 适合硬件调试和无守护进程环境,bluetoothctl 适合日常使用和完整蓝牙功能。
12.3 维护状态说明
hciconfig 在 BlueZ 中标记为 deprecated(不推荐使用),但并未移除,仍然维护。官方推荐使用 bluetoothctl 和 hcitool(也已 deprecated)的替代品。但在以下场景中 hciconfig 仍不可替代:
无 bluetoothd 的嵌入式环境
需要直接修改 MAC 地址(lerandaddr)
需要查询底层硬件能力(features/commands/lestates)
内核驱动开发调试
附录:核心源码文件索引
|-----------------------|---------------------------------------------------------------------|
| 文件 | 关键内容 |
| tools/hciconfig.c | 全部源码:main()、命令表、各 cmd_* 函数、print_dev_info |
| lib/hci.h | ioctl 码定义、struct hci_dev_info / hci_dev_req / hci_request |
| lib/hci.c | hci_open_dev / hci_send_req / hci_send_cmd / hci_read_local_version |
| lib/hci_lib.h | struct hci_request、hci_open_dev / hci_send_req 等函数声明 |
| lib/bluetooth.h | bdaddr_t、ba2str、str2ba 基础定义 |
| doc/hciconfig.1 | 手册页 |
参考文献
BlueZ Source Code:
tools/hciconfig.c,lib/hci.c,lib/hci.hLinux Kernel:
net/bluetooth/hci_core.c,net/bluetooth/hci_sock.cBluetooth Core Specification Version 5.3: Volume 2, Part E (HCI Commands and Events)
Linux ioctl Documentation:
Documentation/userspace-api/ioctl/ioctl-decoding.rst