linux驱动知识点

合集下载

Linux设备驱动之UIO机制

Linux设备驱动之UIO机制

Linux设备驱动之UIO机制⼀个设备驱动的主要任务有两个:1. 存取设备的内存2. 处理设备产⽣的中断对于第⼀个任务。

UIO 核⼼实现了mmap()能够处理物理内存(physical memory),逻辑内存(logical memory),虚拟内存(virtual memory)。

UIO驱动的编写是就不须要再考虑这些繁琐的细节。

第⼆个任务,对于设备中断的应答必须在内核空间进⾏。

所以在内核空间有⼀⼩部分代码⽤来应答中断和禁⽌中断,可是其余的⼯作所有留给⽤户空间处理。

假设⽤户空间要等待⼀个设备中断,它仅仅须要简单的堵塞在对 /dev/uioX的read()操作上。

当设备产⽣中断时,read()操作马上返回。

UIO 也实现了poll()系统调⽤。

你能够使⽤select()来等待中断的发⽣。

select()有⼀个超时參数能够⽤来实现有限时间内等待中断。

对设备的控制还能够通过/sys/class/uio下的各个⽂件的读写来完毕。

你注冊的uio设备将会出如今该⽂件夹下。

假如你的uio设备是uio0那么映射的设备内存⽂件出如今 /sys/class/uio/uio0/maps/mapX。

对该⽂件的读写就是对设备内存的读写。

例如以下的图描写叙述了uio驱动的内核部分。

⽤户空间部分。

和uio 框架以及内核内部函数的关系。

⼆:UIO驱动注册⾸先来看⼀个简单的UIO驱动代码,代码来⾃⽹上,⾮原创,旨在学习内核部分:/** This is simple demon of uio driver.* Version 1*Compile:* Save this file name it simple.c* #echo "obj -m := simple.o" > Makefile* #make -Wall -C /lib/modules/'uname -r'/build M='pwd' modules*Load the module:* #modprobe uio* #insmod simple.ko*/#include <linux/module.h>#include <linux/platform_device.h>#include <linux/uio_driver.h>#include <linux/slab.h>/*struct uio_info {struct uio_device *uio_dev; // 在__uio_register_device中初始化const char *name; // 调⽤__uio_register_device之前必须初始化const char *version; //调⽤__uio_register_device之前必须初始化struct uio_mem mem[MAX_UIO_MAPS];struct uio_port port[MAX_UIO_PORT_REGIONS];long irq; //分配给uio设备的中断号,调⽤__uio_register_device之前必须初始化unsigned long irq_flags;// 调⽤__uio_register_device之前必须初始化void *priv; //irqreturn_t (*handler)(int irq, struct uio_info *dev_info); //uio_interrupt中调⽤,⽤于中断处理// 调⽤__uio_register_device之前必须初始化int (*mmap)(struct uio_info *info, struct vm_area_struct *vma); //在uio_mmap中被调⽤,// 执⾏设备打开特定操作int (*open)(struct uio_info *info, struct inode *inode);//在uio_open中被调⽤,执⾏设备打开特定操作int (*release)(struct uio_info *info, struct inode *inode);//在uio_device中被调⽤,执⾏设备打开特定操作int (*irqcontrol)(struct uio_info *info, s32 irq_on);//在uio_write⽅法中被调⽤,执⾏⽤户驱动的//特定操作。

linux显卡驱动安装

linux显卡驱动安装

linux显卡驱动安装Linux是一个自由和开放源代码的操作系统,它的目标是提供一个可靠、稳定、安全的操作平台。

在Linux系统中,显卡驱动的安装非常重要,因为显卡驱动决定了系统图形性能的好坏。

在Linux系统中,显卡驱动的安装一般有两种方式:通过软件包管理器安装和手动安装。

通过软件包管理器安装显卡驱动是最常见、最简单的方式。

不同的Linux发行版使用不同的软件包管理器,如Ubuntu使用apt,Fedora使用dnf等。

用户只需要打开终端,输入相应的命令就可以自动下载和安装显卡驱动。

例如,在Ubuntu中,用户可以使用以下命令安装NVIDIA显卡驱动:sudo apt-get install nvidia-驱动版本号手动安装显卡驱动则需要用户自己去官网下载驱动程序,并按照安装说明进行安装。

手动安装驱动相对较为复杂,需要用户有一定的技术基础。

首先,用户需要确定自己所使用的显卡型号,并到显卡厂商官方网站上找到对应的Linux驱动程序。

然后,用户需要下载驱动程序,并将其解压到一个合适的目录中。

接下来,用户需要打开终端,进入到驱动程序所在的目录,然后运行安装脚本。

安装脚本会自动检测系统配置,并安装相应的驱动程序。

最后,用户需要重新启动电脑,使驱动程序生效。

无论是通过软件包管理器安装还是手动安装,安装完显卡驱动后,用户可以通过系统设置中的“显示”来查看和配置显卡驱动。

在这里,用户可以调整分辨率、刷新率、亮度等参数,以获得更好的图形性能和显示效果。

总的来说,Linux系统中的显卡驱动安装相对于Windows系统来说稍微复杂一些,但只要按照正确的方法和步骤进行操作,就能轻松完成安装,并获得良好的图形性能。

所以,对于使用Linux系统的用户来说,学会如何安装显卡驱动是非常有必要的。

Linux设备驱动之Ioctl控制

Linux设备驱动之Ioctl控制

Linux设备驱动之Ioctl控制 ⼤部分驱动除了需要具备读写设备的能⼒之外,还需要具备对硬件控制的能⼒。

 ⼀、在⽤户空间,使⽤ioctl系统调⽤来控制设备,原型如下:int ioctl(int fd,unsigned long cmd,...);/*fd:⽂件描述符cmd:控制命令...:可选参数:插⼊*argp,具体内容依赖于cmd*/ ⽤户程序所作的只是通过命令码告诉驱动程序它想做什么,⾄于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

⼆、驱动ioctl⽅法:int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);/*inode与filp两个指针对应于应⽤程序传递的⽂件描述符fd,这和传递open⽅法的参数⼀样。

cmd 由⽤户空间直接不经修改的传递给驱动程序arg 可选。

*/ 在驱动程序中实现的ioctl函数体内,实际上是有⼀个switch {case}结构,每⼀个case对应⼀个命令码,做出⼀些相应的操作。

怎么实现这些操作,这是每⼀个程序员⾃⼰的事情,因为设备都是特定的。

关键在于怎么样组织命令码,因为在ioctl中命令码是唯⼀联系⽤户程序命令和驱动程序⽀持的途径。

在Linux核⼼中是这样定义⼀个命令码的:____________________________________| 设备类型 | 序列号 | ⽅向 | 数据尺⼨ ||----------|--------|------|-------- || 8 bit | 8 bit | 2 bit |8~14 bit||----------|--------|------|-------- | 这样⼀来,⼀个命令就变成了⼀个整数形式的命令码。

