Windows驱动之中断资源

文章目录

  • Windows驱动之中断资源
    • 1. 配置中断
    • 2. IoConnectInterrupt
      • 2.1 KeInitializeInterrupt
      • 2.2 KeConnectInterrupt

Windows驱动之中断资源

我们知道外设如果想要通知CPU的读取数据的话,都是通过中断来实现的,例如我们使用键盘的时候,键盘按键就会导致中断通知CPU,然后CPU通过中断处理程序读取键盘的状态来判断按键信息。

其实对于Windows驱动来说,中断也是一种资源。我们在IRP_MJ_START中,可以提取中断资源。

1. 配置中断

中断资源的提取和配置过程如下:

VOID OnStartDevice(...)
{
	ULONG vector;             // interrupt vector
	KIRQL irql;               // interrupt level
	KINTERRUPT_MODE mode;     // latching mode
	KAFFINITY affinity;       // processor affinity
	BOOLEAN irqshare;         // shared interrupt
	for (ULONG i = 0; i < nres; ++i, ++resource)
	{
		switch (resource->Type)
		{
		case CmResourceTypeInterrupt:
			irql = (KIRQL)resource->u.Interrupt.Level;
			vector = resource->u.Interrupt.Vector;
			affinity = resource->u.Interrupt.Affinity;
			mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED) ? Latched : LevelSensitive;
			irqshare = resource->ShareDisposition == CmResourceShareShared;
			break;
		}
		status = IoConnectInterrupt(&pdx->InterruptObject,
			(PKSERVICE_ROUTINE)OnInterrupt,
			(PVOID)pdx,
			NULL,
			vector,
			irql,
			irql,
			mode,
			irqshare,
			affinity,
			FALSE);
	}
}
BOOLEAN OnInterrupt(...)
{
    IoRequestDpc(pdx->DeviceObject, NULL, (PVOID) pdx);
}

VOID DpcForIsr(...)
{
    PIRP Irp = fdo->CurrentIrp;
    IoStartNextPacket(fdo, TRUE);
    IoCompleteRequest(Irp, <boost value>);
}

说明

  1. Level参数指定这个中断的IRQL。
  2. Vector参数指定这个中断的硬件中断矢量。
  3. Affinity是一个位掩码,它指出该中断应由哪个CPU来处理。
  4. 我们需要告诉IoConnectInterrupt我们的中断是边缘触发方式还是水平触发方式。如果资源标志FlagsCM_RESOURCE_INTERRUPT_LATCHED,则我们的中断是边缘触发方式,否则是水平触发方式。

在这段代码的最后是IoConnectInterrupt调用,在这里我们使用了从中断资源描述符中获得的各个值。

  1. 第一个参数(&pdx->InterruptObject)指出在哪里存放连接操作的结果,即一个指向内核中断对象的指针,该对象描述你的中断。
  2. 第二个参数(OnInterrupt)指向你的中断服务例程(ISR);稍后我将讨论ISR。
  3. 第三个参数(pdx)是每次当你的设备中断时作为参数传递给ISR的一个上下文值。
  4. 第五和第六个参数(vectorirql)为被连接的中断指定中断矢量号和中断请求级。
  5. 第八个参数(mode)应该为LatchedLevelSensitive,它指出中断是边缘触发方式还是水平触发方式。
  6. 第九个参数如果为TRUE,则表明这个中断可以被其它设备共享,反之为FALSE
  7. 第十个参数(affinity)是该中断的处理器亲合掩码。
  8. 第十一个和最后一个参数指出当中断发生时是否需要操作系统保存浮点上下文。

这里有两个IoConnectInterrupt的参数还没有描述。当设备使用多个中断时,这两个参数就变得特别重要。在这种情况下,你需要为这些中断创建自旋锁,并调用KeInitializeSpinLock函数初始化这个自旋锁。在连接中断前,你还需要计算出这些中断中的最大IRQL。在每次调用IoConnectInterrupt时,你应该在第四个参数中指定这个自旋锁的地址,以及在第七个参数中指定最大IRQL。第七个参数指定的IRQL用于同步多个中断,所以你应该使用这些中断中最大的IRQL,这样你一次就只需要响应一个中断。然而,如果设备只使用一个中断,你就不必指定自旋锁(因为I/O管理器自动为你分配一个),并且中断的同步级别将与中断的IRQL相同。

为了防止中断服务例程调用占用时间太长导致其他中断丢失,一般来说,中断服务例程仅仅设置一个DPC(IoRequestDpc),我们在DPC例程中才完成所有的操作。

2. IoConnectInterrupt

IoConnectInterrupt这个函数是用来注册一个中断并设置中断对象的回调函数的,这里我们来看一下这个函数的基本流程:

NTSTATUS
IoConnectInterrupt(
    OUT PKINTERRUPT *InterruptObject,
    IN PKSERVICE_ROUTINE ServiceRoutine,
    IN PVOID ServiceContext,
    IN PKSPIN_LOCK SpinLock OPTIONAL,
    IN ULONG Vector,
    IN KIRQL Irql,
    IN KIRQL SynchronizeIrql,
    IN KINTERRUPT_MODE InterruptMode,
    IN BOOLEAN ShareVector,
    IN KAFFINITY ProcessorEnableMask,
    IN BOOLEAN FloatingSave
    )
{
    CCHAR count;
    BOOLEAN builtinUsed;
    PKINTERRUPT interruptObject;
    KAFFINITY processorMask;
    NTSTATUS status;
    PIO_INTERRUPT_STRUCTURE interruptStructure;
    PKSPIN_LOCK spinLock;

    *InterruptObject = (PKINTERRUPT) NULL;
    status = STATUS_SUCCESS;

    //计算中断向量应该对应多少个CPU
    processorMask = ProcessorEnableMask & KeActiveProcessors;
    count = 0;

    while (processorMask) {
        if (processorMask & 1) {
            count++;
        }
        processorMask >>= 1;
    }

    //创建中断管理的数据结构
    if (count) {

        interruptStructure = ExAllocatePoolWithTag( NonPagedPool,
                                                    ((count - 1) * sizeof( KINTERRUPT )) +
                                                    sizeof( IO_INTERRUPT_STRUCTURE ),
                                                    'nioI' );
        if (interruptStructure == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    } else {
        return STATUS_INVALID_PARAMETER;
    }

    //是否提供了自旋锁
    if (ARGUMENT_PRESENT( SpinLock )) {
        spinLock = SpinLock;
    } else {
        spinLock = &interruptStructure->SpinLock;
    }

    //初始化中断管理结构
    *InterruptObject = &interruptStructure->InterruptObject;

    interruptObject = (PKINTERRUPT) (interruptStructure + 1);
    builtinUsed = FALSE;
    processorMask = ProcessorEnableMask & KeActiveProcessors;

    RtlZeroMemory( interruptStructure, sizeof( IO_INTERRUPT_STRUCTURE ) );

    //针对每一个CPU,来注册中断向量
    for (count = 0; processorMask; count++, processorMask >>= 1) {

        if (processorMask & 1) {
            //初始化中断向量
            KeInitializeInterrupt( builtinUsed ?
                                   interruptObject :
                                   &interruptStructure->InterruptObject,
                                   ServiceRoutine,
                                   ServiceContext,
                                   spinLock,
                                   Vector,
                                   Irql,
                                   SynchronizeIrql,
                                   InterruptMode,
                                   ShareVector,
                                   count,
                                   FloatingSave );

            //连接中断向量
            if (!KeConnectInterrupt( builtinUsed ?
                                     interruptObject :
                                     &interruptStructure->InterruptObject )) {
                if (builtinUsed) {
                    IoDisconnectInterrupt( &interruptStructure->InterruptObject );
                } else {
                    ExFreePool( interruptStructure );
                }
                status = STATUS_INVALID_PARAMETER;
                break;
            }

            //设置中断向量指针
            if (builtinUsed) {
                interruptStructure->InterruptArray[count] = interruptObject++;

            } else {

                builtinUsed = TRUE;
            }
        }
    }

    if (!NT_SUCCESS( status )) {
        *InterruptObject = (PKINTERRUPT) NULL;
    }

    return status;
}

上面的流程比较简单,主要是三个:

  1. 创建一个PIO_INTERRUPT_STRUCTURE来管理中断信息。
  2. 调用KeInitializeInterrupt来初始化一个中断向量。
  3. 调用KeConnectInterrupt来连接一个中断向量。

其中中断管理的数据结构如下:

typedef struct _IO_INTERRUPT_STRUCTURE {
    KINTERRUPT InterruptObject;
    PKINTERRUPT InterruptArray[MAXIMUM_PROCESSORS];
    KSPIN_LOCK SpinLock;
} IO_INTERRUPT_STRUCTURE, *PIO_INTERRUPT_STRUCTURE;

typedef struct _KINTERRUPT {
    CSHORT Type;
    CSHORT Size;
    LIST_ENTRY InterruptListEntry;
    PKSERVICE_ROUTINE ServiceRoutine;
    PVOID ServiceContext;
    KSPIN_LOCK SpinLock;
    ULONG TickCount;
    PKSPIN_LOCK ActualLock;
    PKINTERRUPT_ROUTINE DispatchAddress;
    ULONG Vector;
    KIRQL Irql;
    KIRQL SynchronizeIrql;
    BOOLEAN FloatingSave;
    BOOLEAN Connected;
    CCHAR Number;
    BOOLEAN ShareVector;
    KINTERRUPT_MODE Mode;
    ULONG ServiceCount;
    ULONG DispatchCount;

#if defined(_AMD64_)

    PKTRAP_FRAME TrapFrame;
    PVOID Reserved;
    ULONG DispatchCode[DISPATCH_LENGTH];

#else

    ULONG DispatchCode[DISPATCH_LENGTH];

#endif

} KINTERRUPT;

2.1 KeInitializeInterrupt

KeInitializeInterrupt这个函数用来初始化一个中断向量,流程很简单,就是初始化一个KINTERRUPT结构,代码如下:

VOID
KeInitializeInterrupt (
    __out PKINTERRUPT Interrupt,
    __in PKSERVICE_ROUTINE ServiceRoutine,
    __in_opt PVOID ServiceContext,
    __out_opt PKSPIN_LOCK SpinLock,
    __in ULONG Vector,
    __in KIRQL Irql,
    __in KIRQL SynchronizeIrql,
    __in KINTERRUPT_MODE InterruptMode,
    __in BOOLEAN ShareVector,
    __in CCHAR ProcessorNumber,
    __in BOOLEAN FloatingSave
    )
{

    LONG Index;
    PULONG pl;
    PULONG NormalDispatchCode;

    Interrupt->Type = InterruptObject;
    Interrupt->Size = sizeof(KINTERRUPT);

    Interrupt->ServiceRoutine = ServiceRoutine;
    Interrupt->ServiceContext = ServiceContext;

    if (ARGUMENT_PRESENT(SpinLock)) {
        Interrupt->ActualLock = SpinLock;
    } else {
        KeInitializeSpinLock (&Interrupt->SpinLock);
        Interrupt->ActualLock = &Interrupt->SpinLock;
    }

    Interrupt->Vector = Vector;
    Interrupt->Irql = Irql;
    Interrupt->SynchronizeIrql = SynchronizeIrql;
    Interrupt->Mode = InterruptMode;
    Interrupt->ShareVector = ShareVector;
    Interrupt->Number = ProcessorNumber;
    Interrupt->FloatingSave = FloatingSave;

    Interrupt->TickCount = (ULONG)-1;
    Interrupt->DispatchCount = (ULONG)-1;

    NormalDispatchCode = &(Interrupt->DispatchCode[0]);

    pl = NormalDispatchCode;

    for (Index = 0; Index < NORMAL_DISPATCH_LENGTH; Index += 1) {
        *NormalDispatchCode++ = KiInterruptTemplate[Index];
    }

    pl = (PULONG)((PUCHAR)pl + ((PUCHAR)&KiInterruptTemplateObject -
                                (PUCHAR)KiInterruptTemplate) -4); 
    *pl = (ULONG)Interrupt;

    KeSweepDcache(FALSE);
    Interrupt->Connected = FALSE;
    return;
}

2.2 KeConnectInterrupt

KeConnectInterrupt这个函数是向CPU注册一个中断向量,这个函数逻辑很复杂,但是处理的流程比较简单,先设置线程到指定的中断向量注册的处理器,然后调用KiSetHandlerAddressToIDT设置中断向量信息。

#define KiSetHandlerAddressToIDT(Vector, HandlerAddress) {\
    UCHAR IDTEntry = HalVectorToIDTEntry(Vector); \
    ULONG Ha = (ULONG)HandlerAddress; \
    KeGetPcr()->IDT[IDTEntry].ExtendedOffset = HIGHWORD(Ha); \
    KeGetPcr()->IDT[IDTEntry].Offset = LOWWORD(Ha); \
}

当中断到来的时候,就会调用_KiInterruptDispatch来分发中断信息,这个函数中就会调用我们中断回调例程。

你可能感兴趣的:(Windows驱动开发)