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