您的位置:首页 > 其它

usb驱动框架(usb-skeleton)分析总结

2014-08-31 21:08 513 查看
以下是linux2.6.28 内核代码树 drivers/usb/中的文件usb-skeleton.c,usb-skeleton是usb驱动的框架通过修改一下部分代码就可一轻松的实现一个简单的设备驱驱动

每一个USB设备在内核中抽象为一个数据结构 usb_device,
它描述了一个USB设备的所有的特性。


linux中的驱动一部分被编译进内核,一部分是以模块的形式可动态的加载到内核,驱动是以模块被加载到内
核,驱动在加载时是按模块来加载的。模块的入口函数是module_init(),
在C语言写的应用程序中,程序的入口是main()函数.
驱动是模块,按照模块的加载,卸载方式进行驱动自身的加载,注销
用到:module_init()和module_exit
我们代码中如是:
[code]module_init(usb_skel_init);

module_exit(usb_skel_exit);
模块加载时,module_init()中的参数usb_skel_init被初始化,
usb_skel_init 中包括该驱动程序向系统注册的函数:
usb_register(&skel_driver)
该函数功能是:1。内核有一个USB设备大的链表,所谓的注册就是把这个驱动挂到该链表中

2。检查总线上的所有USB设备的配置空间,如果有和usb-skeleton中的
id_table相同,这就是说该驱动可以驱动在id_table中对上号的设备,
如果设备能对应上id_table驱动会把skel_driver这个数据结构挂到usb_device(本文中是usb_skel)上,
然后调用probe函数来初始化整个设备和做一些准备工作。

我们看一下usb_drivrer中的内容:
static struct usb_driver skel_driver = {

.name =        "skeleton",

.probe =    skel_probe,

.disconnect =    skel_disconnect,

.suspend =    skel_suspend,

.resume =    skel_resume,

.pre_reset =    skel_pre_reset,

.post_reset =    skel_post_reset,

.id_table =    skel_table,

.supports_autosuspend = 1,

};
usb_driver 中必不可少的两个函数是probe(进行设备和驱动的綁定)和discnect(断开设备处理函数)。
其他的根据所要实现的功能进行选择。
在probe中进行次设备注册的时候会掉用对设备进行操作的一组函数
skel_class,调用函数如下:
usb_register_dev(interface, &skel_class)
在skel_class中,包含了file_operation结构,file_operation就是read,write,open,release
等函数的函数指针。通过对应的函数指针就可以在用户空间调用对应的系统函数read,write,open,release
等。

总体来说该驱动框架分析过程应顺着这几个函数:

module_init(usb_skel_init)--->usb_skel_init()中的

usb_register(&skel_driver)---->(驱动中的id_table和设备配置能对上号)skel_driver中的
.probe =    skel_probe,----->
probe函数中
usb_register_dev(interface, &skel_class)语句————>
skel_class 中的
.fops =        &skel_fops,---->skel_fops中就是
系统调用对应的函数指针
,对应的函数实现了系统调用函数的具体功能,这些函数仅当进行系统调用的时候被执行。
这就是usb_skeleton 就是usb驱动框架。
些驱动的指导思想是尽量实现硬件的公能,而不是在这些功能之上添加策略。
驱动中更重要的是同步问题,继续学习中。
初学linux驱动,这篇文章只是做个总结,理一下思路,并不是深入研究后的结果。其中还有好多不明白的地方,
希望大家指出其中的问题,
交流,学习,我的邮箱:luo9168@gmail.com[/code]
* USB Skeleton driver - 2.2

*

* Copyright (C) 2001-2004
Greg Kroah-Hartman (greg@kroah.com)

*

* This program is free software; you
can redistribute it and/or

* modify it under the terms of the GNU General Public License as

* published by the Free Software Foundation, version 2.

*

* This driver is based on the 2.6.3
version of drivers/usb/usb-skeleton.c

* but has been rewritten to be easier to read and use.

