The objective of this lab is that you learn how to use the PC's video card in graphics mode, using the BIOS/VBE interface for its configuration.
Write in C language several functions to use the PC's video card in graphics mode. The goal is to develop a generic module that will be used to create a library, which you will be able to use in the course's project.
Specifically, you shall develop the following functions whose prototypes are declared in video_gr.h:
in a file whose name is video_gr.c. (Actually, you should use the file video_gr.c, which includes stubs for these functions as well as the implementation of other functions, without which you will not be able to test your code.) For more information about these functions please read their documentation.
Furthermore, you shall develop the following function whose prototype is declared in vbe.h:
in a file whose name is vbe.c. (You should use the file vbe.c, which includes a stub for this function as well several preprocessor directives that may facilitate your implementatiaon.) For more information about these functions please read their documentation.
Finally, you will also have to develop scaffolding test code. This code should include the function main()
, which should be in a file named lab2.c
. If you wish you can base it on the code provided for Lab1. (To make understanding of that code easier, you can remove the code provided for emulation of the video RAM.)
All the code for this lab should be in the /usr/src/drivers/lab2/ directory that you will have to create in the Minix 3 VM Player image of the lab's PCs. The Annex 1 describes the steps that you need to perform to do it. Furthermore, Annex 2 describes how to transfer files to and from Minix 3 file system.
So that you can accomplish this lab's objectives, you should do some homework. In addition to read, and understand, this handout and the class notes, you should implement the function vg_init()
.
In this implementation, you can use hard-coded values for the parameters (screen resolution, number of colors per pixel and physical address of graphics mode VRAM) of the graphics mode used for testing (0x105) that are defined as symbolic constants in video_gr.c. In the final version, i.e. the one you should submit at the end of the class, your code should call the function vbe_get_mode_info()
, which you will implement, and that uses the VBE function 0x01 - Return VBE Mode Information to obtain the values of these parameters.
In order to test your implementation, your test code may include the following segment:
#include <stdlib.h>
#include "video_gr.h"
#define WAIT_TIME_S 5
vg_init(0x105); /* Change to video mode */
sleep(WAIT_TIME_S); /* for WAIT_TIME_S (5) seconds */
vg_exit(); /* Return to text mode */
Function vg_exit()
is already provided in file video_gr.c and resets the video card to operate in text mode.
Thus, if vg_init()
is correctly implemented, when you run your program the area of the screen used by the VMwarePlayer should increase temporarily (5 seconds), before returning back to its usual size. While in graphics mode, you will not have access to the virtual terminals; they are available only in text mode. However, upon return to text mode from graphics mode, the screen will be blank in all of them.
In the late 1980's, there was a large number of video card manufacturers offering video cards with higher resolutions than those specified in the VGA standard. In order to allow application programmers to develop portable video-based applications, the VESA (Video Electronics Standards Association) published in 1989 the VBE (VESA BIOS Extensions) standard. During the 1990's this standandard was revised several times. However, the last major version, version 3.0, dates from 1998. This is probably because by that time other standards had emerged, namely DirectX by Microsoft and OpenGL, originally developed by SGI.
The VBE standard defines a set of functions related to the operation of the video card, and specifies how they can be invoked. These functions were implemented by the video card manufacturers, usually in a ROM in the video card. Originally, the interface to these functions used only INT 0x10, i.e. the interrupt number used by the BIOS' video services, and had to be executed with the CPU in real-mode. Starting with version 2.0, VBE specifies also direct call of some time-critical functions from 32-bit applications. Version 3.0 specified a new interface for protected mode for most functions, but it is optional.
Because the VM Player video card's emulation supports only VBE 2.0, we will focus on that version of the standard.
In this lab we will use only a few basic functions that allow
Because these functions are not time-critical, they must be accessed via the "INT 0x10 interface".
As already mentioned, VBE functions are called using the interface used for standard BIOS video services. That is, the call is via the INT 0x10
software interrupt instruction, in real mode, and the function parameters are passed in the CPU registers.
When invoking a VBE function, the AH
register must be set to 0x4F
, thus distinguishing it from the standard VGA BIOS functions. The VBE function being called is specified in the AL
register. The use of the other registers depends on the VBE function.
The AX
register is also used to return a completion status value. If the VBE function called is supported the AL
register is set with value 0x4F
, i.e. the value passed in the AH
register. A different value is returned if the function is not supported. If the VBE function completed successfully, value 0x00
is returned in the AH
, otherwise it is set to indicate the nature of the failure, as shown in the table:
AH | Meaning |
0x01 | Function call failed |
0x02 | Function is not supported in current HW configuration |
0x03 | Function is invalid in current video mode |
The VBE standard defines several operating modes that video cards may support. These modes have different attributes, for example whether they are text or graphic modes. Other attributes in the latter case include, the horizontal and vertical resolution, the number of bits per color. For yet more attributes, you can read the specification of function 0x01 Return VBE Mode Information in page 16 and following of the VBE 2.0 standard. In this lab, we are interested only in the modes with 256 colors:
Screen Resolution | Mode |
640x480 | 0x101 |
800x600 | 0x103 |
1024x768 | 0x105 |
1280x1024 | 0x107 |
For a list of the modes specified in VBE, you can read Section 3 (pg. 6) of the VBE 2.0 standard. Video card manufacturers can also define other video modes. This is made possible by the VBE functions that allow an application to obtain the video card's capabilities. For example, the VBE implementation of the VMware Player used in the labs supports 100 video modes, of which only 10 are defined in the standard.
To initialize the controller and set a video mode, you must use function 0x02 - Set VBE Mode. The mode must be passed in the BX
register. Bit 14 of the BX register should be set to use a linear frame buffer model, which facilitates access to VRAM. You can find more details regarding this function in its specification in pg. 25 of the VBE2.0 standard.
In graphics mode, the screen is abstracted as a matrix of points, pixels (from picture element). The number of pixels on the screen depends on the screen resolution. To each pixel is associated one or more bytes in VRAM whose value determines the color of the corresponding pixel. Thus we can abstract the screen as a set of VRES lines, each of which with HRES pixels.
In the linear/flat frame buffer model, the lines of the screen are located in VRAM one after the other, from the top line to the bottom line of the screen. Furthermore, in each line, the left most pixel comes first, then the pixel to its left, and so on until the right most pixel, which comes last. This is the model you have used in Lab1, the difference is that whereas in Lab 1 the lines were sequence of pairs (character,attribute), one for each symbol in that line, here a line is a sequence of colors, one for each pixel in that line.
Like the text mode VRAM, the graphics mode VRAM is not directly accessible by a user program. To make it accessible you need to map it in the process' address space. Of course, as we have already discussed in Lab1, this operation is privileged and your program needs to have the necessary permissions.
Before your process can map the graphics VRAM in its address space it needs to know the VRAM's physical address. This information can be obtained from the video controller using VBE function 0x01 Return VBE Mode Information, which takes as arguments the mode and a real-mode address of a buffer that will be filled in with the parameters of the specified mode. In addition to the linear buffer physical address, these parameters include the horizontal and vertical resolution, as well as the number of bits per pixel. Generally, all these parameters must be known to change the color of a specific pixel. You can find more details regarding this function in its specification in pg. 16 of the VBE2.0 standard.
Another useful function provided by the VBE standard is function 0x00 Return VBE Controller Information, which returns the capabilities of the controller, including a list of the video mode numbers supported by the controller. Like function 0x01, this one also takes as an argument a real-mode address with a buffer that will be filled in with the controller information. This function and function 0x01 can be used by a graphics application to learn the capabilities of the video card, and set the video-mode that suits it better. You can find more details regarding this function in its specification in pg. 12 of the VBE2.0 standard.
The use of the VBE functions in general and of the functions 0x00 and 0x01 is somewhat tricky because they usually use real-mode addresses: these are physical addresses, and are composed of the base address of a segment, a 16 bit-value that should be shifted by 4 to create a 20 bit address, and a 16-bit offset, that should be added to the 20-bit address. However, Minix uses virtual addresses. Fortunately, Minix provides functions that allow to map virtual addresses into physical addresses, thus making it possible to use the VBE interface.
As already mentioned, in graphics mode you will not have access to the Minix VTs, and hence to the shell. Thus, before terminating your program, you should always reset the video controller to operate in text mode.
The mode used by Minix in text mode is a standard CGA mode, whose number is 0x03. To set this mode, you should use the standard INT 0x10 BIOS interface, namely function 0x00. Thus you should set the AH register to 0x00 and the AL register to 0x03.
Accessing the video card via the VBE interface in Minix 3 raises a few issues:
INT 0x10
instruction in real-modeINT 0x10
in real-mode. There is therefore a need for a change in the operating mode of the processor.0x00
(Return VBE Controller Information) and 0x01
(Return VBE Mode Information) require that a memory buffer be passed as an argument. This buffer will be filled in with the required information by these functions. Because they are executed in real-mode, the buffer must be allocated in the lower 1 MByte of the physical address space.0x00
and 0x01
The last two issue were already discussed in the first lab, thus here we will address only the other three.
INT 0x10
instruction in real-modeMinix 3 offers the SYS_INT86
kernel call, whose description in the Minix 3 Developers Guide is as follows:
The library function to make this kernel call is as follows:
int sys_int86(struct reg86u *reg86p);
It returns either OK
, in the case of success, or EFAULT
otherwise, and takes as arguments a value of type struct reg86u *reg86p
, which allows to specify the values of the interrupt number and of the processor registers. This struct is defined in the header file <machine/int86.h>.
Function vg_exit()
already provided in video_gr.c:
/* Set default text mode */
int vg_exit() {
struct reg86u reg86;
reg86.u.b.intno = 0x10; /* BIOS video services */
reg86.u.b.ah = 0x00; /* Set Video Mode function */
reg86.u.b.al = 0x03; /* 80x25 text mode*/
if( sys_int86(& reg86) != OK ) {
printf("\tvg_exit(): sys_int86() failed \n");
return 1;
}
return 0;
}
resets the video controller to operate in text mode, by calling function sys_int86()
to invoke function 0x00
(Set Video Mode) of the BIOS video services (INT 0x10
).
As usually, access to the lower 1 MByte of the physical address space requires mapping that region into the process' address space. However, functions 0x00
and 0x01
of the VBE standard, require also the allocation of a buffer in that region of the physical address space.
Minix 3 provides the necessary mechanisms for that, but it is neither documented nor straightforward. Therefore, I have abstracted those mechanisms in a set of 3 functions, whose prototypes can be found in lmlib.h:
int lm_init();
void *lm_alloc(unsigned long size, mmap_t *map);
void lm_free(mmap_t *map);
which are implemented in the liblm.a library. For more details, you can read their documentation.
The mmap_t
type, also defined in lmlib.h, contains the necessary information on the mapping of a physical memory region into the virtual address space of a process:
typedef struct {
phys_bytes phys; /* physical address */
void *virtual; /* virtual address */
unsigned long size; /* size of memory region */
} mmap_t;
The phys
member may be useful when using the VBE interface, whereas the virtual
field is useful in all other cases.
Because the amount of memory available in the lower 1 MBytes of the physical address space is very limited, it is important that you free a region of memory that you have allocated with lm_alloc()
as soon as you don't need it, by calling lm_free()
. Not doing it may lead to memory leaks and eventually to the depletion of the available memory in that region. On the other hand, using a memory region that may have already been freed, may lead to all sorts of problems, the least of which is the crash of the process. Thus you should be very careful with the use of these two functions.
Your code should not modify the value of the mmap_t
struct initialized by lm_alloc()
, as it may affect the correctness of the lm_free()
call with that struct as argument.
VBE functions 0x00 and 0x01 return data in a memory buffer, as defined in their specification, in pages 12 to 24 of the VBE 2.0 standard. These data comprises several fields whose size is specified using one of 3 types: db, dw and dd, with sizes 1, 2 and 4 bytes, respectively. Because, memory space was at a premium, this data is stored sequentially in memory, without holes (except for fields defined in previous versions that were deprecated).
This layout creates a potential problem, when one defines C language structs with the fields defined in the VBE standard and uses C types such as uint8_t, uint16_t and uint32_t defined in <stdint.h> corresponding to the "types" used in VBE's specification. The problem is that for performance reasons, most compilers store the members of a C struct in positions whose addresses are aligned according to their types; this may lead to holes in the structure. In this case, access to a member of the struct in C, may actually access a memory position storing a different field, or a different piece of the same field.
The GNU C compiler installed in the Minix 3 image in the lab computers (and VMware Player) provides the __attribute__ ((packed)) extension: when this annotation is used in the source code, the C compiler will not place the members of the C struct in memory positions whose addresses are aligned according to their types, but rather place them sequentially without any holes between them.
Because the definition of the vbe_mode_info_t type in vbe.h is already declared as "packed", you can access directly its members, without concern of their layout in memory.
To summarize, the sequence of operations of the program to develop in this lab is as follows:
vg_init()
, which should call vbe_get_mode_info()
to obtain the mode parameters, and map the graphics mode VRAM in the process' address spacevg_exit()
IMP. Before executing this sequence you must call the function sef_startup()
to synchronize with the RS.
As we have already mentioned, with the linear frame buffer model, each pixel in the screen is mapped sequentially to video RAM from the left to the right, and from the top to the bottom. Thus, to access graphics mode VRAM in C, after mapping it in the process' address space, you can use C pointers, just like you have done in Lab1 to access text mode VRAM.
Note To keep the prototypes of the functions vg_fill(), vg_set_pixel(), etc. simple, they do not take the address on which VRAM is mapped nor the horizontal resolution and so on as arguments. Instead, we have declared 4 static variables in video_gr.c, which are inititialized by vg_init()
:
static char *video_mem; /* Process address to which VRAM is mapped */
static unsigned h_res; /* Horizontal resolution in pixels */
static unsigned v_res; /* Vertical resolution in pixels */
static unsigned bits_per_pixel; /* Number of VRAM bits per pixel */
Although the use of global variables is something you should avoid, there are two reasons why they are acceptable here:
Privileged user processes that access HW resources directly, must be compiled with special MINIX 3 libraries. The make utility can make this task a lot easier. All you have to do is to copy the Makefile used in Lab1 to /usr/src/drivers/lab2/, modify it so that it now uses the libraries and source files provided for this lab, and give the command as user lcom:
$ make
To install your program in /usr/sbin/
all you need to do is, to give the following command as root:
# make install
Your program invokes functions that are privileged. Thus, before you can run it, you need to add to /etc/system.conf.d/ a file named lab2, with the following contents:
service lab2
{
system
UMAP # 14
INT86 # 20
PRIVCTL
READBIOS
;
ipc
SYSTEM # to retrieve system information
rs # for service initialization
vm # to map video RAM
pm # for malloc, if needed
vfs
;
uid 0;
};
In this lab, we do not provide you the file with the main()
function and the scaffolding test code that we will use to grade your submission. You can however use the code provided for Lab1, i.e. file lab1.c and modify it accordingly, so that you can test the functions that you have to develop for this lab.
This means that in this lab, there is no standard way to invoke your code for testing it in class. Nevertheless, because your program must run as a privileged process you'll need to use the service utility to run it, just like in Lab 1.
Starting with this lab, you'll use the SVN repository of your group's LCO; project on Redmine to submit your code, both the preparation code and the final code. Thus, all you need to do is to create a directory named lab2 at the top level in that repository and commit your code to that directory.
The code for this lab should be developed in the /usr/src/drivers/lab2/ directory. However, this directory does not exist in the Minix 3 VMware image in the lab PCs. To set it up, you can execute as root the following steps:
mkdir /usr/src/drivers/lab2
chown lcom /usr/src/drivers/lab2
The first command must be executed as root because the directory /usr/src/drivers/ is owned by it and other users do not have the necessary permissions to perform that task. This command creates the desired directory, but its owner will also be root. The second command changes the ownership of the /usr/src/drivers/lab2/ directory to user lcom, so that you can create files in that directory when logged in as that user. Of course, changing the ownership of a directory owned by root can only be done by itself, thus the second step must also be executed as root. (Actually, the command chown usually can be executed by root only; when executed by other users it has no effect.)
Eclipse's RSE plug-in supports transferring either files or directories/folders between the local file system, i.e. the file system of the OS where Eclipse is run, and the remote file system. Considering the lab's PCs, this means that you can transfer files between the Linux file system and the Minix 3 image on the VMware Player. For this, all you have to do is to select the source file or directory on the RSE tab (entitled Remote Systems), click on the right-hand mouse button and select the Copy operation in the pop-up menu. Then, still on the RSE tab select the destination directory, click on the right-hand mouse and select the Paste operation in the pop-up menu. If the source file/directory is in the local file system and the destination directory is in the remote file system, you'll copy from the host (Linux on the lab's PCs) to the Minix 3 image on VMware Player. On the other hand, if the source file/directory is in the remote file system and the destination directory is in the local file system, the copy will be in the opposite direction.
If you'd rather use the command line, in Linux, you can use for example rsync. I suggest you read its man page in Linux. In any case, to copy the entire /home/lcom/lab2/ directory on Linux to /usr/src/drivers/lab2/ you can give the following command, assuming you have already executed the operations described in Annex 1 and that you execute the command from lcom's home directory:
rsync -auv lab2/ lcom@192.168.148.130:/usr/src/drivers/lab2/
This lab is based on a lab by João Cardoso and Miguel P. Monteiro for DJGPP running on Windows98.