From patchwork Wed Sep 28 09:07:17 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gerd Hoffmann X-Patchwork-Id: 9353393 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 9DE546077B for ; Wed, 28 Sep 2016 09:08:17 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8C41B293A7 for ; Wed, 28 Sep 2016 09:08:17 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 811D72945B; Wed, 28 Sep 2016 09:08:17 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 635C6293A7 for ; Wed, 28 Sep 2016 09:08:16 +0000 (UTC) Received: from localhost ([::1]:57026 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bpAqd-0001xq-GN for patchwork-qemu-devel@patchwork.kernel.org; Wed, 28 Sep 2016 05:08:15 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:48830) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bpAq1-0001oq-CE for qemu-devel@nongnu.org; Wed, 28 Sep 2016 05:07:39 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1bpApz-0001nK-15 for qemu-devel@nongnu.org; Wed, 28 Sep 2016 05:07:37 -0400 Received: from mx1.redhat.com ([209.132.183.28]:57604) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bpApy-0001mu-NN for qemu-devel@nongnu.org; Wed, 28 Sep 2016 05:07:34 -0400 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 5415315556; Wed, 28 Sep 2016 09:07:34 +0000 (UTC) Received: from nilsson.home.kraxel.org (ovpn-116-31.ams2.redhat.com [10.36.116.31]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u8S97Vf3025048; Wed, 28 Sep 2016 05:07:32 -0400 Received: by nilsson.home.kraxel.org (Postfix, from userid 500) id E861B80566; Wed, 28 Sep 2016 11:07:30 +0200 (CEST) From: Gerd Hoffmann To: seabios@seabios.org Date: Wed, 28 Sep 2016 11:07:17 +0200 Message-Id: <1475053640-30483-5-git-send-email-kraxel@redhat.com> In-Reply-To: <1475053640-30483-1-git-send-email-kraxel@redhat.com> References: <1475053640-30483-1-git-send-email-kraxel@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.29]); Wed, 28 Sep 2016 09:07:34 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH 4/7] add serial console support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, Gerd Hoffmann Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Redirect int10 calls to serial console output. Parse serial input and queue key events. Signed-off-by: Gerd Hoffmann --- Makefile | 2 +- src/clock.c | 1 + src/misc.c | 2 + src/optionroms.c | 7 +- src/sercon.c | 595 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 3 + 6 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 src/sercon.c diff --git a/Makefile b/Makefile index 1633cd6..a9d4d72 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ LD32BIT_FLAG:=-melf_i386 # Source files SRCBOTH=misc.c stacks.c output.c string.c block.c cdrom.c disk.c mouse.c kbd.c \ - system.c serial.c clock.c resume.c pnpbios.c vgahooks.c pcibios.c apm.c \ + system.c serial.c sercon.c clock.c resume.c pnpbios.c vgahooks.c pcibios.c apm.c \ cp437.c \ hw/pci.c hw/timer.c hw/rtc.c hw/dma.c hw/pic.c hw/ps2port.c hw/serialio.c \ hw/usb.c hw/usb-uhci.c hw/usb-ohci.c hw/usb-ehci.c \ diff --git a/src/clock.c b/src/clock.c index e83e0f3..e44e112 100644 --- a/src/clock.c +++ b/src/clock.c @@ -295,6 +295,7 @@ clock_update(void) floppy_tick(); usb_check_event(); ps2_check_event(); + sercon_check_event(); } // INT 08h System Timer ISR Entry Point diff --git a/src/misc.c b/src/misc.c index f02237c..f4b656d 100644 --- a/src/misc.c +++ b/src/misc.c @@ -11,6 +11,7 @@ #include "output.h" // debug_enter #include "stacks.h" // call16_int #include "string.h" // memset +#include "util.h" // serial_10 #define PORT_MATH_CLEAR 0x00f0 @@ -57,6 +58,7 @@ handle_10(struct bregs *regs) { debug_enter(regs, DEBUG_HDL_10); // don't do anything, since the VGA BIOS handles int10h requests + sercon_10(regs); } // NMI handler diff --git a/src/optionroms.c b/src/optionroms.c index 65f7fe0..f9e9593 100644 --- a/src/optionroms.c +++ b/src/optionroms.c @@ -432,9 +432,14 @@ vgarom_setup(void) run_file_roms("vgaroms/", 1, NULL); rom_reserve(0); - if (rom_get_last() == BUILD_ROM_START) + if (rom_get_last() == BUILD_ROM_START) { // No VGA rom found + if (romfile_loadint("etc/sercon-enable", 0)) { + sercon_enable(); + enable_vga_console(); + } return; + } VgaROM = (void*)BUILD_ROM_START; enable_vga_console(); diff --git a/src/sercon.c b/src/sercon.c new file mode 100644 index 0000000..69b0c63 --- /dev/null +++ b/src/sercon.c @@ -0,0 +1,595 @@ +// serial console support +// +// Copyright (C) 2016 Gerd Hoffmann +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "biosvar.h" // SET_BDA +#include "bregs.h" // struct bregs +#include "stacks.h" // yield +#include "output.h" // dprintf +#include "util.h" // irqtimer_calc_ticks +#include "hw/serialio.h" // SEROFF_IER +#include "cp437.h" + +static u8 video_rows(void) +{ + return GET_BDA(video_rows)+1; +} + +static u8 video_cols(void) +{ + return GET_BDA(video_cols); +} + +static u8 cursor_pos_col(void) +{ + u16 pos = GET_BDA(cursor_pos[0]); + return pos & 0xff; +} + +static u8 cursor_pos_row(void) +{ + u16 pos = GET_BDA(cursor_pos[0]); + return (pos >> 8) & 0xff; +} + +static void cursor_pos_set(u8 row, u8 col) +{ + u16 pos = ((u16)row << 8) | col; + SET_BDA(cursor_pos[0], pos); +} + +/**************************************************************** + * serial console output + ****************************************************************/ + +VARLOW u16 sercon_port; + +/* + * We have a small output buffer here, for lazy output. That allows + * to avoid a whole bunch of control sequences for pointless cursor + * moves, so when logging the output it'll be *alot* less cluttered. + * + * sercon_char/attr is the actual output buffer. + * sercon_attr_last is the most recent attribute sent to the terminal. + * sercon_col_last is the most recent column sent to the terminal. + * sercon_row_last is the most recent row sent to the terminal. + */ +VARLOW u8 sercon_attr_last; +VARLOW u8 sercon_col_last; +VARLOW u8 sercon_row_last; +VARLOW u8 sercon_char; +VARLOW u8 sercon_attr = 0x07; + +static VAR16 u8 sercon_cmap[8] = { '0', '4', '2', '6', '1', '5', '3', '7' }; + +static void sercon_putchar(u8 chr) +{ + u16 addr = GET_LOW(sercon_port); + u32 end = irqtimer_calc_ticks(0x0a); + +#if 0 + /* for visual control sequence debugging */ + if (chr == '\x1b') + chr = '*'; +#endif + + for (;;) { + u8 lsr = inb(addr+SEROFF_LSR); + if ((lsr & 0x60) == 0x60) { + // Success - can write data + outb(chr, addr+SEROFF_DATA); + break; + } + if (irqtimer_check(end)) { + break; + } + yield(); + } +} + +static void sercon_term_reset(void) +{ + sercon_putchar('\x1b'); + sercon_putchar('c'); +} + +static void sercon_term_clear_screen(void) +{ + sercon_putchar('\x1b'); + sercon_putchar('['); + sercon_putchar('2'); + sercon_putchar('J'); +} + +static void sercon_term_no_linewrap(void) +{ + sercon_putchar('\x1b'); + sercon_putchar('['); + sercon_putchar('?'); + sercon_putchar('7'); + sercon_putchar('l'); +} + +static void sercon_term_cursor_goto(u8 row, u8 col) +{ + row++; col++; + sercon_putchar('\x1b'); + sercon_putchar('['); + sercon_putchar('0' + row / 10); + sercon_putchar('0' + row % 10); + sercon_putchar(';'); + sercon_putchar('0' + col / 10); + sercon_putchar('0' + col % 10); + sercon_putchar('H'); +} + +static void sercon_term_set_color(u8 fg, u8 bg, u8 bold) +{ + sercon_putchar('\x1b'); + sercon_putchar('['); + sercon_putchar('0'); + if (fg != 7) { + sercon_putchar(';'); + sercon_putchar('3'); + sercon_putchar(GET_GLOBAL(sercon_cmap[fg & 7])); + } + if (bg != 0) { + sercon_putchar(';'); + sercon_putchar('4'); + sercon_putchar(GET_GLOBAL(sercon_cmap[bg & 7])); + } + if (bold) { + sercon_putchar(';'); + sercon_putchar('1'); + } + sercon_putchar('m'); +} + +static void sercon_set_attr(u8 attr) +{ + if (attr == GET_LOW(sercon_attr_last)) + return; + + SET_LOW(sercon_attr_last, attr); + sercon_term_set_color((attr >> 0) & 7, + (attr >> 4) & 7, + attr & 0x08); +} + +static void sercon_print_utf8(u8 chr) +{ + u16 unicode = cp437_to_unicode(chr); + + if (unicode < 0x7f) { + sercon_putchar(unicode); + } else if (unicode < 0x7ff) { + sercon_putchar(0xc0 | ((unicode >> 6) & 0x1f)); + sercon_putchar(0x80 | ((unicode >> 0) & 0x3f)); + } else { + sercon_putchar(0xe0 | ((unicode >> 12) & 0x0f)); + sercon_putchar(0x80 | ((unicode >> 6) & 0x3f)); + sercon_putchar(0x80 | ((unicode >> 0) & 0x3f)); + } +} + +static void sercon_lazy_cursor_sync(void) +{ + u8 row = cursor_pos_row(); + u8 col = cursor_pos_col(); + + if (GET_LOW(sercon_row_last) == row && + GET_LOW(sercon_col_last) == col) + return; + + if (col == 0 && GET_LOW(sercon_row_last) <= row) { + if (GET_LOW(sercon_col_last) != 0) { + sercon_putchar('\r'); + SET_LOW(sercon_col_last, 0); + } + while (GET_LOW(sercon_row_last) < row) { + sercon_putchar('\n'); + SET_LOW(sercon_row_last, GET_LOW(sercon_row_last)+1); + } + if (GET_LOW(sercon_row_last) == row && + GET_LOW(sercon_col_last) == col) + return; + } + + sercon_term_cursor_goto(row, col); + SET_LOW(sercon_row_last, row); + SET_LOW(sercon_col_last, col); +} + +static void sercon_lazy_flush(void) +{ + u8 chr, attr; + + chr = GET_LOW(sercon_char); + attr = GET_LOW(sercon_attr); + if (chr) { + sercon_set_attr(attr); + sercon_print_utf8(chr); + SET_LOW(sercon_col_last, GET_LOW(sercon_col_last) + 1); + } + + sercon_lazy_cursor_sync(); + + SET_LOW(sercon_attr, 0x07); + SET_LOW(sercon_char, 0x00); +} + +static void sercon_lazy_cursor_update(u8 row, u8 col) +{ + cursor_pos_set(row, col); + SET_LOW(sercon_row_last, row); + SET_LOW(sercon_col_last, col); +} + +static void sercon_lazy_backspace(void) +{ + u8 col; + + sercon_lazy_flush(); + col = cursor_pos_col(); + if (col > 0) { + sercon_putchar(8); + sercon_lazy_cursor_update(cursor_pos_row(), col-1); + } +} + +static void sercon_lazy_cr(void) +{ + cursor_pos_set(cursor_pos_row(), 0); +} + +static void sercon_lazy_lf(void) +{ + u8 row; + + row = cursor_pos_row() + 1; + if (row >= video_rows()) { + /* scrolling up */ + row = video_rows()-1; + if (GET_LOW(sercon_row_last) > 0) { + SET_LOW(sercon_row_last, GET_LOW(sercon_row_last) - 1); + } + } + cursor_pos_set(row, cursor_pos_col()); +} + +static void sercon_lazy_move_cursor(void) +{ + u8 col; + + col = cursor_pos_col() + 1; + if (col >= video_cols()) { + sercon_lazy_cr(); + sercon_lazy_lf(); + } else { + cursor_pos_set(cursor_pos_row(), col); + } +} + +static void sercon_lazy_putchar(u8 chr, u8 attr, u8 teletype) +{ + if (cursor_pos_row() != GET_LOW(sercon_row_last) || + cursor_pos_col() != GET_LOW(sercon_col_last)) { + sercon_lazy_flush(); + } + + SET_LOW(sercon_char, chr); + if (teletype) + sercon_lazy_move_cursor(); + else + SET_LOW(sercon_attr, attr); +} + +/* Set video mode */ +static void sercon_1000(struct bregs *regs) +{ + u8 clearscreen = !(regs->al & 0x80); + u8 mode = regs->al & 0x7f; + u8 rows, cols; + + switch (mode) { + case 0x03: + default: + cols = 80; + rows = 25; + regs->al = 0x30; + } + SET_LOW(sercon_col_last, 0); + SET_LOW(sercon_row_last, 0); + SET_LOW(sercon_attr_last, 0); + + cursor_pos_set(0, 0); + SET_BDA(video_mode, mode); + SET_BDA(video_cols, cols); + SET_BDA(video_rows, rows-1); + SET_BDA(cursor_type, 0x0007); + + sercon_term_reset(); + sercon_term_no_linewrap(); + if (clearscreen) + sercon_term_clear_screen(); +} + +/* Set text-mode cursor shape */ +static void sercon_1001(struct bregs *regs) +{ + /* show/hide cursor? */ + SET_BDA(cursor_type, regs->cx); +} + +/* Set cursor position */ +static void sercon_1002(struct bregs *regs) +{ + u8 row = regs->dh; + u8 col = regs->dl; + + cursor_pos_set(row, col); +} + +/* Get cursor position */ +static void sercon_1003(struct bregs *regs) +{ + regs->cx = GET_BDA(cursor_type); + regs->dh = cursor_pos_row(); + regs->dl = cursor_pos_col(); +} + +/* Scroll up window */ +static void sercon_1006(struct bregs *regs) +{ + sercon_lazy_flush(); + if (regs->al == 0) { + /* clear rect, do only in case this looks like a fullscreen clear */ + if (regs->ch == 0 && + regs->cl == 0 && + regs->dh == video_rows()-1 && + regs->dl == video_cols()-1) { + sercon_set_attr(regs->bh); + sercon_term_clear_screen(); + } + } else { + sercon_putchar('\r'); + sercon_putchar('\n'); + } +} + +/* Read character and attribute at cursor position */ +static void sercon_1008(struct bregs *regs) +{ + regs->ah = 0x07; + regs->bh = ' '; +} + +/* Write character and attribute at cursor position */ +static void sercon_1009(struct bregs *regs) +{ + u16 count = regs->cx; + + if (count == 1) { + sercon_lazy_putchar(regs->al, regs->bl, 0); + + } else if (regs->al == 0x20 && + video_rows() * video_cols() == count && + cursor_pos_row() == 0 && + cursor_pos_col() == 0) { + /* override everything with spaces -> this is clear screen */ + sercon_lazy_flush(); + sercon_set_attr(regs->bl); + sercon_term_clear_screen(); + + } else { + sercon_lazy_flush(); + sercon_set_attr(regs->bl); + while (count) { + sercon_print_utf8(regs->al); + count--; + } + sercon_term_cursor_goto(cursor_pos_row(), + cursor_pos_col()); + } +} + +/* Teletype output */ +static void sercon_100e(struct bregs *regs) +{ + switch (regs->al) { + case 7: + sercon_putchar(0x07); + break; + case 8: + sercon_lazy_backspace(); + break; + case '\r': + sercon_lazy_cr(); + break; + case '\n': + sercon_lazy_lf(); + break; + default: + sercon_lazy_putchar(regs->al, 0, 1); + break; + } +} + +/* Get current video mode */ +static void sercon_100f(struct bregs *regs) +{ + regs->al = GET_BDA(video_mode); + regs->ah = GET_BDA(video_cols); +} + +/* VBE 2.0 */ +static void sercon_104f(struct bregs *regs) +{ + regs->ax = 0x0100; +} + +static void sercon_10XX(struct bregs *regs) +{ + warn_unimplemented(regs); +} + +void VISIBLE16 +sercon_10(struct bregs *regs) +{ + if (!GET_LOW(sercon_port)) + return; + + switch (regs->ah) { + case 0x00: sercon_1000(regs); break; + case 0x01: sercon_1001(regs); break; + case 0x02: sercon_1002(regs); break; + case 0x03: sercon_1003(regs); break; + case 0x06: sercon_1006(regs); break; + case 0x08: sercon_1008(regs); break; + case 0x09: sercon_1009(regs); break; + case 0x0e: sercon_100e(regs); break; + case 0x0f: sercon_100f(regs); break; + case 0x4f: sercon_104f(regs); break; + default: sercon_10XX(regs); break; + } +} + +void sercon_enable(void) +{ + u16 addr = PORT_SERIAL1; + + SET_LOW(sercon_port, addr); + outb(0x03, addr + SEROFF_LCR); // 8N1 + outb(0x01, addr + 0x02); // enable fifo +} + +/**************************************************************** + * serial input + ****************************************************************/ + +VARLOW u8 rx_buf[16]; +VARLOW u8 rx_bytes; + +static VAR16 struct { + char seq[4]; + u8 len; + u16 keycode; +} termseq[] = { + { .seq = "OP", .len = 2, .keycode = 0x3b00 }, // F1 + { .seq = "OQ", .len = 2, .keycode = 0x3c00 }, // F2 + { .seq = "OR", .len = 2, .keycode = 0x3d00 }, // F3 + { .seq = "OS", .len = 2, .keycode = 0x3e00 }, // F4 + + { .seq = "[15~", .len = 4, .keycode = 0x3f00 }, // F5 + { .seq = "[17~", .len = 4, .keycode = 0x4000 }, // F6 + { .seq = "[18~", .len = 4, .keycode = 0x4100 }, // F7 + { .seq = "[19~", .len = 4, .keycode = 0x4200 }, // F8 + { .seq = "[20~", .len = 4, .keycode = 0x4300 }, // F9 + { .seq = "[21~", .len = 4, .keycode = 0x4400 }, // F10 + { .seq = "[23~", .len = 4, .keycode = 0x5700 }, // F11 + { .seq = "[24~", .len = 4, .keycode = 0x5800 }, // F12 + + { .seq = "[2~", .len = 3, .keycode = 0x52e0 }, // insert + { .seq = "[3~", .len = 3, .keycode = 0x53e0 }, // delete + { .seq = "[5~", .len = 3, .keycode = 0x49e0 }, // page up + { .seq = "[6~", .len = 3, .keycode = 0x51e0 }, // page down + + { .seq = "[A", .len = 2, .keycode = 0x48e0 }, // up + { .seq = "[B", .len = 2, .keycode = 0x50e0 }, // down + { .seq = "[C", .len = 2, .keycode = 0x4de0 }, // right + { .seq = "[D", .len = 2, .keycode = 0x4be0 }, // left + + { .seq = "[H", .len = 2, .keycode = 0x47e0 }, // home + { .seq = "[F", .len = 2, .keycode = 0x4fe0 }, // end +}; + +static void shiftbuf(int remove) +{ + int i, remaining; + + remaining = GET_LOW(rx_bytes) - remove; + SET_LOW(rx_bytes, remaining); + for (i = 0; i < remaining; i++) + SET_LOW(rx_buf[i], GET_LOW(rx_buf[i + remove])); +} + +static int cmpbuf(int seq) +{ + int chr, len; + + len = GET_GLOBAL(termseq[seq].len); + if (GET_LOW(rx_bytes) < len + 1) + return 0; + for (chr = 0; chr < len; chr++) + if (GET_GLOBAL(termseq[seq].seq[chr]) != GET_LOW(rx_buf[chr + 1])) + return 0; + return 1; +} + +static int findseq(void) +{ + int seq; + + for (seq = 0; seq < ARRAY_SIZE(termseq); seq++) + if (cmpbuf(seq)) + return seq; + return -1; +} + +void +sercon_check_event(void) +{ + u16 addr = GET_LOW(sercon_port); + u16 keycode; + u8 byte, count = 0; + int seq, chr; + + // check to see if there is a active serial port + if (!addr) + return; + if (inb(addr + SEROFF_LSR) == 0xFF) + return; + + // flush pending output + sercon_lazy_flush(); + + // read all available data + while (inb(addr + SEROFF_LSR) & 0x01) { + byte = inb(addr + SEROFF_DATA); + if (GET_LOW(rx_bytes) < sizeof(rx_buf)) { + SET_LOW(rx_buf[rx_bytes], byte); + SET_LOW(rx_bytes, GET_LOW(rx_bytes) + 1); + count++; + } + } + + for (;;) { + // no (more) input data + if (!GET_LOW(rx_bytes)) + return; + + // lookup escape sequences + if (GET_LOW(rx_bytes) > 1 && GET_LOW(rx_buf[0]) == 0x1b) { + seq = findseq(); + if (seq >= 0) { + enqueue_key(GET_GLOBAL(termseq[seq].keycode)); + shiftbuf(GET_GLOBAL(termseq[seq].len) + 1); + continue; + } + } + + // Seems we got a escape sequence we didn't recognise. + // -> If we received data wait for more, maybe it is just incomplete. + if (GET_LOW(rx_buf[0]) == 0x1b && count) + return; + + // Handle input as individual char. + chr = GET_LOW(rx_buf[0]); + keycode = ascii_to_keycode(chr); + if (keycode) + enqueue_key(keycode); + shiftbuf(1); + } +} diff --git a/src/util.h b/src/util.h index ef65d32..4e1c0ab 100644 --- a/src/util.h +++ b/src/util.h @@ -233,6 +233,9 @@ void code_mutable_preinit(void); // serial.c void serial_setup(void); void lpt_setup(void); +void sercon_10(struct bregs *regs); +void sercon_enable(void); +void sercon_check_event(void); // version.c extern const char VERSION[], BUILDINFO[];