Overview
蓝牙协议栈与蓝牙底层设备一般是通过串口连接,两者之间通过HCI协议通讯。这就要求实现一个串口tty驱动。而对于Bluez协议栈来说,它是通过建立蓝牙的socket来发送、接收数据。因此,在蓝牙通信中,对上层应用是socket通信,对底层则一般是通过一个tty驱动实现。本文以HCIUART_LL为例,讨论了蓝牙底层的tty驱动部分,代码在drivers\bluetooth\hci_ll.c和hci_ldis.c。
Hci_ldis.c实现一个蓝牙专用的线路规程,disc id为N_HCI,结构体如下:
static struct tty_ldisc_ops hci_uart_ldisc;
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;
在这个结构中,hci_uart_tty_read与hci_uart_tty_write这两个函数为空函数。由此可见,一旦对tty串口设置了N_HCI线路规程,那么上层就不能再通过读写tty设备的方式发送、接收HCI数据。取而代之的,N_HCI中实现了对上层的socket接口,即与/net/bluetooth/目录下的代码交互数据的接口。
初始化
初始化由hci_uart_init函数完成,里面注册了N_HCI,另外调用了ll_init函数。
static int __init hci_uart_init(void)
static struct tty_ldisc_ops hci_uart_ldisc;
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;
tty_register_ldisc(N_HCI, &hci_uart_ldisc);
。。。
ll_init();
ll_init函数很简单,仅仅是调用了hci_uart_register_proto(&llp)。其中llp是一个hci_uart_proto结构。
static struct hci_uart_proto llp = {
.id = HCI_UART_LL,
.open = ll_open,
.close = ll_close,
.recv = ll_recv,
.enqueue = ll_enqueue,
.dequeue = ll_dequeue,
.flush = ll_flush,
};
hci_uart_register_proto函数将会把llp注册到全局数组hup[HCI_UART_LL]中。
初始配置
一般来说,应用程序初始化并使用蓝牙设备的配置步骤如下:
1. 应用程序打开一个tty设备,该tty物理上与蓝牙芯片连接,两者之间使用HCI通信。
2. 调用该tty的ioctl,设置线路规程为N_HCI。此时会调用到hci_uart_tty_open函数。
3. 调用IOCTL HCIUARTSETPROTO,设置proto,此ioctl最终会调用到N_HCI线路规程中的hci_uart_tty_ioctl函数。下文将详细讨论。
4. 应用程序打开蓝牙socket,发送、接收数据。这些数据在内核中最终会转到N_HCI线路规程,并最终通过tty串口驱动与蓝牙芯片交互。
hci_uart_tty_open函数的代码如下:
static int hci_uart_tty_open(struct tty_struct *tty)
struct hci_uart *hu;
hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL);
tty->disc_data = hu;
hu->tty = tty;
tty->receive_room = 65536;
。。。
应用程序调用ioctl HCIUARTSETPROTO之后,内核最终会转到hci_uart_tty_ioctl函数处理:
static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file,
unsigned int cmd, unsigned long arg)
struct hci_uart *hu = (void *)tty->disc_data;
。。。
Case HCIUARTSETPROTO:
hci_uart_set_proto(hu, arg); // arg为proto id
。。。
static int hci_uart_set_proto(struct hci_uart *hu, int id)
// 得到hci_uart_proto。本文中id值是HCI_UART_LL,即p指向前面提到的llp
struct hci_uart_proto *p = hup[id];
// 在本文讨论的HCI_UART_LL中,p->open会调用到ll_open函数
p->open(hu);
hu->proto = p;
// 注册一个抽象的蓝牙串口设备
err = hci_uart_register_dev(hu);
ll_open函数的代码如下:
static int ll_open(struct hci_uart *hu)
struct ll_struct *ll;
ll = kzalloc(sizeof(*ll), GFP_ATOMIC);
skb_queue_head_init(&ll->txq);
skb_queue_head_init(&ll->tx_wait_q);
ll->hcill_state = HCILL_AWAKE;
hu->priv = ll;
hci_uart_register_dev函数用于注册一个抽象的蓝牙设备到蓝牙socket层:
static int hci_uart_register_dev(struct hci_uart *hu)
struct hci_dev *hdev = hci_alloc_dev();
hu->hdev = hdev;
hdev->bus = HCI_UART;
hdev->driver_data = hu;
hdev->open = hci_uart_open;
hdev->close = hci_uart_close;
hdev->flush = hci_uart_flush;
hdev->send = hci_uart_send_frame;
hdev->destruct = hci_uart_destruct;
// 这里调用到了蓝牙socket层的函数,本文暂不讨论
hci_register_dev(hdev);
数据的接收
当tty驱动接收到数据后,会调用到线路规程的receive函数。对于蓝牙,就是N_HCI线路规程中的hci_uart_tty_receive函数。
static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, char *flags, int count)
struct hci_uart *hu = (void *)tty->disc_data;
hu->proto->recv(hu, (void *) data, count);
hu->hdev->stat.byte_rx += count;
// 通知tty驱动清空接收缓冲区
tty_unthrottle(tty);
如果是HCI_UART_LL,上面recv函数会指向ll_recv函数。ll_recv的逻辑比较复杂,但大意就是从数据流中提取出hci帧,然后调用hci_recv_frame发送给蓝牙socket层。此外,HCI_UART_LL还实现了一些特殊操作,比如蓝牙芯片的休眠、唤醒等,具体可查看代码。
数据的发送
当有数据需要发送时,网络层会调用到hdev->send函数指针。在这里,此指针指向的是hci_uart_send_frame 函数。
static int hci_uart_send_frame(struct sk_buff *skb)
struct hci_dev* hdev = (struct hci_dev *) skb->dev;
struct hci_uart *hu = (struct hci_uart *) hdev->driver_data;
hu->proto->enqueue(hu, skb);
hci_uart_tx_wakeup(hu);
enqueue指针指向ll_enqueue,作用是将数据挂到一个发送队列中。然后,hci_uart_tx_wakeup函数会执行真正的发送操作。发送操作使用的是tty->ops->write(tty, skb->data, skb->len),即tty底层驱动的write函数。