*

*/

#include <linux/kernel.h> //
'kernel.h' contains some often-used function

#include <linux/errno.h>

#include <linux/init.h>
//模块初始化需要的头文件

#include <linux/slab.h>

#include <linux/module.h>

#include <linux/kref.h>

#include <asm/uaccess.h>

#include <linux/usb.h>

#include <linux/mutex.h>

/*

内核所用到的头文件都保存在include/目录下。为了方便使用和兼容性,Linus在编制内核程序头文件时所使用的命名方式与标准C库头文件的命名方式相似,在一个Linux系统中,它们与标准库的头文件并存。通常的做法是将这些头文件放置在标准库头文件目录中的子目录下,以让需要用到内核数据结构或常数的程序使用。在Linux系统中,列表14-1中的asm/、linux/和sys/三个子目录下的内核头文件通常需要复制到标准C库头文件所在的目录(/usr/include)中,而其他一些文件若与标准库的头文件没有冲突则可以直接放到标准库头文件目录下,或者改放到这里的三个子目录中
__<linux 内核完全剖析>有关内核头文件变化请参考 http://www.linuxsir.org/bbs/showthread.php?t=303531。 
*/

/* Define these values to match your devices */


//设备的厂商ID

#define USB_SKEL_VENDOR_ID 0xfff0

#define USB_SKEL_PRODUCT_ID 0xfff0

/* table of devices that work with this driver */


//使用本驱动的设备列表

