reactos操作系统实现(92)

DirverEntry函数,可以看到下面这句:

#054 DriverObject->DriverExtension->AddDevice = i8042AddDevice;

这里是设置了驱动程序的AddDevice函数指针,它是指向函数i8042AddDevicePnP管理器将为每个硬件调用一次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

调用函数i8042PacketWriteIRP发送到设备。

#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管理器发送过来的端口操作命令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值