4.14怎么写驱动 linux
2018-08-25 20:16:54 cui841923894 阅读数 377

源于https://kernelnewbies.org/Linux_4.14

1.支持更大的内存
原始x86-64平台受限于4级分页的限制,最大支持256TiB的虚拟地址空间和64TiB的物理地址空间。现在我们已经碰到了这个限制:一些供应商现在开始提供64TiB内存的服务器。因此内核x86平台支持5级分页,突破了128PiB虚拟地址空间和4PiB物理地址空间的限制,This “ought to be enough for anybody”。
详情:https://lwn.net/Articles/717293/

2.添加AMD安全内存加密功能
安全内存加密技术可以通过页表将内存页加密。标记为加密的内存页面在从DRAM读取时会自动解密,并在写入DRAM时自动加密。因此,安全内存加密技术可用于保护DRAM的物理内容免受来自系统的攻击。
详情:https://lwn.net/Articles/686808/#sme

3.ORC unwinder更好地内核跟踪器
“unwinder“,指打印已经执行的函数列表(栈信息,调用图,调用栈)。内核虽然有一个unwinder并且运行良好,但是它一般都不可靠,会导致功能问题。同时它还需要”frame pointers“(CONFIG_FRAME_POINTERS)来打印完整的调用栈,这使得GCC向内核每个函数添加检测代码,内核可执行代码大小增加约3.2%。并且在工作负载比较大的情况下会降低内核的性能。
相比之下,ORC unwinder不需要任何地方插入代码,因此不会影响内核运行性能。
详情:
https://lwn.net/Articles/728339/
http://www.codeblueprint.co.uk/2017/07/31/the-orc-unwinder.html

4.Btrfs和Squashfs支持zstd压缩方式
zstd压缩方式在压缩速度和质量之前有更好的表现,它接近lz4的压缩速度,并且质量接近lzma。zstd解压缩的速度是zlib的两倍以上。
详情:https://github.com/facebook/zstd

5.未来GPU的异构内存管理 (https://lwn.net/Articles/684916/

6.支持异步缓冲I/O
此版本中preadv2(2)加入RWF_NONBLOCK标志,允许用户空间应用程序绕过线程池的队列操作,从而缓解缓冲I/O的阻塞。

7.增强SMP和cpufreq之间协调
在Linux中,任务调度事件通知到cpufreq子系统,cpufreq可以在任务需要时增加频率,并实现良好的交互性。但是,当调度发生在不同的CPU时,不会调用到cpufreq,例如在另一个CPU中创建新进程时。此版本使任务调度可以通知到远程CPU的cpufreq子系统。
详情:https://lwn.net/Articles/732740/

8.CGroup支持线程模式
此版本中,cgroup v2支持线程模式,以支持某些进程的线程需要跨组分发资源。默认情况下,进程的所有线程都属于同一个cgroup,该cgroup看做管理资源的资源域。但线程模式允许线程分布在子树的同时仍然共享资源。
详情:https://lwn.net/Articles/729215/
这里写图片描述

2017-11-15 22:21:33 lovelycheng 阅读数 1556
2018-08-14 10:43:47 tmp_2011 阅读数 430

1、编译uboot,可以参考http://www.wiki.xilinx.com/Build%20U-Boot

2、编译linux内核
首先获取linux内核源码:git clone https://github.com/Xilinx/linux-xlnx.git
然后进到下载目录下的linux-xlnx,设置交叉编译环境变量,该变量可以设置在Makefile文件里面:
例如:CROSS_COMPILE=CROSS_COMPILE=/home/rl/Codesourcery/bin/arm-xilinx-linux-gnueabi-
接下来开始编译内核
1、make ARCH=arm xilinx_zynq_defconfig

2、make ARCH=arm menuconfig
需要设置usb gadget support如图所示
这里写图片描述
3、编译内核 sudo make ARCH=arm uImage modules UIMAGE_LOADADDR=0x00008000
这里写图片描述
如图所示:arch/arm/boot/uImage是生成的内核文件,需要copy到SD卡上。
以下驱动也需要copy到sd卡上
drivers/usb/gadget/function/u_ether.ko
drivers/usb/gadget/function/usb_f_rndis.ko
drivers/usb/gadget/libcomposite.ko
4、编译dtb文件
首先需要修改devicetree,在arch/arm/boot/dts文件下,本文采用了zc702平台
修改arch/arm/boot/dts/zynq-zc702.dts
这里写图片描述
dr_mode由host改为peripheral
然后编译dtb文件,回到linux-xlnx文件,sudo make ARCH=arm dtbs
然后在arch/arm/boot/dts文件下生成zynq-zc702.dtb,copyzynq-zc702.dtb到sd卡,命名为devicetree.dtb

5、uramdisk.image.gz用的从xilinx wiki上下载的,若要编译,参考xilinx wiki
sd卡上最后有如下文件
这里写图片描述

6、将sd卡从插入zc702卡槽,linux就起来了。
mount /dev/mmcblk0p1 /mnt
执行如下脚本
这里写图片描述
本文整理成usb_ehter_boot.sh,
#!/bin/sh
insmod /mnt/libcomposite.ko
insmod /mnt/u_ether.ko
insmod /mnt/usb_f_rndis.ko
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
mkdir g1
cd g1
echo "64" > bMaxPacketSize0
echo "0x200" > bcdUSB
echo "0x100" > bcdDevice
echo "0x03FD" > idVendor
echo "0x0500" > idProduct
mkdir functions/rndis.rn0
mkdir configs/c1.1
ln -s functions/rndis.rn0 configs/c1.1/
echo "ci_hdrc.0" > UDC
ifconfig usb0 192.168.1.200
ifconfig usb0 up
~

本例将zc702 ip设置为192.168.1.200,在host pc上 设置主机ip为192.168.1.100如图所示
这里写图片描述
接下来可以互ping
这里写图片描述
zc702和host pc间usb 网络就通了。
主机可以通ftp访问zc702.如ftp 192.168.1.200
这里写图片描述

接下来开发其他应用。

2012-10-24 11:35:19 houyizi337825770 阅读数 1259

原文链接:http://www.redflag-linux.com/product/emb/resource/1000000825.html

引言
随着人们生活水平的提高,我们用到的USB设备也越来越多,但是Linux在硬件配置上仍然没有做到完全即插即用,对于Linux怎样配置和使用他们,也越来越成为困扰我们的一大问题;本文的目地是使大家了解怎样编制USB设备驱动,为更好地配置和使用USB设备提供方便;对于希望开发Linux系统下USB设备驱动的人员,也可作为进一步学习USB驱动的大体架构进而编写出特殊USB设备的驱动程序。


USB基础知识
USB是英文Universal Serial Bus的缩写,意为通用串行总线。USB最初是为了替代许多不同的低速总线(包括并行、串行和键盘连接)而设计的,它以单一类型的总线连接各种不同的类型的设备。USB的发展已经超越了这些低速的连接方式,它现在可以支持几乎所有可以连接到PC上的设备。最新的USB规范修订了理论上高达480Mbps的高速连接。Linux内核支持两种主要类型的USB驱动程序:宿主系统上的驱动程序和设备上的驱动程序,从宿主的观点来看(一个普通的宿主也就是一个PC机),宿主系统的USB设备驱动程序控制插入其中的USB设备,而USB设备的驱动程序控制该设备如何作为一个USB设备和主机通信。本文将详细介绍运行于PC机上的USB系统是如何运作的。并同时用USB驱动程序的框架程序当例子作详细的说明,我们在此文中不讨论USB器件的驱动程序。

 

USB驱动程序基础
在动手写USB驱动程序这前,让我们先看看写的USB驱动程序在内核中的结构,如下图:


 
USB驱动程序存在于不同的内核子系统和USB硬件控制器之间,USB核心为USB驱动程序提供了一个用于访问和控制USB硬件的接口,而不必考虑系统当前存在的各种不同类型的USB硬件控制器。USB是一个非常复杂的设备,linux内核为我们提供了一个称为USB的核心的子系统来处理大部分的复杂性,USB设备包括配置(configuration)、接口(interface)和端点(endpoint),USB设备绑定到接口上,而不是整个USB设备。如下图所示:


 
USB通信最基本的形式是通过端点(USB端点分中断、批量、等时、控制四种,每种用途不同),USB端点只能往一个方向传送数据,从主机到设备或者从设备到主机,端点可以看作是单向的管道(pipe)。所以我们可以这样认为:设备通常具有一个或者更多的配置,配置经常具有一个或者更多的接口,接口通常具有一个或者更多的设置,接口没有或具有一个以上的端点。驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否已经安装了硬件。USB核心使用一个列表(是一个包含制造商ID和设备号ID的一个结构体)来判断对于一个设备该使用哪一个驱动程序,热插拨脚本使用它来确定当一个特定的设备插入到系统时该自动装载哪一个驱动程序。
上面我们简要说明了驱动程序的基本理论,在写一个设备驱动程序之前,我们还要了解以下两个概念:模块和设备文件。
模块:是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充内核的功能。模块最主要的用处就是用来实现设备驱动程序。Linux下对于一个硬件的驱动,可以有两种方式:直接加载到内核代码中,启动内核时就会驱动此硬件设备。另一种就是以模块方式,编译生成一个.ko文件(在2.4以下内核中是用.o作模块文件,我们以2.6的内核为准,以下同)。当应用程序需要时再加载到内核空间运行。所以我们所说的一个硬件的驱动程序,通常指的就是一个驱动模块。
设备文件:对于一个设备,它可以在/dev下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在,但它不是普通意义上的文件,它是设备文件,更确切的说,它是设备节点。这个节点是通过mknod命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设备,一般对应着确定的驱动程序;次设备号一般是区分不同属性,例如不同的使用方法,不同的位置,不同的操作。这个设备号是从/proc/devices文件中获得的,所以一般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对应的,当你打开一个设备文件时,操作系统就已经知道这个设备所对应的驱动程序。对于一个硬件,Linux是这样来进行驱动的:首先,我们必须提供一个.ko的驱动模块文件。我们要使用这个驱动程序,首先要加载它,我们可以用insmod xxx.ko,这样驱动就会根据自己的类型(字符设备类型或块设备类型,例如鼠标就是字符设备而硬盘就是块设备)向系统注册,注册成功系统会反馈一个主设备号,这个主设备号就是系统对它的唯一标识。驱动就是根据此主设备号来创建一个一般放置在/dev目录下的设备文件。在我们要访问此硬件时,就可以对设备文件通过open、read、write、close等命令进行。而驱动就会接收到相应的read、write操作而根据自己的模块中的相应函数进行操作了。

 

USB驱动程序实践
了解了上述理论后,我们就可以动手写驱动程序,如果你基本功好,而且写过linux下的硬件驱动,USB的硬件驱动和pci_driver很类似,那么写USB的驱动就比较简单了,如果你只是大体了解了linux的硬件驱动,那也不要紧,因为在linux的内核源码中有一个框架程序可以拿来借用一下,这个框架程序在/usr/src/~(你的内核版本,以下同)/drivers/usb下,文件名为usb-skeleton.c。写一个USB的驱动程序最基本的要做四件事:驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urb(USB请求块)(当然也可以不用urb来传输数据,下文我们会说到)。
驱动程序支持的设备:有一个结构体struct usb_device_id,这个结构体提供了一列不同类型的该驱动程序支持的USB设备,对于一个只控制一个特定的USB设备的驱动程序来说,struct usb_device_id表被定义为:
/* 驱动程序支持的设备列表 */
static struct usb_device_id skel_table [] = {
    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    { }                    /* 终止入口 */
};
MODULE_DEVICE_TABLE (usb, skel_table);
对于PC驱动程序,MODULE_DEVICE_TABLE是必需的,而且usb必需为该宏的第一个值,而USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID就是这个特殊设备的制造商和产品的ID了,我们在程序中把定义的值改为我们这款USB的,如:
/* 定义制造商和产品的ID号 */
#define USB_SKEL_VENDOR_ID    0x1234
#define USB_SKEL_PRODUCT_ID    0x2345
这两个值可以通过命令lsusb,当然你得先把USB设备先插到主机上了。或者查看厂商的USB设备的手册也能得到,在我机器上运行lsusb是这样的结果:
Bus 004 Device 001: ID 0000:0000 
Bus 003 Device 002: ID 1234:2345  Abc  Corp.
Bus 002 Device 001: ID 0000:0000 
Bus 001 Device 001: ID 0000:0000
得到这两个值后把它定义到程序里就可以了。
注册USB驱动程序:所有的USB驱动程序都必须创建的结构体是struct usb_driver。这个结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述USB驱动程序。创建一个有效的struct usb_driver结构体,只须要初始化五个字段就可以了,在框架程序中是这样的:
static struct usb_driver skel_driver = {
    .owner =    THIS_MODULE,
    .name =        "skeleton",
    .probe =    skel_probe,
    .disconnect =    skel_disconnect,
    .id_table =    skel_table,
};
struct module *owner :指向该驱动程序的模块所有者的指针。USB核心使用它来正确地对该USB驱动程序进行引用计数,使它不会在不合适的时刻被卸载掉,这个变量应该被设置为THIS_MODULE宏。
const char *name:指向驱动程序名字的指针,在内核的所有USB驱动程序中它必须是唯一的,通常被设置为和驱动程序模块名相同的名字。
int (*probe) (struct usb_interface *intf,const struct usb_device_id *id):这个是指向USB驱动程序中的探测函数的指针。当USB核心认为它有一个接口(usb_interface)可以由该驱动程序处理时,这个函数被调用。
void (disconnect)(struct usb_interface *intf):指向USB驱动程序中的断开函数的指针,当一个USB接口(usb_interface)被从系统中移除或者驱动程序正在从USB核心中卸载时,USB核心将调用这个函数。
const struct usb_device_id *id_table:指向ID设备表的指针,这个表包含了一列该驱动程序可以支持的USB设备,如果没有设置这个变量,USB驱动程序中的探测回调函数就不会被调用。
在这个结构体中还有其它的几个回调函数不是很常用,这里就不一一说明了。以struct usb_driver 指针为参数的usb_register_driver函数调用把struct usb_driver注册到USB核心。一般是在USB驱动程序的模块初始化代码中完成这个工作的:
static int __init usb_skel_init(void)
{
    int result;

    /* 驱动程序注册到USB子系统中*/
    result = usb_register(&skel_driver);
    if (result)
        err("usb_register failed. Error number %d", result);

    return result;
}
当USB驱动程序将要被卸开时,需要把struct usb_driver从内核中注销。通过调用usb_deregister_driver来完成这个工作,当调用发生时,当前绑定到该驱动程序上的任何USB接口都被断开,断开函数将被调用:
static void __exit usb_skel_exit(void)
{
    /* 从子系统注销驱动程序 */
    usb_deregister(&skel_driver);
}
探测和断开:当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用,探测函数检查传递给它的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因为某种原因不应该控制设备时,断开函数被调用,它可以做一些清理工作(个人理解:这两个函数的自动调用是有usbcore自动实现的drivers/usb/core文件下面的源码,不用usb驱动开发者实现)。探测回调函数中,USB驱动程序初始化任何可能用于控制USB设备的局部结构体,它还把所需的任何设备相关信息保存到一个局部结构体中,下面是探测函数的部分源码,我们加以分析。
    /* 设置端点信息 */
    /* 只使用第一个批量IN和批量OUT端点 */
    iface_desc = interface->cur_altsetting;
    for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
        endpoint = &iface_desc->endpoint[i].desc;

        if (!dev->bulk_in_endpointAddr &&
            (endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                    == USB_ENDPOINT_XFER_BULK)) {
            /* 找到一个批量IN端点 */
            buffer_size = endpoint->wMaxPacketSize;
            dev->bulk_in_size = buffer_size;
            dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
            dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
            if (!dev->bulk_in_buffer) {
                err("Could not allocate bulk_in_buffer");
                goto error;
            }
        }

        if (!dev->bulk_out_endpointAddr &&
            !(endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                    == USB_ENDPOINT_XFER_BULK)) {
            /* 找到一个批量OUT端点 */
            dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }
    }
    if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
        err("Could not find both bulk-in and bulk-out endpoints");
        goto error;
    }
在探测函数里,这个循环首先访问该接口中存在的每一个端点,给该端点一个局部指针以便以后访问:
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
        endpoint = &iface_desc->endpoint[i].desc;
在一轮探测过后,我们就有了一个端点,在还没有发现批量IN类型的端点时,探测该端点方向是否为IN,这可以通过检查USB_DIR_IN是否包含在bEndpointAddress端点变量有确定,如果是的话,我们在探测该端点类型是否为批量,先用USB_ENDPOINT_XFERTYPE_MASK位掩码来取bmAttributes变量的值,然后探测它是否和USB_ENDPOINT_XFER_BULK值匹配:
        if (!dev->bulk_out_endpointAddr &&
            !(endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                    == USB_ENDPOINT_XFER_BULK))
如果所有这些探测都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点的相关信息保存到一个局部结构体中以便稍后用它来和端点进行通信:
            /* 找到一个批量IN类型的端点 */
            buffer_size = endpoint->wMaxPacketSize;
            dev->bulk_in_size = buffer_size;
            dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
            dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
            if (!dev->bulk_in_buffer) {
                err("Could not allocate bulk_in_buffer");
                goto error;
            }
因为USB驱动程序要在设备的生命周期的稍后时间获取和接口相关联的局部数据结构体,所以调用了usb_set_intfdata函数,把它保存到struct usb_interface结构体中以便后面的访问
    /* 把数据指针保存到这个接口设备中 */
    usb_set_intfdata(interface, dev);
我们以后调用usb_set_intfdata函数来获取数据。当这一切都完成后,USB驱动程序必须在探测函数中调用usb_register_dev函数来把该设备注册到USB核心里:
    /* 注册设备到USB核心 */
    retval = usb_register_dev(interface, &skel_class);
    if (retval) {
        /* 有些情况下是不允许注册驱动程序的 */
        err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }
当一个USB设备被断开时,和该设备相关联的所有资源都应该被尽可能的清理掉,在此时,如果已在在探测函数中调用了注册函数来为该USB设备分配了一个次设备号话,必须调用usb_deregister_dev函数来把次设备号交还给USB核心。在断开函数中,从接口获取之前调用usb_set_intfdata设置的任何数据也是很重要的。然后设置struct usb_interface结构体中的数据指针为NULL,以防任何不适当的对该数据的错误访问。
在探测函数中会对每一个接口进行一次探测,所以我们在写USB驱动程序的时候,只要做好第一个端点,其它的端点就会自动完成探测。在探测函数中我们要注意的是在内核中用结构体struct usb_host_endpoint来描述USB端点,这个结构体在另一个名为struct usb_endpoint_descriptor的结构体中包含了真正的端点信息,struct usb_endpoint_descriptor结构体包含了所有的USB特定的数据,该结构体中我们要关心的几个字段是:
bEndpointAddress:这个是特定的USB地址,可以结合USB_DIR_IN和USB_DIR_OUT来使用,以确定该端点的数据是传向设备还是主机。
bmAttributes:这个是端点的类型,这个值可以结合位掩码USB_ENDPOINT_XFERTYPE_MASK来使用,以确定此端点的类型是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)、USB_ENDPOINT_XFER_INT的哪一种。
wMaxPacketSize:这个是端点一次可以处理的最大字节数,驱动程序可以发送数量大于此值的数据到端点,在实际传输中,数据量如果大于此值会被分割。
bInterval:这个值只有在端点类型是中断类型时才起作用,它是端点中断请求的间隔时间,以毫秒为单位。
提交和控制urb:当驱动程序有数据要发送到USB设备时(大多数情况是在驱动程序的写函数中),要分配一个urb来把数据传输给设备:
    /* 创建一个urb,并且给它分配一个缓存*/
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!urb) {
        retval = -ENOMEM;
        goto error;
    }
