JLINKDCC_Process.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. /*********************************************************************
  2. * SEGGER Microcontroller GmbH *
  3. * Solutions for real time microcontroller applications *
  4. **********************************************************************
  5. * *
  6. * (c) 1995 - 2018 SEGGER Microcontroller GmbH *
  7. * *
  8. * www.segger.com Support: support@segger.com *
  9. * *
  10. **********************************************************************
  11. ----------------------------------------------------------------------
  12. File : JLINKDCC_Process.c
  13. Purpose : Data handler for ARM J-Link type communication via DCC (IAR version, using intrinsics)
  14. Changes : 08-10-08
  15. Support for "channels"added, where
  16. channel 0 is reserved for terminal
  17. channel 1 is reserved for OS communication such as embOSView
  18. 11-11-15
  19. Cortex A/R defines modified.
  20. Notes : (1) How to use
  21. In order to use the DCC communication to read / write memory, the
  22. following needs to be done:
  23. * Add this file to the project / make-file
  24. * Make sure this data handler is called regularly
  25. * Add the JLINKDCC data abort handler (optional)
  26. For details, refer to the documentation or see file JLINKDCC_HandleDataAbort.s79.
  27. (2) Compatibility
  28. The J-Link ARM DCC handler is compatible to the DCC communication
  29. protocol used by IAR in the embedded workbench for ARM and allows
  30. using the live data window in C-Spy
  31. Protocol
  32. Following response packets from target will be possible:
  33. 00 00 00 XX - reading a byte XX
  34. 00 00 XX XX - reading a half word XXXX
  35. XX XX XX XX - reading a word, except words starting with 93zX XXXX (10010011-1xxxxxxx-xxxxxxx-xxxxxxx)
  36. 93 8z 00 XX - terminal I/O one byte XX to channel z=0-15
  37. 93 9z 00 XX - terminal I/O one byte XX to channel z=16-31
  38. 93 Az XX XX - terminal I/O two bytes XX XX to channel z=0-15
  39. 93 Bz XX XX - terminal I/O two bytes XX XX to channel z=16-31
  40. 93 C0 XX XX - escape sequence for words starting with 93XX, the lower 16-bit part is in XX XX
  41. 93 C1 XX XX - escape sequence for words starting with 93XX, the upper 16-bit part is in XX XX
  42. 93 C2 XX XX - data abort for reading
  43. 91 CA XX XX - signature (same as before)
  44. There is a new capability flag. C-SPY will use the new protocol when this is set.
  45. #define DCC_CAP_TERM_OUT 0x80
  46. Sequence for U8 write:
  47. H->T Addr & 0xFFFFFFFE
  48. H->T ((Addr & 1) << 31) | (Data << 2) | 0x40000001
  49. Sequence for U16 write:
  50. H->T Addr & 0xFFFFFFFE
  51. H->T ((Addr & 1) << 31) | (Data << 2) | 0x20000001
  52. Sequence for U32 write:
  53. H->T Addr & 0xFFFFFFFE
  54. H->T (Data & 0xFFFF) << 2
  55. H->T ((Addr & 1) << 31) | ((Data >> 14) & 0xFFFF) | 0x10000001
  56. Sequence for U8 Read:
  57. H->T Addr & 0xFFFFFFFE
  58. H->T ((Addr & 1) << 31) | (NumItems << 2) | 0x04000001
  59. if (Aborted) {
  60. T->H 0x93c20000
  61. } else {
  62. T->H Data
  63. }
  64. Sequence for U16 Read:
  65. H->T Addr & 0xFFFFFFFE
  66. H->T ((Addr & 1) << 31) | (NumItems << 2) | 0x02000001
  67. if (Aborted) {
  68. T->H 0x93c20000
  69. } else {
  70. T->H Data
  71. }
  72. Sequence for U32 Read:
  73. H->T Addr & 0xFFFFFFFE
  74. H->T ((Addr & 1) << 31) | (NumItems << 2) | 0x01000001
  75. if (Aborted) {
  76. T->H 0x93c20000
  77. } else if ((Data >> 24) != 0x93) {
  78. T->H Data
  79. } else {
  80. T->H 0x93c0.0000 | (Data & 0xFFFF)
  81. T->H 0x93c1.0000 | (Data >> 16)
  82. }
  83. Terminal IN: (target receives data)
  84. H->T 0x93000000 + (Channel << 19) + (Data8 << 1) + DCC_OP_COMMAND
  85. Terminal OUT: (target sends data)
  86. T->H 0x93800000 + (Channel << 16) + (Data8)
  87. ---------------------------END-OF-HEADER------------------------------
  88. */
  89. #include "JLINKDCC.h"
  90. #ifdef __ICCARM__
  91. /* With IAR workbench we use intrinsics for CP14 communication */
  92. #include <intrinsics.h>
  93. #else
  94. #define __ARM7A__ 1
  95. #define __ARM7R__ 2
  96. #define __CORE__ 0 // Default is: DCC module is designed for ARM7/9. In order to support Cortex-A/R, __CORE__ has to be set to __ARM7A__ or __ARM7R__
  97. /* We use external functions from assembly module JLINKDCC_Process_ASM.s */
  98. extern unsigned long CP14_ReadDCCStat(void);
  99. extern unsigned long CP14_ReadDCC(void);
  100. extern void CP14_WriteDCC(unsigned long Data);
  101. #endif
  102. /*********************************************************************
  103. *
  104. * Defines, configurable
  105. *
  106. **********************************************************************
  107. */
  108. #ifndef JLINKDCC_BUFFER_SIZE
  109. #define JLINKDCC_BUFFER_SIZE 1024 // Used for channel 0 (terminal out buffer)
  110. #endif
  111. #define NUM_CHANNELS 2
  112. /*********************************************************************
  113. *
  114. * Defines, non- configurable
  115. *
  116. **********************************************************************
  117. */
  118. #define U8 unsigned char
  119. #define U16 unsigned short
  120. #define U32 unsigned long
  121. #define DCC_OP_READ_U32 0x01000000
  122. #define DCC_OP_READ_U16 0x02000000
  123. #define DCC_OP_READ_U8 0x04000000
  124. #define DCC_OP_GET_CAPS 0x08000000
  125. #define DCC_OP_WRITE_U32 0x10000000
  126. #define DCC_OP_WRITE_U16 0x20000000
  127. #define DCC_OP_WRITE_U8 0x40000000
  128. #define DCC_OP_ODD_ADDR 0x80000000
  129. #define DCC_OP_COMMAND 0x00000001
  130. #define DCC_SIGNATURE 0x91CA0000
  131. #define DCC_CONFIG 0xFF // All features are supported
  132. #define DCC_CHANNEL_TERMINAL 0
  133. #define DCC_CHANNEL_OS 1
  134. //
  135. // The bit positions for DCC RX and TX ready are different for ARM7/9 and Cortex-A/R,
  136. // so we have to distinguish here.
  137. //
  138. #if (defined (__ARM7A__) && (__CORE__ == __ARM7A__)) || (defined (__ARM7R__) && (__CORE__ == __ARM7R__))
  139. #define RX_FULL_FLAG_SHIFT 30
  140. #define TX_FULL_FLAG_SHIFT 29
  141. #else
  142. #define RX_FULL_FLAG_SHIFT 0
  143. #define TX_FULL_FLAG_SHIFT 1
  144. #endif
  145. /*********************************************************************
  146. *
  147. * Global data
  148. *
  149. **********************************************************************
  150. */
  151. U8 JLINKDCC_IsInHandler;
  152. U8 JLINKDCC_AbortOccurred;
  153. /*********************************************************************
  154. *
  155. * Static data
  156. *
  157. **********************************************************************
  158. */
  159. void (* _pfOnRx[2]) (unsigned Channel, U8 Data);
  160. void (* _pfOnTx[2]) (unsigned Channel);
  161. static U16 _NumReadItems;
  162. static U32 _Command;
  163. static U32 _Addr;
  164. static char _acBuffer[JLINKDCC_BUFFER_SIZE];
  165. static U16 _RdPos;
  166. static U16 _WrPos;
  167. static U8 _ActiveChannel = 0;
  168. static char _WritePendingNB = 0;
  169. static U32 _PendingPacketNB = 0;
  170. static char _WritePending;
  171. static U32 _Data;
  172. /*********************************************************************
  173. *
  174. * Static code
  175. *
  176. **********************************************************************
  177. */
  178. #ifdef __ICCARM__
  179. /****** Use intrinsics for CP14 communication ***********************/
  180. #if (defined (__ARM7A__) && (__CORE__ == __ARM7A__)) || (defined (__ARM7R__) && (__CORE__ == __ARM7R__))
  181. /*********************************************************************
  182. *
  183. * _ReadDCCStat
  184. *
  185. * IAR macro: unsigned long __MRC(coproc, opcode_1, CRn, CRm, opcode_2);
  186. * Inst: MRC<c> <coproc>,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>} Move from coproc to ARM reg
  187. * ARMv4 / ARMv5: MRC p14,0,<Rt>,c0,c0,0
  188. * ARMv7-AR: MRC p14,0,<Rt>,c0,c1,0 DBGDSCR
  189. */
  190. static int _ReadDCCStat(void) {
  191. return __MRC(14, 0, 0, 1, 0);
  192. }
  193. /*********************************************************************
  194. *
  195. * _ReadDCC
  196. *
  197. * IAR macro: unsigned long __MRC(coproc, opcode_1, CRn, CRm, opcode_2);
  198. * Inst: MRC<c> <coproc>,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>} Move from coproc to ARM reg
  199. * ARMv4 / ARMv5: MRC p14,0,<Rt>,c1,c0,0
  200. * ARMv7-AR: MRC p14,0,<Rt>,c0,c5,0 DTRRX
  201. */
  202. static U32 _ReadDCC(void) {
  203. return __MRC(14, 0, 0, 5, 0);
  204. }
  205. /*********************************************************************
  206. *
  207. * _WriteDCC
  208. *
  209. * IAR macro: void __MCR(coproc, opcode_1, Data, CRn, CRm, opcode_2);
  210. * Inst: MCR<c> <coproc>,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>} Move to coproc
  211. * ARMv4 / ARMv5: MCR P14,0,<Rt>,C1,C0,0
  212. * ARMv7-AR: MCR p14,0,<Rt>,c0,c5,0 DTRTX
  213. */
  214. static void _WriteDCC(U32 Data) {
  215. __MCR(14, 0, Data, 0, 5, 0);
  216. }
  217. #else // (defined (__ARM7A__) && (__CORE__ == __ARM7A__)) || (defined (__ARM7R__) && (__CORE__ == __ARM7R__))
  218. /*********************************************************************
  219. *
  220. * _ReadDCCStat
  221. *
  222. * IAR macro: unsigned long __MRC(coproc, opcode_1, CRn, CRm, opcode_2);
  223. * Inst: MRC<c> <coproc>,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>} Move from coproc to ARM reg
  224. * ARMv4 / ARMv5: MRC p14,0,<Rt>,c0,c0,0
  225. * ARMv7-AR: MRC p14,0,<Rt>,c0,c1,0 DBGDSCR
  226. */
  227. static __interwork __arm int _ReadDCCStat(void) {
  228. return __MRC( 14, 0, 0, 0, 0 ); // __asm("mrc P14,0,R0,C0,C0,0");
  229. }
  230. /*********************************************************************
  231. *
  232. * _ReadDCC
  233. *
  234. * IAR macro: unsigned long __MRC(coproc, opcode_1, CRn, CRm, opcode_2);
  235. * Inst: MRC<c> <coproc>,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>} Move from coproc to ARM reg
  236. * ARMv4 / ARMv5: MRC p14,0,<Rt>,c1,c0,0
  237. * ARMv7-AR: MRC p14,0,<Rt>,c0,c5,0 DTRRX
  238. */
  239. static __interwork __arm U32 _ReadDCC(void) {
  240. return __MRC( 14, 0, 1, 0, 0 ); // __asm("mrc P14,0,R0,C1,C0,0");
  241. }
  242. /*********************************************************************
  243. *
  244. * _WriteDCC
  245. *
  246. * IAR macro: void __MCR(coproc, opcode_1, Data, CRn, CRm, opcode_2);
  247. * Inst: MCR<c> <coproc>,<opc1>,<Rt>,<CRn>,<CRm>{,<opc2>} Move to coproc
  248. * ARMv4 / ARMv5: MCR P14,0,<Rt>,C1,C0,0
  249. * ARMv7-AR: MCR p14,0,<Rt>,c0,c5,0 DTRTX
  250. */
  251. static __interwork __arm void _WriteDCC(U32 Data) {
  252. __MCR( 14, 0, Data, 1, 0, 0 ); // __asm("mcr P14,0,R0,C1,C0,0");
  253. }
  254. #endif // (defined (__ARM7A__) && (__CORE__ == __ARM7A__)) || (defined (__ARM7R__) && (__CORE__ == __ARM7R__))
  255. #else //__ICCARM__
  256. /****** Map externel CP14 communication routines ********************/
  257. #define _ReadDCCStat CP14_ReadDCCStat
  258. #define _ReadDCC CP14_ReadDCC
  259. #define _WriteDCC(Data) CP14_WriteDCC(Data)
  260. #endif // __ICCARM__
  261. /*********************************************************************
  262. *
  263. * _HandleRead
  264. *
  265. * Function description
  266. * Performs Command / data read from host
  267. */
  268. static void _HandleRead(void) {
  269. U32 Data;
  270. if (((_ReadDCCStat() >> RX_FULL_FLAG_SHIFT) & 1) == 0) { // Data or command received ?
  271. return; // Nothing received
  272. }
  273. Data = _ReadDCC();
  274. //
  275. // If item received does not have the command-flag set, it is the new addr.
  276. //
  277. if ((Data & DCC_OP_COMMAND) == 0) {
  278. _Addr = Data;
  279. return;
  280. }
  281. //
  282. // If item received is a terminal out command,
  283. //
  284. if ((Data & 0xFF000000) == 0x93000000) {
  285. unsigned Channel;
  286. Channel = (Data >> 19) & 0x1F;
  287. if (_pfOnRx[Channel]) {
  288. _pfOnRx[Channel](Channel, (Data >> 1) & 0xFF);
  289. }
  290. return;
  291. }
  292. //
  293. // We received a new command.
  294. //
  295. _Command = Data;
  296. if (_Command & DCC_OP_ODD_ADDR) {
  297. _Addr |= 1;
  298. }
  299. if (_Command & (DCC_OP_READ_U32 | DCC_OP_READ_U16 | DCC_OP_READ_U8 | DCC_OP_GET_CAPS)) {
  300. _NumReadItems = (_Command >> 2) & 0xffff;
  301. } else {
  302. // Clear before write
  303. JLINKDCC_AbortOccurred = 0;
  304. if (_Command & DCC_OP_WRITE_U32) {
  305. _Data |= (_Command << 14) & 0xffff0000;
  306. } else {
  307. _Data = (_Command >> 2) & 0xffff;
  308. }
  309. if (_Command & DCC_OP_WRITE_U8) {
  310. *(U8*)_Addr = _Data;
  311. _Addr += 1;
  312. }
  313. if (_Command & DCC_OP_WRITE_U16) {
  314. *(U16*)_Addr = _Data;
  315. _Addr += 2;
  316. }
  317. if (_Command & DCC_OP_WRITE_U32) {
  318. *(U32*)_Addr =_Data;
  319. _Addr += 4;
  320. }
  321. }
  322. }
  323. /*********************************************************************
  324. *
  325. * _HandleWrite
  326. *
  327. * Function description
  328. * Sends data back to host if there is space in DCC buffer and data to be send.
  329. */
  330. static void _HandleWrite(void) {
  331. U32 Data;
  332. int DCCBusy;
  333. int NumBytes;
  334. Data = 0;
  335. DCCBusy = (_ReadDCCStat() >> TX_FULL_FLAG_SHIFT) & 1;
  336. if (DCCBusy) { // Can we send data ?
  337. return; // If not, we are done.
  338. }
  339. if (_ActiveChannel) {
  340. U32 Channel;
  341. Channel = _ActiveChannel;
  342. _ActiveChannel = 0;
  343. if (_WritePendingNB) {
  344. _WriteDCC(_PendingPacketNB);
  345. _WritePendingNB = 0;
  346. }
  347. if (_pfOnTx[Channel]) {
  348. _pfOnTx[Channel](Channel);
  349. }
  350. return;
  351. }
  352. //
  353. // Check if a data item is pending
  354. //
  355. if (_WritePending) {
  356. _WriteDCC(_Data);
  357. _WritePending = 0;
  358. return;
  359. }
  360. //
  361. // Check if a read command is pending
  362. //
  363. if (_NumReadItems) {
  364. if (_Command & DCC_OP_GET_CAPS) {
  365. Data = (DCC_CONFIG | DCC_SIGNATURE);
  366. Data |= (JLINKDCC_AbortOccurred << 8); // write abort status
  367. JLINKDCC_AbortOccurred = 0;
  368. } else {
  369. // Clear before next read
  370. JLINKDCC_AbortOccurred = 0;
  371. if (_Command & DCC_OP_READ_U8) {
  372. Data = *(U8*)_Addr;
  373. _Addr += 1;
  374. } else if (_Command & DCC_OP_READ_U16) {
  375. Data = *(U16*)_Addr;
  376. _Addr += 2;
  377. } else if (_Command & DCC_OP_READ_U32) {
  378. Data = *(U32*)_Addr;
  379. _Addr += 4;
  380. if ((Data & 0xFF800000) == 0x93800000) { // Do we need to use the escape sequence and split it up into 2 transfers ?
  381. _Data = 0x93c10000 | (Data >> 16);
  382. Data = 0x93c00000 | (Data & 0xFFFF);
  383. _WritePending = 1;
  384. }
  385. }
  386. if (JLINKDCC_AbortOccurred) {
  387. Data = 0x93c20000; // read abort status
  388. _WritePending = 0;
  389. JLINKDCC_AbortOccurred = 0; // clear it
  390. }
  391. }
  392. _WriteDCC(Data);
  393. _NumReadItems--;
  394. return;
  395. }
  396. //
  397. // Handle terminal out. Up to 2 bytes in one 32-bit unit
  398. //
  399. NumBytes = _WrPos - _RdPos;
  400. if (NumBytes < 0) {
  401. NumBytes += sizeof(_acBuffer);
  402. }
  403. if (NumBytes) {
  404. int i;
  405. if (NumBytes > 2) {
  406. NumBytes = 2;
  407. }
  408. if (NumBytes == 2) {
  409. Data = 0x93A00000;
  410. } else {
  411. Data = 0x93800000;
  412. }
  413. for (i = 0; i < NumBytes; i++) {
  414. Data |= _acBuffer[_RdPos] << (8*i);
  415. _RdPos++;
  416. if (_RdPos == sizeof(_acBuffer)) {
  417. _RdPos = 0;
  418. }
  419. }
  420. _WriteDCC(Data);
  421. }
  422. }
  423. /*********************************************************************
  424. *
  425. * Public code
  426. *
  427. **********************************************************************
  428. */
  429. /*********************************************************************
  430. *
  431. * JLINKDCC_Process
  432. *
  433. * Function description
  434. * This function should be called more or less regularily to allow
  435. * memory reads while the application progam is running.
  436. * The more often it is called, the higher the transfer speed.
  437. */
  438. void JLINKDCC_Process(void) {
  439. //
  440. // Avoid problems if this code is called from multiple threads or interrupts
  441. //
  442. if (JLINKDCC_IsInHandler) {
  443. return;
  444. }
  445. JLINKDCC_IsInHandler = 1;
  446. _HandleRead();
  447. _HandleWrite();
  448. JLINKDCC_IsInHandler = 0;
  449. }
  450. /*********************************************************************
  451. *
  452. * JLINKDCC_SendChar
  453. *
  454. * Function description
  455. * Sends a single char to terminal out.
  456. */
  457. void JLINKDCC_SendChar(char c) {
  458. int Pos;
  459. Pos = _WrPos + 1;
  460. if (Pos == sizeof(_acBuffer)) {
  461. Pos = 0;
  462. }
  463. if (Pos == _RdPos) {
  464. return;
  465. }
  466. _acBuffer[_WrPos] = c;
  467. _WrPos = Pos;
  468. }
  469. /*********************************************************************
  470. *
  471. * JLINKDCC_SendString
  472. *
  473. * Function description
  474. * Sends a NUL- terminated string to terminal out.
  475. */
  476. void JLINKDCC_SendString(const char * s) {
  477. char c;
  478. while (1) {
  479. c = *s++;
  480. if (c == 0) {
  481. break;
  482. }
  483. JLINKDCC_SendChar(c);
  484. }
  485. }
  486. /*********************************************************************
  487. *
  488. * JLINKDCC_SendCharOnChannelNB
  489. *
  490. * Function description
  491. * Send data to the host on selected channel.
  492. * This function is non-blocking.
  493. * If data can not be send it is stored in a buffer
  494. * and sent later, when the DCC Handler is called.
  495. */
  496. void JLINKDCC_SendCharOnChannelNB(unsigned Channel, U8 Data) {
  497. U32 DCCPacket;
  498. int DCCBusy;
  499. DCCPacket = 0x93800000 | (Channel << 16);
  500. DCCPacket |= Data;
  501. DCCBusy = _ReadDCCStat() & 2;
  502. if (DCCBusy == 0) {
  503. _WriteDCC(DCCPacket);
  504. } else {
  505. _WritePendingNB = 1;
  506. _PendingPacketNB = DCCPacket;
  507. }
  508. _ActiveChannel = Channel;
  509. }
  510. /*********************************************************************
  511. *
  512. * JLINKDCC_SendOnChannel
  513. *
  514. * Function description
  515. * Send data to the host.
  516. * 32 channels are available, channel 0 is used for terminal output and is buffered,
  517. * all other channels are unbuffered.
  518. */
  519. void JLINKDCC_SendOnChannel(unsigned Channel, U8 * pData, int NumItems) {
  520. U32 Data;
  521. if (Channel == DCC_CHANNEL_TERMINAL) {
  522. while (NumItems-- > 0) {
  523. Data = *pData++;
  524. JLINKDCC_SendChar(Data);
  525. }
  526. } else {
  527. while (NumItems-- > 0) {
  528. Data = *pData++;
  529. if (NumItems > 0) {
  530. Data |= *pData++ << 8;
  531. NumItems--;
  532. Data |= 0x200000;
  533. }
  534. Data |= 0x93800000;
  535. Data |= Channel << 16;
  536. while (_ReadDCCStat() & 2); // Wait until we can send data
  537. _WriteDCC(Data);
  538. }
  539. }
  540. }
  541. /*********************************************************************
  542. *
  543. * JLINKDCC_SetpfOnRx
  544. *
  545. */
  546. void JLINKDCC_SetpfOnRx(unsigned Channel, void (* pf)(unsigned Channel, U8 Data)) {
  547. _pfOnRx[Channel] = pf;
  548. }
  549. /*********************************************************************
  550. *
  551. * JLINKDCC_SetpfOnTx
  552. *
  553. */
  554. void JLINKDCC_SetpfOnTx(unsigned Channel, void (* pf)(unsigned Channel)) {
  555. _pfOnTx[Channel] = pf;
  556. }
  557. /*************************** end of file ****************************/