Interface de solicitação OID síncrona no NDIS 6.80

Os drivers de rede do Windows usam solicitações OID para enviar mensagens de controle para baixo na pilha de associação do NDIS. Os drivers de protocolo, como TCPIP ou vSwitch, dependem de dezenas de OIDs para configurar cada recurso do driver NIC subjacente. Antes Windows 10, versão 1709, as solicitações OID eram enviadas de duas maneiras: Regular e Direct.

Este tópico apresenta um terceiro estilo de chamada OID: síncrono. Uma chamada síncrona deve ser de baixa latência, sem bloqueio, escalonável e confiável. A interface de solicitação OID síncrona está disponível a partir do NDIS 6.80, que está incluído no Windows 10, versão 1709 e posterior.

Comparação com solicitações OID regulares e diretas

Com solicitações OID síncronas, o conteúdo da chamada (o próprio OID) é exatamente o mesmo que com solicitações OID regulares e diretas. A única diferença está na chamada em si. Portanto, o que é o mesmo em todos os três tipos de OIDs; apenas o modo como é diferente.

A tabela a seguir descreve as diferenças entre OIDs Regulares, OIDs Diretos e OIDs Síncronos.

Atributo Regular OID OID direto OID síncrono
Carga útil NDIS_OID_REQUEST NDIS_OID_REQUEST NDIS_OID_REQUEST
Tipos de OID Estatísticas, Consulta, Conjunto, Método Estatísticas, Consulta, Conjunto, Método Estatísticas, Consulta, Conjunto, Método
Pode ser emitido por Protocolos, filtros Protocolos, filtros Protocolos, filtros
Pode ser concluído por Miniportos, filtros Miniportos, filtros Miniportos, filtros
Os filtros podem modificar Sim Yes Sim
O NDIS aloca memória Para cada filtro (clone de OID) Para cada filtro (clone de OID) Somente se um número extraordinariamente grande de filtros (contexto de chamada)
Pode pendente Sim Sim Não
Pode bloquear Sim Não Não
IRQL == PASSIVO <= DISPATCH <= DISPATCH
Serializado pelo NDIS Sim Não Não
Os filtros são invocados Recursivamente Recursivamente Iterativamente
Filtros clonam o OID Sim Yes Não

Filtragem

Como os outros dois tipos de chamadas OID, os drivers de filtro têm controle total sobre a solicitação OID em uma chamada síncrona. Os drivers de filtro podem observar, interceptar, modificar e emitir OIDs síncronos. No entanto, para eficiência, a mecânica de um OID síncrono é um pouco diferente.

Passagem, interceptação e origem

Conceitualmente, todas as solicitações de OID são emitidas de um driver superior e são concluídas por um driver inferior. Ao longo do caminho, a solicitação OID pode passar por qualquer número de drivers de filtro.

No caso mais comum, um driver de protocolo emite uma solicitação OID e todos os filtros simplesmente passam a solicitação OID para baixo, não modificados. A figura a seguir ilustra esse cenário comum.

O caminho OID típico originou-se de um protocolo.

No entanto, qualquer módulo de filtro tem permissão para interceptar a solicitação OID e concluí-la. Nesse caso, a solicitação não passa para drivers inferiores, conforme mostrado no diagrama a seguir.

Caminho OID típico originado de um protocolo e interceptado por um filtro.

Em alguns casos, um módulo de filtro pode decidir originar sua própria solicitação OID. Essa solicitação começa no nível do módulo de filtro e percorre apenas drivers inferiores, como mostra o diagrama a seguir.

Caminho OID típico originado de um filtro.

Todas as solicitações de OID têm esse fluxo básico: um driver mais alto (um driver de protocolo ou de filtro) emite uma solicitação e um driver inferior (um driver de miniporto ou filtro) a conclui.

Como as solicitações OID regulares e diretas funcionam

Solicitações OID regulares ou diretas são enviadas recursivamente. O diagrama a seguir mostra a sequência de chamadas de função. Observe que a sequência em si é muito semelhante à sequência descrita nos diagramas da seção anterior, mas é organizada para mostrar a natureza recursiva das solicitações.

Sequência de chamadas de função para solicitações OID regulares e diretas.

Se houver filtros suficientes instalados, o NDIS será forçado a alocar uma nova pilha de threads para continuar se aprofundando.

O NDIS considera uma estrutura NDIS_OID_REQUEST válida para apenas um único salto ao longo da pilha. Se um driver de filtro quiser passar a solicitação para o próximo driver inferior (que é o caso da grande maioria dos OIDs), o driver de filtro deverá inserir várias dezenas de linhas de código clichê para clonar a solicitação OID. Este texto clichê tem vários problemas:

  1. Ele força uma alocação de memória a clonar o OID. Atingir o pool de memória é lento e torna impossível garantir o progresso da solicitação OID.
  2. O design da estrutura OID deve permanecer o mesmo ao longo do tempo porque todos os drivers de filtro codificam em código a mecânica de copiar o conteúdo de um NDIS_OID_REQUEST para outro.
  3. Exigir tanta clichê obscurece o que o filtro está realmente fazendo.

O modelo de filtragem para solicitações OID síncronas

O modelo de filtragem para solicitações OID síncronas aproveita a natureza síncrona da chamada para resolver os problemas discutidos na seção anterior.

Manipuladores De problemas e completos

Ao contrário das solicitações OID Regulares e Diretas, há dois ganchos de filtro para solicitações OID síncronas: um manipulador de problemas e um manipulador Complete. Um driver de filtro pode registrar nenhum, um ou ambos os ganchos.

As chamadas de problema são invocadas para cada driver de filtro, começando da parte superior da pilha até a parte inferior da pilha. Qualquer chamada de problema de filtro pode impedir que o OID continue para baixo e concluir o OID com alguns status código. Se nenhum filtro decidir interceptar o OID, o OID atingirá o driver NIC, que deve concluir o OID de forma síncrona.

Depois que um OID é concluído, as chamadas Completas são invocadas para cada driver de filtro, começando de onde quer que o OID tenha sido concluído, até a parte superior da pilha. Uma chamada Completa pode inspecionar ou modificar a solicitação OID e inspecionar ou modificar a conclusão do OID status código.

O diagrama a seguir ilustra o caso típico, em que um protocolo emite uma solicitação OID síncrona e os filtros não interceptam a solicitação.

Sequência de chamadas de função para solicitações OID síncronas. Sequência de chamadas de função

Observe que o modelo de chamada para OIDs síncronos é iterativo. Isso mantém o uso da pilha limitado por uma constante, eliminando a necessidade de expandir a pilha.

Se um driver de filtro interceptar um OID síncrono em seu manipulador De problemas, o OID não será dado a filtros inferiores ou ao driver NIC. No entanto, manipuladores completos para filtros mais altos ainda são invocados, conforme mostrado no diagrama a seguir:

para solicitações OID síncronas com uma interceptação por um filtro.Sequência de chamadas de função Sequência de chamadas de função

Alocações mínimas de memória

Solicitações OID regulares e diretas exigem um driver de filtro para clonar um NDIS_OID_REQUEST. Por outro lado, solicitações OID síncronas não têm permissão para serem clonadas. A vantagem desse design é que os OIDs síncronos têm menor latência – a solicitação OID não é clonada repetidamente, pois percorre a pilha de filtros – e há menos oportunidades de falha.

No entanto, isso gera um novo problema. Se o OID não puder ser clonado, onde um driver de filtro armazenará seu estado por solicitação? Por exemplo, suponha que um driver de filtro traduza um OID para outro. No caminho para baixo da pilha, o filtro precisa salvar o OID antigo. No caminho de backup da pilha, o filtro precisa restaurar o OID antigo.

Para resolver esse problema, o NDIS aloca um slot do tamanho do ponteiro para cada driver de filtro para cada solicitação OID síncrona em andamento. O NDIS preserva esse slot na chamada do manipulador De problemas de um filtro para o manipulador Complete. Isso permite que o manipulador de problemas salve o estado que é consumido posteriormente pelo manipulador Complete. O snippet de código a seguir mostra um exemplo.

