一知半解的蓝牙

背景:

        最近在项目上遇到一个蓝牙的问题,项目基于嵌入式linux开发,系统上也没有集成bluetoothd服务,移植了一个Bluez的代码,开发了一个BLE的功能,与手机app端进行通信;基本功能没有什么问题,但根据反馈,连接稳定性不好,通常是有干扰,最大的问题就是,有概率在连接断开之后,手机APP再也连接不上,需要重跑嵌入式程序,手机APP才能重新建立连接;

        我也没有接触过蓝牙协议栈的代码,带着问题去分析了一下源代码,也不知道理解得对不对,各位看官如果发现我理解不对的地方,还请告知在下;

分析:

        从开发工程师上了解到,他使用的是开源的Bluez-5.63版本的代码,使用里面的peripheral目录下的例子修改的;我看了一下,peripheral下一共就没有几个文件:

        e6db72ad07b8486f826a3c622abe8e4d.png

        其中, attach.c,通过名称可以大概看出应该是和蓝牙绑定相关的,里面实现了打开串口的操作,还有就是通过串口的fd进行ioctl,对蓝牙的一些标志的设置和复位。

static int open_serial(const char *path);

static int attach_proto(const char *path, unsigned int proto,
						unsigned int flags);

void attach_start(void);

void attach_stop(void);

        efivars.c是efi相关的变量操作,具体是干啥用的我也没搞懂,主要提供了对变量的读取和写入的两个接口;

int efivars_read(const char *name, uint32_t *attributes,
					void *data, size_t size);

int efivars_write(const char *name, uint32_t attributes,
					const void *data, size_t size);

        log.c是类似提供了日志的功能,但是好像代码中也没有用上这个接口,所以这个好像也没什么用;

void log_open(void);
// 打开了/dev/kmsg节点,返回kmsg_fd

void log_close(void);
//关闭kmsg_fd

        剩下的main.c、gap.c和gatt.c是这次分析的主要对象,大部分的功能都是在这几个文件中实现的,我这里根据main的流程去分析。

int main(int argc, char *argv[])
{
	int exit_status;

	if (getpid() == 1 && getppid() == 0)
		is_init = true;

	mainloop_init();

	printf("Bluetooth periperhal ver %s\n", VERSION);

	prepare_filesystem();

	if (is_init) {
		uint8_t addr[6];

		if (efivars_read("BluetoothStaticAddress", NULL,
							addr, 6) < 0) {
			printf("Generating new persistent static address\n");

			if (util_getrandom(addr, sizeof(addr), 0) < 0) {
				perror("Failed to get random static address");
				return EXIT_FAILURE;
			}
			/* Overwrite the MSB to make it a static address */
			addr[5] = 0xc0;

			efivars_write("BluetoothStaticAddress",
					EFIVARS_NON_VOLATILE |
					EFIVARS_BOOTSERVICE_ACCESS |
					EFIVARS_RUNTIME_ACCESS,
					addr, 6);
		}

		gap_set_static_address(addr);

		run_shell();
	}

	log_open();
	gap_start();

	if (is_init)
		attach_start();

	exit_status = mainloop_run_with_signal(signal_callback, NULL);

	if (is_init)
		attach_stop();

	gap_stop();
	log_close();

	return exit_status;
}

        先看看大的流程,main里面调用了mainloop_init进行了初始化,这个mainloop_init没有在main里面定义,这里先不分析。

        prepare_filesystem和efivars_read、efivars_write主要的作用就是看有没有设置BluetoothStaticAddress这个变量,如果有,就读取这个变量的内容来设置一个静态的MAC地址。设置静态MAC地址这里调用的是gap_set_static_address函数进行设置,gap_set_static_address函数是在gap.c里定义的,其实就是把addr赋值给gap.c里的全局变量而已,完全可以自己修改这个全局数组实现。

static uint8_t static_addr[6] = { 0x90, 0x78, 0x56, 0x34, 0x12, 0xc0 };

void gap_set_static_address(uint8_t addr[6])
{
	memcpy(static_addr, addr, sizeof(static_addr));

	printf("Using static address %02x:%02x:%02x:%02x:%02x:%02x\n",
			static_addr[5], static_addr[4], static_addr[3],
			static_addr[2], static_addr[1], static_addr[0]);
}

        然后就是gap_start函数了,之后是mainloop_run_with_signal,看上去是进入主循环了,main函数会在这里阻塞住,直到signal的到来,然后函数返回,执行gap_stop函数。

        重点看一下gap_start函数,这个函数在gap.c中定义

static struct mgmt *mgmt = NULL;

