Статья
Версия для печати
Обсудить на форуме
Как писать драйвера (часть 4)


В прошлый раз мы заготовили списки необходимых функций, зарегистрировали их, а сегодня рассмотрим их поподробнее.

Группа минипорт.
Функции этой группы занимаются обработкой потока данных и событий, происходящих в верхнем уровне драйвера, и вызываемых обращением к NDIS TCP/IP стека.

Если посмотреть на схемы из второй части, то видно, что в нижней части находятся функции протокола, а в верхней минипорта. Почему? Каждый драйвер выступает в двух ипостасях. Общаясь с верхним уровнем драйверов он становиться для него драйвером минипорта, а для нижнего уровня, драйвером протокола.

Список функций минипорт:

MPInitialize - инициализация группы.

MPSend
MPSendPackets
MPTransferData
MPReturnPacket

Функции отвечающие за пересылку пакетов данных.

MPQueryInformation
MPSetInformation
MPQueryPNPCapbilities
MPIsSendOID
MPProcessSetPowerOid

Функции работы с питанием состоянием системы и системой PlugNPlay. Сказать особенно нечего. Стандартное отслеживание внутренних событий системы прописанное Microsoft.

MPHalt - отработка выгрузки и де регистрации драйвера при аварийном.
MPReset - как написано у Microsoft - мы не должны ничего делать :)

Работа с системой - необходимость отрабатывать события важные для сервиса корректно.

MPSetMiniportSecondary
MPPromoteSecondary
MPBundleSearchAndSetSecondary

В системе может быть не один адаптер и соответственно не один драйвер к которому приходится обращаться. В случае такого используются эти функции.

В нашем случае основными функциями из этой группы - являются функции пересылки данных. Все остальные мы можем не рассматривать, их назначение - обслуживать правильно системные связи, вся основная часть которых написана Microsoft.

MPSend
Основная функция вызываемая всегда, при прохождении данных. По правилам работы с данными в NDIS необходимо написать (что в примере и сделано) re-wrap пакету.

Для этого сначала пакет надо захватить, перекопировать содержимое пакета в свою память и переслать его далее, после чего освободить пакет. Вот как будет это выглядеть в коде:

Код:
   PADAPT pAdapt = (PADAPT)MiniportAdapterContext;
   //Контекст адаптера приходящий в качестве параметра. Присвоим его своему типизированному указателю.
   NDIS_STATUS Status;

   //Возвращаемый статус.
   PNDIS_PACKET MyPacket;

   //Наш пакет - пока только указатель.
   PRSVD Rsvd;

   //Резервный указатель.
   PVOID MediaSpecificInfo = NULL;

   //Тип адаптера с которым будем работать.
   ULONG MediaSpecificInfoSize = 0;

   //Размер типа адаптера.
   ASSERT (pAdapt->pSecondaryAdapt);
   pAdapt = pAdapt->pSecondaryAdapt;

   //Проверка наличия второго сетевого адаптера. Вверху я говорил, что его наличие необходимо предусматривать.
   if (IsIMDeviceStateOn (pAdapt) == FALSE)
   {
      return NDIS_STATUS_FAILURE;
   }

   //Проверка наличия и состояния.
   NdisAllocatePacket(&Status, &MyPacket, pAdapt->SendPacketPoolHandle);

   //Выделение места под размер полученного пакета (Pool) данных.
   if (Status == NDIS_STATUS_SUCCESS)
   {
      PNDIS_PACKET_EXTENSION Old, New;
      Rsvd = (PRSVD)(MyPacket->ProtocolReserved);
      Rsvd->OriginalPkt = Packet;
      MyPacket->Private.Flags = Flags;
      MyPacket->Private.Head = Packet->Private.Head;
      MyPacket->Private.Tail = Packet->Private.Tail;

      //Собственно копирование всей служебной информации
      NdisSetPacketFlags(MyPacket, NDIS_FLAGS_DONT_LOOPBACK);


      //Установка ее в наш внутренний буфер.

      NdisMoveMemory(NDIS_OOB_DATA_FROM_PACKET(MyPacket), NDIS_OOB_DATA_FROM_PACKET(Packet),
                     sizeof(NDIS_PACKET_OOB_DATA));

      //Перенос данных в сам пакет.
      NdisIMCopySendPerPacketInfo(MyPacket, Packet);

      //Копирование служебных данных по пересылке пакета.
      //Копирование данных о типе адаптера, куда пересылать данные.

      NDIS_GET_PACKET_MEDIA_SPECIFIC_INFO(Packet, &MediaSpecificInfo, &MediaSpecificInfoSize);
      if (MediaSpecificInfo || MediaSpecificInfoSize)
      {
         NDIS_SET_PACKET_MEDIA_SPECIFIC_INFO(MyPacket, MediaSpecificInfo, MediaSpecificInfoSize);
      }

      //Собственно пересылка имеющихся данных в NDIS, что вызовет нормальное
      //прохождение пакета далее по цепочке драйверов.
      NdisSend(&Status, pAdapt->BindingHandle, MyPacket);
      if (Status != NDIS_STATUS_PENDING)
      {
         NdisIMCopySendCompletePerPacketInfo (Packet, MyPacket);
         NdisFreePacket(MyPacket);
      }

      //Если нет задержки на отсылке освободить пакет.
   }
   else
   {
      //Это говорит об отсутствии пакета в системе - ничего не надо делать.
   }
   return(Status);

