蓝牙bluez enable bcm私有模式(brcm_patchram_plus1)方式介绍

news2024/10/1 12:56:01

 零. 前言

由于Bluez的介绍文档有限,以及对Linux 系统/驱动概念、D-Bus 通信和蓝牙协议都有要求,加上网络上其实没有一个完整的介绍Bluez系列的文档,所以不管是蓝牙初学者还是蓝牙从业人员,都有不小的难度,学习曲线也相对较陡,所以我有了这个想法,专门对Bluez做一个系统性的介绍,尽可能的涵盖所有内容。

-------------------------------------------------------------------------------------------------------------------------

蓝牙视频教程(跟韦东山老师合作), 其中专题22就是专门针对Bluez做的系统介绍

https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22329603896.20.5aeb41f98e267j&id=693788592796

--------------------------------------------------------------------------------------------------------------------------

版本

日期

作者

变更表述

1.0

2024/09/02

于忠军

文档创建

前言

我们在前面已经介绍了蓝牙enable的三种方式,而还有一些芯片厂家自己的工具,不具有通用性,所以单独拎出来介绍,比如bcm的brcm_patchram_plus1工具,这个工具的作用是在user space下载蓝牙fw以及触发标准的kernel中bt进行SIG HCI command的交互,来进行蓝牙使能的动作,也就是我们所说的这种方式:

另外,brcm_patchram_plus1指令也可以通过几种方式来启动:

  • 手动启动
  • 自动启动

手动启动这个不用多说,就是我们自己来敲命令行,对于调试阶段这个还是非常有必要的,自动启动的方式就比较多了,你可以自己写脚本,也可以用类似rockchip这种使用udev机制来监听rfkill,来启动特定的service来启动.

在介绍这个工具之前,我们先来介绍下udev,因为rockchip的开发板是采用这种方式来启动brcm_patchram_plus1工具的。

一. udev介绍

Linux 中的 udev 是一个设备管理器,用于管理用户空间和内核之间的设备文件系统。它在设备连接、移除或改变状态时动态创建和删除设备节点。udev 是现代 Linux 系统中硬件管理的核心部分,主要负责设备文件(如 /dev/sda)的创建和维护。

1. udev 的工作机制

内核设备事件

    • 当一个设备被插入或移除时,内核会生成一个 uevent,其中包含了设备的相关信息,如设备的类型、属性等。
    • 这些 uevent 会通过 netlink 套接字被传递到用户空间。

udev 监控 uevent

    • udev 守护进程(udevd)监听这些 uevent,并根据事件的内容决定如何处理该事件。udev 根据事件的类型和设备的属性,决定是否创建、修改或删除对应的设备节点。

设备规则匹配

    • udev 使用规则文件来决定如何处理设备事件。这些规则文件通常存储在 /etc/udev/rules.d//lib/udev/rules.d/ 目录下。
    • 规则文件的语法允许你匹配设备属性(如厂商 ID、产品 ID、序列号等),并基于这些属性执行特定的动作,如创建符号链接、更改设备文件权限或名称、运行脚本等。

设备节点的创建

    • 如果 udev 规则匹配成功,udev 会在 /dev/ 目录下创建对应的设备节点(如 /dev/sda),并且可以创建额外的符号链接或执行额外的操作。

设备管理的灵活性

    • udev 的规则系统非常灵活,你可以定义自定义的规则来适应不同的设备管理需求。例如,可以为特定的 USB 设备创建特定的符号链接,或者当设备插入时自动执行某个脚本。

承接上篇rfkill文章,我们可以看到rfkill的rfkill_set_block后会上报一个uevent

static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
{
	unsigned long flags;
	bool prev, curr;
	int err;

	if (unlikely(rfkill->dev.power.power_state.event & PM_EVENT_SLEEP))
		return;

	/*
	 * Some platforms (...!) generate input events which affect the
	 * _hard_ kill state -- whenever something tries to change the
	 * current software state query the hardware state too.
	 */
	if (rfkill->ops->query)
		rfkill->ops->query(rfkill, rfkill->data);

	spin_lock_irqsave(&rfkill->lock, flags);
	prev = rfkill->state & RFKILL_BLOCK_SW;

	if (rfkill->state & RFKILL_BLOCK_SW)
		rfkill->state |= RFKILL_BLOCK_SW_PREV;
	else
		rfkill->state &= ~RFKILL_BLOCK_SW_PREV;

	if (blocked)
		rfkill->state |= RFKILL_BLOCK_SW;
	else
		rfkill->state &= ~RFKILL_BLOCK_SW;

	rfkill->state |= RFKILL_BLOCK_SW_SETCALL;
	spin_unlock_irqrestore(&rfkill->lock, flags);

    /* 调用特定的rfkill设备驱动中的file operation中的set block */
	err = rfkill->ops->set_block(rfkill->data, blocked);

	spin_lock_irqsave(&rfkill->lock, flags);
	if (err) {
		/*
		 * Failed -- reset status to _prev, this may be different
		 * from what set set _PREV to earlier in this function
		 * if rfkill_set_sw_state was invoked.
		 */
		if (rfkill->state & RFKILL_BLOCK_SW_PREV)
			rfkill->state |= RFKILL_BLOCK_SW;
		else
			rfkill->state &= ~RFKILL_BLOCK_SW;
	}
	rfkill->state &= ~RFKILL_BLOCK_SW_SETCALL;
	rfkill->state &= ~RFKILL_BLOCK_SW_PREV;
	curr = rfkill->state & RFKILL_BLOCK_SW;
	spin_unlock_irqrestore(&rfkill->lock, flags);

	rfkill_led_trigger_event(rfkill);

    /* 注意,这点非常重要,上层可以做很多事情 */
	if (prev != curr)
		rfkill_event(rfkill);
}
static void rfkill_event(struct rfkill *rfkill)
{
	if (!rfkill->registered)
		return;

	kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);

	/* also send event to /dev/rfkill */
	rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}

从这里发送到user space。而用户空间就是通过systemd-udevd来监听内核上来的uevent

2. udev 规则的结构

一个 udev 规则文件由一系列的规则组成,每条规则通常由以下部分构成:

  • 匹配条件
    • 匹配条件用于过滤设备属性。例如,ATTR{idVendor}=="1234" 可以匹配 USB 设备的厂商 ID 为 1234 的设备。
  • 动作(Action)
    • 当匹配条件满足时,执行相应的动作。常见的动作包括 NAME(命名设备文件)、SYMLINK(创建符号链接)、RUN(运行脚本或命令)等。

下面就是蓝牙的一个rules,在目录/lib/udev/rules.d/50-bluetooth.rules

SUBSYSTEM=="rfkill", ACTION=="change", ATTR{name}=="bt_default", ATTR{type}=="bluetooth", ATTRS{state}=="1", RUN+="/bin/systemctl start bt-attach.service"

SUBSYSTEM=="rfkill", ACTION=="change", ATTR{name}=="bt_default", ATTR{type}=="bluetooth", ATTRS{state}=="0", RUN+="/bin/systemctl stop bt-attach.service"

可以看到这里就是监听了rfkill的状态变化,如果是1,那么/bin/systemctl start bt-attach.service 来启动bt-attach.service,如果是0,那么通过/bin/systemctl stop bt-attach.service 来停止bt-attach.service服务。

3. bt-attach.service服务

其中注意bt-attach并不是我们说的bluez的工具btattach,还是有差异的哈,注意看中间的横线,而这里的bt-attach是一个脚本。

4. bt-attach脚本解析

首先,我们来cat下这个脚本cat /usr/bin/bt-attach

#!/usr/bin/env bash

model_name=$(tr -d '\0' </sys/firmware/devicetree/base/model | tr 'a-z' 'A-Z')

while read line
do
        chip_name=$(echo ${line} | cut -d ' ' -f 1)

        if [[ ${model_name} == *3588* ]]; then
                uart=$(basename /sys/firmware/devicetree/base/pinctrl/wireless-bluetooth/uart*-gpios | tr -cd "[0-9]")
                uart="/dev/ttyS${uart}"
                break
        elif [[ ${model_name} == *${chip_name}* ]]; then
                uart=$(echo ${line} | cut -d ' ' -f 2)
                break
        fi

done < /usr/bin/bt_uart.cfg

bt_type=$(rk_wifi_gettype)