但是命令码⾮常的不直观,所以Linux Kernel中提供了⼀些宏,这些宏可根据便于理解的字符串⽣成命令码,或者是从命令码得到⼀些⽤户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送⽅向和数据传输尺⼨。

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块内核模块介绍Linux采用的是整体式的内核结构,这种结构采用的是整体式的内核结构,采用的是整体式的内核结构的内核一般不能动态的增加新的功能。

为此,的内核一般不能动态的增加新的功能。

为此,Linux提供了一种全新的机制,叫(可安装) 提供了一种全新的机制,可安装) 提供了一种全新的机制模块” )。

利用这个机制“模块”(module)。

利用这个机制,可以)。

利用这个机制,根据需要,根据需要,在不必对内核重新编译链接的条件将可安装模块动态的插入运行中的内核,下,将可安装模块动态的插入运行中的内核,成为内核的一个有机组成部分;成为内核的一个有机组成部分;或者从内核移走已经安装的模块。

正是这种机制,走已经安装的模块。

正是这种机制,使得内核的内存映像保持最小,的内存映像保持最小,但却具有很大的灵活性和可扩充性。

和可扩充性。

内核模块内核模块介绍可安装模块是可以在系统运行时动态地安装和卸载的内核软件。

严格来说,卸载的内核软件。

严格来说,这种软件的作用并不限于设备驱动,并不限于设备驱动,例如有些文件系统就是以可安装模块的形式实现的。

但是,另一方面,可安装模块的形式实现的。

但是,另一方面,它主要用来实现设备驱动程序或者与设备驱动密切相关的部分(如文件系统等)。

密切相关的部分(如文件系统等)。

课程内容内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块应用层加载模块操作过程内核引导的过程中,会识别出所有已经安装的硬件设备,内核引导的过程中,会识别出所有已经安装的硬件设备,并且创建好该系统中的硬件设备的列表树:文件系统。

且创建好该系统中的硬件设备的列表树:/sys 文件系统。

(udev 服务就是通过读取该文件系统内容来创建必要的设备文件的。

)。

Linux设备驱动之HID驱动---非常全面而且深刻

Linux设备驱动之HID驱动---非常全面而且深刻