Возврат значения SUCCESS или код ошибки.

Стоит остановиться еще на одном моменте. Когда система ответила, что посылка данных закончена кодом задержки пакета - NDIS_STATUS_PENDING.

В этом случае мы пакет не освободим, блокировав таким образом всю систему NDIS по пересылке данных. Такое случается при посылке по медленной сети большого числа пакетов.

Как нам освободить пакет? Система предусматривает такое и при освобождении ресурса после освобождению пакета вызовет функцию из группы протокола PtSendComplete. Смена на протокольную группу вызвана тем, что система получит сообщение от низлежащего драйвера, что вызывает обращение именно к этой группе.

В данной функции, код которой идет следом мы увидим параметры в которых нам передадут контекст операции Send и адаптера, в результате чего мы получим возможность вызвать функцию NdisMSendComplete после NdisDprFreePacket и освободить NDIS для передачи нам следующих пакетов.
Код:
   {
      PADAPT pAdapt =(PADAPT)ProtocolBindingContext;
      PNDIS_PACKET Pkt;
      PRSVD Rsvd;
      pAdapt = pAdapt->pPrimaryAdapt;

      Rsvd =(PRSVD)(Packet->ProtocolReserved);
      Pkt = Rsvd->OriginalPkt;

      NdisIMCopySendCompletePerPacketInfo (Pkt, Packet);

      NdisDprFreePacket(Packet);

      NdisMSendComplete(pAdapt->MiniportHandle, Pkt, Status);

Группа протокол.
Протокольные функции несколько отличаются от функций минипорта, но имеют схожую функциональную нагрузку, обратную функциям минипорта по направлению передачи пакетов.

PtOpenAdapterComplete
PtCloseAdapterComplete
PtBindAdapter
PtUnbindAdapter

Функции работы с адаптером - в нашем случае адаптером является драйвер модема или сетевой карты. Адаптер соответственно при запуске надо открыть и при окончании работы - закрыть. При обращении к нему привязать (bind) адаптер, захватить ресурс. В конце работы - освободить.

PtResetComplete

Абсолютно пустая функция - она должна быть но мы сюда не приходим.

PtRequestComplete

Функция вызываемая из PtPnPNetEventSetPower.

PtStatus

Функция отвечающая за проверку статуса нижестоящего адаптера, вернее статуса взаимодействия с ним нашего уровня.

PtStatusComplete

Завершение в случае невозможности быстрого ответа, примерно как и в случае MPSend.

PtSendComplete

Описана в секции минипорт

PtTransferDataComplete
PtReceive
PtReceiveComplete
PtReceivePacket

Функции работы с данными - аналогичны функциям минипорта. Одну из функций мы уже разбирали. Функции остальных - чуть позже.

PtUnload
Функция заведующая выгрузкой драйвера при выходе.

PtPNPHandler
PtPnPNetEventReconfigure
PtPnPNetEventSetPower

Работа с PnP.

Вновь обратим внимание на симметричную функцию PtReceive.
Код:
   PADAPT pAdapt =(PADAPT)ProtocolBindingContext;

   //Контекст адаптера
   PNDIS_PACKET MyPacket, Packet;  //Пакеты.
   NDIS_STATUS Status = NDIS_STATUS_SUCCESS;

   //Статус
   if(!pAdapt->MiniportHandle)
   {
      Status = NDIS_STATUS_FAILURE;
   }
   else
      do
      {
         //Эта часть работает при наличии второго адаптера :)
         if(pAdapt->isSecondary)
         {
             DBGPRINT("PASSTHRU GETTING RECIEVES ON SECONDARYn");
             ASSERT(0);
         }

         //Забираем указатель на пакет в NDIS.
         Packet = NdisGetReceivedPacket(pAdapt->BindingHandle, MacReceiveContext);

         //Если пакета нет то мы выходим иначе продолжаем как и в случае с отправкой.
         if(Packet != NULL)
         {
            //Резервируем пакет для себя.
            NdisDprAllocatePacket(&Status, &MyPacket, pAdapt->RecvPacketPoolHandle);
            if(Status == NDIS_STATUS_SUCCESS)
            {
                //Копируем данные, как служебные, так и сами данные передаваемые наверх.
                MyPacket->Private.Head = Packet->Private.Head;
                MyPacket->Private.Tail = Packet->Private.Tail;
                NDIS_SET_ORIGINAL_PACKET(MyPacket, NDIS_GET_ORIGINAL_PACKET(Packet));
                NDIS_SET_PACKET_HEADER_SIZE(MyPacket, HeaderBufferSize);
                NdisGetPacketFlags(MyPacket) = NdisGetPacketFlags(Packet);
                NDIS_SET_PACKET_STATUS(MyPacket, NDIS_STATUS_RESOURCES);

                //В этом случае мы не посылаем пакет как при отправке, а просто указываем
                //NDIS что MyPacket готов к передаче наверх.
                NdisMIndicateReceivePacket(pAdapt->MiniportHandle, &MyPacket, 1);
                ASSERT(NDIS_GET_PACKET_STATUS(MyPacket) == NDIS_STATUS_RESOURCES);
                //Освобождение пакета при нормальной передаче.
                NdisDprFreePacket(MyPacket);
                break;
            }
         }
      }

Выбор типа адаптера внизу дает возможность применить для индикации готовности к передаче в случае ошибки стандартного сообщения NDIS. Это происходит в случае специфичных сетей и обрабатывается функциями связанными с этими типами.

Код:
   pAdapt->IndicateRcvComplete = TRUE;
   switch(pAdapt->Medium)
   {
   case NdisMedium802_3:

      NdisMEthIndicateReceive(pAdapt->MiniportHandle, MacReceiveContext, HeaderBuffer,
                              HeaderBufferSize, LookAheadBuffer,  LookAheadBufferSize, PacketSize);
      break;

   case NdisMedium802_5:
      NdisMTrIndicateReceive(pAdapt->MiniportHandle, MacReceiveContext, HeaderBuffer,
                             HeaderBufferSize, LookAheadBuffer, LookAheadBufferSize, PacketSize);
      break;

   case NdisMediumFddi:
      NdisMFddiIndicateReceive(pAdapt->MiniportHandle, MacReceiveContext, HeaderBuffer,
                               HeaderBufferSize, LookAheadBuffer, LookAheadBufferSize, PacketSize);
      break;

   default:
      //Это в случае если тип сети неизвестен.
      ASSERT(0);
      break;
   }

   } while(FALSE);

   return Status;

Выход с статусом завершения.

Некоторое пояснение.
Когда тип адаптера и сети специфичен и отличается от стандарта LAN, нам нужно сообщить о пакете и его отправке соответствующей части сервиса NDIS. Именно в связи с этим и появляется выбор типа адаптера. При получении возможно наличие одновременного запроса на прием пакета с разных адаптеров.

В случае с операцией Send этого не происходит, так как NDIS сама по контексту определяет к какому адаптеру предназначен пакет.

Оставшиеся функции аналогичны по назначению с группой функций минипорта.
Версия для печати
Обсудить на форуме