rtk_attach() {
        ret=`ps -ef |grep rtk_hciattach |grep -v "grep" |wc -l`
        if [ ${ret} = 1 ]; then
            killall rtk_hciattach
            sleep 1
        fi
        /usr/bin/rtk_hciattach -n -s 115200 ${uart} rtk_h5 1500000 noflow &
}

if [[ ${bt_type} = "RTL"* ]]; then
        rtk_attach
        exit 0
else
        killall brcm_patchram_plus1
        fwname=$(/usr/bin/bt_fwname ${uart})

        if [[ ${fwname} = "bcm43438a1.hcd" ]]; then
                ln -sf /vendor/etc/firmware/nvram_ap6212a.txt /vendor/etc/firmware/nvram.txt
        elif [[ ${fwname} = "BCM4345C0.hcd" ]]; then
                ln -sf /vendor/etc/firmware/nvram_ap6255.txt /vendor/etc/firmware/nvram.txt
        fi

        start-stop-daemon --start --oknodo --pidfile /var/run/hciattach.pid --background --startas \
/usr/bin/brcm_patchram_plus1 -- --enable_hci --no2bytes --use_baudrate_for_download \
--tosleep  200000 --baudrate 1500000 --patchram /system/etc/firmware/${fwname} ${uart}
fi

exit 0

我们可以看到串口号是如下,rk3399是/dev/ttyS0

root@firefly:~# cat /usr/bin/bt_uart.cfg
1808 /dev/ttyS4
3128 /dev/ttyS0
3288 /dev/ttyS0
3308 /dev/ttyS4
3328 /dev/ttyS2
3399 /dev/ttyS0
3566 /dev/ttyS1
3568 /dev/ttyS8
1126 /dev/ttyS0
PX30 /dev/ttyS1
3588 /dev/ttyS6

其次我们不是realtek的芯片,所以通过这里来启动

start-stop-daemon --start --oknodo --pidfile /var/run/hciattach.pid --background --startas \
/usr/bin/brcm_patchram_plus1 -- --enable_hci --no2bytes --use_baudrate_for_download \
--tosleep  200000 --baudrate 1500000 --patchram /system/etc/firmware/${fwname} ${uart}

这里说白了就是启动brcm_patchram_plus1 这个工具,后面带有一堆参数,包括波特率啦,fw路径啦,串口号啦等,那我们就直接来分析下brcm_patchram_plus1这个工具的源码吧!

二. brcm_patchram_plus1源码介绍

这个文件在路径Z:\board\rk3399\linux\rk3399_linux_release_v2.5.1_20210301\external\rkwifibt\brcm_tools中,其实bcm已经非常成熟,不太推荐用这种私有模式,这个工具跟hciattach的作用非常像,完全可以用hciattach来代替,也可以看他这个TODO也是很醒目,哈哈

但是本着把这bluez讲的面面俱到的初衷,我还是来介绍下这个工具的source code.

1. main函数

启动过程在main函数中,你可以看到他的流程,我们先大体看下,后面我们来拆分下。

int main (int argc, char **argv)
{
    #ifdef ANDROID
    read_default_bdaddr();
    #endif
    fprintf(stderr, "###AMPAK FW Auto detection patch version = [%s]###\n", FW_TABLE_VERSION);
    if (parse_cmd_line(argc, argv)) {
        exit(1);
    }

    if (uart_fd < 0) {
        exit(2);
    }

    init_uart();

    proc_reset();
    proc_read_local_name();
    proc_open_patchram();

    if (use_baudrate_for_download) {
        if (termios_baudrate) {
            proc_baudrate();
        }
    }

    if (hcdfile_fd > 0) {
        proc_patchram();
    }

    if (termios_baudrate) {
        proc_baudrate();
    }

    if (bdaddr_flag) {
        proc_bdaddr();
    }

    if (enable_lpm) {
        proc_enable_lpm();
    }

    if (scopcm) {
        proc_scopcm();
    }

    if (i2s) {
        proc_i2s();
    }

    if (enable_hci) {
        proc_enable_hci();

        while (1) {
            sleep(UINT_MAX);
        }
    }

    exit(0);
}

在介绍整个流程之前,我们要介绍下hci command发送以及hci event的读取,因为好多发送,接收都大量使用这两个函数,复用性比较强,我们就不一一介绍了。

2. 发送hci command的函数

void
hci_send_cmd(uchar *buf, int len)
{
	if (debug) {
		fprintf(stderr, "writing\n");
		dump(buf, len);
	}

	write(uart_fd, buf, len);
}

这个比较简单,就是直接发送数据进uart,可以看到这里的发送并不经过kernel

3. 读取hci event的函数

void
read_event(int fd, uchar *buffer)
{
	int i = 0;
	int len = 3;
	int count;

	while ((count = read(fd, &buffer[i], len)) < len) {
		i += count;
		len -= count;
		//fprintf(stderr, "received11 %d\n", count);
	}

	i += count;
	len = buffer[2];
	//fprintf(stderr, "received22 %d\n", count);

	while ((count = read(fd, &buffer[i], len)) < len) {
		i += count;
		len -= count;
	}
	//fprintf(stderr, "received33 %d\n", count);

	if (debug) {
		count += i;

		fprintf(stderr, "received %d\n", count);
		dump(buffer, count);
	}
}

就是直接从uart读取数据,可以看到这里的接收同样不经过kernel

那么他为啥这么实现呢,我们来解析下。

3.1. 读取3个byte

首先先读取3个byte,为什么是3个byte呢?我们看下hci event的格式就有答案了,hci event的格式如下:

可以看没到event code+parameter len是2个byte,然后加上h4 type的1个byte,所以一共是3个byte

3.2. 根据event len读取长度

这个比较好理解,就是根据长度,把所有的参数读取回来

4. 串口相关的设置

4.1. 打开串口

int
parse_cmd_line(int argc, char **argv)
{
	......

	if (optind < argc) {
		if (debug)
			printf ("%s \n", argv[optind]);
        // 打开串口号,也就是我们上面说明的rk3399的 /dev/ttyS0
		if ((uart_fd = open(argv[optind], O_RDWR | O_NOCTTY)) == -1) {
			fprintf(stderr, "port %s could not be opened, error %d\n",
					argv[2], errno);
		}
	}

	return(0);
}

4.2. 设置串口相关的信息

void
init_uart()
{
	tcflush(uart_fd, TCIOFLUSH);
	tcgetattr(uart_fd, &termios);

#ifndef __CYGWIN__
	cfmakeraw(&termios);
#else
	termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                | INLCR | IGNCR | ICRNL | IXON);
	termios.c_oflag &= ~OPOST;
	termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
	termios.c_cflag &= ~(CSIZE | PARENB);
	termios.c_cflag |= CS8;
#endif

	termios.c_cflag |= CRTSCTS;
	tcsetattr(uart_fd, TCSANOW, &termios);
	tcflush(uart_fd, TCIOFLUSH);
	tcsetattr(uart_fd, TCSANOW, &termios);
	tcflush(uart_fd, TCIOFLUSH);
	tcflush(uart_fd, TCIOFLUSH);
	cfsetospeed(&termios, B115200);
	cfsetispeed(&termios, B115200);
	tcsetattr(uart_fd, TCSANOW, &termios);
}

以上就是设置了串口相关的信息,包括流控,数据大小,波特率等。

5. 发送hci reset

uchar hci_reset[] = { 0x01, 0x03, 0x0c, 0x00 };
void
expired(int sig)
{
	hci_send_cmd(hci_reset, sizeof(hci_reset));
	alarm(4);
}

void
proc_reset()
{
	signal(SIGALRM, expired);

	fprintf(stderr, "proc_reset");

	hci_send_cmd(hci_reset, sizeof(hci_reset));

	alarm(4);

	read_event(uart_fd, buffer);

	alarm(0);
}

其中hci_reset可以看到{ 0x01, 0x03, 0x0c, 0x00 }; 是标准的h4的格式(1byte h4 type+ hci command),不熟悉的回头去看下hci的章节,然后读取返回的event.

这里加了一个alarm,为了什么呢,就是为了如果发送hci reset不回复的话,触发timeout,4S再发hci reset hci command

6. 发送read local name

