summaryrefslogtreecommitdiffstats
path: root/drivers/lguest/switcher.S
blob: e7cb8c1235583dc50401ff8addf4878ebcebf444 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*P:900 This is the Switcher: code which sits at 0xFFC00000 to do the low-level
 * Guest<->Host switch.  It is as simple as it can be made, but it's naturally
 * very specific to x86.
 *
 * You have now completed Preparation.  If this has whet your appetite; if you
 * are feeling invigorated and refreshed then the next, more challenging stage
 * can be found in "make Guest". :*/

#include <linux/linkage.h>
#include <asm/asm-offsets.h>
#include "lg.h"

.text
ENTRY(start_switcher_text)

/* %eax points to lguest pages for this CPU.  %ebx contains cr3 value.
   All normal registers can be clobbered! */
ENTRY(switch_to_guest)
	/* Save host segments on host stack. */
	pushl	%es
	pushl	%ds
	pushl	%gs
	pushl	%fs
	/* With CONFIG_FRAME_POINTER, gcc doesn't let us clobber this! */
	pushl	%ebp
	/* Save host stack. */
	movl	%esp, LGUEST_PAGES_host_sp(%eax)
	/* Switch to guest stack: if we get NMI we expect to be there. */
	movl	%eax, %edx
	addl	$LGUEST_PAGES_regs, %edx
	movl	%edx, %esp
	/* Switch to guest's GDT, IDT. */
	lgdt	LGUEST_PAGES_guest_gdt_desc(%eax)
	lidt	LGUEST_PAGES_guest_idt_desc(%eax)
	/* Switch to guest's TSS while GDT still writable. */
	movl	$(GDT_ENTRY_TSS*8), %edx
	ltr	%dx
	/* Set host's TSS GDT entry to available (clear byte 5 bit 2). */
	movl	(LGUEST_PAGES_host_gdt_desc+2)(%eax), %edx
	andb	$0xFD, (GDT_ENTRY_TSS*8 + 5)(%edx)
	/* Switch to guest page tables:	lguest_pages->state now read-only. */
	movl	%ebx, %cr3
	/* Restore guest regs */
	popl	%ebx
	popl	%ecx
	popl	%edx
	popl	%esi
	popl	%edi
	popl	%ebp
	popl	%gs
	popl	%eax
	popl	%fs
	popl	%ds
	popl	%es
	/* Skip error code and trap number */
	addl	$8, %esp
	iret

#define SWITCH_TO_HOST							\
	/* Save guest state */						\
	pushl	%es;							\
	pushl	%ds;							\
	pushl	%fs;							\
	pushl	%eax;							\
	pushl	%gs;							\
	pushl	%ebp;							\
	pushl	%edi;							\
	pushl	%esi;							\
	pushl	%edx;							\
	pushl	%ecx;							\
	pushl	%ebx;							\
	/* Load lguest ds segment for convenience. */			\
	movl	$(LGUEST_DS), %eax;					\
	movl	%eax, %ds;						\
	/* Figure out where we are, based on stack (at top of regs). */	\
	movl	%esp, %eax;						\
	subl	$LGUEST_PAGES_regs, %eax;				\
	/* Put trap number in %ebx before we switch cr3 and lose it. */ \
	movl	LGUEST_PAGES_regs_trapnum(%eax), %ebx;			\
	/* Switch to host page tables (host GDT, IDT and stack are in host   \
	   mem, so need this first) */					\
	movl	LGUEST_PAGES_host_cr3(%eax), %edx;			\
	movl	%edx, %cr3;						\
	/* Set guest's TSS to available (clear byte 5 bit 2). */	\
	andb	$0xFD, (LGUEST_PAGES_guest_gdt+GDT_ENTRY_TSS*8+5)(%eax); \
	/* Switch to host's GDT & IDT. */				\
	lgdt	LGUEST_PAGES_host_gdt_desc(%eax);			\
	lidt	LGUEST_PAGES_host_idt_desc(%eax);			\
	/* Switch to host's stack. */					\
	movl	LGUEST_PAGES_host_sp(%eax), %esp;			\
	/* Switch to host's TSS */					\
	movl	$(GDT_ENTRY_TSS*8), %edx;				\
	ltr	%dx;							\
	popl	%ebp;							\
	popl	%fs;							\
	popl	%gs;							\
	popl	%ds;							\
	popl	%es

/* Return to run_guest_once. */
return_to_host:
	SWITCH_TO_HOST
	iret

deliver_to_host:
	SWITCH_TO_HOST
	/* Decode IDT and jump to hosts' irq handler.  When that does iret, it
	 * will return to run_guest_once.  This is a feature. */
	movl	(LGUEST_PAGES_host_idt_desc+2)(%eax), %edx
	leal	(%edx,%ebx,8), %eax
	movzwl	(%eax),%edx
	movl	4(%eax), %eax
	xorw	%ax, %ax
	orl	%eax, %edx
	jmp	*%edx

/* Real hardware interrupts are delivered straight to the host.  Others
   cause us to return to run_guest_once so it can decide what to do.  Note
   that some of these are overridden by the guest to deliver directly, and
   never enter here (see load_guest_idt_entry). */
.macro IRQ_STUB N TARGET
	.data; .long 1f; .text; 1:
 /* Make an error number for most traps, which don't have one. */
 .if (\N <> 8) && (\N < 10 || \N > 14) && (\N <> 17)
	pushl	$0
 .endif
	pushl	$\N
	jmp	\TARGET
	ALIGN
.endm

.macro IRQ_STUBS FIRST LAST TARGET
 irq=\FIRST
 .rept \LAST-\FIRST+1
	IRQ_STUB irq \TARGET
  irq=irq+1
 .endr
.endm

/* We intercept every interrupt, because we may need to switch back to
 * host.  Unfortunately we can't tell them apart except by entry
 * point, so we need 256 entry points.
 */
.data
.global default_idt_entries
default_idt_entries:
.text
	IRQ_STUBS 0 1 return_to_host		/* First two traps */
	IRQ_STUB 2 handle_nmi			/* NMI */
	IRQ_STUBS 3 31 return_to_host		/* Rest of traps */
	IRQ_STUBS 32 127 deliver_to_host	/* Real interrupts */
	IRQ_STUB 128 return_to_host		/* System call (overridden) */
	IRQ_STUBS 129 255 deliver_to_host	/* Other real interrupts */

/* We ignore NMI and return. */
handle_nmi:
	addl	$8, %esp
	iret

ENTRY(end_switcher_text)
OpenPOWER on IntegriCloud