怎样写linux下的USB设备驱动程序

怎样写linux下的USB设备驱动程序,第1张

写一个USB的驱动程序最 基本的要做四件事:驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urb(USB请求块)

驱动程序支持的设备:有一个结构体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_ID0x1234

#define USB_SKEL_PRODUCT_ID0x2345

这两个值可以通过命令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,

}

探测和断开:当 一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用,探测函数检查传递给它的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因 为某种原因不应该控制设备时,断开函数被调用,它可以做一些清理工作。探测回调函数中,USB驱动程序初始化任何可能用于控制USB设备的局部结构体,它 还把所需的任何设备相关信息保存到一个局部结构体中,

提交和控制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,__u8request,__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文件。

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_ID0x1234#define USB_SKEL_PRODUCT_ID0x2345这两个值可以通过命令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,}

static int __init ohci_hcd_mod_init(void)

{

platform_driver_register(&ohci_hcd_s3c2410_driver)

}

其实真正注册的是ohci_hcd_s3c2410_driver这个驱动。那我们来看一下这个结构体的具体值。

static struct platform_driver ohci_hcd_s3c2410_driver= {

.probe = ohci_hcd_s3c2410_drv_probe,

.remove = ohci_hcd_s3c2410_drv_remove,

.shutdown = usb_hcd_platform_shutdown,

.driver = {

.owner = THIS_MODULE,

.name = "s3c2410-ohci",

},

}

那我们一一来看上述的每一个函数的实现。

2.1 hcd 探测

函数很简单其实现功能的是usb_hcd_s3c2410_probe函数。

static int ohci_hcd_s3c2410_drv_probe(structplatform_device *pdev)

{

returnusb_hcd_s3c2410_probe(&ohci_s3c2410_hc_driver, pdev)

}

ohci_s3c2410_hc_driver提供了对于ohci的 *** 作集。对于这些函数在后面的学习中去看,在此不加扩展。我们将下面的函数剔除枝叶留其主干。

static int usb_hcd_s3c2410_probe (const structhc_driver *driver,

struct platform_device *dev)

{

structusb_hcd *hcd = NULL

int retval

#if !defined(CONFIG_ARCH_2410)

usb_host_clk_en() --使能clk

#endif

s3c2410_usb_set_power(dev->dev.platform_data,1, 1)

s3c2410_usb_set_power(dev->dev.platform_data,2, 1)

hcd =usb_create_hcd(driver, &dev->dev, "s3c24xx") --创建一个hcd

hcd->rsrc_start= dev->resource[0].start--获取物理地址

hcd->rsrc_len = dev->resource[0].end -dev->resource[0].start + 1

request_mem_region(hcd->rsrc_start,hcd->rsrc_len, hcd_name)

clk =clk_get(&dev->dev, "usb-host")

s3c2410_start_hc(dev,hcd)

hcd->regs= ioremap(hcd->rsrc_start, hcd->rsrc_len)

ohci_hcd_init(hcd_to_ohci(hcd))

retval = usb_add_hcd(hcd,dev->resource[1].start, IRQF_DISABLED)

return 0

}

对于usb的电源管理,我们暂时不看,不看不代表不重要,电源管理是很重要的。

那依次来看上面的函数。usb_create_hcd创建和初始化一个hcd结构体。

s3c2410_start_hc启动hc。这里有一个很奇怪的结构体就是struct s3c2410_hcd_info,在s3c6410中并没有看到该结构体的赋值。也许有人对此很困惑,该结构体做什么用的。那我们来看该结构体的真正面目。

struct s3c2410_hcd_info {

structusb_hcd *hcd --保存该hcd_info所属的hcd

structs3c2410_hcd_portport[2]--两个端口。

void(*power_control)(intport, int to)--电源控制

void(*enable_oc)(structs3c2410_hcd_info *, int on)

void(*report_oc)(structs3c2410_hcd_info *, int ports)

}

在usb-host.txt中对其功能进行了说明,就是一对函数,使能过流检测和控制端口电源状态。

power_control:使能或禁止端口电源

enable_oc:使能或禁止端口过流检测

report_oc:当端口存在过流,则会调用该函数。

static void s3c2410_start_hc(structplatform_device *dev, struct usb_hcd *hcd)

{

structs3c2410_hcd_info *info = dev->dev.platform_data

clk_enable(clk)

if (info !=NULL) { --在s3c6410中该info为空。

info->hcd = hcd

info->report_oc= s3c2410_hcd_oc

if(info->enable_oc != NULL) {

(info->enable_oc)(info,1)

}

}

}

初始化ohci_hcd

static void ohci_hcd_init(structohci_hcd *ohci)

{

ohci->next_statechange= jiffies

spin_lock_init(&ohci->lock)

INIT_LIST_HEAD(&ohci->pending)

}

初始化并注册usb_hcd

完成通用hcd的初始化和注册,在这里同时完成中断的申请和注册。

int usb_add_hcd(struct usb_hcd *hcd,unsigned intirqnum, unsigned long irqflags)

{

int retval

structusb_device *rhdev

hcd->authorized_default= hcd->wireless? 0 : 1 --判断是否为无线

set_bit(HCD_FLAG_HW_ACCESSIBLE,&hcd->flags)--设置HW_ACCESSIBLE旗标

if ((retval =hcd_buffer_create(hcd)) != 0) { --开辟hcd的缓冲区

returnretval

}

if ((retval =usb_register_bus(&hcd->self)) <0)

gotoerr_register_bus

if ((rhdev =usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {

retval= -ENOMEM

gotoerr_allocate_root_hub

}

rhdev->speed= (hcd->driver->flags &HCD_USB2) ? USB_SPEED_HIGH :USB_SPEED_FULL--指定根hub的speed

hcd->self.root_hub= rhdev

device_init_wakeup(&rhdev->dev,1)

if(hcd->driver->reset &&(retval = hcd->driver->reset(hcd))<0) {--为NULL

gotoerr_hcd_driver_setup

}

if(device_can_wakeup(hcd->self.controller)

&&device_can_wakeup(&hcd->self.root_hub->dev))

dev_dbg(hcd->self.controller,"supports USB remote wakeup\n")

if(hcd->driver->irq) { --中断处理

if(irqflags &IRQF_SHARED)

irqflags&= ~IRQF_DISABLED

snprintf(hcd->irq_descr,sizeof(hcd->irq_descr), "%s:usb%d",

hcd->driver->description,hcd->self.busnum)

request_irq(irqnum,&usb_hcd_irq, irqflags,hcd->irq_descr, hcd)--申请中断线

}

hcd->irq= irqnum

} else {

hcd->irq= -1;

}

hcd->driver->start(hcd) --调用start为 ohci_s3c2410_start

rhdev->bus_mA= min(500u, hcd->power_budget)

register_root_hub(hcd)); --注册root hub

retval =sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group)

if (retval<0) {

gotoerror_create_attr_group

}

if(hcd->uses_new_polling &&hcd->poll_rh)

usb_hcd_poll_rh_status(hcd)

returnretval

}

那一一来看上面的函数,学习内核就要有打破砂锅问到底的精神,唯有知道那背后的种种风光,才能领略那种种风采。闲话不说,继续!

记住下面结构体中flag的值。那就看这几个宏定义是什么意思。

#defineHCD_MEMORY 0x0001 --hc的寄存器使用memory映射

#defineHCD_LOCAL_MEM 0x0002 --hc使用local memory

#defineHCD_USB11 0x0010 --usb1.1

#defineHCD_USB20x0020 --usb2.0

static const struct hc_driver ohci_s3c2410_hc_driver=

{

.flags =HCD_USB11 | HCD_MEMORY,

};

为hcd分配缓冲池,当hc需要使用DMA内存分配器。

int hcd_buffer_create(struct usb_hcd *hcd)

{

charname[16]

int i, size

if(!hcd->self.controller->dma_mask &&

!(hcd->driver->flags &HCD_LOCAL_MEM))

return 0

--#define HCD_BUFFER_POOLS 4

我们查看pool_max其实是一个全局数组。如果需要开辟的缓冲区更大的话,直接采用分配page的函数。

static const size_tpool_max[HCD_BUFFER_POOLS] = {

32,128,512,PAGE_SIZE/ 2

}

for (i = 0i<HCD_BUFFER_POOLSi++) {

size = pool_max[i]

if(!size)

continue

snprintf(name,sizeof name, "buffer-%d", size)

hcd->pool[i] = dma_pool_create(name,hcd->self.controller,size, size, 0)

if(!hcd->pool [i]) {

hcd_buffer_destroy(hcd)

return-ENOMEM

}

}

return 0

}

dma_pool_create创建一个DMA池(生成一个dma_pool,并没有分配相应空间,真正分配物理内存将在dma_pool_alloc()总实现)。

下面的函数是usb_bus注册,对于该函数也许很难理解。不过参照网上http://www.sudu.cn/info/html/edu/20080425/301909.html的说明,估计会好理解很多。

每个主机控制器拥有一个USB系统,称为一个USB总线。USBD支持多个主机控制器,即多个USB总线。当每增加一个主机控制器时,会给他分配一个usb_bus结构。USBD动态安装和卸载主机驱动。主机驱动安装时,他的初始化函数一方面完成主机控制器硬件的设置和初始化工作,另一方面调用usb_alloc_bus和usb_register_bus来将自己注册到USBD中去,供USB子系统访问。

static int usb_register_bus(struct usb_bus *bus)

{

int result =-E2BIG

int busnum

mutex_lock(&usb_bus_list_lock)

busnum =find_next_zero_bit (busmap.busmap, USB_MAXBUS, 1)

--用busmap来存储主机驱动,一个bit位代表一个主机驱动

if (busnum >=USB_MAXBUS) {

return result

}

set_bit (busnum,busmap.busmap)

bus->busnum = busnum

bus->dev =device_create(usb_host_class, bus->controller, MKDEV(0, 0),bus,"usb_host%d", busnum)

--在usb_host类下创建一个usb_host设备。

list_add(&bus->bus_list, &usb_bus_list)

mutex_unlock(&usb_bus_list_lock)

usb_notify_add_bus(bus)

return 0

}


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7289722.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-03
下一篇 2023-04-03

发表评论

登录后才能评论

评论列表(0条)

保存