uchar hci_read_local_name[] = { 0x01, 0x14, 0x0c, 0x00 };
void
proc_read_local_name()
{
	int i;
	char *p_name;
	hci_send_cmd(hci_read_local_name, sizeof(hci_read_local_name));
	read_event(uart_fd, buffer);
	p_name = &buffer[1+HCI_EVT_CMD_CMPL_LOCAL_NAME_STRING];
	for (i=0; (i < LOCAL_NAME_BUFFER_LEN)||(*(p_name+i) != 0); i++)
		*(p_name+i) = toupper(*(p_name+i));
	strcpy(local_name,p_name);
	fprintf(stderr,"chip id = %s\n", local_name);
}

这里就是读取回来没有下载固件的时候,芯片默认ROM中的名字,保存在local_name,而这个是直接为了下载固件的时候匹配名字

7. open patch ram

这个只要的作用根据文件夹路径以及芯片型号找到特定的hcd文件(.hcd就是bcm的fw)

7.1. 找文件夹路径

int
parse_patchram(char *optarg)
{
	int len = strlen(optarg);
	char *p = optarg+len-1;;
	/*Look for first '/' to know the fw path*/
	while(len>0)
	{
		if(*p == '/')
			break;
		len--;
		p--;
	}
	if(len>0)
	{
		*p =0;
		strcpy(fw_folder_path,optarg);
		fprintf(stderr,"FW folder path = %s\n", fw_folder_path);
	}
#if 0
	char *p;

	if (!(p = strrchr(optarg, '.'))) {
		fprintf(stderr, "file %s not an HCD file\n", optarg);
		exit(3);
	}

	p++;

	if (strcasecmp("hcd", p) != 0) {
		fprintf(stderr, "file %s not an HCD file\n", optarg);
		exit(4);
	}

	if ((hcdfile_fd = open(optarg, O_RDONLY)) == -1) {
		fprintf(stderr, "file %s could not be opened, error %d\n", optarg, errno);
		exit(5);
	}

#endif
	return(0);
}

主要是根据patchram启动参数来决定的

7.2. 整个流程

void
proc_open_patchram()
{
	char fw_path[1024];
	char *p;
	int i;
	fw_auto_detection_entry_t *p_entry;
	p_entry = (fw_auto_detection_entry_t *)fw_auto_detection_table;
	while (p_entry->chip_id != NULL)
	{
		if (strstr(local_name, p_entry->chip_id)!=NULL)
		{
			strcpy(local_name,p_entry->updated_chip_id);
			break;
		}
		p_entry++;
	}
	sprintf(fw_path,"%s/%s.hcd",fw_folder_path,local_name);
	fprintf(stderr, "FW path = %s\n", fw_path);
	if ((hcdfile_fd = open(fw_path, O_RDONLY)) == -1) {
		fprintf(stderr, "file %s could not be opened, error %d\n", fw_path , errno);
		p = local_name;
		fprintf(stderr, "Retry lower case FW name\n");
		for (i=0; (i < LOCAL_NAME_BUFFER_LEN)||(*(p+i) != 0); i++)
			*(p+i) = tolower(*(p+i));
		sprintf(fw_path,"%s/%s.hcd",fw_folder_path,local_name);
		fprintf(stderr, "FW path = %s\n", fw_path);
		if ((hcdfile_fd = open(fw_path, O_RDONLY)) == -1) {
			fprintf(stderr, "file %s could not be opened, error %d\n", fw_path, errno);
			exit(5);
		}
	}
}

可以看到sprintf(fw_path,"%s/%s.hcd",fw_folder_path,local_name); 主要通过组合成特定的fw路径,然后打开hcd,方便后续下载。

8. 设置高波特率方便下载固件更快

8.1. vendor command修改波特率

由于SIG并没有标准的HCI command来修改波特率,所以每家芯片都有自己的vendor command来修改波特率,格式如下:

我们可以看到里面的格式为:

uchar hci_update_baud_rate[] = { 0x01, 0x18, 0xfc, 0x06, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00 };

8.2. 解析/更新波特率

启动参数为:

start-stop-daemon --start --oknodo --pidfile /var/run/hciattach.pid --background --startas \
/usr/bin/brcm_patchram_plus1 -- --enable_hci --no2bytes --use_baudrate_for_download \
--tosleep  200000 --baudrate 1500000 --patchram /system/etc/firmware/${fwname} ${uart}

可以看到是1.5M的波特率。

下面是解析波特率并且更新到我们上面buffer中的vendor command中

int
parse_baudrate(char *optarg)
{
	int baudrate = atoi(optarg);

	if (validate_baudrate(baudrate, &termios_baudrate)) {
		BRCM_encode_baud_rate(baudrate, &hci_update_baud_rate[6]);
	}

	return(0);
}
void
BRCM_encode_baud_rate(uint baud_rate, uchar *encoded_baud)
{
	if(baud_rate == 0 || encoded_baud == NULL) {
		fprintf(stderr, "Baudrate not supported!");
		return;
	}

	encoded_baud[3] = (uchar)(baud_rate >> 24);
	encoded_baud[2] = (uchar)(baud_rate >> 16);
	encoded_baud[1] = (uchar)(baud_rate >> 8);
	encoded_baud[0] = (uchar)(baud_rate & 0xFF);
}

8.3. 平台端更新波特率



if (use_baudrate_for_download) {
		if (termios_baudrate) {
			proc_baudrate();
		}
	}

void
proc_baudrate()
{
	hci_send_cmd(hci_update_baud_rate, sizeof(hci_update_baud_rate));

	read_event(uart_fd, buffer);
	
	usleep(200000);

	cfsetospeed(&termios, termios_baudrate);
	cfsetispeed(&termios, termios_baudrate);
	tcsetattr(uart_fd, TCSANOW, &termios);

	if (debug) {
		fprintf(stderr, "Done setting baudrate\n");
	}
}

9. 下载fw

整个流程如下:

if (hcdfile_fd > 0) {
    proc_patchram();
}
void
proc_patchram()
{
    int len;

    hci_send_cmd(hci_download_minidriver, sizeof(hci_download_minidriver));

    fprintf(stderr, "send hci_download_minidriver");

    read_event(uart_fd, buffer);

    if (!no2bytes) {
        read(uart_fd, &buffer[0], 2);
    }

    if (tosleep) {
        usleep(tosleep);
    }

    while (read(hcdfile_fd, &buffer[1], 3)) {
        buffer[0] = 0x01;

        len = buffer[3];

        read(hcdfile_fd, &buffer[4], len);

        hci_send_cmd(buffer, len + 4);

        read_event(uart_fd, buffer);
    }
    usleep(200000);

    if (use_baudrate_for_download) {
        cfsetospeed(&termios, B115200);
        cfsetispeed(&termios, B115200);
        tcsetattr(uart_fd, TCSANOW, &termios);
    }
    proc_reset();
}

9.1. download minidrver

uchar hci_download_minidriver[] = { 0x01, 0x2e, 0xfc, 0x00 };
hci_send_cmd(hci_download_minidriver, sizeof(hci_download_minidriver));
	
fprintf(stderr, "send hci_download_minidriver");

read_event(uart_fd, buffer);

这个命令是什么作用呢?这个就相当于告诉芯片"我要下载fw",然后芯片回复"好的,你下载吧"

这个同样的是vendor command

9.2. 从hcd文件中读取,下载到芯片中

while (read(hcdfile_fd, &buffer[1], 3)) {
		buffer[0] = 0x01;

		len = buffer[3];

		read(hcdfile_fd, &buffer[4], len);

		hci_send_cmd(buffer, len + 4);

		read_event(uart_fd, buffer);
	}

在这里注意的是在fw中是标准的hci指令,所以你可以看到处理,是读取3个byte,是放在&buffer[1]位置,在buffer[0]=1, 这个是设置h4 type hci command。

另外,一个隐藏的知识点是,fw的hci指令是什么呢?主要就是两种vendor command

可以看到流程

write ram说白了就是把fw写到芯片的ram中,lunch ram,就是类似sw reboot.

9.3. 下载完毕后降低波特率

	if (use_baudrate_for_download) {
		cfsetospeed(&termios, B115200);
		cfsetispeed(&termios, B115200);
		tcsetattr(uart_fd, TCSANOW, &termios);
	}

sw reboot后,芯片恢复到115200了,所以平台的也要对应的设置为115200

然后发送hci reset

9.4. 回复到高波特率

if (termios_baudrate) {
		proc_baudrate();
	}