static struct usb_device_id skel_table [] = {

{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

{ } /* Terminating entry USB_DEVICE 宏
利用厂商ID
和设备ID 提供了设备的唯一标识。当系统插入一个与ID匹配的USB设备到USB总线时,驱动会在USB core 中注册。
, */

};


//描述特定驱动支持的设备

MODULE_DEVICE_TABLE(usb, skel_table);


/*

MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是USB设备,那自然是usb(如果是PCI设备,那将是pci,这两个子系统用同一个宏来注册所支持的设备。这涉及PCI设备的驱动了,在此先不深究)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor
ID和product ID,如果它们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。*/
/* Get a minor range for your devices from the usb maintainer */

#define USB_SKEL_MINOR_BASE 192

/* our private defines. if this grows any larger, use your own .h file */

#define MAX_TRANSFER (PAGE_SIZE - 512)

/* MAX_TRANSFER is chosen so that the VM is not stressed by

allocations > PAGE_SIZE and the number of packets in a page

is an integer 512 is the largest possible packet on EHCI */

#define WRITES_IN_FLIGHT 8

/* arbitrarily chosen */

/* Structure to hold all of our device specific stuff */


//定义了设备的数据结构,后面会对该数据结构进行初始化以及赋值调用。


//sub_skel 结构存储的信息是本驱动所拥有的所有资源及状态

struct usb_skel {

struct usb_device *udev; /*
the usb device for this device */

struct usb_interface *interface; /*
the interface for this device */

struct semaphore limit_sem; /* limiting the number of writes in progress
限制进行中的写操作数量*/

struct usb_anchor submitted; /* in case we need to retract our submissions
*/

unsigned char *bulk_in_buffer; /*
the buffer to receive data */

size_t bulk_in_size; /* the size of the receive buffer */

__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint批量输入断点地址 */

__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint 批量输出端点地址*/

int errors; /* the last request tanked */

int open_count; /* count the number of openers */

spinlock_t err_lock; /* lock for errors */

struct kref kref;

struct mutex io_mutex; /* synchronize I/O with disconnect 同步变量*/

};

#define to_skel_dev(d) container_of(d, struct usb_skel, kref)


//container_of 函数功能是通过结构中的成员找到该结构的指针

static struct usb_driver skel_driver;

static void skel_draw_down(struct usb_skel *dev);

static void skel_delete(struct kref *kref)

{

struct usb_skel *dev = to_skel_dev(kref);

usb_put_dev(dev->udev);

kfree(dev->bulk_in_buffer);

kfree(dev);

}


/*
由于只是一个象征性的骨架程序,open()成员函数的实现非常简单,它根据usb_driver和次设备号通过
usb_find_interface()获得USB接口,之后通过usb_get_intfdata()获得接口的私有数据并赋予file-> private_data*/

static int skel_open(struct inode *inode, struct file *file)

{

struct usb_skel *dev;

struct usb_interface *interface;

int subminor;

int retval = 0;

//得到次设备号

subminor = iminor(inode);

//通过次设备号寻找接口

interface = usb_find_interface(&skel_driver, subminor);

if (!interface) {

err ("%s - error, can't find device for minor %d",

__func__, subminor);

retval = -ENODEV;

goto exit;

}

//得到用usb_get_intfdata保存的设备

dev = usb_get_intfdata(interface);

if (!dev) {

retval = -ENODEV;

goto exit;

}

/* increment our usage count for the device 增加引用计数*/

kref_get(&dev->kref);

/* lock the device to allow correctly handling errors

* in resumption防止设备自动挂起 */

mutex_lock(&dev->io_mutex);

if (!dev->open_count++) {

retval = usb_autopm_get_interface(interface);

if (retval) {

dev->open_count--;

mutex_unlock(&dev->io_mutex);

kref_put(&dev->kref, skel_delete);

goto exit;

}

} /* else { //uncomment this block if you want exclusive open

retval = -EBUSY;

dev->open_count--;

mutex_unlock(&dev->io_mutex);

kref_put(&dev->kref, skel_delete);

goto exit;

} */

/* prevent the device from being autosuspended */

/* save our object in the file's private structure保存设备到文件私有数据*/

file->private_data = dev;

mutex_unlock(&dev->io_mutex);

exit:

return retval;

}


/*
由于在open()函数中并没有申请任何软件和硬件资源,因此与open()函数对应的release()函数不用进行资源的释放,它会减少在open()中增加的引用计数*/

static int skel_release(struct inode *inode, struct file *file)

{

struct usb_skel *dev;

dev = (struct usb_skel *)file->private_data;

if (dev == NULL)

return -ENODEV;

/* allow the device to be autosuspended 允许设备自动挂起*/

mutex_lock(&dev->io_mutex);

if (!--dev->open_count && dev->interface)

usb_autopm_put_interface(dev->interface);

mutex_unlock(&dev->io_mutex);

/* decrement the count on our device
减少引用计数
*/

kref_put(&dev->kref, skel_delete);

return 0;

}

static int skel_flush(struct file *file, fl_owner_t
id)

{

struct usb_skel *dev;

int res;

dev = (struct usb_skel *)file->private_data;

if (dev == NULL)

return -ENODEV;

/* wait for io to stop */

mutex_lock(&dev->io_mutex);

skel_draw_down(dev);

/* read out errors, leave subsequent opens a clean slate */

spin_lock_irq(&dev->err_lock);

res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0;

dev->errors = 0;

spin_unlock_irq(&dev->err_lock);

mutex_unlock(&dev->io_mutex);

return res;

}

static ssize_t skel_read(struct file *file, char *buffer, size_t count, loff_t *ppos)

{

struct usb_skel *dev;

int retval;

int bytes_read;

//从私有数据中获得设备信息

dev = (struct usb_skel *)file->private_data;

//获得锁

mutex_lock(&dev->io_mutex);

if (!dev->interface) { /*
disconnect() was called */

retval = -ENODEV;

goto exit;

}

/* do a blocking bulk read to get data from the device 发布一个阻塞式的批量读写*/

retval = usb_bulk_msg(dev->udev,

usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),

dev->bulk_in_buffer,

min(dev->bulk_in_size, count),

&bytes_read, 10000);

/* if the read was successful, copy the data to userspace将数据复制到用户空间 */

if (!retval) {

if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))