当urb被成功分配后,还要创建一个DMA缓冲区来以高效的方式发送数据到设备,传递给驱动程序的数据要复制到这块缓冲中去:
    buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
    if (!buf) {
        retval = -ENOMEM;
        goto error;
    }

    if (copy_from_user(buf, user_buffer, count)) {
        retval = -EFAULT;
        goto error;
    }
当数据从用户空间正确复制到局部缓冲区后,urb必须在可以被提交给USB核心之前被正确初始化:
    /* 初始化urb */
    usb_fill_bulk_urb(urb, dev->udev,
              usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
              buf, count, skel_write_bulk_callback, dev);
    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
然后urb就可以被提交给USB核心以传输到设备了:
    /* 把数据从批量OUT端口发出 */
    retval = usb_submit_urb(urb, GFP_KERNEL);
    if (retval) {
        err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
        goto error;
    }
当urb被成功传输到USB设备之后,urb回调函数将被USB核心调用,在我们的例子中,我们初始化urb,使它指向skel_write_bulk_callback函数,以下就是该函数:
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
    struct usb_skel *dev;

    dev = (struct usb_skel *)urb->context;

    if (urb->status &&
        !(urb->status == -ENOENT ||
          urb->status == -ECONNRESET ||
          urb->status == -ESHUTDOWN)) {
        dbg("%s - nonzero write bulk status received: %d",
            __FUNCTION__, urb->status);
    }

    /* 释放已分配的缓冲区 */
    usb_buffer_free(urb->dev, urb->transfer_buffer_length,
            urb->transfer_buffer, urb->transfer_dma);
}
有时候USB驱动程序只是要发送或者接收一些简单的数据,驱动程序也可以不用urb来进行数据的传输,这是里涉及到两个简单的接口函数:usb_bulk_msg和usb_control_msg ,在这个USB框架程序里读操作就是这样的一个应用:
/* 进行阻塞的批量读以从设备获取数据 */
    retval = usb_bulk_msg(dev->udev,
                  usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                  dev->bulk_in_buffer,
                  min(dev->bulk_in_size, count),
                  &count, HZ*10);

    /*如果读成功,复制到用户空间 */
    if (!retval) {
        if (copy_to_user(buffer, dev->bulk_in_buffer, count))
            retval = -EFAULT;
        else
            retval = count;
    }
usb_bulk_msg接口函数的定义如下:
int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,
void *data,int len,int *actual_length,int timeout);
其参数为:
struct usb_device *usb_dev:指向批量消息所发送的目标USB设备指针。
unsigned int pipe:批量消息所发送目标USB设备的特定端点,此值是调用usb_sndbulkpipe或者usb_rcvbulkpipe来创建的。
void *data:如果是一个OUT端点,它是指向即将发送到设备的数据的指针。如果是IN端点,它是指向从设备读取的数据应该存放的位置的指针。
int len:data参数所指缓冲区的大小。
int *actual_length:指向保存实际传输字节数的位置的指针,至于是传输到设备还是从设备接收取决于端点的方向。
int timeout:以Jiffies为单位的等待的超时时间,如果该值为0,该函数一直等待消息的结束。
如果该接口函数调用成功,返回值为0,否则返回一个负的错误值。
usb_control_msg接口函数定义如下:
int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8    request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)
除了允许驱动程序发送和接收USB控制消息之外,usb_control_msg函数的运作和usb_bulk_msg函数类似,其参数和usb_bulk_msg的参数有几个重要区别:
struct usb_device *dev:指向控制消息所发送的目标USB设备的指针。
unsigned int pipe:控制消息所发送的目标USB设备的特定端点,该值是调用usb_sndctrlpipe或usb_rcvctrlpipe来创建的。
__u8 request:控制消息的USB请求值。
__u8 requesttype:控制消息的USB请求类型值。
__u16 value:控制消息的USB消息值。
__u16 index:控制消息的USB消息索引值。
void *data:如果是一个OUT端点,它是指身即将发送到设备的数据的指针。如果是一个IN端点,它是指向从设备读取的数据应该存放的位置的指针。
__u16 size:data参数所指缓冲区的大小。
int timeout:以Jiffies为单位的应该等待的超时时间,如果为0,该函数将一直等待消息结束。
如果该接口函数调用成功,返回传输到设备或者从设备读取的字节数;如果不成功它返回一个负的错误值。
这两个接口函数都不能在一个中断上下文中或者持有自旋锁的情况下调用,同样,该函数也不能被任何其它函数取消,使用时要谨慎。
我们要给未知的USB设备写驱动程序,只需要把这个框架程序稍做修改就可以用了,前面我们已经说过要修改制造商和产品的ID号,把0xfff0这两个值改为未知USB的ID号。
 #define USB_SKEL_VENDOR_ID      0xfff0
     #define USB_SKEL_PRODUCT_ID     0xfff0
还有就是在探测函数中把需要探测的接口端点类型写好,在这个框架程序中只探测了批量(USB_ENDPOINT_XFER_BULK)IN和OUT端点,可以在此处使用掩码(USB_ENDPOINT_XFERTYPE_MASK)让其探测其它的端点类型,驱动程序会对USB设备的每一个接口进行一次探测,当探测成功后,驱动程序就被绑定到这个接口上。再有就是urb的初始化问题,如果你只写简单的USB驱动,这块不用多加考虑,框架程序里的东西已经够用了,这里我们简单介绍三个初始化urb的辅助函数:
usb_fill_int_urb :它的函数原型是这样的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);
这个函数用来正确的初始化即将被发送到USB设备的中断端点的urb。
usb_fill_bulk_urb :它的函数原型是这样的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)
这个函数是用来正确的初始化批量urb端点的。
usb_fill_control_urb :它的函数原型是这样的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);
这个函数是用来正确初始化控制urb端点的。
还有一个初始化等时urb的,它现在还没有初始化函数,所以它们在被提交到USB核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的/usr/src/~/drivers/usb/media下的konicawc.c文件。

 

驱动模块的编译、配置和使用
现在我们的驱动程序已经大体写好了,然后在linux下把它编译成模块就可以把驱动模块插入到内核中运行了,编译的Makefile文件可以这样来写:
ifneq ($(KERNELRELEASE),)
    obj-m := xxx.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
    rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*
其中xxx是源文件的文件名,在linux下直接执行make就可以生成驱动模块(xxx.ko)了。生成驱动模块后使用insmod xxx.ko就可以插入到内核中运行了,用lsmod可以看到你插入到内核中的模块,也可以从系统中用命令rmmod xxx把模块卸载掉;如果把编译出来的驱动模块拷贝到/lib/modules/~/kernel/drivers/usb/下,然后depmod一下,那么你在插入USB设备的时候,系统就会自动为你加载驱动模块的;当然这个得有hotplug的支持;加载驱动模块成功后就会在/dev/下生成设备文件了,如果用命令cat /proc/bus/usb/devices,我们可以看到驱动程序已经绑定到接口上了:
T:  Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#=  2 Spd=12  MxCh= 0
D:  Ver= 1.10 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=1234 ProdID=2345 Rev= 1.10
C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr=  0mA
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=test_usb_driver /*我们的驱动*/
E:  Ad=01(O) Atr=02(Bulk) MxPS=  64 Ivl=0ms
E:  Ad=82(I) Atr=02(Bulk) MxPS=  64 Ivl=0ms
此框架程序生成的是skel0(可以自由修改)的设备文件,现在就可以对这个设备文件进行打开、读写、关闭等的操作了。

 

结束语
面对层出不穷的新的USB设备,必须有人不断编写新的驱动程序以便让这些设备能够在linux下正常的工作,从这个意义上讲,驱动程序的编写本身就是一件非常有意义的工作,本文只是起到一个抛砖引玉的作用,帮助那些有志于写驱动程序的开发人员进一步了解USB驱动程序的设计思路,从而吸引更多的人加入到这个队伍中来。linux不仅为我们提供了一个顶级质量的操作系统,而且也为我们提供了参与到其未来开发过程的机会,我们完全可以从中得到无尽的快乐!
2019-07-06 14:20:19 natty715 阅读数 68

软件环境:vivado 2017.4        硬件平台:XC7Z020        系统及补丁版本:Debian 9 - patch 4.14


首先一定一定一定要明确,通常所说的Linux系统,是非实时的操作系统。那么,与此相对,什么是实时操作系统,什么又是非实时操作系统,两者之间的差别是啥。在我看来,最主要的差别感觉应该算是内核中进程调度的抢占与非抢占式。好嘛,那啥子又是抢占,啥子又是非抢占,且听我细细哔哔。举个栗子,如果对于一个系统,IO设备在任意时刻发生中断,CPU都会停止当下正在执行的进程,保护现场,然后转入中断处理函数,如果在执行当前中断处理程序中,又有高优先级中断进来,依旧能够停止当下进程,保护现场,转入中断处理函数,那么当前系统就可被看作是可抢占式的实时操作系统,因为中断的处理和进程的切换,都能够在确定的时间内完成。那非实时呢?首先想到的词应该是时间片轮转调度,排队,来中断了?搁队列后面等着,到你的时间片了再处理你,什么?等五年了?等五年了也得等着。

