/* ia32-activation-entry.S - Activation handler dispatcher. Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc. Written by Neal H. Walfield . This file is part of the GNU Hurd. The GNU Hurd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. The GNU Hurd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include .text #ifdef __x86_64 # define WS 8 #else # define WS 4 #endif #define CONCAT(a, b) a##b #ifdef __x86_64 # define R(reg) CONCAT(%r, reg) #else # define R(reg) CONCAT(%e, reg) #endif /* Offsets into a struct vg_utcb. */ #define UTCB_MODE (0*WS) #define UTCB_SAVED_IP (1*WS) #define UTCB_SAVED_SP (2*WS) #define UTCB_ACTIVATION_FRAME_STACK (512 + 0*WS) /* The bits of the mode word. */ #define ACTIVATED_MODE_BIT 0 #define PENDING_MESSAGE_BIT 1 #define INTERRUPT_IN_TRANSITION_BIT 2 /* Offsets into a struct activation_frame. */ #define AF_SAVED_AX (0) #define AF_SAVED_CX (1*WS) #define AF_SAVED_DX (2*WS) #define AF_SAVED_FLAGS (3*WS) #define AF_SAVED_IP (4*WS) #define AF_SAVED_BX (5*WS) #define AF_SAVED_DI (6*WS) #define AF_SAVED_SI (7*WS) #define AF_SAVED_BP (8*WS) #define AF_SAVED_SP (9*WS) #ifndef __x86_64 # define AF_SAVED_REGS_END (AF_SAVED_SP + WS) #else # define AF_SAVED_8 (10*WS) # define AF_SAVED_9 (11*WS) # define AF_SAVED_10 (12*WS) # define AF_SAVED_11 (13*WS) # define AF_SAVED_12 (14*WS) # define AF_SAVED_13 (15*WS) # define AF_SAVED_14 (16*WS) # define AF_SAVED_15 (17*WS) # define AF_SAVED_REGS_END (AF_SAVED_15 + WS) #endif #define AF_NORMAL_MODE_STACK AF_SAVED_REGS_END #define AF_NEXT (AF_NORMAL_MODE_STACK + WS) /* Handle an activation. */ .globl hurd_activation_handler_entry, _hurd_activation_handler_entry hurd_activation_handler_entry: _hurd_activation_handler_entry: /* How we will use the stack: relative to entry sp | | v +0 pointer to utcb -1*WS entry eflags -2*WS entry edx -3*WS start of save area The save area contains the caller saved registers. According to the i386 ABI: - caller saved registers are: eax, ecx, edx - callee saved registers are: ebp, ebx, esi, edi x86-64: - caller saved registers are: rax, rcx, rdx, rdi, rsi, r8 - r11 - callee saved registers are: rbp, rbx, r12, r13, r14, r15 */ #define SAVE_AREA_UTCB (WS+SAVE_AREA_ENTRY_FLAGS) #define SAVE_AREA_ENTRY_FLAGS (WS+SAVE_AREA_ENTRY_DX) #ifdef __x86_64 # define SAVE_AREA_ENTRY_DX (10*WS) # define SAVE_AREA_11 (9*WS) # define SAVE_AREA_10 (8*WS) # define SAVE_AREA_9 (7*WS) # define SAVE_AREA_8 (6*WS) # define SAVE_AREA_SI (5*WS) # define SAVE_AREA_DI (4*WS) #else # define SAVE_AREA_ENTRY_DX (4*WS) #endif #define SAVE_AREA_AX (3*WS) #define SAVE_AREA_CX (2*WS) #define SAVE_AREA_FLAGS (1*WS) #define SAVE_AREA_DX (0*WS) /* SP points to the top of the UTCB. If the interrupt-in- transition flag is not set, then we need to save the caller-saved registers. Otherwise, we were interrupted while returning to normal mode and the the saved state, not our registers, reflects the user state that we want to return to (see big comment below for more information). */ /* Save the eflags before we do *anything*. */ pushf /* We need to check if the interrupt in transition flag is set. Free up a register and make it a pointer to the UTCB. Recall: we stashed a pointer to the UTCB at the word following the botton of the stack. */ push R(dx) mov 2*WS(R(SP)), R(dx) bt $INTERRUPT_IN_TRANSITION_BIT, UTCB_MODE(R(dx)) jc skip_save /* Nope; we need to save some of the regsiter state to the save area. current EAX and ECX and copy the entry eflags and EDX into the save area. */ #ifdef __x86_64 push R(11) push R(10) push R(9) push R(8) push R(si) push R(di) #endif push R(ax) push R(cx) /* Copy the entry flags. */ push (SAVE_AREA_ENTRY_FLAGS-2*WS)(R(sp)) /* Copy the entry dx. */ push (SAVE_AREA_ENTRY_DX-WS)(R(sp)) jmp after_adjust skip_save: /* We don't save the entry registers but use the saved values. Adjust the stack pointer to point to the start of the save area. */ sub $(SAVE_AREA_ENTRY_DX), R(sp) after_adjust: /* Call the activation handler. The only caller saved value that we care about is our pointer to the UTCB, which is in dx. Save it. */ push R(dx) /* The activation handler function takes a single argument: the UTCB. */ #ifdef __x86_64 mov R(dx), R(di) #else push R(dx) #endif /* Clear the direction flag as per the calling conventions. */ cld call hurd_activation_handler_activated /* The activation frame, if any, is in EAX. */ #ifndef __x86_64 /* Clear the arguments. */ add $(WS), R(sp) #endif /* Restore UTCB pointer. */ pop R(dx) /* Check if hurd_activation_handler_activated returned an activation frame. */ test R(ax), R(ax) jnz activation_frame_run /* There is no activation frame, transition immediately back to normal mode. To return to normal mode, we need to restore the saved registers, including the saved general registers, saved ESP and saved EIP. On x86, there is no way to atomically restore ESP and EIP from user code. The solution we use is: - save the saved EIP on the user stack - restore the saved ESP minus 4 - execute a ret instruction Beyond not being atomic, this has the additional problem that writing on the user stack may cause a fault. To work around this latter problem, we only write on the user stack once we return to normal mode. If this faults, the kernel can transition us back to activated mode. But this raises another problem: the IP and SP that the kernel sees are not those that return us to user code. As this code relies on the activation stack, a nested stack will leave us in an inconsistent state. (This can also happen if we receive a message before returning to user code.) To avoid this, we register our restore to normal mode function with the kernel. If the kernel transitions us back to activated mode while the EIP is in this range, then it does not save the EIP and ESP and invokes the activation handler with the interrupt_in_transition flag set. */ /* Reset the activation bit. */ and $(~1), UTCB_MODE(R(dx)) /* Check for pending messages. This does not need to be atomic as if we get interrupted here, we automatically transition back to activated mode. */ bt $PENDING_MESSAGE_BIT, UTCB_MODE(R(dx)) jnc no_pending /* There is a pending activation. Force its delivery. As we are no longer in activated mode, either we'll be activated with the interrupt-in-transition bit set (and thus never return here) or we'll return. In the latter case, we just resume execution. */ /* XXX: Implement me. We have to eventually restore the state that we have. The activation will overwrite that state unless it thinks we are in the interrupt in transition area. hurd_activation_fetch is not in that area. This can be fixed by explicitly setting the INTERRUPT_IN_TRANSITION bit here, and changing Viengoos to not save the current register file if that bit is set. This also means that we need to manually clear this bit. */ int $3 /* Save the UTCB. */ push R(dx) cld call hurd_activation_fetch pop R(dx) no_pending: /* Set EAX to the start of the save area. */ mov R(sp), R(ax) /* Restore the user stack. */ mov UTCB_SAVED_SP(R(dx)), R(sp) /* Copy the saved EIP and saved flags to the user stack. */ mov UTCB_SAVED_IP(R(dx)), R(cx) push R(cx) mov (SAVE_AREA_FLAGS)(R(ax)), R(cx) push R(cx) /* Restore the general-purpose registers. */ #ifdef __x86_64 mov (SAVE_AREA_11)(R(ax)), %r11 mov (SAVE_AREA_10)(R(ax)), %r10 mov (SAVE_AREA_9)(R(ax)), %r9 mov (SAVE_AREA_8)(R(ax)), %r8 mov (SAVE_AREA_SI)(R(ax)), %rsi mov (SAVE_AREA_DI)(R(ax)), %rdi #endif mov (SAVE_AREA_DX)(R(ax)), R(dx) mov (SAVE_AREA_CX)(R(ax)), R(cx) mov (SAVE_AREA_AX)(R(ax)), R(ax) /* Restore the saved eflags. */ popf /* And finally, the saved EIP and in doing so the saved ESP. */ ret activation_frame_run: /* EAX contains the activation frame, EDX the UTCB, and ESP points to the bottom of the save area. ECX has been saved in the save area. */ /* Copy all relevant register state from the UTCB and save area to the activation frame. We use ecx as the intermediate. */ mov UTCB_SAVED_IP(R(dx)), R(cx) mov R(cx), AF_SAVED_IP(R(ax)) mov UTCB_SAVED_SP(R(dx)), R(cx) mov R(cx), AF_SAVED_SP(R(ax)) pop AF_SAVED_DX(R(ax)) pop AF_SAVED_FLAGS(R(ax)) pop AF_SAVED_CX(R(ax)) pop AF_SAVED_AX(R(ax)) #ifdef __x86_64 pop AF_SAVED_DI(R(ax)) pop AF_SAVED_SI(R(ax)) pop AF_SAVED_8(R(ax)) pop AF_SAVED_9(R(ax)) pop AF_SAVED_10(R(ax)) pop AF_SAVED_11(R(ax)) #endif /* We save the rest for debugging purposes. */ mov R(bx), AF_SAVED_BX(R(ax)) mov R(bp), AF_SAVED_BP(R(ax)) #ifdef __x86_64 mov R(12), AF_SAVED_12(R(ax)) mov R(13), AF_SAVED_13(R(ax)) mov R(14), AF_SAVED_14(R(ax)) mov R(15), AF_SAVED_15(R(ax)) #else mov R(di), AF_SAVED_DI(R(ax)) mov R(si), AF_SAVED_SI(R(ax)) #endif /* Abandon the activation stack. If AF->NORMAL_MODE_STACK is 0, we use the top of the normal user stack. Otherwise, we use the stack indicated by AF->NORMAL_MODE_STACK. */ mov AF_NORMAL_MODE_STACK(R(ax)), R(sp) test R(sp), R(sp) jnz stack_setup mov UTCB_SAVED_SP(R(dx)), R(sp) stack_setup: /* We've now stashed away all the state that was in the UTCB or on the activation stack that we need to restore the interrupted state. */ /* Clear the activation bit. */ and $(~1), UTCB_MODE(R(dx)) .global hurd_activation_handler_end, _hurd_activation_handler_end hurd_activation_handler_end: _hurd_activation_handler_end: /* We have now left activated mode. We've saved all the state we need to return to the interrupted state in the activation frame and ESP points to another stack (i.e., not the activate stack). If a fault now occurs, nothing bad can happend. */ /* Save the UTCB pointer. */ push R(dx) /* Save the activation frame pointer. */ push R(ax) /* Call the continuation (two arguments: activation frame pointer and the utcb). */ #ifdef __x86_64 mov R(ax), R(di) mov R(dx), R(si) #else push R(dx) push R(ax) #endif cld call hurd_activation_handler_normal #ifndef __x86_64 /* Remove the arguments. */ add $8, R(sp) #endif /* Restore the activation frame pointer. */ pop R(ax) /* And restore the UTCB pointer. */ pop R(dx) /* Restore the interrupted state. */ /* First, the interrupted stack. */ mov AF_SAVED_SP(R(ax)), R(sp) /* Then, push some of the state onto that stack--we will restore the ip and flags from that stack and we need a few registers to finish cleaning up properly. */ push AF_SAVED_IP(R(ax)) push AF_SAVED_FLAGS(R(ax)) push AF_SAVED_DX(R(ax)) push AF_SAVED_CX(R(ax)) push AF_SAVED_AX(R(ax)) /* Now, start restoring the register state. */ #ifdef __x86_64 mov AF_SAVED_8(R(ax)), %r8 mov AF_SAVED_9(R(ax)), %r9 mov AF_SAVED_10(R(ax)), %r10 mov AF_SAVED_11(R(ax)), %r11 mov AF_SAVED_SI(R(ax)), %rsi mov AF_SAVED_DI(R(ax)), %rdi #endif /* Remove our activation frame, which is at the top of the activation frame stack. */ mov AF_NEXT(R(ax)), R(cx) mov R(cx), UTCB_ACTIVATION_FRAME_STACK(R(dx)) /* Finally, restore the register file. */ pop R(ax) pop R(cx) pop R(dx) popf /* And return. */ ret