Sincronizando código de interrupção
Os seguintes fatores complicam o código do driver que lida com interrupções de hardware em sistemas multiprocessadores:
Sempre 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 em IRQLs relativamente altos e suas ISRs (rotinas de serviço de interrupção) podem interromper a execução de outro código de driver.
Para interrupções dirql, o ISR deve ser executado em 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. Tempos longos de execução do ISR podem retardar o sistema ou possivelmente causar perda de dados.
Tanto o ISR quanto a rotina de DPC (chamada de procedimento adiado) 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 elas não acessem a área de armazenamento ao mesmo tempo.
Devido a todos esses fatores, você deve usar as seguintes regras ao escrever código de driver que lida com interrupções:
Somente a função de retorno de chamada EvtInterruptIsr acessa dados de interrupção voláteis, como registros 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, a função de retorno de chamada EvtInterruptWorkItem ou várias funções de retorno de chamada EvtDpcFunc podem acessar.
Se o driver fornecer funções de retorno de chamada EvtInterruptDpc ou EvtInterruptWorkItem para seus objetos de interrupção, o melhor lugar para armazenar dados de interrupção será 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 eles recebem.
Se o driver fornecer várias funções de retorno de chamada EvtDpcFunc para cada função de retorno de chamada EvtInterruptIsr , você poderá armazenar dados de interrupção no espaço de contexto de cada objeto DPC.
Todo o código de 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 acessa esse buffer de dados em IRQL = DIRQL enquanto mantém o bloqueio de rotação fornecido pelo driver do objeto de interrupção. Portanto, todas as rotinas que acessam o buffer também devem ser executadas em 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 fazer um dos seguintes procedimentos:
- Chame WdfInterruptSynchronize para agendar uma função de retorno de chamada EvtInterruptSynchronize que acessa o buffer de dados de interrupção.
- Coloque o código que acessa o buffer de dados de interrupção entre chamadas para WdfInterruptAcquireLock e WdfInterruptReleaseLock.
Ambas as técnicas permitem que a função EvtInterruptDpc ou EvtDpcFunc acesse dados de interrupção no DIRQL enquanto mantém o bloqueio de rotação 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 dispositivo der suporte a vários vetores ou mensagens de interrupção e se você quiser sincronizar o tratamento do driver dessas interrupções, você poderá 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 nenhum vetor de interrupção ou mensagens no conjunto.
Para objetos de interrupção de nível passivo, a estrutura adquire o bloqueio de interrupção de nível passivo antes de chamar a função de retorno de chamada EvtInterruptIsr do driver em 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 acessa o buffer. Para obter informações sobre como adquirir o bloqueio de interrupção de uma função de retorno de chamada EvtInterruptWorkItem , consulte a seção Comentários dessa página.
Você também pode sincronizar o tratamento do driver de vários vetores de interrupção atribuindo um único bloqueio de espera a vários objetos de interrupção de nível passivo.
Se alguns de seus códigos que lidam com interrupções DIRQL precisarem ser executados em IRQL = PASSIVE_LEVEL, sua função de retorno de chamada EvtInterruptDpc ou EvtDpcFunc poderá criar um ou mais itens de trabalho para que o código seja executado como funções de retorno de chamada 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 EvtInterruptIsr de um driver pode chamar WdfInterruptQueueWorkItemForIsr ou WdfInterruptQueueDpcForIsr, mas não ambos.)
Se for importante sincronizar as funções de retorno de chamada EvtInterruptDpc e EvtDpcFunc do driver entre si e com outras funções de retorno de chamada associadas a um dispositivo, o driver poderá definir o membro AutomaticSerialization como TRUE na estrutura WDF_INTERRUPT_CONFIG da interrupção e na estrutura WDF_DPC_CONFIG do objeto DPC. Como alternativa, o driver pode usar bloqueios de rotação de estrutura. (Definir o membro de 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 driver, consulte Técnicas de sincronização para drivers de Framework-Based.