设置视频(Video)模式

接下来main将执行进入保护模式前最重要的一个步骤,设置视频模式,调用set_video()函数,来自linux/arch/x86/boot/video.c

 

315void set_video(void)

 316{

 317        u16 mode = boot_params.hdr.vid_mode;

 318

 319        RESET_HEAP();

 320

 321        store_mode_params();

 322        save_screen();

 323        probe_cards(0);

 324

 325        for (;;) {

 326                if (mode == ASK_VGA)

 327                        mode = mode_menu();

 328

 329                if (!set_mode(mode))

 330                        break;

 331

 332                printf("Undefined video mode number: %x/n", mode);

 333                mode = ASK_VGA;

 334        }

 335        boot_params.hdr.vid_mode = mode;

 336        vesa_store_edid();

 337        store_mode_params();

 338

 339        if (do_restore)

 340                restore_screen();

 341}

 

317行,根据传过来的hdr参数得到视频模式,存储到内部变量mode中。我们看到header.S中的vid_mode值是SVGA_MODE。这时候要从新设置一下堆的位置,把它设定到_end处。

 

为什么要调整一下堆的位置,我也搞不清楚,也许是跟视频配置有着千丝万缕的关系吧,有兴趣的同志可以去研究一下。随后,调用store_mode_params()来设置boot_paramsscreen_info字段:

 

53/*

  54 * Store the video mode parameters for later usage by the kernel.

  55 * This is done by asking the BIOS except for the rows/columns

  56 * parameters in the default 80x25 mode -- these are set directly,

  57 * because some very obscure BIOSes supply insane values.

  58 */

  59static void store_mode_params(void)

  60{

  61        u16 font_size;

  62        int x, y;

  63

  64        /* For graphics mode, it is up to the mode-setting driver

  65           (currently only video-vesa.c) to store the parameters */

  66        if (graphic_mode)

  67                return;

  68

  69        store_cursor_position();

  70        store_video_mode();

  71

  72        if (boot_params.screen_info.orig_video_mode == 0x07) {

  73                /* MDA, HGC, or VGA in monochrome mode */

  74                video_segment = 0xb000;

  75        } else {

  76                /* CGA, EGA, VGA and so forth */

  77                video_segment = 0xb800;

  78        }

  79

  80        set_fs(0);

  81        font_size = rdfs16(0x485); /* Font size, BIOS area */

  82        boot_params.screen_info.orig_video_points = font_size;

  83

  84        x = rdfs16(0x44a);

  85        y = (adapter == ADAPTER_CGA) ? 25 : rdfs8(0x484)+1;

  86

  87        if (force_x)

  88                x = force_x;

  89        if (force_y)

  90                y = force_y;

  91

  92        boot_params.screen_info.orig_video_cols  = x;

  93        boot_params.screen_info.orig_video_lines = y;

  94}

 

store_mode_params()函数主要是利用BIOS显示服务程序对视频显示进行设置。有关BIOS显示服务程序请参考有关博文“BIOS系统服务 —— 显示服务”

 

首先,我们看到69store_cursor_position()函数:

 

  20static void store_cursor_position(void)

  21{

  22        struct biosregs ireg, oreg;

  23

  24        initregs(&ireg);

  25        ireg.ah = 0x03;

  26        intcall(0x10, &ireg, &oreg);

  27

  28        boot_params.screen_info.orig_x = oreg.dl;

  29        boot_params.screen_info.orig_y = oreg.dh;

  30

  31        if (oreg.ch & 0x20)

  32                boot_params.screen_info.flags |= VIDEO_FLAGS_NOCURSOR;

  33

  34        if ((oreg.ch & 0x1f) > (oreg.cl & 0x1f))

  35                boot_params.screen_info.flags |= VIDEO_FLAGS_NOCURSOR;

  36}

 

25行,AH03表示在文本坐标下,读取光标各种信息功能。出口参数oreg为:CH=光标的起始行;CL=光标的终止行;DH=行(Y坐标)DL=列(X坐标)。那么将DLDH的值分别存储到boot_params参数的screen_info.orig_x字段和screen_info.orig_y中,表示当前光标的位置,并根据结果修改screen_infoflags

 

