Virtual-8086 environments - Support for environments
that run in the x86 Virtual-8086 mode was added. The bulk of
this support is in kern/v86.c. New V86
environments can be allocated with v86_env_alloc,
which behaves just like env_alloc except for
special flags and mappings set up in the environment, and a
special "breakpoint handler" function that can be associated
with the environment.
The trap handling system had to be changed to add some
extra fields to the Trapframe that get pushed and
popped on the boundary between a V86 environment and the
kernel, and to dispatch to v86_handle_brkpt and
v86_handle_gpf on breakpoint and GPF interrupts,
respectively. The GPF handler takes care of the
virtualization of certain instructions that require it in V86
code (I/O is not virtualized by this because I enabled access
to all I/O ports via the I/O map in the TSS [yes, this is a
security hole]). The breakpoint handler dispatches to the
breakpoint function registered with the current environment.
Breakpoint interrupts are handled specially in V86 code, which
is why they are used to form a communication channel from the
V86 code back into the kernel.
Coincidentally, the UTF was getting in my way, so I got rid of it. In addition to the nice effect of getting rid of an abominable pointer, this has the nice effect of leaving the kernel with one and only one path to return to user space, and simplifying a lot of code that had to check if it needed to modify the UTF or the trapframe in an environment.
VESA driver - Support for VESA VBE 2.0-compliant
video (the standard emulated by Bochs in conjunction with the
VGABIOS) was added. The bulk of this can be found in
kern/vesa.c. This uses a V86 environment for
invoking the video BIOS, which expects to run in real mode.
Code is dynamically generated and injected into this
environment for each step of the VESA initialization process.
This code simply consists of invoking the int 10h
video BIOS interface (the appropriate subfunctions are
specified by the values of registers, which are set in the
trap frame), pushing a breakpoint argument and function on its
stack, and causing a breakpoint interrupt. This breakpoint
interrupt is caught and dispatched to the VESA driver, which
either sets up the entry into the video BIOS, or makes the
environment not runnable if no more work needs to be done.
Because the kernel is non-reentrant, all of this needs to be
done asynchronously (further details on the control flow of
the VESA code can be found in the comment at the top of
kern/vesa.c).
The initialization process consists of detecting VESA support and getting mode information for all of the supported video modes. Specifically, this mode information consists of layout and geometry information, as well as the physical address of the linear framebuffer for each mode.
Two syscalls have been added for interfacing with the VESA
driver from user space. One sets the video mode, given the
desired width, height, and color depth. It returns a
structure which contains the layout and geometry of the set
video mode. The other maps the framebuffer of the current
video mode into the current environment at the specified
virtual address. Paging system - Because the linear
framebuffer lies at a very high physical address
(0xe0000000 in Bochs) that isn't backed by
regular physical RAM, support for these special "non-physical"
page mappings had to be added to the paging system.
Mouse driver - The mouse driver is split between the
kernel and the user space library. The kernel part can be
found in kern/console.c and the user space part
can be found in lib/mouse.c. In the kernel, it
is integrated with the rest of the console system (necessarily
so, since it goes through the same control chip that the
keyboard does). The console system was rewritten to use a
generalized character ring system and there are two rings now
defined: one for keyboard input and one for mouse input. The
mouse driver goes through the necessary steps during
initialization to enable the mouse port. It does not actually
initialize the mouse because this requires a relatively
lengthy dialog with the mouse which would have been difficult
to do sufficiently asynchronously in the kernel. It exports
to user space the ability to read from and write to the mouse
port a character at a time, in a manner very similar to how
the console keyboard is accessed.
The user space library includes a simple mouse access library. It contains the necessary code to initialize the mouse and a function to get the current location of the mouse. The function the gets the location of the mouse is responsible for receiving any new data from the mouse driver's ring, buffering it until an entire packet has been received, and decoding motion packets as they arrive.
Scheduler - While not directly related to graphics,
because graphics tend to be CPU intensive, the scheduler was
rewritten so yielding is now an O(1) operation. A tail queue
of all runnable environments is maintained
(env_status can no longer be directly
manipulated, but must be modified via a function that also
maintains the run queue). Instead of searching for the next
runnable environment on each invocation, yielding simply
rotates the run queue, running the first environment in it.
This could be further improved by taking into consideration
when processes give up part of their time slice, and when
processes are waiting on other processes.
Graphics library - The graphics library is a
relatively simple abstraction around the framebuffer access
provided by the kernel. It can be found in
lib/gfx.c. It provides a function for setting
the mode that wraps the syscalls in a palatable manner, and a
Gfx_buffer abstraction that wraps the framebuffer. It can
also allocate off-screen buffers which are represented in the
same way as the screen buffer. For drawing, it includes a
function to blit rectangular regions between buffers
(including support for clipping of those regions), and a
function for drawing glyphs. For fonts, the Linux kernel's
8x8 and 4x6 fonts have been borrowed. In order to make glyph
blitting as fast as possible, the font data is decoded and
cached when a font is first used.
Channel abstraction - Because JOS's IPC mechanism is
difficult to actually use and is very synchronous, a one-way
asynchronous channel abstraction was written. It can be found
in lib/channel.c. It uses IPC to set up an
initial shared page, and all other communications are carried
out by writing to and reading from a ring buffer that's kept
in that page. Both stream-oriented and datagram-oriented
communications are included in the abstraction.