retval = -EFAULT;

else

retval = bytes_read;

}

exit:


//释放锁

mutex_unlock(&dev->io_mutex);

return retval;

}


//回掉函数主要作用是释放内存

static void skel_write_bulk_callback(struct urb *urb)

{

struct usb_skel *dev;

dev = urb->context;

/* sync/async unlink faults aren't errors */

if (urb->status) {

if(!(urb->status == -ENOENT ||

urb->status == -ECONNRESET ||

urb->status == -ESHUTDOWN))

err("%s - nonzero write bulk status received: %d",

__func__, urb->status);

spin_lock(&dev->err_lock);

dev->errors = urb->status;

spin_unlock(&dev->err_lock);

}

/* free up our allocated buffer */

usb_buffer_free(urb->dev, urb->transfer_buffer_length,

urb->transfer_buffer, urb->transfer_dma);

up(&dev->limit_sem);

}


//write函数现分配一个urb,并把用户空间的数据复制到驱动中,然后填充刚刚分配的批量urb结构,最后向usb子系统提交该urb


static ssize_t skel_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos)

{

struct usb_skel *dev;

int retval = 0;

struct urb *urb = NULL;

char *buf = NULL;

size_t writesize = min(count, (size_t)MAX_TRANSFER);

//从是有数据中获取设备信息


dev = (struct usb_skel *)file->private_data;

/* verify that we actually have some data to write */

if (count == 0)

goto exit;

/* limit the number of URBs in flight to stop a user from using up all RAM 限制urb的数量防止RAM用完*/

if (down_interruptible(&dev->limit_sem)) {

retval = -ERESTARTSYS;

goto exit;

}

spin_lock_irq(&dev->err_lock);

if ((retval = dev->errors) < 0) {

/* any error is reported once */

dev->errors = 0;

/* to preserve notifications about reset */

retval = (retval == -EPIPE) ? retval : -EIO;

}

spin_unlock_irq(&dev->err_lock);

if (retval < 0)

goto error;

/* create a urb, and a buffer for it, and copy the data to the urb 创建urb*/

urb = usb_alloc_urb(0, GFP_KERNEL);

if (!urb) {

retval = -ENOMEM;

goto error;

}

//分配数据缓存


buf = usb_buffer_alloc(dev->udev, writesize, GFP_KERNEL, &urb->transfer_dma);

if (!buf) {

retval = -ENOMEM;

goto error;

}

//将用户数据复制到内核。

if (copy_from_user(buf, user_buffer, writesize)) {

retval = -EFAULT;

goto error;

}

/* this lock makes sure we don't submit URBs to gone devices */

mutex_lock(&dev->io_mutex);

if (!dev->interface) { /*
disconnect() was called */

mutex_unlock(&dev->io_mutex);

retval = -ENODEV;

goto error;

}

/* initialize the urb properly填充urb */

usb_fill_bulk_urb(urb, dev->udev,

usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),

buf, writesize, skel_write_bulk_callback, dev);

urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

usb_anchor_urb(urb, &dev->submitted);

/* send the data out the bulk port 提交urb块儿*/

retval = usb_submit_urb(urb, GFP_KERNEL);

mutex_unlock(&dev->io_mutex);

if (retval) {

err("%s - failed submitting write urb, error %d", __func__, retval);

goto error_unanchor;

}

/* release our reference to this urb, the USB core will eventually free it entirely 释放urb*/

usb_free_urb(urb);

return writesize;

error_unanchor:

usb_unanchor_urb(urb);

error:

if (urb) {

usb_buffer_free(dev->udev, writesize, buf, urb->transfer_dma);

usb_free_urb(urb);

}

up(&dev->limit_sem);

exit:

return retval;

}


