Computer Labs 2013/2014 - 1st Semester
Lab 2: The PC's Video Card in Graphics Mode


1. Objective

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.

2. What to Do

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:

  1. void *vg_init(unsigned long mode)
  2. int vg_fill(unsigned long color)
  3. int vg_set_pixel(unsigned long x, unsigned long y, unsigned long color)
  4. int vg_get_pixel(unsigned long x, unsigned long y)
  5. int vg_draw_line(unsigned long xi, unsigned long yi, unsigned long xf, unsigned long yf, unsigned long color)

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:

  1. int vbe_get_mode_info(unsigned short mode, vbe_mode_info_t *vmi_p)

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.

All the code for this lab should be in the /home/lcom/lab2/ directory that you will have to create in the Minix 3 VM Player image of the lab's PCs. Annex 1 describes how to transfer files to and from Minix 3 file system.

So that your work can be graded, you must commit all the source code for this lab and a makefile to directory lab2 that you should create under the root directory of the SVN repository associated with your Redmine project. Checking out this directory, copying liblm.a to that directory and running make should be enough to generate a Minix 3 privileged program that implements the functions listed above. Annex 2 describes how you can use the svn client on Minix to achieve this.

2.1 Class Preparation

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.

3. The VBE Standard

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

  1. to retrieve information regarding the video card's capabilities, including the modes supported and their characteristics and
  2. to change the operating mode.

Because these functions are not time-critical, they must be accessed via the "INT 0x10 interface".

3.1 Accessing the VBE Functions

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:

AHMeaning
0x01Function call failed
0x02Function is not supported in current HW configuration
0x03Function is invalid in current video mode

3.2 Setting the Graphics 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.

3.3 Linear/Flat Frame Buffer Model

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.

3.4 Returning to Text Mode

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.

4. Use of the VBE Interface in Minix 3

Accessing the video card via the VBE interface in Minix 3 raises a few issues:

Invocation of the INT 0x10 instruction in real-mode
Minix 3 executes in protected mode, however VBE requires the invocation of INT 0x10 in real-mode. There is therefore a need for a change in the operating mode of the processor.
Allocation and access to memory in the lower 1 MByte of the physical address space
VBE functions 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.
Access and processing of the information returned by VBE functions 0x00 and 0x01
Actually, there are two issues related to this. First, this information is stored in a sequence of memory positions without any concern for the alignment of the data according to their type. Second, most pointers, with the notable exception of the physical address of video VRAM, are real-mode far-pointers.
Mapping of VRAM into the process address space
Graphics mode VRAM, like text mode VRAM, is not directly accessible to a user process in Minix 3. So that a process can access VRAM, it must first map it into its address space.
Granting the necessary permissions to the "driver"
Addressing most of the issues in this list requires the execution of privileged operations. Therefore, the program you'll develop needs to be granted the necessary permissions, and therefore synchronize with the RS server.

The last two issue were already discussed in the first lab, thus here we will address only the other three.

4.1 Invocation of the INT 0x10 instruction in real-mode

Minix 3 offers the SYS_INT86 kernel call, whose description in the Minix 3 Developers Guide is as follows:

Make a real-mode BIOS on behalf of a user-space device driver. This temporarily switches from 32-bit protected mode to 16-bit real-mode to access the BIOS calls.

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).

4.2 Allocation and access to memory in the lower 1 MByte of the physical address space

As usual, 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.

4.3 Access and Processing to the Data Returned by VBE Functions 0x00 and 0x01

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.

4.4 Summary

To summarize, the sequence of operations of the program to develop in this lab is as follows:

  1. Initialize the video graphics mode module, by calling vg_init(), which should call vbe_get_mode_info() to obtain the mode parameters, and map the graphics mode VRAM in the process' address space
  2. Modify video RAM, by calling the functions you'll develop for this lab.
  3. Switch back to text mode, by calling vg_exit()

IMP. Before executing this sequence you must call the function sef_startup() to synchronize with the RS.

5. Accessing Graphics Mode Video RAM in C

As we have already mentioned, with the linear frame buffer model, each pixel on 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:

  1. These are static global variables, and thus their scope is limited to the file where they are declared, i.e. they are not visible in other files
  2. We are structuring our code very much like in object oriented programming, and these variables are akin to the private member variables of a C++ class.

6. Compiling your Program

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 /home/lcom/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

7. Configuring your Program

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; };

8. Running your program

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.

9. Submission

Starting with this lab, you'll use the SVN repository of your group's LCOM 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. You may wish to check Annex 2 for some hints on how to do this.

Annex 1: Copying files to the Minix 3 file system

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 /home/lcom/lab2/ you can give the following command, assuming that you execute the command from lcom's home directory on Linux:

$ rsync -auv lab2/ lcom@<minix_ip_addr>:/home/lcom/lab2/