NDIS_STATUS
MyFilterSynchronousOidRequest(
  _In_ NDIS_HANDLE FilterModuleContext,
  _Inout_ NDIS_OID_REQUEST *OidRequest,
  _Outptr_result_maybenull_ PVOID *CallContext)
{
  if ( . . . should intercept this OID . . . )
  {
    // preserve the original buffer in the CallContext
    *CallContext = OidRequest->DATA.SET_INFORMATION.InformationBuffer;

    // replace the buffer with a new one
    OidRequest->DATA.SET_INFORMATION.InformationBuffer = . . . something . . .;
  }

  return NDIS_STATUS_SUCCESS;
}

VOID
MyFilterSynchronousOidRequestComplete(
  _In_ NDIS_HANDLE FilterModuleContext,
  _Inout_ NDIS_OID_REQUEST *OidRequest,
  _Inout_ NDIS_STATUS *Status,
  _In_ PVOID CallContext)
{
  // if the context is not null, we must have replaced the buffer.
  if (CallContext != null)
  {
    // Copy the data from the miniport back into the protocol’s original buffer.
    RtlCopyMemory(CallContext, OidRequest->DATA.SET_INFORMATION.InformationBuffer,...);
     
    // restore the original buffer into the OID request
    OidRequest->DATA.SET_INFORMATION.InformationBuffer = CallContext;
  }
}

O NDIS salva um PVOID por filtro por chamada. O NDIS aloca heuristicamente um número razoável de slots na pilha, de modo que não haja alocações de pool zero no caso comum. Isso geralmente não é mais do que sete filtros. Se o usuário configurar um caso patológico, o NDIS fará fallback para uma alocação de pool.

Texto clichê reduzido

Considere o texto clichê em Exemplo de texto clichê para lidar com solicitações OID regulares ou diretas. Esse código é o custo da entrada apenas para registrar um manipulador OID. Se você quiser emitir seus próprios OIDs, precisará adicionar mais uma dúzia de linhas de texto clichê. Com os OIDs síncronos, não há necessidade da complexidade adicional de lidar com a conclusão assíncrona. Portanto, você pode cortar grande parte desse clichê.

Aqui está um manipulador de problemas mínimo com OIDs síncronos:

NDIS_STATUS
MyFilterSynchronousOidRequest(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  PVOID *CallContext)
{
  return NDIS_STATUS_SUCCESS;
}

Se você quiser interceptar ou modificar um OID específico, poderá fazer isso adicionando apenas algumas linhas de código. O manipulador complete mínimo é ainda mais simples:

VOID
MyFilterSynchronousOidRequestComplete(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  NDIS_STATUS *Status,
  PVOID CallContext)
{
  return;
}

Da mesma forma, um driver de filtro pode emitir uma nova solicitação OID síncrona própria usando apenas uma linha de código:

status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);

Por outro lado, um driver de filtro que precisa emitir um OID Regular ou Direto deve configurar um manipulador de conclusão assíncrona e implementar algum código para distinguir suas próprias conclusões de OID das conclusões de OIDs que acabou de clonar. Um exemplo desse texto clichê é mostrado em Exemplo de texto clichê para emitir uma solicitação OID regular.

Interoperabilidade

Embora os estilos de chamada Regular, Direto e Síncrono usem as mesmas estruturas de dados, os pipelines não vão para o mesmo manipulador no miniporto. Além disso, alguns OIDs não podem ser usados em alguns dos pipelines. Por exemplo, OID_PNP_SET_POWER requer sincronização cuidadosa e muitas vezes força o miniporto a fazer chamadas de bloqueio. Isso dificulta o tratamento em um retorno de chamada de OID Direto e impede seu uso em um retorno de chamada OID síncrono.

Portanto, assim como acontece com solicitações OID diretas, chamadas OID síncronas só podem ser usadas com um subconjunto de OIDs. No Windows 10, versão 1709, há suporte apenas para o OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES OID usado no RSSv2 (Receive Side Scaling Version 2) no caminho OID síncrono.

Implementando solicitações OID síncronas

Para obter mais informações sobre como implementar a interface de solicitação OID síncrona em drivers, consulte os seguintes tópicos: