reactos操作系统实现(92)
在DirverEntry函数,可以看到下面这句: #054 DriverObject->DriverExtension->AddDevice = i8042AddDevice; 这里是设置了驱动程序的AddDevice函数指针,它是指向函数i8042AddDevice。PnP管理器将为每个硬件调用一次AddDevice函数,如下: 下面开始调用即插即用的函数AddDevice来添加设备。 #023 DPRINT("Calling %wZ->AddDevice(%wZ)/n", #024 &DriverObject->DriverName, #025 &DeviceNode->InstancePath); #026 Status = DriverObject->DriverExtension->AddDevice( #027 DriverObject,DeviceNode->PhysicalDeviceObject); #028 if (!NT_SUCCESS(Status)) #029 { #030 IopDeviceNodeSetFlag(DeviceNode,DNF_DISABLED); #031 return Status; #032 } #033 上面第26行,就是PnP管理器调用AddDevice函数,可见给这个函数传送了两个参数,一个是驱动程序对象,一个是物理设备对象。由于DirverEntry只能每次加载驱动程序时调用一次,但一个驱动程序可以创建多个设备,那么就需要一个函数来创建设备,这个函数就是AddDevice函数。现在来分析键盘添加设备的函数,如下: #001 NTSTATUS NTAPI #002 i8042AddDevice( #003 IN PDRIVER_OBJECT DriverObject, #004 IN PDEVICE_OBJECT Pdo) #005 { #006 PI8042_DRIVER_EXTENSION DriverExtension; #007 PFDO_DEVICE_EXTENSION DeviceExtension = NULL; #008 PDEVICE_OBJECT Fdo = NULL; #009 ULONG DeviceExtensionSize; #010 NTSTATUS Status; #011 #012 TRACE_(I8042PRT,"i8042AddDevice(%p %p)/n",DriverObject,Pdo); #013
获取前面分配的驱动程序扩展对象。 #014 DriverExtension = (PI8042_DRIVER_EXTENSION)IoGetDriverObjectExtension(DriverObject,DriverObject); #015
判断输入的物理设备对象是否合法,如果不合法就返回。 #016 if (Pdo == NULL) #017 { #018 /* We're getting a NULL Pdo at the first call as #019 * we are a legacy driver. Ignore it */ #020 return STATUS_SUCCESS; #021 } #022 #023 /* Create new device object. As we don't know if the device would be a keyboard #024 * or a mouse,we have to allocate the biggest device extension. */
分配一个设备内存,由于这个驱动程序支持两种输入类型,一种是鼠标,一种是键盘,因此只能分配最大内存的设备对象。 #025 DeviceExtensionSize = MAX(sizeof(I8042_KEYBOARD_EXTENSION),sizeof(I8042_MOUSE_EXTENSION));
调用函数IoCreateDevice来创建物理设备对象。 #026 Status = IoCreateDevice( #027 DriverObject, #028 DeviceExtensionSize, #029 NULL, #030 Pdo->DeviceType, #031 FILE_DEVICE_SECURE_OPEN, #032 TRUE, #033 &Fdo); #034 if (!NT_SUCCESS(Status)) #035 { #036 WARN_(I8042PRT,"IoCreateDevice() failed with status 0x%08lx/n",Status); #037 goto cleanup; #038 } #039
设置物理设备对象的属性。 #040 DeviceExtension = (PFDO_DEVICE_EXTENSION)Fdo->DeviceExtension; #041 RtlZeroMemory(DeviceExtension,DeviceExtensionSize); #042 DeviceExtension->Type = Unknown; #043 DeviceExtension->Fdo = Fdo; #044 DeviceExtension->Pdo = Pdo; #045 DeviceExtension->PortDeviceExtension = &DriverExtension->Port;
调用函数IoAttachDeviceToDeviceStackSafe把新设备放到设备栈上。 #046 Status = IoAttachDeviceToDeviceStackSafe(Fdo,Pdo,&DeviceExtension->LowerDevice); #047 if (!NT_SUCCESS(Status)) #048 { #049 WARN_(I8042PRT,"IoAttachDeviceToDeviceStackSafe() failed with status 0x%08lx/n",Status); #050 goto cleanup; #051 } #052
把设备添加设备队列。 #053 ExInterlockedInsertTailList( #054 &DriverExtension->DeviceListHead, #055 &DeviceExtension->ListEntry, #056 &DriverExtension->DeviceListLock); #057
设置物理设备对象已经初始化。 #058 Fdo->Flags &= ~DO_DEVICE_INITIALIZING; #059 return STATUS_SUCCESS; #060
不成功时清除动作。 #061 cleanup: #062 if (DeviceExtension && DeviceExtension->LowerDevice) #063 IoDetachDevice(DeviceExtension->LowerDevice); #064 if (Fdo) #065 IoDeleteDevice(Fdo); #066 return Status; #067 } #068
通过上面的函数分析,可以看到键盘驱动程序是怎么样实现AddDevice函数的,大体的步骤如下: 1) 调用函数IoCreateDevice创建设备对象,并建立一个私有的设备扩展对象。 2) 调用函数IoAttachDeviceToDeviceStackSafe,把新设备对象放到堆栈上。 3) 把新设备添加到设备队列。 4) 初始化设备对象的Flag成员。
下面来分析i8042StartIo函数的实现,它主要处理标准的IRP排队方式。 #001 static VOID NTAPI #002 i8042StartIo( #003 IN PDEVICE_OBJECT DeviceObject, #004 IN PIRP Irp) #005 { #006 PFDO_DEVICE_EXTENSION DeviceExtension; #007
获取驱动程序扩展。 #008 DeviceExtension = (PFDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
根据驱动程序创建的设备类型来处理。 #009 switch (DeviceExtension->Type) #010 {
这里是处理键盘的IRP。 #011 case Keyboard: #012 i8042KbdStartIo(DeviceObject,Irp); #013 break; #014 default: #015 ERR_(I8042PRT,"Unknown FDO type %u/n",DeviceExtension->Type); #016 ASSERT(FALSE); #017 break; #018 } #019 }
这个函数调用是在内核的I/O管理器里调用,主要在这几个函数里调用: IoStartPacket IopStartNextPacket IopStartNextPacketByKey 接着来分析函数i8042KbdStartIo的实现,如下: #001 VOID NTAPI #002 i8042KbdStartIo( #003 IN PDEVICE_OBJECT DeviceObject, #004 IN PIRP Irp) #005 { #006 PIO_STACK_LOCATION Stack; #007 PI8042_KEYBOARD_EXTENSION DeviceExtension; #008 PPORT_DEVICE_EXTENSION PortDeviceExtension; #009
获取当前IRP栈。 #010 Stack = IoGetCurrentIrpStackLocation(Irp);
获取设备扩展结构。 #011 DeviceExtension = (PI8042_KEYBOARD_EXTENSION)DeviceObject->DeviceExtension;
获取端口描述结构。 #012 PortDeviceExtension = DeviceExtension->Common.PortDeviceExtension; #013
根据IoControlCode来处理。 #014 switch (Stack->Parameters.DeviceIoControl.IoControlCode) #015 {
设置键盘的指示灯。 #016 case IOCTL_KEYBOARD_SET_INDICATORS: #017 { #018 TRACE_(I8042PRT,"IOCTL_KEYBOARD_SET_INDICATORS/n"); #019 INFO_(I8042PRT,"Leds: {%s%s%s }/n", #020 DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_CAPS_LOCK_ON ? " CAPSLOCK" : "", #021 DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_NUM_LOCK_ON ? " NUMLOCK" : "", #022 DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_SCROLL_LOCK_ON ? " SCROLLLOCK" : ""); #023
设置要写到键盘端口的指示灯数据。 #024 PortDeviceExtension->PacketBuffer[0] = KBD_CMD_SET_LEDS; #025 PortDeviceExtension->PacketBuffer[1] = 0; #026 if (DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_CAPS_LOCK_ON) #027 PortDeviceExtension->PacketBuffer[1] |= KBD_LED_CAPS; #028 #029 if (DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_NUM_LOCK_ON) #030 PortDeviceExtension->PacketBuffer[1] |= KBD_LED_NUM; #031 #032 if (DeviceExtension->KeyboardIndicators.LedFlags & KEYBOARD_SCROLL_LOCK_ON) #033 PortDeviceExtension->PacketBuffer[1] |= KBD_LED_SCROLL; #034
调用函数i8042StartPacket来处理指示灯的IRP。 #035 i8042StartPacket( #036 PortDeviceExtension, #037 &DeviceExtension->Common, #038 PortDeviceExtension->PacketBuffer, #039 2, #040 Irp); #041 break; #042 } #043 default: #044 { #045 ERR_(I8042PRT,"Unknown ioctl code 0x%lx/n", #046 Stack->Parameters.DeviceIoControl.IoControlCode); #047 ASSERT(FALSE); #048 } #049 } #050 }
接着来分析函数i8042StartPacket的实现,这个函数必须在DIRQL级别下调用,如下: #001 NTSTATUS #002 i8042StartPacket( #003 IN PPORT_DEVICE_EXTENSION DeviceExtension, #004 IN PFDO_DEVICE_EXTENSION FdoDeviceExtension, #005 IN PUCHAR Bytes, #006 IN ULONG ByteCount, #007 IN PIRP Irp) #008 { #009 KIRQL Irql; #010 NTSTATUS Status; #011
获取设备请求级别。 #012 Irql = KeAcquireInterruptSpinLock(DeviceExtension->HighestDIRQLInterrupt); #013
如果设备不空闲,就直接返回。 #014 if (DeviceExtension->Packet.State != Idle) #015 { #016 Status = STATUS_DEVICE_BUSY; #017 goto done; #018 } #019
根据设备类型来决定分配访问端口。 #020 switch (FdoDeviceExtension->Type) #021 {
键盘是从端口偏移0开始。 #022 case Keyboard: DeviceExtension->PacketPort = 0; break;
鼠标是从端口偏移CTRL_WRITE_MOUSE开始。 #023 case Mouse: DeviceExtension->PacketPort = CTRL_WRITE_MOUSE; break; #024 default: #025 ERR_(I8042PRT,FdoDeviceExtension->Type); #026 ASSERT(FALSE); #027 Status = STATUS_INTERNAL_ERROR; #028 goto done; #029 } #030
设置将要写到端里的字节,以及相关的数据描述。 #031 DeviceExtension->Packet.Bytes = Bytes; #032 DeviceExtension->Packet.CurrentByte = 0; #033 DeviceExtension->Packet.ByteCount = ByteCount;
标记设备正在发送中。 #034 DeviceExtension->Packet.State = SendingBytes; #035 DeviceExtension->PacketResult = Status = STATUS_PENDING; #036 DeviceExtension->CurrentIrp = Irp; #037 DeviceExtension->CurrentIrpDevice = FdoDeviceExtension->Fdo; #038
调用函数i8042PacketWrite把IRP发送到设备。 #039 if (!i8042PacketWrite(DeviceExtension)) #040 { #041 Status = STATUS_IO_TIMEOUT; #042 DeviceExtension->Packet.State = Idle; #043 DeviceExtension->PacketResult = STATUS_ABANDONED; #044 goto done; #045 } #046 #047 DeviceExtension->Packet.CurrentByte++; #048 #049 done: #050 KeReleaseInterruptSpinLock(DeviceExtension->HighestDIRQLInterrupt,Irql); #051
如果状态不是阻塞状态,就立即设置这个IRP已经完成。 #052 if (Status != STATUS_PENDING) #053 { #054 DeviceExtension->CurrentIrp = NULL; #055 DeviceExtension->CurrentIrpDevice = NULL; #056 Irp->IoStatus.Status = Status; #057 IoCompleteRequest(Irp,IO_NO_INCREMENT); #058 } #059 return Status; #060 }
接着来分析函数i8042PacketWrite,如下: #001 static BOOLEAN #002 i8042PacketWrite( #003 IN PPORT_DEVICE_EXTENSION DeviceExtension) #004 {
获取键盘或鼠标的输出数据的端口。 #005 UCHAR Port = DeviceExtension->PacketPort; #006
如果端口存在是一个控制端口命令,否则就是数据端口命令。 #007 if (Port) #008 { #009 if (!i8042Write(DeviceExtension, #010 DeviceExtension->ControlPort, #011 Port)) #012 { #013 /* something is really wrong! */ #014 WARN_(I8042PRT,"Failed to send packet byte!/n"); #015 return FALSE; #016 } #017 } #018
这里把数据写到数据端口命令。 #019 return i8042Write(DeviceExtension, #020 DeviceExtension->DataPort, #021 DeviceExtension->Packet.Bytes[DeviceExtension->Packet.CurrentByte]); #022 }
接着再来查看函数i8042Write的实现,如下: #001 BOOLEAN #002 i8042Write( #003 IN PPORT_DEVICE_EXTENSION DeviceExtension, #004 IN PUCHAR addr, #005 IN UCHAR data) #006 { #007 ULONG Counter; #008 #009 ASSERT(addr); #010 ASSERT(DeviceExtension->ControlPort != NULL); #011
Counter指定要放弃和超时操作之前轮询硬件 (以轮询模式) 的时间标准数。 请考虑增加此值,如果该驱动程序无法初始化或正常工作,并且事件查看器中的系统日志包含 i8042prt 源中的以下信息:"操作正在超时 (出是通过注册表配置的时间)" #012 Counter = DeviceExtension->Settings.PollingIterations; #013
等待键盘设备空闲。 #014 while ((KBD_IBF & READ_PORT_UCHAR(DeviceExtension->ControlPort)) && #015 (Counter--)) #016 { #017 KeStallExecutionProcessor(50); #018 } #019
如果没有超时,就可以把键盘操作数据发送给键盘设备。 #020 if (Counter) #021 {
调用CPU操作IO的指令,第一个参数是端口地址,第二个端口的数据。 #022 WRITE_PORT_UCHAR(addr,data); #023 INFO_(I8042PRT,"Sent 0x%x to port %p/n",data,addr); #024 return TRUE; #025 } #026 return FALSE; #027 } 到这里,就已经把i8042StartIo处理的IRP完全发送到键盘设备了,然后键盘设备就会响应这些指令。由整个过程可见,DriverStartIo函数其实就是立即处理IO管理器发送过来的端口操作命令。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |