2 Copyright © 1995-2018, The AROS Development Team. All rights reserved.
5 Desc: Intel IA-32 APIC driver.
8 #include <aros/macros.h>
11 #include <exec/types.h>
13 #define __KERNEL_NOLIBBASE__
14 #include <proto/kernel.h>
15 #include <proto/exec.h>
16 #include <proto/acpica.h>
18 #include <acpica/acnames.h>
19 #include <acpica/accommon.h>
23 #include "kernel_base.h"
24 #include "kernel_intern.h"
25 #include "kernel_objects.h"
26 #include "kernel_debug.h"
27 #include "kernel_syscall.h"
28 #include "kernel_timer.h"
29 #include "kernel_ipi.h"
31 #include "kernel_interrupts.h"
34 #include "apic_ia32.h"
38 #define DWAKE(x) /* Badly interferes with AP startup */
39 #define DID(x) /* Badly interferes with everything */
40 /* #define DEBUG_WAIT */
43 * On i386 platform we need to support various quirks of old APICs.
44 * x86-64 is free of that crap.
50 extern int core_APICErrorHandle(struct ExceptionContext
*, void *, void *);
51 extern int core_APICSpuriousHandle(struct ExceptionContext
*, void *, void *);
52 extern int APICHeartbeatServer(struct ExceptionContext
*regs
, struct KernelBase
*KernelBase
, struct ExecBase
*SysBase
);
54 /* APIC Interrupt Controller Functions ... ***************************/
56 struct APICInt_Private
61 icid_t
APICInt_Register(struct KernelBase
*KernelBase
)
63 DINT(bug("[Kernel:APIC-IA32] %s()\n", __func__
));
65 return (icid_t
)APICInt_IntrController
.ic_Node
.ln_Type
;
68 BOOL
APICInt_Init(struct KernelBase
*KernelBase
, icid_t instanceCount
)
70 struct PlatformData
*kernPlatD
= (struct PlatformData
*)KernelBase
->kb_PlatformData
;
71 struct ACPIData
*acpiData
= kernPlatD
->kb_ACPI
;
72 struct APICData
*apicPrivate
= kernPlatD
->kb_APIC
;
76 DINT(bug("[Kernel:APIC-IA32] %s(%d)\n", __func__
, instanceCount
));
78 /* It's not fatal to fail on these IRQs */
79 if ((ssp
= SuperState()) != NULL
)
81 /* Set up the APIC IRQs for CPU #0 */
82 for (irq
= (APIC_IRQ_BASE
- X86_CPU_EXCEPT_COUNT
); irq
< HW_IRQ_COUNT
; irq
++)
84 if (!krnInitInterrupt(KernelBase
, irq
, APICInt_IntrController
.ic_Node
.ln_Type
, 0))
86 D(bug("[Kernel:APIC-IA32] %s: failed to obtain IRQ %d\n", __func__
, irq
);)
90 /* Don't enable the vector yet */
91 if (!core_SetIDTGate((apicidt_t
*)apicPrivate
->cores
[0].cpu_IDT
, HW_IRQ_BASE
+ irq
, (uintptr_t)IntrDefaultGates
[HW_IRQ_BASE
+ irq
], FALSE
))
93 bug("[Kernel:APIC-IA32] %s: failed to set IRQ %d's Vector gate\n", __func__
, irq
);
103 * If we have at least 32 APIC interrupts available (the
104 * most a single MSI device will request) then report that
107 if ((count
> 32) && (acpiData
->acpi_fadt
))
109 ACPI_TABLE_FADT
*fadt
= (ACPI_TABLE_FADT
*)acpiData
->acpi_fadt
;
111 if ((!(fadt
->BootFlags
& ACPI_FADT_NO_MSI
)) &&
112 (!(kernPlatD
->kb_PDFlags
& PLATFORMF_HAVEMSI
)))
114 // TODO: Register the MSI interrupt controller..
115 kernPlatD
->kb_PDFlags
|= PLATFORMF_HAVEMSI
;
116 bug("[Kernel:APIC-IA32] MSI Interrupts enabled\n");
122 BOOL
APICInt_DisableIRQ(APTR icPrivate
, icid_t icInstance
, icid_t intNum
)
124 struct PlatformData
*kernPlatD
= (struct PlatformData
*)KernelBase
->kb_PlatformData
;
125 struct APICData
*apicPrivate
= kernPlatD
->kb_APIC
;
126 apicid_t cpuNum
= KrnGetCPUNumber();
131 DINT(bug("[Kernel:APIC-IA32.%03u] %s(#$%02X)\n", cpuNum
, __func__
, intNum
));
133 IGATES
= (apicidt_t
*)apicPrivate
->cores
[cpuNum
].cpu_IDT
;
135 if ((KrnIsSuper()) || ((ssp
= SuperState()) != NULL
))
137 IGATES
[HW_IRQ_BASE
+ intNum
].p
= 0;
147 BOOL
APICInt_EnableIRQ(APTR icPrivate
, icid_t icInstance
, icid_t intNum
)
149 struct PlatformData
*kernPlatD
= (struct PlatformData
*)KernelBase
->kb_PlatformData
;
150 struct APICData
*apicPrivate
= kernPlatD
->kb_APIC
;
151 apicid_t cpuNum
= KrnGetCPUNumber();
156 DINT(bug("[Kernel:APIC-IA32.%03u] %s(#$%02X)\n", cpuNum
, __func__
, intNum
));
158 IGATES
= (apicidt_t
*)apicPrivate
->cores
[cpuNum
].cpu_IDT
;
160 if ((KrnIsSuper()) || ((ssp
= SuperState()) != NULL
))
162 IGATES
[HW_IRQ_BASE
+ intNum
].p
= 1;
172 BOOL
APICInt_AckIntr(APTR icPrivate
, icid_t icInstance
, icid_t intNum
)
176 DINT(bug("[Kernel:APIC-IA32] %s(%03u #$%02X)\n", __func__
, icInstance
, intNum
));
178 /* Write zero to EOI of APIC */
179 apic_base
= core_APIC_GetBase();
181 APIC_REG(apic_base
, APIC_EOI
) = 0;
186 struct IntrController APICInt_IntrController
=
189 .ln_Name
= "x86 Local APIC",
193 AROS_MAKE_ID('A','P','I','C'),
203 /* APIC IPI Related Functions ... ***************************/
205 static ULONG
DoIPI(IPTR __APICBase
, ULONG target
, ULONG cmd
)
207 ULONG ipisend_timeout
, status_ipisend
;
210 apicid_t cpuNum
= KrnGetCPUNumber();
211 bug("[Kernel:APIC-IA32.%03u] %s: Command 0x%08X to target %03u\n", cpuNum
, __func__
, cmd
, target
);
216 * First we write target APIC ID into high command register.
217 * Writing to the low register triggers the IPI itself.
219 APIC_REG(__APICBase
, APIC_ICRH
) = target
<< 24;
220 APIC_REG(__APICBase
, APIC_ICRL
) = cmd
;
222 D(bug("[Kernel:APIC-IA32.%03u] %s: Waiting for IPI to complete ", cpuNum
, __func__
));
224 for (ipisend_timeout
= 1000; ipisend_timeout
> 0; ipisend_timeout
--)
228 if ((ipisend_timeout
% 100) == 0)
233 status_ipisend
= APIC_REG(__APICBase
, APIC_ICRL
) & ICR_DS
;
234 /* Delivery status resets to 0 when delivery is done */
235 if (status_ipisend
== 0)
239 D(bug("[Kernel:APIC-IA32.%03u] %s: ... left wait loop (status = 0x%08X)\n", cpuNum
, __func__
, status_ipisend
));
241 return status_ipisend
;
244 /**********************************************************
246 **********************************************************/
248 void core_APIC_Init(struct APICData
*apic
, apicid_t cpuNum
)
250 IPTR __APICBase
= apic
->lapicBase
;
251 ULONG apic_ver
= APIC_REG(__APICBase
, APIC_VERSION
);
252 ULONG maxlvt
= APIC_LVT(apic_ver
), calibrated
= 0;
253 LONG lapic_initial
, lapic_final
;
254 UQUAD tsc_initial
, tsc_final
;
255 UQUAD calibrated_tsc
= 0;
257 icintrid_t coreICInstID
;
260 /* 82489DX doesn't report no. of LVT entries. */
261 if (!APIC_INTEGRATED(apic_ver
))
265 if ((coreICInstID
= krnAddInterruptController(KernelBase
, &APICInt_IntrController
)) != (icintrid_t
)-1)
270 D(bug("[Kernel:APIC-IA32.%03u] %s: APIC IC ID #%d:%d\n", cpuNum
, __func__
, ICINTR_ICID(coreICInstID
), ICINTR_INST(coreICInstID
)));
273 * NB: - BSP calls us in user mode, but AP's call us from supervisor
275 if ((KrnIsSuper()) || ((ssp
= SuperState()) != NULL
))
277 /* Obtain/set the critical IRQs and Vectors */
278 for (i
= 0; i
< X86_CPU_EXCEPT_COUNT
; i
++)
280 if (!core_SetIDTGate((apicidt_t
*)apic
->cores
[cpuNum
].cpu_IDT
, i
, (uintptr_t)IntrDefaultGates
[i
], TRUE
))
282 krnPanic(NULL
, "Failed to set CPU Exception Vector\n"
283 "Vector #$%02X\n", i
);
286 for (i
= X86_CPU_EXCEPT_COUNT
; i
< APIC_EXCEPT_TOP
; i
++)
288 if (i
== APIC_EXCEPT_SYSCALL
)
291 if (!core_SetIDTGate((apicidt_t
*)apic
->cores
[cpuNum
].cpu_IDT
, APIC_CPU_EXCEPT_TO_VECTOR(i
), (uintptr_t)IntrDefaultGates
[APIC_CPU_EXCEPT_TO_VECTOR(i
)], TRUE
))
293 krnPanic(NULL
, "Failed to set APIC Exception Vector\n"
294 "Vector #$%02X\n", i
);
297 D(bug("[Kernel:APIC-IA32.%03u] %s: APIC Exception Vectors configured\n", cpuNum
, __func__
));
301 KrnAddExceptionHandler(APIC_EXCEPT_ERROR
, core_APICErrorHandle
, KernelBase
, NULL
);
302 KrnAddExceptionHandler(APIC_EXCEPT_SPURIOUS
, core_APICSpuriousHandle
, KernelBase
, NULL
);
304 D(bug("[Kernel:APIC-IA32.%03u] %s: APIC Error Exception handler (exception #$%02X) installed\n", cpuNum
, __func__
, APIC_EXCEPT_ERROR
));
306 for (i
= APIC_EXCEPT_IPI_NOP
; i
<= APIC_EXCEPT_IPI_CAUSE
; i
++)
308 KrnAddExceptionHandler(i
, core_IPIHandle
, (void *)((intptr_t)i
- APIC_EXCEPT_IPI_NOP
), KernelBase
);
310 D(bug("[Kernel:APIC-IA32.%03u] %s: APIC IPI Vectors configured\n", cpuNum
, __func__
));
318 krnPanic(NULL
, "Failed to configure APIC\n"
319 "APIC #%03e ID %03u\n", cpuNum
, apic
->cores
[cpuNum
].cpu_LocalID
);
322 /* Use flat interrupt model with logical destination ID = 1 */
323 APIC_REG(__APICBase
, APIC_DFR
) = DFR_FLAT
;
324 APIC_REG(__APICBase
, APIC_LDR
) = 1 << LDR_ID_SHIFT
;
326 /* Set Task Priority to 'accept all interrupts' */
327 APIC_REG(__APICBase
, APIC_TPR
) = 0;
329 /* Set spurious IRQ vector to 0xFF. APIC enabled, focus check disabled. */
330 APIC_REG(__APICBase
, APIC_SVR
) = SVR_ASE
|APIC_CPU_EXCEPT_TO_VECTOR(APIC_EXCEPT_SPURIOUS
);
333 * Set LINT0 to external and LINT1 to NMI.
334 * These are common defaults and they are going to be overridden by ACPI tables.
336 * On all other LAPICs mask LINT0 and use some fake vector (0xff in this case),
337 * otherwise LAPIC may throw an error.
340 APIC_REG(__APICBase
, APIC_LINT0_VEC
) = LVT_MT_EXT
;
342 APIC_REG(__APICBase
, APIC_LINT0_VEC
) = LVT_MASK
| 0xff;
344 APIC_REG(__APICBase
, APIC_LINT1_VEC
) = LVT_MT_NMI
;
347 /* Due to the Pentium erratum 3AP. */
349 APIC_REG(__APICBase
, APIC_ESR
) = 0;
352 D(bug("[Kernel:APIC-IA32.%03u] %s: APIC ESR before enabling vector: %08x\n", cpuNum
, __func__
, APIC_REG(__APICBase
, APIC_ESR
)));
354 /* Set APIC error interrupt to fixed vector interrupt "APIC_IRQ_ERROR", on APIC error */
355 APIC_REG(__APICBase
, APIC_ERROR_VEC
) = APIC_CPU_EXCEPT_TO_VECTOR(APIC_EXCEPT_ERROR
);
357 /* spec says clear errors after enabling vector. */
359 APIC_REG(__APICBase
, APIC_ESR
) = 0;
361 D(bug("[Kernel:APIC-IA32.%03u] %s: APIC ESR after enabling vector: %08x\n", cpuNum
, __func__
, APIC_REG(__APICBase
, APIC_ESR
)));
364 * Now the tricky thing - calibrate LAPIC timer frequency.
365 * In fact we could simply query CPU's clock frequency, but... x86 sucks. There's no
366 * unified way to get it on whatever CPU. Intel has own way, AMD has own way... Etc... Which, additionally,
367 * varies between CPU generations.
369 * The idea behind the calibration is to run the timer once, and see how many ticks
370 * pass in some defined period of time. Then calculate a proportion.
371 * We use 8253 PIT as our reference.
372 * This calibration algorithm is based on NetBSD one.
375 /* Set the timer to one-shot mode, no interrupt, 1:1 divisor */
376 APIC_REG(__APICBase
, APIC_TIMER_VEC
) = LVT_MASK
| APIC_CPU_EXCEPT_TO_VECTOR(APIC_EXCEPT_HEARTBEAT
);
377 APIC_REG(__APICBase
, APIC_TIMER_DIV
) = TIMER_DIV_1
;
378 APIC_REG(__APICBase
, APIC_TIMER_ICR
) = 0x80000000; /* Just some very large value */
381 * Now wait for 11931 PIT ticks, which is equal to 10 milliseconds.
382 * We don't use pit_udelay() here, because for improved accuracy we need to sample LAPIC timer counter twice,
383 * before and after our actual delay (PIT setup also takes up some time, so LAPIC will count away from its
384 * initial value). We run it 10 times to make up for cache setup discrepancies.
386 for (i
= 0; i
< 10; i
++)
389 lapic_initial
= (LONG
)APIC_REG(__APICBase
, APIC_TIMER_CCR
);
390 tsc_initial
= RDTSC();
392 pit_final
= pit_wait(11931);
395 lapic_final
= (LONG
)APIC_REG(__APICBase
, APIC_TIMER_CCR
);
397 calibrated
+= (((QUAD
)(lapic_initial
- lapic_final
) * 11931LL)/(11931LL - (QUAD
)pit_final
)) ;
398 calibrated_tsc
+= ((tsc_final
- tsc_initial
) * 11931LL) / (11931LL - (QUAD
)pit_final
);
400 apic
->cores
[cpuNum
].cpu_TimerFreq
= 10 * calibrated
;
401 apic
->cores
[cpuNum
].cpu_TSCFreq
= 10 * calibrated_tsc
;
402 D(bug("[Kernel:APIC-IA32.%03u] %s: LAPIC frequency should be %u Hz (%u MHz)\n", cpuNum
, __func__
, apic
->cores
[cpuNum
].cpu_TimerFreq
, (apic
->cores
[cpuNum
].cpu_TimerFreq
+ 500000) / 1000000));
403 D(bug("[Kernel:APIC-IA32.%03u] %s: TSC frequency should be %u kHz (%u MHz)\n", cpuNum
, __func__
, (ULONG
)((apic
->cores
[cpuNum
].cpu_TSCFreq
+ 500)/1000), (ULONG
)((apic
->cores
[cpuNum
].cpu_TSCFreq
+ 500000) / 1000000)));
405 * Once APIC timer has been calibrated -:
406 * # Set it to run at its full frequency.
407 * # Enable the heartbeat vector and use a suitable rate,
408 * otherwise set to reload every second and disable it.
412 KrnAddExceptionHandler(APIC_EXCEPT_HEARTBEAT
, APICHeartbeatServer
, KernelBase
, SysBase
);
414 apic
->flags
|= APF_TIMER
;
417 APIC_REG(__APICBase
, APIC_TIMER_DIV
) = TIMER_DIV_1
;
419 if ((apic
->flags
& APF_TIMER
) &&
420 ((KrnIsSuper()) || ((ssp
= SuperState()) != NULL
)))
422 #if defined(__AROSEXEC_SMP__)
423 tls_t
*apicTLS
= apic
->cores
[cpuNum
].cpu_TLS
;
424 struct X86SchedulerPrivate
*schedData
= apicTLS
->ScheduleData
;
425 D(bug("[Kernel:APIC-IA32.%03u] %s: tls @ 0x%p, scheduling data @ 0x%p\n", cpuNum
, __func__
, apicTLS
, schedData
);)
428 apic
->cores
[cpuNum
].cpu_LAPICTick
= 0;
429 D(bug("[Kernel:APIC-IA32.%03u] %s: heartbeat Exception Vector #$%02X (%d) set\n", cpuNum
, __func__
, APIC_EXCEPT_HEARTBEAT
, APIC_CPU_EXCEPT_TO_VECTOR(APIC_EXCEPT_HEARTBEAT
));)
434 #if defined(__AROSEXEC_SMP__)
435 // TODO: Adjust based on the amount of work the APIC can do at its given frequency.
436 schedData
->Granularity
= 1;
437 schedData
->Quantum
= 5;
438 APIC_REG(__APICBase
, APIC_TIMER_ICR
) = (apic
->cores
[cpuNum
].cpu_TimerFreq
);
440 APIC_REG(__APICBase
, APIC_TIMER_ICR
) = (apic
->cores
[cpuNum
].cpu_TimerFreq
+ 25) / 50;
442 APIC_REG(__APICBase
, APIC_TIMER_VEC
) = APIC_CPU_EXCEPT_TO_VECTOR(APIC_EXCEPT_HEARTBEAT
); // | LVT_TMM_PERIOD;
446 APIC_REG(__APICBase
, APIC_TIMER_ICR
) = apic
->cores
[cpuNum
].cpu_TimerFreq
;
447 APIC_REG(__APICBase
, APIC_TIMER_VEC
) = LVT_MASK
| LVT_TMM_PERIOD
| APIC_CPU_EXCEPT_TO_VECTOR(APIC_EXCEPT_HEARTBEAT
);
452 apicid_t
core_APIC_GetID(IPTR _APICBase
)
456 /* The actual ID is in 8 most significant bits */
457 _apic_id
= APIC_REG(_APICBase
, APIC_ID
) >> APIC_ID_SHIFT
;
458 DID(bug("[Kernel:APIC-IA32] %s: %03u\n", __func__
, _apic_id
));
463 ULONG
core_APIC_Wake(APTR wake_apicstartrip
, apicid_t wake_apicid
, IPTR __APICBase
)
465 ULONG status_ipisend
, status_ipirecv
;
468 ULONG apic_ver
= APIC_REG(__APICBase
, APIC_VERSION
);
471 apicid_t cpuNo
= KrnGetCPUNumber();
473 bug("[Kernel:APIC-IA32.%03u] %s(%03u @ %p)\n", cpuNo
, __func__
, wake_apicid
, wake_apicstartrip
);
474 bug("[Kernel:APIC-IA32.%03u] %s: Base @ %p\n", cpuNo
, __func__
, __APICBase
);
478 * Check if we have old 82489DX discrete APIC (version & 0xF0 == 0).
479 * This APIC needs different startup procedure. It doesn't support STARTUP IPI
480 * because old CPUs didn't have INIT signal. They jump to BIOS ROM boot code
481 * immediately after INIT IPI. In order to run the bootstrap, a BIOS warm reset
482 * magic has to be used there.
484 if (!APIC_INTEGRATED(apic_ver
))
487 * BIOS warm reset magic, part one.
488 * Write real-mode bootstrap routine address to 40:67 (real-mode address) location.
489 * This is standard feature of IBM PC AT BIOS. If a warm reset condition is detected,
490 * the BIOS jumps to the given address.
492 D(bug("[Kernel:APIC-IA32.%03u] %s: Setting BIOS vector for trampoline @ %p ..\n", cpuNo
, __func__
, wake_apicstartrip
));
493 *((volatile unsigned short *)0x469) = (IPTR
)wake_apicstartrip
>> 4;
494 *((volatile unsigned short *)0x467) = 0; /* Actually wake_apicstartrip & 0x0F, it's page-aligned. */
497 * BIOS warm reset magic, part two.
498 * This writes 0x0A into CMOS RAM, location 0x0F. This signals a warm reset condition to BIOS,
499 * making part one work.
501 D(bug("[Kernel:APIC-IA32.%03u] %s: Setting warm reset code ..\n", cpuNo
, __func__
));
507 /* Flush TLB (we are supervisor here) */
508 wrcr(cr3
, rdcr(cr3
));
510 /* First we send the INIT command (reset the core). Vector must be zero for this. */
511 status_ipisend
= DoIPI(__APICBase
, wake_apicid
, ICR_INT_LEVELTRIG
| ICR_INT_ASSERT
| ICR_DM_INIT
);
514 D(bug("[Kernel:APIC-IA32.%03u] %s: Error asserting INIT\n", cpuNo
, __func__
));
515 return status_ipisend
;
518 /* Deassert INIT after a small delay */
519 pit_udelay(10 * 1000);
522 status_ipisend
= DoIPI(__APICBase
, wake_apicid
, ICR_INT_LEVELTRIG
| ICR_DM_INIT
);
525 D(bug("[Kernel:APIC-IA32.%03u] %s: Error deasserting INIT\n", cpuNo
, __func__
));
526 return status_ipisend
;
530 asm volatile("mfence":::"memory");
533 /* If it's 82489DX, we are done. */
534 if (!APIC_INTEGRATED(apic_ver
))
536 DWAKE(bug("[Kernel:APIC-IA32.%03u] %s: 82489DX detected, wakeup done\n", cpuNo
, __func__
));
542 * Perform IPI STARTUP loop.
543 * According to official Intel specification, this must be done twice.
544 * It's not explained why. ;-)
546 for (start_count
= 1; start_count
<= 2; start_count
++)
548 D(bug("[Kernel:APIC-IA32.%03u] %s: Attempting STARTUP .. %u\n", cpuNo
, __func__
, start_count
));
550 /* Clear any pending error condition */
551 APIC_REG(__APICBase
, APIC_ESR
) = 0;
555 * The processor starts up at CS = (vector << 16) and IP = 0.
557 status_ipisend
= DoIPI(__APICBase
, wake_apicid
, ICR_DM_STARTUP
| ((IPTR
)wake_apicstartrip
>> 12));
559 /* Allow the target APIC to accept the IPI */
563 /* Pentium erratum 3AP quirk */
564 if (APIC_LVT(apic_ver
) > 3)
565 APIC_REG(__APICBase
, APIC_ESR
) = 0;
568 status_ipirecv
= APIC_REG(__APICBase
, APIC_ESR
) & 0xEF;
572 * On my machine (macmini 3,1, as OS X system profiler says), the core starts up from first
573 * attempt. The second attempt ends up in error (according to the documentation, the STARTUP
574 * can be accepted only once, while the core in RESET or INIT state, and first STARTUP, if
575 * successful, brings the core out of this state).
576 * Here we try to detect this condition. If the core accepted STARTUP, we suggest that it has
577 * started up, and break the loop.
578 * A topic at osdev.org forum (http://forum.osdev.org/viewtopic.php?f=1&t=23018)
579 * also tells about some problems with double STARTUP. According to it, the second STARTUP can
580 * manage to re-run the core from the given address, leaving it in 64-bit mode, causing it to crash.
582 * If startup problems pops up (the core doesn't respond and AROS halts at "Launching APIC no X" stage),
583 * the following two variations of this algorithm can be tried:
584 * a) Always send STARTUP twice, but signal error condition only if both attempts failed.
585 * b) Send first STARTUP, abort on error. Allow second attempt to fail and ignore its result.
587 * Sonic <pavel_fedin@mail.ru>
589 if (!status_ipisend
&& !status_ipirecv
)
593 DWAKE(bug("[Kernel:APIC-IA32.%03u] %s: STARTUP run status 0x%08X, error 0x%08X\n", cpuNo
, __func__
, status_ipisend
, status_ipirecv
));
596 * We return nonzero on error.
597 * Actually least significant byte of this value holds ESR value, and 12th bit
598 * holds delivery status flag from DoIPI() routine. It will be '1' if we got
599 * stuck at sending phase.
601 return (status_ipisend
| status_ipirecv
);