Fix yield() so it works from boot code.
[seabios.git] / src / stacks.c
1 // Code for manipulating stack locations.
2 //
3 // Copyright (C) 2009  Kevin O'Connor <kevin@koconnor.net>
4 //
5 // This file may be distributed under the terms of the GNU LGPLv3 license.
6
7 #include "biosvar.h" // get_ebda_seg
8 #include "util.h" // dprintf
9 #include "bregs.h" // CR0_PE
10
11 static inline u32 getcr0() {
12     u32 cr0;
13     asm("movl %%cr0, %0" : "=r"(cr0));
14     return cr0;
15 }
16 static inline void sgdt(struct descloc_s *desc) {
17     asm("sgdtl %0" : "=m"(*desc));
18 }
19 static inline void lgdt(struct descloc_s *desc) {
20     asm("lgdtl %0" : : "m"(*desc) : "memory");
21 }
22
23 // Call a 32bit SeaBIOS function from a 16bit SeaBIOS function.
24 static inline int
25 call32(void *func)
26 {
27     ASSERT16();
28     u32 cr0 = getcr0();
29     if (cr0 & CR0_PE)
30         // Called in 16bit protected mode?!
31         return -1;
32
33     // Backup cmos index register and disable nmi
34     u8 cmosindex = inb(PORT_CMOS_INDEX);
35     outb(cmosindex | NMI_DISABLE_BIT, PORT_CMOS_INDEX);
36     inb(PORT_CMOS_DATA);
37
38     // Backup fs/gs and gdt
39     u16 fs = GET_SEG(FS), gs = GET_SEG(GS);
40     struct descloc_s gdt;
41     sgdt(&gdt);
42
43     func -= BUILD_BIOS_ADDR;
44     u32 bkup_ss, bkup_esp;
45     asm volatile(
46         // Backup ss/esp / set esp to flat stack location
47         "  movl %%ss, %0\n"
48         "  movl %%esp, %1\n"
49         "  shll $4, %0\n"
50         "  addl %0, %%esp\n"
51         "  movl %%ss, %0\n"
52
53         // Transition to 32bit mode, call func, return to 16bit
54         "  pushl $(" __stringify(BUILD_BIOS_ADDR) " + 1f)\n"
55         "  jmp transition32\n"
56         "  .code32\n"
57         "1:calll %2\n"
58         "  pushl $2f\n"
59         "  jmp transition16big\n"
60
61         // Restore ds/ss/esp
62         "  .code16gcc\n"
63         "2:movl %0, %%ds\n"
64         "  movl %0, %%ss\n"
65         "  movl %1, %%esp\n"
66         : "=&r" (bkup_ss), "=&r" (bkup_esp)
67         : "m" (*(u8*)func)
68         : "eax", "ecx", "edx", "cc", "memory");
69
70     // Restore gdt and fs/gs
71     lgdt(&gdt);
72     SET_SEG(FS, fs);
73     SET_SEG(GS, gs);
74
75     // Restore cmos index register
76     outb(cmosindex, PORT_CMOS_INDEX);
77     inb(PORT_CMOS_DATA);
78     return 0;
79 }
80
81
82 /****************************************************************
83  * Stack in EBDA
84  ****************************************************************/
85
86 // Switch to the extra stack in ebda and call a function.
87 inline u32
88 stack_hop(u32 eax, u32 edx, u32 ecx, void *func)
89 {
90     ASSERT16();
91     u16 ebda_seg = get_ebda_seg(), bkup_ss;
92     u32 bkup_esp;
93     asm volatile(
94         // Backup current %ss/%esp values.
95         "movw %%ss, %w3\n"
96         "movl %%esp, %4\n"
97         // Copy ebda seg to %ds/%ss and set %esp
98         "movw %w6, %%ds\n"
99         "movw %w6, %%ss\n"
100         "movl %5, %%esp\n"
101         // Call func
102         "calll %7\n"
103         // Restore segments and stack
104         "movw %w3, %%ds\n"
105         "movw %w3, %%ss\n"
106         "movl %4, %%esp"
107         : "+a" (eax), "+d" (edx), "+c" (ecx), "=&r" (bkup_ss), "=&r" (bkup_esp)
108         : "i" (EBDA_OFFSET_TOP_STACK), "r" (ebda_seg), "m" (*(u8*)func)
109         : "cc", "memory");
110     return eax;
111 }
112
113
114 /****************************************************************
115  * Threads
116  ****************************************************************/
117
118 #define THREADSTACKSIZE 4096
119
120 struct thread_info {
121     struct thread_info *next;
122     void *stackpos;
123 };
124
125 struct thread_info VAR16VISIBLE MainThread;
126 int VAR16VISIBLE CanPreempt;
127
128 void
129 thread_setup()
130 {
131     MainThread.next = &MainThread;
132     MainThread.stackpos = NULL;
133     CanPreempt = 0;
134 }
135
136 // Return the 'struct thread_info' for the currently running thread.
137 struct thread_info *
138 getCurThread()
139 {
140     u32 esp = getesp();
141     if (esp <= BUILD_STACK_ADDR)
142         return &MainThread;
143     return (void*)ALIGN_DOWN(esp, THREADSTACKSIZE);
144 }
145
146 // Switch to next thread stack.
147 static void
148 switch_next(struct thread_info *cur)
149 {
150     struct thread_info *next = cur->next;
151     if (cur == next)
152         // Nothing to do.
153         return;
154     asm volatile(
155         "  pushl $1f\n"                 // store return pc
156         "  pushl %%ebp\n"               // backup %ebp
157         "  movl %%esp, 4(%%eax)\n"      // cur->stackpos = %esp
158         "  movl 4(%%ecx), %%esp\n"      // %esp = next->stackpos
159         "  popl %%ebp\n"                // restore %ebp
160         "  retl\n"                      // restore pc
161         "1:\n"
162         : "+a"(cur), "+c"(next)
163         :
164         : "ebx", "edx", "esi", "edi", "cc", "memory");
165 }
166
167 // Briefly permit irqs to occur.
168 void
169 yield()
170 {
171     if (MODE16 || !CONFIG_THREADS) {
172         // Just directly check irqs.
173         check_irqs();
174         return;
175     }
176     struct thread_info *cur = getCurThread();
177     if (cur == &MainThread)
178         // Permit irqs to fire
179         check_irqs();
180
181     // Switch to the next thread
182     switch_next(cur);
183 }
184
185 // Last thing called from a thread (called on "next" stack).
186 static void
187 __end_thread(struct thread_info *old)
188 {
189     struct thread_info *pos = &MainThread;
190     while (pos->next != old)
191         pos = pos->next;
192     pos->next = old->next;
193     free(old);
194     dprintf(DEBUG_thread, "\\%08x/ End thread\n", (u32)old);
195 }
196
197 // Create a new thread and start executing 'func' in it.
198 void
199 run_thread(void (*func)(void*), void *data)
200 {
201     ASSERT32();
202     if (! CONFIG_THREADS)
203         goto fail;
204     struct thread_info *thread;
205     thread = memalign_tmphigh(THREADSTACKSIZE, THREADSTACKSIZE);
206     if (!thread)
207         goto fail;
208
209     thread->stackpos = (void*)thread + THREADSTACKSIZE;
210     struct thread_info *cur = getCurThread();
211     thread->next = cur->next;
212     cur->next = thread;
213
214     dprintf(DEBUG_thread, "/%08x\\ Start thread\n", (u32)thread);
215     asm volatile(
216         // Start thread
217         "  pushl $1f\n"                 // store return pc
218         "  pushl %%ebp\n"               // backup %ebp
219         "  movl %%esp, 4(%%edx)\n"      // cur->stackpos = %esp
220         "  movl 4(%%ebx), %%esp\n"      // %esp = thread->stackpos
221         "  calll *%%ecx\n"              // Call func
222
223         // End thread
224         "  movl (%%ebx), %%ecx\n"       // %ecx = thread->next
225         "  movl 4(%%ecx), %%esp\n"      // %esp = next->stackpos
226         "  movl %%ebx, %%eax\n"
227         "  calll %4\n"                  // call __end_thread(thread)
228         "  popl %%ebp\n"                // restore %ebp
229         "  retl\n"                      // restore pc
230         "1:\n"
231         : "+a"(data), "+c"(func), "+b"(thread), "+d"(cur)
232         : "m"(*(u8*)__end_thread)
233         : "esi", "edi", "cc", "memory");
234     return;
235
236 fail:
237     func(data);
238 }
239
240 // Wait for all threads (other than the main thread) to complete.
241 void
242 wait_threads()
243 {
244     ASSERT32();
245     if (! CONFIG_THREADS)
246         return;
247     while (MainThread.next != &MainThread)
248         yield();
249 }
250
251
252 /****************************************************************
253  * Thread preemption
254  ****************************************************************/
255
256 static u32 PreemptCount;
257
258 // Turn on RTC irqs and arrange for them to check the 32bit threads.
259 void
260 start_preempt()
261 {
262     if (! CONFIG_THREADS || ! CONFIG_THREAD_OPTIONROMS)
263         return;
264     CanPreempt = 1;
265     PreemptCount = 0;
266     useRTC();
267 }
268
269 // Turn off RTC irqs / stop checking for thread execution.
270 void
271 finish_preempt()
272 {
273     if (! CONFIG_THREADS || ! CONFIG_THREAD_OPTIONROMS)
274         return;
275     CanPreempt = 0;
276     releaseRTC();
277     dprintf(1, "Done preempt - %d checks\n", PreemptCount);
278 }
279
280 extern void yield_preempt();
281 #if !MODE16
282 // Try to execute 32bit threads.
283 void VISIBLE32
284 yield_preempt()
285 {
286     PreemptCount++;
287     switch_next(&MainThread);
288 }
289 #endif
290
291 // 16bit code that checks if threads are pending and executes them if so.
292 void
293 check_preempt()
294 {
295     if (! CONFIG_THREADS || ! CONFIG_THREAD_OPTIONROMS
296         || !GET_GLOBAL(CanPreempt)
297         || GET_GLOBAL(MainThread.next) == &MainThread)
298         return;
299
300     call32(yield_preempt);
301 }