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.

629 lines
23 KiB

  1. /*
  2. (C) Copyright 1998
  3. All rights reserved.
  4. Portions of this software are:
  5. (C) Copyright 1995, 1999 TriplePoint, Inc. -- http://www.TriplePoint.com
  6. License to use this software is granted under the terms outlined in
  7. the TriplePoint Software Services Agreement.
  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 Interupt Interupt_c
  12. @module Interupt.c |
  13. This module implements the Miniport interrupt processing routines and
  14. asynchronous processing routines. This module is very dependent on the
  15. hardware/firmware interface and should be looked at whenever changes
  16. to these interfaces occur.
  17. @comm
  18. This driver does not support the physical hardware, so there is no need
  19. for the typical interrupt handler routines. However, the driver does
  20. have an asynchronous event handler which is contained in this module.
  21. @head3 Contents |
  22. @index class,mfunc,func,msg,mdata,struct,enum | Interupt_c
  23. @end
  24. */
  25. #define __FILEID__ INTERRUPT_OBJECT_TYPE
  26. // Unique file ID for error logging
  27. #include "Miniport.h" // Defines all the miniport objects
  28. #if defined(NDIS_LCODE)
  29. # pragma NDIS_LCODE // Windows 95 wants this code locked down!
  30. # pragma NDIS_LDATA
  31. #endif
  32. /* @doc INTERNAL Interupt Interupt_c MiniportCheckForHang
  33. @func
  34. <f MiniportCheckForHang> reports the state of the network interface card.
  35. @comm
  36. In NIC drivers, <f MiniportCheckForHang> does nothing more than check
  37. the internal state of the NIC and return TRUE if it detects that
  38. the NIC is not operating correctly.
  39. In intermediate drivers, <f MiniportCheckForHang> can periodically check the
  40. state of the driver's virtual NIC to determine whether the underlying
  41. device driver appears to be hung.
  42. By default, the NDIS library calls <f MiniportCheckForHang> approximately
  43. every two seconds.
  44. If <f MiniportCheckForHang> returns TRUE, NDIS then calls the driver's
  45. MiniportReset function.
  46. If a NIC driver has no <f MiniportCheckForHang> function and NDIS
  47. judges the driver unresponsive as, for example, when NDIS holds
  48. many pending sends and requests queued to the miniport for a time-out
  49. interval, NDIS calls the driver's <f MiniportReset> function. The NDIS
  50. library's default time-out interval for queued sends and requests is
  51. around four seconds. However, a NIC driver's <f MiniportInitialize>
  52. function can extend NDIS's time-out interval by calling NdisMSetAttributesEx
  53. from <f MiniportInitialize> to avoid unnecessary resets.
  54. The <f MiniportInitialize> function of an intermediate driver
  55. should disable NDIS's time-out interval with NdisMSetAttributesEx
  56. because such a driver can neither control nor estimate a reasonable
  57. completion interval for the underlying device driver.
  58. <f MiniportCheckForHang> can be pre-empted by an interrupt.
  59. By default, <f MiniportCheckForHang> runs at IRQL DISPATCH_LEVEL.
  60. <f Note>:
  61. If your hardware/firmware is flakey you can request that the NDIS
  62. wrapper call your MiniportReset routine by returning TRUE from this
  63. routine. For well behaved hardware/firmware you should always return
  64. FALSE from this routine.
  65. @rdesc
  66. <f MiniportCheckForHang> returns FALSE if the NIC is working properly.<nl>
  67. Otherwise, a TRUE return value indicates that the NIC needs to be reset.
  68. */
  69. BOOLEAN MiniportCheckForHang(
  70. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  71. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  72. )
  73. {
  74. DBG_FUNC("MiniportCheckForHang")
  75. // If your hardware can lockup, then you can return TRUE here.
  76. // If you return TRUE, your MiniportReset routine will be called.
  77. return (FALSE);
  78. }
  79. #if defined(CARD_REQUEST_ISR)
  80. #if (CARD_REQUEST_ISR == FALSE)
  81. /* @doc INTERNAL Interupt Interupt_c MiniportDisableInterrupt
  82. @func
  83. <f MiniportDisableInterrupt> disables the interrupt capability of
  84. the NIC to keep it from generating interrupts.
  85. @comm
  86. <f MiniportDisableInterrupt> typically disables interrupts by writing
  87. a mask to the NIC. If a driver does not have this function, typically
  88. its <f MiniportISR> disables interrupts on the NIC.
  89. If the NIC does not support dynamic enabling and disabling of
  90. interrupts or if it shares an IRQ, the miniport driver must register
  91. a <f MiniportISR> function and set RequestIsr to TRUE when it calls
  92. NdisMRegisterMiniport. Such a driver's MiniportISR function must
  93. acknowledge each interrupt generated by the NIC and save any
  94. necessary interrupt information for the driver's
  95. MiniportHandleInterrupt function.
  96. By default, MiniportDisableInterrupt runs at DIRQL, in particular
  97. at the DIRQL assigned when the NIC driver's MiniportInitialize
  98. function called NdisMRegisterInterrupt. Therefore,
  99. MiniportDisableInterrupt can call only a subset of the NDIS library
  100. functions, such as the NdisRawXxx functions that are safe to call
  101. at any IRQL.
  102. If <f MiniportDisableInterrupt> shares resources, such as NIC registers,
  103. with another MiniportXxx that runs at a lower IRQL, that MiniportXxx
  104. must call NdisMSychronizeWithInterrupt so the driver's
  105. <f MiniportSynchronizeISR> function will access those shared
  106. resources in a synchronized and multiprocessor-safe manner.
  107. Otherwise, while it is accessing the shared resources, that
  108. MiniportXxx function can be pre-empted by <f MiniportDisableInterrupt>,
  109. possibly undoing the work just done by MiniportXxx.
  110. @xref
  111. <f MiniportEnableInterrupt>
  112. <f MiniportHandleInterrupt>
  113. <f MiniportInitialize>
  114. <f MiniportISR>
  115. */
  116. void MiniportDisableInterrupt(
  117. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  118. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  119. )
  120. {
  121. DBG_FUNC("MiniportDisableInterrupt")
  122. DBG_ERROR(pAdapter,("This should not be called!\n"));
  123. }
  124. /* @doc INTERNAL Interupt Interupt_c MiniportEnableInterrupt
  125. @func
  126. <f MiniportEnableInterrupt> enables the NIC to generate interrupts.
  127. @comm
  128. <f MiniportEnableInterrupt> typically enables interrupts by writing
  129. a mask to the NIC.
  130. A NIC driver that exports a <f MiniportDisableInterrupt> function
  131. need not have a reciprocal <f MiniportEnableInterrupt> function.
  132. Such a driver's <f MiniportHandleInterrupt> function is responsible
  133. for re-enabling interrupts on the NIC.
  134. If its NIC does not support dynamic enabling and disabling of
  135. interrupts or if it shares an IRQ, the NIC driver must register
  136. a <f MiniportISR> function and set RequestIsr to TRUE when it calls
  137. NdisMRegisterMiniport. Such a driver's <f MiniportISR> function must
  138. acknowledge each interrupt generated by the NIC and save any
  139. necessary interrupt information for the driver's
  140. <f MiniportHandleInterrupt> function.
  141. <f MiniportEnableInterrupt> can be pre-empted by an interrupt.
  142. By default, <f MiniportEnableInterrupt> runs at IRQL DISPATCH_LEVEL.
  143. @xref
  144. <f MiniportDisableInterrupt>
  145. <f MiniportHandleInterrupt>
  146. <f MiniportInitialize>
  147. <f MiniportISR>
  148. */
  149. void MiniportEnableInterrupt(
  150. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  151. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  152. )
  153. {
  154. DBG_FUNC("MiniportEnableInterrupt")
  155. DBG_ERROR(pAdapter,("This should not be called!\n"));
  156. }
  157. #else // !(CARD_REQUEST_ISR == FALSE)
  158. /* @doc INTERNAL Interupt Interupt_c MiniportISR
  159. @func
  160. <f MiniportISR> is the miniport driver's interrupt service routine
  161. and it runs at a high priority in response to an interrupt.
  162. @comm
  163. Any NIC driver should do as little work as possible in its
  164. <f MiniportISR> function, deferring I/O operations for each
  165. interrupt the NIC generates to the <f MiniportHandleInterrupt>
  166. function. A NIC driver's ISR is not re-entrant, although two
  167. instantiations of a <f MiniportISR> function can execute concurrently
  168. in SMP machines, particularly if the miniport supports
  169. full-duplex sends and receives.
  170. Miniport ISR is called under the following conditions:
  171. An interrupt occurs on the NIC while the driver's <f MiniportInitialize>
  172. or <f MiniportHalt> function is running. An interrupt occurs on the I/O bus
  173. and the NIC shares an IRQ with other devices on that bus.
  174. If the NIC shares an IRQ with other devices, that miniport's ISR
  175. must be called on every interrupt to determine whether its NIC
  176. actually generated the interrupt. If not, <f MiniportISR> should return
  177. FALSE immediately so the driver of the device that actually generated
  178. the interrupt is called quickly. This strategy maximizes I/O throughput
  179. for every device on the same bus.
  180. An interrupt occurs and the NIC driver specified that its ISR should be
  181. called to handle every interrupt when its <f MiniportInitialize> function
  182. called NdisMRegisterInterrupt.
  183. Miniports that do not provide <f MiniportDisableInterrupt>/<f MiniportEnableInterrupt>
  184. functionality must have their ISRs called on every interrupt.
  185. <f MiniportISR> dismisses the interrupt on the NIC, saves whatever state
  186. it must about the interrupt, and defers as much of the I/O processing
  187. for each interrupt as possible to the <f MiniportHandleInterrupt> function.
  188. After <f MiniportISR> returns control with the variables at InterruptRecognized
  189. and QueueMiniportHandleInterrupt set to TRUE, the corresponding
  190. <f MiniportHandleInterrupt> function runs at a lower hardware priority
  191. (IRQL DISPATCH_LEVEL) than that of the ISR (DIRQL). As a general
  192. rule, <f MiniportHandleInterrupt> should do all the work for interrupt-driven
  193. I/O operations except for determining whether the NIC actually generated
  194. the interrupt, and, if necessary, preserving the type (receive, send,
  195. reset...) of interrupt.
  196. However, a driver writer should not rely on a one-to-one correspondence
  197. between the execution of <f MiniportISR> and <f MiniportHandleInterrupt>. A
  198. <f MiniportHandleInterrupt> function should be written to handle the I/O
  199. processing for more than one NIC interrupt. Its MiniportISR and
  200. <f MiniportHandleInterrupt> functions can run concurrently in SMP machines.
  201. Moreover, as soon as <f MiniportISR> acknowledges a NIC interrupt, the NIC
  202. can generate another interrupt, while the <f MiniportHandleInterrupt> DPC
  203. can be queued for execution once for such a sequence of interrupts.
  204. The <f MiniportHandleInterrupt> function is not queued if the driver's
  205. <f MiniportHalt> or <f MiniportInitialize> function is currently executing.
  206. If <f MiniportISR> shares resources, such as NIC registers or state
  207. variables, with another MiniportXxx that runs at lower IRQL,
  208. that MiniportXxx must call NdisMSychronizeWithInterrupt so the
  209. driver's MiniportSynchronizeISR function will access those shared
  210. resources in a synchronized and multiprocessor-safe manner. Otherwise,
  211. while it is accessing the shared resources, that MiniportXxx function
  212. can be pre-empted by <f MiniportISR>, possibly undoing the work just done
  213. by MiniportXxx.
  214. By default, <f MiniportISR> runs at DIRQL, in particular at the DIRQL
  215. assigned when the driver initialized the interrupt object with
  216. NdisMRegisterInterrupt. Therefore, <f MiniportIsr> can call only a
  217. subset of the NDIS library functions, such as the NdisRawXxx or
  218. NdisRead/WriteRegisterXxx functions that are safe to call at
  219. any IRQL.
  220. @devnote
  221. <f MiniportISR> must not call any support functions in the NDIS
  222. interface library or the transport driver.
  223. @xref
  224. <f MiniportDisableInterrupt>
  225. <f MiniportEnableInterrupt>
  226. <f MiniportHalt>
  227. <f MiniportHandleInterrupt>
  228. <f MiniportInitialize>
  229. <f MiniportSynchronizeISR>
  230. */
  231. void MiniportISR(
  232. OUT PBOOLEAN InterruptRecognized, // @parm
  233. // If the miniport driver is sharing an interrupt line and it detects
  234. // that the interrupt came from its NIC, <f MiniportISR> should set
  235. // this parameter to TRUE.
  236. OUT PBOOLEAN QueueMiniportHandleInterrupt, // @parm
  237. // If the miniport driver is sharing an interrupt line and if
  238. // <f MiniportHandleInterrupt> must be called to complete handling of
  239. // the interrupt, <f MiniportISR> should set this parameter to TRUE.
  240. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  241. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  242. )
  243. {
  244. DBG_FUNC("MiniportISR")
  245. ULONG InterruptStatus;
  246. if ((InterruptStatus = pAdapter->TODO) == 0)
  247. {
  248. *InterruptRecognized =
  249. *QueueMiniportHandleInterrupt = FALSE;
  250. }
  251. else
  252. {
  253. pAdapter->pCard->InterruptStatus = InterruptStatus;
  254. *InterruptRecognized =
  255. *QueueMiniportHandleInterrupt = TRUE;
  256. }
  257. }
  258. #endif // (CARD_REQUEST_ISR == FALSE)
  259. #endif // defined(CARD_REQUEST_ISR)
  260. /* @doc INTERNAL Interupt Interupt_c MiniportHandleInterrupt
  261. @func
  262. <f MiniportHandleInterrupt> is called by the deferred processing routine
  263. in the NDIS library to process an interrupt.
  264. @comm
  265. <f MiniportHandleInterrupt> does the deferred processing of all
  266. outstanding interrupt operations and starts any new operations.
  267. That is, the driver's <f MiniportISR> or <f MiniportDisableInterrupt>
  268. function dismisses the interrupt on the NIC, saves any necessary
  269. state about the operation, and returns control as quickly as possible,
  270. thereby deferring most interrupt-driven I/O operations to
  271. <f MiniportHandleInterrupt>.
  272. <f MiniportHandleInterrupt> carries out most operations to indicate
  273. receives on NICs that generate interrupts, including but not
  274. limited to the following:
  275. Adjusting the size of the buffer descriptor(s) to match the size of
  276. the received data and chaining the buffer descriptor(s) to the packet
  277. descriptor for the indication.
  278. Setting up an array of packet descriptors and setting up any
  279. out-of-band information for each packet in the array for the
  280. indication or, if the miniport does not support multipacket
  281. receive indications, setting up a lookahead buffer
  282. If the driver supports multipacket receives, it must indicate
  283. packet arrays in which the packet descriptors were allocated
  284. from packet pool and the buffer descriptors chained to those
  285. packets were allocated from buffer pool.
  286. Calling the appropriate Ndis..IndicateReceive function for the
  287. received data.
  288. <f MiniportHandleInterrupt> also can call NdisSendComplete on packets
  289. for which the MiniportSendPackets or <f MiniportWanSend> function
  290. returned NDIS_STATUS_PENDING.
  291. If the NIC shares an IRQ, <f MiniportHandleInterrupt> is called only i
  292. f the <f MiniportISR> function returned InterruptRecognized set to
  293. TRUE, thereby indicating that the NIC generated a particular interrupt.
  294. When <f MiniportHandleInterrupt> is called, interrupts are disabled
  295. on the NIC, either by the <f MiniportISR> or <f MiniportDisableInterrupt>
  296. function. Before it returns control, <f MiniportHandleInterrupt> can
  297. re-enable interrupts on the NIC. Otherwise, NDIS calls a driver-supplied
  298. MiniportEnableInterrupt function to do so when <f MiniportHandleInterrupt>
  299. returns control.
  300. By default, <f MiniportHandleInterrupt> runs at IRQL DISPATCH_LEVEL.
  301. @xref
  302. <f MiniportDisableInterrupt>
  303. <f MiniportEnableInterrupt>
  304. <f MiniportInitialize>
  305. <f MiniportISR>
  306. <f MiniportWanSend>
  307. */
  308. void MiniportHandleInterrupt(
  309. IN PMINIPORT_ADAPTER_OBJECT pAdapter // @parm
  310. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  311. )
  312. {
  313. DBG_FUNC("MiniportHandleInterrupt")
  314. PBCHANNEL_OBJECT pBChannel;
  315. // A Pointer to one of our <t BCHANNEL_OBJECT>'s.
  316. ULONG BChannelIndex;
  317. // Index into the pBChannelArray.
  318. /*
  319. // Process NIC interrupt.
  320. */
  321. CardInterruptHandler(pAdapter->pCard);
  322. /*
  323. // Walk through all the links to see if there is any post-proccessing
  324. // that needs to be done.
  325. */
  326. for (BChannelIndex = 0; BChannelIndex < pAdapter->NumBChannels; ++BChannelIndex)
  327. {
  328. pBChannel = GET_BCHANNEL_FROM_INDEX(pAdapter, BChannelIndex);
  329. if (pBChannel->IsOpen)
  330. {
  331. /*
  332. // If this is the last transmit queued on this link, and it has
  333. // been closed, close the link and notify the protocol that the
  334. // link has been closed.
  335. */
  336. if (IsListEmpty(&pBChannel->TransmitBusyList)
  337. && pBChannel->CallClosing)
  338. {
  339. DBG_FILTER(pAdapter, DBG_TAPICALL_ON,
  340. ("#%d Call=0x%X CallState=0x%X CLOSE PENDED\n",
  341. pBChannel->BChannelIndex,
  342. pBChannel->htCall, pBChannel->CallState));
  343. /*
  344. // This must not be called until all transmits have been dequeued
  345. // and ack'd. Otherwise the wrapper will hang waiting for transmit
  346. // request to complete.
  347. */
  348. DChannelCloseCall(pAdapter->pDChannel, pBChannel);
  349. /*
  350. // Indicate close complete to the wrapper.
  351. */
  352. NdisMSetInformationComplete(
  353. pAdapter->MiniportAdapterHandle,
  354. NDIS_STATUS_SUCCESS
  355. );
  356. }
  357. /*
  358. // Indicate a receive complete if it's needed.
  359. */
  360. if (pBChannel->NeedReceiveCompleteIndication)
  361. {
  362. pBChannel->NeedReceiveCompleteIndication = FALSE;
  363. /*
  364. // Indicate receive complete to the NDIS wrapper.
  365. */
  366. DBG_RXC(pAdapter, pBChannel->BChannelIndex);
  367. NdisMWanIndicateReceiveComplete(
  368. pAdapter->MiniportAdapterHandle,
  369. pBChannel->NdisLinkContext
  370. );
  371. }
  372. }
  373. }
  374. /*
  375. // Indicate a status complete if it's needed.
  376. */
  377. if (pAdapter->NeedStatusCompleteIndication)
  378. {
  379. pAdapter->NeedStatusCompleteIndication = FALSE;
  380. NdisMIndicateStatusComplete(pAdapter->MiniportAdapterHandle);
  381. }
  382. }
  383. /* @doc INTERNAL Interupt Interupt_c MiniportTimer
  384. @func
  385. <f MiniportTimer> is a required function if a Miniport's NIC does not
  386. generate interrupts. Otherwise, one or more <f MiniportTimer> functions
  387. are optional.
  388. @comm
  389. For a NIC that does not generate interrupts, the <f MiniportTimer>
  390. function is used to poll the state of the NIC.
  391. After such a driver's <f MiniportInitialize> function sets up the
  392. driver-allocated timer object with NdisMInitializeTimer, a
  393. call to NdisMSetPeriodicTimer causes the <f MiniportTimer> function
  394. associated with the timer object to be run repeatedly and
  395. automatically at the interval specified by MillisecondsPeriod.
  396. Such a polling <f MiniportTimer> function monitors the state of the
  397. NIC to determine when to make indications, when to complete
  398. pending sends, and so forth. In effect, such a polling <f MiniportTimer>
  399. function has the same functionality as the <f MiniportHandleInterrupt>
  400. function in the driver of a NIC that does generate interrupts.
  401. By contrast, calling NdisMSetTimer causes the <f MiniportTimer>
  402. function associated with the timer object to be run once when
  403. the given MillisecondsToDelay expires. Such a <f MiniportTimer>
  404. function usually performs some driver-determined action if a
  405. particular operation times out.
  406. If either type of <f MiniportTimer> function shares resources with
  407. other driver functions, the driver should synchronize access to
  408. those resources with a spin lock.
  409. Any NIC driver or intermediate driver can have more than one
  410. <f MiniportTimer> function at the discretion of the driver writer.
  411. Each such <f MiniportTimer> function must be associated with a
  412. driver-allocated and initialized timer object.
  413. A call to NdisMCancelTimer cancels execution of a nonpolling
  414. <f MiniportTimer> function, provided that the interval passed in
  415. the immediately preceding call to NdisMSetTimer has not yet
  416. expired. After a call to NdisMSetPeriodicTimer, a call to
  417. NdisMSetTimer or NdisMCancelTimer with the same timer object
  418. disables a polling <f MiniportTimer> function: either the
  419. MiniportTimer function runs once, or it is canceled.
  420. The <f MiniportHalt> function of any driver with a <f MiniportTimer>
  421. function should call NdisMCancelTimer to ensure that the
  422. <f MiniportTimer> function does not attempt to access resources
  423. that <f MiniportHalt> has already released.
  424. By default, <f MiniportTimer> runs at IRQL DISPATCH_LEVEL.
  425. @xref
  426. <f MiniportHalt>
  427. <f MiniportInitialize>
  428. <f NdisAcquireSpinLock>
  429. <f NdisAllocateSpinLock>
  430. */
  431. void MiniportTimer(
  432. IN PVOID SystemSpecific1, // @parm
  433. // Points to a system-specific variable, which is opaque
  434. // to <f MiniportTimer> and reserved for system use.
  435. // UNREFERENCED_PARAMETER
  436. IN PMINIPORT_ADAPTER_OBJECT pAdapter, // @parm
  437. // A pointer to the <t MINIPORT_ADAPTER_OBJECT> instance.
  438. IN PVOID SystemSpecific2, // @parm
  439. // UNREFERENCED_PARAMETER
  440. IN PVOID SystemSpecific3 // @parm
  441. // UNREFERENCED_PARAMETER
  442. )
  443. {
  444. DBG_FUNC("MiniportTimer")
  445. DBG_ENTER(pAdapter);
  446. /*
  447. // If this is a nested callback, just return, and we'll loop back to
  448. // the DoItAgain before leaving the outermost callback.
  449. */
  450. if (++(pAdapter->NestedEventHandler) > 1)
  451. {
  452. DBG_WARNING(pAdapter,("NestedEventHandler=%d > 1\n",
  453. pAdapter->NestedEventHandler));
  454. return;
  455. }
  456. DoItAgain:
  457. #if defined(SAMPLE_DRIVER)
  458. /*
  459. // This sample driver uses timer to simulate interrupts.
  460. */
  461. MiniportHandleInterrupt(pAdapter);
  462. #else // SAMPLE_DRIVER
  463. // TODO - Add code here to handle timer interrupt events.
  464. #endif // SAMPLE_DRIVER
  465. /*
  466. // If we got a nested callback, we have to loop back around.
  467. */
  468. if (--(pAdapter->NestedEventHandler) > 0)
  469. {
  470. goto DoItAgain;
  471. }
  472. else if (pAdapter->NestedEventHandler < 0)
  473. {
  474. DBG_ERROR(pAdapter,("NestedEventHandler=%d < 0\n",
  475. pAdapter->NestedEventHandler));
  476. }
  477. DBG_LEAVE(pAdapter);
  478. UNREFERENCED_PARAMETER(SystemSpecific1);
  479. UNREFERENCED_PARAMETER(SystemSpecific2);
  480. UNREFERENCED_PARAMETER(SystemSpecific3);
  481. }