void
proc_baudrate()
{
	hci_send_cmd(hci_update_baud_rate, sizeof(hci_update_baud_rate));

	read_event(uart_fd, buffer);
	
	usleep(200000);

	cfsetospeed(&termios, termios_baudrate);
	cfsetispeed(&termios, termios_baudrate);
	tcsetattr(uart_fd, TCSANOW, &termios);

	if (debug) {
		fprintf(stderr, "Done setting baudrate\n");
	}
}

10. 写蓝牙地址

同样,SIG HCI command中并没有写蓝牙地址的操作,也是交于芯片厂家自己实现。所以各个常见也是通过vendor command来实现。

其实这块是option的,有的芯片或者模组在产线已经把蓝牙地址写到芯片中,所以不需要在通过vendor command来写地址

uchar hci_write_bd_addr[] = { 0x01, 0x01, 0xfc, 0x06,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void
proc_bdaddr()
{
	hci_send_cmd(hci_write_bd_addr, sizeof(hci_write_bd_addr));

	read_event(uart_fd, buffer);
}

11. 处理低功耗管理

uchar hci_write_sleep_mode[] = { 0x01, 0x27, 0xfc, 0x0c,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
	0x00, 0x00 };
if (enable_lpm) {
		proc_enable_lpm();
	}
void
proc_enable_lpm()
{
	hci_send_cmd(hci_write_sleep_mode, sizeof(hci_write_sleep_mode));

	read_event(uart_fd, buffer);
}

同样是vendor command

12. 处理音频格式

if (scopcm) {
		proc_scopcm();
	}

	if (i2s) {
		proc_i2s();
	}

12.1. pcm

这几个都是vendor command


uchar hci_write_sco_pcm_int[] =
	{ 0x01, 0x1C, 0xFC, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 };

uchar hci_write_pcm_data_format[] =
	{ 0x01, 0x1e, 0xFC, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 };
void
proc_scopcm()
{
	hci_send_cmd(hci_write_sco_pcm_int,
		sizeof(hci_write_sco_pcm_int));

	read_event(uart_fd, buffer);

	hci_send_cmd(hci_write_pcm_data_format,
		sizeof(hci_write_pcm_data_format));

	read_event(uart_fd, buffer);
}

12.2. i2s

uchar hci_write_i2spcm_interface_param[] =
	{ 0x01, 0x6d, 0xFC, 0x04, 0x00, 0x00, 0x00, 0x00 };
void
proc_i2s()
{
	hci_send_cmd(hci_write_i2spcm_interface_param,
		sizeof(hci_write_i2spcm_interface_param));

	read_event(uart_fd, buffer);
}

13. 触发kernel进行蓝牙enable流程

if (enable_hci) {
		proc_enable_hci();

		while (1) {
			sleep(UINT_MAX);
		}
	}
void
proc_enable_hci()
{
	int i = N_HCI;
	int proto = HCI_UART_H4;
	if (ioctl(uart_fd, TIOCSETD, &i) < 0) {
		fprintf(stderr, "Can't set line discipline\n");
		return;
	}

	if (ioctl(uart_fd, HCIUARTSETPROTO, proto) < 0) {
		fprintf(stderr, "Can't set hci protocol\n");
		return;
	}
	fprintf(stderr, "Done setting line discpline\n");
	return;
}

以上就就通过ioctl设置H4 protocol进入kernel,让kernel进行标准的SIG HCI command指令,这个我们在后面介绍,kernel中怎么处理的!

三. kernel介绍

1. 蓝牙子系统目录介绍

内核部分一共分为三个部分,分别为 driver/bluetooth以及net/bluetooth,这里面会引用linux的一些头文件include/net/bluetooth

1.1. driver/bluetooth

这部分的内核代码,你可以参照任意一个kernel版本,包含但是不局限于我们公版使用的内核代码,我就贴下一个开源的查看linux代码的链接吧:

bluetooth - drivers/bluetooth - Linux source code (v6.10.5) - Bootlin

这部分就是整个蓝牙的驱动,包含两个部分:

  • 各个厂商的驱动,比如博通的,高通的,瑞昱的,intel,marvell, nxp的等等
  • 蓝牙协议栈的transport层,包括uart的h4/h5/bcsp, sdio, usb等

我们来截图下文件,后面我们介绍特定的功能再来介绍下蓝牙的驱动

1.2. net/bluetooth

这部分的内核代码,你可以参照任意一个kernel版本,包含但是不局限于我们公版使用的内核代码,我就贴下一个开源的查看linux代码的链接吧:

bluetooth - net/bluetooth - Linux source code (v6.10.5) - Bootlin

部分就是整个蓝牙的protocol,包含bnep/cmtp/hid/rfcomm/l2cap/hci等

我们来截图下文件,后面我们介绍特定的功能再来介绍下蓝牙的内核

1.3. include\net\bluetooth

这个部分就是net/bluetooth以及driver/bluetooth依赖的头文件部分,链接如下:

bluetooth - include/net/bluetooth - Linux source code (v6.10.5) - Bootlin

2. kernel中蓝牙子系统启动

在介绍蓝牙子系统启动之前,我们先来看下linux是怎样启动子系统的

2.1. subsys_initcall 介绍

subsys_initcall 是 Linux 内核中的一种宏,用于定义子系统初始化函数。它用于在内核启动过程中初始化特定的子系统。其作用是在内核启动的早期阶段,按照特定的顺序调用初始化函数,以便内核中的不同子系统能够正确配置并准备好使用。

作用

subsys_initcall 的主要作用是注册一个初始化函数,这个函数将在内核启动过程中的特定阶段执行。通常用于那些依赖于核心系统初始化完成,但又希望在大部分设备驱动程序初始化之前运行的子系统。比如,某些文件系统或者网络子系统可能会使用 subsys_initcall 来确保在设备驱动程序之前完成初始化。

启动阶段

Linux 内核的启动过程大致可以分为多个阶段,依次是:

  • 架构相关的初始化:包括处理器和内存管理等初始化。
  • 核心系统初始化:这部分包括一些基础设施的初始化,比如内存分配器、调度器、中断管理等等。
  • 子系统初始化subsys_initcall 所在的阶段,这时候内核已经具备了运行子系统初始化的基础条件。
  • 设备驱动初始化:在子系统初始化之后,设备驱动程序会开始初始化。
  • 用户空间的初始化:最后,内核会启动 init 进程,进入用户空间。

在这些阶段中,subsys_initcall 所定义的函数会在子系统初始化阶段被调用。内核按照预先定义好的顺序依次调用这些初始化函数,保证内核各个部分按需初始化,从而为设备驱动程序和用户空间的操作提供良好的基础。

2.2. 蓝牙子系统启动

蓝牙子系统首先会先运行 net/bluetooth/af_bluetooth.c中的

subsys_initcall(bt_init);

subsys_initcall 这个宏我们已经在前面介绍了,所以会系统运行子系统的时候,会运行蓝牙蓝牙子系统,也就是入口函数bt_init会被执行。

static int __init bt_init(void)
{
	int err;

	sock_skb_cb_check_size(sizeof(struct bt_skb_cb));

	BT_INFO("Core ver %s", VERSION);

	err = bt_selftest();
	if (err < 0)
		return err;

	bt_debugfs = debugfs_create_dir("bluetooth", NULL);

    // 创建/sys/class/bluetooth,这个比较重要
	err = bt_sysfs_init();
	if (err < 0)
		return err;

	err = sock_register(&bt_sock_family_ops);
	if (err < 0) {
		bt_sysfs_cleanup();
		return err;
	}

	BT_INFO("HCI device and connection manager initialized");

	err = hci_sock_init();
	if (err < 0)
		goto error;

	err = l2cap_init();
	if (err < 0)
		goto sock_err;

	err = sco_init();
	if (err < 0) {
		l2cap_exit();
		goto sock_err;
	}

	err = mgmt_init();
	if (err < 0) {
		sco_exit();
		l2cap_exit();
		goto sock_err;
	}

	return 0;

sock_err:
	hci_sock_cleanup();

error:
	sock_unregister(PF_BLUETOOTH);
	bt_sysfs_cleanup();

	return err;
}

这块主要做的东西就是kernel相关的socket init, 也就是以下红框部分

其中RFCOMM/BNEP/HIDP/CMTP的socket初始化并没有默认包含进来,这几个的socket是根据不同的kernel config来决定是否要加载相应的驱动来开启这个功能。路径在

module_init(rfcomm_init);	// net/bluetooth/rfcomm/core.c
module_init(bnep_init);		// net/bluetooth/bnep/core.c
module_init(hidp_init);		// net/bluetooth/hidp/core.c
module_init(cmtp_init);		// net/bluetooth/cmtp/core.c

这里我们就不一一介绍了,等具体介绍到这几个功能的时候,我们再来填坑!

3. HCI UART驱动加载

3.1. module_init 介绍

module_init 是 Linux 内核中的一个宏,用于指定模块加载时的初始化函数。它是设备驱动程序或其他内核模块开发中经常使用的宏。这个宏的主要作用是让内核知道在加载模块时应该调用哪个函数来完成模块的初始化。

我们前面已经介绍了linux的加载过程,我们在列下:

Linux 内核的启动过程大致可以分为多个阶段,依次是:

  • 架构相关的初始化:包括处理器和内存管理等初始化。
  • 核心系统初始化:这部分包括一些基础设施的初始化,比如内存分配器、调度器、中断管理等等。
  • 子系统初始化subsys_initcall 所在的阶段,这时候内核已经具备了运行子系统初始化的基础条件。
  • 设备驱动初始化:在子系统初始化之后,设备驱动程序会开始初始化。
  • 用户空间的初始化:最后,内核会启动 init 进程,进入用户空间。

而module_init是设备驱动初始化阶段!

3.1.1. 作用

module_init 的作用是将一个函数标记为模块的初始化函数,当模块被加载到内核时,内核会自动调用这个函数。这通常用于设备驱动的初始化工作,比如注册设备、分配资源、设置中断处理程序等。

3.1.2. 使用方法

module_init 宏通常和一个初始化函数一起使用,这个初始化函数需要实现模块加载时的所有必要操作。它的使用方法如下:

static int __init my_module_init(void)
{
    // 模块初始化代码,例如注册设备、分配资源等
    printk(KERN_INFO "My module is loaded.\n");
    return 0; // 返回0表示初始化成功,非0表示失败
}

module_init(my_module_init);

在这个示例中,my_module_init 函数是模块的初始化函数,当模块加载时,内核会调用这个函数执行初始化操作。

3.1.3. 工作流程
  • 编写初始化函数:首先,开发者编写一个初始化函数,通常带有 __init 标记,以指示内核这个函数只在初始化时使用,之后可以被丢弃以节省内存。
  • 使用 module_init:使用 module_init 宏将初始化函数与模块加载过程关联起来。
  • 模块加载时调用:当用户使用 insmodmodprobe 命令加载模块时,内核会自动调用 module_init 宏指定的初始化函数。有的模块也会默认编译进kernel,自动加载
  • 初始化成功或失败:如果初始化函数返回 0,则表示初始化成功,模块加载完成;如果返回非 0 值,则表示初始化失败,模块加载会被中止。
3.1.4. 模块的卸载

module_init 相对的,还有一个 module_exit 宏,它用于指定模块卸载时的清理函数。在卸载模块时,内核会调用该函数进行资源释放和清理工作:

static void __exit my_module_exit(void)
{
    // 模块卸载时的清理代码
    printk(KERN_INFO "My module is unloaded.\n");
}

module_exit(my_module_exit);
3.1.5. 总结

module_init 是用于标记内核模块初始化函数的一个宏。它让内核知道当加载一个模块时,应该调用哪个函数进行初始化操作。结合 module_exit 宏,开发者可以轻松地编写模块的加载和卸载代码。

3.2. hci uart驱动加载

由于我们是介绍的uart蓝牙,蓝牙uart驱动模块的代码在路径 driver/bluetooth/hci_ldisc.c中

module_init(hci_uart_init);

所以模块加载的时候(不管是手动加载还是系统自动启动)就会运行hci_uart_init函数

static int __init hci_uart_init(void)
{
    static struct tty_ldisc_ops hci_uart_ldisc;
    int err;

    BT_INFO("HCI UART driver ver %s", VERSION);

    /* Register the tty discipline */
    // 填写tty_ldis operation 函数指针
    memset(&hci_uart_ldisc, 0, sizeof(hci_uart_ldisc));
    hci_uart_ldisc.magic		= TTY_LDISC_MAGIC;
    hci_uart_ldisc.name		= "n_hci";
    hci_uart_ldisc.open		= hci_uart_tty_open;
    hci_uart_ldisc.close		= hci_uart_tty_close;
    hci_uart_ldisc.read		= hci_uart_tty_read;
    hci_uart_ldisc.write		= hci_uart_tty_write;
    hci_uart_ldisc.ioctl		= hci_uart_tty_ioctl;
    hci_uart_ldisc.poll		= hci_uart_tty_poll;
    hci_uart_ldisc.receive_buf	= hci_uart_tty_receive;
    hci_uart_ldisc.write_wakeup	= hci_uart_tty_wakeup;
    hci_uart_ldisc.owner		= THIS_MODULE;

    err = tty_register_ldisc(N_HCI, &hci_uart_ldisc);
    if (err) {
        BT_ERR("HCI line discipline registration failed. (%d)", err);
        return err;
    }

    // 这里就是根据特定的内核开启的来做特定的transport以及芯片来初始化uart protocol
    #ifdef CONFIG_BT_HCIUART_H4
    h4_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_BCSP
    bcsp_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_LL
    ll_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_ATH3K
    ath_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_3WIRE
    h5_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_INTEL
    intel_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_BCM
    bcm_init();
    #endif
    #ifdef CONFIG_BT_HCIUART_QCA
    qca_init();
    #endif

    return 0;
}

初始化特定的transport的地方,是一个结构体指针数组,代码如下:

static const struct hci_uart_proto *hup[HCI_UART_MAX_PROTO];

不同的transport以及特定芯片的驱动,根据以下索引存储到不同的位置:

/* UART protocols */
#define HCI_UART_MAX_PROTO	9

#define HCI_UART_H4	0
#define HCI_UART_BCSP	1
#define HCI_UART_3WIRE	2
#define HCI_UART_H4DS	3
#define HCI_UART_LL	4
#define HCI_UART_ATH3K	5
#define HCI_UART_INTEL	6
#define HCI_UART_BCM	7
#define HCI_UART_QCA	8

4. 用户层触发的kernel行为

plus1的最后的行为是用ioctl触发kernel行为,代码如下:

void
proc_enable_hci()
{
	int i = N_HCI;
	int proto = HCI_UART_H4;
    // 调用内核的tty_ioctl,采用ldisc机制改变hci行规
	if (ioctl(uart_fd, TIOCSETD, &i) < 0) {
		fprintf(stderr, "Can't set line discipline\n");
		return;
	}

	if (ioctl(uart_fd, HCIUARTSETPROTO, proto) < 0) {
		fprintf(stderr, "Can't set hci protocol\n");
		return;
	}
	fprintf(stderr, "Done setting line discpline\n");
	return;
}

而HCIUARTSETPROTO 是我们的重头戏,因为brcm_patchram_plus1是HCI_UART_H4

4.1. HCIUARTSETPROTO ioctl kernel的处理

static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file *file,
			      unsigned int cmd, unsigned long arg)
{
	struct hci_uart *hu = tty->disc_data;
	int err = 0;

	BT_DBG("");

	/* Verify the status of the device */
	if (!hu)
		return -EBADF;

	switch (cmd) {
	case HCIUARTSETPROTO:
		if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) {
			err = hci_uart_set_proto(hu, arg);
			if (err) {
				clear_bit(HCI_UART_PROTO_SET, &hu->flags);
				return err;
			}
		} else
			return -EBUSY;
		break;

	.....

	return err;
}
static int hci_uart_set_proto(struct hci_uart *hu, int id)
{
	const struct hci_uart_proto *p;
	int err;

	p = hci_uart_get_proto(id);
	if (!p)
		return -EPROTONOSUPPORT;

	err = p->open(hu);
	if (err)
		return err;

	hu->proto = p;

	err = hci_uart_register_dev(hu);
	if (err) {
		p->close(hu);
		return err;
	}

	set_bit(HCI_UART_PROTO_READY, &hu->flags);

	return 0;
}

其中hci_uart_get_proto就是获取h4的结构体指针,所以对应的kernel中也是h4相应的处理生效

剩下的我们就不一个函数介绍了,我们贴一个图

注意:不同的kernel版本之间的函数名称稍微有点差异,这点需要注意

4.2. 蓝牙SIG的初始化流程(下发命令)

4.2.1. hci command发送原理
4.2.1.1. hci_uart_write_work初始化
INIT_WORK(&hu->write_work, hci_uart_write_work);
INIT_WORK(&hdev->cmd_work, hci_cmd_work);

以上是创建一个两个work queue,一个是hci_uart_write_work,一个是hci_cmd_work

4.2.1.2. hci发送hci command

可以看到在kernel蓝牙子系统中,有很多hci command通过hci_req_add这个函数发送

hci_req_add(req, HCI_OP_READ_BD_ADDR, 0, NULL);

void hci_req_add(struct hci_request *req, u16 opcode, u32 plen,
		 const void *param)
{
	hci_req_add_ev(req, opcode, plen, param, 0);
}

其中hci_req_add_ev实现如下:

void hci_req_add_ev(struct hci_request *req, u16 opcode, u32 plen,
		    const void *param, u8 event)
{
	.....
        
	skb_queue_tail(&req->cmd_q, skb);
}

因为我们前面有个cmd_q的队列,所以对应的处理如下:

static void hci_cmd_work(struct work_struct *work)
{
    skb = skb_dequeue(&hdev->cmd_q);
    ...
    if (hdev->sent_cmd) {
        atomic_dec(&hdev->cmd_cnt);
        hci_send_frame(hdev, skb);
    }
}
static void hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
    ...
	err = hdev->send(hdev, skb);
    ...
}

而在前面结构体的send指针是如下:

hdev->open  = hci_uart_open;
hdev->close = hci_uart_close;
hdev->flush = hci_uart_flush;
hdev->send  = hci_uart_send_frame;
hdev->setup = hci_uart_setup;
/* Send frames from HCI layer */
static int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
    ...
	hu->proto->enqueue(hu, skb);

	hci_uart_tx_wakeup(hu);

	return 0;
}

其中hu->proto->enqueu就是我们h4 transport的enq的函数指针,也就是这个结构体中的内容:

static const struct hci_uart_proto h4p = {
	.id		= HCI_UART_H4,
	.name		= "H4",
	.open		= h4_open,
	.close		= h4_close,
	.recv		= h4_recv,
	.enqueue	= h4_enqueue,
	.dequeue	= h4_dequeue,
	.flush		= h4_flush,
};
static int h4_enqueue(struct hci_uart *hu, struct sk_buff *skb)
{
	struct h4_struct *h4 = hu->priv;

	BT_DBG("hu %p skb %p", hu, skb);

	/* Prepend skb with frame type */
	memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
	skb_queue_tail(&h4->txq, skb);

	return 0;
}

然后进入了hci_uart_write_work就出发了前面的消息队列

int hci_uart_tx_wakeup(struct hci_uart *hu)
{
	if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) {
		set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state);
		return 0;
	}

	BT_DBG("");

	schedule_work(&hu->write_work);

	return 0;
}
static void hci_uart_write_work(struct work_struct *work)
{
	...

	while ((skb = hci_uart_dequeue(hu))) {
		int len;

		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
		len = tty->ops->write(tty, skb->data, skb->len);
		hdev->stat.byte_tx += len;

		skb_pull(skb, len);
		if (skb->len) {
			hu->tx_skb = skb;
			break;
		}

		hci_uart_tx_complete(hu, bt_cb(skb)->pkt_type);
		kfree_skb(skb);
	}

	...
}

这里的tty->ops->write通常由 TTY 子系统在打开终端设备时调用。这个函数的调用过程和 tty 的来源可以追溯到用户空间应用程序通过调用 open() 系统调用来打开一个与终端设备(如 /dev/ttyS0)关联的文件。这个请求最终会进入内核,并由 TTY 子系统处理。

4.2.2. 接收处理
4.2.2.1. rx work queue初始化
INIT_WORK(&hdev->rx_work, hci_rx_work);
4.2.2.2. 接收逻辑
/* hci_uart_tty_receive()
 *
 *     Called by tty low level driver when receive data is
 *     available.
 *
 * Arguments:  tty          pointer to tty isntance data
 *             data         pointer to received data
 *             flags        pointer to flags for data
 *             count        count of received data in bytes
 *
 * Return Value:    None
 */
static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
				 char *flags, int count)
{
    ...
	/* It does not need a lock here as it is already protected by a mutex in
	 * tty caller
	 */
	hu->proto->recv(hu, data, count);
    ...

}

我们可以看到注释,这个是底层的tty系统有数据call上来的,所以你在蓝牙子系统中追不到了,如果想继续了解更多,那么就需要了解tty子系统,当然你可以认为底层uart数据从这个函数入口开始的!

h4的recv的函数指针如下:

static const struct hci_uart_proto h4p = {
	.id		= HCI_UART_H4,
	.name		= "H4",
	.open		= h4_open,
	.close		= h4_close,
	.recv		= h4_recv,
	.enqueue	= h4_enqueue,
	.dequeue	= h4_dequeue,
	.flush		= h4_flush,
};
static const struct h4_recv_pkt h4_recv_pkts[] = {
	{ H4_RECV_ACL,   .recv = hci_recv_frame },
	{ H4_RECV_SCO,   .recv = hci_recv_frame },
	{ H4_RECV_EVENT, .recv = hci_recv_frame },
};

/* Recv data */
static int h4_recv(struct hci_uart *hu, const void *data, int count)
{
	...
	h4->rx_skb = h4_recv_buf(hu->hdev, h4->rx_skb, data, count,
				 h4_recv_pkts, ARRAY_SIZE(h4_recv_pkts));
    ...

	return count;
}
/* Receive frame from HCI drivers */
int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
	...

	skb_queue_tail(&hdev->rx_q, skb);
	queue_work(hdev->workqueue, &hdev->rx_work);

	return 0;
}

然后开始进入队列处理函数hci_rx_work处理

然后再分不同阶段去处理acl,sco,event处理了

static void hci_rx_work(struct work_struct *work)
{
	...

	while ((skb = skb_dequeue(&hdev->rx_q))) {
		...

		/* Process frame */
		switch (bt_cb(skb)->pkt_type) {
		case HCI_EVENT_PKT:
			BT_DBG("%s Event packet", hdev->name);
			hci_event_packet(hdev, skb);
			break;

		case HCI_ACLDATA_PKT:
			BT_DBG("%s ACL data packet", hdev->name);
			hci_acldata_packet(hdev, skb);
			break;

		case HCI_SCODATA_PKT:
			BT_DBG("%s SCO data packet", hdev->name);
			hci_scodata_packet(hdev, skb);
			break;

		default:
			kfree_skb(skb);
			break;
		}
	}
}
4.2.3. 第零阶段(HCI_UNCONFIGURED)
static void hci_init0_req(struct hci_request *req, unsigned long opt)
{
	struct hci_dev *hdev = req->hdev;

	BT_DBG("%s %ld", hdev->name, opt);

	/* Reset */
	if (!test_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks))
		hci_reset_req(req, 0);

	/* Read Local Version */
	hci_req_add(req, HCI_OP_READ_LOCAL_VERSION, 0, NULL);

	/* Read BD Address */
	if (hdev->set_bdaddr)
		hci_req_add(req, HCI_OP_READ_BD_ADDR, 0, NULL);
}

可以看到有hci reset, read local verson, read local name的操作

4.2.4. 第一阶段
static void hci_init1_req(struct hci_request *req, unsigned long opt)
{
	struct hci_dev *hdev = req->hdev;

	BT_DBG("%s %ld", hdev->name, opt);

	/* Reset */
	if (!test_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks))
		hci_reset_req(req, 0);

	switch (hdev->dev_type) {
	case HCI_BREDR:
		bredr_init(req);
		break;

	case HCI_AMP:
		amp_init1(req);
		break;

	default:
		BT_ERR("Unknown device type %d", hdev->dev_type);
		break;
	}
}
static void bredr_init(struct hci_request *req)
{
	req->hdev->flow_ctl_mode = HCI_FLOW_CTL_MODE_PACKET_BASED;

	/* Read Local Supported Features */
	hci_req_add(req, HCI_OP_READ_LOCAL_FEATURES, 0, NULL);

	/* Read Local Version */
	hci_req_add(req, HCI_OP_READ_LOCAL_VERSION, 0, NULL);

	/* Read BD Address */
	hci_req_add(req, HCI_OP_READ_BD_ADDR, 0, NULL);
}

