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

你可能感兴趣的:(react)