Processando IRPs em um driver de Lowest-Level

Os drivers físicos de nível mais baixo têm determinadas rotinas padrão que os drivers de nível superior não precisam. O conjunto de rotinas padrão para drivers de nível mais baixo também varia de acordo com os seguintes critérios:

  • A natureza do dispositivo que cada driver controla

  • Se o driver configura seus objetos de dispositivo para E/S direta ou em buffer

  • O design do driver individual

Para ilustrar as funções das rotinas de driver padrão, a figura a seguir mostra o caminho que um IRP de exemplo pode seguir, pois é processado por um driver de dispositivo de armazenamento em massa de nível mais baixo. O driver na figura tem as seguintes características:

  • O dispositivo gera interrupções no final de cada operação de E/S, portanto, esse driver tem rotinas ISR e DpcForIsr .

  • O driver tem uma rotina StartIo , em vez de configurar filas internas para IRPs e gerenciar seu próprio enfileiramento.

  • O driver usa o DMA do sistema, portanto, ele define os Sinalizadores de seus objetos de dispositivo para E/S direta e tem uma rotina AdapterControl .

diagrama ilustrando um caminho de irp por meio de rotinas de driver de nível mais baixo.

Como mostra essa figura, o gerente de E/S cria um IRP e o envia para a rotina de expedição do driver para o código de função principal especificado. Supondo que o código da função seja IRP_MJ_READ ou IRP_MJ_WRITE, a rotina de expedição é DDDispatchReadWrite.

Chamando IoGetCurrentIrpStackLocation

Qualquer rotina de driver que exija parâmetros IRP deve chamar IoGetCurrentIrpStackLocation para obter o local da pilha de E/S do driver. Essas rotinas incluem rotinas de expedição que lidam com mais de um código de função de E/S principal (IRP_MJ_*XXX), lidam com uma função que dá suporte a funções secundárias (IRP_MN_XXX) ou lidam com solicitações de controle de E/S do dispositivo (*IRP_MJ_DEVICE_CONTROL e/ou IRP_MJ_INTERNAL_DEVICE_CONTROL), juntamente com todas as outras rotinas de driver que processam um IRP.

O local da pilha de E/S desse driver é o mais baixo, com um número indefinido de locais de pilha de E/S de drivers de nível superior mostrados sombreados. Para simplificar, as chamadas para as rotinas IoGetCurrentIrpStackLocation das rotinas DispatchReadWrite, StartIo, AdapterControl e DpcForIsr não são mostradas na figura anterior.

Chamando IoMarkIrpPending e IoStartPacket

O driver de exemplo não conclui o IRP em sua rotina de expedição, mas processa o IRP em sua rotina StartIo . Antes de fazer isso, a rotina de expedição chama IoMarkIrpPending para indicar que o IRP ainda não foi concluído. Em seguida, ele chama IoStartPacket para enfileirar o IRP para processamento adicional pela rotina StartIo do driver. A rotina de expedição também retorna o valor NTSTATUS STATUS_PENDING.

A figura a seguir ilustra a chamada para IoStartPacket.

diagrama ilustrando uma chamada para iostartpacket.

Se o driver estiver ocupado processando outro IRP no dispositivo, IoStartPacket inserirá o IRP na fila de dispositivos associada ao objeto do dispositivo. Opcionalmente, o driver pode fornecer um valor key como um parâmetro para IoStartPacket para impor uma ordem determinada pelo driver em IRPs na fila do dispositivo.

Se o driver não estiver ocupado e a fila do dispositivo estiver vazia, o gerenciador de E/S chamará imediatamente sua rotina StartIo , passando o IRP de entrada.

Para dispositivos de armazenamento em massa, o driver de nível mais baixo não precisa fornecer uma rotina Cancelar quando chama IoStartPacket por dois motivos:

  1. Um sistema de arquivos em camadas sobre esse driver normalmente manipula o cancelamento de solicitações de E/S de arquivo.

  2. Drivers de dispositivo de armazenamento em massa processam IRPs rapidamente.

Normalmente, o driver de nível mais alto em uma cadeia de drivers em camadas lida com o cancelamento de IRPs.

Chamando AllocateAdapterChannel e MapTransfer

