在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, "Unknown FDO type %u/n", 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管理器发送过来的端口操作命令。