回到store_mode_params()函数中,接下来执行store_video_mode()函数

 

  38static void store_video_mode(void)

  39{

  40        struct biosregs ireg, oreg;

  41

  42        /* N.B.: the saving of the video page here is a bit silly,

  43           since we pretty much assume page 0 everywhere. */

  44        initregs(&ireg);

  45        ireg.ah = 0x0f;

  46        intcall(0x10, &ireg, &oreg);

  47

  48        /* Not all BIOSes are clean with respect to the top bit */

  49        boot_params.screen_info.orig_video_mode = oreg.al & 0x7f;

  50        boot_params.screen_info.orig_video_page = oreg.bh;

  51}

 

45行,AH0f表示读取显示器模式功能,出口参数oreg中,ah为屏幕字符的列数;al为显示模式;bh=页码。随后将这些信息分别存储到screen_infoorig_video_modeorig_video_page中,表示当前的显示属性。

 

回到store_mode_params()函数中,第7278行,没有问题,根据刚刚得到的显示模式,设置全局变量video_segment的值。同样,这个全局变量跟_end一样,是编译的时候生成的。第8081行,从BIOS中获取字体大小,保存到内部变量font_size中。注意,为什么说是从BIOS中,因为调用rdfs16内联函数:

static inline u16 rdfs16(addr_t addr)

{

       u16 v;

       asm volatile("movw %%fs:%1,%0" : "=r" (v) : "m" (*(u16 *)addr));

       return v;

}

 

其参数addr 的值为0x485,对照内核映像解压缩后的内存布局,可以看到这个地址是在BIOS的区域内。82行的代码就保留这个值。

 

8493行,仍然是通过直接读BIOS的方式得到显示属性的行数和列数,并填充screen_infoorig_video_colsorig_video_lines字段。注意这里有个编译选项,如果在编译的时候设定了force_xforce_y,就把显示属性的行数和列数强制设为这个值。

 

store_mode_params()函数结束了,基本的显示参数都存储到boot_paramsscreen_info字段中了,回到set_video函数中,第322行,save_screen()函数,用于将当前屏幕的内容存储到指定的内存空间中:

 

230/* Save screen content to the heap */

231static struct saved_screen {

232        int x, y;

233        int curx, cury;

234        u16 *data;

235} saved;

236

237static void save_screen(void)

 238{

 239        /* Should be called after store_mode_params() */

 240        saved.x = boot_params.screen_info.orig_video_cols;

 241        saved.y = boot_params.screen_info.orig_video_lines;

 242        saved.curx = boot_params.screen_info.orig_x;

 243        saved.cury = boot_params.screen_info.orig_y;

 244

 245        if (!heap_free(saved.x*saved.y*sizeof(u16)+512))

 246                return;         /* Not enough heap to save the screen */

 247

 248        saved.data = GET_HEAP(u16, saved.x*saved.y);

 249

 250        set_fs(video_segment);

 251        copy_from_fs(saved.data, 0, saved.x*saved.y*sizeof(u16));

 252}

 

一路走来的朋友们对这种函数应该熟悉了吧,240行道248行设置全局变量saved。有个地方不看不懂,就是GET_HEAP

 

#define RESET_HEAP() ((void *)( HEAP = _end ))

 

static inline char *__get_heap(size_t s, size_t a, size_t n)

{

       char *tmp;

 

       HEAP = (char *)(((size_t)HEAP+(a-1)) & ~(a-1));

       tmp = HEAP;

       HEAP += s*n;

       return tmp;

}

#define GET_HEAP(type, n) /

       ((type *)__get_heap(sizeof(type),__alignof__(type),(n)))

 

像这么复杂的代码在Linux中比比皆是。虽然我们看不懂,但是可以猜。不过就像当年我们学英语做阅读题一样,必须有根据地猜,不能瞎猜。首先,还记得先前set_video319行有个RESET_HEAP,就是把HEAP全局变量设置成了_end_end这个东西我们熟悉,是vmlinuz实模式部分的结束位置,也是堆栈段的开始位置。调用GET_HEAP宏之前,HEAP指向的_end。而调用GET_HEAP(u16, saved.x*saved.y),对应__get_heap返回后,HEAP大约就指向了_end后再经过2*saved.x*saved.y*2字节偏移的位置(我们假设sizeof(u16)2)。

 

因此,我猜测,屏幕假设有saved.x*saved.y个像素,那么每个像素需要2*24个字节的内存来存储,248行使用GET_HEAP宏就是分配这么个堆来存储这些东西,并且saveddata字段指向这个堆的首地址。顺便提一下,学过操作系统的都知道,堆跟栈是差不多的概念,唯一不同的是栈是向上增长的,堆跟栈正好相反,向下增长。所以这个堆用在这里,给这个多分配了这么多空间,就不会与其他内存中的内容发生冲突。

 