Where <minix_ip_addr> is the IP address used by Minix. You can find it easily by executing the following command on Minix:

$ ifconfig

Annex 2: Using SVN

In this annex I describe the most straightforward way, in my opinion, to work with the SVN repository you have created on Redmine, while using the file hierarchy suggested on Minix, i.e. to use a subdirectory per lab under the home directory of user lcom. There are however other ways to do it, that you can find more convenient.

The approach I suggest is to use the svn client directly under Minix. This approach has the advantage that you need not do any additional copies: you use a working directory under Minix, and whenever necessary you commit the changes you have made directly to the SVN repository associated with your Redmine project.

The first step, and the trickiest, is to get a working copy for the root of the repository. I'm assuming that the SVN repository is empty, as after the creation of your Redmine project. So to achieve this step all you need to do is to issue the following command on lcom's home directory in Minix:

$ svn checkout https://svn.fe.up.pt/repos/lcom1314-t0g00/ ./

Where you should replace lcom1314-t0g00 by the identifier of your Redmine project. As a result, you'll get the following output:

Error validating server certificate for 'https://svn.fe.up.pt:443': - The certificate is not issued by a trusted authority. Use the fingerprint to validate the certificate manually! Certificate information: - Hostname: svn.fe.up.pt - Valid: from Tue, 14 Jun 2011 00:00:00 GMT until Fri, 13 Jun 2014 23:59:59 GMT - Issuer: TERENA, NL - Fingerprint: 3b:22:ae:0a:7c:c0:fe:0a:48:c0:db:b4:63:a3:ae:7c:41:8f:58:60 (R)eject, accept (t)emporarily or accept (p)ermanently?

You can type either t or p, depending on whether you do not mind to be bothered every time you access the SVN repository. (If you are doing this on I304 computers, "permanently" will mean until the next time you logout from Linux.) Either way, you'll get the following response:

Authentication realm: Redmine Subversion Repository Password for 'lcom':

At this point, the svn command is assuming, wrongly, that your account in the SVN repository is lcom. Thus, what you need to do is just to press the enter-key, and as a result, you will be prompted for your credentials (user name first and then password) in SIGARRA, as shown here (for user pfs, i.e. me):

Authentication realm: Redmine Subversion Repository Username: pfs Password for 'pfs':

After entering correctly your credentials you'll get the following:

----------------------------------------------------------------------- ATTENTION! Your password for authentication realm: Redmine Subversion Repository can only be stored to disk unencrypted! You are advised to configure your system so that Subversion can store passwords encrypted, if possible. See the documentation for details. You can avoid future appearances of this warning by setting the value of the 'store-plaintext-passwords' option to either 'yes' or 'no' in '/home/lcom/.subversion/servers'. ----------------------------------------------------------------------- Store password unencrypted (yes/no)?

What's happening here is that the svn client is not well configured and it stores the passwords in the clear. So you are asked if you allow it. If you do, your SIGARRA password will be stored in the clear somewhere in Minix's file system. This is not very serious if you are using the VM image on I304 computers, as that image will be removed as soon as someone else logs in as user lcom. Answering no here means that every time the svn client tries to access the SVN repository on your behalf, you will be asked for your SIGARRA credentials. The decision is yours, but I'd say that saving the password in the clear in these conditions is not a big security risk (this should be compared against having to type your password multiple times, possibly in the presence of others).

Upon success, you should get an empty working directory in lcom's home directory on Minix. Assuming that you had previously created a subdirectory named lab2 and that you had copied to that subdirectory the files provided for this lab, you can now put them under control of SVN by executing the following commands (starting in lcom's home directory) on Minix:

$ svn add lab2 $ cd lab2 $ svn add *.[ch] Makefile

IMP: You should not add liblm.a to the repository: this is an archive file, which contains object code not source code.

Finally you can make these changes effective on the repository by committing them with the command:

$ svn commit -m "New directory and files for lab2"

The option -m is very important. It allows you to provide a message (string) summarizing the changes made that lead to this new revision/version. If you do not specify this message, svn will initiate vi so that you can type in a message (presumably longer). Given that most of you are not familiar with vi, I suggest that you use only the -m option and use a short message. For longer messages, you can a text file which is itself kept in the repository.

During your lab, you can edit the files provided to add code to the stub functions in video_gr.c, and as you complete the different steps I suggest that you commit your work with an appropriate log message. E.g.:

$ svn commit -m "vg_init() working with hard-coded parameters."

This way, you will be gradually submitting your work, and will ensure that if something bad happens to Minix's image, you will not loose most of your work. (You can reduce the amount of work you loose by committing your changes frequently.) In this case, I suggest you use a log message such as "Backup", making it clear that the versions you are creating correspond to incomplete versions of your code.

Acknowledgments

This lab is based on a lab by João Cardoso and Miguel P. Monteiro for DJGPP running on Windows98.