关键词:hid、hidraw、usbhid、hidp等等。
下面首先介绍hidraw设备主要用途,然后简要分析hidraw设备驱动(但是不涉及到相关USB/Bluwtooth驱动),最后分析用户空间接口并实例。
1. hidraw介绍
在内核Documentation/hid/hidraw.txt中对hidra设备进行介绍,以及和hiddev的区别。
hidraw提供了一个通过USB/Bluetooth接口的裸数据接口,它和hiddev的区别体现在其数据不经过HID parser解析,而是直接将数据传输。
如果用户空间应用程序知道怎么恰当和硬件设备通信,和能够手动构建HID 报表,那么hidraw应该被使用。这通常是在用户控件驱动自定义HID 设备的时候。
Hidraw与不符合规范的HID 设备通信也是有利 的,这些设备以一种不符合报表描述符不一致的方式发送和接收数据。因为Hiddev解析器通过他发送和接收报表,检测设备的报表描述符,这样的通信是不可能使用hiddev。Hidraw是唯一的选择,为这些不兼容的设备编写一个定制的内核驱动程序。
Hidraw一个好处是用户空间应用程序使用独立的底层硬件类型。当前,hidraw是通过bluetooth 和 usb实现。在将来,随着硬件总线的发展,hidraw将支持更多的类型。
2. hidraw驱动
hidraw也是hid类设备,hidraw_init()被hid_init调用,在调用之前创建了虚拟的hid总线hid_bus_type,并且创建/sys/kernel/debug/hid调试接口。
static int __init hid_init(void) { int ret; ... ret = bus_register(&hid_bus_type); ... ret = hidraw_init(); if (ret) goto err_bus; hid_debug_init(); ... } static void __exit hid_exit(void) { hid_debug_exit(); hidraw_exit(); bus_unregister(&hid_bus_type); }
struct hidraw是hidraw设备实例,struct hid_device是hid框架下表示hid设备的实例。
hidraw_list是hidraw设备一次传输的实例。
struct hidraw { unsigned int minor;----------------------从设备号。 int exist;-------------------------------表示设备是否连接。 int open;--------------------------------表示设备open计数。 wait_queue_head_t wait; struct hid_device *hid;------------------对应hid设备实例。 struct device *dev; spinlock_t list_lock; struct list_head list; }; struct hidraw_report { __u8 *value; int len; }; struct hidraw_list { struct hidraw_report buffer[HIDRAW_BUFFER_SIZE]; int head; int tail; struct fasync_struct *fasync; struct hidraw *hidraw; struct list_head node; struct mutex read_mutex; };
2.1 hid总线
hid_bus_type提供了hid总线上进行match、probe、remove的接口,以及uevent上报接口。
static struct bus_type hid_bus_type = { .name = "hid", .dev_groups = hid_dev_groups, .match = hid_bus_match, .probe = hid_device_probe, .remove = hid_device_remove, .uevent = hid_uevent, }; static int hid_uevent(struct device *dev, struct kobj_uevent_env *env) { struct hid_device *hdev = to_hid_device(dev); if (add_uevent_var(env, "HID_ID=%04X:%08X:%08X", hdev->bus, hdev->vendor, hdev->product)) return -ENOMEM; if (add_uevent_var(env, "HID_NAME=%s", hdev->name)) return -ENOMEM; if (add_uevent_var(env, "HID_PHYS=%s", hdev->phys)) return -ENOMEM; if (add_uevent_var(env, "HID_UNIQ=%s", hdev->uniq)) return -ENOMEM; if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X", hdev->bus, hdev->group, hdev->vendor, hdev->product)) return -ENOMEM; return 0; }
2.1 hidraw初始化
hidraw首先创建了字符设备hidraw_class,并和字符设备hidraw_cdev绑定,对应的文件操作函数集为hidraw_ops。
其中字符设备hidraw_cdev从设备好范围为0~63,后面创建设备通过hidraw_class即可。
int __init hidraw_init(void) { int result; dev_t dev_id; result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR, HIDRAW_MAX_DEVICES, "hidraw");---------------------------------从设备号0~63。 if (result < 0) { pr_warn("can't get major number "); goto out; } hidraw_major = MAJOR(dev_id); hidraw_class = class_create(THIS_MODULE, "hidraw");------------------创建hidraw设备类。 if (IS_ERR(hidraw_class)) { result = PTR_ERR(hidraw_class); goto error_cdev; } cdev_init(&hidraw_cdev, &hidraw_ops);------------------------------初始化一个字符设备hidraw_dev,操作函数集为hidraw_ops。 result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); if (result < 0) goto error_class; printk(KERN_INFO "hidraw: raw HID events driver (C) Jiri Kosina "); out: return result; error_class: class_destroy(hidraw_class); error_cdev: unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); goto out; } void hidraw_exit(void) { dev_t dev_id = MKDEV(hidraw_major, 0); cdev_del(&hidraw_cdev); class_destroy(hidraw_class); unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); }
对于hidraw设备的操作,都在hidraw_ops中体现,包括open/close/read/write/ioctl,以及poll、fasync。
static const struct file_operations hidraw_ops = { .owner = THIS_MODULE, .read = hidraw_read, .write = hidraw_write, .poll = hidraw_poll, .open = hidraw_open, .release = hidraw_release, .unlocked_ioctl = hidraw_ioctl, .fasync = hidraw_fasync, #ifdef CONFIG_COMPAT .compat_ioctl = hidraw_ioctl, #endif .llseek = noop_llseek, }; static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct hidraw_list *list = file->private_data; int ret = 0, len; DECLARE_WAITQUEUE(wait, current); mutex_lock(&list->read_mutex); while (ret == 0) { if (list->head == list->tail) { add_wait_queue(&list->hidraw->wait, &wait); set_current_state(TASK_INTERRUPTIBLE); while (list->head == list->tail) { if (signal_pending(current)) { ret = -ERESTARTSYS; break; } if (!list->hidraw->exist) { ret = -EIO; break; } if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; break; } /* allow O_NONBLOCK to work well from other threads */ mutex_unlock(&list->read_mutex); schedule(); mutex_lock(&list->read_mutex); set_current_state(TASK_INTERRUPTIBLE); } set_current_state(TASK_RUNNING); remove_wait_queue(&list->hidraw->wait, &wait); } if (ret) goto out; len = list->buffer[list->tail].len > count ? count : list->buffer[list->tail].len; if (list->buffer[list->tail].value) { if (copy_to_user(buffer, list->buffer[list->tail].value, len)) { ret = -EFAULT; goto out; } ret = len; } kfree(list->buffer[list->tail].value); list->buffer[list->tail].value = NULL; list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); } out: mutex_unlock(&list->read_mutex); return ret; } static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type) { unsigned int minor = iminor(file_inode(file)); struct hid_device *dev; __u8 *buf; int ret = 0; if (!hidraw_table[minor] || !hidraw_table[minor]->exist) { ret = -ENODEV; goto out; } dev = hidraw_table[minor]->hid;-----------------------------------------------根据minor号找到对应hidraw设备的hid_device。 ... buf = memdup_user(buffer, count); if (IS_ERR(buf)) { ret = PTR_ERR(buf); goto out; } if ((report_type == HID_OUTPUT_REPORT) && !(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) { ret = hid_hw_output_report(dev, buf, count); if (ret != -ENOSYS) goto out_free; } ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type, HID_REQ_SET_REPORT);----------------------------------------------调用实际硬件接口发送report,比如usb、bluetooth等。 out_free: kfree(buf); out: return ret; } static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { ssize_t ret; mutex_lock(&minors_lock); ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT); mutex_unlock(&minors_lock); return ret; } static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type) { unsigned int minor = iminor(file_inode(file)); struct hid_device *dev; __u8 *buf; int ret = 0, len; unsigned char report_number; dev = hidraw_table[minor]->hid; ... buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); if (!buf) { ret = -ENOMEM; goto out; } if (copy_from_user(&report_number, buffer, 1)) { ret = -EFAULT; goto out_free; } ret = hid_hw_raw_request(dev, report_number, buf, count, report_type, HID_REQ_GET_REPORT);---------------------------------------------从硬件接口接收数据。 ... if (copy_to_user(buffer, buf, len)) { ret = -EFAULT; goto out_free; } ret = len; out_free: kfree(buf); out: return ret; } static unsigned int hidraw_poll(struct file *file, poll_table *wait) { struct hidraw_list *list = file->private_data; poll_wait(file, &list->hidraw->wait, wait); if (list->head != list->tail) return POLLIN | POLLRDNORM; if (!list->hidraw->exist) return POLLERR | POLLHUP; return 0; } static int hidraw_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); struct hidraw *dev; struct hidraw_list *list; unsigned long flags; int err = 0; if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { err = -ENOMEM; goto out; } mutex_lock(&minors_lock); if (!hidraw_table[minor] || !hidraw_table[minor]->exist) { err = -ENODEV; goto out_unlock; } dev = hidraw_table[minor]; if (!dev->open++) { err = hid_hw_power(dev->hid, PM_HINT_FULLON);--------------调用底层设备即具体接口的power()函数,hdev->ll_driver->power()。 if (err < 0) { dev->open--; goto out_unlock; } err = hid_hw_open(dev->hid);-------------------------------调用底层设备的open()函数,hdev->ll_driver->open()。 if (err < 0) { hid_hw_power(dev->hid, PM_HINT_NORMAL); dev->open--; goto out_unlock; } } list->hidraw = hidraw_table[minor]; mutex_init(&list->read_mutex); spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); list_add_tail(&list->node, &hidraw_table[minor]->list); spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); file->private_data = list; ... } static int hidraw_fasync(int fd, struct file *file, int on) { struct hidraw_list *list = file->private_data; return fasync_helper(fd, file, on, &list->fasync);--------------发送异步通知信号。 } static void drop_ref(struct hidraw *hidraw, int exists_bit) { if (exists_bit) { hidraw->exist = 0; if (hidraw->open) { hid_hw_close(hidraw->hid); wake_up_interruptible(&hidraw->wait); } device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); } else { --hidraw->open;---------------------------------------------打开计算减1。 } if (!hidraw->open) {--------------------------------------------当计数为0后,开始释放资源。 if (!hidraw->exist) { hidraw_table[hidraw->minor] = NULL; kfree(hidraw); } else { /* close device for last reader */ hid_hw_power(hidraw->hid, PM_HINT_NORMAL); hid_hw_close(hidraw->hid); } } } static int hidraw_release(struct inode * inode, struct file * file) { unsigned int minor = iminor(inode); struct hidraw_list *list = file->private_data; unsigned long flags; mutex_lock(&minors_lock); spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); list_del(&list->node); spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); kfree(list); drop_ref(hidraw_table[minor], 0); mutex_unlock(&minors_lock); return 0; } static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(file); unsigned int minor = iminor(inode); long ret = 0; struct hidraw *dev; void __user *user_arg = (void __user*) arg; mutex_lock(&minors_lock); dev = hidraw_table[minor]; if (!dev) { ret = -ENODEV; goto out; } switch (cmd) { case HIDIOCGRDESCSIZE:----------------------------------------------Get report descriptor size。 if (put_user(dev->hid->rsize, (int __user *)arg)) ret = -EFAULT; break; case HIDIOCGRDESC:--------------------------------------------------Get report descriptor。 { __u32 len; if (get_user(len, (int __user *)arg)) ret = -EFAULT; else if (len > HID_MAX_DESCRIPTOR_SIZE - 1) ret = -EINVAL; else if (copy_to_user(user_arg + offsetof( struct hidraw_report_descriptor, value[0]), dev->hid->rdesc, min(dev->hid->rsize, len))) ret = -EFAULT; break; } case HIDIOCGRAWINFO:------------------------------------------------Get raw info,包括bus类型,vid和pid。 { struct hidraw_devinfo dinfo; dinfo.bustype = dev->hid->bus; dinfo.vendor = dev->hid->vendor; dinfo.product = dev->hid->product; if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) ret = -EFAULT; break; } default: { struct hid_device *hid = dev->hid; if (_IOC_TYPE(cmd) != 'H') { ret = -EINVAL; break; } if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {-------------Send a feature report。 int len = _IOC_SIZE(cmd); ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); break; } if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {-------------Get a feature report。 int len = _IOC_SIZE(cmd); ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); break; } /* Begin Read-only ioctls. */ if (_IOC_DIR(cmd) != _IOC_READ) { ret = -EINVAL; break; } if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {--------------Get raw name。 int len = strlen(hid->name) + 1; if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); ret = copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; break; } if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {---------------Get physical address。 int len = strlen(hid->phys) + 1; if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); ret = copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; break; } } ret = -ENOTTY; } out: mutex_unlock(&minors_lock); return ret; }
由于一个系统下可能存在多个hidraw设备,常通过HIDIOCGRAWINFO获取信息,判断对应的hidraw设备。
2.3 创建hidraw设备
int hidraw_connect(struct hid_device *hid) { int minor, result; struct hidraw *dev; /* we accept any HID device, all applications */ dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL); if (!dev) return -ENOMEM; result = -EINVAL; mutex_lock(&minors_lock); for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) {--------------------分配hidraw设备的minor号,并将dev赋给hidraw_table[]。 if (hidraw_table[minor]) continue; hidraw_table[minor] = dev; result = 0; break; } if (result) { mutex_unlock(&minors_lock); kfree(dev); goto out; } dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor), NULL, "%s%d", "hidraw", minor);------------------------------创建/dev/hidrawX设备节点。 if (IS_ERR(dev->dev)) { hidraw_table[minor] = NULL; mutex_unlock(&minors_lock); result = PTR_ERR(dev->dev); kfree(dev); goto out; } init_waitqueue_head(&dev->wait); spin_lock_init(&dev->list_lock); INIT_LIST_HEAD(&dev->list); dev->hid = hid; dev->minor = minor; dev->exist = 1; hid->hidraw = dev; mutex_unlock(&minors_lock); out: return result; } EXPORT_SYMBOL_GPL(hidraw_connect); void hidraw_disconnect(struct hid_device *hid) { struct hidraw *hidraw = hid->hidraw; mutex_lock(&minors_lock); drop_ref(hidraw, 1); mutex_unlock(&minors_lock); } EXPORT_SYMBOL_GPL(hidraw_disconnect);
所以hidraw的驱动分为两部分,一是hidraw_init()发起的整个hidraw设备驱动的初始化,二是底层驱动检测到hidraw设备后通过hidraw_connect()/hidraw_disconnect()创建或者销毁设备。
当USB/Bluetooth检查到设备是hidraw类型之后,就会调用hidraw提供的接口创建设备,表现为创建节点/dev/hidrawx。
后续用户空间程序对/dev/hidrawx进行read/write/ioctl等操作。
3. hidraw测试程序
内核提供了一个示例程序hid-example.c,下面结合内核代码简单分析一下。遍历/dev下所有的hidraw设备,然后读取信息。
/* Linux */ #include <linux/types.h> #include <linux/input.h> #include <linux/hidraw.h> #include <dirent.h> #ifndef HIDIOCSFEATURE #warning Please have your distro update the userspace kernel headers #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; char device[32] = "/dev/hidraw0"; DIR *dir = NULL; struct dirent *ptr; /* Open dir /dev. */ dir = opendir("/dev");-------------------------------------------------打开/dev目录。 if(!dir) { perror("Popen dir failed..."); return -ENOENT; } /* */ while(ptr = readdir(dir)) {--------------------------------------------遍历/dev目录下所有的文件。 if(ptr->d_type != DT_CHR)------------------------------------------判断设备是否为字符设备,其他设备包括DT_UNKNOWN/DT_FIFO/DT_CHR DT_DIR/DT_BLK/DT_REG/DT_LNK/DT_SOCK/DT_WHT。
continue;
if(!strncmp(ptr->d_name, "hidraw", 6)) { snprintf(device, sizeof(device), "%s/%s", "/dev", ptr->d_name); /* Open the Device with non-blocking reads. In real life, don't use a hard coded path; use libudev instead. */ fd = open(device, O_RDWR|O_NONBLOCK);--------------------------打开hidraw设备,对应内核的hidraw_open()。 if (fd < 0) { printf("Unable to open device %s. ", device); return 1; } memset(&rpt_desc, 0x0, sizeof(rpt_desc)); memset(&info, 0x0, sizeof(info)); memset(buf, 0x0, sizeof(buf)); printf(" =================================Device %s info================================= ", device); /* Get Raw Name */ res = ioctl(fd, HIDIOCGRAWNAME(256), buf);---------------------对应hidraw_ioctl()的HIDIOCGRAWNAME。 if (res < 0) perror("HIDIOCGRAWNAME"); else printf("Raw Name: %s ", buf); /* Get Physical Location */ res = ioctl(fd, HIDIOCGRAWPHYS(256), buf);---------------------对应HIDIOCGRAWPHYS。 if (res < 0) perror("HIDIOCGRAWPHYS"); else printf("Raw Phys: %s ", buf); /* Get Raw Info */ res = ioctl(fd, HIDIOCGRAWINFO, &info);-------------------------对应HIDIOCGRAWINFO。 if (res < 0) { perror("HIDIOCGRAWINFO"); } else { printf("Raw Info: "); printf(" bustype: %d (%s) ", info.bustype, bus_str(info.bustype)); printf(" vendor: 0x%04hx ", info.vendor); printf(" product: 0x%04hx ", info.product); } /* Get Report Descriptor Size */ res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);------------------对应HIDIOCGRDESCSIZE if (res < 0) perror("HIDIOCGRDESCSIZE"); else printf("Report Descriptor Size: %d ", desc_size); /* Get Report Descriptor */ rpt_desc.size = desc_size; res = ioctl(fd, HIDIOCGRDESC, &rpt_desc);------------------------对应HIDIOCGRDESC。 if (res < 0) { perror("HIDIOCGRDESC"); } else { printf("Report Descriptor: "); for (i = 0; i < rpt_desc.size; i++) printf("%hhx ", rpt_desc.value[i]); puts(" "); } /* Set Feature */ buf[0] = 0x9; /* Report Number */ buf[1] = 0xff; buf[2] = 0xff; buf[3] = 0xff; res = ioctl(fd, HIDIOCSFEATURE(4), buf);-------------------------对应HIDIOCSFEATURE。 if (res < 0) perror("HIDIOCSFEATURE"); else printf("ioctl HIDIOCGFEATURE returned: %d ", res); /* Get Feature */ buf[0] = 0x9; /* Report Number */ res = ioctl(fd, HIDIOCGFEATURE(256), buf);-----------------------对应HIDIOCGFEATURE。 if (res < 0) { perror("HIDIOCGFEATURE"); } else { printf("ioctl HIDIOCGFEATURE returned: %d ", res); printf("Report data (not containing the report number): "); for (i = 0; i < res; i++) printf("%hhx ", buf[i]); puts(" "); } /* 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 ", errno); perror("write"); } else { printf("write() wrote %d bytes ", res); } /* Get a report from the device */ res = read(fd, buf, 16); if (res < 0) { perror("read"); } else { printf("read() read %d bytes: ", res); for (i = 0; i < res; i++) printf("%hhx ", buf[i]); puts(" "); } 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; } }
综合来看hidraw设备是hid的一种,传输裸数据,对应的底层硬件可能是USB/Bluetooth等。
通过对hidraw设备的操作,ioctl可以配置hidraw设备,read/write可以对hidraw设备进行读写。
参考文档:
《HIDRAW - Raw Access to USB and Bluetooth Human Interface Devices》:包括简要介绍以及hidraw相关API介绍,尤其是ioctl命令。
《Linux之访问/dev/hidraw》是对上文的翻译,附加了两个示例。