蓝牙底层HCI驱动的实现

 

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函数。

你可能感兴趣的:(Linux,蓝牙)