最近在一个Linux系统的ARM板子上移植一款蓝牙芯片,因为我们做的是机顶盒,所以首要功能就是能连接上蓝牙遥控器,并且能正常的接收按键。之前在安卓平台,连接上蓝牙遥控器后,会自动创建/dev/input/eventX和/dev/hidrawX节点,通过读取这两个节点,能看到我们机顶盒接收到的按键数据。但是最近在Linux平台,连接上蓝牙遥控器后,并没有创建什么节点,所以我也不知道怎么将遥控器数据上抛给上层应用去读取。网上尝试找一些资料,不过这方面的文章比较少,所以决定自己加些打印,跟一下代码流程,下面的文章记录一下我的跟踪思路。
在正式开始分析代码前,我们先了解一个概念: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函数呢?这个问题我们接下来分析
通过加日志和搜索大量的代码,终于找到了怎么才能调用到上面的.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会有稍许不同,但是基本流程类似。