set_videofunction which is defined in the arch/x86/boot/video.c source code file. We can see that it starts by first getting the video mode from the
copy_boot_paramsfunction (you can read about it in the previous post).
vid_modeis an obligatory field which is filled by the bootloader. You can find information about it in the kernel
vgaoption to the grub (or another bootloader's) configuration file and it will pass this option to the kernel command line. This option can have different values as mentioned in the description. For example, it can be an integer number
ask. If you pass
vga, you will see a menu like this:
u16etc. in the kernel setup code. Let's look at a couple of data types provided by the kernel:
init_heapfunction. We have a couple of utility macros and functions for managing the heap which are defined in
extern char _end;
__get_heapwith 3 parameters:
__alignof__(type)specifies how variables of this type are to be aligned
nspecifies how many items to allocate
__get_heapworks. We can see here that
HEAP(which is equal to
RESET_HEAP()) is assigned the address of the aligned memory according to the
aparameter. After this we save the memory address from
HEAPto the end of the allocated block and return
tmpwhich is the start address of allocated memory.
RESET_HEAP()call in the
set_videofunction. Next is the call to
store_mode_paramswhich stores video mode parameters in the
boot_params.screen_infostructure which is defined in include/uapi/linux/screen_info.h header file.
store_mode_paramsfunction, we can see that it starts with a call to the
store_cursor_positionfunction. As you can understand from the function name, it gets information about the cursor and stores it.
store_cursor_positioninitializes two variables which have type
AH = 0x3, and calls the
0x10BIOS interruption. After the interruption is successfully executed, it returns row and column in the
DHregisters. Row and column will be stored in the
orig_yfields of the
store_cursor_positionis executed, the
store_video_modefunction will be called. It just gets the current video mode and stores it in
store_mode_paramschecks the current video mode and sets the
video_segment. After the BIOS transfers control to the boot sector, the following addresses are for video memory:
0xb000if the current video mode is MDA, HGC, or VGA in monochrome mode and to
0xb800if the current video mode is in color mode. After setting up the address of the video segment, the font size needs to be stored in
FSregister with the
set_fsfunction. We already saw functions like
set_fsin the previous part. They are all defined in arch/x86/boot/boot.h. Next, we read the value which is located at address
0x485(this memory location is used to get the font size) and save the font size in
0x44aand rows by address
0x484and store them in
boot_params.screen_info.orig_video_lines. After this, execution of
save_screenfunction which just saves the contents of the screen to the heap. This function collects all the data which we got in the previous functions (like the rows and columns, and stuff) and stores it in the
saved_screenstructure, which is defined as:
probe_cards(0)from arch/x86/boot/video-mode.c source code file. It goes over all video_cards and collects the number of modes provided by the cards. Here is the interesting part, we can see the loop:
video_cardsis not declared anywhere. The answer is simple: every video mode presented in the x86 kernel setup code has a definition that looks like this:
__videocardis a macro:
video_cardsis just a memory address and all
card_infostructures are placed in this segment. It means that all
card_infostructures are placed between
video_cards_end, so we can use a loop to go over all of it. After
probe_cardsexecutes we have a bunch of structures like
static __videocard video_vgawith the
nmodes(the number of video modes) filled in.
probe_cardsfunction is done, we move to the main loop in the
set_videofunction. There is an infinite loop which tries to set up the video mode with the
set_modefunction or prints a menu if we passed
vid_mode=askto the kernel command line or if video mode is undefined.
set_modefunction is defined in video-mode.c and gets only one parameter,
mode, which is the number of video modes (we got this value from the menu or in the start of
setup_video, from the kernel setup header).
set_modefunction checks the
modeand calls the
raw_set_modecalls the selected card's
card->set_mode(struct mode_info*). We can get access to this function from the
card_infostructure. Every video mode defines this structure with values filled depending upon the video mode (for example for
vgait is the
video_vga.set_modefunction. See the above example of the
vga_set_mode, which checks the vga mode and calls the respective function:
0x10BIOS interrupt with a certain value in the
vesa_store_edidis called. This function simply stores the EDID (Extended Display Identification Data) information for kernel use. After this
store_mode_paramsis called again. Lastly, if
do_restoreis set, the screen is restored to an earlier state.
go_to_protected_mode- in arch/x86/boot/main.c. As the comment says:
Do the last things and invoke protected mode, so let's see what these last things are and switch into protected mode.
go_to_protected_modefunction is defined in arch/x86/boot/pm.c. It contains some functions which make the last preparations before we can jump into protected mode, so let's look at it and try to understand what it does and how it works.
go_to_protected_mode. This function invokes the real mode switch hook if it is present and disables NMI. Hooks are used if the bootloader runs in a hostile environment. You can read more about hooks in the boot protocol (see ADVANCED BOOT LOADER HOOKS).
realmode_switchhook presents a pointer to the 16-bit real mode far subroutine which disables non-maskable interrupts. After the
realmode_switchhook (it isn't present for me) is checked, Non-Maskable Interrupts(NMI) is disabled:
cliinstruction which clears the interrupt flag (
IF). After this, external interrupts are disabled. The next line disables NMI (non-maskable interrupt).
0x80(disabled bit) to
0x70(the CMOS Address register). After that, a call to the
io_delaycauses a small delay and looks like:
0x80should delay exactly 1 microsecond. So we can write any value (the value from
ALin our case) to the
0x80port. After this delay the
realmode_switch_hookfunction has finished execution and we can move to the next function.
GSregister. Next, we read the value at the address
0x200) and put this value into the variables
wrfs32function, then delay for 1ms, and then read the value from the
GSregister into the address
A20_TEST_ADDR+0x10. In a case when
a20line is disabled, the address will be overlapped, in other case if it's not zero
a20line is already enabled the A20 line.
a20.c. For example, it can be done with a call to the
0x15BIOS interrupt with
reset_coprocessorfunction is called:
0xf0and then resets it by writing
mask_all_interruptsfunction is called:
null_idtcontains the address and size of the IDT, but for now they are just zero.
gdt_ptrstructure, it is defined as:
len) of the IDT and the 32-bit pointer to it (More details about the IDT and interruptions will be seen in the next posts).
__attribute__((packed))means that the size of
gdt_ptris the minimum required size. So the size of the
gdt_ptrwill be 6 bytes here or 48 bits. (Next we will load the pointer to the
GDTRregister and you might remember from the previous post that it is 48-bits in size).
setup_gdtfunction which sets up the GDT (you can read about it in the post Kernel booting process. Part 2.). There is a definition of the
boot_gdtarray in this function, which contains the definition of the three segments:
boot_gdt. First of all note that it has the
__attribute__((aligned(16)))attribute. It means that this structure will be aligned by 16 bytes.
intfield must be 4 bytes in size, but an
alignedstructure will need 16 bytes to store in memory:
GDT_ENTRY_BOOT_CShas index - 2 here,
GDT_ENTRY_BOOT_CS + 1and etc. It starts from 2, because the first is a mandatory null descriptor (index - 0) and the second is not used (index - 1).
GDT_ENTRYis a macro which takes flags, base, limit and builds a GDT entry. For example, let's look at the code segment entry.
GDT_ENTRYtakes the following values:
0xfffff(1 MB). Let's look at the flags. It is
0xc09band it will be:
boot_gdtand subtract 1 (the last valid address in the GDT).
boot_gdtand add it to the address of the data segment left-shifted by 4 bits (remember we're in real mode now).
lgdtlinstruction to load the GDT into the GDTR register:
go_to_protected_modefunction. We loaded the IDT and GDT, disabled interrupts and now can switch the CPU into protected mode. The last step is calling the
protected_mode_jumpfunction with two parameters:
protected_mode_jump. As I wrote above, you can find it in
arch/x86/boot/pmjump.S. The first parameter will be in the
eaxregister and the second one is in
esiregister and the address of the code segment register
bxby 4 bits and add it to the memory location labeled
(cs << 4) + in_pm32, the physical address to jump after transitioned to 32-bit mode) and jump to label
2will be overwritten with
(cs << 4) + in_pm32.
GDT_ENTRY_BOOT_CShas index 2 and every GDT entry is 8 byte, so
2 * 8 = 16,
__BOOT_DSis 24 etc.
PE(Protection Enable) bit in the
0x66is the operand-size prefix which allows us to mix 16-bit and 32-bit code
0xea- is the jump opcode
in_pm32is the segment offset under protect mode, which has value
(cs << 4) + in_pm32derived from real mode
__BOOT_CSis the code segment we want to jump to.
cxregister. Now we fill it with all segment registers besides
eaxcontains the address of the 32-bit entry (we passed it as the first parameter into