save_screen()函数的最后一行,调用一个copy_from_fs函数将当前屏幕的每个像素的内容拷贝到这个堆中。注意,video_segmentgrub传递过来的参数,记录着内存中显示器设备的内容。

 

回到set_video中,接下来315行调用probe_cards(0),用来扫描显卡,来自

linux/arch/x86/boot/video-mode.c

 

/* Probe the video drivers and have them generate their mode lists. */

  33void probe_cards(int unsafe)

  34{

  35        struct card_info *card;

  36        static u8 probed[2];

  37

  38        if (probed[unsafe])

  39                return;

  40

  41        probed[unsafe] = 1;

  42

  43        for (card = video_cards; card < video_cards_end; card++) {

  44                if (card->unsafe == unsafe) {

  45                        if (card->probe)

  46                                card->nmodes = card->probe();

  47                        else

  48                                card->nmodes = 0;

  49                }

  50        }

  51}

 

传递给probe_cards的参数为0,所以unsafe=0。随后扫描整个显卡列表。video_cardsvideo_cards_end都是grub传递过来的显卡列表。

 

回到set_video中,325行进入一个循环。根据bootloader传进来的hdrvid_mode,如果modeASK_VGA,就进行一些交互式的工作。grub传进来的vid_mode不是ASK_VGA,所以我们不去分析mode_menu()函数了,感兴趣的,或者想了解BIOS编程的可以去分析一下它,这个函数是交互式BIOS程序的编程典范。

 

直接跳出循环,来到336行。vesa_store_edid()函数,是对EDID的设置。EDID是一种VESA 标准数据格式,其中包含有关监视器及其性能的参数,包括供应商信息、最大图像大小、颜色设置、厂商预设置、频率范围的限制以及显示器名和序列号的字符串。我们不去分析它了。

 

337行再次执行store_mode_params()来设置boot_paramsscreen_info字段,最后根据是否进入了mode_menu设置了do_restore来恢复刚刚被保存的screen_info信息,它跟我们刚刚介绍过的save_screen正好相反:

 

254static void restore_screen(void)

 255{

 256        /* Should be called after store_mode_params() */

 257        int xs = boot_params.screen_info.orig_video_cols;

 258        int ys = boot_params.screen_info.orig_video_lines;

 259        int y;

 260        addr_t dst = 0;

 261        u16 *src = saved.data;

 262        struct biosregs ireg;

 263

 264        if (graphic_mode)

 265                return;         /* Can't restore onto a graphic mode */

 266

 267        if (!src)

 268                return;         /* No saved screen contents */

 269

 270        /* Restore screen contents */

 271

 272        set_fs(video_segment);

 273        for (y = 0; y < ys; y++) {

 274                int npad;

 275

 276                if (y < saved.y) {

 277                        int copy = (xs < saved.x) ? xs : saved.x;

 278                        copy_to_fs(dst, src, copy*sizeof(u16));

 279                        dst += copy*sizeof(u16);

 280                        src += saved.x;

 281                        npad = (xs < saved.x) ? 0 : xs-saved.x;

 282                } else {

 283                        npad = xs;

 284                }

 285

 286                /* Writes "npad" blank characters to

 287                   video_segment:dst and advances dst */

 288                asm volatile("pushw %%es ; "

 289                             "movw %2,%%es ; "

 290                             "shrw %%cx ; "

 291                             "jnc 1f ; "

 292                             "stosw /n/t"

 293                             "1: rep;stosl ; "

 294                             "popw %%es"

 295                             : "+D" (dst), "+c" (npad)

 296                             : "bdS" (video_segment),

 297                               "a" (0x07200720));

 298        }

 299

 300        /* Restore cursor position */

 301        if (saved.curx >= xs)

 302                saved.curx = xs-1;

 303        if (saved.cury >= ys)

 304                saved.cury = ys-1;

 305

 306        initregs(&ireg);

 307        ireg.ah = 0x02;         /* Set cursor position */

 308        ireg.dh = saved.cury;

 309        ireg.dl = saved.curx;

 310        intcall(0x10, &ireg, NULL);

 311

 312        store_cursor_position();

 313}

 

当然,针对我们的grub,也是不会走这一步的,所以上面的代码也就只是摆设而已了。

你可能感兴趣的:(struct,video,存储,Parameters,menu,DST)