Linux设备驱动之HID驱动---⾮常全⾯⽽且深刻本⽂系本站原创,欢迎转载!转载请注明出处:/------------------------------------------⼀:前⾔继前⾯分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做⼀个具体的USB设备驱动分析的例⼦.HID是Human Interface Devices的缩写.翻译成中⽂即为⼈机交互设备.这⾥的⼈机交互设备是⼀个宏观上⾯的概念,任何设备,只要符合HID spec,都⼆:HID驱动⼊⼝分析USB HID设备驱动⼊⼝位于linux-2.6.25/drivers/hid/usbhid/hid-core.c中.该module的⼊⼝为hid_init().代码如下:static int __init hid_init(void){int retval;retval = usbhid_quirks_init(quirks_param);if (retval)goto usbhid_quirks_init_fail;retval = hiddev_init();if (retval)goto hiddev_init_fail;retval = usb_register(&hid_driver);if (retval)goto usb_register_fail;info(DRIVER_VERSION ":" DRIVER_DESC);return0;usb_register_fail:hiddev_exit();hiddev_init_fail:usbhid_quirks_exit();usbhid_quirks_init_fail:return retval;}⾸先来看usbhid_quirks_init()函数.quirks我们在分析UHCI和HUB的时候也接触过,表⽰需要做某种修正的设备.该函数调⽤的参数是quirks_param.定义如下:static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };module_param_array_named(quirks, quirks_param, charp, NULL, 0444);从此可以看出, quirks_param是MAX_USBHID_BOOT_QUIRKS元素的字符串数组.并且在加载module的时候,可以动态的指定这些值.分析到这⾥.有⼈可以反应过来了,usbhid_quirks_init()是⼀种动态进⾏HID设备修正的⽅式.具体要修正哪些设备,要修正设备的那些⽅⾯,都可以由加载模块是所带参数来决定.usbhid_quirks_init()的代码如下:int usbhid_quirks_init(char **quirks_param){u16 idVendor, idProduct;u32 quirks;int n = 0, m;for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",&idVendor, &idProduct, &quirks);if (m != 3 ||usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {printk(KERN_WARNING"Could not parse HID quirk module param %s\n",quirks_param[n]);}}return0;}由此可以看出, quirks_param数组中的每⼀项可以分为三个部份,分别是要修正设备的VendorID,ProductID和要修正的功能.⽐如0x1000 0x0001 0x0004就表⽰:要忽略掉VendorID为0x1000,ProductID为0x0004的设备.(在代码中,有#define HID_QUIRK_跟进usbhid_modify_dquirk()函数,代码如下:int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,const u32 quirks){struct quirks_list_struct *q_new, *q;int list_edited = 0;if (!idVendor) {dbg_hid("Cannot add a quirk with idVendor = 0\n");return -EINVAL;}q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);if (!q_new) {dbg_hid("Could not allocate quirks_list_struct\n");return -ENOMEM;}q_new->hid_bl_item.idVendor = idVendor;q_new->hid_bl_item.idProduct = idProduct;q_new->hid_bl_item.quirks = quirks;down_write(&dquirks_rwsem);list_for_each_entry(q, &dquirks_list, node) {if (q->hid_bl_item.idVendor == idVendor &&q->hid_bl_item.idProduct == idProduct) {list_replace(&q->node, &q_new->node);kfree(q);list_edited = 1;break;}}if (!list_edited)list_add_tail(&q_new->node, &dquirks_list);up_write(&dquirks_rwsem);return0;}这个函数⽐较简单,就把quirks_param数组项中的三个部份存⼊⼀个封装结构.然后将其结构挂载到dquirks_list表.如果dquirks_list有重复的VendorId和ProductID就更新其quirks信息.经过usbhid_quirks_init()之后,所有要修正的设备的相关操作都会存放在dquirks_list中.返回到hid_init(),继续往下⾯分析.hiddev_init()是⼀个⽆关的操作,不会影响到后⾯的操作.忽略后⾯就是我们今天要分析的重点了,如下:retval = usb_register(&hid_driver);通过前⾯对HUB的驱动分析,相信对usb_redister()应该很熟悉了.hid_driver定义如下:static struct usb_driver hid_driver = {.name = "usbhid",.probe = hid_probe,.disconnect = hid_disconnect,.suspend = hid_suspend,.resume = hid_resume,.reset_resume = hid_post_reset,.pre_reset = hid_pre_reset,.post_reset = hid_post_reset,.id_table = hid_usb_ids,.supports_autosuspend = 1,};其中,id_table的结构为hid_usb_ids.定义如下:static struct usb_device_id hid_usb_ids [] = {{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,.bInterfaceClass = USB_INTERFACE_CLASS_HID },{ } /* Terminating entry */};也就是说,该驱动会匹配interface的ClassID,所有ClassID为USB_INTERFACE_CLASS_HID的设备都会被这个驱动所匹配.所以,所有USB HID设备都会由这个module来驱动.三:HID驱动的probe过程从上⾯的分析可看到,probe接⼝为hid_probe().定义如下:static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id){struct hid_device *hid;char path[64];int i;char *c;dbg_hid("HID probe called for ifnum %d\n",intf->altsetting->desc.bInterfaceNumber);//config the hid deviceif (!(hid = usb_hid_configure(intf)))return -ENODEV;usbhid_init_reports(hid);hid_dump_device(hid);if (hid->quirks & HID_QUIRK_RESET_LEDS)usbhid_set_leds(hid);if (!hidinput_connect(hid))hid->claimed |= HID_CLAIMED_INPUT;if (!hiddev_connect(hid))hid->claimed |= HID_CLAIMED_HIDDEV;if (!hidraw_connect(hid))hid->claimed |= HID_CLAIMED_HIDRAW;usb_set_intfdata(intf, hid);if (!hid->claimed) {printk ("HID device claimed by neither input, hiddev nor hidraw\n");hid_disconnect(intf);return -ENODEV;}if ((hid->claimed & HID_CLAIMED_INPUT))hid_ff_init(hid);if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),intf->cur_altsetting->desc.bInterfaceNumber);printk(KERN_INFO);if (hid->claimed & HID_CLAIMED_INPUT)printk("input");if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) ||hid->claimed & HID_CLAIMED_HIDRAW))printk(",");if (hid->claimed & HID_CLAIMED_HIDDEV)printk("hiddev%d", hid->minor);if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) &&(hid->claimed & HID_CLAIMED_HIDRAW))printk(",");if (hid->claimed & HID_CLAIMED_HIDRAW)printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor);c = "Device";for (i = 0; i < hid->maxcollection; i++) {if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&(hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&(hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {c = hid_types[hid->collection[i].usage & 0xffff];break;}}usb_make_path(interface_to_usbdev(intf), path, 63);printk(": USB HID v%x.%02x %s [%s] on %s\n",hid->version >> 8, hid->version & 0xff, c, hid->name, path);return0;}这个函数看起来是不是让⼈⼼慌慌?其实这个函数的最后⼀部份就是打印出⼀个Debug信息,我们根本就不需要去看. hiddev_connect()和hidraw_connect()是⼀个选择编译的操作,也不可以不要去理会.然后,剩下的就没多少了.3.1:usb_hid_configure()函数分析先来看usb_hid_configure().顾名思义,该接⼝⽤来配置hid设备.怎么配置呢?还是深⼊到代码来分析,该函数有⼀点长,分段分析如下:static struct hid_device *usb_hid_configure(struct usb_interface *intf){struct usb_host_interface *interface = intf->cur_altsetting;struct usb_device *dev = interface_to_usbdev (intf);struct hid_descriptor *hdesc;struct hid_device *hid;u32 quirks = 0;unsigned rsize = 0;char *rdesc;int n, len, insize = 0;struct usbhid_device *usbhid;quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));/* Many keyboards and mice don't like to be polled for reports,* so we will always set the HID_QUIRK_NOGET flag for them. *///如果是boot设备,跳出.不由此驱动处理if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)quirks |= HID_QUIRK_NOGET;}//如果是要忽略的if (quirks & HID_QUIRK_IGNORE)return NULL;if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&(interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))return NULL;⾸先找到该接⼝需要修正的操作,也就是上⾯代码中的quirks值,如果没有修正操作,则quirks为0.另外,根据usb hid spec中的定义,subclass如果为1,则说明该设备是⼀个boot阶段使⽤的hid设备,然后Protocol Code为1和2时分别代表Keyboard和Mouse. 如//get hid descriptorsif (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&(!interface->desc.bNumEndpoints ||usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {dbg_hid("class descriptor not present\n");return NULL;}//bNumDescriptors:⽀持的附属描述符数⽬for (n = 0; n < hdesc->bNumDescriptors; n++)if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);//如果Report_Descriptors长度不合法if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {dbg_hid("weird size of report descriptor (%u)\n", rsize);return NULL;}if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {dbg_hid("couldn't allocate rdesc memory\n");return NULL;}//Set idle_time = 0hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);//Get Report_Descriptorsif ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {dbg_hid("reading report descriptor failed\n");kfree(rdesc);return NULL;}//是否属于fixup?usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct), rdesc,rsize, rdesc_quirks_param);dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);for (n = 0; n < rsize; n++)dbg_hid_line(" %02x", (unsigned char) rdesc[n]);dbg_hid_line("\n");对于HID设备来说,在interface description之后会附加⼀个hid description, hid description中的最后部份包含有Report description或者Physical Descriptors的长度.在上⾯的代码中,⾸先取得附加在interface description之后的hid description,然后,再从hid description中取得report description的长度.最后,取得report description的详细信息.在这⾥,还会将idle时间设备为0,表⽰⽆限时,即,从上⼀次报表传输后,只有在报表发⽣改变时,才会传送此报表内容,否则,传送NAK.这段代码的最后⼀部份是相关的fixup操作,不做详细分析.//pasrse the report_descriptorif (!(hid = hid_parse_report(rdesc, n))) {dbg_hid("parsing report descriptor failed\n");kfree(rdesc);return NULL;}kfree(rdesc);hid->quirks = quirks;if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))goto fail_no_usbhid;hid->driver_data = usbhid;usbhid->hid = hid;解析获得的report description,解析之后的信息,存放在hid_device->collection和hid_device->report_enum[ ]中,这个解析过程之后会做详细分析.然后,初始化⼀个usbhid_device结构,使usbhid_device->hid指向刚解析report description获得的hid_device.同 usbhid->bufsize = HID_MIN_BUFFER_SIZE;//计算各传输⽅向的最⼤bufferhid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)usbhid->bufsize = HID_MAX_BUFFER_SIZE;//in⽅向的传输最⼤值hid_find_max_report(hid, HID_INPUT_REPORT, &insize);if (insize > HID_MAX_BUFFER_SIZE)insize = HID_MAX_BUFFER_SIZE;if (hid_alloc_buffers(dev, hid)) {hid_free_buffers(dev, hid);goto fail;}计算传输数据的最⼤缓存区,并以这个⼤⼩为了hid设备的urb传输分配空间.另外,这⾥有⼀个最⼩值限制即代码中所看到的HID_MIN_BUFFER_SIZE,为64, 即⼀个⾼速设备的⼀个端点⼀次传输的数据量.在这⾥定义最⼩值为64是为了照顾低速然后,调⽤hid_alloc_buffers()为hid的urb传输初始化传输缓冲区.另外,需要注意的是,insize为INPUT⽅向的最⼤数据传输量.// 初始化usbhid->urbin和usbhid->usboutfor (n = 0; n < interface->desc.bNumEndpoints; n++) {struct usb_endpoint_descriptor *endpoint;int pipe;int interval;endpoint = &interface->endpoint[n].desc;//不是中断传输退出if ((endpoint->bmAttributes & 3) != 3) /* Not an interrupt endpoint */continue;interval = endpoint->bInterval;/* Change the polling interval of mice. *///修正⿏标的双击时间if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)interval = hid_mousepoll_interval;if (usb_endpoint_dir_in(endpoint)) {if (usbhid->urbin)continue;if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))goto fail;pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize,hid_irq_in, hid, interval);usbhid->urbin->transfer_dma = usbhid->inbuf_dma;usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;} else {if (usbhid->urbout)continue;if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL)))goto fail;pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress);usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0,hid_irq_out, hid, interval);usbhid->urbout->transfer_dma = usbhid->outbuf_dma;usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;}}if (!usbhid->urbin) {err_hid("couldn't find an input interrupt endpoint");goto fail;}遍历接⼝中的所有endpoint,并初始化in中断传输⽅向和out中断⽅向的urb.如果⼀个hid设备没有in⽅向的中断传输,⾮法.另外,在这⾥要值得注意的是, 在为OUT⽅向urb初始化的时候,它的传输缓存区⼤⼩被设为了0.IN⽅向的中断传输缓存区⼤⼩被设为了insize,传输缓存区⼤⼩在submit的时候会修正的. init_waitqueue_head(&hid->wait);INIT_WORK(&usbhid->reset_work, hid_reset);setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);spin_lock_init(&usbhid->inlock);spin_lock_init(&usbhid->outlock);spin_lock_init(&usbhid->ctrllock);hid->version = le16_to_cpu(hdesc->bcdHID);hid->country = hdesc->bCountryCode;hid->dev = &intf->dev;usbhid->intf = intf;usbhid->ifnum = interface->desc.bInterfaceNumber;hid->name[0] = 0;if (dev->manufacturer)strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));if (dev->product) {if (dev->manufacturer)strlcat(hid->name, "", sizeof(hid->name));strlcat(hid->name, dev->product, sizeof(hid->name));}if (!strlen(hid->name))snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));hid->bus = BUS_USB;hid->vendor = le16_to_cpu(dev->descriptor.idVendor);hid->product = le16_to_cpu(dev->descriptor.idProduct);usb_make_path(dev, hid->phys, sizeof(hid->phys));strlcat(hid->phys, "/input", sizeof(hid->phys));len = strlen(hid->phys);if (len < sizeof(hid->phys) - 1)snprintf(hid->phys + len, sizeof(hid->phys) - len,"%d", intf->altsetting[0].desc.bInterfaceNumber);if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)hid->uniq[0] = 0;初始化hid的相关信息.//初始化hid 的ctrl传输usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);if (!usbhid->urbctrl)goto fail;usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,usbhid->ctrlbuf, 1, hid_ctrl, hid);usbhid->urbctrl->setup_dma = usbhid->cr_dma;usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);hid->hidinput_input_event = usb_hidinput_input_event;hid->hid_open = usbhid_open;hid->hid_close = usbhid_close;#ifdef CONFIG_USB_HIDDEVhid->hiddev_hid_event = hiddev_hid_event;hid->hiddev_report_event = hiddev_report_event;#endifhid->hid_output_raw_report = usbhid_output_raw_report;return hid;初始化usbhid的控制传输urb,之后⼜初始化了usbhid的⼏个操作函数.这个操作有什么⽤途,等⽤到的时候再来进⾏分析.fail:usb_free_urb(usbhid->urbin);usb_free_urb(usbhid->urbout);usb_free_urb(usbhid->urbctrl);hid_free_buffers(dev, hid);kfree(usbhid);fail_no_usbhid:hid_free_device(hid);return NULL;}经过上⾯的分析之后,我们对这个函数的⼤概操作有了⼀定的了解.现在分析⾥⾯调⽤的⼀些重要的⼦调函数.等这些⼦函数全部分析完了之后,不妨回过头看下这个函数.3.1.1:hid_parse_report()分析第⼀个要分析的函数是hid_parse_report().该函数⽤来解析report description.解析report description是⼀个繁杂的过程,对这个描述符不太清楚的,仔细看⼀下spec.在这⾥我们只会做代码上的分析.代码如下:struct hid_device *hid_parse_report(__u8 *start, unsigned size){struct hid_device *device;struct hid_parser *parser;struct hid_item item;__u8 *end;unsigned i;static int (*dispatch_type[])(struct hid_parser *parser,struct hid_item *item) = {hid_parser_main,hid_parser_global,hid_parser_local,hid_parser_reserved};if (!(device = kzalloc(sizeof(struct hid_device), GFP_KERNEL)))return NULL;//默认HID_DEFAULT_NUM_COLLECTIONS 项if (!(device->collection = kzalloc(sizeof(struct hid_collection) *HID_DEFAULT_NUM_COLLECTIONS, GFP_KERNEL))) {kfree(device);return NULL;}//hid_device->collection_size: collection的项数device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;for (i = 0; i < HID_REPORT_TYPES; i++)INIT_LIST_HEAD(&device->report_enum[i].report_list);if (!(device->rdesc = kmalloc(size, GFP_KERNEL))) {kfree(device->collection);kfree(device);return NULL;}//hid_device->rdesc存放report_descriptor,hid_device->size存放这个描述符的⼤⼩memcpy(device->rdesc, start, size);device->rsize = size;if (!(parser = vmalloc(sizeof(struct hid_parser)))) {kfree(device->rdesc);kfree(device->collection);kfree(device);return NULL;}memset(parser, 0, sizeof(struct hid_parser));parser->device = device;end = start + size;while ((start = fetch_item(start, end, &item)) != NULL) {//long item在这⾥暂不做parseif (item.format != HID_ITEM_FORMAT_SHORT) {dbg_hid("unexpected long global item\n");hid_free_device(device);vfree(parser);return NULL;}//parse the short itemif (dispatch_type[item.type](parser, &item)) {dbg_hid("item %u %u %u %u parsing failed\n",item.format, (unsigned)item.size, (unsigned)item.type, (unsigned)item.tag);hid_free_device(device);vfree(parser);return NULL;}//如果全部解析完了if (start == end) {if (parser->collection_stack_ptr) {dbg_hid("unbalanced collection at end of report description\n");hid_free_device(device);vfree(parser);return NULL;}if (parser->local.delimiter_depth) {dbg_hid("unbalanced delimiter at end of report description\n");hid_free_device(device);vfree(parser);return NULL;}vfree(parser);return device;}}dbg_hid("item fetching failed at offset %d\n", (int)(end - start));hid_free_device(device);vfree(parser);return NULL;}进⼊到这个函数,我们⾸先看到的是Main,Globa,Local标签的解析函数.然后,分配并初始化了hid_device结构和hid_ parser.在代码中我们看到,hid_ parser-> device指向了hid_device.后hid_device没有任何域指向hid_parser. 实际上hid_parser只是⼀个辅另外,hid_device-> rdesc保存了⼀份report description副本.然后,就开始对report description的解析.函数fetch_item()⽤来取出report description的⼀项数据.代码如下:static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item){u8 b;//合法性检测if ((end - start) <= 0)return NULL;//取前⾯⼀个字节.对于短项.它的⾸个字节定义了bsize,bType,bTag.⽽对于长项,它的值为0xFEb = *start++;item->type = (b >> 2) & 3;item->tag = (b >> 4) & 15;//如果为长项.它的Type和Tag在其后的⼆个字节中.item->data.longdata指向数据的起始位置if (item->tag == HID_ITEM_TAG_LONG) {item->format = HID_ITEM_FORMAT_LONG;if ((end - start) < 2)return NULL;item->size = *start++;item->tag = *start++;if ((end - start) < item->size)return NULL;item->data.longdata = start;start += item->size;return start;}//对于短项的情况.取得size值.并根据size值取得它的data域item->format = HID_ITEM_FORMAT_SHORT;item->size = b & 3;switch (item->size) {case0:return start;case1:if ((end - start) < 1)return NULL;item->data.u8 = *start++;return start;case2:if ((end - start) < 2)return NULL;item->data.u16 = le16_to_cpu(get_unaligned((__le16*)start));start = (__u8 *)((__le16 *)start + 1);return start;case3:item->size++;if ((end - start) < 4)return NULL;item->data.u32 = le32_to_cpu(get_unaligned((__le32*)start));start = (__u8 *)((__le32 *)start + 1);return start;}return NULL;}对照代码中的注释,应该很容易看懂这个函数,不再详细分析.返回到hid_parse_report()中,取得相应项之后,如果是长项,这⾥不会做处理.对于短项.为不同的type调⽤不同的解析函数.3.1.1.1:Global项解析Global的解析⼊⼝是hid_parser_global().代码如下:static int hid_parser_global(struct hid_parser *parser, struct hid_item *item){switch (item->tag) {//PUSH项case HID_GLOBAL_ITEM_TAG_PUSH:if (parser->global_stack_ptr == HID_GLOBAL_STACK_SIZE) {dbg_hid("global enviroment stack overflow\n");return -1;}memcpy(parser->global_stack + parser->global_stack_ptr++,&parser->global, sizeof(struct hid_global));return0;//POP项case HID_GLOBAL_ITEM_TAG_POP:if (!parser->global_stack_ptr) {dbg_hid("global enviroment stack underflow\n");return -1;}memcpy(&parser->global, parser->global_stack + --parser->global_stack_ptr,sizeof(struct hid_global));return0;case HID_GLOBAL_ITEM_TAG_USAGE_PAGE:parser->age_page = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM:parser->global.logical_minimum = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM:if (parser->global.logical_minimum < 0)parser->global.logical_maximum = item_sdata(item);elseparser->global.logical_maximum = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM:parser->global.physical_minimum = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM:if (parser->global.physical_minimum < 0)parser->global.physical_maximum = item_sdata(item);elseparser->global.physical_maximum = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:parser->global.unit_exponent = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_UNIT:parser->global.unit = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:if ((parser->global.report_size = item_udata(item)) > 32) {dbg_hid("invalid report_size %d\n", parser->global.report_size);return -1;}return0;case HID_GLOBAL_ITEM_TAG_REPORT_COUNT:if ((parser->global.report_count = item_udata(item)) > HID_MAX_USAGES) {dbg_hid("invalid report_count %d\n", parser->global.report_count);return -1;}return0;case HID_GLOBAL_ITEM_TAG_REPORT_ID:if ((parser->global.report_id = item_udata(item)) == 0) {dbg_hid("report_id 0 is invalid\n");return -1;}return0;default:dbg_hid("unknown global tag 0x%x\n", item->tag);return -1;}}这个函数虽然长,但是逻辑很简单,对于global信息,存放在hid_parse->global中.如果遇到了PUSH项,将当前的global项⼊栈,栈即为hid_parse-> global_stack[ ].当前的栈顶位置由hid_parse-> global_stack_ptr指定.如果遇到了POP项,就将栈中的global信息出栈.3.1.1.2:Local项解析Local项解析的相应接⼝为hid_parser_local().代码如下:static int hid_parser_local(struct hid_parser *parser, struct hid_item *item){__u32 data;unsigned n;if (item->size == 0) {dbg_hid("item data expected for local item\n");return -1;}data = item_udata(item);switch (item->tag) {//DELIMITER项,定义⼀个Local项的开始case HID_LOCAL_ITEM_TAG_DELIMITER://data>1:⼀个local项开始,0:⼀个local项结束//parse->local.delimiter_branch:表⽰local项计数.//进⼊⼀个local项时,local.delimiter_depth为1,退出⼀个local项时local.delimiter_depth为0// TODO: Local项不能嵌套if (data) {/** We treat items before the first delimiter* as global to all usage sets (branch 0).* In the moment we process only these global* items and the first delimiter set.*/if (parser->local.delimiter_depth != 0) {dbg_hid("nested delimiters\n");return -1;}parser->local.delimiter_depth++;parser->local.delimiter_branch++;} else {if (parser->local.delimiter_depth < 1) {dbg_hid("bogus close delimiter\n");return -1;}parser->local.delimiter_depth--;}return1;//以下各项不能出现在有DELIMITER标签的地⽅case HID_LOCAL_ITEM_TAG_USAGE:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}//local的usage项有扩展⽤法,它的⾼16可以定义usage_page.如果⾼16为空,它的//usage_page则定义在global中的usage_page if (item->size <= 2)data = (parser->age_page << 16) + data;//然后添加到parse->local的usage列表return hid_add_usage(parser, data);//对于有usage_min和usage_max的情况,将usage_min和usage_max之间的usage添加到//parse=>local的usage列表case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}if (item->size <= 2)data = (parser->age_page << 16) + data;parser->age_minimum = data;return0;case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}if (item->size <= 2)data = (parser->age_page << 16) + data;for (n = parser->age_minimum; n <= data; n++)if (hid_add_usage(parser, n)) {dbg_hid("hid_add_usage failed\n");return -1;}return0;default:dbg_hid("unknown local item tag 0x%x\n", item->tag);return0;}return0;}详细分析⼀下hid_add_usage().代码如下:static int hid_add_usage(struct hid_parser *parser, unsigned usage){if (parser->age_index >= HID_MAX_USAGES) {dbg_hid("usage index exceeded\n");return -1;}parser->age[parser->age_index] = usage;parser->local.collection_index[parser->age_index] =parser->collection_stack_ptr ?parser->collection_stack[parser->collection_stack_ptr - 1] : 0;parser->age_index++;return0;}如果usage项超过了HID_MAX_USAGES,为⾮法.最⼤为8192项.Parse->age_index表⽰local的项数,当然也表⽰了parse->age[ ]数组中的下⼀个可⽤项.parser->local.collection_index表⽰该usage所在的collection项序号.具体的collection信息存放在hid_deivce->collection[ ]中.关于collection我们在分析Main项解析的时候会详细分析.3.1.1.3:Main项解析Main项解析的⼊⼝为hid_parser_main().代码如下:static int hid_parser_main(struct hid_parser *parser, struct hid_item *item){__u32 data;int ret;//data域data = item_udata(item);switch (item->tag) {//Collectioncase HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:ret = open_collection(parser, data & 0xff);break;//End Collectioncase HID_MAIN_ITEM_TAG_END_COLLECTION:ret = close_collection(parser);break;//Inputcase HID_MAIN_ITEM_TAG_INPUT:ret = hid_add_field(parser, HID_INPUT_REPORT, data);break;//Outpputcase HID_MAIN_ITEM_TAG_OUTPUT:ret = hid_add_field(parser, HID_OUTPUT_REPORT, data);break;//Featurecase HID_MAIN_ITEM_TAG_FEATURE:ret = hid_add_field(parser, HID_FEATURE_REPORT, data);break;default:dbg_hid("unknown main item tag 0x%x\n", item->tag);ret = 0;}memset(&parser->local, 0, sizeof(parser->local)); /* Reset the local parser environment */return ret;}对Main项的解析要稍微复杂⼀点,Main项主要有两个部份,⼀个是Collection,⼀个是Input/Output/Feature项.先来看Collection项的解析.所有的collection信息都存放在hid_device->collection[ ]中.⽽Collection项⼜有嵌套的情况,每遇到⼀个Collection项就将collection的序号⼊栈,栈为parser_device->collection_stack[ ].栈顶指针为parser_device->collection_stack_ptr .遇到了⼀个end coll 熟悉这个⼤概的情况之后,就可以跟进open_collection()了.代码如下://所有的collection都存放在hid_dev->collection 中, ⽽hid_dev->maxcollection 表⽰collection[]中的下⼀个空闲位置//paser->collection_stack[ ]存放的是当前解析的collection在hid_dev->collection[ ]中的序号static int open_collection(struct hid_parser *parser, unsigned type){struct hid_collection *collection;unsigned usage;usage = parser->age[0];//colletcion嵌套过多if (parser->collection_stack_ptr == HID_COLLECTION_STACK_SIZE) {dbg_hid("collection stack overflow\n");return -1;}//device->maxcollection:存放的collection个数//device->collection[ ]太⼩,必须扩⼤存放空间if (parser->device->maxcollection == parser->device->collection_size) {collection = kmalloc(sizeof(struct hid_collection) *parser->device->collection_size * 2, GFP_KERNEL);if (collection == NULL) {dbg_hid("failed to reallocate collection array\n");return -1;}memcpy(collection, parser->device->collection,sizeof(struct hid_collection) *parser->device->collection_size);memset(collection + parser->device->collection_size, 0,sizeof(struct hid_collection) *parser->device->collection_size);kfree(parser->device->collection);parser->device->collection = collection;parser->device->collection_size *= 2;}//将collection序号⼊栈parser->collection_stack[parser->collection_stack_ptr++] =parser->device->maxcollection;//存⼊hid_device->collection[]collection = parser->device->collection +parser->device->maxcollection++;collection->type = type;collection->usage = usage;//collection的深度collection->level = parser->collection_stack_ptr - 1;if (type == HID_COLLECTION_APPLICATION)parser->device->maxapplication++;return0;}对照上⾯的分析和函数中的注释,理解这个函数应该很简单,不做详细分析.对于Input/Output/Feature项的解析:先来看⼀下hid_device结构的定义⽚段:struct hid_device{…………struct hid_report_enum report_enum[HID_REPORT_TYPES];……}对于INPUT/OUTPUT/FEATURE,每种类型都对应report_enum[ ]中的⼀项.Struct hid_report_enum定义如下:struct hid_report_enum {unsigned numbered;struct list_head report_list;struct hid_report *report_id_hash[256];};对于每⼀个report_id,对应report_id_hash[ ]中的⼀项,同时,将所对应的hid_report添加到report_list链表中.如果有多个report_id 的情况,numbered被赋为1.Struct hid_report定义如下:struct hid_report {struct list_head list;unsigned id; /* id of this report */unsigned type; /* report type */struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */unsigned maxfield; /* maximum valid field index */unsigned size; /* size of the report (bits) */struct hid_device *device; /* associated device */}List:⽤来形成链表Id:表⽰report_idType: INPUT/OUTPUT/FEATUREField[ ]:成员列表,对应⼀个report_id有多个INPUT(OUTPUT/FEATURE)项Maxfield: field[ ]中的有效项数Size: 该report的⼤⼩Device:所属的hid_device了解了这些之后,就可以来看⼀下代码了:如下:static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsigned flags){struct hid_report *report;struct hid_field *field;int usages;unsigned offset;int i;//找到类型和对应report_id所在的report.如果不存在,则新建之if (!(report = hid_register_report(parser->device, report_type, parser->global.report_id))) {dbg_hid("hid_register_report failed\n");return -1;}//对当前global数据的有效性判断。

linux 安装virtio驱动的方法

linux 安装virtio驱动的方法

linux 安装virtio驱动的方法一、什么是virtio驱动virtio驱动是一种用于虚拟化环境中的通用驱动程序,它提供了一种标准的接口,使得虚拟机可以与宿主机之间进行高效的数据传输和通信。

virtio驱动支持各种不同的设备类型,如网络适配器、磁盘控制器等,可以提高虚拟机的性能和稳定性。

二、安装virtio驱动的准备工作在安装virtio驱动之前,需要先准备以下工作:1. 确认虚拟化环境:确保已经在虚拟机中安装了KVM或者其他虚拟化软件,并且虚拟机已经开启了virtio设备的支持。

2. 下载virtio驱动:从官方网站或其他可信来源下载virtio驱动的安装包,一般以ISO镜像的形式提供。

三、安装virtio驱动安装virtio驱动的具体步骤如下:1. 将virtio驱动ISO镜像挂载到虚拟机中,可以通过以下命令挂载: ```# mount /dev/cdrom /mnt```其中,/dev/cdrom是ISO镜像所在的设备,/mnt是挂载点。

2. 进入virtio驱动的安装目录,一般是/mnt目录,可以通过以下命令进入:```# cd /mnt```3. 根据需要安装相应的virtio驱动,如网络适配器驱动、磁盘控制器驱动等。

执行以下命令安装网络适配器驱动:```# ./install.sh -n```执行以下命令安装磁盘控制器驱动:```# ./install.sh -d```其中,-n表示安装网络适配器驱动,-d表示安装磁盘控制器驱动。

根据实际需要选择对应的选项。

4. 完成安装后,重新启动虚拟机以使驱动生效。

四、验证virtio驱动是否安装成功安装完成后,可以通过以下方法验证virtio驱动是否成功安装:1. 检查设备:执行以下命令查看是否存在virtio设备:```# lspci | grep Virtio```如果输出中存在virtio设备,则说明驱动安装成功。

2. 检查驱动模块:执行以下命令查看是否加载了virtio驱动的模块: ```# lsmod | grep virtio```如果输出中存在virtio驱动的模块,则说明驱动加载成功。

Linux摄像头驱动学习之:(四)UVC-摄像头驱动框架分析

Linux摄像头驱动学习之:(四)UVC-摄像头驱动框架分析UVC: USB Video ClassUVC驱动:drivers\media\video\uvc\uvc_driver.c分析:1. usb_register(&uvc_driver.driver);2. uvc_probeuvc_register_videovdev = video_device_alloc();vdev->fops = &uvc_fops;video_register_device在下载 uvc specification,UVC 1.5 Class specification.pdf : 有详细描述USB_Video_Example 1.5.pdf : 有⽰例通过VideoControl Interface来控制,通过VideoStreaming Interface来读视频数据,VC⾥含有多个Unit/Terminal等功能模块,可以通过访问这些模块进⾏控制,⽐如调亮度分析UVC驱动调⽤过程:const struct v4l2_file_operations uvc_fops = {.owner = THIS_MODULE,.open = uvc_v4l2_open,.release = uvc_v4l2_release,.ioctl = uvc_v4l2_ioctl,.read = uvc_v4l2_read,.mmap = uvc_v4l2_mmap,.poll = uvc_v4l2_poll,};1. open:uvc_v4l2_open2. VIDIOC_QUERYCAP // video->streaming->type 应该是在设备被枚举时分析描述符时设置的if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)cap->capabilities = V4L2_CAP_VIDEO_CAPTURE| V4L2_CAP_STREAMING;elsecap->capabilities = V4L2_CAP_VIDEO_OUTPUT| V4L2_CAP_STREAMING;3. VIDIOC_ENUM_FMT // format数组应是在设备被枚举时设置的format = &video->streaming->format[fmt->index];4. VIDIOC_G_FMTuvc_v4l2_get_format // USB摄像头⽀持多种格式fromat, 每种格式下有多种frame(⽐如分辨率)struct uvc_format *format = video->streaming->cur_format;struct uvc_frame *frame = video->streaming->cur_frame;5. VIDIOC_TRY_FMTuvc_v4l2_try_format/* Check if the hardware supports the requested format. *//* Find the closest image size. The distance between image sizes is* the size in pixels of the non-overlapping regions between the* requested size and the frame-specified size.*/6. VIDIOC_S_FMT // 只是把参数保存起来,还没有发给USB摄像头uvc_v4l2_set_formatuvc_v4l2_try_formatvideo->streaming->cur_format = format;video->streaming->cur_frame = frame;7. VIDIOC_REQBUFSuvc_alloc_buffersfor (; nbuffers > 0; --nbuffers) {mem = vmalloc_32(nbuffers * bufsize);if (mem != NULL)break;}8. VIDIOC_QUERYBUFuvc_query_buffer__uvc_query_buffermemcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf); // 复制参数9. mmapuvc_v4l2_mmap10. VIDIOC_QBUFuvc_queue_bufferlist_add_tail(&buf->stream, &queue->mainqueue);list_add_tail(&buf->queue, &queue->irqqueue);11. VIDIOC_STREAMONuvc_video_enable(video, 1) // 把所设置的参数发给硬件,然后启动摄像头/* Commit the streaming parameters. */uvc_commit_videouvc_set_video_ctrl /* 设置格式fromat, frame */ret = __uvc_query_ctrl(video->dev /* 哪⼀个USB设备 */, SET_CUR, 0,video->streaming->intfnum /* 哪⼀个接⼝: VS */,probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,uvc_timeout_param);/* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */uvc_init_video(video, GFP_KERNEL);uvc_init_video_isoc / uvc_init_video_bulkurb->complete = uvc_video_complete; (收到数据后此函数被调⽤,它⼜调⽤video->decode(urb, video, buf); ==>uvc_video_decode_isoc/uvc_video_encode_bulk => uvc_queue_next_buffer => wake_up(&buf->wait);)usb_submit_urb12. polluvc_v4l2_polluvc_queue_pollpoll_wait(file, &buf->wait, wait); // 休眠等待有数据13. VIDIOC_DQBUFuvc_dequeue_bufferlist_del(&buf->stream);14. VIDIOC_STREAMOFFuvc_video_enable(video, 0);usb_kill_urb(urb);usb_free_urb(urb);分析设置亮度过程:ioctl: VIDIOC_S_CTRLuvc_ctrl_setuvc_ctrl_commit__uvc_ctrl_commit(video, 0);uvc_ctrl_commit_entity(video->dev, entity, rollback);ret = uvc_query_ctrl(dev /* 哪⼀个USB设备 */, SET_CUR, ctrl->entity->id /* 哪⼀个unit/terminal */,dev->intfnum /* 哪⼀个接⼝: VC interface */, ctrl->info->selector,uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),ctrl->info->size);总结:1. UVC设备有2个interface: VideoControl Interface, VideoStreaming Interface2. VideoControl Interface⽤于控制,⽐如设置亮度。

驱动面试题目(3篇)

第1篇一、基础知识1. 请简述Linux内核的基本组成部分及其作用。

2. 解释Linux内核中的进程管理、内存管理、文件系统等核心概念。

3. 描述Linux内核中的中断处理机制,包括中断请求、中断处理和中断响应。

4. 解释Linux内核中的虚拟内存管理原理,包括页表、分页和交换等技术。

5. 简述Linux内核中的同步机制,如信号量、互斥锁、自旋锁等。

6. 请简述Linux内核中的I/O子系统,包括字符设备、块设备、网络设备等。

7. 解释Linux内核中的设备驱动程序的作用,以及它们与内核之间的关系。

8. 描述Linux内核中的文件系统结构,包括超级块、inode、dentry等概念。

9. 请简述Linux内核中的网络协议栈,包括TCP/IP、UDP、ICMP等协议。

10. 解释Linux内核中的模块加载和卸载机制,以及模块之间的依赖关系。

二、设备驱动程序开发1. 请简述字符设备驱动程序的开发流程,包括设备注册、设备初始化、设备打开、设备读写等。

2. 解释块设备驱动程序的开发流程,包括设备注册、设备初始化、设备请求处理等。

3. 请简述网络设备驱动程序的开发流程,包括设备注册、设备初始化、数据包处理等。

4. 解释USB设备驱动程序的开发流程,包括设备注册、设备初始化、USB传输处理等。

5. 请简述I2C设备驱动程序的开发流程,包括设备注册、设备初始化、I2C传输处理等。

6. 解释SPI设备驱动程序的开发流程,包括设备注册、设备初始化、SPI传输处理等。

7. 请简述PCI设备驱动程序的开发流程,包括设备注册、设备初始化、PCI配置空间处理等。

8. 解释ACPI设备驱动程序的开发流程,包括设备注册、设备初始化、ACPI事件处理等。

9. 请简述设备驱动程序中的中断处理,包括中断请求、中断处理和中断响应。

10. 解释设备驱动程序中的同步机制,如信号量、互斥锁、自旋锁等。

三、调试与优化1. 请简述Linux内核中常见的调试工具,如kgdb、gdb、strace等。

linux内核驱动中_IO, _IOR, _IOW, _IOWR 宏的用法与解析

在驱动程序里,ioctl() 函数上传送的变量cmd 是应用程序用于区别设备驱动程序请求处理内容的值。

cmd除了可区别数字外,还包含有助于处理的几种相应信息。

cmd的大小为32位,共分 4 个域:bit31~bit30 2位为“区别读写” 区,作用是区分是读取命令还是写入命令。

bit29~bit15 14位为"数据大小" 区,表示ioctl() 中的arg 变量传送的内存大小。

bit20~bit08 8位为“魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的ioctl 命令进行区别。

bit07~bit00 8位为"区别序号" 区,是区分命令的命令顺序序号。

像命令码中的“区分读写区” 里的值可能是_IOC_NONE (0值)表示无数据传输,_IOC_READ (读),_IOC_WRITE (写) ,_IOC_READ|_IOC_WRITE (双向)。

内核定义了_IO() , _IOR() , IOW() 和_IOWR() 这4 个宏来辅助生成上面的cmd 。

下面分析_IO() 的实现,其它的类似:在asm-generic/ioctl.h 里可以看到_IO() 的定义:#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)再看_IOC() 的定义:#define _IOC(dir,type,nr,size) \(((dir) << _IOC_DIRSHIFT) | \((type) << _IOC_TYPESHIFT) | \((nr) << _IOC_NRSHIFT) | \((size) << _IOC_SIZESHIFT))可见,_IO() 的最后结果由_IOC() 中的4 个参数移位组合而成。

再看_IOC_DIRSHIT 的定义:#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) _IOC_SIZESHIFT 的定义:#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) _IOC_TYPESHIF 的定义:#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) _IOC_NRSHIFT 的定义:#define _IOC_NRSHIFT 0_IOC_NRBITS 的定义:#define _IOC_NRBITS 8_IOC_TYPEBITS 的定义:#define _IOC_TYPEBITS 8由上面的定义,往上推得到:引用_IOC_TYPESHIFT = 8_IOC_SIZESHIFT = 16_IOC_DIRSHIFT = 30所以,(dir) << _IOC_DIRSHIFT) 表是dir 往左移30 位,即移到bit31~bit30 两位上,得到方向(读写)的属性;(size) << _IOC_SIZESHIFT) 位左移16 位得到“数据大小”区;(type) << _IOC_TYPESHIFT) 左移8位得到"魔数区" ;(nr) << _IOC_NRSHIFT) 左移0 位( bit7~bit0) 。

Linux设备驱动之pci设备的枚举

一:前言Pci,是Peripheral Component Interconnect的缩写,翻译成中文即为外部设备互联.与传统的总线相比.它的传输速率较高.能为用户提供动态查询pci deivce.和局部总线信息的方法,此外,它还能自动为总线提供仲裁.在近几年的发展过程中,被广泛应用于多种平台.pci协议比较复杂,关于它的详细说明,请查阅有关pci规范的资料,本文不会重复这些部份.对于驱动工程师来说,Pci设备的枚举是pci设备驱动编写最复杂的操作。

分析和理解这部份,是进行深入分析pci设备驱动架构的基础。

我们也顺便来研究一下,linux是怎么对这个庞然大物进行封装的。

二:pci架构概貌上图展现了pci驱动架构中,pci_bus、pci_dev之间的关系。

如上图所示,所有的根总线都链接在pci_root_buses 链表中。

Pci_bus ->device链表链接着该总线下的所有设备。

而pci_bus->children链表链接着它的下层总线。

对于pci_dev来说。

pci_dev->bus指向它所属的pci_bus。

Pci_dev->bus_list链接在它所属bus的device链表上。

此外,所有pci设备都链接在pci_device链表中。

三:pci设备的配置空间每个pci设备都有最多256个连续的配置空间。

配置空间中包含了设备的厂商ID,设备ID,I RQ,设备存储区信息等.摘下LDD3中的一副说明图,如下:要注意了,上图是以字节为单位的,而不是以位为单位.那怎么去读取每个设备的配置空间呢?我们在开篇的时候提到过,pci总线为用户提供了动态查询pci设备信息的方法。

在x86上,保留了0xCF8~0xCFF的8个寄存器.实际上就是对应地址为0xCF8的32位寄存器和地址为0xCFC的32位寄存器。

在0xCF8寄存中写入要访问设备对应的总线号, 设备号、功能号和寄存器号组成的一个32位数写入0xCF8.然后从0xCFC上就可以取出对应pci设备的信息.写入到0xCF8寄存器的格式如下:低八位(0~7): (寄存器地址)&0xFC.低二位为零8~10:功能位. 有时候,一个pci设备对应多个功能.将每个功能单元分离出来,对应一个独立的pci device 11~15位:设备号对应该pci总线上的设备序号16~23位:总线号根总线的总线号为0.每遍历到下层总线,总线号+131:有效位如果该位为1.则说明写入的数据有效,否则无效例如:要读取n总线号m设备号f功能号对应设备的vendor id和Device id.过程如下:要写入到0xCF8中的数为:l = 0x80<<23 | n<<16 | m<<11 | f<<8 | 0x00 即:outl(l,0xCF8)从0xCFC中读相关信息:L = Inw(0xCFC) (从上图中看到,vendor id和device id总共占四个字节.因此用in w)所以device id = L&0xFF,V endor id = (L>>8)&0xFF。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Linux驱动相关的知识点包括:
驱动简介:Linux的驱动本质上就是一种软件程序,上层软件可以在不了解硬件特性的情况下,通过驱动提供的接口和计算机以及外设进行通信。

驱动程序将内核与硬件之间串联起来,为应用开发屏蔽了硬件的细节,使应用能够直接操作硬件。

对于Linux来说,硬件设备就是一个设备文件,应用程序可以像操作普通文件那样对硬件设备进行操作。

驱动分类:Linux将外设和存储器分为三个基础大类,分别是块设备驱动、字符设备驱动、网络设备驱动。

字符设备驱动是较为简单和初学者首先进行学习的驱动。

设备文件:Linux系统将所有的设备都当做文件来处理,每个设备都有一个对应的设备文件,应用程序通过访问这些设备文件来访问硬件。

主设备号和次设备号:主设备号用来表明设备的类型,与一个确定的驱动程序对应;次设备号则是用于标明不同的属性,例如不同的使用方法,不同的位置,不同的操作等,它标志着某个具体的物理设备。

加载和卸载驱动:在Linux中,可以使用insmod命令加载驱动模块,使用rmmod命令卸载驱动模块。

设备操作:Linux驱动中的设备操作包括对设备的初始化和释放、数据的传送、设备错误的检测和处理等。

读写设备:读写设备的主要任务就是把内核空间的数据复制到用户空间,或者从用户空间复制到内核空间。

以上是关于Linux驱动的一些主要知识点,但请注意,这些只是基础知识,深入学习还需要参考更详细的资料和实践经验。

相关文档
最新文档