hid设备驱动linux,linux usb hid设备驱动(3)
本文分析了蓝牙bluez协议栈中HID协议的实现。
1. 基本概念
HID协议用于人机输入设备。Bluez中关于HID的实现代码在其根目录下的input目录。蓝牙规范中包含关于HID的profile,里面重用了USB中关于HID的一些协议规范。
Bluez协议栈与上层应用之间使用dbus接口。
Bluez与kernel之间使用AF_BLUETOOTH协议族的socket通信,并使用了gtk+中的glib库。
2. 初始化
HID的初始化在input目录的main.c中,input_manager_init函数。该函数会调用input_manager_init。在input_manager_init中,主要是做了三个操作:
btd_register_adapter_driver(&input_server_driver);
btd_register_device_driver(&input_hid_driver);
btd_register_device_driver(&input_headset_driver);
下面分别讨论。
2.1 btd_register_adapter_driver
btd_register_adapter_driver(&input_server_driver);
static struct btd_adapter_driver input_server_driver =
{
.name =
"input-server",
.probe = hid_server_probe,
.remove = hid_server_remove,
};
这个调用的作用是注册一个adapter
driver。系统启动后对每一个本地蓝牙的硬件实例,即每一个HCI设备,都会调用里面的probe函数hid_server_probe。
static int hid_server_probe(struct btd_adapter
*adapter)
// 得到hci设备的本地蓝牙地址
adapter_get_address(adapter, &src);
// 启动hid服务
server_start(&src);
。。。
int server_start(const bdaddr_t *src)
struct input_server *server = g_new0(struct input_server,
1);
//
在ctrl通道(L2CAP_PSM_HIDP_CTRL)上listen,回调函数connect_event_cb
server->ctrl = bt_io_listen(BT_IO_L2CAP,
connect_event_cb, NULL,
server, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, src,
BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
BT_IO_OPT_INVALID);
// 在intr通道(L2CAP_PSM_HIDP_INTR)listen,回调函数confirm_event_cb
server->intr = bt_io_listen(BT_IO_L2CAP,
NULL, confirm_event_cb,
server, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, src,
BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
BT_IO_OPT_INVALID);
上面的ctrl通道和intr通道都是由蓝牙的HID spec规定。
对于control通道,当设备端有主动连接本机时,会由glib调用回调函数connect_event_cb:
static void connect_event_cb(GIOChannel *chan, GError *err,
gpointer data)
// 得到该设备的源地址和目的地址,psm等
bt_io_get(chan, BT_IO_L2CAP, &gerr,
BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_PSM,
&psm,
BT_IO_OPT_INVALID);
// 设置input_device
input_device_set_channel(&src,
&dst, psm, chan);
// 如果是非法设备,并且当前是控制通道,那么根据HID协议,需要向对方发送“unplug virtual
cable”消息
if (ret == -ENOENT && psm ==
L2CAP_PSM_HIDP_CTRL) {
unsigned char unplug = 0x15;
int err, sk = g_io_channel_unix_get_fd(chan);
err = write(sk, &unplug, sizeof(unplug));
}
下面继续研究input_device_set_channel函数。
int input_device_set_channel(const bdaddr_t *src, const
bdaddr_t *dst, int psm, GIOChannel *io)
//
根据对方设备的地址,从HID设备链表中找到对应的input_dev设备。这里有一个问题,就是对应的input_dev设备是什么时候登记到链表中的,这一点稍后再讨论
struct input_device *idev = find_device(src, dst);
// 在该设备中,查找名为”hid”的连接
struct input_conn * iconn =
find_connection(idev->connections, "hid");
switch (psm) {
case L2CAP_PSM_HIDP_CTRL:
if (iconn->ctrl_io)
return -EALREADY;
iconn->ctrl_io = g_io_channel_ref(io);
break;
case L2CAP_PSM_HIDP_INTR:
if (iconn->intr_io)
return -EALREADY;
iconn->intr_io = g_io_channel_ref(io);
break;
}
//
当ctrl通道和intr通道都被设置后,才会进入input_device_connadd。目前我们是沿着L2CAP_PSM_HIDP_CTRL的回调函数connect_event_cb看到这里的,所以暂时先不深入研究
if (iconn->intr_io
&&
iconn->ctrl_io)
input_device_connadd(idev, iconn);
。。。
下面再看一下server_start函数中对L2CAP_PSM_HIDP_INTR的情况,此时会调用到confirm_event_cb函数。关于bt_io_listen中关于connect和confirm这两个函数的区别,可以自行查看glib的文档或者bluez的源代码。
static void confirm_event_cb(GIOChannel *chan, gpointer
user_data)
bt_io_get(chan, BT_IO_L2CAP, &err,
BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_INVALID);
server->confirm = g_io_channel_ref(chan);
// 请求authorization操作,并指定完成后的回调函数为auth_callback
btd_request_authorization(&src,
&dst, HID_UUID, auth_callback, server);
static void auth_callback(DBusError *derr, void
*user_data)
bt_io_get(server->confirm, BT_IO_L2CAP,
&err,
BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_INVALID);
bt_io_accept(server->confirm, connect_event_cb,
server, NULL, &err)
由此可见,authorization结束后,会调用bt_io_accept,并同样指定回调函数为connect_event_cb。此时connect_event_cb会设置intr通道,并最终调用input_device_connadd函数。
static int input_device_connadd(struct input_device *idev,
struct input_conn *iconn)
input_device_connected(idev, iconn)
。。。
static int input_device_connected(struct input_device *idev,
struct input_conn *iconn)
hidp_add_connection(idev, iconn)
。。。
connected = TRUE;
// 通过dbus发送已连接的信号
emit_property_changed(idev->conn,
idev->path, INPUT_DEVICE_INTERFACE,
"Connected", DBUS_TYPE_BOOLEAN,
&connected);
。。。
static int hidp_add_connection(const struct input_device
*idev, const struct input_conn *iconn)
struct hidp_connadd_req *req;
sdp_record_t *rec;
req = g_new0(struct hidp_connadd_req, 1);
req->ctrl_sock =
g_io_channel_unix_get_fd(iconn->ctrl_io);
req->intr_sock =
g_io_channel_unix_get_fd(iconn->intr_io);
req->flags = 0;
req->idle_to =
iconn->timeout;
ba2str(&idev->src,
src_addr);
ba2str(&idev->dst,
dst_addr);
// 查找该设备对应的SDP
rec = fetch_record(src_addr, dst_addr,
idev->handle);
// 从SDP
record中得到一些属性从而设置req中某些域,具体可看代码,包括HID的设备描述符等都在这里设置
extract_hid_record(rec, req);
sdp_record_free(rec);
// 根据SDP得到设备的vendor、product等信息
read_device_id(src_addr, dst_addr, NULL,
&req->vendor,
&req->product,
&req->version);
// 下面是支持fakehid的代码,目前仅有PS3的设备支持,所以这里不分析
struct fake_hid *fake_hid =
get_fake_hid(req->vendor,
req->product);
。。。
if (req->subclass & 0x40) //
如果是键盘,则启动加密
bt_acl_encrypt(&idev->src,
&idev->dst, encrypt_completed,
req);
。。。
//
ioctl_connadd中会建立一个BTPROTO_HIDP的socket,并调用HIDPCONNADD新建一个连接。到这里,与远端设备的连接就建立了。建立之后,kernel会建立一个HID设备,此HID设备与bluez之间通过ctrl
sock和intr sock进行数据交互
ioctl_connadd(req);
2.2 btd_register_device_driver
btd_register_device_driver用于注册设备驱动,在bluez中使用这个函数注册的设备有两个,分别是input_headset_driver和input_hid_driver。
其中input-headset与蓝牙耳机有关;input-hid则用于普通的HID设备。
下面先看一下input_hid_driver设备。
input_hid_driver
static struct btd_device_driver input_hid_driver =
{
.name = "input-hid",
.uuids =
BTD_UUIDS(HID_UUID),
.probe = hid_device_probe,
.remove = hid_device_remove,
};
当bluez检测到有一个hid设备,即uuid中包含HID_UUID的设备连接上时,就会调用其中的probe函数。
static int hid_device_probe(struct btd_device *device,
GSList *uuids)
。。。
input_device_register(connection, device, path,
&src, &dst,
HID_UUID, rec->handle, idle_timeout * 60);
int input_device_register(DBusConnection *conn, struct
btd_device *device,
const char *path, const bdaddr_t *src,
const bdaddr_t *dst, const char *uuid,
uint32_t handle, int timeout)
。。。
// 分配一个新的input_device结构体,并添加到全局链表devices中
// 前文分析input_device_set_channel函数时,提到的添加idev的地方,就在这里
idev = input_device_new(conn, device, path, src, dst,
handle);
devices = g_slist_append(devices, idev);
。。。
// 添加一个名为”hid”的连接
iconn = input_conn_new(idev, uuid, "hid", timeout);
idev->connections =
g_slist_append(idev->connections, iconn);
在函数input_device_new中,除了新建设备之外,还添加了一个dbus接口:
g_dbus_register_interface(conn, idev->path,
INPUT_DEVICE_INTERFACE,
device_methods, device_signals, NULL,
idev, device_unregister)
static GDBusMethodTable device_methods[] = {
{
"Connect", "", "", input_device_connect,
G_DBUS_METHOD_FLAG_ASYNC },
{
"Disconnect", "", "", input_device_disconnect
},
{
"VirtualUnplug", "", "", input_device_unplug
},
{
"GetProperties", "", "a{sv}",input_device_get_properties
},
{ }
};
前文分析HID连接的建立时,都是本机作为服务器,等待远端设备连接。有了这个dbus接口之后,本地应用程序就可以主动连接远端设备,只要调用”Connect”方法即可,此方法会被链接到input_device_connect函数。
Input_device_connect中的流程与前文中本机作为服务器的流程基本相同。在此函数中,会先建立ctrl通道的连接,然后再建立intr通道的连接。最终通过调用函数hidp_add_connection通知内核建立一个HID设备或者input设备(HID
boot protocol设备)。
input-headset
static struct btd_device_driver input_headset_driver =
{
.name =
"input-headset",
.uuids =
BTD_UUIDS(HSP_HS_UUID),
.probe = headset_probe,
.remove = headset_remove,
};
Input-headset的流程比较特殊,与input-hid的区别至少有以下几点:
1. HID设备的连接建立在l2cap上,headset的连接建立在rfcomm上。
2. HID设备会通知内核建立一个HID设备或input设备,headset则只是实例化一个uinput设备。
3. 前文提到的ctrl通道、intr通道都不能适用于headset,因为他们都是在l2cap上的连接。
如果是本地主动连接远端的headset设备,同样是由应用程序调用”connect”方法启动连接过程,具体实现可查看代码。
hid设备驱动linux,linux usb hid设备驱动(3)相关推荐
- Linux USB设备驱动程序设计 和 USB下载线驱动设计
Linux USB设备驱动程序设计 和 USB下载线驱动设计 USB设备驱动模型 USB设备包括配置(configuration).接口(interface)和端点(endpoint),一个USB设备 ...
- Linux下USB HID device driver研究
首先介绍HID: HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HI ...
- hid设备驱动linux,Linux HID 驱动开发(2) USB HID Report 描述及usage 概念
在USB的枚举后,即交互完 设备描述符(device descriptor),配置描述符(configure descriptor),接口描述符(interface descriptor)和 在inp ...
- Linux内核USB总线--设备控制器驱动框架分析
正文 1.概述 如下图所示,USB控制器可以呈现出两种不同的状态.USB控制器作为Host时,称为USB主机控制器,使用USB主机控制器驱动.USB控制器作为Device时,称为USB设备控制器,使用 ...
- linux cdc设备驱动,Linux下USB CDC ACM 驱动简析
一.硬件平台:TI AM335X 芯片 二.软件平台:Ubuntu 10.04 三.USB CDC ACM 驱动简介 USB的CDC类是USB通信设备类 (Communication Device C ...
- linux 看usb 存储设备,找到哪个驱动器对应于Linux中的哪个USB大容量存储设备
我有几个USB大容量存储闪存驱动器连接到Ubuntu Linux计算机(Ubuntu 10.04.1,内核2.6.32-25-386),我需要以编程方式区分它们(如果可能的话,从bash,但我不害怕编 ...
- Linux主机USB RNDIS网卡驱动实现不完整导致的一例问题
某通信模块设备,通过USB提供RDNIS和ECM网卡功能.在实际应用中发现,USB RNDIS网卡模式下,当使用AT指令以不同的CID拨号的时候,在Windows主机上能正常拨号成功,但在Linux主 ...
- Linux中usb的UDC驱动挂和usb端口设置
背景 Linux 可能有多个usb,想要把指定usb口提供某些功能,这时候就会存在问题.有的驱动会因为usb 的模式自动挂载到其他口,有的功能只能挂在到host或者device模式,usb口A想要用某 ...
- linux无线usb网卡,Linux下USB无线网卡WL-167G驱动安装过程
最近经过前期调研选定网卡,又折腾一两天,终于搞定了Linux下的无线网卡,对遇到的问题进行了分析,并整理了下详细过程,现与大家分享,还有更具体的开发文档,有需要的可以联系下一步工作要将其移植到ARM平 ...
- linux设备驱动子系统,Linux设备驱动子系统终极弹 - USB
0. 预备理论 1. USB Core 2. USB Hub 3. USB OTG 4. USB Host 5. USB Gadget 6. USB Mass Storage USB博大精深,不是一两 ...
最新文章
- Python,OpenCV图像处理超好用的工具包imutils
- linux 路由跟踪表满错误 nf_conntrack: table full, dropping packet 原理解决方法
- Vue中使用vue-quil-editor富文本编辑器+el-upload实现带图片上传到SpringBoot后台接口
- 神策数据荣获“2017金融科技·大数据优秀案例之最佳实践案例奖”
- 玛丽莲·梦露从未公开的照片
- Win10的远程桌面
- 力扣90. 子集 II(JavaScript)
- python-levenshtein —— 字符串相似度的计算
- SOLID原则(转载)
- java-java动态性之反射,动态编译,动态执行js及动态字节码操作
- Laravel框架从零搭建
- 中级软考-软件设计师(一)
- VM296:1 Uncaught SyntaxError: Unexpected token u in JSON at position 0 at JSON.parse (anonymous)
- Mapped Statements collection already contains value for com.bai.dao.Userdao.UserByID
- python 千位分隔符_千位分隔符的完整攻略
- Oracle12.2c统一审计(unified auditing)六问
- 《数据库系统工程师》备考指南
- 在线制作名片php,关于一个名片在线制作的问题,求高手指点阿..
- Java线程的死锁和活锁
- matlab中心极限定理、高斯分布拟合。