(转)Linux设备驱动之HID驱动 源码分析
http://blog.chinaunix.net/uid-20543183-id-1930836.html
- read(): This is the event interface. When the HID device performs an interrupt transfer, indicating a change of state, data will be made available at the associated hiddev device with the content of a struct hiddev_event:struct hiddev_event { unsigned hid; signed int value; };containing the HID usage identifier for the status that changed, and the value that it was changed to.
- ioctl(): This is the control interface. There are a number of controls:
HIDIOCGVERSION int (read) Gets the version code out of the hiddev driver. HIDIOCAPPLICATION (none) This ioctl call returns the HID application usage associated with the hid device. The third argument to ioctl() specifies which application index to get. This is useful when the device has more than one application collection. If the index is invalid (greater or equal to the number of application collections this device has) the ioctl returns -1. You can find out beforehand how many application collections the device has from the num_applications field from the hiddev_devinfo structure. HIDIOCGDEVINFO struct hiddev_devinfo (read) Gets a hiddev_devinfo structure which describes the device. HIDIOCGSTRING struct struct hiddev_string_descriptor (read/write) Gets a string descriptor from the device. The caller must fill in the "index" field to indicate which descriptor should be returned. HIDIOCINITREPORT Instructs the kernel to retrieve all input and feature report values from the device. At this point, all the usage structures will contain current values for the device, and will maintain it as the device changes. HIDIOCGNAME string (variable length) Gets the device name HIDIOCGREPORT struct hiddev_report_info (write) Instructs the kernel to get a feature or input report from the device, in order to selectively update the usage structures (in contrast to INITREPORT). HIDIOCSREPORT struct hiddev_report_info (write) Instructs the kernel to send a report to the device. This report can be filled in by the user throughHIDIOCSUSAGE calls (below) to fill in individual usage values in the report before sending the report in full to the device. HIDIOCGREPORTINFO struct hiddev_report_info (read/write) Fills in a hiddev_report_info structure for the user. The report is looked up by type (input, output or feature) and id, so these fields must be filled in by the user. The ID can be absolute -- the actual report id as reported by the device -- or relative -- HID_REPORT_ID_FIRST for the first report, and (HID_REPORT_ID_NEXT | report_id) for the next report after report_id. Without a-priori information about report ids, the right way to use this ioctl is to use the relative IDs above to enumerate the valid IDs. The ioctl returns non-zero when there is no more next ID. The real report ID is filled into the returned hiddev_report_info structure. HIDIOCGFIELDINFO struct hiddev_field_info (read/write) Returns the field information associated with a report in a hiddev_field_info structure. The user must fill in report_id and report_type in this structure, as above. The field_index should also be filled in, which should be a number from 0 and maxfield-1, as returned from a previous HIDIOCGREPORTINFO call. HIDIOCGUCODE struct hiddev_usage_ref (read/write) Returns the usage_code in a hiddev_usage_ref structure, given that given its report type, report id, field index, and index within the field have already been filled into the structure. HIDIOCGUSAGE struct hiddev_usage_ref (read/write) Returns the value of a usage in a hiddev_usage_ref structure. The usage to be retrieved can be specified as above, or the user can choose to fill in the report_type field and specify the report_id asHID_REPORT_ID_UNKNOWN. In this case, the hiddev_usage_ref will be filled in with the report and field infomation associated with this usage if it is found. HIDIOCSUSAGE struct hiddev_usage_ref (write) Sets the value of a usage in an output report.
Linux 2.6内核中包含了HID驱动,能够自动把USB Key等HID外设识别成“/dev/hiddev0”之类的设备。但是该驱动没有实现write接口,因此无法象Windows平台那样使用 ReadFile和WriteFile来读写HID设备,而只能使用ioctl接口。
网上有各种各样读写HID设备的源代码例子,有的是通过HIDIOCSUSAGE和HIDIOCGUSAGE来每次收发4个字节,适合鼠标、键盘之类数据传输量小的设备;有的是通过HIDIOCSUSAGES和HIDIOCGUSAGES来连续接收和发送多个字节,适合USB Key一类的设备。
在上一篇日志(已删除)中,介绍了如何利用《USB and PIC: quick guide to an USB HID framework》一文提供的方法与USB Key进行通信(先发送HIDIOCSUSAGES和HIDIOCSREPORT进行写操作,再发送HIDIOCGREPORT和HIDIOCGUSAGES进行读操作,从而完成一次通信过程)。但是经过好友测试,发现该方法不论是在PC机上,还是在Cavium Octeon 52XX开发板上均存在问题,读出的数据始终是第一次通信的结果,除非在每次通信之前都发送HIDIOCINITREPORT控制码,但这又会造成相当长时间的阻塞。
进一步的测试表明,如果按照HIDIOCGUCODE、HIDIOCSUSAGES、HIDIOCSREPORT、HIDIOCGUCODE、HIDIOCGUSAGES的顺序发送控制码,那么可以每次都读出正确数据。不过该方法虽然在PC机上只需400毫秒延时,但是在Octeon开发板上仍会长时间阻塞在usbhid_wait_io函数那里。
无奈之下,我只好根据Cavium SDK自带的Linux内核源码中的usb_skeleton.c写了一个USB设备驱动程序,试图通过直接读写USB端点来完成通信过程。以下是在开发和调试过程中需要注意的几个问题:
首先,必须卸载Linux内核自带的HID驱动,否则它会自动“接管”新插入的USB Key,导致我们自己编写的驱动程序找不到设备。对于开发板,可以在编译内核时去掉HID相关的选项;对于PC机上已经安装好的Linux,我也不知道该怎么卸载其中的HID驱动。
其次,端点类型。在usb_skeleton.c中是通过bulk端点来访问USB设备的,而USB Key作为HID设备,一般只有0号控制端点和一个中断输入端点(例如3号)。对于中断端点,可以用usb_interrupt_msg(其实就是usb_bulk_msg)函数进行访问;对于控制端点,则稍微麻烦一些,因为除了数据,还需要构造一个8字节的setup包。有关setup包的详细结构,可以参考USB和HID规范。获取setup包具体数值最简单的方法,就是在Windows环境下用BusHound观察USB Key的通信过程。
最后,关于Report ID。在Windows环境下通过ReadFile和WriteFile访问HID设备时,必须在数据开头附加1字节的Report ID(一般为0)。在Linux环境下,如果使用HID驱动的ioctl接口,那么需要在hiddev_usage_ref结构中指定Report ID;如果使用自己编写的USB驱动程序,则不需要考虑Report ID,直接发送数据就得了。
经过测试,利用自己编写的驱动程序,可以在Octeon开发板上正确读写HID类型的USB Key,而且读写之间的时间间隔也可以缩短至50毫秒。
write: Broken pipe
在内核 2.6.35以后进行了修正, 参考如下:
https://patchwork.kernel.org/patch/99990/
点击(此处)折叠或打开
- diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
- index 56d06cd..6fd833d 100644
- --- a/drivers/hid/usbhid/hid-core.c
- +++ b/drivers/hid/usbhid/hid-core.c
- static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t count,
- unsigned char report_type)
- {
- struct usbhid_device *usbhid = hid->driver_data;
- struct usb_device *dev = hid_to_usb_dev(hid);
- struct usb_interface *intf = usbhid->intf;
- struct usb_host_interface *interface = intf->cur_altsetting;
- int ret;
- - ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
- - HID_REQ_SET_REPORT,
- - USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
- - ((report_type + 1) << 8) | *buf,
- - interface->desc.bInterfaceNumber, buf + 1, count - 1,
- - USB_CTRL_SET_TIMEOUT);
- -
- - /* count also the report id */
- - if (ret > 0)
- - ret++;
- + if (usbhid->urbout) {
- + int actual_length;
- + int skipped_report_id = 0;
- + if (buf[0] == 0x0) {
- + /* Don't send the Report ID */
- + buf++;
- + count--;
- + skipped_report_id = 1;
- + }
- + ret = usb_interrupt_msg(dev, usbhid->urbout->pipe,
- + buf, count, &actual_length,
- + USB_CTRL_SET_TIMEOUT);
- + /* return the number of bytes transferred */
- + if (ret == 0) {
- + ret = actual_length;
- + /* count also the report id */
- + if (skipped_report_id)
- + ret++;
- + }
- + } else {
- + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
- + HID_REQ_SET_REPORT,
- + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
- + ((report_type + 1) << 8) | *buf,
- + interface->desc.bInterfaceNumber, buf + 1, count - 1,
- + USB_CTRL_SET_TIMEOUT);
- + /* count also the report id */
- + if (ret > 0)
- + ret++;
- + }
- return ret;
- }
点击(此处)折叠或打开
- /*
- * Hidraw Userspace Example
- *
- * Copyright (c) 2010 Alan Ott <alan@signal11.us>
- * Copyright (c) 2010 Signal 11 Software
- *
- * The code may be used by anyone for any purpose,
- * and can serve as a starting point for developing
- * applications using hidraw.
- */
- /* Linux */
- #include <linux/types.h>
- #include <linux/input.h>
- #include <linux/hidraw.h>
- /*
- * Ugly hack to work around failing compilation on systems that don't
- * yet populate new version of hidraw.h to userspace.
- *
- * If you need this, please have your distro update the kernel headers.
- */
- #ifndef HIDIOCSFEATURE
- #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
- #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
- #endif
- /* Unix */
- #include <sys/ioctl.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- /* C */
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <errno.h>
- const char *bus_str(int bus);
- int main(int argc, char **argv)
- {
- int fd;
- int i, res, desc_size = 0;
- char buf[256];
- struct hidraw_report_descriptor rpt_desc;
- struct hidraw_devinfo info;
- /* Open the Device with non-blocking reads. In real life,
- don't use a hard coded path; use libudev instead. */
- fd = open("/dev/hidraw0", O_RDWR|O_NONBLOCK);
- if (fd < 0) {
- perror("Unable to open device");
- return 1;
- }
- memset(&rpt_desc, 0x0, sizeof(rpt_desc));
- memset(&info, 0x0, sizeof(info));
- memset(buf, 0x0, sizeof(buf));
- /* Get Report Descriptor Size */
- res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);
- if (res < 0)
- perror("HIDIOCGRDESCSIZE");
- else
- printf("Report Descriptor Size: %d\n", desc_size);
- /* Get Report Descriptor */
- rpt_desc.size = desc_size;
- res = ioctl(fd, HIDIOCGRDESC, &rpt_desc);
- if (res < 0) {
- perror("HIDIOCGRDESC");
- } else {
- printf("Report Descriptor:\n");
- for (i = 0; i < rpt_desc.size; i++)
- printf("%hhx ", rpt_desc.value[i]);
- puts("\n");
- }
- /* Get Raw Name */
- res = ioctl(fd, HIDIOCGRAWNAME(256), buf);
- if (res < 0)
- perror("HIDIOCGRAWNAME");
- else
- printf("Raw Name: %s\n", buf);
- /* Get Physical Location */
- res = ioctl(fd, HIDIOCGRAWPHYS(256), buf);
- if (res < 0)
- perror("HIDIOCGRAWPHYS");
- else
- printf("Raw Phys: %s\n", buf);
- /* Get Raw Info */
- res = ioctl(fd, HIDIOCGRAWINFO, &info);
- if (res < 0) {
- perror("HIDIOCGRAWINFO");
- } else {
- printf("Raw Info:\n");
- printf("\tbustype: %d (%s)\n",
- info.bustype, bus_str(info.bustype));
- printf("\tvendor: 0x%04hx\n", info.vendor);
- printf("\tproduct: 0x%04hx\n", info.product);
- }
- /* Set Feature */
- buf[0] = 0x9; /* Report Number */
- buf[1] = 0xff;
- buf[2] = 0xff;
- buf[3] = 0xff;
- res = ioctl(fd, HIDIOCSFEATURE(4), buf);
- if (res < 0)
- perror("HIDIOCSFEATURE");
- else
- printf("ioctl HIDIOCGFEATURE returned: %d\n", res);
- /* Get Feature */
- buf[0] = 0x9; /* Report Number */
- res = ioctl(fd, HIDIOCGFEATURE(256), buf);
- if (res < 0) {
- perror("HIDIOCGFEATURE");
- } else {
- printf("ioctl HIDIOCGFEATURE returned: %d\n", res);
- printf("Report data (not containing the report number):\n\t");
- for (i = 0; i < res; i++)
- printf("%hhx ", buf[i]);
- puts("\n");
- }
- /* Send a Report to the Device */
- buf[0] = 0x1; /* Report Number */
- buf[1] = 0x77;
- res = write(fd, buf, 2);
- if (res < 0) {
- printf("Error: %d\n", errno);
- perror("write");
- } else {
- printf("write() wrote %d bytes\n", res);
- }
- /* Get a report from the device */
- res = read(fd, buf, 16);
- if (res < 0) {
- perror("read");
- } else {
- printf("read() read %d bytes:\n\t", res);
- for (i = 0; i < res; i++)
- printf("%hhx ", buf[i]);
- puts("\n");
- }
- close(fd);
- return 0;
- }
- const char *
- bus_str(int bus)
- {
- switch (bus) {
- case BUS_USB:
- return "USB";
- break;
- case BUS_HIL:
- return "HIL";
- break;
- case BUS_BLUETOOTH:
- return "Bluetooth";
- break;
- case BUS_VIRTUAL:
- return "Virtual";
- break;
- default:
- return "Other";
- break;
- }
- }
点击(此处)折叠或打开
- #if 1
- ucNeedSend = 1;
- Pos = HID_OFFSET_LENGTH + 1;
- #if 0
- ucSendData = 64;
- for (i=0; i<ucSendData-2; i++)
- SendBuffer[i+Pos] = 0x70 + i;
- #else
- ucSendData = RcvBuffer[HID_OFFSET_LENGTH];
- for (i=0; i<ucSendData-2; i++)
- SendBuffer[i+Pos] = RcvBuffer[i+Pos];
- #endif
- if (ucNeedSend) {
- /* SendBuffer format
- 1st byte : ID_SEND_HID_RESPONSE
- 2nd byte : repply or error code flag
- 3rd byte : nb byte
- others bytes: data
- #define HID_SEND_HID_RESPONSE 0x07
- #define HID_OFFSET 0x00
- #define HID_OFFSET_CMDCODE HID_OFFSET + 1
- #define HID_OFFSET_LENGTH HID_OFFSET + 2
- */
- SendBuffer[HID_OFFSET] = HID_SEND_HID_RESPONSE;
- //SendBuffer[HID_OFFSET_LENGTH] = MIN(HID_MAX_BUFFER_SIZE, SendBuffer[HID_OFFSET_LENGTH]);
- SendBuffer[HID_OFFSET_LENGTH] = ucSendData;
- /* Allows the transmission */
- SetEPTxStatus(ENDP3, EP_TX_VALID);
- USB_SIL_Write(EP3_IN, SendBuffer, HID_MAX_BUFFER_SIZE);
- }
路由器上调试如下:
(转)Linux设备驱动之HID驱动 源码分析的更多相关文章
- imx6ul linux4.1.15 LED驱动配置及heartbeat源码分析【转】
本文转载自:https://blog.csdn.net/u010444107/article/details/78328807 1)查看内核配置wujun@wj-vBox:~/freescale/li ...
- Linux MMC 驱动子系统简述(源码剖析)
1. Linux MMC 驱动子系统 块设备是Linux系统中的基础外设之一,而 MMC/SD 存储设备是一种典型的块设备.Linux内核设计了 MMC子系统,用于管理 MMC/SD 设备. MMC ...
- 9.1 IIC驱动源码分析
学习目标:分析linux内核源码下的i2c总线驱动 drivers/i2c/busses/i2c-s3c2410.c 和 driver/i2c/chips/eeprom.c 设备驱动: 一.i2c驱动 ...
- linux内存源码分析 - 内存回收(整体流程)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文 ...
- linux中断源码分析 - 中断发生(三)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 回顾 上篇文章linux中断源码分析 - 初始化(二)已经描述了中断描述符表和中断描述符数组的初始化,由于在初始 ...
- Linux下USB suspend/resume源码分析【转】
转自:http://blog.csdn.net/aaronychen/article/details/3928479 Linux下USB suspend/resume源码分析 Author:aaron ...
- Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】
原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...
- (转)linux内存源码分析 - 内存回收(整体流程)
http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ...
- Linux内核源码分析方法
一.内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径.我们都 ...
随机推荐
- Adaboost算法及其代码实现
. . Adaboost算法及其代码实现 算法概述 AdaBoost(adaptive boosting),即自适应提升算法. Boosting 是一类算法的总称,这类算法的特点是通过训练若干弱分类器 ...
- spring启动,spring mvc ,要不要xml配置,基于注解配置
老项目是09-11年搞的,用的是spring+struts2,没有用注解,全xml配置.web.xml中也配置了一大堆. 现在启动新项目,在项目中用spring+springmvc ,主要用注解,也用 ...
- 吴裕雄--天生自然 pythonTensorFlow图形数据处理:TensorFlow图像处理函数
import numpy as np import tensorflow as tf import matplotlib.pyplot as plt #读取图片 image_raw_data = tf ...
- 【lca+输入】Attack on Alpha-Zet
Attack on Alpha-Zet 题目描述 Space pirate Captain Krys has recently acquired a map of the artificial and ...
- rest framework-分页-长期维护
############### 分页组件 ############### # 分页组件 # # django也有分页,rest framework也有分页,但是没有页面这个概念了, # 这个分页 ...
- python3下scrapy爬虫(第十二卷:解决scrapy数据存储大量数据时阻塞问题)
之前我们使用scrapy爬取数据,用的存储方式是直接引入PYMYSQL,或者MYSQLDB,案例中数据量并不大,这种数据存储方式属于同步过程,也就是上一条语句执行完才能执行下一条语句,当数据量变大时, ...
- MySQL的详细操作
MySQL的详细操作 存储引擎 不同的数据应该有不同的处理机制 mysql存储引擎 Innodb:默认的存储引擎 查询速度较myisam慢 但是更安全,支持事务,行锁,外键由于以上的支持,数据更安全, ...
- 利用数目找中位数(牛客第七场E)
https://ac.nowcoder.com/acm/contest/887/E 树状数组做法(代码有注释) #include<bits/stdc++.h> using namespac ...
- 机器学习算法之——KNN、Kmeans
一.Kmeans算法 kmeans算法又名k均值算法.其算法思想大致为:先从样本集中随机选取 kk 个样本作为簇中心,并计算所有样本与这 kk 个“簇中心”的距离,对于每一个样本,将其划分到与其距离最 ...
- 系统学习javaweb重点难点1--如何区分<input/>框里的三种常用属性:type属性 name属性 和 value属性
感想:这是我系统学习javaweb的时候感觉这个是一个初学者十分容易搞混的点 学习笔记: 首先,是type属性. 表单输入项标签之一,用户可以在该标签上通过填写和选择进行数据输入. type属性设置该 ...