Implement KdpSendBuffer, KdpCalculateChecksum,
[reactos.git] / reactos / drivers / base / kdcom / i386 / kdbg.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: drivers/base/kdcom/kdbg.c
5 * PURPOSE: Serial i/o functions for the kernel debugger.
6 * PROGRAMMER: Alex Ionescu
7 * Hervé Poussineau
8 * Timo Kreuzer
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #define NOEXTAPI
14 #include <ntddk.h>
15 #define NDEBUG
16 #include <halfuncs.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <debug.h>
20 #include "arc/arc.h"
21 #include "windbgkd.h"
22 #include <kddll.h>
23 #include <ioaccess.h> /* port intrinsics */
24
25 typedef struct _KD_PORT_INFORMATION
26 {
27 ULONG ComPort;
28 ULONG BaudRate;
29 ULONG_PTR BaseAddress;
30 } KD_PORT_INFORMATION, *PKD_PORT_INFORMATION;
31
32 BOOLEAN
33 NTAPI
34 KdPortInitializeEx(
35 IN PKD_PORT_INFORMATION PortInformation,
36 IN ULONG Unknown1,
37 IN ULONG Unknown2);
38
39 BOOLEAN
40 NTAPI
41 KdPortGetByteEx(
42 IN PKD_PORT_INFORMATION PortInformation,
43 OUT PUCHAR ByteReceived);
44
45 BOOLEAN
46 NTAPI
47 KdPortPollByteEx(
48 IN PKD_PORT_INFORMATION PortInformation,
49 OUT PUCHAR ByteReceived);
50
51 VOID
52 NTAPI
53 KdPortPutByteEx(
54 IN PKD_PORT_INFORMATION PortInformation,
55 IN UCHAR ByteToSend);
56
57 /* serial debug connection */
58 #define DEFAULT_DEBUG_PORT 2 /* COM2 */
59 #define DEFAULT_DEBUG_COM1_IRQ 4 /* COM1 IRQ */
60 #define DEFAULT_DEBUG_COM2_IRQ 3 /* COM2 IRQ */
61 #define DEFAULT_DEBUG_BAUD_RATE 115200 /* 115200 Baud */
62
63 #define DEFAULT_BAUD_RATE 19200
64
65 #ifdef _M_IX86
66 const ULONG BaseArray[5] = {0, 0x3F8, 0x2F8, 0x3E8, 0x2E8};
67 #elif defined(_M_PPC)
68 const ULONG BaseArray[2] = {0, 0x800003f8};
69 #elif defined(_M_MIPS)
70 const ULONG BaseArray[3] = {0, 0x80006000, 0x80007000};
71 #elif defined(_M_ARM)
72 const ULONG BaseArray[2] = {0, 0xF1012000};
73 #elif defined(_M_AMD64)
74 const ULONG BaseArray[5] = {0, 0x3F8, 0x2F8, 0x3E8, 0x2E8};
75 #else
76 #error Unknown architecture
77 #endif
78
79 /* MACROS *******************************************************************/
80
81 #define SER_RBR(x) ((PUCHAR)(x)+0)
82 #define SER_THR(x) ((PUCHAR)(x)+0)
83 #define SER_DLL(x) ((PUCHAR)(x)+0)
84 #define SER_IER(x) ((PUCHAR)(x)+1)
85 #define SR_IER_ERDA 0x01
86 #define SR_IER_ETHRE 0x02
87 #define SR_IER_ERLSI 0x04
88 #define SR_IER_EMS 0x08
89 #define SR_IER_ALL 0x0F
90 #define SER_DLM(x) ((PUCHAR)(x)+1)
91 #define SER_IIR(x) ((PUCHAR)(x)+2)
92 #define SER_FCR(x) ((PUCHAR)(x)+2)
93 #define SR_FCR_ENABLE_FIFO 0x01
94 #define SR_FCR_CLEAR_RCVR 0x02
95 #define SR_FCR_CLEAR_XMIT 0x04
96 #define SER_LCR(x) ((PUCHAR)(x)+3)
97 #define SR_LCR_CS5 0x00
98 #define SR_LCR_CS6 0x01
99 #define SR_LCR_CS7 0x02
100 #define SR_LCR_CS8 0x03
101 #define SR_LCR_ST1 0x00
102 #define SR_LCR_ST2 0x04
103 #define SR_LCR_PNO 0x00
104 #define SR_LCR_POD 0x08
105 #define SR_LCR_PEV 0x18
106 #define SR_LCR_PMK 0x28
107 #define SR_LCR_PSP 0x38
108 #define SR_LCR_BRK 0x40
109 #define SR_LCR_DLAB 0x80
110 #define SER_MCR(x) ((PUCHAR)(x)+4)
111 #define SR_MCR_DTR 0x01
112 #define SR_MCR_RTS 0x02
113 #define SR_MCR_OUT1 0x04
114 #define SR_MCR_OUT2 0x08
115 #define SR_MCR_LOOP 0x10
116 #define SER_LSR(x) ((PUCHAR)(x)+5)
117 #define SR_LSR_DR 0x01
118 #define SR_LSR_TBE 0x20
119 #define SER_MSR(x) ((PUCHAR)(x)+6)
120 #define SR_MSR_CTS 0x10
121 #define SR_MSR_DSR 0x20
122 #define SER_SCR(x) ((PUCHAR)(x)+7)
123
124
125 /* GLOBAL VARIABLES *********************************************************/
126
127 /* STATIC VARIABLES *********************************************************/
128
129 static KD_PORT_INFORMATION DefaultPort = { 0, 0, 0 };
130
131 /* The com port must only be initialized once! */
132 static BOOLEAN PortInitialized = FALSE;
133
134 ULONG KdpPort;
135 ULONG KdpPortIrq;
136
137 /* STATIC FUNCTIONS *********************************************************/
138
139 static BOOLEAN
140 KdpDoesComPortExist(
141 IN ULONG_PTR BaseAddress)
142 {
143 BOOLEAN found;
144 UCHAR mcr;
145 UCHAR msr;
146
147 found = FALSE;
148
149 /* save Modem Control Register (MCR) */
150 mcr = READ_PORT_UCHAR(SER_MCR(BaseAddress));
151
152 /* enable loop mode (set Bit 4 of the MCR) */
153 WRITE_PORT_UCHAR(SER_MCR(BaseAddress), SR_MCR_LOOP);
154
155 /* clear all modem output bits */
156 WRITE_PORT_UCHAR(SER_MCR(BaseAddress), SR_MCR_LOOP);
157
158 /* read the Modem Status Register */
159 msr = READ_PORT_UCHAR(SER_MSR(BaseAddress));
160
161 /*
162 * the upper nibble of the MSR (modem output bits) must be
163 * equal to the lower nibble of the MCR (modem input bits)
164 */
165 if ((msr & 0xF0) == 0x00)
166 {
167 /* set all modem output bits */
168 WRITE_PORT_UCHAR(SER_MCR(BaseAddress), SR_MCR_DTR | SR_MCR_RTS | SR_MCR_OUT1 | SR_MCR_OUT2 | SR_MCR_LOOP);
169
170 /* read the Modem Status Register */
171 msr = READ_PORT_UCHAR(SER_MSR(BaseAddress));
172
173 /*
174 * the upper nibble of the MSR (modem output bits) must be
175 * equal to the lower nibble of the MCR (modem input bits)
176 */
177 if ((msr & 0xF0) == 0xF0)
178 {
179 /*
180 * setup a resonable state for the port:
181 * enable fifo and clear recieve/transmit buffers
182 */
183 WRITE_PORT_UCHAR(SER_FCR(BaseAddress),
184 (SR_FCR_ENABLE_FIFO | SR_FCR_CLEAR_RCVR | SR_FCR_CLEAR_XMIT));
185 WRITE_PORT_UCHAR(SER_FCR(BaseAddress), 0);
186 READ_PORT_UCHAR(SER_RBR(BaseAddress));
187 WRITE_PORT_UCHAR(SER_IER(BaseAddress), 0);
188 found = TRUE;
189 }
190 }
191
192 /* restore MCR */
193 WRITE_PORT_UCHAR(SER_MCR(BaseAddress), mcr);
194
195 return found;
196 }
197
198
199 /* FUNCTIONS ****************************************************************/
200
201 NTSTATUS
202 DriverEntry(
203 IN PDRIVER_OBJECT DriverObject,
204 IN PUNICODE_STRING RegistryPath)
205 {
206 return STATUS_SUCCESS;
207 }
208
209 /* HAL.KdPortInitialize */
210 BOOLEAN
211 NTAPI
212 KdPortInitialize(
213 IN PKD_PORT_INFORMATION PortInformation,
214 IN ULONG Unknown1,
215 IN ULONG Unknown2)
216 {
217 SIZE_T i;
218 CHAR buffer[80];
219
220 if (!PortInitialized)
221 {
222 DefaultPort.BaudRate = PortInformation->BaudRate;
223
224 if (PortInformation->ComPort == 0)
225 {
226 for (i = sizeof(BaseArray) / sizeof(BaseArray[0]) - 1; i > 0; i--)
227 {
228 if (KdpDoesComPortExist(BaseArray[i]))
229 {
230 DefaultPort.BaseAddress = BaseArray[i];
231 DefaultPort.ComPort = i;
232 PortInformation->BaseAddress = DefaultPort.BaseAddress;
233 PortInformation->ComPort = DefaultPort.ComPort;
234 break;
235 }
236 }
237 if (i == 0)
238 {
239 sprintf(buffer,
240 "\nKernel Debugger: No COM port found!\n\n");
241 HalDisplayString(buffer);
242 return FALSE;
243 }
244 }
245
246 PortInitialized = TRUE;
247 }
248
249 /* initialize port */
250 if (!KdPortInitializeEx(&DefaultPort, Unknown1, Unknown2))
251 return FALSE;
252
253 /* set global info */
254 *KdComPortInUse = (PUCHAR)DefaultPort.BaseAddress;
255
256 return TRUE;
257 }
258
259
260 /* HAL.KdPortInitializeEx */
261 BOOLEAN
262 NTAPI
263 KdPortInitializeEx(
264 IN PKD_PORT_INFORMATION PortInformation,
265 IN ULONG Unknown1,
266 IN ULONG Unknown2)
267 {
268 ULONG_PTR ComPortBase;
269 CHAR buffer[80];
270 ULONG divisor;
271 UCHAR lcr;
272
273 #ifdef _ARM_
274 UNIMPLEMENTED;
275 return FALSE;
276 #endif
277
278 if (PortInformation->BaudRate == 0)
279 PortInformation->BaudRate = DEFAULT_BAUD_RATE;
280
281 if (PortInformation->ComPort == 0)
282 return FALSE;
283
284 if (!KdpDoesComPortExist(BaseArray[PortInformation->ComPort]))
285 {
286 sprintf(buffer,
287 "\nKernel Debugger: Serial port not found!\n\n");
288 HalDisplayString(buffer);
289 return FALSE;
290 }
291
292 ComPortBase = BaseArray[PortInformation->ComPort];
293 PortInformation->BaseAddress = ComPortBase;
294 #ifndef NDEBUG
295 sprintf(buffer,
296 "\nSerial port COM%ld found at 0x%lx\n",
297 PortInformation->ComPort,
298 ComPortBase);
299 HalDisplayString(buffer);
300 #endif /* NDEBUG */
301
302 /* set baud rate and data format (8N1) */
303
304 /* turn on DTR and RTS */
305 WRITE_PORT_UCHAR(SER_MCR(ComPortBase), SR_MCR_DTR | SR_MCR_RTS);
306
307 /* set DLAB */
308 lcr = READ_PORT_UCHAR(SER_LCR(ComPortBase)) | SR_LCR_DLAB;
309 WRITE_PORT_UCHAR(SER_LCR(ComPortBase), lcr);
310
311 /* set baud rate */
312 divisor = 115200 / PortInformation->BaudRate;
313 WRITE_PORT_UCHAR(SER_DLL(ComPortBase), (UCHAR)(divisor & 0xff));
314 WRITE_PORT_UCHAR(SER_DLM(ComPortBase), (UCHAR)((divisor >> 8) & 0xff));
315
316 /* reset DLAB and set 8N1 format */
317 WRITE_PORT_UCHAR(SER_LCR(ComPortBase),
318 SR_LCR_CS8 | SR_LCR_ST1 | SR_LCR_PNO);
319
320 /* read junk out of the RBR */
321 lcr = READ_PORT_UCHAR(SER_RBR(ComPortBase));
322
323 #ifndef NDEBUG
324 /* print message to blue screen */
325 sprintf(buffer,
326 "\nKernel Debugger: COM%ld (Port 0x%lx) BaudRate %ld\n\n",
327 PortInformation->ComPort,
328 ComPortBase,
329 PortInformation->BaudRate);
330
331 HalDisplayString(buffer);
332 #endif /* NDEBUG */
333
334 return TRUE;
335 }
336
337
338 /* HAL.KdPortGetByte */
339 BOOLEAN
340 NTAPI
341 KdPortGetByte(
342 OUT PUCHAR ByteReceived)
343 {
344 if (!PortInitialized)
345 return FALSE;
346 return KdPortGetByteEx(&DefaultPort, ByteReceived);
347 }
348
349
350 /* HAL.KdPortGetByteEx */
351 BOOLEAN
352 NTAPI
353 KdPortGetByteEx(
354 IN PKD_PORT_INFORMATION PortInformation,
355 OUT PUCHAR ByteReceived)
356 {
357 PUCHAR ComPortBase = (PUCHAR)PortInformation->BaseAddress;
358
359 if ((READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_DR))
360 {
361 *ByteReceived = READ_PORT_UCHAR(SER_RBR(ComPortBase));
362 return TRUE;
363 }
364
365 return FALSE;
366 }
367
368
369 /* HAL.KdPortPollByte */
370 BOOLEAN
371 NTAPI
372 KdPortPollByte(
373 OUT PUCHAR ByteReceived)
374 {
375 if (!PortInitialized)
376 return FALSE;
377 return KdPortPollByteEx(&DefaultPort, ByteReceived);
378 }
379
380
381 /* HAL.KdPortPollByteEx */
382 BOOLEAN
383 NTAPI
384 KdPortPollByteEx(
385 IN PKD_PORT_INFORMATION PortInformation,
386 OUT PUCHAR ByteReceived)
387 {
388 PUCHAR ComPortBase = (PUCHAR)PortInformation->BaseAddress;
389
390 while ((READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_DR) == 0)
391 ;
392
393 *ByteReceived = READ_PORT_UCHAR(SER_RBR(ComPortBase));
394
395 return TRUE;
396 }
397
398
399 /* HAL.KdPortPutByte */
400 VOID
401 NTAPI
402 KdPortPutByte(
403 IN UCHAR ByteToSend)
404 {
405 if (!PortInitialized)
406 return;
407 KdPortPutByteEx(&DefaultPort, ByteToSend);
408 }
409
410 /* HAL.KdPortPutByteEx */
411 VOID
412 NTAPI
413 KdPortPutByteEx(
414 IN PKD_PORT_INFORMATION PortInformation,
415 IN UCHAR ByteToSend)
416 {
417 PUCHAR ComPortBase = (PUCHAR)PortInformation->BaseAddress;
418
419 while ((READ_PORT_UCHAR(SER_LSR(ComPortBase)) & SR_LSR_TBE) == 0)
420 ;
421
422 WRITE_PORT_UCHAR(SER_THR(ComPortBase), ByteToSend);
423 }
424
425
426 /* HAL.KdPortRestore */
427 VOID
428 NTAPI
429 KdPortRestore(VOID)
430 {
431 UNIMPLEMENTED;
432 }
433
434
435 /* HAL.KdPortSave */
436 VOID
437 NTAPI
438 KdPortSave(VOID)
439 {
440 UNIMPLEMENTED;
441 }
442
443
444 /* HAL.KdPortDisableInterrupts */
445 BOOLEAN
446 NTAPI
447 KdPortDisableInterrupts(VOID)
448 {
449 UCHAR ch;
450
451 if (!PortInitialized)
452 return FALSE;
453
454 ch = READ_PORT_UCHAR(SER_MCR(DefaultPort.BaseAddress));
455 ch &= (~(SR_MCR_OUT1 | SR_MCR_OUT2));
456 WRITE_PORT_UCHAR(SER_MCR(DefaultPort.BaseAddress), ch);
457
458 ch = READ_PORT_UCHAR(SER_IER(DefaultPort.BaseAddress));
459 ch &= (~SR_IER_ALL);
460 WRITE_PORT_UCHAR(SER_IER(DefaultPort.BaseAddress), ch);
461
462 return TRUE;
463 }
464
465
466 /* HAL.KdPortEnableInterrupts */
467 BOOLEAN
468 NTAPI
469 KdPortEnableInterrupts(VOID)
470 {
471 UCHAR ch;
472
473 if (PortInitialized == FALSE)
474 return FALSE;
475
476 ch = READ_PORT_UCHAR(SER_IER(DefaultPort.BaseAddress));
477 ch &= (~SR_IER_ALL);
478 ch |= SR_IER_ERDA;
479 WRITE_PORT_UCHAR(SER_IER(DefaultPort.BaseAddress), ch);
480
481 ch = READ_PORT_UCHAR(SER_MCR(DefaultPort.BaseAddress));
482 ch &= (~SR_MCR_LOOP);
483 ch |= (SR_MCR_OUT1 | SR_MCR_OUT2);
484 WRITE_PORT_UCHAR(SER_MCR(DefaultPort.BaseAddress), ch);
485
486 return TRUE;
487 }
488
489 /* NEW INTERNAL FUNCTIONS ****************************************************/
490
491 /******************************************************************************
492 * \name KdpCalculateChecksum
493 * \brief Calculates the checksum for the packet data.
494 * \param Buffer Pointer to the packet data.
495 * \param Length Length of data in bytes.
496 * \return The calculated checksum.
497 * \sa http://www.vista-xp.co.uk/forums/technical-reference-library/2540-basics-debugging.html
498 */
499 ULONG
500 NTAPI
501 KdpCalculateChecksum(
502 IN PVOID Buffer,
503 IN ULONG Length)
504 {
505 ULONG i, Checksum = 0;
506
507 for (i = 0; i < Length; i++)
508 {
509 Checksum += ((PUCHAR)Buffer)[i];
510 }
511 return Checksum;
512 }
513
514 /******************************************************************************
515 * \name KdpSendBuffer
516 * \brief Sends a buffer of data to the KD port.
517 * \param Buffer Pointer to the data.
518 * \param Size Size of data in bytes.
519 */
520 VOID
521 NTAPI
522 KdpSendBuffer(
523 IN PVOID Buffer,
524 IN ULONG Size)
525 {
526 INT i;
527 for (i = 0; i < Size; i++)
528 {
529 KdPortPutByteEx(&DefaultPort, ((PUCHAR)Buffer)[i]);
530 }
531 }
532
533 /******************************************************************************
534 * \name KdDebuggerInitialize0
535 * \brief Phase 0 initialization.
536 * \param [opt] LoaderBlock Pointer to the Loader parameter block. Can be NULL.
537 * \return Status
538 */
539 NTSTATUS
540 NTAPI
541 KdDebuggerInitialize0(
542 IN PLOADER_PARAMETER_BLOCK LoaderBlock OPTIONAL)
543 {
544 ULONG Value;
545 PCHAR CommandLine, Port, BaudRate, Irq;
546
547 /* Apply default values */
548 KdpPortIrq = 0;
549 DefaultPort.ComPort = DEFAULT_DEBUG_PORT;
550 DefaultPort.BaudRate = DEFAULT_DEBUG_BAUD_RATE;
551
552 /* Check if e have a LoaderBlock */
553 if (LoaderBlock)
554 {
555 /* Get the Command Line */
556 CommandLine = LoaderBlock->LoadOptions;
557
558 /* Upcase it */
559 _strupr(CommandLine);
560
561 /* Get the port and baud rate */
562 Port = strstr(CommandLine, "DEBUGPORT");
563 BaudRate = strstr(CommandLine, "BAUDRATE");
564 Irq = strstr(CommandLine, "IRQ");
565
566 /* Check if we got the /DEBUGPORT parameter */
567 if (Port)
568 {
569 /* Move past the actual string, to reach the port*/
570 Port += strlen("DEBUGPORT");
571
572 /* Now get past any spaces and skip the equal sign */
573 while (*Port == ' ') Port++;
574 Port++;
575
576 /* Do we have a serial port? */
577 if (strncmp(Port, "COM", 3) != 0)
578 {
579 return STATUS_INVALID_PARAMETER;
580 }
581
582 /* Gheck for a valid Serial Port */
583 Port += 3;
584 Value = atol(Port);
585 if (Value > 4)
586 {
587 return STATUS_INVALID_PARAMETER;
588 }
589
590 /* Set the port to use */
591 DefaultPort.ComPort = Value;
592 }
593
594 /* Check if we got a baud rate */
595 if (BaudRate)
596 {
597 /* Move past the actual string, to reach the rate */
598 BaudRate += strlen("BAUDRATE");
599
600 /* Now get past any spaces */
601 while (*BaudRate == ' ') BaudRate++;
602
603 /* And make sure we have a rate */
604 if (*BaudRate)
605 {
606 /* Read and set it */
607 Value = atol(BaudRate + 1);
608 if (Value) DefaultPort.BaudRate = Value;
609 }
610 }
611
612 /* Check Serial Port Settings [IRQ] */
613 if (Irq)
614 {
615 /* Move past the actual string, to reach the rate */
616 Irq += strlen("IRQ");
617
618 /* Now get past any spaces */
619 while (*Irq == ' ') Irq++;
620
621 /* And make sure we have an IRQ */
622 if (*Irq)
623 {
624 /* Read and set it */
625 Value = atol(Irq + 1);
626 if (Value) KdpPortIrq = Value;
627 }
628 }
629 }
630
631 /* Get base address */
632 DefaultPort.BaseAddress = BaseArray[DefaultPort.ComPort];
633
634 /* Check if the COM port does exist */
635 if (!KdpDoesComPortExist(DefaultPort.BaseAddress))
636 {
637 return STATUS_INVALID_PARAMETER;
638 }
639
640 /* Initialize the port */
641 KdPortInitializeEx(&DefaultPort, 0, 0);
642
643 return STATUS_SUCCESS;
644 }
645
646 /******************************************************************************
647 * \name KdDebuggerInitialize0
648 * \brief Phase 0 initialization.
649 * \param [opt] LoaderBlock Pointer to the Loader parameter block. Can be NULL.
650 * \return Status
651 */
652 NTSTATUS
653 NTAPI
654 KdDebuggerInitialize1(
655 IN PLOADER_PARAMETER_BLOCK LoaderBlock OPTIONAL)
656 {
657 UNIMPLEMENTED;
658 return STATUS_NOT_IMPLEMENTED;
659 }
660
661 /*
662 * @implemented
663 */
664 NTSTATUS
665 NTAPI
666 KdSave(
667 IN BOOLEAN SleepTransition)
668 {
669 /* Nothing to do on COM ports */
670 return STATUS_SUCCESS;
671 }
672
673 /*
674 * @implemented
675 */
676 NTSTATUS
677 NTAPI
678 KdRestore(
679 IN BOOLEAN SleepTransition)
680 {
681 /* Nothing to do on COM ports */
682 return STATUS_SUCCESS;
683 }
684
685 /*
686 * @unimplemented
687 */
688 VOID
689 NTAPI
690 KdSendPacket(
691 IN ULONG PacketType,
692 IN PSTRING MessageHeader,
693 IN PSTRING MessageData,
694 IN OUT PKD_CONTEXT Context)
695 {
696 ULONG i;
697
698 switch (PacketType)
699 {
700 case PACKET_TYPE_KD_DEBUG_IO:
701 /* Copy Message to COM port */
702 for (i = 0; i < MessageData->Length; i++)
703 {
704 char c = MessageData->Buffer[i];
705 if ( c == 10 )
706 {
707 KdPortPutByteEx(&DefaultPort, 13);
708 KdPortPutByteEx(&DefaultPort, 10);
709 }
710 else
711 {
712 KdPortPutByteEx(&DefaultPort, c);
713 }
714 }
715 break;
716
717 default:
718 break;
719 }
720
721 return;
722 }
723
724 /*
725 * @unimplemented
726 */
727 KDSTATUS
728 NTAPI
729 KdReceivePacket(
730 IN ULONG PacketType,
731 OUT PSTRING MessageHeader,
732 OUT PSTRING MessageData,
733 OUT PULONG DataLength,
734 IN OUT PKD_CONTEXT Context)
735 {
736 // UNIMPLEMENTED;
737 return 0;
738 }
739
740 /* EOF */