可以看到有read local feature/ read local version/ read local addr

4.2.5. 第二阶段
static void hci_init2_req(struct hci_request *req, unsigned long opt)
{
	struct hci_dev *hdev = req->hdev;

	if (hdev->dev_type == HCI_AMP)
		return amp_init2(req);

	if (lmp_bredr_capable(hdev))
		bredr_setup(req);
	else
		hci_dev_clear_flag(hdev, HCI_BREDR_ENABLED);

	if (lmp_le_capable(hdev))
		le_setup(req);

	/* All Bluetooth 1.2 and later controllers should support the
	 * HCI command for reading the local supported commands.
	 *
	 * Unfortunately some controllers indicate Bluetooth 1.2 support,
	 * but do not have support for this command. If that is the case,
	 * the driver can quirk the behavior and skip reading the local
	 * supported commands.
	 */
	if (hdev->hci_ver > BLUETOOTH_VER_1_1 &&
	    !test_bit(HCI_QUIRK_BROKEN_LOCAL_COMMANDS, &hdev->quirks))
		hci_req_add(req, HCI_OP_READ_LOCAL_COMMANDS, 0, NULL);

	if (lmp_ssp_capable(hdev)) {
		/* When SSP is available, then the host features page
		 * should also be available as well. However some
		 * controllers list the max_page as 0 as long as SSP
		 * has not been enabled. To achieve proper debugging
		 * output, force the minimum max_page to 1 at least.
		 */
		hdev->max_page = 0x01;

		if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED)) {
			u8 mode = 0x01;

			hci_req_add(req, HCI_OP_WRITE_SSP_MODE,
				    sizeof(mode), &mode);
		} else {
			struct hci_cp_write_eir cp;

			memset(hdev->eir, 0, sizeof(hdev->eir));
			memset(&cp, 0, sizeof(cp));

			hci_req_add(req, HCI_OP_WRITE_EIR, sizeof(cp), &cp);
		}
	}

	if (lmp_inq_rssi_capable(hdev) ||
	    test_bit(HCI_QUIRK_FIXUP_INQUIRY_MODE, &hdev->quirks)) {
		u8 mode;

		/* If Extended Inquiry Result events are supported, then
		 * they are clearly preferred over Inquiry Result with RSSI
		 * events.
		 */
		mode = lmp_ext_inq_capable(hdev) ? 0x02 : 0x01;

		hci_req_add(req, HCI_OP_WRITE_INQUIRY_MODE, 1, &mode);
	}

	if (lmp_inq_tx_pwr_capable(hdev))
		hci_req_add(req, HCI_OP_READ_INQ_RSP_TX_POWER, 0, NULL);

	if (lmp_ext_feat_capable(hdev)) {
		struct hci_cp_read_local_ext_features cp;

		cp.page = 0x01;
		hci_req_add(req, HCI_OP_READ_LOCAL_EXT_FEATURES,
			    sizeof(cp), &cp);
	}

	if (hci_dev_test_flag(hdev, HCI_LINK_SECURITY)) {
		u8 enable = 1;
		hci_req_add(req, HCI_OP_WRITE_AUTH_ENABLE, sizeof(enable),
			    &enable);
	}
}
4.2.6. 第三阶段
static void hci_init3_req(struct hci_request *req, unsigned long opt)
{
	struct hci_dev *hdev = req->hdev;
	u8 p;

	hci_setup_event_mask(req);

	if (hdev->commands[6] & 0x20 &&
	    !test_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks)) {
		struct hci_cp_read_stored_link_key cp;

		bacpy(&cp.bdaddr, BDADDR_ANY);
		cp.read_all = 0x01;
		hci_req_add(req, HCI_OP_READ_STORED_LINK_KEY, sizeof(cp), &cp);
	}

	if (hdev->commands[5] & 0x10)
		hci_setup_link_policy(req);

	if (hdev->commands[8] & 0x01)
		hci_req_add(req, HCI_OP_READ_PAGE_SCAN_ACTIVITY, 0, NULL);

	/* Some older Broadcom based Bluetooth 1.2 controllers do not
	 * support the Read Page Scan Type command. Check support for
	 * this command in the bit mask of supported commands.
	 */
	if (hdev->commands[13] & 0x01)
		hci_req_add(req, HCI_OP_READ_PAGE_SCAN_TYPE, 0, NULL);

	if (lmp_le_capable(hdev)) {
		u8 events[8];

		memset(events, 0, sizeof(events));
		events[0] = 0x0f;

		if (hdev->le_features[0] & HCI_LE_ENCRYPTION)
			events[0] |= 0x10;	/* LE Long Term Key Request */

		/* If controller supports the Connection Parameters Request
		 * Link Layer Procedure, enable the corresponding event.
		 */
		if (hdev->le_features[0] & HCI_LE_CONN_PARAM_REQ_PROC)
			events[0] |= 0x20;	/* LE Remote Connection
						 * Parameter Request
						 */

		/* If the controller supports the Data Length Extension
		 * feature, enable the corresponding event.
		 */
		if (hdev->le_features[0] & HCI_LE_DATA_LEN_EXT)
			events[0] |= 0x40;	/* LE Data Length Change */

		/* If the controller supports Extended Scanner Filter
		 * Policies, enable the correspondig event.
		 */
		if (hdev->le_features[0] & HCI_LE_EXT_SCAN_POLICY)
			events[1] |= 0x04;	/* LE Direct Advertising
						 * Report
						 */

		/* If the controller supports the LE Read Local P-256
		 * Public Key command, enable the corresponding event.
		 */
		if (hdev->commands[34] & 0x02)
			events[0] |= 0x80;	/* LE Read Local P-256
						 * Public Key Complete
						 */

		/* If the controller supports the LE Generate DHKey
		 * command, enable the corresponding event.
		 */
		if (hdev->commands[34] & 0x04)
			events[1] |= 0x01;	/* LE Generate DHKey Complete */

		hci_req_add(req, HCI_OP_LE_SET_EVENT_MASK, sizeof(events),
			    events);

		if (hdev->commands[25] & 0x40) {
			/* Read LE Advertising Channel TX Power */
			hci_req_add(req, HCI_OP_LE_READ_ADV_TX_POWER, 0, NULL);
		}

		if (hdev->commands[26] & 0x40) {
			/* Read LE White List Size */
			hci_req_add(req, HCI_OP_LE_READ_WHITE_LIST_SIZE,
				    0, NULL);
		}

		if (hdev->commands[26] & 0x80) {
			/* Clear LE White List */
			hci_req_add(req, HCI_OP_LE_CLEAR_WHITE_LIST, 0, NULL);
		}

		if (hdev->le_features[0] & HCI_LE_DATA_LEN_EXT) {
			/* Read LE Maximum Data Length */
			hci_req_add(req, HCI_OP_LE_READ_MAX_DATA_LEN, 0, NULL);

			/* Read LE Suggested Default Data Length */
			hci_req_add(req, HCI_OP_LE_READ_DEF_DATA_LEN, 0, NULL);
		}

		hci_set_le_support(req);
	}

	/* Read features beyond page 1 if available */
	for (p = 2; p < HCI_MAX_PAGES && p <= hdev->max_page; p++) {
		struct hci_cp_read_local_ext_features cp;

		cp.page = p;
		hci_req_add(req, HCI_OP_READ_LOCAL_EXT_FEATURES,
			    sizeof(cp), &cp);
	}
}
4.2.7. 第四阶段
static void hci_init4_req(struct hci_request *req, unsigned long opt)
{
	struct hci_dev *hdev = req->hdev;

	/* Some Broadcom based Bluetooth controllers do not support the
	 * Delete Stored Link Key command. They are clearly indicating its
	 * absence in the bit mask of supported commands.
	 *
	 * Check the supported commands and only if the the command is marked
	 * as supported send it. If not supported assume that the controller
	 * does not have actual support for stored link keys which makes this
	 * command redundant anyway.
	 *
	 * Some controllers indicate that they support handling deleting
	 * stored link keys, but they don't. The quirk lets a driver
	 * just disable this command.
	 */
	if (hdev->commands[6] & 0x80 &&
	    !test_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks)) {
		struct hci_cp_delete_stored_link_key cp;

		bacpy(&cp.bdaddr, BDADDR_ANY);
		cp.delete_all = 0x01;
		hci_req_add(req, HCI_OP_DELETE_STORED_LINK_KEY,
			    sizeof(cp), &cp);
	}

	/* Set event mask page 2 if the HCI command for it is supported */
	if (hdev->commands[22] & 0x04)
		hci_set_event_mask_page_2(req);

	/* Read local codec list if the HCI command is supported */
	if (hdev->commands[29] & 0x20)
		hci_req_add(req, HCI_OP_READ_LOCAL_CODECS, 0, NULL);

	/* Get MWS transport configuration if the HCI command is supported */
	if (hdev->commands[30] & 0x08)
		hci_req_add(req, HCI_OP_GET_MWS_TRANSPORT_CONFIG, 0, NULL);

	/* Check for Synchronization Train support */
	if (lmp_sync_train_capable(hdev))
		hci_req_add(req, HCI_OP_READ_SYNC_TRAIN_PARAMS, 0, NULL);

	/* Enable Secure Connections if supported and configured */
	if (hci_dev_test_flag(hdev, HCI_SSP_ENABLED) &&
	    bredr_sc_enabled(hdev)) {
		u8 support = 0x01;

		hci_req_add(req, HCI_OP_WRITE_SC_SUPPORT,
			    sizeof(support), &support);
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2182378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot | Maven快速上手

文章目录 一、Maven1.1 Maven 简介&#xff1a;1.2 Maven 的核心功能&#xff1a;1.2.1 项目构建&#xff1a;1.2.2 依赖管理&#xff1a; 1.3 Maven 仓库&#xff1a;1.3.1 本地仓库&#xff1a;1.3.2 中央仓库&#xff1a;1.3.3 私服&#xff1a; 二、第一个 SpringBoot 程序…

Spring Boot与足球青训后台系统的协同

3 系统分析 3.1 可行性分析 可行性分析是该平台系统进行投入开发的基础第一步&#xff0c;必须对其进行可行性分析才能够降低不必要的需要从而使资源合理利用&#xff0c;更具有性价比和降低成本&#xff0c;同时也是系统平台的成功的未雨绸缪的一步。 3.1.1 技术可行性 技术可…

# linux从入门到精通--从基础学起,逐步提升,探索linux奥秘(五)

linux从入门到精通–从基础学起&#xff0c;逐步提升&#xff0c;探索linux奥秘&#xff08;五&#xff09; 一、linux进阶指令&#xff08;1&#xff09; 1、df指令 1&#xff09;作用&#xff1a;查看磁盘的空间 2&#xff09;语法&#xff1a;#df -h -h表示以可读性较高的…

LeetCode 面试经典150题 50.Pow(x,n)

题目&#xff1a;实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c; &#xff09;。 思路&#xff1a; 代码&#xff1a; class Solution {public double myPow(double x, int n) {double ans 1;long N n;if (N < 0) {N -N;x 1 / x;}…

微服务JMeter解析部署使用全流程

目录 1、介绍 2、下载 3、运行 4、设置简体中文版 5、开始测试 1、添加线程组 2、添加监听器 3、添加请求 先.测试userController里的查询方法 6、查看结果 1、查看结果树 2、汇总报告 3、聚合报告 7、JMeter报错 1、介绍 Apache JMeter 是 Apache 组织基于 Java…

Edge SCDN:安全与速度并进的解决方案

在当今数字化时代&#xff0c;网络速度与安全成为企业不可忽视的双刃剑。Edge SCDN&#xff08;边缘安全加速&#xff09;正重新定义着内容分发与安全防护的边界&#xff0c;它不仅优化了内容传输的速度&#xff0c;还强化了网络的安全防护能力&#xff0c;为企业构建了一个既快…

docker的harbor仓库登录问题

目录 一、问题描述 二、证书信任问题 三、DNS解析问题 四、解决 参考链接&#xff1a;Docker login Harbor报错解决&#xff1a;Error response from daemon: Get https:..-阿里云开发者社区 一、问题描述 问题&#xff1a; 挂机或者挂机重启之后harbor登录不上 查看日…

【Android 14源码分析】WMS-窗口显示-第二步:relayoutWindow -1

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

贝锐蒲公英工业物联方案:助力美的智慧楼宇全球布局

智慧楼宇正日益成为现代城市发展的基石&#xff0c;作为该领域的先锋&#xff0c;美的楼宇科技通过其创新的iBUILDING数字化平台和低碳技术&#xff0c;引领着智慧空间的可持续发展&#xff0c;并持续推动建筑及相关行业的数字化转型。 美的楼宇科技的解决方案融合了先进的楼宇…

六,MyBatis-Plus 扩展功能(逻辑删除,通用枚举,字段类型处理,自动填充功能,防全表更新与删除插件,MybatisX快速开发插件)

六&#xff0c;MyBatis-Plus 扩展功能&#xff08;逻辑删除&#xff0c;通用枚举&#xff0c;字段类型处理&#xff0c;自动填充功能&#xff0c;防全表更新与删除插件&#xff0c;MybatisX快速开发插件&#xff09; 文章目录 六&#xff0c;MyBatis-Plus 扩展功能&#xff08;…

51单片机学习第六课---B站UP主江协科技

DS18B20 1、基本知识讲解 2、DS18B20读取温度值 main.c #include<regx52.h> #include"delay.h" #include"LCD1602.h" #include"key.h" #include"DS18B20.h"float T; void main () {LCD_Init();LCD_ShowString(1,1,"temp…

领域偏移:协变量移位下的域自适应

现在我们将焦点转移到一种叫做协变量转移的扰动上。我们在一个分类或回归设置中工作&#xff0c;我们希望从x预测y&#xff0c;并假设p≈(y | x)和p∗(y | x)是相同的(标记函数在训练和测试之间不会改变) 假设 (Covariate Shift)。对于列车分布p~和检验分布p∗&#xff0c;我们…

安达发|纺织行业APS系统中的物料替代解决方案

在纺织行业中&#xff0c;物料替代是应对原材料短缺、成本波动和供应链不确定性的一种重要策略。高级计划与排程系统&#xff08;APS&#xff09;通过集成物料替代功能&#xff0c;可以帮助企业在保持生产效率的同时&#xff0c;灵活应对市场变化。本文将探讨纺织行业在APS系统…

Leetcode Hot 100 | 543.二叉树的直径 | 递归+优化

写法一 自己一开始直接写的&#xff0c;没考虑时间复杂度… class Solution {/*递归思路&#xff1a;不准进递归&#xff08;除非之后用简单例子验证一下&#xff09;将方法按照自己想要返回的值来补充其他的代码细节&#xff1b;用最值来模拟返回结果补充代码细节&#xff0…

Win10鼠标总是频繁自动失去焦点-非常有效-重启之后立竿见影

针对Win10鼠标频繁自动失去焦点的问题&#xff0c;可以尝试以下解决方案&#xff1a; 一、修改注册表&#xff08;最有效的方法-重启之后立竿见影&#xff09; 打开注册表编辑器&#xff1a; 按下WindowsR组合键&#xff0c;打开运行窗口。在运行窗口中输入“regedit”&#x…

什么是reactor以及其三种版本

写在前面 本文来看下什么是reactor以及其三种版本。 1&#xff1a;什么是reactor以及其三种版本 为了更好的理解什么是reactor&#xff0c;我们结合现实生活中的例子来看下。 翠花是个貌美如花的姑娘&#xff0c;人称赛东施&#xff0c;她的梦想是嫁给王子&#xff0c;可是天…

【机器学习-无监督学习】降维与主成分分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…

揭秘帕金森病:多因素交织下的“沉默杀手”

在老年人群中&#xff0c;帕金森病如同一位“沉默的杀手”&#xff0c;悄然侵袭着无数人的生活。它以其独特的静止性震颤、运动迟缓、肌强直和姿势平衡障碍等症状&#xff0c;让患者的生活质量大打折扣。那么&#xff0c;帕金森病究竟是如何得的呢&#xff1f;本文将带您深入探…

基于Springboot+Vue的基于协同过滤算法的个性化音乐推荐系统 (含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统中…

Android 13.0 系统wifi列表显示已连接但无法访问网络问题解决

1.前言 在13.0的系统rom产品定制化开发中,在wifi模块也很重要,但是在某些情况下对于一些wifi连接成功后,确显示已连接成功,但是无法访问互联网 的情况,所以实际上这时可以正常上网的,就是显示的不正常,所以就需要分析连接流程然后解决问题 如图所示: 2.系统wifi列表显示…