Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

642 lines
24 KiB

  1. /*
  2. (C) Copyright 1999
  3. All rights reserved.
  4. Portions of this software are:
  5. (C) Copyright 1995 TriplePoint, Inc. -- http://www.TriplePoint.com
  6. License to use this software is granted under the same terms
  7. outlined in the Microsoft Windows Device Driver Development Kit.
  8. (C) Copyright 1992 Microsoft Corp. -- http://www.Microsoft.com
  9. License to use this software is granted under the terms outlined in
  10. the Microsoft Windows Device Driver Development Kit.
  11. @doc INTERNAL Transmit Transmit_c
  12. @module Transmit.c |
  13. This module implements the Miniport packet Transmit routines. This module is
  14. very dependent on the hardware/firmware interface and should be looked at
  15. whenever changes to these interfaces occur.
  16. @head3 Contents |
  17. @index class,mfunc,func,msg,mdata,struct,enum | Transmit_c
  18. @end
  19. */
  20. /* @doc EXTERNAL INTERNAL
  21. @topic 3.3 Sending Packets |
  22. To send packets over the network, a connection-oriented client or call
  23. manger calls NdisCoSendPackets. A connection-oriented client associated with
  24. an MCM also calls NdisCoSendPackets. An MCM, however, never calls
  25. NdisCoSendPackets; instead, since the interface between the call manager and
  26. MCM is internal to the MCM, the MCM passes packets directly to the NIC
  27. without notifying NDIS.
  28. @ex Sending packets through an MCM |
  29. NdisWan NDIS Miniport
  30. |----------------------------------|----------------------------------|
  31. | NdisCoSendPackets | |
  32. |---------------------------------| |
  33. | | MiniportCoSendPackets |
  34. | |---------------------------------|
  35. | | . |
  36. | | . |
  37. | | . |
  38. | | NdisMCoSendComplete |
  39. | |---------------------------------|
  40. | ProtocolCoSendComplete | |
  41. |---------------------------------| |
  42. |----------------------------------|----------------------------------|
  43. @normal
  44. MiniportCoSendPackets should transmit each packet in the array sequentially,
  45. preserving the order of packets in the array. MiniportCoSendPackets can call
  46. NdisQueryPacket to extract information, such as the number of buffer
  47. descriptors chained to the packet and the total size in bytes of the
  48. requested transfer.
  49. MiniportCoSendPackets can call NdisGetFirstBufferFromPacket,
  50. NdisQueryBuffer, or NdisQueryBufferOffset to extract information about
  51. individual buffers containing the data to be transmitted.
  52. MiniportCoSendPackets can retrieve protocol-supplied OOB information
  53. associated with each packet by using the appropriate NDIS_GET_PACKET_XXX
  54. macros. The MiniportCoSendPackets function usually ignores the Status member
  55. of the NDIS_PACKET_OOB_DATA block, but it can set this member to the same
  56. status that it subsequently passes to NdisMCoSendComplete.
  57. Rather than relying on NDIS to queue and resubmit send packets whenever
  58. MiniportCoSendPackets has insufficient resources to transmit the given
  59. packets, a connection-oriented miniport manages its own internal packet
  60. queueing. The miniport must hold incoming send packets in its internal queue
  61. until they can be transmitted over the network. This queue preserves the
  62. protocol-determined ordering of packet descriptors incoming to the
  63. miniport's MiniportCoSendPackets function.
  64. A connection-oriented miniport must complete each incoming send packet with
  65. NdisMCoSendComplete. It cannot call NdisMSendResourcesAvailable. A
  66. connection-oriented miniport should never pass STATUS_INSUFFICIENT_RESOURCES
  67. to NdisMCoSendComplete with a protocol-allocated packet descriptor that was
  68. originally submitted to its MiniportCoSendPackets function.
  69. The call to NdisMCoSendComplete causes NDIS to call the
  70. ProtocolCoSendComplete function of the client that initiated the send
  71. operation. ProtocolCoSendComplete performs any postprocessing necessary for
  72. a completed transmit operation, such as notifying the client that originally
  73. requested the protocol to send data over the network on the VC.
  74. Completion of a send operation usually implies that the underlying NIC
  75. driver actually has transmitted the given packet over the network. However,
  76. the driver of an "intelligent" NIC can consider a send complete as soon as
  77. it downloads the net packet to its NIC.
  78. Although NDIS always submits protocol-supplied packet arrays to the
  79. underlying miniport in the protocol-determined order passed in calls to
  80. NdisCoSendPackets, the underlying driver can complete the given packets in
  81. random order. That is, every bound protocol can rely on NDIS to submit the
  82. packets the protocol passes to NdisCoSendPackets in FIFO order to the
  83. underlying driver, but no protocol can rely on that underlying driver to
  84. call NdisMCoSendComplete with those packets in the same order.
  85. @end
  86. */
  87. #define __FILEID__ TRANSMIT_OBJECT_TYPE
  88. // Unique file ID for error logging
  89. #include "Miniport.h" // Defines all the miniport objects
  90. #if defined(NDIS_LCODE)
  91. # pragma NDIS_LCODE // Windows 9x wants this code locked down!
  92. # pragma NDIS_LDATA
  93. #endif
  94. /* @doc INTERNAL Transmit Transmit_c TransmitAddToQueue
  95. @func
  96. <f TransmitAddToQueue> places the packet on the transmit queue. If the
  97. queue was empty to begin with, TRUE is returned so the caller can kick
  98. start the transmiter.
  99. @rdesc
  100. <f TransmitAddToQueue> returns TRUE if this is the only entry in the
  101. list, FALSE otherwise.
  102. */
  103. DBG_STATIC BOOLEAN TransmitAddToQueue(
  104. IN PMINIPORT_ADAPTER_OBJECT pAdapter, // @parm
  105. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  106. IN PBCHANNEL_OBJECT pBChannel, // @parm
  107. // A pointer to the <t BCHANNEL_OBJECT> returned by <f BChannelCreate>.
  108. IN PNDIS_PACKET pNdisPacket // @parm
  109. // A pointer to the associated NDIS packet structure <t NDIS_PACKET>.
  110. )
  111. {
  112. DBG_FUNC("TransmitAddToQueue")
  113. BOOLEAN ListWasEmpty;
  114. // Note if the list is empty to begin with.
  115. DBG_ENTER(pAdapter);
  116. /*
  117. // Place the packet on the TransmitPendingList.
  118. */
  119. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  120. *((PBCHANNEL_OBJECT *) &pNdisPacket->MiniportReservedEx[8]) = pBChannel;
  121. ListWasEmpty = IsListEmpty(&pAdapter->TransmitPendingList);
  122. InsertTailList(&pAdapter->TransmitPendingList,
  123. GET_QUEUE_FROM_PACKET(pNdisPacket));
  124. NdisReleaseSpinLock(&pAdapter->TransmitLock);
  125. DBG_RETURN(pAdapter, ListWasEmpty);
  126. return (ListWasEmpty);
  127. }
  128. /* @doc INTERNAL Transmit Transmit_c TransmitPacketHandler
  129. @func
  130. <f TransmitPacketHandler> removes an entry from the TransmitPendingList
  131. and places the packet on the appropriate B-channel and starts the
  132. transmission. The packet is then placed on the <t TransmitBusyList> to
  133. await a transmit complete event processed by <f TransmitCompleteHandler>.
  134. @comm
  135. The packets go out in a FIFO order for the entire driver, independent of
  136. the channel on which it goes out. This means that a slow link, or one
  137. that is backed up can hold up all other channels. There is no good way
  138. to get around this because we must to deliver packets in the order they
  139. are given to the Miniport, regardless of the link they are on.
  140. */
  141. DBG_STATIC VOID TransmitPacketHandler(
  142. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  143. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  144. )
  145. {
  146. DBG_FUNC("TransmitPacketHandler")
  147. PNDIS_PACKET pNdisPacket;
  148. // Holds the packet being transmitted.
  149. UINT BytesToSend;
  150. // Tells us how many bytes are to be transmitted.
  151. PBCHANNEL_OBJECT pBChannel;
  152. // A pointer to one of our <t BCHANNEL_OBJECT>'s.
  153. DBG_ENTER(pAdapter);
  154. /*
  155. // MUTEX to protect against async EventHandler access at the same time.
  156. */
  157. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  158. #if DBG
  159. { // Sanity check!
  160. PLIST_ENTRY pList = &pAdapter->TransmitPendingList;
  161. ASSERT(pList->Flink && pList->Flink->Blink == pList);
  162. ASSERT(pList->Blink && pList->Blink->Flink == pList);
  163. }
  164. #endif // DBG
  165. /*
  166. // This might be called when no packets are queued!
  167. */
  168. while (!IsListEmpty(&pAdapter->TransmitPendingList))
  169. {
  170. PLIST_ENTRY pList;
  171. /*
  172. // Remove the packet from the TransmitPendingList.
  173. */
  174. pList = RemoveHeadList(&pAdapter->TransmitPendingList);
  175. pNdisPacket = GET_PACKET_FROM_QUEUE(pList);
  176. /*
  177. // Release MUTEX
  178. */
  179. NdisReleaseSpinLock(&pAdapter->TransmitLock);
  180. /*
  181. // Retrieve the information we saved in the packet reserved fields.
  182. */
  183. pBChannel = *((PBCHANNEL_OBJECT *) &pNdisPacket->MiniportReservedEx[8]);
  184. ASSERT(pBChannel && pBChannel->ObjectType == BCHANNEL_OBJECT_TYPE);
  185. /*
  186. // Make sure the link is still up and can accept transmits.
  187. */
  188. if (pBChannel->CallState != LINECALLSTATE_CONNECTED)
  189. {
  190. /*
  191. // Indicate send complete failure to the NDIS wrapper.
  192. */
  193. DBG_WARNING(pAdapter,("Flushing send on channel #%d (Packet=0x%X)\n",
  194. pBChannel->ObjectID, pNdisPacket));
  195. if (pBChannel->NdisVcHandle)
  196. {
  197. NdisMCoSendComplete(NDIS_STATUS_FAILURE,
  198. pBChannel->NdisVcHandle,
  199. pNdisPacket
  200. );
  201. }
  202. /*
  203. // Reacquire MUTEX
  204. */
  205. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  206. }
  207. else
  208. {
  209. NdisQueryPacket(pNdisPacket,
  210. NULL,
  211. NULL,
  212. NULL,
  213. &BytesToSend);
  214. pAdapter->TotalTxBytes += BytesToSend;
  215. pAdapter->TotalTxPackets++;
  216. /*
  217. // Attempt to place the packet on the NIC for transmission.
  218. */
  219. if (!CardTransmitPacket(pAdapter->pCard, pBChannel, pNdisPacket))
  220. {
  221. /*
  222. // ReQueue the packet on the TransmitPendingList and leave.
  223. // Reacquire MUTEX
  224. */
  225. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  226. InsertTailList(&pAdapter->TransmitPendingList,
  227. GET_QUEUE_FROM_PACKET(pNdisPacket));
  228. break;
  229. }
  230. /*
  231. // Reacquire MUTEX
  232. */
  233. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  234. }
  235. }
  236. /*
  237. // Release MUTEX
  238. */
  239. NdisReleaseSpinLock(&pAdapter->TransmitLock);
  240. DBG_LEAVE(pAdapter);
  241. }
  242. /* @doc INTERNAL Transmit Transmit_c TransmitCompleteHandler
  243. @func
  244. <f TransmitCompleteHandler> is called by <f MiniportTimer> to handle a
  245. transmit complete event. We walk the <t TransmitCompleteList> to find
  246. all the packets that have been sent out on the wire, and then tell the
  247. protocol stack that we're done with the packet, and it can be re-used.
  248. */
  249. VOID TransmitCompleteHandler(
  250. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  251. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  252. )
  253. {
  254. DBG_FUNC("TransmitCompleteHandler")
  255. PNDIS_PACKET pNdisPacket;
  256. // Holds the packet that's just been transmitted.
  257. PBCHANNEL_OBJECT pBChannel;
  258. // A pointer to one of our <t BCHANNEL_OBJECT>'s.
  259. DBG_ENTER(pAdapter);
  260. /*
  261. // I find it useful to do this nest check, just so I can make sure
  262. // I handle it correctly when it happens.
  263. */
  264. if (++(pAdapter->NestedDataHandler) > 1)
  265. {
  266. DBG_ERROR(pAdapter,("NestedDataHandler=%d > 1\n",
  267. pAdapter->NestedDataHandler));
  268. }
  269. /*
  270. // MUTEX to protect against async EventHandler access at the same time.
  271. */
  272. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  273. #if DBG
  274. { // Sanity check!
  275. PLIST_ENTRY pList = &pAdapter->TransmitCompleteList;
  276. ASSERT(pList->Flink && pList->Flink->Blink == pList);
  277. ASSERT(pList->Blink && pList->Blink->Flink == pList);
  278. }
  279. #endif // DBG
  280. while (!IsListEmpty(&pAdapter->TransmitCompleteList))
  281. {
  282. PLIST_ENTRY pList;
  283. /*
  284. // Remove the packet from the TransmitCompleteList.
  285. */
  286. pList = RemoveHeadList(&pAdapter->TransmitCompleteList);
  287. pNdisPacket = GET_PACKET_FROM_QUEUE(pList);
  288. /*
  289. // Release MUTEX
  290. */
  291. NdisReleaseSpinLock(&pAdapter->TransmitLock);
  292. /*
  293. // Retrieve the information we saved in the packet reserved fields.
  294. */
  295. pBChannel = *((PBCHANNEL_OBJECT *) &pNdisPacket->MiniportReservedEx[8]);
  296. *((PBCHANNEL_OBJECT *) &pNdisPacket->MiniportReservedEx[8]) = NULL;
  297. ASSERT(pBChannel && pBChannel->ObjectType == BCHANNEL_OBJECT_TYPE);
  298. /*
  299. // Indicate send complete to the NDIS wrapper.
  300. */
  301. DBG_TXC(pAdapter, pBChannel->ObjectID);
  302. NdisMCoSendComplete(NDIS_STATUS_SUCCESS,
  303. pBChannel->NdisVcHandle,
  304. pNdisPacket
  305. );
  306. /*
  307. // Reacquire MUTEX
  308. */
  309. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  310. }
  311. /*
  312. // Release MUTEX
  313. */
  314. NdisReleaseSpinLock(&pAdapter->TransmitLock);
  315. /*
  316. // Start any other pending transmits.
  317. */
  318. TransmitPacketHandler(pAdapter);
  319. /*
  320. // I find it useful to do this nest check, just so I can make sure
  321. // I handle it correctly when it happens.
  322. */
  323. if (--(pAdapter->NestedDataHandler) < 0)
  324. {
  325. DBG_ERROR(pAdapter,("NestedDataHandler=%d < 0\n",
  326. pAdapter->NestedDataHandler));
  327. }
  328. DBG_LEAVE(pAdapter);
  329. }
  330. /* @doc INTERNAL Transmit Transmit_c FlushSendPackets
  331. @func
  332. <f FlushSendPackets> is called by <f MiniportTimer> to handle a
  333. transmit complete event. We walk the <t TransmitCompleteList> to find
  334. all the packets that have been sent out on the wire, and then tell the
  335. protocol stack that we're done with the packet, and it can be re-used.
  336. */
  337. VOID FlushSendPackets(
  338. IN PMINIPORT_ADAPTER_OBJECT pAdapter, // @parm
  339. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  340. PBCHANNEL_OBJECT pBChannel // @parm
  341. // A pointer to one of our <t BCHANNEL_OBJECT>'s.
  342. )
  343. {
  344. DBG_FUNC("FlushSendPackets")
  345. PLIST_ENTRY pList;
  346. DBG_ENTER(pAdapter);
  347. // Move all outstanding packets to the complete list.
  348. NdisAcquireSpinLock(&pAdapter->TransmitLock);
  349. while (!IsListEmpty(&pBChannel->TransmitBusyList))
  350. {
  351. pList = RemoveHeadList(&pBChannel->TransmitBusyList);
  352. InsertTailList(&pBChannel->pAdapter->TransmitCompleteList, pList);
  353. }
  354. NdisReleaseSpinLock(&pAdapter->TransmitLock);
  355. // This will complete all the packets now on the TransmitCompleteList,
  356. // and will fail any remaining packets left on the TransmitPendingList.
  357. TransmitCompleteHandler(pAdapter);
  358. DBG_LEAVE(pAdapter);
  359. }
  360. /* @doc EXTERNAL INTERNAL Transmit Transmit_c MiniportCoSendPackets
  361. @func
  362. <f MiniportCoSendPackets> is a required function for connection-oriented
  363. miniports. MiniportCoSendPackets is called to transfer some number of
  364. packets, specified as an array of pointers, over the network.
  365. @comm
  366. MiniportCoSendPackets is called by NDIS in response to a request by a bound
  367. protocol driver to send a ordered list of data packets across the network.
  368. MiniportCoSendPackets should transmit each packet in any given array
  369. sequentially. MiniportCoSendPackets can call NdisQueryPacket to extract
  370. information, such as the number of buffer descriptors chained to the packet
  371. and the total size in bytes of the requested transfer. It can call
  372. NdisGetFirstBufferFromPacket, NdisQueryBuffer, or NdisQueryBufferOffset to
  373. extract information about individual buffers containing the data to be
  374. transmitted.
  375. MiniportCoSendPackets can retrieve protocol-supplied out-of-band information
  376. associated with each packet by using the appropriate NDIS_GET_PACKET_XXX
  377. macros.
  378. MiniportCoSendPackets can use only the eight-byte area at MiniportReserved
  379. within the NDIS_PACKET structure for its own purposes.
  380. The NDIS library ignores the OOB block in all packet descriptors it submits
  381. to MiniportCoSendPackets and assumes that every connection-oriented miniport
  382. is a deserialized driver that will complete each input packet descriptor
  383. asynchronously with NdisMCoSendComplete. Consequently, such a deserialized
  384. driver's MiniportCoSendPackets function usually ignores the Status member of
  385. the NDIS_PACKET_OOB_DATA block, but it can set this member to the same
  386. status as it subsequently passes to NdisMCoSendComplete.
  387. Rather than relying on NDIS to queue and resubmit send packets whenever
  388. MiniportCoSendPackets has insufficient resources to transmit the given
  389. packets, a deserialized miniport manages its own internal packet queueing.
  390. Such a driver is responsible for holding incoming send packets in its
  391. internal queue until they can be transmitted over the network and for
  392. preserving the protocol-determined ordering of packet descriptors incoming
  393. to its MiniportCoSendPackets function. A deserialized miniport must complete
  394. each incoming send packet with NdisMCoSendComplete, and it cannot call
  395. NdisMSendResourcesAvailable.
  396. A deserialized miniport should never pass STATUS_INSUFFICIENT_RESOURCES to
  397. NdisMCoSendComplete with a protocol-allocated packet descriptor that was
  398. originally submitted to its MiniportCoSendPackets function. Such a returned
  399. status effectively fails the send operation requested by the protocol, and
  400. NDIS would return the packet descriptor and all associated resources to the
  401. protocol that originally allocated it.
  402. MiniportCoSendPackets can be called at any IRQL \<= DISPATCH_LEVEL.
  403. Consequently, MiniportCoSendPackets function is responsible for
  404. synchronizing access to its internal queue(s) of packet descriptors with the
  405. driver's other MiniportXxx functions that also access the same queue(s).
  406. @xref
  407. <f ProtocolCoCreateVc>, <f MiniportCoRequest>, <f MiniportInitialize>,
  408. NdisAllocatePacket, NdisCoSendPackets, NdisGetBufferPhysicalArraySize,
  409. NdisGetFirstBufferFromPacket, NdisGetNextBuffer,
  410. NDIS_GET_PACKET_MEDIA_SPECIFIC_INFO, NDIS_GET_PACKET_TIME_TO_SEND,
  411. NdisMCoSendComplete, NdisMoveMemory, NdisMoveToMappedMemory,
  412. NdisMSendResourcesAvailable, NdisMSetupDmaTransfer,
  413. NdisMStartBufferPhysicalMapping, NDIS_OOB_DATA_FROM_PACKET, NDIS_PACKET,
  414. NDIS_PACKET_OOB_DATA, NdisQueryBuffer, NdisQueryBufferOffset,
  415. NdisQueryPacket, NdisZeroMemory
  416. */
  417. VOID MiniportCoSendPackets(
  418. IN PBCHANNEL_OBJECT pBChannel, // @parm
  419. // A pointer to the <t BCHANNEL_OBJECT> instance returned by
  420. // <f ProtocolCoCreate>. AKA MiniportVcContext.<nl>
  421. // Specifies the handle to a miniport-allocated context area in which the
  422. // miniport maintains its per-VC state. The miniport supplied this handle
  423. // to NDIS from its <f ProtocolCoCreateVc> function.
  424. IN PPNDIS_PACKET PacketArray, // @parm
  425. // Points to the initial element in a packet array, with each element
  426. // specifying the address of a packet descriptor for a packet to be
  427. // transmitted, along with an associated out-of-band data block containing
  428. // information such as the packet priority, an optional timestamp, and the
  429. // per-packet status to be set by MiniportCoSendPackets.
  430. IN UINT NumberOfPackets // @parm
  431. // Specifies the number of pointers to packet descriptors at PacketArray.
  432. )
  433. {
  434. DBG_FUNC("MiniportCoSendPackets")
  435. UINT BytesToSend;
  436. // Tells us how many bytes are to be transmitted.
  437. NDIS_STATUS Result = NDIS_STATUS_SUCCESS;
  438. // Holds the result code returned by this function.
  439. UINT Index;
  440. PMINIPORT_ADAPTER_OBJECT pAdapter;
  441. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  442. ASSERT(pBChannel && pBChannel->ObjectType == BCHANNEL_OBJECT_TYPE);
  443. pAdapter = pBChannel->pAdapter;
  444. ASSERT(pAdapter && pAdapter->ObjectType == MINIPORT_ADAPTER_OBJECT_TYPE);
  445. DBG_ENTER(pAdapter);
  446. if (pBChannel->CallClosing)
  447. {
  448. DBG_ERROR(pAdapter,("BChannel Closed\n"));
  449. }
  450. for (Index = 0; Index < NumberOfPackets; Index++)
  451. {
  452. /*
  453. // Return if call has been closed.
  454. */
  455. if (pBChannel->CallClosing)
  456. {
  457. NDIS_SET_PACKET_STATUS(PacketArray[Index], NDIS_STATUS_CLOSED);
  458. continue;
  459. }
  460. NdisQueryPacket(PacketArray[Index], NULL, NULL, NULL, &BytesToSend);
  461. /*
  462. // Make sure the packet size is something we can deal with.
  463. */
  464. if ((BytesToSend == 0) || (BytesToSend > pAdapter->pCard->BufferSize))
  465. {
  466. DBG_ERROR(pAdapter,("Bad packet size = %d\n",BytesToSend));
  467. NdisMCoSendComplete(NDIS_STATUS_INVALID_PACKET,
  468. pBChannel->NdisVcHandle,
  469. PacketArray[Index]
  470. );
  471. }
  472. else
  473. {
  474. /*
  475. // We have to accept the frame if possible, I just want to know
  476. // if somebody has lied to us...
  477. */
  478. if (BytesToSend > pBChannel->WanLinkInfo.MaxSendFrameSize)
  479. {
  480. DBG_NOTICE(pAdapter,("Channel #%d Packet size=%d > %d\n",
  481. pBChannel->ObjectID, BytesToSend,
  482. pBChannel->WanLinkInfo.MaxSendFrameSize));
  483. }
  484. /*
  485. // Place the packet in the transmit list.
  486. */
  487. if (TransmitAddToQueue(pAdapter, pBChannel, PacketArray[Index]) &&
  488. pAdapter->NestedDataHandler < 1)
  489. {
  490. /*
  491. // The queue was empty so we've gotta kick start it.
  492. // Once it's going, it runs off the DPC.
  493. //
  494. // No kick start is necessary if we're already running the the
  495. // TransmitCompleteHandler -- In fact, it will screw things up if
  496. // we call TransmitPacketHandler while TransmitCompleteHandler is
  497. // running.
  498. */
  499. TransmitPacketHandler(pAdapter);
  500. }
  501. }
  502. NDIS_SET_PACKET_STATUS(PacketArray[Index], NDIS_STATUS_PENDING);
  503. }
  504. DBG_LEAVE(pAdapter);
  505. }