本文分析了蓝牙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)相关推荐

  1. Linux USB设备驱动程序设计 和 USB下载线驱动设计

    Linux USB设备驱动程序设计 和 USB下载线驱动设计 USB设备驱动模型 USB设备包括配置(configuration).接口(interface)和端点(endpoint),一个USB设备 ...

  2. Linux下USB HID device driver研究

    首先介绍HID: HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HI ...

  3. hid设备驱动linux,Linux HID 驱动开发(2) USB HID Report 描述及usage 概念

    在USB的枚举后,即交互完 设备描述符(device descriptor),配置描述符(configure descriptor),接口描述符(interface descriptor)和 在inp ...

  4. Linux内核USB总线--设备控制器驱动框架分析

    正文 1.概述 如下图所示,USB控制器可以呈现出两种不同的状态.USB控制器作为Host时,称为USB主机控制器,使用USB主机控制器驱动.USB控制器作为Device时,称为USB设备控制器,使用 ...

  5. linux cdc设备驱动,Linux下USB CDC ACM 驱动简析

    一.硬件平台:TI AM335X 芯片 二.软件平台:Ubuntu 10.04 三.USB CDC ACM 驱动简介 USB的CDC类是USB通信设备类 (Communication Device C ...

  6. linux 看usb 存储设备,找到哪个驱动器对应于Linux中的哪个USB大容量存储设备

    我有几个USB大容量存储闪存驱动器连接到Ubuntu Linux计算机(Ubuntu 10.04.1,内核2.6.32-25-386),我需要以编程方式区分它们(如果可能的话,从bash,但我不害怕编 ...

  7. Linux主机USB RNDIS网卡驱动实现不完整导致的一例问题

    某通信模块设备,通过USB提供RDNIS和ECM网卡功能.在实际应用中发现,USB RNDIS网卡模式下,当使用AT指令以不同的CID拨号的时候,在Windows主机上能正常拨号成功,但在Linux主 ...

  8. Linux中usb的UDC驱动挂和usb端口设置

    背景 Linux 可能有多个usb,想要把指定usb口提供某些功能,这时候就会存在问题.有的驱动会因为usb 的模式自动挂载到其他口,有的功能只能挂在到host或者device模式,usb口A想要用某 ...

  9. linux无线usb网卡,Linux下USB无线网卡WL-167G驱动安装过程

    最近经过前期调研选定网卡,又折腾一两天,终于搞定了Linux下的无线网卡,对遇到的问题进行了分析,并整理了下详细过程,现与大家分享,还有更具体的开发文档,有需要的可以联系下一步工作要将其移植到ARM平 ...

  10. linux设备驱动子系统,Linux设备驱动子系统终极弹 - USB

    0. 预备理论 1. USB Core 2. USB Hub 3. USB OTG 4. USB Host 5. USB Gadget 6. USB Mass Storage USB博大精深,不是一两 ...

最新文章

  1. Python,OpenCV图像处理超好用的工具包imutils
  2. linux 路由跟踪表满错误 nf_conntrack: table full, dropping packet 原理解决方法
  3. Vue中使用vue-quil-editor富文本编辑器+el-upload实现带图片上传到SpringBoot后台接口
  4. 神策数据荣获“2017金融科技·大数据优秀案例之最佳实践案例奖”
  5. 玛丽莲·梦露从未公开的照片
  6. Win10的远程桌面
  7. 力扣90. 子集 II(JavaScript)
  8. python-levenshtein —— 字符串相似度的计算
  9. SOLID原则(转载)
  10. java-java动态性之反射,动态编译,动态执行js及动态字节码操作
  11. Laravel框架从零搭建
  12. 中级软考-软件设计师(一)
  13. VM296:1 Uncaught SyntaxError: Unexpected token u in JSON at position 0 at JSON.parse (anonymous)
  14. Mapped Statements collection already contains value for com.bai.dao.Userdao.UserByID
  15. python 千位分隔符_千位分隔符的完整攻略
  16. Oracle12.2c统一审计(unified auditing)六问
  17. 《数据库系统工程师》备考指南
  18. 在线制作名片php,关于一个名片在线制作的问题,求高手指点阿..
  19. Java线程的死锁和活锁
  20. matlab中心极限定理、高斯分布拟合。

热门文章

  1. 浅拷贝(shallow copy)和深拷贝(deep copy)
  2. Hystrix 从入门到深入——一阅读官网
  3. JavaScript基础总结-10
  4. PTA 7-9 买复印纸
  5. js创建数组的方法有哪些?
  6. 如何设计与搭建古风饰品小程序
  7. Windows下实现COM口通信c++源代码(验证可行)
  8. win10 uwp 判断文件存在
  9. 打开计算机硬盘竖着两列,怎么把excel的两行改成列
  10. android 动画菜鸟,android---动画入门(一)