6.5. Exception ProcessingFigure 69 shows a high-level view of exception processing. Recall from earlier discussion that the vectors reside in physical memory starting at location 0x0. Consider the example of an instruction access exception, which is caused when the effective address of an instruction fails to translate to a virtual address. As listed in Table 51, the vector offset for this exception is 0x400. Consequently, the processor executes the code at physical address 0x400 to handle this exception. Most exception handlers simply save GPR13 and GPR11, set an interrupt code in GPR11, and jump to .L_exception_entry() [osfmk/ppc/lowmem_vectors.s] for further processing. For some exceptions, such as system reset (0x100), system call (0xC00), and trace (0xD00), the first-level exception handlers perform more work. Nevertheless, there exists a code path from each handler to .L_exception_entry(). Figure 69. A high-level view of exception processing in the xnu kernelFigure 610 shows the code structure of .L_exception_entry(). It first saves a variety of contextan operation whose implementation is different for 32-bit and 64-bit processors. The address labeled as extPatch32 contains an unconditional branch instruction to 64-bit-specific code. This branch must not be taken on a 32-bit processorinstead, execution should continue with the 32-bit code that follows this instruction. As we saw in Chapter 5, the kernel performs boot-time memory patching of instructions and data. In this case, the kernel would replace the unconditional branch with a no-op instruction at boot time on a 32-bit processor. Figure 610. Common code for exception processing
Once .L_exception_entry() has saved all the context, it refers to an exception vector filter table called xcpTable, which too is defined in osfmk/ppc/lowmem_vectors.s and resides in low memory (the first 32KB of physical memory). The common exception-handling code in .L_exception_entry() uses the incoming exception code to look up the handler in the filter table, after which it branches to the handler. Table 69 lists the exception handlers corresponding to the various exception codes set by the exception vectors. For example, the codes T_INTERRUPT (vector offset 0x500), T_DECREMENTER (vector offset 0x900), T_SYSTEM_MANAGEMENT (vector offset 0x1400), and T_THERMAL (vector offset 0x1700) are all channeled to code labeled as PassUpRupt, which leads to a higher-level interrupt handler. Similarly, traps (various exception codes) and system calls (the T_SYSTEM_CALL exception code) are channeled to the PassUpTrap and xcpSyscall labels, respectively. Figure 611 depicts a simplified view of the processing of traps, interrupts, and system calls.
Figure 611. Processing of traps, interrupts, and system calls
Table 69 lists some handlers with the EXT() macro, which is defined in osfmk/ppc/asm.h. This macro simply adds an underscore prefix to its argument, allowing assembly code to refer to the corresponding external symbol while maintaining the C language name (without the underscore) for visual consistency. Examples of exception handlers[5] shown in Figure 611 and Table 69 include PassUpTrap, PassUpRupt, EatRupt, xcpSyscall, and WhoaBaby. Let us briefly look at each of these.
PassUp [osfmk/ppc/lowmem_vectors.s] places the exception code in GPR3 and the next-level exception handler's address in SRR0. It also switches segment registers between the kernel and the user. It finally executes the rfid instruction (rfi on 32-bit processors) to launch the exception handler. 6.5.1. Hardware InterruptsA true hardware interrupt can only occur because of a device that has a physical connectionan interrupt linefrom itself to the system's interrupt controller. Such a connection may involve a device controller. A PCI device is a good example: An interrupt line connects a PCI device slot to the PCI controller, which connects it to the interrupt controller. When the system boots, Open Firmware assigns one or more interrupts to a PCI device. The interrupts used by the device are listed in an array called IOInterruptSpecifiers in the device's I/O Registry node. When the device causes a hardware interrupt, it is signaled to the processor by setting an interrupt identifier bit and asserting the processor's external interrupt input signal, causing an external interrupt exception (vector offset 0x500, exception code T_INTERRUPT). As we saw earlier, processing of this exception will eventually lead to the ihandler() function. Moreover, as shown in Figure 611, other exception codes such as T_DECREMENTER and T_SHUTDOWN also lead to ihandler().
Not all devices cause true hardware interrupts. A USB device, for example, generates an "interrupt" by sending a message on the USB bus without involving the system's interrupt controller. ihandler() ensures the integrity of the interrupt stack, marks it busy, and calls the higher-level interrupt() function [osfmk/ppc/interrupt.c], which disables preemption and performs different operations depending on the specific exception code it is called with. // osfmk/ppc/interrupt.c struct savearea * interrupt(int type, struct savearea *ssp, unsigned int dsisr, unsigned int dar) { ... disable_preemption(); ... switch (type) { case T_DECREMENTER: ... break; case T_INTERRUPT: ... break; ... default: #if MACH_KDP || MACH_KDB if (!Call_Debugger(type, ssp)) #endif unresolved_kernel_trap(type, ssp, dsisr, dar, NULL); break; } enable_preemption(); return ssp; } In the case of a T_DECREMENTER exception code, interrupt() calls rtclock_intr() [osfmk/ppc/rtclock.c]the real-time clock device interrupt function. interrupt() also checks whether the current thread has its quick-activation single-shot timer set; if so, it checks whether the timer has expired, in which case it is cleared. The kernel's virtual machine monitor facility uses this timer. In the case of a T_INTERRUPT exception code, interrupt() increments the count of incoming interrupts and calls the platform-specific interrupt handler function referred to in the per-processor structure. The type of this handler function (IOInterruptHandler) is defined in iokit/IOKit/IOInterrupts.h. typedef void (* IOInterruptHandler)(void *target, void *refCon, void *nub, int source); In the case of a T_SHUTDOWN exception code, which is generated by a special system call (a so-called firmware callsee Section 6.8.8.1), interrupt() calls cpu_doshutdown() [osfmk/ppc/cpu.c].
If an invalid exception code is sent to ihandler() for processing, it will either panic the system or drop into the debugger if one is available. The panic will be accompanied by an "Unresolved kernel trap . . ." message. 6.5.2. Miscellaneous TrapsThe low-level trap handlerthandler() [osfmk/ppc/hw_exception.s]performs different operations depending on the specific trap. A system call exception may end up in the trap handler if there is nothing for the system call to do except generate a trap. The trap handler may jump to the interrupt handler if it finds that it is running on an interrupt stack. thandler() eventually calls the higher-level TRap() function [osfmk/ppc/trap.c] to process traps. // osfmk/ppc/trap.c struct savearea * trap(int trapno, struct savearea *ssp, unsigned int dsisr, addr64_t dar) { int exception; ... exception = 0; ... if (/* kernel mode */) { // Handle traps originating from the kernel first // Examples of such traps are T_PREEMPT, T_PERF_MON, T_RESET // Various traps should never be seen here // Panic if any of these traps are encountered ... } else { /* user mode */ // Handle user mode traps ... } // The 'exception' variable may have been set during trap processing if (exception) { doexception(exception, code, subcode); } ... if (/* user mode */) { // If an AST is needed, call ast_taken() // Repeat until an AST is not needed } return ssp; } There are several criteria for the invalidity of a trap's occurrence. For example, T_IN_VAIN should never be seen by trap() because it should have been disposed of by EatRupt in osfmk/ppc/lowmem_vectors.s. Note that TRap() determines whether a trap originated from the user mode or kernel mode by looking at the contents of the SRR1 in the save area. It uses the USER_MODE() macro [osfmk/ppc/proc_reg.h] for this purpose. // osfmk/ppc/proc_reg.h #define ENDIAN_MASK(val,size) (1 << ((size-1) - val)) ... #define MASK32(PART) ENDIAN_MASK(PART ## _BIT, 32) ... #define MASK(PART) MASK32(PART) ... #define MSR_PR_BIT 17 ... #define USER_MODE(msr) (msr & MASK(MSR_PR) ? TRUE : FALSE)
6.5.3. System CallsThe remaining type of exception is for system calls. As noted earlier, system calls are well-defined entry points into the kernel typically used by user-level programs. The next section covers the details of the Mac OS X system call mechanism. |