Partilhar via


Sincronizando o código de interrupção

Os seguintes fatores complicam o código do driver que manipula interrupções de hardware em sistemas multiprocessadores:

  • Cada vez que um dispositivo interrompe, ele fornece informações específicas de interrupção que são voláteis porque podem ser substituídas na próxima vez que o dispositivo interromper.

  • Os dispositivos interrompem a níveis de IRQL relativamente altos, e as suas rotinas de serviço de interrupção (ISRs) podem interromper a execução de outros códigos de driver.

  • Para interrupções DIRQL, o ISR deve ser executado no DIRQL enquanto mantém um bloqueio de rotação fornecido pelo driver, para que o ISR possa evitar interrupções adicionais enquanto salva informações voláteis. O DIRQL impede a interrupção pelo processador atual e o bloqueio de rotação impede a interrupção por outro processador.

  • O ISR deve ser executado rapidamente porque o dispositivo não pode interromper enquanto o ISR está em execução. Longos tempos de execução ISR podem retardar o sistema ou possivelmente causar perda de dados.

  • Tanto o ISR quanto a rotina de chamada de procedimento diferido (DPC) normalmente devem acessar uma área de armazenamento na qual o ISR armazena os dados voláteis do dispositivo. Essas rotinas devem ser sincronizadas entre si para que não acessem a área de armazenamento ao mesmo tempo.

Devido a todos esses fatores, você deve usar as seguintes regras ao escrever o código do driver que manipula interrupções:

  • Somente a função callback EvtInterruptIsr acessa dados de interrupção voláteis, como registradores de dispositivo que contêm informações de interrupção.

    A função de retorno de chamada EvtInterruptIsr deve mover os dados voláteis para um buffer de dados de interrupção definido pelo driver que a função de retorno de chamada EvtInterruptDpc do driver, ou a função de retorno de chamada EvtInterruptWorkItem, ou múltiplas funções de retorno de chamada EvtDpcFunc possam acessar.

    Se o driver fornecer EvtInterruptDpc ou EvtInterruptWorkItem como funções de retorno de chamada para os seus objetos de interrupção, o melhor lugar para armazenar dados de interrupção é o espaço de contexto do objeto de interrupção. As funções de retorno de chamada do objeto de interrupção podem acessar o espaço de contexto do objeto usando o identificador de objeto que recebem.

    Se o driver fornecer várias funções de retorno de chamada do EvtDpcFunc para cada função de retorno de chamada do EvtInterruptIsr, poder-se-á armazenar dados de interrupção no espaço de contexto de cada objeto DPC.

  • Todo o código do driver que acessa o buffer de dados de interrupção deve ser sincronizado para que apenas uma rotina acesse os dados de cada vez.

    Para objetos de interrupção DIRQL, a função de retorno de chamada EvtInterruptIsr acede a este buffer de dados em IRQL = DIRQL enquanto mantém o spin lock fornecido pelo driver do objeto de interrupção. Portanto, todas as rotinas que acessam o buffer também devem ser executadas no DIRQL enquanto mantêm o bloqueio de rotação. (Normalmente, a função de retorno de chamada EvtInterruptDpc ou EvtDpcFunc da interrupção é a única outra rotina que deve acessar o buffer.)

    Todas as rotinas que acessam um buffer de dados de interrupção, exceto a função de retorno de chamada EvtInterruptIsr, devem seguir um destes procedimentos:

    Ambas as técnicas permitem que a EvtInterruptDpc ou a função EvtDpcFunc acesse dados de interrupção no DIRQL enquanto mantém o spin lock da interrupção. O DIRQL impede a interrupção pelo processador atual e o bloqueio de rotação impede a interrupção por outro processador.

    Se o seu dispositivo suportar vários vetores ou mensagens de interrupção e se pretender sincronizar o tratamento destas interrupções pelo controlador, pode atribuir um único bloqueio de rotação a vários objetos de interrupção DIRQL. A estrutura determina o DIRQL mais alto do conjunto de interrupções, e sempre adquire o bloqueio de rotação nesse DIRQL para que o código sincronizado não possa ser interrompido por quaisquer vetores de interrupção ou mensagens no conjunto.

    Para objetos de interrupção de nível passivo, o framework adquire o bloqueio de interrupção de nível passivo antes de chamar a função de retorno de chamada do do driver EvtInterruptIsr no IRQL = PASSIVE_LEVEL. Como resultado, todas as rotinas que acessam o buffer devem adquirir o bloqueio de interrupção ou sincronizar internamente o acesso ao buffer. Normalmente, a função de retorno de chamada EvtInterruptWorkItem da interrupção é a única outra rotina que acede ao buffer. Para obter informações sobre como adquirir o bloqueio de interrupção a partir de uma função de retorno de chamada EvtInterruptWorkItem, consulte a secção Observações dessa página.

    Você também pode sincronizar a forma como o driver lida com vários vetores de interrupção, ao associar um único bloqueio de espera a vários objetos de interrupção de nível passivo.

  • Se parte do seu código que manipula interrupções DIRQL deve ser executado em IRQL = PASSIVE_LEVEL, a sua função callback EvtInterruptDpc ou função callback EvtDpcFunc pode criar um ou mais itens de trabalho para que o código seja executado como funções callback EvtWorkItem.

    Como alternativa, nas versões 1.11 e posteriores do KMDF, o driver pode solicitar um item de trabalho de interrupção chamando WdfInterruptQueueWorkItemForIsr. (Lembre-se de que a função de retorno de chamada do driver EvtInterruptIsr pode chamar WdfInterruptQueueWorkItemForIsr ou WdfInterruptQueueDpcForIsr, mas não ambos.)

  • Se for importante sincronizar as funções de retorno de chamada do driver EvtInterruptDpc e EvtDpcFunc entre si e com outras funções de retorno de chamada associadas a um dispositivo, o driver pode definir o membro AutomaticSerialization para TRUE na estrutura de interrupção WDF_INTERRUPT_CONFIG e na estrutura do objeto DPC WDF_DPC_CONFIG. Como alternativa, o driver pode usar bloqueios de rotação do framework . (Definir o membro AutomaticSerialization como TRUE não sincroniza uma função de retorno de chamada EvtInterruptIsr com outras funções de retorno de chamada. Use WdfInterruptSynchronize ou WdfInterruptAcquireLock para sincronizar uma função de retorno de chamada EvtInterruptIsr, conforme descrito anteriormente neste tópico.)

Para obter mais informações sobre como sincronizar rotinas de controlador, consulte Técnicas de Sincronização para Controladores Framework-Based.