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