背景

最近在一个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.cint 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_createint 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_addstatic 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.cstatic 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会有稍许不同,但是基本流程类似。

蓝牙遥控器连接流程分析相关推荐

  1. [笔记分享] [遥控器]Android红外及蓝牙遥控器适配流程

    在海思.Amlogic.Mstar平台上适配蓝牙或者红外遥控器,可以按照以下的步骤进行排查和修改. 1.前置工作 1.1 getevent -l 无论是蓝牙遥控器还是红外遥控器,都需要确定其使用的kl ...

  2. 蓝牙enable的流程分析

    本文主要基于高通Android 12 来分析蓝牙enable的流程 framework 流程不做分析,直接从packages\apps\Bluetooth进行分析 packages/apps/Blue ...

  3. Android 蓝牙手柄连接流程解析和自动化方案

    为了提高蓝牙手柄的连接成功率,实现自动连接蓝牙手柄,替代用户手动连接蓝牙手柄的整个流程. 首先,我们将"连接蓝牙手柄"这个步骤拆分开来,可以细分为搜索.识别.配对.连接四个步骤.为 ...

  4. Android 源码 Wi-Fi 连接流程分析

    Wi-Fi 连接过程可以从 Settings App 中点击任意 Wi-Fi 条目连接说起.点击条目以后会弹出一个对话框,根据不同的 Wi-Fi 类型需要填入必要的信息,再点击连接按钮,发起连接过程. ...

  5. wifi连接流程分析

    Wifi 连接部分 当用户选择一个AP时会弹出一个AP参数配置对话框,此对话框会显示当前选择的AP信号强度,若此AP设置了密码则需要用户输入密码才能登录.WifiSettings中的 onPrefer ...

  6. PD快充协议连接流程分析

    DFP和UFP均会实时监控CC1和CC2引脚的电压,以此来评估DFP和UFP是否都已经在位.同时DFP可以根据电压确定自己所能提供的电流的大小.

  7. IoT:BLE4.0教程一 蓝牙协议连接过程与广播分析

    IoT:BLE4.0教程一 蓝牙协议连接过程与广播分析 1.蓝牙简介 什么是蓝牙4.0 蓝牙无线技术是使用范围最广泛的全球短距离无线标准之一,蓝牙4.0版本涵盖了三种蓝牙技术,即传统蓝牙.高速蓝牙和低 ...

  8. ble连接过程建立_BLE4.0教程一 蓝牙协议连接过程与广播分析

    1.蓝牙简介 什么是蓝牙4.0 蓝牙无线技术是使用范围最广泛的全球短距离无线标准之一,蓝牙4.0版本涵盖了三种蓝牙技术,即传统蓝牙.高速蓝牙和低功耗蓝牙技术,将三种规范合而为一.它继承了蓝牙技术在无线 ...

  9. BLE4.0教程一 蓝牙协议连接过程与广播分析

    1.蓝牙简介 什么是蓝牙4.0 蓝牙无线技术是使用范围最广泛的全球短距离无线标准之一,蓝牙4.0版本涵盖了三种蓝牙技术,即传统蓝牙.高速蓝牙和低功耗蓝牙技术,将三种规范合而为一.它继承了蓝牙技术在无线 ...

最新文章

  1. elasticsearch如何安全重启节点
  2. 金蝶K3cloud问题单排查
  3. c语言中abc是什么类型,基金分为ABC三类,分别代表什么意思,哪一类适合普通投资者?...
  4. Robot Framework + Selenium library + IEDriver环境搭建
  5. Spring4:具有Java 8 Date-Time API的@DateTimeFormat
  6. Linux(CentOS 7.x) 下如何设置 nginx 开机启动?
  7. OpenGL 学习 显示列表
  8. 抢人竞争激烈,为保障iPhone需求,富士康为新员工提供万元奖金
  9. 异常org.hibernate.proxy.HibernateProxy.Forgot to register a type adapter
  10. acm康复训练记4-world final 2017
  11. 爱情指数测试脸型软件,心理测试:你和谁的脸型最像?测出你的幸运指数是多少!...
  12. 陈纪修老师《数学分析》 第02章:数列极限 笔记
  13. R语言实战应用精讲50篇(十七)--使用R语言实现时间序列分析
  14. 反向代理Reverse proxy
  15. 传销?花生日记罚款7456万元这个微信社群营销分钱模式要知道
  16. 大话设计模式(五)观察者模式
  17. 在线测试c语言程序代码,C语言在线测评系统的使用
  18. 什么品牌把产品做到极致,只服苹果和TOM邮箱
  19. title在python中是什么意思_python – 在matplotlib中,title()和suptitle()之间有什么区别?...
  20. jdk11新特性,是否有必要从JDK8切换到JDK11

热门文章

  1. windows下整数分区的计算方法
  2. Oracle 使用xtts升级11g-to-19c
  3. vue3 将文字或链接生成二维码 qrcode.vue
  4. 关注两篇gatekeeper的论坛
  5. ubuntu使用exiftool查看图片exif信息
  6. ORA-01034 ORACLE not available
  7. uhuntu五笔输入法fcitx安装
  8. Tensorflow2训练Fer2013数据集
  9. 关于EI计算机视觉投稿
  10. 大型游戏后台实践浅谈