Some real machine can apparently have a flaky real-time-clock. So, don't use it for timing the boot menu - the regular timer irq is fine for this purpose.
Kevin O'Connor (4): Allow wait_irq to be called in 32bit code. Rename check_time() to check_tsc(). Generalize timer based delay code. Don't use RTC to time boot menu delay.
Makefile | 2 +- src/ata.c | 8 +++--- src/boot.c | 2 +- src/cdrom.c | 2 +- src/clock.c | 36 +++++++++++++++++++++--- src/ps2port.c | 2 +- src/serial.c | 37 ++++--------------------- src/stacks.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++------- src/usb-ehci.c | 10 +++--- src/usb-hub.c | 4 +- src/usb-ohci.c | 8 +++--- src/usb-uhci.c | 6 ++-- src/util.c | 54 ++---------------------------------- src/util.h | 20 +++++-------- 14 files changed, 144 insertions(+), 130 deletions(-)
If wait_irq() is called from 32bit code, then jump to 16bit mode for the wait.
Have wait_irq check for threads, and have it use yield if threads are pending. This ensures threads aren't delayed if anything calls wait_irq.
Use wait_irq() in 32bit mode during a failed boot. --- Makefile | 2 +- src/boot.c | 2 +- src/stacks.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++------- src/util.c | 29 -------------------- src/util.h | 14 +++------- 5 files changed, 79 insertions(+), 51 deletions(-)
diff --git a/Makefile b/Makefile index d0b8881..72d711d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ SRC16=$(SRCBOTH) system.c disk.c apm.c font.c SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \ acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \ lzmadecode.c usb-hub.c paravirt.c -SRC32SEG=util.c output.c pci.c pcibios.c apm.c +SRC32SEG=util.c output.c pci.c pcibios.c apm.c stacks.c
cc-option = $(shell if test -z "`$(1) $(2) -S -o /dev/null -xc \ /dev/null 2>&1`"; then echo "$(2)"; else echo "$(3)"; fi ;) diff --git a/src/boot.c b/src/boot.c index 36450f0..335522f 100644 --- a/src/boot.c +++ b/src/boot.c @@ -449,7 +449,7 @@ do_boot(u16 seq_nr) printf("No bootable device.\n"); // Loop with irqs enabled - this allows ctrl+alt+delete to work. for (;;) - biosusleep(1000000); + wait_irq(); }
/* Do the loading, and set up vector as a far pointer to the boot diff --git a/src/stacks.c b/src/stacks.c index 859de3f..f5feeeb 100644 --- a/src/stacks.c +++ b/src/stacks.c @@ -8,6 +8,20 @@ #include "util.h" // dprintf #include "bregs.h" // CR0_PE
+// Thread info - stored at bottom of each thread stack - don't change +// without also updating the inline assembler below. +struct thread_info { + struct thread_info *next; + void *stackpos; + struct thread_info **pprev; +}; +struct thread_info VAR16VISIBLE MainThread; + + +/**************************************************************** + * Low level helpers + ****************************************************************/ + static inline u32 getcr0(void) { u32 cr0; asm("movl %%cr0, %0" : "=r"(cr0)); @@ -77,6 +91,65 @@ call32(void *func) return 0; }
+// 16bit trampoline for enabling irqs from 32bit mode. +ASM16( + " .global trampoline_checkirqs\n" + "trampoline_checkirqs:\n" + " rep ; nop\n" + " lretw" + ); + +static void +check_irqs(void) +{ + if (MODESEGMENT) { + asm volatile( + "sti\n" + "nop\n" + "rep ; nop\n" + "cli\n" + "cld\n" + : : :"memory"); + return; + } + extern void trampoline_checkirqs(); + struct bregs br; + br.flags = F_IF; + br.code.seg = SEG_BIOS; + br.code.offset = (u32)&trampoline_checkirqs; + call16big(&br); +} + +// 16bit trampoline for waiting for an irq from 32bit mode. +ASM16( + " .global trampoline_waitirq\n" + "trampoline_waitirq:\n" + " sti\n" + " hlt\n" + " lretw" + ); + +// Wait for next irq to occur. +void +wait_irq(void) +{ + if (MODESEGMENT) { + asm volatile("sti ; hlt ; cli ; cld": : :"memory"); + return; + } + if (CONFIG_THREADS && MainThread.next != &MainThread) { + // Threads still active - do a yield instead. + yield(); + return; + } + extern void trampoline_waitirq(); + struct bregs br; + br.flags = 0; + br.code.seg = SEG_BIOS; + br.code.offset = (u32)&trampoline_waitirq; + call16big(&br); +} +
/**************************************************************** * Stack in EBDA @@ -115,16 +188,6 @@ stack_hop(u32 eax, u32 edx, void *func) ****************************************************************/
#define THREADSTACKSIZE 4096 - -// Thread info - stored at bottom of each thread stack - don't change -// without also updating the inline assembler below. -struct thread_info { - struct thread_info *next; - void *stackpos; - struct thread_info **pprev; -}; - -struct thread_info VAR16VISIBLE MainThread; int VAR16VISIBLE CanPreempt;
void diff --git a/src/util.c b/src/util.c index e146c97..b078d5f 100644 --- a/src/util.c +++ b/src/util.c @@ -57,35 +57,6 @@ __call16_int(struct bregs *callregs, u16 offset) call16(callregs); }
-// 16bit trampoline for enabling irqs from 32bit mode. -ASM16( - " .global trampoline_checkirqs\n" - "trampoline_checkirqs:\n" - " rep ; nop\n" - " lretw" - ); - -void -check_irqs(void) -{ - if (MODE16) { - asm volatile( - "sti\n" - "nop\n" - "rep ; nop\n" - "cli\n" - "cld\n" - : : :"memory"); - } else { - extern void trampoline_checkirqs(); - struct bregs br; - br.flags = F_IF; - br.code.seg = SEG_BIOS; - br.code.offset = (u32)&trampoline_checkirqs; - call16big(&br); - } -} -
/**************************************************************** * String ops diff --git a/src/util.h b/src/util.h index e47860f..a5c5b75 100644 --- a/src/util.h +++ b/src/util.h @@ -21,7 +21,7 @@ static inline void irq_enable(void) static inline unsigned long irq_save(void) { unsigned long flags; - asm volatile("pushfl ; popl %0" : "=g" (flags)); + asm volatile("pushfl ; popl %0" : "=g" (flags): :"memory"); irq_disable(); return flags; } @@ -36,12 +36,6 @@ static inline void cpu_relax(void) asm volatile("rep ; nop": : :"memory"); }
-// Atomically enable irqs and sleep until an irq; then re-disable irqs. -static inline void wait_irq(void) -{ - asm volatile("sti ; hlt ; cli ; cld": : :"memory"); -} - static inline void nop(void) { asm volatile("nop"); @@ -49,12 +43,12 @@ static inline void nop(void)
static inline void hlt(void) { - asm volatile("hlt"); + asm volatile("hlt": : :"memory"); }
static inline void wbinvd(void) { - asm volatile("wbinvd"); + asm volatile("wbinvd": : :"memory"); }
#define CPUID_MSR (1 << 5) @@ -182,7 +176,6 @@ inline void __call16_int(struct bregs *callregs, u16 offset); extern void irq_trampoline_ ##nr (); \ __call16_int((callregs), (u32)&irq_trampoline_ ##nr ); \ } while (0) -void check_irqs(void); u8 checksum_far(u16 buf_seg, void *buf_far, u32 len); u8 checksum(void *buf, u32 len); size_t strlen(const char *s); @@ -209,6 +202,7 @@ extern struct thread_info MainThread; void thread_setup(void); struct thread_info *getCurThread(void); void yield(void); +void wait_irq(void); void run_thread(void (*func)(void*), void *data); void wait_threads(void); struct mutex_s { u32 isLocked; };
--- src/ata.c | 8 ++++---- src/cdrom.c | 2 +- src/clock.c | 6 +++--- src/ps2port.c | 2 +- src/usb-ehci.c | 10 +++++----- src/usb-hub.c | 4 ++-- src/usb-ohci.c | 8 ++++---- src/usb-uhci.c | 6 +++--- src/util.h | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/src/ata.c b/src/ata.c index 346c340..62cdbb8 100644 --- a/src/ata.c +++ b/src/ata.c @@ -35,7 +35,7 @@ await_ide(u8 mask, u8 flags, u16 base, u16 timeout) u8 status = inb(base+ATA_CB_STAT); if ((status & mask) == flags) return status; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return -1; } @@ -106,7 +106,7 @@ ata_reset(struct atadrive_s *adrive_g) if (inb(iobase1 + ATA_CB_DH) == ATA_CB_DH_DEV1) break; // Change drive request failed to take effect - retry. - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); goto done; } @@ -453,7 +453,7 @@ ata_dma_transfer(struct disk_op_s *op) if (status & BM_STATUS_IRQ) break; // Transfer in progress - if (check_time(end)) { + if (check_tsc(end)) { // Timeout. warn_timeout(); break; @@ -850,7 +850,7 @@ powerup_await_non_bsy(u16 base) dprintf(4, "powerup IDE floating\n"); return orstatus; } - if (check_time(SpinupEnd)) { + if (check_tsc(SpinupEnd)) { warn_timeout(); return -1; } diff --git a/src/cdrom.c b/src/cdrom.c index a0fdc50..f7af425 100644 --- a/src/cdrom.c +++ b/src/cdrom.c @@ -197,7 +197,7 @@ atapi_is_ready(struct disk_op_s *op) int in_progress = 0; u64 end = calc_future_tsc(5000); for (;;) { - if (check_time(end)) { + if (check_tsc(end)) { dprintf(1, "read capacity failed\n"); return -1; } diff --git a/src/clock.c b/src/clock.c index c79f392..0567955 100644 --- a/src/clock.c +++ b/src/clock.c @@ -95,7 +95,7 @@ tscdelay(u64 diff) { u64 start = rdtscll(); u64 end = start + diff; - while (!check_time(end)) + while (!check_tsc(end)) cpu_relax(); }
@@ -104,7 +104,7 @@ tscsleep(u64 diff) { u64 start = rdtscll(); u64 end = start + diff; - while (!check_time(end)) + while (!check_tsc(end)) yield(); }
@@ -164,7 +164,7 @@ rtc_updating(void) for (;;) { if ((inb_cmos(CMOS_STATUS_A) & RTC_A_UIP) == 0) return 0; - if (check_time(end)) + if (check_tsc(end)) // update-in-progress never transitioned to 0 return -1; yield(); diff --git a/src/ps2port.c b/src/ps2port.c index f379eb3..5b73d23 100644 --- a/src/ps2port.c +++ b/src/ps2port.c @@ -161,7 +161,7 @@ ps2_recvbyte(int aux, int needack, int timeout) dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data, status); }
- if (check_time(end)) { + if (check_tsc(end)) { // Don't warn on second byte of a reset if (timeout > 100) warn_timeout(); diff --git a/src/usb-ehci.c b/src/usb-ehci.c index 4df4b1c..4e228bd 100644 --- a/src/usb-ehci.c +++ b/src/usb-ehci.c @@ -194,7 +194,7 @@ configure_ehci(void *data) cmd = readl(&cntl->regs->usbcmd); if (!(cmd & CMD_HCRESET)) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); goto fail; } @@ -314,7 +314,7 @@ ehci_wait_qh(struct usb_ehci_s *cntl, struct ehci_qh *qh) if (qh->qtd_next & EHCI_PTR_TERM) // XXX - confirm return 0; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return -1; } @@ -341,7 +341,7 @@ ehci_waittick(struct usb_ehci_s *cntl) if (!(cmd & CMD_IAAD)) break; } - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return; } @@ -354,7 +354,7 @@ ehci_waittick(struct usb_ehci_s *cntl) sts = readl(&cntl->regs->usbsts); if (sts & STS_IAA) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return; } @@ -566,7 +566,7 @@ ehci_wait_td(struct ehci_qtd *td) status = td->token; if (!(status & QTD_STS_ACTIVE)) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return -1; } diff --git a/src/usb-hub.c b/src/usb-hub.c index 1586bac..b2d9ff2 100644 --- a/src/usb-hub.c +++ b/src/usb-hub.c @@ -88,7 +88,7 @@ usb_hub_detect(struct usbhub_s *hub, u32 port) if (sts.wPortStatus & USB_PORT_STAT_CONNECTION) // Device connected. break; - if (check_time(end)) + if (check_tsc(end)) // No device found. return -1; msleep(5); @@ -129,7 +129,7 @@ usb_hub_reset(struct usbhub_s *hub, u32 port) goto fail; if (!(sts.wPortStatus & USB_PORT_STAT_RESET)) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); goto fail; } diff --git a/src/usb-ohci.c b/src/usb-ohci.c index 43fe238..7b975d9 100644 --- a/src/usb-ohci.c +++ b/src/usb-ohci.c @@ -60,7 +60,7 @@ ohci_hub_reset(struct usbhub_s *hub, u32 port) if (!(sts & RH_PS_PRS)) // XXX - need to ensure USB_TIME_DRSTR time in reset? break; - if (check_time(end)) { + if (check_tsc(end)) { // Timeout. warn_timeout(); ohci_hub_disconnect(hub, port); @@ -129,7 +129,7 @@ start_ohci(struct usb_ohci_s *cntl, struct ohci_hcca *hcca) u32 status = readl(&cntl->regs->cmdstatus); if (! status & OHCI_HCR) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return -1; } @@ -246,7 +246,7 @@ wait_ed(struct ohci_ed *ed) for (;;) { if (ed->hwHeadP == ed->hwTailP) return 0; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return -1; } @@ -265,7 +265,7 @@ ohci_waittick(struct usb_ohci_s *cntl) for (;;) { if (hcca->frame_no != startframe) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return; } diff --git a/src/usb-uhci.c b/src/usb-uhci.c index b05e09a..6549808 100644 --- a/src/usb-uhci.c +++ b/src/usb-uhci.c @@ -214,7 +214,7 @@ wait_qh(struct usb_uhci_s *cntl, struct uhci_qh *qh) for (;;) { if (qh->element & UHCI_PTR_TERM) return 0; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); struct uhci_td *td = (void*)(qh->element & ~UHCI_PTR_BITS); dprintf(1, "Timeout on wait_qh %p (td=%p s=%x c=%x/%x)\n" @@ -237,7 +237,7 @@ uhci_waittick(u16 iobase) for (;;) { if (inw(iobase + USBFRNUM) != startframe) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return; } @@ -417,7 +417,7 @@ wait_td(struct uhci_td *td) status = td->status; if (!(status & TD_CTRL_ACTIVE)) break; - if (check_time(end)) { + if (check_tsc(end)) { warn_timeout(); return -1; } diff --git a/src/util.h b/src/util.h index a5c5b75..7902e83 100644 --- a/src/util.h +++ b/src/util.h @@ -292,7 +292,7 @@ void lpt_setup(void); // clock.c #define PIT_TICK_RATE 1193180 // Underlying HZ of PIT #define PIT_TICK_INTERVAL 65536 // Default interval for 18.2Hz timer -static inline int check_time(u64 end) { +static inline int check_tsc(u64 end) { return (s64)(rdtscll() - end) > 0; } void timer_setup(void);
Move the timer based counting code in serial.c to clock.c.
Rework the interface to make it similar to the tsc based timers. --- src/clock.c | 26 +++++++++++++++++++++++++- src/serial.c | 37 ++++++------------------------------- src/util.h | 3 +++ 3 files changed, 34 insertions(+), 32 deletions(-)
diff --git a/src/clock.c b/src/clock.c index 0567955..062658c 100644 --- a/src/clock.c +++ b/src/clock.c @@ -54,7 +54,6 @@ * TSC timer ****************************************************************/
-#define TICKS_PER_DAY (u32)((u64)60*60*24*PIT_TICK_RATE / PIT_TICK_INTERVAL) #define CALIBRATE_COUNT 0x800 // Approx 1.7ms
u32 cpu_khz VAR16VISIBLE; @@ -223,6 +222,31 @@ timer_setup(void) * Standard clock functions ****************************************************************/
+#define TICKS_PER_DAY (u32)((u64)60*60*24*PIT_TICK_RATE / PIT_TICK_INTERVAL) + +// Calculate the timer value at 'count' number of full timer ticks in +// the future. +u32 +calc_future_timer_ticks(u32 count) +{ + return (GET_BDA(timer_counter) + count + 1) % TICKS_PER_DAY; +} +// Return the timer value that is 'msecs' time in the future. +u32 +calc_future_timer(u32 msecs) +{ + u32 kticks = DIV_ROUND_UP((u64)(msecs * PIT_TICK_RATE), PIT_TICK_INTERVAL); + u32 ticks = DIV_ROUND_UP(kticks, 1000); + return calc_future_timer_ticks(ticks); +} +// Check if the given timer value has passed. +int +check_timer(u32 end) +{ + return (((GET_BDA(timer_counter) + TICKS_PER_DAY - end) % TICKS_PER_DAY) + < (TICKS_PER_DAY/2)); +} + // get current clock count static void handle_1a00(struct bregs *regs) diff --git a/src/serial.c b/src/serial.c index 3f68bc4..21b4bd0 100644 --- a/src/serial.c +++ b/src/serial.c @@ -9,31 +9,6 @@ #include "util.h" // debug_enter #include "bregs.h" // struct bregs
-// Timers based on 18.2Hz clock irq. -struct tick_timer_s { - u16 last_tick, remaining; -}; - -struct tick_timer_s -initTickTimer(u16 count) -{ - struct tick_timer_s tt = {GET_BDA(timer_counter), count}; - return tt; -} - -int -checkTickTimer(struct tick_timer_s *tt) -{ - u16 timer = GET_BDA(timer_counter); - if (tt->last_tick != timer) { - tt->last_tick = timer; - tt->last_tick--; - if (!tt->last_tick) - return 1; - } - return 0; -} -
/**************************************************************** * COM ports @@ -117,7 +92,7 @@ handle_1401(struct bregs *regs) u16 addr = getComAddr(regs); if (!addr) return; - struct tick_timer_s tt = initTickTimer(GET_BDA(com_timeout[regs->dx])); + u32 end = calc_future_timer_ticks(GET_BDA(com_timeout[regs->dx])); for (;;) { u8 lsr = inb(addr+SEROFF_LSR); if ((lsr & 0x60) == 0x60) { @@ -127,7 +102,7 @@ handle_1401(struct bregs *regs) regs->ah = lsr; break; } - if (checkTickTimer(&tt)) { + if (check_timer(end)) { // Timed out - can't write data. regs->ah = lsr | 0x80; break; @@ -144,7 +119,7 @@ handle_1402(struct bregs *regs) u16 addr = getComAddr(regs); if (!addr) return; - struct tick_timer_s tt = initTickTimer(GET_BDA(com_timeout[regs->dx])); + u32 end = calc_future_timer_ticks(GET_BDA(com_timeout[regs->dx])); for (;;) { u8 lsr = inb(addr+SEROFF_LSR); if (lsr & 0x01) { @@ -153,7 +128,7 @@ handle_1402(struct bregs *regs) regs->ah = lsr; break; } - if (checkTickTimer(&tt)) { + if (check_timer(end)) { // Timed out - can't read data. regs->ah = lsr | 0x80; break; @@ -261,7 +236,7 @@ handle_1700(struct bregs *regs) if (!addr) return;
- struct tick_timer_s tt = initTickTimer(GET_BDA(lpt_timeout[regs->dx])); + u32 end = calc_future_timer_ticks(GET_BDA(lpt_timeout[regs->dx]));
outb(regs->al, addr); u8 val8 = inb(addr+2); @@ -276,7 +251,7 @@ handle_1700(struct bregs *regs) regs->ah = v ^ 0x48; break; } - if (checkTickTimer(&tt)) { + if (check_timer(end)) { // Timeout regs->ah = (v ^ 0x48) | 0x01; break; diff --git a/src/util.h b/src/util.h index 7902e83..09d9e40 100644 --- a/src/util.h +++ b/src/util.h @@ -304,6 +304,9 @@ void usleep(u32 count); void msleep(u32 count); u64 calc_future_tsc(u32 msecs); u64 calc_future_tsc_usec(u32 usecs); +u32 calc_future_timer_ticks(u32 count); +u32 calc_future_timer(u32 msecs); +int check_timer(u32 end); void handle_1583(struct bregs *regs); void handle_1586(struct bregs *regs); void useRTC(void);
It appears real machines sometimes have a flaky RTC, so avoid using the RTC irq during boot. Instead, use a delay based on the standard timer irq.
This also optimizes CONFIG_THREAD_OPTIONROMS users as it is no longer necessary to use preemption - the wait_irq() call handles task switching natively. --- src/clock.c | 4 ++++ src/util.c | 25 +++---------------------- src/util.h | 1 - 3 files changed, 7 insertions(+), 23 deletions(-)
diff --git a/src/clock.c b/src/clock.c index 062658c..49ab901 100644 --- a/src/clock.c +++ b/src/clock.c @@ -231,14 +231,18 @@ calc_future_timer_ticks(u32 count) { return (GET_BDA(timer_counter) + count + 1) % TICKS_PER_DAY; } + // Return the timer value that is 'msecs' time in the future. u32 calc_future_timer(u32 msecs) { + if (!msecs) + return GET_BDA(timer_counter); u32 kticks = DIV_ROUND_UP((u64)(msecs * PIT_TICK_RATE), PIT_TICK_INTERVAL); u32 ticks = DIV_ROUND_UP(kticks, 1000); return calc_future_timer_ticks(ticks); } + // Check if the given timer value has passed. int check_timer(u32 end) diff --git a/src/util.c b/src/util.c index b078d5f..b2a22f7 100644 --- a/src/util.c +++ b/src/util.c @@ -244,19 +244,6 @@ strtcpy(char *dest, const char *src, size_t len) * Keyboard calls ****************************************************************/
-// Wait for 'usec' microseconds using (with irqs enabled) using int 1586. -void -biosusleep(u32 usec) -{ - struct bregs br; - memset(&br, 0, sizeof(br)); - br.flags = F_IF; - br.ah = 0x86; - br.cx = usec >> 16; - br.dx = usec; - call16_int(0x15, &br); -} - // See if a keystroke is pending in the keyboard buffer. static int check_for_keystroke(void) @@ -265,9 +252,7 @@ check_for_keystroke(void) memset(&br, 0, sizeof(br)); br.flags = F_IF; br.ah = 1; - start_preempt(); call16_int(0x16, &br); - finish_preempt(); return !(br.flags & F_ZF); }
@@ -278,9 +263,7 @@ get_raw_keystroke(void) struct bregs br; memset(&br, 0, sizeof(br)); br.flags = F_IF; - start_preempt(); call16_int(0x16, &br); - finish_preempt(); return br.ah; }
@@ -288,14 +271,12 @@ get_raw_keystroke(void) int get_keystroke(int msec) { + u32 end = calc_future_timer(msec); for (;;) { if (check_for_keystroke()) return get_raw_keystroke(); - if (msec <= 0) + if (check_timer(end)) return -1; - start_preempt(); - biosusleep(50*1000); - finish_preempt(); - msec -= 50; + wait_irq(); } } diff --git a/src/util.h b/src/util.h index 09d9e40..b475c42 100644 --- a/src/util.h +++ b/src/util.h @@ -193,7 +193,6 @@ void *memcpy(void *d1, const void *s1, size_t len); void iomemcpy(void *d, const void *s, u32 len); void *memmove(void *d, const void *s, size_t len); char *strtcpy(char *dest, const char *src, size_t len); -void biosusleep(u32 usec); int get_keystroke(int msec);
// stacks.c