Allow wait_irq to be called in 32bit 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 // Thread info - stored at bottom of each thread stack - don't change
12 // without also updating the inline assembler below.
13 struct thread_info {
14     struct thread_info *next;
15     void *stackpos;
16     struct thread_info **pprev;
17 };
18 struct thread_info VAR16VISIBLE MainThread;
19
20
21 /****************************************************************
22  * Low level helpers
23  ****************************************************************/
24
25 static inline u32 getcr0(void) {
26     u32 cr0;
27     asm("movl %%cr0, %0" : "=r"(cr0));
28     return cr0;
29 }
30 static inline void sgdt(struct descloc_s *desc) {
31     asm("sgdtl %0" : "=m"(*desc));
32 }
33 static inline void lgdt(struct descloc_s *desc) {
34     asm("lgdtl %0" : : "m"(*desc) : "memory");
35 }
36
37 // Call a 32bit SeaBIOS function from a 16bit SeaBIOS function.
38 static inline int
39 call32(void *func)
40 {
41     ASSERT16();
42     u32 cr0 = getcr0();
43     if (cr0 & CR0_PE)
44         // Called in 16bit protected mode?!
45         return -1;
46
47     // Backup cmos index register and disable nmi
48     u8 cmosindex = inb(PORT_CMOS_INDEX);
49     outb(cmosindex | NMI_DISABLE_BIT, PORT_CMOS_INDEX);
50     inb(PORT_CMOS_DATA);
51
52     // Backup fs/gs and gdt
53     u16 fs = GET_SEG(FS), gs = GET_SEG(GS);
54     struct descloc_s gdt;
55     sgdt(&gdt);
56
57     u32 bkup_ss, bkup_esp;
58     asm volatile(
59         // Backup ss/esp / set esp to flat stack location
60         "  movl %%ss, %0\n"
61         "  movl %%esp, %1\n"
62         "  shll $4, %0\n"
63         "  addl %0, %%esp\n"
64         "  movl %%ss, %0\n"
65
66         // Transition to 32bit mode, call func, return to 16bit
67         "  pushl $(" __stringify(BUILD_BIOS_ADDR) " + 1f)\n"
68         "  jmp transition32\n"
69         "  .code32\n"
70         "1:calll *%2\n"
71         "  pushl $2f\n"
72         "  jmp transition16big\n"
73
74         // Restore ds/ss/esp
75         "  .code16gcc\n"
76         "2:movl %0, %%ds\n"
77         "  movl %0, %%ss\n"
78         "  movl %1, %%esp\n"
79         : "=&r" (bkup_ss), "=&r" (bkup_esp)
80         : "r" (func)
81         : "eax", "ecx", "edx", "cc", "memory");
82
83     // Restore gdt and fs/gs
84     lgdt(&gdt);
85     SET_SEG(FS, fs);
86     SET_SEG(GS, gs);
87
88     // Restore cmos index register
89     outb(cmosindex, PORT_CMOS_INDEX);
90     inb(PORT_CMOS_DATA);
91     return 0;
92 }
93
94 // 16bit trampoline for enabling irqs from 32bit mode.
95 ASM16(
96     "  .global trampoline_checkirqs\n"
97     "trampoline_checkirqs:\n"
98     "  rep ; nop\n"
99     "  lretw"
100     );
101
102 static void
103 check_irqs(void)
104 {
105     if (MODESEGMENT) {
106         asm volatile(
107             "sti\n"
108             "nop\n"
109             "rep ; nop\n"
110             "cli\n"
111             "cld\n"
112             : : :"memory");
113         return;
114     }
115     extern void trampoline_checkirqs();
116     struct bregs br;
117     br.flags = F_IF;
118     br.code.seg = SEG_BIOS;
119     br.code.offset = (u32)&trampoline_checkirqs;
120     call16big(&br);
121 }
122
123 // 16bit trampoline for waiting for an irq from 32bit mode.
124 ASM16(
125     "  .global trampoline_waitirq\n"
126     "trampoline_waitirq:\n"
127     "  sti\n"
128     "  hlt\n"
129     "  lretw"
130     );
131
132 // Wait for next irq to occur.
133 void
134 wait_irq(void)
135 {
136     if (MODESEGMENT) {
137         asm volatile("sti ; hlt ; cli ; cld": : :"memory");
138         return;
139     }
140     if (CONFIG_THREADS && MainThread.next != &MainThread) {
141         // Threads still active - do a yield instead.
142         yield();
143         return;
144     }
145     extern void trampoline_waitirq();
146     struct bregs br;
147     br.flags = 0;
148     br.code.seg = SEG_BIOS;
149     br.code.offset = (u32)&trampoline_waitirq;
150     call16big(&br);
151 }
152
153
154 /****************************************************************
155  * Stack in EBDA
156  ****************************************************************/
157
158 // Switch to the extra stack in ebda and call a function.
159 inline u32
160 stack_hop(u32 eax, u32 edx, void *func)
161 {
162     ASSERT16();
163     u16 ebda_seg = get_ebda_seg(), bkup_ss;
164     u32 bkup_esp;
165     asm volatile(
166         // Backup current %ss/%esp values.
167         "movw %%ss, %w3\n"
168         "movl %%esp, %4\n"
169         // Copy ebda seg to %ds/%ss and set %esp
170         "movw %w6, %%ds\n"
171         "movw %w6, %%ss\n"
172         "movl %5, %%esp\n"
173         // Call func
174         "calll *%2\n"
175         // Restore segments and stack
176         "movw %w3, %%ds\n"
177         "movw %w3, %%ss\n"
178         "movl %4, %%esp"
179         : "+a" (eax), "+d" (edx), "+c" (func), "=&r" (bkup_ss), "=&r" (bkup_esp)
180         : "i" (EBDA_OFFSET_TOP_STACK), "r" (ebda_seg)
181         : "cc", "memory");
182     return eax;
183 }
184
185
186 /****************************************************************
187  * Threads
188  ****************************************************************/
189
190 #define THREADSTACKSIZE 4096
191 int VAR16VISIBLE CanPreempt;
192
193 void
194 thread_setup(void)
195 {
196     MainThread.next = &MainThread;
197     MainThread.pprev = &MainThread.next;
198     MainThread.stackpos = NULL;
199     CanPreempt = 0;
200 }
201
202 // Return the 'struct thread_info' for the currently running thread.
203 struct thread_info *
204 getCurThread(void)
205 {
206     u32 esp = getesp();
207     if (esp <= BUILD_STACK_ADDR)
208         return &MainThread;
209     return (void*)ALIGN_DOWN(esp, THREADSTACKSIZE);
210 }
211
212 // Switch to next thread stack.
213 static void
214 switch_next(struct thread_info *cur)
215 {
216     struct thread_info *next = cur->next;
217     if (cur == next)
218         // Nothing to do.
219         return;
220     asm volatile(
221         "  pushl $1f\n"                 // store return pc
222         "  pushl %%ebp\n"               // backup %ebp
223         "  movl %%esp, 4(%%eax)\n"      // cur->stackpos = %esp
224         "  movl 4(%%ecx), %%esp\n"      // %esp = next->stackpos
225         "  popl %%ebp\n"                // restore %ebp
226         "  retl\n"                      // restore pc
227         "1:\n"
228         : "+a"(cur), "+c"(next)
229         :
230         : "ebx", "edx", "esi", "edi", "cc", "memory");
231 }
232
233 // Briefly permit irqs to occur.
234 void
235 yield(void)
236 {
237     if (MODESEGMENT || !CONFIG_THREADS) {
238         // Just directly check irqs.
239         check_irqs();
240         return;
241     }
242     struct thread_info *cur = getCurThread();
243     if (cur == &MainThread)
244         // Permit irqs to fire
245         check_irqs();
246
247     // Switch to the next thread
248     switch_next(cur);
249 }
250
251 // Last thing called from a thread (called on "next" stack).
252 static void
253 __end_thread(struct thread_info *old)
254 {
255     old->next->pprev = old->pprev;
256     *old->pprev = old->next;
257     free(old);
258     dprintf(DEBUG_thread, "\\%08x/ End thread\n", (u32)old);
259     if (MainThread.next == &MainThread)
260         dprintf(1, "All threads complete.\n");
261 }
262
263 // Create a new thread and start executing 'func' in it.
264 void
265 run_thread(void (*func)(void*), void *data)
266 {
267     ASSERT32FLAT();
268     if (! CONFIG_THREADS)
269         goto fail;
270     struct thread_info *thread;
271     thread = memalign_tmphigh(THREADSTACKSIZE, THREADSTACKSIZE);
272     if (!thread)
273         goto fail;
274
275     thread->stackpos = (void*)thread + THREADSTACKSIZE;
276     struct thread_info *cur = getCurThread();
277     thread->next = cur;
278     thread->pprev = cur->pprev;
279     cur->pprev = &thread->next;
280     *thread->pprev = thread;
281
282     dprintf(DEBUG_thread, "/%08x\\ Start thread\n", (u32)thread);
283     asm volatile(
284         // Start thread
285         "  pushl $1f\n"                 // store return pc
286         "  pushl %%ebp\n"               // backup %ebp
287         "  movl %%esp, 4(%%edx)\n"      // cur->stackpos = %esp
288         "  movl 4(%%ebx), %%esp\n"      // %esp = thread->stackpos
289         "  calll *%%ecx\n"              // Call func
290
291         // End thread
292         "  movl (%%ebx), %%ecx\n"       // %ecx = thread->next
293         "  movl 4(%%ecx), %%esp\n"      // %esp = next->stackpos
294         "  movl %%ebx, %%eax\n"
295         "  calll %4\n"                  // call __end_thread(thread)
296         "  popl %%ebp\n"                // restore %ebp
297         "  retl\n"                      // restore pc
298         "1:\n"
299         : "+a"(data), "+c"(func), "+b"(thread), "+d"(cur)
300         : "m"(*(u8*)__end_thread)
301         : "esi", "edi", "cc", "memory");
302     return;
303
304 fail:
305     func(data);
306 }
307
308 // Wait for all threads (other than the main thread) to complete.
309 void
310 wait_threads(void)
311 {
312     ASSERT32FLAT();
313     if (! CONFIG_THREADS)
314         return;
315     while (MainThread.next != &MainThread)
316         yield();
317 }
318
319 void
320 mutex_lock(struct mutex_s *mutex)
321 {
322     ASSERT32FLAT();
323     if (! CONFIG_THREADS)
324         return;
325     while (mutex->isLocked)
326         yield();
327     mutex->isLocked = 1;
328 }
329
330 void
331 mutex_unlock(struct mutex_s *mutex)
332 {
333     ASSERT32FLAT();
334     if (! CONFIG_THREADS)
335         return;
336     mutex->isLocked = 0;
337 }
338
339
340 /****************************************************************
341  * Thread preemption
342  ****************************************************************/
343
344 static u32 PreemptCount;
345
346 // Turn on RTC irqs and arrange for them to check the 32bit threads.
347 void
348 start_preempt(void)
349 {
350     if (! CONFIG_THREADS || ! CONFIG_THREAD_OPTIONROMS)
351         return;
352     CanPreempt = 1;
353     PreemptCount = 0;
354     useRTC();
355 }
356
357 // Turn off RTC irqs / stop checking for thread execution.
358 void
359 finish_preempt(void)
360 {
361     if (! CONFIG_THREADS || ! CONFIG_THREAD_OPTIONROMS) {
362         yield();
363         return;
364     }
365     CanPreempt = 0;
366     releaseRTC();
367     dprintf(9, "Done preempt - %d checks\n", PreemptCount);
368     yield();
369 }
370
371 // Check if preemption is on, and wait for it to complete if so.
372 int
373 wait_preempt(void)
374 {
375     if (MODESEGMENT || !CONFIG_THREADS || !CONFIG_THREAD_OPTIONROMS
376         || !CanPreempt)
377         return 0;
378     while (CanPreempt)
379         yield();
380     return 1;
381 }
382
383 extern void yield_preempt(void);
384 #if MODESEGMENT == 0
385 // Try to execute 32bit threads.
386 void VISIBLE32FLAT
387 yield_preempt(void)
388 {
389     PreemptCount++;
390     switch_next(&MainThread);
391 }
392 #endif
393
394 // 16bit code that checks if threads are pending and executes them if so.
395 void
396 check_preempt(void)
397 {
398     if (! CONFIG_THREADS || ! CONFIG_THREAD_OPTIONROMS
399         || !GET_GLOBAL(CanPreempt)
400         || GET_GLOBAL(MainThread.next) == &MainThread)
401         return;
402
403     call32(yield_preempt);
404 }