void gap_start(void)
{
	mgmt = mgmt_new_default();
	if (!mgmt) {
		fprintf(stderr, "Failed to open management socket\n");
		return;
	}

	if (!mgmt_send(mgmt, MGMT_OP_READ_COMMANDS,
				MGMT_INDEX_NONE, 0, NULL,
				read_commands_complete, NULL, NULL)) {
		fprintf(stderr, "Failed to read supported commands\n");
		return;
	}
}

        这个函数中,使用mgmt_new_default创建了mgmt,这个mgmt是个全局变量指针,文件中有大量的mgmt_send函数会使用这个指针作为句柄进行消息的发送,看上去像是一个类似socket的东西;

        mgmt_new_default函数在src/shared/mgmt.c中定义

struct mgmt *mgmt_new_default(void)
{
	struct mgmt *mgmt;
	union {
		struct sockaddr common;
		struct sockaddr_hci hci;
	} addr;
	int fd;

	fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
								BTPROTO_HCI);
	if (fd < 0)
		return NULL;

	memset(&addr, 0, sizeof(addr));
	addr.hci.hci_family = AF_BLUETOOTH;
	addr.hci.hci_dev = HCI_DEV_NONE;
	addr.hci.hci_channel = HCI_CHANNEL_CONTROL;

	if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) {
		close(fd);
		return NULL;
	}

	mgmt = mgmt_new(fd);
	if (!mgmt) {
		close(fd);
		return NULL;
	}

	mgmt->close_on_unref = true;

	return mgmt;
}

        从函数中可以看出,mgmt确实和socket有关,它绑定了一个socket的fd句柄,这个socket是使用PF_BLUETOOTH创建的,绑定的地址中,使用了HCI_CHANNEL_CONTROL作为hci的channel,从这些信息中分析,这个mgmt应该就是可蓝牙管理控制相关的句柄。

struct mgmt {
	int ref_count;
	int fd;
	bool close_on_unref;
	struct io *io;
	bool writer_active;
	struct queue *request_queue;
	struct queue *reply_queue;
	struct queue *pending_list;
	struct queue *notify_list;
	unsigned int next_request_id;
	unsigned int next_notify_id;
	bool need_notify_cleanup;
	bool in_notify;
	void *buf;
	uint16_t len;
	uint16_t mtu;
	mgmt_debug_func_t debug_callback;
	mgmt_destroy_func_t debug_destroy;
	void *debug_data;
};

        从mgmt的结构体定义中看到,除了这个fd之外,结构体中一个io的数据,还有几个队列的数据,还有mtu等等的参数。其中这个io是比较关键的数据,可以进一步分析一下这个数据:

	mgmt->io = io_new(fd);
	if (!mgmt->io) {
		free(mgmt->buf);
		free(mgmt);
		return NULL;
	}

        mgmt初始化的过程中,把socket得到的fd进行了一次io_new操作,映射到这个mgmt->io上了,这个io_new函数在bluez中有好几个定义的地方,追查代码发现,应该是对应src/shared/io-mainloop.c中定义的:


struct io *io_new(int fd)
{
	struct io *io;

	if (fd < 0)
		return NULL;

	io = new0(struct io, 1);
	io->fd = fd;
	io->events = 0;
	io->close_on_destroy = false;

	if (mainloop_add_fd(io->fd, io->events, io_callback,
						io, io_cleanup) < 0) {
		free(io);
		return NULL;
	}

	return io_ref(io);
}

        这里不难看出,它把这个fd通过mainloop_add_fd的函数添加到mainloop里面去了,这个函数有点像epoll的多并发IO操作,把某个fd添加到epoll里,并设置event事件,有事件到来,就调用io_callback函数;到这里也只是我的猜想,要证实那就继续追踪mainloop_add_fd函数,看看实现;

        mainloop_add_fd函数的定义是在src/shared/mainloop.c中定义的。

int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback,
				void *user_data, mainloop_destroy_func destroy)
{
	struct mainloop_data *data;
	struct epoll_event ev;
	int err;

	if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1 || !callback)
		return -EINVAL;

	data = malloc(sizeof(*data));
	if (!data)
		return -ENOMEM;

	memset(data, 0, sizeof(*data));
	data->fd = fd;
	data->events = events;
	data->callback = callback;
	data->destroy = destroy;
	data->user_data = user_data;

	memset(&ev, 0, sizeof(ev));
	ev.events = events;
	ev.data.ptr = data;

	err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev);
	if (err < 0) {
		free(data);
		return err;
	}

	mainloop_list[fd] = data;

	return 0;
}

        可以看到,确实和我想象的一样,就是通过epoll_ctl接口把对应的fd和event添加到epoll的监听列表里了,事件关联的data的数据结构是mainloop_data,这里面包含了两个函数指针和一个用户数据指针。