再哔哔下RTLinux到底是个啥,跟Linux差别在哪,我这里说的都很简单,想深挖的朋友可以CSDN搜一搜,货很多。

通常的Linux是没有上图中的实时内核和RT进程这两个框的,RTLinux中,通过多的这一层实时内核,全面接管外部硬件中断,并把原本的Linux内核,作为实时内核的一个低优先级的进程,当有实时任务需要处理时,通过实时内核调度RT进程完成,无实时任务时,运行优先级较低的Linux非实时进程。


接下来说下具体怎么搞,首先需要确定自己内核的版本,进入kernel以后make menuconfig最顶上就能看见当前内核版本,依次进入processor type and features -> preemption model可以看到,当前没有RT配置选项。如果你用不了make menuconfig命令,调出控制台,sudo apt-get install libncurses-dev。

在下方网址下载对应自己内核版本的补丁,如果找不到,就在older路径下面找。

https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/

补丁下载好后,放在跟自己内核一个路径的文件夹下。

 然后调出控制台,依次进行如下操作。

不对,你先别急,先别急着操作,我在这里要着重用红字哔哔几句,这里有个大坑,在给内核打补丁之前一定切记切记切记,把内核的配置文件.config先单独保存出来,最好连内核也备份一份出来,以便打补丁遇到疑难杂症的时候好恢复回去,.config单独备份出来是有重要原因的,因为补丁打完了以后.config会被改的面目全非,直接编译内核各种报错加启动不起来,最好的做法是.config单独保存,然后再给内核打补丁,补丁打完了以后,把单独保存的.config再覆盖回去,这样就是打过补丁的原配置了,最起码能保证编译正确,然后再在新内核修改RTLinux的配置项。

好叻,继续看打补丁进行的操作。

cd kernel
gzip -cd ../patch-4.14-rt1.patch.gz | patch -p1 --verbose

其中p后面的参数通常有p0和p1,p0意思是不忽略目录,从当前目录开始打补丁,p1是忽略第一层目录,因为我们要进入内核目录打补丁,所以使用p1参数。补丁打完以后如下图所示。

此时,在kernel里make menuconfig, 进入processor type and features -> preemption model可以看到,有关RTLinux的配置项就出来啦,说明补丁包打的目前基本没什么问题。

kernel配置修改完毕以后,做系统这些就不多说了吧,各人有各人的做法, 先看下没打补丁之前,系统启动以后用uname -a看当前操作系统内核信息如下。

打了补丁以后,系统启动uname -a再看看系统内核信息,可以看到内核有RT标识了。 

但是这也仅仅只是标识上面多了个RT而已吗?究竟性能上有没有实际差异呢?继续往下看。


 这里就要说下测试工具了,cyclictest,两种方式得到,一种是控制台里用git命令

git clone git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git

cd rt-tests
 
git checkout stable/v1.0

另一种是直接去网页里面下,网页下面还有很多其他版本的,这里我用的是v1.0版的。

 https://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git/

下载解压以后,进入rt-test-1.0,如果你跟我一样要测zynq平台的话,别直接make直接make的用不了,要改一下makefile文件。

把默认的CC和AR修改成你给zynq用的编译工具的CC和AR,也就是arm-linux-gnueabihf-路径。 修改后如下图。

接下来再make,搞定以后,把生成的cyclictest拖到zynq上。

输入测试命令./cyclictest -p 80 -t5 -n,设置线程优先级为80,开启5个线程,循环输出。

对测试结果做如下说明,第一个红框,内核非RT的增强版本。第二个红款,先说下T、P、C等字母含义。

T: 线程号      P: 线程优先级   

C: 计数器,线程的时间间隔每达到一次,计数器加1   

I: 1000 时间间隔为1000微秒(us)

Min: 最小延时(us)     Act: 最近一次的延时(us)   

 Avg:平均延时(us)     Max: 最大延时(us)

所以可以看出,非RT下,0线程测试6万8千次时,出现了6万次时最大值延迟6798 us,接下来看看RT核跑的情况。
 

 RT核下,同样是0线程,跑68万次,这个结果已经很可以说明问题了吧。

博文 来自: weixin_44215960

虚拟摄像驱动vivi

博文 来自: ljmiaw
没有更多推荐了,返回首页