Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for VGA console output in bare metal mode #588

Merged
merged 5 commits into from
Sep 7, 2022
Merged

Add support for VGA console output in bare metal mode #588

merged 5 commits into from
Sep 7, 2022

Conversation

tkchia
Copy link
Contributor

@tkchia tkchia commented Sep 4, 2022

I have added (as of https://github.com/tkchia/cosmopolitan/commit/c42c607c80c2920b585ee1a65a9c66ec68acd609) some very rough and preliminary support for console output to the VGA screen — in addition to the serial port — when in bare metal mode.

I hope to improve on it further, e.g.:

libc/vga/vga.h Outdated Show resolved Hide resolved
libc/vga/vga.mk Show resolved Hide resolved
/* TODO: ensure screen in a known mode (text or graphics), at rlinit time */
/* TODO: use our own font, rather than rely on BIOS's CP437 font */
/* TODO: handle UTF-8 multi-bytes */
/* TODO: handle VT102 escape sequences */
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is https://github.com/jart/cosmopolitan/blob/master/tool/build/lib/pty.c something that might be useful to you? It's how Blinkenlights displays a terminal inside a terminal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is https://github.com/jart/cosmopolitan/blob/master/tool/build/lib/pty.c something that might be useful to you?

Thank you — yes, this looks like it might be useful to me. Let me take a further look at the pty.c code and see how I can use it.

@jart
Copy link
Owner

jart commented Sep 5, 2022

By the way those checkboxes shouldn't need to be blockers. It'd be great to get an incremental improvement merged and then keep sending more changes. Works of progress are great so long as tests pass.

@tkchia tkchia marked this pull request as ready for review September 5, 2022 21:37
@tkchia
Copy link
Contributor Author

tkchia commented Sep 5, 2022

By the way those checkboxes shouldn't need to be blockers. It'd be great to get an incremental improvement merged and then keep sending more changes. Works of progress are great so long as tests pass.

Thanks @jart. I am currently working on implementing some basic VT102 support based on a pared-down version of pty.c.

Thank you!

The code is based on a pared-down version of the pty code
in tool/build/lib/pty.c.

It currently adds about 12 KiB to the size of the final
executable (compared to the earlier (simplistic) teletype
implementation).

Some terminal features, such as kTtyBell & kTtyNocursor,
are not yet implemented.
@tkchia
Copy link
Contributor Author

tkchia commented Sep 6, 2022

Hello @jart,

The VT102 and UTF-8 support (based on pty.c) which I have added to the VGA support code seems to be mostly working. I can now say something like

#include "libc/math.h"
#include "libc/stdio/stdio.h"

STATIC_YOINK("vga_console");

int main(int argc, char *argv[]) {
  volatile long double x = -.5;
  volatile long double y = 1.5;
  unsigned i;
  for (i = 0; i < 12; ++i) {
    printf("Hello World!\n"
           "\e[1;32m atan : %Lg \u00f7 \e[0;32m%Lg \u21a6 %.19Lg\e[0m\n",
           x, y, atan2l(x, y));
    ++x;
    ++y;
  }
  return 0;
}

and libc/vga/tty.c will try to do the correct thing (the character is mapped to ).

I see some remaining issues:

  • The VT support code currently adds about 12 KiB to the size of the final executable (compared to the earlier (simplistic) teletype implementation, and compared to having no VGA support).
  • Some terminal features, such as the BEL character code & ESC [ ? 25 l, are not yet implemented. I think for BEL, I would want to see timer IRQs up and running first, before trying to add it.
  • I cannot use bing( ) and unbing( ) to map between CP437 and UTF-8, though I would like to. (Using bing() and unbing() results in a circular library dependency.)

Other than the above, the PR should be ready to merge now, if that is fine with you.

Thank you!

@jart
Copy link
Owner

jart commented Sep 6, 2022

The VT support code currently adds about 12 KiB to the size of the final executable (compared to the earlier (simplistic) teletype implementation, and compared to having no VGA support).

That's really small and it's not a problem at all, since we're using STATIC_YOINK It'll only add size to the binary if the user explicitly requests it. If you want to verify that's the case, then run make -j16 o/tiny/examples/life.com and confirm it's still 16kb in size.

Some terminal features, such as the BEL character code

Please do not implement the terminal bell. It's very distracting.

I cannot use bing( ) and unbing( ) to map between CP437 and UTF-8, though I would like to. (Using bing() and unbing() results in a circular library dependency.)

You can't link unbing()? You should be able to, since LIBC_FMT is very high up in the topological order of things. It should have far fewer dependencies than your package. Perhaps you were depending on things in the wrong direction?

libc/calls/calls.mk Outdated Show resolved Hide resolved
@ghaerr
Copy link
Contributor

ghaerr commented Sep 6, 2022

Hello @tkchia, hello @jart,

The prospect of writing 32-bit/64-bit C code that also runs and displays on PC VGA hardware is exciting. I can see some very nice usefulness in writing sophisticated programs using Cosmopolitan that can run and display without an operating system, particularly for TUI programs. I thought to ask a few questions and comments on this work, both to make sure I fully understand how it works, as well to make a suggestion about a future enhancement for maximum TUI usability.

I don't quite understand why all of the wcs, bing/unbing and other extensive translation support needs to be carried along with this code, intended for operation only on IBM PC systems with VGA hardware. The hardware can only display cp437 (in text modes), which it does continuously for all (8-bit) characters and attributes within its current displayed text memory page. So why not just translate from UTF-8 to cp437 once, immediately upon receiving or converting the Unicode input, and then keeping the data in cp437 without the complex translation routines? What is the purpose of carrying the wcs[] array, when the character data will never be inspected or available to the host program? It seems there is quite a bit of extra code being kept (from pty.c), which won't be used as a result of VGA hardware. Of course, if the VGA is in graphics mode and is drawing each glyph using software, that would be quite different. Is that the reason? I suppose all this doesn't really matter, since 12K is quite small compared to the bloat of modern day programs, but was thinking that perhaps text-only versus graphics mode glyphs might want to be considered to be implemented/yoinked in separately, to keep the ability to create very tiny bare metal programs. Just some thoughts :)

The VT100/102/220 terminals have a special characteristic, implemented by very few other terminals, which comes in very handy for those writing full-screen TUI applications: the ability to write the character in the last line and last column, without the screen scrolling. This characteristic is described by "XN hack/soft-wrap", xn in termcap, xenl in terminfo, and discussed in more detail in https://stackoverflow.com/questions/31360385/an-obscure-one-documented-vt100-soft-wrap-escape-sequence. The basic idea is that the terminal performs a scroll-check before a character is displayed, rather than afterwards. This allows writing to location 25,80 followed by a cursor position or other escape sequence, and having the entire 80x25 screen remain intact without scrolling. It would be nice to have this VT102 feature implemented in the VGA driver. (Almost all xterm terminal emulators also implement this feature). A sample implementation I wrote is available in ELKS at https://github.com/jbruchon/elks/blob/master/elks/arch/i86/drivers/char/console.c in function stdchar.

Thank you!

@tkchia
Copy link
Contributor Author

tkchia commented Sep 6, 2022

Hello @jart,

If you want to verify that's the case, then run make -j16 o/tiny/examples/life.com and confirm it's still 16kb in size.

Yes, it is so.

Please do not implement the terminal bell. It's very distracting.

OK.

You can't link unbing()? You should be able to, since LIBC_FMT is very high up in the topological order of things. It should have far fewer dependencies than your package. Perhaps you were depending on things in the wrong direction?

Thanks! Using bing() and unbing() seems to work now, after I followed your suggestion to break the LIBC_CALLSLIBC_VGA dependency and moved things around a bit in the main makefile (https://github.com/tkchia/cosmopolitan/commit/88217deddc314172493f9aa9192ebdca8d46c4b3).

Thank you!

@tkchia
Copy link
Contributor Author

tkchia commented Sep 6, 2022

Hello @ghaerr,

What is the purpose of carrying the wcs[] array, when the character data will never be inspected or available to the host program?

Well, the tty->wcs[] array was already being maintained in the original tool/build/lib/pty.c code. I thought, rather than removing it wholesale in libc/vga/tty.c, it might be simpler to leave it around in the source code, and let the compiler optimize it away for now. (Then we can simply enable it once it turns out to be needed.)

The Linux kernel mailing list thread regarding Linux's /dev/vcsu* device files — introduced in 2018 — mentions a use case for maintaining a text terminal's "underlying" wide characters.

the ability to write the character in the last line and last column, without the screen scrolling.

I believe @jart's pty.c code already implements this, through the kPtyRedzone internal flag. I should probably try to exercise this feature. 🙂

Thank you!

Copy link
Owner

@jart jart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Great work!

@jart jart merged commit 3fdb1c1 into jart:master Sep 7, 2022
@tkchia
Copy link
Contributor Author

tkchia commented Sep 7, 2022

Hello @jart,

Thank you again!

For the terminal features, would you prefer to support blinking characters, or to support bright (high intensity) background colours?

I see that the VGA BIOSes allow one to change the meaning of the text buffer's high attribute bits to yield bright backgrounds. (It seems that Linux currently has decided to support blinking and err on the dark side for backgrounds.)

Thank you!

@jart
Copy link
Owner

jart commented Sep 7, 2022

What the terminal calls "bright" is in practice usually just a different shade of the 16 ANSI colors, so you can get 32 colors in total. I use this theme for that:

foreground        : 187,187,187    ansi green bold   :  85,187, 85
bold foreground   : 255,255,255    ansi yellow       : 187,187,  0
background        :   0,  0,  0    ansi yellow bold  : 255,255, 85
bold background   :  85, 85, 85    ansi blue         :   0,  0,187
cursor text       :   0,  0,  0    ansi blue bold    :  85, 85,187
cursor color      :   0,255,  0    ansi magenta      : 187,  0,187
ansi black        :   0,  0,  0    ansi magenta bold : 255, 85,255
ansi black bold   :  85, 85, 85    ansi cyan         :   0,187,187
ansi red          : 187,  0,  0    ansi cyan bold    :  85,255,255
ansi red bold     : 255, 85, 85    ansi white        : 187,187,187
ansi green        :   0,187,  0    ansi white bold   : 255,255,255

There's also \e[1m which is bright and/or bold.

Blinking is annoying. If it were 100% free to support, I'd say do it. But if we need to add things like timers and other bloat, then it's probably not worth it.

The features I care about are what's implemented in pty.c. Namely things like XTERM256, 24-bit color, etc. I also care deeply about being able to use UNICODE characters if possible. At the very minimum CP-437. Every file in this codebase uses UNICODE characters that are part of CP437. There's also some files in the codebase which use astral plane mathematical characters in comments. I love characters.

Definitely err on the dark side for background. Not sure I understand Torvalds' hack there. But looks interesting.

@tkchia
Copy link
Contributor Author

tkchia commented Sep 7, 2022

Hello @jart,

Blinking is annoying. If it were 100% free to support, I'd say do it. But if we need to add things like timers and other bloat, then it's probably not worth it.

Blinking is almost 100% free in VGA text mode. The VGA hardware directly supports it. But if blinking is used, then we cannot have bright background colours — for the entire screen. More precisely, there is a hardware setting (which can be set by BIOS int 0x10) that governs whether the attribute byte for each plotted character is interpreted as

 7 6   4 3     0
┌─┬─┬─┬─┼─┬─┬─┬─┐
│b│bkgrd│foregrd│
└┼┴─┴─┴─┴┼┴─┴─┴─┘
 │       foreground intensity
 └────── blink

or

 7     4 3     0
┌─┬─┬─┬─┼─┬─┬─┬─┐
│backgrd│foregrd│
└┼┴─┴─┴─┴┼┴─┴─┴─┘
 │       foreground intensity
 └────── background intensity

(QEMU does not emulate VGA's text mode blinking; but VirtualBox does.)

Thank you!

@jart
Copy link
Owner

jart commented Sep 8, 2022

In that case it's a no-brainer. Blinking would be bad.

@ghaerr
Copy link
Contributor

ghaerr commented Sep 8, 2022

Hello @tkchia, hello @jart,

For what it's worth, because the EGA/VGA blink bit is system-wide and the previous state not necessarily tracked by early (non-VGA) BIOSes, all of the color code in the TUI library I'm developing for Cosmopolitan never attempts to set or clear the blink/background intensity bit. That is, applications only have 16 fg and 8 bg colors available, and bright background versus blink is a non-issue.

(The current TUI color and cp437 handling for D-Flat is in tty-cp437.c and tty.c, and is compatible with this PR. I plan on creating a Cosmopolitan bare metal version of it to exercise and show off the new VGA console output capabilities, as well as contribute the TUI library (broken into smaller modules) into Cosmopolitan at tools/tui).

Along the lines of implementing a modern (bare metal) console, if the console were to implement the private mode sequence(s) ESC [ ? 1000 h, (possibly along with 1002, 1006 and 1015 mouse status variations), then TUI bare metal applications would have mouse input capability from Cosmopolitan without having to worry about a mouse driver. Since most xterm256 and other host terminal emulators already do this, that would provide a nice compatibility layer between different Cosmopolitan modes.

Thank you!

@ghaerr
Copy link
Contributor

ghaerr commented Sep 10, 2022

Hello @tkchia,

I finally ported D-Flat to Cosmopolitan, described and tracked in shmup/awesome-cosmopolitan#2, which is a TUI library that takes advantage of the CP437 character set as well as lots of 16-color foreground and 8-color background color use. It uses ANSI ESC sequences throughout, and I thought it might be useful for testing or showing off your new VGA bare metal support.

The library uses ESC [ 6 n (report cursor position) in order to determine the window size, which could be a good test of your upcoming VGA ANSI status report-backs. Until the Report Cursor Position is implemented, the D-Flat demo may hang on startup, as it doesn't (yet) timeout if the sequence is not implemented (It will parse any received ANSI keyboard arrow key and mouse sequences though.)

May I ask whether you're using an emulator for VGA bare metal testing, and what the command line is, so I can help test D-Flat on VGA?

Thank you!

@tkchia
Copy link
Contributor Author

tkchia commented Sep 11, 2022

Hello @ghaerr,

I am mostly using QEMU, with a simple command line, like so:

qemu-system-x86_64 -hda program.com -serial stdio

or

qemu-system-x86_64 -fda program.com -serial stdio

You can also get debugging dumps from QEMU using the -d ... and -D ... options.

Thank you!

*
* @see lkml.kernel.org/lkml/204888.1529277815@turing-police.cc.vt.edu/T/
*/
#undef VGA_USE_WCS
Copy link

@paulwratt paulwratt Sep 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this correct, its check for VGA_USE_WCS later down the header, and it will turn it off for writev-vga.c & tty.c

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @paulwratt,

Yes. This is intentional.

/*
 * VGA_TTY_HEIGHT, VGA_TTY_WIDTH, & VGA_USE_WCS are configuration knobs
 * which can potentially be used to tweak the features to be compiled into
 * our VGA teletypewriter support.
 */

Thank you!

@jart
Copy link
Owner

jart commented Sep 12, 2022

Is there any chance we could get a VGA demo added to the examples/ folder that has a comment at the top:

/**
 * @fileoverview VGA Bare Metal Demo
 *
 *     make -j8 o//examples/vga.com
 *     qemu-system-x86_64 -hda o//examples/vga.com -serial stdio
 * 
 */

If you want to see our Cosmopolitan CGA demo:

make -j16 MODE=tiny o/tiny/tool/build/blinkenlights.com o/tiny/tool/build/emubin/spiral.bin
o/tiny/tool/build/blinkenlights.com -rt o/tiny/tool/build/emubin/spiral.bin
# press CTRL-T CTRL-T CTRL-T for turbo mode
# press c to continue

image

@tkchia
Copy link
Contributor Author

tkchia commented Sep 12, 2022

Hello @jart, hello @ghaerr,

Is there any chance we could get a VGA demo added to the examples/ folder that has a comment at the top:

Agreed — this will be cool to have. Thank you!

@tkchia
Copy link
Contributor Author

tkchia commented Sep 12, 2022

Hello @ghaerr,

Until the Report Cursor Position is implemented, the D-Flat demo may hang on startup, as it doesn't (yet) timeout if the sequence is not implemented

OK. I am in the process of getting this implemented. It looks like, besides implementing read(...) for the VGA terminal, I will also need to implement the appropriate ioctl(s) to glue with @jart's code, and put the terminal in the correct mode to retrieve the response string.

Thank you!

@ghaerr
Copy link
Contributor

ghaerr commented Sep 12, 2022

Hello @tkchia,

It looks like, besides implementing read(...) for the VGA terminal, I will also need to implement the appropriate ioctl(s) to glue with @jart's code, and put the terminal in the correct mode to retrieve the response string.

Yes, and I hadn't thought of that (major) complication... I suppose what you're talking about is the implementation of a full-blown bare metal console framework, emulating the file-descriptor mechanism of a typical kernel. This could get complicated quickly, depending on the depth of supported "system calls". There could also be complications with keyboards, scan codes, etc especially for Unicode support.

If you'd like to take a more phased approach to getting this all working, say by using vga_xxx routines that don't (yet) take a file descriptor, etc., it shouldn't be a big deal for myself to modify the D-Flat (or another) demo to use those routines, especially since all the routines either file descriptor 0 or 1, which would always refer to the VGA console.

[EDIT: I hadn't realized that Cosmopolitan already handles calling target routines based on the file descriptor. I need to study that portion of libc in more detail.]

Thank you!

@tkchia
Copy link
Contributor Author

tkchia commented Sep 12, 2022

Hello @ghaerr,

🙂 Then again, it seems the common use case for "reading" from a display driver, is to simply obtain the raw status response strings. So perhaps we can do just that, and ignore the termios mode, for now (at least, until we really must start paying attention to the termios mode). I have now submitted a proposed patch (#613) to do this.

Thank you!

@ghaerr
Copy link
Contributor

ghaerr commented Sep 12, 2022

Hello @tkchia,

it seems the common use case for "reading" from a display driver, is to simply obtain the raw status response strings.

Well, yes, but also the "console device" should return anything typed from the keyboard too, since that and the status response strings are part of the same stream, right?. I admit to not being familiar with this portion of Cosmopolitan yet - does a console read result an INT 16h (read keyboard) or other function for bare metal at this time?

The termios ioctls can probably be ignored for the time being, agreed, if the default operation is ~(ECHO|ICANON).

I have now submitted a proposed patch (#613) to do this.

Looks good. I notice that currently the driver is always non-blocking, and it seems it will return 0 on no data available. That shouldn't be a problem, but I wanted to mention the overall structure of how most Cosmopolitan TUI programs and the proposed TUI library deal with text input in general: In some simpler cases, the TUI program hangs on the read inside readansi (which eventually calls your VGA read function). In other cases, mostly where a timer is required, the program blocks in select and then calls readansi, possibly followed in the TUI library by other ANSI sequence recognition routines for mouse and cursor/arrow key translation. I haven't checked to see how a select may be handled for the VGA bare metal case, but in the others, the program main loop will busy-loop while readansi returns 0. This shouldn't be a problem for bare metal, since there's nothing else to do, but we may have to check for proper handling of readansi returning 0 downstream.

Thank you!

@tkchia
Copy link
Contributor Author

tkchia commented Sep 12, 2022

Hello @ghaerr,

does a console read result an INT 16h (read keyboard) or other function for bare metal at this time?

Currently only input via serial port is implemented. There is no code yet for reading from a keyboard, whether via the PS/2 interface or via USB.

Unfortunately, IRQs — needed to handle asynchronous events and timeouts — are also not yet implemented. (But they really should be!)

Thank you!

@jart
Copy link
Owner

jart commented Sep 13, 2022

Termios is something we certainly need. How it's configured takes some getting used to. The stuff that matters the most is documented in the "commentary" column of libc/sysv/consts.sh

syscon termios OPOST 0b0000000000000001 0b000000000000000001 0b000000000000000001 0b0000000000000001 0b0000000000000001 0b0000000000000001 # termios.c_oflag&=~OPOST disables output processing magic, e.g. MULTICS newlines
syscon termios OLCUC 0b0000000000000010 0 0 0b0000000000100000 0 0b0000000000000010 # termios.c_oflag|=OLCUC maps a-z → A-Z output (SHOUTING)
syscon termios ONLCR 0b0000000000000100 0b000000000000000010 0b000000000000000010 0b0000000000000010 0b0000000000000010 0b0000000000000100 # termios.c_oflag|=ONLCR map \n → \r\n output (MULTICS newline) and requires OPOST
syscon termios OCRNL 0b0000000000001000 0b000000000000010000 0b000000000000010000 0b0000000000010000 0b0000000000010000 0b0000000000001000 # termios.c_oflag|=OCRNL maps \r → \n output
syscon termios ONOCR 0b0000000000010000 0b000000000000100000 0b000000000000100000 0b0000000001000000 0b0000000001000000 0b0000000000010000 # termios.c_oflag|=ONOCR maps \r → ∅ output iff column 0
syscon termios ONLRET 0b0000000000100000 0b000000000001000000 0b000000000001000000 0b0000000010000000 0b0000000010000000 0b0000000000100000 # termios.c_oflag|=ONLRET maps \r → ∅ output

For example, ONLCR is the setting for MULTICS newline. In raw mode, the keyboard driver reports ENTER as \r carriage return. But UNIX programs expect to receive \n by default. The underlying driver should translate this, unless the user requests raw mode.

image

Another important one is ICANON where the serial / terminal driver should buffer lines. It's basically GNU readline from the 1960's so it's a little tricky to implement.

I've updated the examples/hello4.c demo (now named examples/vga.c to show how readansi() and our special printf() syntax for escaping the raw codes can be used. This event loop also prevents the machine from immediately resetting. The results are AWESOME.

image

I'm typing via the serial port and it's showing up on the display. Some of the codes that come later are for example the arrow keys.

@tkchia
Copy link
Contributor Author

tkchia commented Sep 13, 2022

Hello @jart,

This event loop also prevents the machine from immediately resetting. The results are AWESOME.

I am glad it works. 🙂

The underlying driver should translate this, unless the user requests raw mode.

Is this something you would also like to get implemented in the existing serial input code (sys_readv_serial) some time? I see that right now it just returns whatever it sees on port 0x3f8 et seq.

Thank you!

@jart
Copy link
Owner

jart commented Sep 14, 2022

Is this something you would also like to get implemented in the existing serial input code (sys_readv_serial) some time?

Very much so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants