蓝牙遥控器连接流程分析

背景

最近在一个Linux系统的ARM板子上移植一款蓝牙芯片,因为我们做的是机顶盒,所以首要功能就是能连接上蓝牙遥控器,并且能正常的接收按键。之前在安卓平台,连接上蓝牙遥控器后,会自动创建/dev/input/eventX和/dev/hidrawX节点,通过读取这两个节点,能看到我们机顶盒接收到的按键数据。但是最近在Linux平台,连接上蓝牙遥控器后,并没有创建什么节点,所以我也不知道怎么将遥控器数据上抛给上层应用去读取。网上尝试找一些资料,不过这方面的文章比较少,所以决定自己加些打印,跟一下代码流程,下面的文章记录一下我的跟踪思路。

正文

一、bus、driver、device总线部分

在正式开始分析代码前,我们先了解一个概念:match函数,一个由具体的bus driver实现的回调函数。当任何属于该Bus的device或者device_driver添加到内核时,内核都会调用该接口,如果新加的device或device_driver匹配上了自己的另一半的话,该接口要返回非零值,此时Bus模块的核心逻辑就会执行后续的处理。

0、在调用probe函数之前,会先调用match函数

代码目录:drivers\hid\hid-core.c

函数调用关系:
hid_bus_match
->hid_match_device
    ->hid_match_one_id

具体看一下hid_match_one_id函数,会通过判断vendor和product等值,来确定是否匹配成功,这两个值就是连接的蓝牙设备传过来的。

static bool hid_match_one_id(struct hid_device *hdev,
		const struct hid_device_id *id)
{
	return (id->bus == HID_BUS_ANY || id->bus == hdev->bus) &&
		(id->group == HID_GROUP_ANY || id->group == hdev->group) &&
		(id->vendor == HID_ANY_ID || id->vendor == hdev->vendor) &&
		(id->product == HID_ANY_ID || id->product == hdev->product);
}

1、当调用到probe函数,会有下面的所有流程发生

代码目录:
1)hid_device_probe:drivers\hid\hid-core.c
2)hid_hw_start:include\linux\hid.h

函数调用关系:
hid_device_probe
->hid_hw_start
  ->hid_connect

 

2、上面一步可以看出来最后调用到connect函数,字面意思就是开始正式连接,这个函数比较重要,我们进到具体代码看一下

代码目录:drivers\hid\hid-core.c

int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
{
    ...
    /* 下面会创建/dev/input/eventX节点 */
    if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
				connect_mask & HID_CONNECT_HIDINPUT_FORCE))
		hdev->claimed |= HID_CLAIMED_INPUT;
    
    /*下面会创建/dev/hidrawX节点*/
    if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev))
		hdev->claimed |= HID_CLAIMED_HIDRAW;
    ...
    hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s\n",
		 buf, bus, hdev->version >> 8, hdev->version & 0xff,
		 type, hdev->name, hdev->phys);

	return 0;
}

在connect函数中有两个比较重要的函数调用hidinput_connect和hidraw_connect,下面我们分别看一下:

2.1、

代码目录:
1)hidinput_connect:drivers\hid\hid-core.c
2)input_register_device:common\drivers\input\input.c:

函数调用关系:
hidinput_connect
->input_register_device   //input_register_device就是我们熟悉的,将设备注册到Input子系统

int input_register_device(struct input_dev *dev)
{
    ...
    path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
    pr_info("%s as %s\n",dev->name ? dev->name : "Unspecified device",path ? path : "N/A");
    kfree(path);
    ...
    error = device_add(&dev->dev);
    if (error)
	    goto err_free_vals;
    ...
}

上面的代码我们又遇到一个很关键的函数:device_add(),下面继续跟踪到device_add函数里面去。

2.1.1、

代码目录:
1)device_add:drivers\base\core.c
2)bus_probe_device:drivers\base\bus.c
3)device_attach:drivers\base\dd.c
4)__device_attach:drivers\base\dd.c
5)driver_match_device:drivers\base\base.h

函数调用关系:
device_add
->bus_probe_device
    ->device_attach
        ->__device_attach
            ->driver_match_device //最终会调用driver的match函数

在设备指定总线,且允许自动匹配的前提下(可以通过节点查看:cat /sys/bus/hid/drivers_autoprobe),bus_probe_device调用device_attach(dev),而在device_attach中又分两个分支:
第一、设备指定了驱动,那么device_attach直接调用device_bind_driver(dev)将驱动和设备绑定完事。
第二、设备没有指定驱动,那么device_attach通过bus_for_each_drv(dev->bus, NULL, dev, __device_attach)枚举总线上的驱动与设备进行匹配
具体代码如下所示:

int device_attach(struct device *dev)
{
    int ret = 0;

    device_lock(dev);
    if (dev->driver) { /*指定了驱动*/
        if (klist_node_attached(&dev->p->knode_driver)) {
            ret = 1;
            goto out_unlock;
        }
        ret = device_bind_driver(dev);
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {
        /* 通过枚举总线上的驱动和驱动进行匹配 */
        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
        pm_request_idle(dev);
    }
out_unlock:
    device_unlock(dev);
    return ret;
}

2.2、

代码目录:
hidraw_connect:drivers\hid\hidraw.c

函数调用关系:
hidraw_connect
->device_create

int hidraw_connect(struct hid_device *hid)
{
    ...
    dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),NULL, "%s%d", "hidraw", minor);
    ...
}

我们都知道,device_create()函数就是/dev下创建节点的,所以调用完hidraw_connect()后,就会有/dev/hidrawX节点了

 

3、

到目前为止,我们已经知道设备是怎么通过总线和驱动对应上了,但是问题又来了,怎么才能调用到.match函数呢?这个问题我们接下来分析

二、uhid驱动部分

通过加日志和搜索大量的代码,终于找到了怎么才能调用到上面的.match函数——hid_bus_match。下面我们看一下内核中的uhid驱动代码。

1、

代码目录:
drivers\hid\uhid.c

函数调用关系:
uhid_char_write
->uhid_dev_create
    ->hid_add_device
        ->device_add
    
static const struct file_operations uhid_fops = {
    .owner		= THIS_MODULE,
    .open		= uhid_char_open,
    .release	= uhid_char_release,
    .read		= uhid_char_read,
    .write		= uhid_char_write,
    .poll		= uhid_char_poll,
    .llseek		= no_llseek,
};

static struct miscdevice uhid_misc = {
    .fops		= &uhid_fops,
    .minor		= UHID_MINOR,
    .name		= UHID_NAME,
};

static int __init uhid_init(void)
{
    return misc_register(&uhid_misc);
}

从函数调用关系来看,又来到我们上面讲到的device_add函数了,就是在bus总线上匹配对应的驱动。翻看uhid这个驱动的源代码,我们可以发现这是一个misc杂散设备,实际上也确实创建了一个/dev/uhid节点。所以回到我们文章最开始的问题,怎么创建到/dev/input/eventX和/dev/hidrawX节点?流程大概应该是这样:
 

int fd = open(/dev/uhid);
write(fd, ...);

最终就会调用到uhid_char_write,并且最终匹配到对应的驱动程序,并且过程中创建了/dev/input/eventX和/dev/hidrawX节点。有了这个思路,我们自然就去查找,到底谁负责打开这个节点呢?自然而然我们就会想到是蓝牙协议栈做这个工作了,搜索代码后,果然是这样。我搜索了mtk的蓝牙协议栈代码,就发现了有打开/dev/uhid的动作。
 

2、

这里再提一下uhid设备匹配到的驱动程序——hid-generic。

代码目录:
drivers\hid\hid-generic.c

static const struct hid_device_id hid_table[] = {
    { HID_DEVICE(HID_BUS_ANY, HID_GROUP_GENERIC, HID_ANY_ID, HID_ANY_ID) },
    { }
};
MODULE_DEVICE_TABLE(hid, hid_table);

static struct hid_driver hid_generic = {
    .name = "hid-generic",
    .id_table = hid_table,
};
module_hid_driver(hid_generic);

注册完uhid设备后,会遍历所有的HID的驱动,因为从hid-generic的id_table来看是匹配任何设备的,所以最后就匹配到了hid-generic。不过大概看了一下这个驱动,好像并没有实际做什么。

结语

到目前为止就简单的跟踪了一下流程,还有很多细节没有细究,有兴趣的同学可以分析的更详细,但是有了上面的流程指引,我相信分析起来也会顺利很多了。另外,上面的代码流程是基于Linux 3.14.29版本,对于我现在准备做的Linux 4.9.113会有稍许不同,但是基本流程类似。
 

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