Supondo que a rotina StartIo , mostrada na figura que ilustra um caminho IRP por meio de rotinas de driver de nível mais baixo, determine que a solicitação de transferência pode ser feita por uma única operação de DMA, a rotina StartIo chama AllocateAdapterChannel com o ponto de entrada da rotina AdapterControl do driver e o IRP.

Quando o controlador de DMA do sistema está disponível, o gerenciador de E/S chama a rotina AdapterControl do driver para configurar a operação de transferência. A rotina AdapterControl chama MapTransfer para configurar o controlador de DMA do sistema. Em seguida, o driver programa seu dispositivo para a operação DMA e retorna. (Para obter mais informações sobre como usar DMA e objetos de adaptador, consulte Técnicas de entrada/saída.)

Chamando IoRequestDpc do ISR do Driver

Quando o dispositivo interrompe para indicar que sua operação de transferência foi concluída, o ISR do driver impede que o dispositivo gere interrupções e chama IoRequestDpc, conforme mostrado na figura que ilustra um caminho IRP por meio de rotinas de driver de nível mais baixo.

Essa chamada enfileira a rotina DpcForIsr do driver para concluir o máximo possível da operação de transferência em uma IRQL (prioridade de hardware) mais baixa.

Chamando IoStartNextPacket e IoCompleteRequest

Quando a rotina DpcForIsr terminar de processar a transferência, ela chamará IoStartNextPacket imediatamente para que a rotina StartIo do driver seja chamada com o próximo IRP na fila do dispositivo, se algum estiver na fila. A rotina DpcForIsr também define o bloco de status de E/S do IRP concluído e, em seguida, chama IoCompleteRequest para o IRP.

A figura a seguir ilustra as chamadas desse driver para IoStartNextPacket e IoCompleteRequest.

chamando iostartnextpacket e iocompleterequest.

Os drivers devem chamar IoStartNextPacket ou IoStartNextPacketByKey para iniciar a próxima operação de E/S solicitada o mais rápido possível, preferencialmente antes de chamar IoCompleteRequest.

Se algum IRPs estiver na fila para o dispositivo, IoStartNextPacket chamará KeRemoveDeviceQueue para remover o próximo IRP da fila. O gerente de E/S então chama a rotina StartIo do driver, passando o IRP desemqueado. Se nenhum IRPs estiver atualmente na fila do dispositivo, IoStartNextPacket apenas retornará ao chamador.

Definindo o bloco de status de E/S em um IRP

Cada driver de nível mais baixo deve definir o bloco de status de E/S do IRP antes de chamar IoCompleteRequest. (Na figura anterior, a segunda área sombreada indica o bloco status.) O bloco de status de E/S fornece informações para drivers de nível superior e, por fim, para o solicitante original da operação de E/S. Qualquer driver de nível superior em camadas acima do driver na figura anterior pode ter configurado uma rotina IoCompletion que lê o bloco de status de E/S definido por esse driver. Os drivers de nível superior geralmente não modificam o bloco de status de E/S em um IRP que foi concluído por um driver de dispositivo, a menos que o driver de nível superior esteja repetindo o IRP, caso em que reinicializa o bloco de status de E/S.

Cada driver de nível superior que conclui um IRP sem enviá-lo para o próximo driver inferior também deve definir o bloco de status de E/S nesse IRP antes de chamar IoCompleteRequest. Para uma boa taxa de transferência geral de E/S, um driver de nível superior deve marcar os parâmetros em seu próprio local de pilha de E/S de cada IRP e, se os parâmetros forem inválidos, deverá definir o bloco de E/S status e concluir a solicitação em si. Sempre que possível, um driver deve evitar passar uma solicitação inválida para drivers inferiores na cadeia.

Supondo que a operação de transferência na figura anterior seja bem-sucedida, a rotina DpcForIsr, mostrada na figura que ilustra um caminho IRP por meio de rotinas de driver de nível mais baixo, define STATUS_SUCCESS em Status e o número de bytes transferidos em Informações para o bloco de status de E/S do IRP.

Muitas das rotinas de driver padrão também retornam valores do tipo NTSTATUS. Para obter mais informações sobre constantes NTSTATUS como STATUS_SUCCESS, consulte Erros de log.