详细解析windows usb驱动和linux usb驱动的相似和差异(六)
2011-03-19 23:12
441 查看
版权信息:
版权归smilestone322所有,保留所有权利,仅用来学习,请勿用于商业用途,欢迎转载,转载请注明出处。谢谢!
3 windows下usb驱动开发
3.1 dw+ddk usb驱动开发实例
采用driver studio3.2进行驱动开发是最简单的方法,因为它可以采用driver works生成一个驱动的框架,你只要填充你自己的部分实现和硬件通信的一部分就可以了,主要要做的工作就是发送usb设备厂商请求,其实这就是实现和硬件的通信协议,如果不是在ARM上开发,软件开发者只要搞清楚usb芯片的端口等东西就可以了,也可以和硬件开发者定义一个usb的传输协议,就是你发送什么控制命令,硬件给你传输什么数据,比如你在命令发送一些数据给硬件,硬件开发者就根据你的数据配置FPGA的某个寄存器。通信协议定义好了后,最重要的就是传输数据了,首先发送一个usb厂商请求,然后硬件从某个端口传输数据,软件部分就只要去监听那个相应的端口了。根据传输方式的不同,有bulk传输,中断传输和同步传输3种方式,一般控制传输用来发送控制命令用。这些都在usb的开发基础中,已经讲过了。下面就根据driver
studio 3.2的一个例子进行讲解。
下面我以driver works下面的usbbulk的例子进行讲解:
任何驱动程序都是从DriverEntry开始执行的,DriverEntry就像c语言的main函数一样,在usbbulk的例子当中,我们看到DriverEntry没有做任何工作,这是因为driver
studio对DriverEntry进行了封装,这部分代码就不需要我们编写了,但是如果单独采用DDK进行编写驱动,则必须自己编写这个函数里面的内容,这部分内容在下一节的ddk驱动开发中进行讲解。
NTSTATUS UsbBulkDriver::DriverEntry(PUNICODE_STRING RegistryPath)
{
Tracer << "In Driver Entry";
m_Unit = 0;
return STATUS_SUCCESS;
}
讲解完DriverEntry函数后,就要接着讲解AddDevice函数了,该函数的作用是在设备被首次枚举时,AddDevice例程在系统初始化时被调用,当系统运行时,一旦有一个新设备被枚举,系统将调用AddDevice例程。AddDevice函数的基本职责是创建一个设备对象并把它连接到以底层的设备堆栈中,相关步骤如下:
(1)创建设备对象,并建立一个私有的设备扩展对象。
(2)寄存一个或多个设备接口,以便应用程序能知道设备的存在,另外,还可以给出设备名并创建符号链接名。
(3)初始化设备扩展对象和设备对象的标志位。
(4)把新设备对象附加到设备堆栈中。
NTSTATUS UsbBulkDriver::AddDevice(PDEVICE_OBJECT Pdo)
{
Tracer << "Into AddDevice\n";
UsbBulkDevice* pDevice =
new (NonPagedPool) UsbBulkDevice(Pdo, m_Unit);
if (pDevice)
{
NTSTATUS status = pDevice->ConstructorStatus();
if ( !NT_SUCCESS(status) ) {
Tracer << "Error constructing device UsbBulk"
<< (ULONG)m_Unit << "\n";
delete pDevice;
} else {
m_Unit++;
}
return status;
}
else {
Tracer << "Error creating device UsbBulk"
<< (ULONG)m_Unit << "\n";
return STATUS_INSUFFICIENT_RESOURCES;
}
}
该函数中调用了usbbulk的构造函数,定义如下:
UsbBulkDevice::UsbBulkDevice(PDEVICE_OBJECT Pdo, ULONG Unit) :
KPnpDevice(
Pdo,
KUnitizedName(L"UsbBulk", Unit),
FILE_DEVICE_UNKNOWN,
KUnitizedName(L"UsbBulk", Unit),
0,
DO_DIRECT_IO
)
{
Tracer << "UsbBulkDevice::UsbBulkDevice() Entry\n";
// initialize the USB lower device
m_Usb.Initialize(this, Pdo);
// initialize the USB interface
m_Interface.Initialize(
m_Usb, //KUsbLowerDevice
0, //Interface Number
1, //Configuration Value
0 //Initial Alternate Setting for the Interface
);
// initialize description of data in pipe (In Bulk)
m_Pipe0.Initialize(
m_Usb, //KUsbLowerDevice
0x81 //Endpoint Address
);
// initialize description of data out pipe (Out Bulk)
m_Pipe1.Initialize(
m_Usb, //KUsbLowerDevice
0x2 //Endpoint Address
);
// inform PnP code of our Top-of-stack device
SetLowerDevice(&m_Usb);
// use the default Pnp policy SetPnpPolicy在kpnpdev.cpp中定义,就是选择是和否,因为pnp策略
driverworks已经做了大量的工作,所以我们只要选择是或否来完成就行了。
SetPnpPolicy();
// use the default Power policy
SetPowerPolicy();
}
该构造函数的作用就是做一些初始化的工作,比如初始化usb设备,将它链接到底层设备堆栈PDO,接着初始化usb的接口,usb设备有几个接口,几个配置等等,然后就是初始化usb管道,即usb端点了。在上面的构造函数中可以看出,有2个端点,0x81为输入bulk端点,0x2为输出的bulk端点,接着初始化Pnp和电源管理。当采用driverworks生成驱动框架时,它已经为我们产生了3个pnp例程,即OnStartDevice,OnStopDevice,OnRemoveDevice及一个默认的DefaultPnp例程。
下面看看pnp默认例程和电源管理例程:
NTSTATUS UsbBulkDevice::DefaultPnp(KIrp I)
{
static char* minors[] = {
"IRP_MN_START_DEVICE",
"IRP_MN_QUERY_REMOVE_DEVICE",
"IRP_MN_REMOVE_DEVICE",
"IRP_MN_CANCEL_REMOVE_DEVICE",
"IRP_MN_STOP_DEVICE",
"IRP_MN_QUERY_STOP_DEVICE",
"IRP_MN_CANCEL_STOP_DEVICE",
"IRP_MN_QUERY_DEVICE_RELATIONS",
"IRP_MN_QUERY_INTERFACE",
"IRP_MN_QUERY_CAPABILITIES",
"IRP_MN_QUERY_RESOURCES",
"IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
"IRP_MN_QUERY_DEVICE_TEXT",
"IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
"IRP_MN_undefined",
"IRP_MN_READ_CONFIG",
"IRP_MN_WRITE_CONFIG",
"IRP_MN_EJECT",
"IRP_MN_SET_LOCK",
"IRP_MN_QUERY_ID",
"IRP_MN_QUERY_PNP_DEVICE_STATE",
"IRP_MN_QUERY_BUS_INFORMATION",
"IRP_MN_DEVICE_USAGE_NOTIFICATION",
"IRP_MN_SURPRISE_REMOVAL"
};
ULONG Minor = I.MinorFunction();
CHAR* IrpName;
if ( Minor < IRP_MN_SURPRISE_REMOVAL )
IrpName = minors[Minor];
else
IrpName = "<unknown>";
DbgPrint("Pnp IRP minor function=%s\n", IrpName);
I.ForceReuseOfCurrentStackLocationInCalldown();
return m_Usb.PnpCall(this, I);
}
NTSTATUS UsbBulkDevice::DefaultPower(KIrp I)
{
I.IndicatePowerIrpProcessed();
I.CopyParametersDown();
return m_Usb.PnpPowerCall(this, I);
}
即插即用(PNP)是硬件和软件支持的组合,Pnp需要获得硬件设备、系统软件和驱动程序的支持。这些软件支持无需用户干预就能使系统自动识别或适应硬件配置的一些改变,用户可从微机系统中添加或删除设备,不需要做一些复查的配置。Pnp的功能如下:
自动或动态识别安装的pnp设备,当添加一个pnp设备时,如,动态插入usb设备,系统就能自动识别它。
硬件资源的分配和再分配,当有新的pnp设备添加到正在使用的系统中时,可以重新分配资源,如I/O端口,硬件中断号IRQ等。
在驱动里面,pnp管理和i/o管理都是通过Irp起作用的,如指导启动,停止和删除设备,所有的即插即用的IRP的主功能代码为IRP_MJ_PNP。
从usbbulk例子中看,使用的默认的pnp函数,它调用了:
ForceReuseOfCurrentStackLocationInCalldown,该函数的作用是强制使用当前的相同栈,而不使用新的栈,pnpcall是将irp传递给硬件设备。
Pnp例程是怎样起作用的呢,这就是在kpnpdev.cpp文件中有个pnp(Kirp
I)函数,在pnp(Kirp I)函数中,我们可以看到它通过判断pnp的minor来进行switch的跳转,跳到不同的函数进行处理。而在usbbulk中,当设备不存在时,系统会调用DriverEntry中的AddDevice函数指针来添加设备,同时做一些初始化缓冲区之类的与操作系统相关的工作。当设备的功能驱动程序加载后,pnp管理器就开始工作了。首先会有一个IRP_MN_START_DEVICE的pnp的IRP,驱动程序从这个Irp中获取硬件资源后,对设备做一些初始化工作,然后驱动程序就开始正常的工作。
Pnp(Kirp I)函数有系统调用,在该函数的源码中,我们可以看到,它根据不同的mimor irp switch跳转执行不同的pnp例程,比如case:IRP_MN_START_DEVICE,就跳转到OnStartDevice例程了,case:IRP_MN_REMOVE_DEVICE就跳转到OnRemoveDevice例程了,当case:IRP_MN_STOP时,就跳转到OnStopDevice例程,如果出现我们没有定义的irp例程时,就调用我们定义的默认的DefaultPnp例程,下面直接贴代码:
NTSTATUS UsbBulkDevice::OnStartDevice(KIrp I)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
AC_STATUS acStatus;
Tracer << "UsbBulkDevice::OnStartDevice() Entry\n";
// The default Pnp policy has already cleared the IRP with the lower device
acStatus = m_Usb.ActivateConfiguration(
1 // configuration value 1
);
switch (acStatus)
{
case AC_SUCCESS:
Tracer << "Configuration OK\n";
status = STATUS_SUCCESS;
break;
case AC_COULD_NOT_LOCATE_INTERFACE:
Tracer << "Could not locate interface\n";
break;
case AC_COULD_NOT_PRECONFIGURE_INTERFACE:
Tracer << "Could not get configuration descriptor\n";
break;
case AC_CONFIGURATION_REQUEST_FAILED:
Tracer << "Board did not accept configuration URB\n";
break;
case AC_FAILED_TO_INITIALIZE_INTERFACE_OBJECT:
Tracer << "Failed to initialize interface\n";
break;
case AC_FAILED_TO_OPEN_PIPE_OBJECT:
//NOTE: this may or may not be fatal. It could mean that
//the device has an endpoint for which a KUsbPipe object has
//not been instanced. If the intention is to not use this pipe,
//then it's ok. Otherwise, there is a failure. Clients can
//iterate through the pipe array in KUsbLowerDevice to check
//which pipes are open/closed.
Tracer << "Failed to open pipe object \n";
break;
default:
Tracer << "Unexpected error activating USB configuration\n";
break;
}
return status; // base class completes the IRP
}
从OnStartDevice函数可以看出,他调用的是ActivateConfiguration函数激活配置,在usbbulk例子中没有做其它工作。
/////////////////////////////////////////////////////////////////////////
// OnStopDevice
//
// The system calls this when the device is stopped
NTSTATUS UsbBulkDevice::OnStopDevice(KIrp I)
{
Tracer << "UsbBulkDevice::OnStopDevice() Entry\n";
return m_Usb.DeActivateConfiguration();
// base class passes to lower device
}
OnStopDevice所做的工作完全和OnStartDevice例程相反,就是去激活配置。
/////////////////////////////////////////////////////////////////////////
// OnRemoveDevice
//
// The system calls this when the device is removed.
NTSTATUS UsbBulkDevice::OnRemoveDevice(KIrp I)
{
Tracer << "UsbBulkDevice::OnRemoveDevice() Entry\n";
// Our PnP policy will take care of
// (1) giving the IRP to USBD
// (2) detaching the PDO
// (3) deleting the device object
m_Usb.ReleaseResources();
return STATUS_SUCCESS;
}
OnRemoveDevice当硬件设备被删除时调用,主要是释放为硬件分配的资源。电源管理策略在usbbulk中采用默认的电源管理,都是driver studio已经做了,我们不必要在关心它,但是如果自己实现电源管理的话,还是挺难的。
分析完电源管理和pnp例程,我们在来分析I/O例程了。前面我们讲过应用程序和驱动之间的通信都是通过win 32 api实现的,最主要的api有WriteFile,ReadFile,DeviceIoControl函数。那么这些是怎么和驱动程序的读写函数联系起来的呢,其中DeviceIoControl首先产生一个IRP_MJ_DEVICE_CONTROL的irp,然后是通过的参数dwIoControlCode和驱动联系起来的,在写应用程序的时候,会将驱动中定义dwIoControlCode的头文件#include进来。DeviceIoControl的函数原型如下:
BOOL DeviceIoControl
{
HANDLE hDevice , //函数返回的设备句柄
DWORD dwIoControlCode,//应用程序调用驱动程序的控制命令
LPVOID lpInBuffer, //应用程序传递给驱动程序的数据缓冲区地址
DWORD nInBufferSize,//应用程序传递驱动程序的数据字节数
LPVOID lpOutBuffer, //存放驱动程序返回的数据缓冲区地址
DWORD nOutBufferSize,//存放驱动程序返回的数据字节数
LPDWORD lpBytesReturned //存放驱动程序实际返回数据字节数的变量地址
LPOVERLAPPED lpOverlapped //一个指向overlapped结构的变量地址,同步null
}
下面回到usbbulk的例子,在Bulkioct.h中定义了dwIoControlCode:
#define IOCTL_GET_CONFIG \
CTL_CODE (FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
这样当应用程序调用DeviceIoControl时候就可以和驱动程序通信了,在usbbulk的例子中驱动程序相应的函数是:
NTSTATUS UsbBulkDevice::DeviceControl(KIrp I)
{
Tracer << "UsbBulkDevice::DeviceControl() Entry\n";
#pragma warning(disable:4065) // switch with no cases
switch (I.IoctlCode())
{
default:
Tracer << "UNKNOWN [ID=" << I.IoctlCode() << "] );\n",
I.Information() = 0;
return I.PnpComplete(this, STATUS_INVALID_PARAMETER);
}
}
在usbbulk中,DeviceControl中没有做任何工作,其实DeviceIoControl可以实现WriteFile的功能,而且从它的函数原型中可以看出该函数还可以从驱动中返回数据,挺好用的,如果要返回数据,就是要设置一个缓冲区,然后设置数据的大小,就是通过I.Information()赋值实现的。这里就不多说了,我在写usb的驱动时,都只用了这个函数给usb驱动程序发送控制命令或厂商请求啥的,而不喜欢用WriteFile,DeviceIocontrol有一个参数lpOverlapped是一个结构体,可以用来控制同步还是异步通信。
讲解完DeviceIoControl就该轮到读写函数了,对于应用程序来说,无论怎么封装,且无论你采用什么的方式与硬件通信,比如说usb,串口,网口等等,不论你封装的层次怎样,到了底层都会调用WriteFile函数和ReadFile函数,下面直接讲解这两个函数是怎样和硬件进行通信的。
当应用程序调用WriteFile时,会产生一个irp,IRP_MJ_WRITE,通过这个irp就可以和驱动程序对应起来了,在usbbulk中,这个IRP对应的函数就是:Write(KIrp
I) :
NTSTATUS UsbBulkDevice::Write(KIrp I)
{
NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
Tracer << "UsbBulkDevice::Write() Entry\n";
// Declare a memory object
KMemory Mem(I.Mdl());
ULONG dwTotalSize = I.WriteSize(CURRENT);
ULONG dwMaxSize = m_Pipe1.MaximumTransferSize();
// If the total requested write size is greater than the Maximum Transfer
// Size for the Pipe, request to write only the Maximum Transfer Size since
// the bus driver will fail an URB with a TransferBufferLength of greater
// than the Maximum Transfer Size.
if ( dwTotalSize > dwMaxSize )
{
ASSERT(dwMaxSize);
dwTotalSize = dwMaxSize;
}
ULONG dwBytesSent = 0;
PURB pUrb = m_Pipe1.BuildBulkTransfer(
Mem, // Where is data coming from?
dwTotalSize, // How much data to write?
FALSE, // direction (FALSE = OUT)
NULL // Link to next URB
);
// Submit the URB to our USB device, synchronously
if (pUrb != NULL)
{
status = m_Pipe1.SubmitUrb(pUrb, NULL, NULL);
if ( NT_SUCCESS(status) )
{
dwBytesSent = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength;
Tracer << "Write() posted " << dwTotalSize << " bytes to USB\n";
}
delete pUrb;
}
I.Information() = dwBytesSent;
return I.PnpComplete(this, status, IO_NO_INCREMENT);
}
驱动程序所创建的设备一般有2种读写方式,一种为缓冲区读写方式,另外一种就是直接读写方式,在上面的Write函数中采用的是直接的读写方式。
KMemory Mem(I.Mdl());函数的作用是分配内存,ULONG dwTotalSize = I.WriteSize(CURRENT);函数的作用是获得要写的size,这个size由WriteFile传递,WriteFile函数的原型如下:
BOOL WriteFile(
HANDLE hFile, //通过CreateFile返回的设备句柄
LPCVOID lpBuffer, //应用程序传递给驱动程序的buffer
DWORD nNumberOfBytesToWrite, //应用程序传递给驱动的buffer
size
LPDWORD lpNumberOfBytesWritten,//保存应用程序实际传递给驱动程序的buffer
size
LPOVERLAPPED lpOverlapped//一个指向overlapped结构的变量地址,同步null
)
dwTotalSize实际上就等于nNumberOfBytesToWrite,在驱动程序里面dwTotalSize要和驱动支持的最大maxsize比较,如果应用程序传递给它的size大于maxsize,那么驱动程序就只能传递maxsize大小的数据,除非驱动程序做特殊的处理,比如说分多次写。这个在ddk程序设计中,有时间在讲解。在driver
studio 3.2中,对应usb设备来说,有几个函数拿出来讲讲,一个是构建厂商请求或类请求,一个就是发送URB,这两个函数的原型如下:
PURB BuildVendorRequest(
PUCHAR TransferBuffer,//为驱动程序存放传输数据的内存区;
ULONG TransferBufferLength,//传输的字节数,对应于wLengh;
UCHAR RequestTypeReservedBits,//类别请求字节中的保留位;
UCHAR Request,//具体的请求数值,对应于bReques;
USHORT Value,//对应于wValue
BOOLEAN bIn=FALSE,//TRUE表示输入,数据从设备到主机,FLASE表示输出;
BOOLEAN bShortOk=FALSE,//TRUE表示设备传输的字节数可以少于指定的字节数
PURB Link=NULL,//连接下一个传输的URB,若没有则NULL;
UCHAR Index=0,//索引值,对应于wIndex;
USHORT Function=URB_FUNCTION_VENDOR_DEVICE,//类别请求;
PURB pUrb=NULL);//NULL表示分配一个新的URB,如果已经存在URB,则初始化URB
返回值:指向URB的指针。
PURB BuildClassRequest(
PUCHAR TransferBuffer,//或KMemory
& TransferBufferMDL
ULONG TransferBufferLength,//传输的字节数,对应于wLengh;
UCHAR RequestTypeReservedBits,//类别请求字节中的保留位;
UCHAR Request,//具体的请求数值,对应于bRequest;
USHORT Value,//对应于wValue
BOOLEAN bIn=FALSE,//TRUE表示输入,数据从设备到主机,FLASE表示输出;
BOOLEAN bShortOk=FALSE,//TRUE表示设备传输的字节数可以少于指定的字节数
PURB Link=NULL,//连接下一个传输的URB,若没有则NULL;
UCHAR Index=0,//索引值,对应于wIndex;
USHORT Function=URB_FUNCTION_CLASS_DEVICE,//类别请求;
PURB pUrb=NULL);//NULL表示分配一个新的URB,如果已经存在URB,则初始化URB
这里对这些参数不在进行详细分析了,主要讲一下容易出错的地方,bIn,这个参数很重要,TRUE表示输入,是以主机为准的,及从设备到主机,bShortOk也重要,表示设备传输的字节数是否可以小于指定的字节数。将厂商请求构建好了后,可以通过SubmitUrb提交给硬件了。NTSTATUS
SubmitUrb(
PURB pUrb, //
创建的URB
PIO_COMPLETION_ROUTINE CompletionRoutine=NULL,//完成例程,默认为NULL。
PVOID CompletionContext=NULL,//传递给完成例程的环境变量参数,默认为NULL
ULONG mSecTimeOut=0);//为对于同步调用的定时参数,当超过定时参数后,返回//STATUS_TIMEOUT
以上两个函数对于usb设备,usb接口和usb pipe都适用,我一般在devicecontrol函数中使用这两个函数,呵呵,可能是我个人的习惯。
对于usb管道(端点),还有其它的函数可以用来构建urb,根据传输方式的不同,有以下几个函数可以调用,对于bulk传输来说,调用的函数就是BuildBulkTransfer,对于中断传输来说调用的函数就是BuildInterruptTransfer,对于控制传输来说就是BuildControlTransfer。而对于同步传输就是BuildIsochronousTransfer。下面就结合usbbulk的例子以BuildBulkTransfer进行讲解,其它函数的函数原型都类似:
BuildBulkTransfer(
KMemory&Mdl, //或PVOID Buffer,如果是直接方式读写就是Mdl,如果是缓冲方式读写就是Buffer
ULONG Length,//传输的字节数
BOOLEAN bIn,//传输的方向
PURB Link,//链接到下一个urb,如果没有=NULL
BOOLEAN bShortOk,//传输字节数是否可以少于指定的字节数
PURB pUrb);//如果指向一个存在的urb,则初始化该urb,如果为NULL则分配一个新的urb
dwBytesSent = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength;这个函数的作用是获得实际传输的长度,然后I.Information()
= dwBytesSent;这个意思就是赋值给I.Information(), return I.PnpComplete(this, status, IO_NO_INCREMENT);就是完成这个IRP,这样在WriteFile的参数lpNumberOfBytesWritten的值就等于dwBytesSent了。分析完:Write函数,让我们看看Read函数,当应用程序使用ReadFile时,会产生一个IRP,和驱动程序对应起来,在驱动程序就会调用Read函数了,源码如下:
NTSTATUS UsbBulkDevice::Read(KIrp I)
{
NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
Tracer << "UsbBulkDevice::Read() Entry\n";
// Declare a memory object
KMemory Mem(I.Mdl());
ULONG dwTotalSize = I.ReadSize(CURRENT);
ULONG dwMaxSize = m_Pipe0.MaximumTransferSize();
// If the total requested read size is greater than the Maximum Transfer
// Size for the Pipe, request to read only the Maximum Transfer Size since
// the bus driver will fail an URB with a TransferBufferLength of greater
// than the Maximum Transfer Size.
if ( dwTotalSize > dwMaxSize )
{
ASSERT(dwMaxSize);
dwTotalSize = dwMaxSize;
}
ULONG dwBytesRead = 0;
// Create an URB to do actual Bulk read from Pipe0
PURB pUrb = m_Pipe0.BuildBulkTransfer(
Mem, // Where is data coming from?
dwTotalSize, // How much data to read?
TRUE, // direction (TRUE = IN)
NULL // Link to next URB
);
if ( pUrb != NULL)
{
// Submit the URB to our USB device, synchronously - say less is OK
pUrb->UrbBulkOrInterruptTransfer.TransferFlags =
(USBD_TRANSFER_DIRECTION_IN | USBD_SHORT_TRANSFER_OK);
/*
TransferFlags标记的作用就是设定传输方向和bShortOk,如果默认的话,bShortOk=FALSE的,所以这里设定它。
*/
status = m_Pipe0.SubmitUrb(pUrb, NULL, NULL);
if ( NT_SUCCESS(status) )
{
dwBytesRead = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength;
if (dwBytesRead > 0)
Tracer << "Read() got " << dwTotalSize << " bytes from USB\n";
}
delete pUrb;
}
I.Information() = dwBytesRead;
return I.PnpComplete(this, status, IO_NO_INCREMENT);
}
Read函数就不多进行讲解了。呵呵,里面的内容都和Write函数类似的,一口气将driver stdudio 3.2开发驱动程序讲解完了。休息下,继续讲解ddk驱动开发。
相关文章推荐
- 详细解析windows usb驱动和linux usb驱动的相似和差异(二)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(三)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(四)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(五)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(七)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(一)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(八)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(九)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(十)
- 详细解析windows usb驱动和linux usb驱动的相似和差异(五)
- Linux USB gadget设备驱动解析(2)---驱动调试
- 针对windows编程-linux驱动编程-usb编程的号文章--推荐
- linux gspca usb摄像头驱动添加对新型号的详细移植步骤
- linux gspca usb摄像头驱动添加对新型号的详细移植步骤
- Linux应用程序访问字符设备驱动详细过程解析
- Linux应用程序访问字符设备驱动详细过程解析
- linux usb 驱动详解 二
- Linux下的硬件驱动——USB设备(上)
- Linux操作系统内核启动参数详细解析
- linux 系统调用与标准库调用的区别详细解析