struct mainloop_data {
	int fd;
	uint32_t events;
	mainloop_event_func callback;
	mainloop_destroy_func destroy;
	void *user_data;
};

        回到最开始,epoll的创建是在mainloop_init函数中完成的,这也就是mian流程里一开始调用了mainloop_init的作用,就是为了创建epoll并初始化相关的资源,为后续mgmt_new_default创建的socket可以进行监听。换句话说,mgmt_new_default其实就是创建了一个socket,并放到epoll的监听列表里监听了起来。

        但在gap层面,mgmt_new_default并没有看到fd对应的回调的设置,这个fd如果有事件发生,是不是不需要gap进行介入处理?带着这个问题,我们回到gap_start继续分析;

数据发送流程:

        mgmt_new_default之后,gap_start中调用一个mgmt_send函数就没有其他操作了,所以猫腻可能还在mgmt_send函数中。mgmt_send的定义也是在src/shared/mgmt.c中,和mgmt_new_default这个是同一个层面;

unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index,
				uint16_t length, const void *param,
				mgmt_request_func_t callback,
				void *user_data, mgmt_destroy_func_t destroy)
{
	struct mgmt_request *request;

	if (!mgmt)
		return 0;

	request = create_request(mgmt, opcode, index, length, param,
					callback, user_data, destroy);
	if (!request)
		return 0;

	if (mgmt->next_request_id < 1)
		mgmt->next_request_id = 1;

	request->id = mgmt->next_request_id++;

	if (!queue_push_tail(mgmt->request_queue, request)) {
		free(request->buf);
		free(request);
		return 0;
	}

	wakeup_writer(mgmt);

	return request->id;
}

        从这个函数中可以看出,它调用了create_request函数创建了一个请求,然后把这个请求放到了mgmt->request_queue队列的尾部,然后调用wakeup_writer(mgmt)就没啥操作了;从这里看出,每一个请求,都对应有一个request->id,这个id是由mgmt里的next_request_id统一管理的,每对mgmt发一个请求,序号就递增;

        这里可以顺便追踪一下消息发送的流程。跟踪这个mgmt->request_queue队列可以发现,在执行wakeup_writer函数的时候,会设置mgmt->io的EPOLLOUT事件,也就是前面说的socket的fd,如果这个fd是可以写的,就交给epoll去完成IO的操作;

bool io_set_write_handler(struct io *io, io_callback_func_t callback,
				void *user_data, io_destroy_func_t destroy)
{
	uint32_t events;

	if (!io || io->fd < 0)
		return false;

	if (io->write_destroy)
		io->write_destroy(io->write_data);

	if (callback)
		events = io->events | EPOLLOUT;
	else
		events = io->events & ~EPOLLOUT;

	io->write_callback = callback;
	io->write_destroy = destroy;
	io->write_data = user_data;

	if (events == io->events)
		return true;

	if (mainloop_modify_fd(io->fd, events) < 0)
		return false;

	io->events = events;

	return true;
}

        了解epoll模型的都知道,如果有EPOLLOUT事件,也就是fd可以写的时候,会触发回调,也就是wakeup_writer函数中的can_write_data函数最终会被调用;这个函数会最终触发发送数据的函数。

static bool can_write_data(struct io *io, void *user_data)
{
	struct mgmt *mgmt = user_data;
	struct mgmt_request *request;
	bool can_write;

	request = queue_pop_head(mgmt->reply_queue);
	if (!request) {
		/* only reply commands can jump the queue */
		if (!queue_isempty(mgmt->pending_list))
			return false;

		request = queue_pop_head(mgmt->request_queue);
		if (!request)
			return false;

		can_write = false;
	} else {
		/* allow multiple replies to jump the queue */
		can_write = !queue_isempty(mgmt->reply_queue);
	}

	if (!send_request(mgmt, request))
		return true;

	return can_write;
}

        send_request函数最终会调用io_send函数,把IO请求发送出去,io_send函数也有多个地方定义,根据前面的逻辑,应该也是io_mainloop.c里面的定义的,最终是调用writev系统调用接口将数据发送给内核;

ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt)
{
	ssize_t ret;

	if (!io || io->fd < 0)
		return -ENOTCONN;

	do {
		ret = writev(io->fd, iov, iovcnt);
	} while (ret < 0 && errno == EINTR);

	if (ret < 0)
		return -errno;

	return ret;
}

        应用层mgmt_send的数据发送流程就分析到这里,再深入就是内核驱动上面了。总的概括,mgmt_send发送的数据是通过mgmt->io进行交互的,最终的数据是通过系统调用writev发送给内核的;