/*大多数usb驱动都会涉及另外一个驱动系统,如scsi,网络或者tty子系统,这些驱动程序在其他驱动系统中注册,同时任何用户空间的交互操作都有那些接口提供。如把usb的驱动同时向csi注册,那么usb的read,write函数的操作就会调用scsi的read,write函数进行操作,但是对于扫描仪等驱动程序来说,并没有一个合适的驱动系统可以使用,那么就要自己处理于用户空间交互的read,write函数。usb子系统提供一种方法去注册一个次设备号和一个file_operation函数指针,这样就可以与用户空间实现交互的访问。这个方法就是usb_class_driver,他提供了次设备号,文件接口,devfs接口等。而usb_class_driver中的file_operations中就存放着内核与用户空间进行交互的函数指针。*/



static const struct file_operations
skel_fops = {

.owner = THIS_MODULE,

.read = skel_read,
//用户空间的read函数就是这里实现的skel_read函数

.write = skel_write,

.open = skel_open,

.release = skel_release,

.flush = skel_flush,

};

/*

* usb class driver info in order to get a minor number from the usb core,

* and to have the device registered with the driver core

*/

static struct usb_class_driver skel_class = {

.name = "skel%d",

.fops = &skel_fops,

.minor_base = USB_SKEL_MINOR_BASE,

};


//skel_probe 这是非常重要的一个函数。这个函数实现了设备于驱动的綁定。USB_DEVICE 宏利用厂商ID 和设备ID 提供了设备的唯一标识。当系统插入一个与ID匹配的USB设备到USB总线时,驱动会在USB core 中注册。驱动程序的probe函数也就会被调用。usb_device
结构指针,接口号和接口ID都会被传递到函数中。
probe是usb子系统自动调用的一个函数,有USB设备接到硬件集线器时,usb子系统会根据production
ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备调用相应驱动程序的probe(探测)函数,对于skeleton来说,就是skel_probe。系统会传递给探测函数一个usb_interface
*跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor
ID、Production ID等。probe()会根据usb_interface的成员寻找第一个批量输入和输出端点,将端点地址、缓冲区等信息存入 为USB骨架程序定义的usb_skel结构体,并将usb_skel实例的指针传入usb_set_intfdata()作为USB接口的私有数据,最
后,它会注册USB设备

static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)

{

struct usb_skel *dev;

struct usb_host_interface *iface_desc;

struct usb_endpoint_descriptor *endpoint;

size_t buffer_size;

int i;

int retval = -ENOMEM;

/* allocate memory for our device state and initialize it */


//为设备驱动分配内存并初始化,请回头看一看struct usb_skel,其实就是为该设备驱动分配内存并进行//初始化。

dev = kzalloc(sizeof(*dev), GFP_KERNEL);

if (!dev) {

err("Out of memory");

goto error;

}

kref_init(&dev->kref);

sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);

mutex_init(&dev->io_mutex);

spin_lock_init(&dev->err_lock);

init_usb_anchor(&dev->submitted);


//根据接口获取设备
在初始化了一些资源之后,可以看到第一个关键的函数调用——interface_to_usbdev。他同uo一个usb_interface来得到该接口所在设备的设备描述结构。本来,要得到一个usb_device只要用interface_to_usbdev就够了,但因为要增加对该usb_device的引用计数,我们应该在做一个usb_get_dev的操作,来增加引用计数,并在释放设备时用usb_put_dev来减少引用计数。这里要解释的是,该引用计数值是对该usb_device的计数,并不是对本模块的计数,本模块的计数要由kref来维护。所以,probe一开始就有初始化kref。事实上,kref_init操作不单只初始化kref,还将其置设成1。所以在出错处理代码中有kref_put,它把kref的计数减1,如果kref计数已经为0,那么kref会被释放。kref_put的第二个参数是一个函数指针,指向一个清理函数。注意,该指针不能为空,或者kfree。该(skel_delete)函数会在最后一个对kref的引用释放时被调用

dev->udev = usb_get_dev(interface_to_usbdev(interface));

dev->interface = interface;

/* set up the endpoint information */