数据接收流程:

        看完数据的发送流程,只知道gap_start函数中把数据发送出去了,还需要分析一下数据是怎么接收回来的。

        在前面讲到的mgmt_new_default函数中,除了创建一个socket的fd之外,其实在mgmt_new函数中还创建了一系列的队列,也包括前面发送流程中的请求队列,还有接收队列等等;

	mgmt->request_queue = queue_new();
	mgmt->reply_queue = queue_new();
	mgmt->pending_list = queue_new();
	mgmt->notify_list = queue_new();

        除了这些基本的数据结构,还对这个socket的fd的io设置了回调函数:


	if (!io_set_read_handler(mgmt->io, can_read_data, mgmt, NULL)) {
		queue_destroy(mgmt->notify_list, NULL);
		queue_destroy(mgmt->pending_list, NULL);
		queue_destroy(mgmt->reply_queue, NULL);
		queue_destroy(mgmt->request_queue, NULL);
		io_destroy(mgmt->io);
		free(mgmt->buf);
		free(mgmt);
		return NULL;
	}

        不出意外的话,这个io_set_read_handler函数还是在io_mainloop.c中定义的:

bool io_set_read_handler(struct io *io, io_callback_func_t callback,
				void *user_data, io_destroy_func_t destroy)
{
	uint32_t events;

	if (!io || io->fd < 0)
		return false;

	if (io->read_destroy)
		io->read_destroy(io->read_data);

	if (callback)
		events = io->events | EPOLLIN;
	else
		events = io->events & ~EPOLLIN;

	io->read_callback = callback;
	io->read_destroy = destroy;
	io->read_data = user_data;

	if (events == io->events)
		return true;

	if (mainloop_modify_fd(io->fd, events) < 0)
		return false;

	io->events = events;

	return true;
}

        还是熟悉的epoll模型,对这个fd进行了可读事件的绑定,当fd可以读的时候,会回调对应的callback函数,也就是mgmt_new中设置的can_read_data函数。

static bool can_read_data(struct io *io, void *user_data)
{
	struct mgmt *mgmt = user_data;
	struct mgmt_hdr *hdr;
	struct mgmt_ev_cmd_complete *cc;
	struct mgmt_ev_cmd_status *cs;
	ssize_t bytes_read;
	uint16_t opcode, event, index, length;

	bytes_read = read(mgmt->fd, mgmt->buf, mgmt->len);
	if (bytes_read < 0)
		return false;

	util_hexdump('>', mgmt->buf, bytes_read,
				mgmt->debug_callback, mgmt->debug_data);

	if (bytes_read < MGMT_HDR_SIZE)
		return true;

	hdr = mgmt->buf;
	event = btohs(hdr->opcode);
	index = btohs(hdr->index);
	length = btohs(hdr->len);

	if (bytes_read < length + MGMT_HDR_SIZE)
		return true;

	mgmt_ref(mgmt);

	switch (event) {
	case MGMT_EV_CMD_COMPLETE:
		cc = mgmt->buf + MGMT_HDR_SIZE;
		opcode = btohs(cc->opcode);

		util_debug(mgmt->debug_callback, mgmt->debug_data,
				"[0x%04x] command 0x%04x complete: 0x%02x",
						index, opcode, cc->status);

		request_complete(mgmt, cc->status, opcode, index, length - 3,
						mgmt->buf + MGMT_HDR_SIZE + 3);
		break;
	case MGMT_EV_CMD_STATUS:
		cs = mgmt->buf + MGMT_HDR_SIZE;
		opcode = btohs(cs->opcode);

		util_debug(mgmt->debug_callback, mgmt->debug_data,
				"[0x%04x] command 0x%02x status: 0x%02x",
						index, opcode, cs->status);

		request_complete(mgmt, cs->status, opcode, index, 0, NULL);
		break;
	default:
		util_debug(mgmt->debug_callback, mgmt->debug_data,
				"[0x%04x] event 0x%04x", index, event);

		process_notify(mgmt, event, index, length,
						mgmt->buf + MGMT_HDR_SIZE);
		break;
	}

	mgmt_unref(mgmt);

	return true;
}

        can_read_data函数是接收内核消息的回调函数,根据event的类型分为MGMT_EV_CMD_COMPLETE和MGMT_EV_CMD_STATUS这些,这里的event和上面说的epoll的event不是一个概念,这里的event对应的是mgmt消息的opcode;暂时也不用管这些类型,从 can_read_data函数可以看到,不管是哪种类型,都会调用request_complete函数进行对内核消息的处理。

static void request_complete(struct mgmt *mgmt, uint8_t status,
					uint16_t opcode, uint16_t index,
					uint16_t length, const void *param)
{
	struct opcode_index match = { .opcode = opcode, .index = index };
	struct mgmt_request *request;

	request = queue_remove_if(mgmt->pending_list,
					match_request_opcode_index, &match);
	if (!request) {
		util_debug(mgmt->debug_callback, mgmt->debug_data,
				"Unable to find request for opcode 0x%04x",
				opcode);

		/* Attempt to remove with no opcode */
		request = queue_remove_if(mgmt->pending_list,
						match_request_index,
						UINT_TO_PTR(index));
	}

	if (request) {
		if (request->callback)
			request->callback(status, length, param,
							request->user_data);

		destroy_request(request);
	}

	wakeup_writer(mgmt);
}

        request_complete函数中,从mgmt->pending_list队列里查找与opcode和index均匹配的request,并调用request->callback函数。这里,实际上找到匹配的request后,就会从mgmt->pending_list列表里删除这个request了,但是request的空间还没有释放,调用request->callback完成后,使用destroy_request(request)函数释放掉真正的request空间。

        这里的request->callback就是每个mgmt_send函数传递下来的callback函数指针。前面说到,mgmt_send发送数据的时候是最终调用到send_request函数发送的,send_request函数在调用io_send之后,还会把这个请求放到mgmt->pending_list队列中,便于接收内核消息,只有收到内核消息之后,这个request才会真正完成使命。

        总的来说,mgmt监听并接收内核消息的动作在mgmt_new_default的过程就已经设置好了,每个消息通过mgmt_send发送的时候,只需要绑定对应的回调函数,内核有这个消息的回复的时候,就会自动触发这个回调函数,总的消息接收过程大概是这个样子。

蓝牙GAP初始化流程:

        有了上面说的数据发送流程和数据接收流程的基础之后,我们继续分析gap_start的过程。gap_start中我们更关注的是消息的内容了。

        从前面的逻辑可以知道,gap_start中发送了MGMT_OP_READ_COMMANDS这个命令之后,如果内核收到并回复的话,会触发read_commands_complete函数。

static void read_commands_complete(uint8_t status, uint16_t len,
					const void *param, void *user_data)
{
	const struct mgmt_rp_read_commands *rp = param;
	uint16_t num_commands;
	bool ext_index_list = false;
	int i;

	if (status) {
		fprintf(stderr, "Reading index list failed: %s\n",
						mgmt_errstr(status));
		return;
	}

	num_commands = le16_to_cpu(rp->num_commands);

	for (i = 0; i < num_commands; i++) {
		uint16_t op = get_le16(rp->opcodes + 1);

		if (op == MGMT_OP_READ_EXT_INDEX_LIST)
			ext_index_list = true;
		else if (op == MGMT_OP_READ_ADV_FEATURES)
			adv_features = true;
	}

	if (ext_index_list) {
		mgmt_register(mgmt, MGMT_EV_EXT_INDEX_ADDED, MGMT_INDEX_NONE,
					ext_index_added_event, NULL, NULL);
		mgmt_register(mgmt, MGMT_EV_EXT_INDEX_REMOVED, MGMT_INDEX_NONE,
					ext_index_removed_event, NULL, NULL);

		if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST,
				MGMT_INDEX_NONE, 0, NULL,
				read_ext_index_list_complete, NULL, NULL)) {
			fprintf(stderr, "Failed to read extended index list\n");
			return;
		}
	} else {
		mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE,
					index_added_event, NULL, NULL);
		mgmt_register(mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE,
					index_removed_event, NULL, NULL);

		if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST,
				MGMT_INDEX_NONE, 0, NULL,
				read_index_list_complete, NULL, NULL)) {
			fprintf(stderr, "Failed to read index list\n");
			return;
		}
	}
}

        read_commands_complete函数中,对应的消息数据结构是struct mgmt_rp_read_commands

struct mgmt_rp_read_commands {
	uint16_t num_commands;
	uint16_t num_events;
	uint16_t opcodes[0];
} __packed;

        从这个数据结构中,可以大概知道,大概率是查询蓝牙控制器支持多少个命令,支持多少事件等等;

        但这里似乎有个bug,我不知道是什么原因,for循环中应该是通过i去遍历所有的支持的命令,但是确使用了+1的操作,如果是+1,根本没必要循环,作者的本意应该是遍历吧,有没有知道的看官?我看了一下github上的代码也是+1,这里是我没有搞懂的地方;

a36e2ff842224b16b57ca608fd63010c.png

        通过控制器是否支持MGMT_OP_READ_EXT_INDEX_LIST确定以哪种方式注册不同的事件函数回调。

        mgmt_register函数应该和mgmt_send函数类似,注册的对应事件后,对应的回调函数会被触发。

        通过跟踪上面的几个情况,会发现,无论是哪种情况,回调函数都是统一进行了发送MGMT_OP_READ_INFO命令,并都绑定了read_info_complete函数。也就是说,无论MGMT_OP_READ_COMMANDS命令发送后内核消息返回哪一个,最终都会触发MGMT_OP_READ_INFO的发送,并最后回调到read_info_complete函数。那我们只需要继续关注read_info_complete函数就行。

        MGMT_OP_READ_INFO命令对应返回的消息数据结构是struct mgmt_rp_read_info:

struct mgmt_rp_read_info {
	bdaddr_t bdaddr;
	uint8_t version;
	uint16_t manufacturer;
	uint32_t supported_settings;
	uint32_t current_settings;
	uint8_t dev_class[3];
	uint8_t name[MGMT_MAX_NAME_LENGTH];
	uint8_t short_name[MGMT_MAX_SHORT_NAME_LENGTH];
} __packed;

        这里面包含了控制器的MAC地址,版本,厂家等信息,以及supported_settings,表示支持的设置,current_settings表示当前的设置,还有名称、类型等内容。

        read_info_complete函数里面根据支持的设置和当前的设置,注册了一系列的事件回调,用于监听蓝牙的事件发生,这其中就包括设置变更事件、名称改变事件、秘钥变更事件、连接事件、断开连接事件、认证失败事件、配对事件、取消配对事件、连接参数更新事件、广播事件等;然后就是初始化流程:

        1、发送MGMT_OP_SET_POWERED命令关闭蓝牙;

        2、发送MGMT_SETTING_LE设置蓝牙是BLE模式;

        3、发送MGMT_OP_SET_SECURE_CONN设置蓝牙加密连接;

        4、发送MGMT_SETTING_DEBUG_KEYS关闭调试秘钥;

        5、发送MGMT_SETTING_BONDABLE设置可以被发现;

        6、发送MGMT_OP_SET_STATIC_ADDRESS设置蓝牙地址;

        7、发送MGMT_OP_READ_ADV_FEATURES设置广播信息;

        函数中还调用gatt_server_start函数进行了Gatt服务的初始化流程;从上面的各种事件和下发命令看出,gap应该处理蓝牙连接相关的功能,包括配对、连接、广播等等;gap.c大概的工作内容就是这些,还有部分就是各个事件回调的处理,这里的gap.c比较简单,各个事件回调只是调用printf进行打印了一下,并没有做什么实际性的处理。

蓝牙GATT初始化流程:

        上面的gap初始化流程中,在初始化的过程中,调用了gatt_server_start函数触发了GATT层的初始化,这里看看gatt层主要做了哪些工作。

void gatt_server_start(void)
{
	struct sockaddr_l2 addr;

	if (att_fd >= 0)
		return;

	att_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_CLOEXEC,
							BTPROTO_L2CAP);
	if (att_fd < 0) {
		fprintf(stderr, "Failed to create ATT server socket: %m\n");
		return;
	}

	memset(&addr, 0, sizeof(addr));
	addr.l2_family = AF_BLUETOOTH;
	addr.l2_cid = htobs(ATT_CID);
	memcpy(&addr.l2_bdaddr, static_addr, 6);
	addr.l2_bdaddr_type = BDADDR_LE_RANDOM;

	if (bind(att_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		fprintf(stderr, "Failed to bind ATT server socket: %m\n");
		close(att_fd);
		att_fd = -1;
		return;
	}

	if (listen(att_fd, 1) < 0) {
		fprintf(stderr, "Failed to listen on ATT server socket: %m\n");
		close(att_fd);
		att_fd = -1;
		return;
	}

	gatt_db = gatt_db_new();
	if (!gatt_db) {
		close(att_fd);
		att_fd = -1;
		return;
	}

	populate_gap_service(gatt_db);
	populate_devinfo_service(gatt_db);

	gatt_cache = gatt_db_new();

	conn_list = queue_new();
	if (!conn_list) {
		gatt_db_unref(gatt_db);
		gatt_db = NULL;
		close(att_fd);
		att_fd = -1;
		return;
	}

	mainloop_add_fd(att_fd, EPOLLIN, att_conn_callback, NULL, NULL);
}

        从gatt_server_start中可以看到,gatt也初始化了一个socket的att_fd句柄,然后设置了这个att_fd和BDADDR_LE_RANDOM以及static_addr绑定起来了,这个static_addr就是最开始说的main函数中设置的静态MAC地址,在gap中也有涉及这个地址,这个att_fd句柄最终通过mainloop_add_fd函数添加到了epoll监听列表里,有前面的基础就知道,如果这个att_fd有对应的EPOLLIN事件,会触发att_conn_callback函数回调,这个函数稍后再分析,先看看gatt_server_start还做了哪些事情。

        gatt_server_start中通过gatt_db_new函数获取了一个gatt_db

struct gatt_db *gatt_db_new(void)
{
	struct gatt_db *db;

	db = new0(struct gatt_db, 1);
	db->crypto = bt_crypto_new();
	db->services = queue_new();
	db->notify_list = queue_new();
	db->next_handle = 0x0001;

	return gatt_db_ref(db);
}

        可以看到这个gatt_db管理着服务列表和通知列表;

        gatt_server_start中还通过populate_gap_service和populate_devinfo_service两个函数注册了俩个服务,闭着眼睛想都知道,这两个服务肯定是挂载gatt_db->services的列表下了。

static void populate_gap_service(struct gatt_db *db)
{
	struct gatt_db_attribute *service;
	bt_uuid_t uuid;

	bt_uuid16_create(&uuid, UUID_GAP);
	service = gatt_db_add_service(db, &uuid, true, 6);

	bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
	gatt_db_service_add_characteristic(service, &uuid,
					BT_ATT_PERM_READ,
					BT_GATT_CHRC_PROP_READ,
					gap_device_name_read, NULL, NULL);

	gatt_db_service_set_active(service, true);
}

static void populate_devinfo_service(struct gatt_db *db)
{
	struct gatt_db_attribute *service;
	bt_uuid_t uuid;

	bt_uuid16_create(&uuid, 0x180a);
	service = gatt_db_add_service(db, &uuid, true, 17);

	gatt_db_service_set_active(service, true);
}

        从代码层面理解得话,每个服务有唯一的UUID,服务下有多个characteristic,每个characteristic也有唯一的UUID,每个characteristic通过gatt_db_service_add_characteristic函数与对应的service绑定在一起,每个characteristic有多个attribute,例如有读属性,写属性,通知属性等。最后通过gatt_db_service_set_active(service, true)激活服务。

        前面提到,gatt_server_start最终是绑定fd和att_conn_callback函数,当fd可以读的时候,对应了有蓝牙连接的事件,也就是说手机app连接蓝牙的时候,att_conn_callback函数会被调用。再看看是怎么处理蓝牙连接的。

static void att_conn_callback(int fd, uint32_t events, void *user_data)
{
	struct gatt_conn *conn;
	struct sockaddr_l2 addr;
	socklen_t addrlen;
	int new_fd;

	if (events & (EPOLLERR | EPOLLHUP)) {
		mainloop_remove_fd(fd);
		return;
	}

	memset(&addr, 0, sizeof(addr));
	addrlen = sizeof(addr);

	new_fd = accept(att_fd, (struct sockaddr *) &addr, &addrlen);
	if (new_fd < 0) {
		fprintf(stderr, "Failed to accept new ATT connection: %m\n");
		return;
	}

	conn = gatt_conn_new(new_fd);
	if (!conn) {
		fprintf(stderr, "Failed to create GATT connection\n");
		close(new_fd);
		return;
	}

	if (!queue_push_tail(conn_list, conn)) {
		fprintf(stderr, "Failed to add GATT connection\n");
		gatt_conn_destroy(conn);
		close(new_fd);
	}

	printf("New device connected\n");
}

        当有新的连接过来的时候,程序会通过accept函数接受一个连接。这个和TCP是挺类似的,前面也是使用socket-bind-listen-accept,也就是说,前面的监听的att_fd句柄就类似于TCP服务器上的socketFd,这里的accept返回的new_fd就对应于一个客户端的fd。然后gatt_conn_new函数应该把fd通过epoll监听起来,这样才能接受app发送的消息。

static struct gatt_conn *gatt_conn_new(int fd)
{
	struct gatt_conn *conn;
	uint16_t mtu = 0;

	conn = new0(struct gatt_conn, 1);
	if (!conn)
		return NULL;

	conn->att = bt_att_new(fd, false);
	if (!conn->att) {
		fprintf(stderr, "Failed to initialze ATT transport layer\n");
		free(conn);
		return NULL;
	}

	bt_att_set_close_on_unref(conn->att, true);
	bt_att_register_disconnect(conn->att, gatt_conn_disconnect, conn, NULL);

	bt_att_set_security(conn->att, BT_SECURITY_MEDIUM);

	conn->gatt = bt_gatt_server_new(gatt_db, conn->att, mtu, 0);
	if (!conn->gatt) {
		fprintf(stderr, "Failed to create GATT server\n");
		bt_att_unref(conn->att);
		free(conn);
		return NULL;
	}

	conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu, 0);
	if (!conn->gatt) {
		fprintf(stderr, "Failed to create GATT client\n");
		bt_gatt_server_unref(conn->gatt);
		bt_att_unref(conn->att);
		free(conn);
		return NULL;
	}

	bt_gatt_client_ready_register(conn->client, client_ready_callback,
								conn, NULL);
	bt_gatt_client_set_service_changed(conn->client,
				client_service_changed_callback, conn, NULL);

	return conn;
}

        这里没有那么明显的通过mainloop_add_fd监听客户端连接的fd,而是通过bt_att_new函数进行处理,底层应该也是这个接口。通过分析,也确实是这样,bt_att_new->bt_att_chan_new->io_new->mainloop_add_fd。

        bt_att_new函数除了监听fd,还设置了断开回调函数gatt_conn_disconnect,应该是蓝牙断开的时候会触发这个函数。还有一个是注册了client_ready_callback函数,从名称上看应该是客户端就绪的函数回调。断开回调函数也只是把这个conn从连接列表里移除和释放资源而已。

static void gatt_conn_disconnect(int err, void *user_data)
{
	struct gatt_conn *conn = user_data;

	printf("Device disconnected: %s\n", strerror(err));

	queue_remove(conn_list, conn);
	gatt_conn_destroy(conn);
}

        总体上,GATT层大概也是这些内容,不同的服务可能有不同的特征和属性,GATT完成后,app上蓝牙连接后,应该是能够看到这些注册的服务和服务有哪些特征、哪些属性等等。具体的消息交换,可能针对某个服务的某个特性进行的,不是针对整个gatt进行的,gatt只是提供了一个服务平台。

回归问题:

        整个peripheral框架大概的内容是分析完成了,从上面的分析可以看到,无论是gap还是gatt,都是依赖于mainloop中的epoll进行的,这也是我分析的是比较困惑的地方。可以先看看mainloop的实现。

        mainloop中定义了一个全局的指针数组,大小是128。

#define MAX_MAINLOOP_ENTRIES 128

static struct mainloop_data *mainloop_list[MAX_MAINLOOP_ENTRIES];

        在mainloop_init的时候,会将这128个数组全部都指向NULL;

void mainloop_init(void)
{
	unsigned int i;

	epoll_fd = epoll_create1(EPOLL_CLOEXEC);

	for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++)
		mainloop_list[i] = NULL;

	epoll_terminate = 0;

	mainloop_notify_init();
}

        mianloop_run函数会创建epoll,然后循环用epoll_wait来等待事件到来,然后进行处理。熟悉epoll模型的看官应该都清楚,这流程没有什么异常。

int mainloop_run(void)
{
	unsigned int i;

	while (!epoll_terminate) {
		struct epoll_event events[MAX_EPOLL_EVENTS];
		int n, nfds;

		nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1);
		if (nfds < 0)
			continue;

		for (n = 0; n < nfds; n++) {
			struct mainloop_data *data = events[n].data.ptr;

			data->callback(data->fd, events[n].events,
							data->user_data);
		}
	}

	for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) {
		struct mainloop_data *data = mainloop_list[i];

		mainloop_list[i] = NULL;

		if (data) {
			epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL);

			if (data->destroy)
				data->destroy(data->user_data);

			free(data);
		}
	}

	close(epoll_fd);
	epoll_fd = 0;

	mainloop_notify_exit();

	return exit_status;
}

        在监听fd的时候,上面会调用mainloop_add_fd来增加fd到epoll监听列表中,也就是通过epoll_ctl去增加监听事件。但是,对于mainloop_list的维护,这里确采用了fd作为数组的下标进行维护,我有点不理解。

int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback,
				void *user_data, mainloop_destroy_func destroy)
{
	struct mainloop_data *data;
	struct epoll_event ev;
	int err;

	if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1 || !callback)
		return -EINVAL;

	data = malloc(sizeof(*data));
	if (!data)
		return -ENOMEM;

	memset(data, 0, sizeof(*data));
	data->fd = fd;
	data->events = events;
	data->callback = callback;
	data->destroy = destroy;
	data->user_data = user_data;

	memset(&ev, 0, sizeof(ev));
	ev.events = events;
	ev.data.ptr = data;

	err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev);
	if (err < 0) {
		free(data);
		return err;
	}

	mainloop_list[fd] = data;

	return 0;
}

        fd的范围可能会超过128,虽然在函数中对fd的范围进行了约束,最终数组肯定不会越界。但是超过128的fd却无法被监听起来,也就是说,超过128后,这个蓝牙模型就不能用了,监听不了事件,其他的回调也不会被触发。各位看官有没有知道这个是什么原因?为啥官方要这样定义和维护mainloop_list?

        这个也是为啥app与设备连接-断开多次之后,fd增长超过128,这个时候accept是成功的,也就是app的连接请求是被设备accept的,但是app发数据,设备却感知不到的原因。重启程序后,fd又从小开始,于是又正常了。

        于是我对mainloop动手了,增加了两个函数。


static uint32_t get_idle_index(void)
{
    uint32_t i = 0;
    for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) {
        struct mainloop_data *data = mainloop_list[i];
        if(data == NULL){
            return i;
        }
    }
    return MAX_MAINLOOP_ENTRIES;
}

static uint32_t get_index_by_fd(int fd)
{
    uint32_t i = 0;
    for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) {
        struct mainloop_data *data = mainloop_list[i];
        if(data && data->fd == fd){
            return i;
        }
    }
    return MAX_MAINLOOP_ENTRIES;
}

        新增fd的时候不再是直接用fd作为下标对mainloop_list进行管理了,而是通过查询mainloop_list中空的项,返回对应的下标进行控制。

        测试发现问题确实解决了,即使是超大的fd,也不会有问题。

        唯一的解释是,这个代码只能是个参考例子,不能作为正式使用。但这个mainloop在各个地方都有用到,也解释不通。

        不明觉厉。

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mr_xiaogui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值