/* use only the first bulk-in and bulk-out endpoints */


//建立端口信息,使用第一个批量输入,输出的端点

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 &&

usb_endpoint_is_bulk_in(endpoint)) {

/* we found a bulk in endpoint找到一个批量输入端点 */

buffer_size = le16_to_cpu(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 &&

usb_endpoint_is_bulk_out(endpoint)) {

/* we found a bulk out endpoint 找到一个批量输出端点*/

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;

}

/* save our data pointer in this interface device */

//将设备信息报存在接口结构中
,(就是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到,)

usb_set_intfdata(interface, dev);

/* we can register the device now, as it is ready */


//一切就绪,注册次设备
即向这个interface注册一个skel_class结构

此处是重点!skel_class 中包含

retval = usb_register_dev(interface, &skel_class);

if (retval) {//注册失败

/* something prevented us from registering this driver */

err("Not able to get a minor for this device.");

usb_set_intfdata(interface, NULL);

goto error;

}

/* let the user know what node this device is now attached to */

info("USB Skeleton device now attached to USBSkel-%d", interface->minor);

return 0;

error:

if (dev)

/* this frees allocated memory */

kref_put(&dev->kref, skel_delete);

return retval;

}


//当设备被拔出集线器时,usb子系统会自动地调用disconnect,他做的事情不多,最重要的是注销class_driver(交还次设备号)和interface的data,然后他会用kref_put(&dev->kref,
skel_delete)进行清理

static void skel_disconnect(struct usb_interface *interface)

{

struct usb_skel *dev;

int minor = interface->minor;

dev = usb_get_intfdata(interface);

usb_set_intfdata(interface, NULL);

/* give back our minor 注销次设备*/

usb_deregister_dev(interface, &skel_class);

/* prevent more I/O from starting防止更多的IO操作 */

mutex_lock(&dev->io_mutex);

dev->interface = NULL;

mutex_unlock(&dev->io_mutex);

usb_kill_anchored_urbs(&dev->submitted);

/* decrement our usage count 减少引用计数*/

kref_put(&dev->kref, skel_delete);

info("USB Skeleton #%d now disconnected", minor);

}

static void skel_draw_down(struct usb_skel *dev)

{

int time;

time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);

if (!time)

usb_kill_anchored_urbs(&dev->submitted);

}

static int skel_suspend(struct usb_interface *intf, pm_message_t
message)

{

struct usb_skel *dev = usb_get_intfdata(intf);

if (!dev)

return 0;

skel_draw_down(dev);

return 0;

}

static int skel_resume (struct usb_interface *intf)

{

return 0;

}

static int skel_pre_reset(struct usb_interface *intf)

{

struct usb_skel *dev = usb_get_intfdata(intf);

mutex_lock(&dev->io_mutex);

skel_draw_down(dev);

return 0;

}

static int skel_post_reset(struct usb_interface *intf)

{

struct usb_skel *dev = usb_get_intfdata(intf);

/* we are sure no URBs are active - no locking needed */

dev->errors = -EPIPE;

mutex_unlock(&dev->io_mutex);

return 0;

}

static struct usb_driver skel_driver = {

.name = "skeleton",

.probe = skel_probe,

.disconnect = skel_disconnect,

.suspend = skel_suspend,

.resume = skel_resume,

.pre_reset = skel_pre_reset,

.post_reset = skel_post_reset,

.id_table = skel_table,

.supports_autosuspend = 1,

};


//模块初始化函数

static int __init usb_skel_init(void)

{

int result;

/* register this driver with the USB subsystem */

result = usb_register(&skel_driver);

if (result)

err("usb_register failed. Error number %d", result);

return result;

}


//模块注销函数

static void __exit usb_skel_exit(void)

{

/* deregister this driver with the USB subsystem */

usb_deregister(&skel_driver);

}

module_init(usb_skel_init);

module_exit(usb_skel_exit);

MODULE_LICENSE("GPL");

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: