aboutsummaryrefslogtreecommitdiffstats
path: root/xen/arch/x86/boot/video.S
blob: b238bf3af5119204160ed11aaa860bcc2488d9b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
/******************************************************************************
 * video.S
 *
 * Display adapter & video mode setup, version 2.13 (14-May-99)
 *
 * Copyright (C) 1995 -- 1998 Martin Mares <mj@ucw.cz>
 * Based on the original setup.S code (C) Linus Torvalds and Mats Anderson
 *
 * Rewritten to use GNU 'as' by Chris Noe <stiker@northlink.com> May 1999
 *
 * Updated and ported for Xen by Keir Fraser <keir@xensource.com> June 2007
 */

        .code16

#include "video.h"

/* Scratch space layout: trampoline_end to trampoline_end+0x1000. */
#define modelist       bootsym(trampoline_end)   /* 2kB (256 entries) */
#define vesa_glob_info (modelist + 0x800)        /* 1kB */
#define vesa_mode_info (vesa_glob_info + 0x400)  /* 1kB */

/* Retrieve Extended Display Identification Data. */
#define CONFIG_FIRMWARE_EDID

/* Force 400 scan lines for standard modes (hack to fix bad BIOS behaviour */
#undef CONFIG_VIDEO_400_HACK

/* Positions of various video parameters passed to the kernel */
/* (see also include/linux/tty.h) */
#define PARAM_CURSOR_POS        0x00
#define PARAM_VIDEO_MODE        0x02
#define PARAM_VIDEO_COLS        0x03
#define PARAM_VIDEO_LINES       0x04
#define PARAM_HAVE_VGA          0x05
#define PARAM_FONT_POINTS       0x06
#define PARAM_CAPABILITIES      0x08
#define PARAM_LFB_LINELENGTH    0x0c
#define PARAM_LFB_WIDTH         0x0e
#define PARAM_LFB_HEIGHT        0x10
#define PARAM_LFB_DEPTH         0x12
#define PARAM_LFB_BASE          0x14
#define PARAM_LFB_SIZE          0x18
#define PARAM_LFB_COLORS        0x1c
#define PARAM_VESAPM_SEG        0x24
#define PARAM_VESAPM_OFF        0x26
#define PARAM_VESA_ATTRIB       0x28
#define _param(param) bootsym(boot_vid_info)+(param)

video:  xorw    %ax, %ax
        movw    %ax, %gs        # GS is zero
        cld
        call    basic_detect    # Basic adapter type testing (EGA/VGA/MDA/CGA)
        cmpb    $0,_param(PARAM_HAVE_VGA)
        je      1f                # Bail if there's no VGA
        movw    bootsym(boot_vid_mode), %ax     # User selected video mode
        cmpw    $ASK_VGA, %ax                   # Bring up the menu
        jz      vid2

        call    mode_set                        # Set the mode
        jc      vid1

        leaw    bootsym(badmdt), %si            # Invalid mode ID
        call    prtstr
vid2:   call    mode_menu
vid1:   call    store_edid
        call    mode_params                     # Store mode parameters
1:      ret

# Detect if we have CGA, MDA, EGA or VGA and pass it to the kernel.
basic_detect:
        movb    $0, _param(PARAM_HAVE_VGA)
        movb    $0x12, %ah                      # Check EGA/VGA
        movb    $0x10, %bl
        int     $0x10
        cmpb    $0x10, %bl                      # No, it's a CGA/MDA/HGA card.
        je      basret
        movw    $0x1a00, %ax                    # Check EGA or VGA?
        int     $0x10
        cmpb    $0x1a, %al                      # 1a means VGA...
        jne     basret                          # anything else is EGA.
        incb    _param(PARAM_HAVE_VGA)          # We've detected a VGA
basret: ret

# Store the video mode parameters for later usage by the kernel.
# This is done by asking the BIOS except for the rows/columns
# parameters in the default 80x25 mode -- these are set directly,
# because some very obscure BIOSes supply insane values.
mode_params:
        cmpb    $0, bootsym(graphic_mode)
        jnz     mopar_gr
        movb    $0x03, %ah                      # Read cursor position
        xorb    %bh, %bh
        int     $0x10
        movw    %dx, _param(PARAM_CURSOR_POS)
        movb    $0x0f, %ah                      # Read page/mode/width
        int     $0x10
        movw    %ax, _param(PARAM_VIDEO_MODE)   # Video mode and screen width
        movw    %gs:(0x485), %ax                # Font size
        movw    %ax, _param(PARAM_FONT_POINTS)  # (valid only on EGA/VGA)
        movw    bootsym(force_size), %ax        # Forced size?
        orw     %ax, %ax
        jz      mopar1

        movb    %ah, _param(PARAM_VIDEO_COLS)
        movb    %al, _param(PARAM_VIDEO_LINES)
        ret

mopar1: movb    %gs:(0x484), %al                # On EGA/VGA, use the EGA+ BIOS
        incb    %al                             # location of max lines.
mopar2: movb    %al, _param(PARAM_VIDEO_LINES)
        ret

# Fetching of VESA frame buffer parameters
mopar_gr:
        leaw    vesa_mode_info, %di
        movb    $0x23, _param(PARAM_HAVE_VGA)
        movw    16(%di), %ax
        movw    %ax, _param(PARAM_LFB_LINELENGTH)
        movw    18(%di), %ax
        movw    %ax, _param(PARAM_LFB_WIDTH)
        movw    20(%di), %ax
        movw    %ax, _param(PARAM_LFB_HEIGHT)
        movb    25(%di), %al
        movb    $0, %ah
        movw    %ax, _param(PARAM_LFB_DEPTH)
        movl    40(%di), %eax
        movl    %eax, _param(PARAM_LFB_BASE)
        movl    31(%di), %eax
        movl    %eax, _param(PARAM_LFB_COLORS)
        movl    35(%di), %eax
        movl    %eax, _param(PARAM_LFB_COLORS+4)
        movw    0(%di), %ax
        movw    %ax, _param(PARAM_VESA_ATTRIB)

# get video mem size
        leaw    vesa_glob_info, %di
        xorl    %eax, %eax
        movw    18(%di), %ax
        movl    %eax, _param(PARAM_LFB_SIZE)

# store mode capabilities
        movl    10(%di), %eax
        movl    %eax, _param(PARAM_CAPABILITIES)

# switching the DAC to 8-bit is for <= 8 bpp only
        movw    _param(PARAM_LFB_DEPTH), %ax
        cmpw    $8, %ax
        jg      dac_done

# get DAC switching capability
        xorl    %eax, %eax
        movb    10(%di), %al
        testb   $1, %al
        jz      dac_set

# attempt to switch DAC to 8-bit
        movw    $0x4f08, %ax
        movw    $0x0800, %bx
        int     $0x10
        cmpw    $0x004f, %ax
        jne     dac_set
        movb    %bh, bootsym(dac_size)          # store actual DAC size

dac_set:
# set color size to DAC size
        movb    bootsym(dac_size), %al
        movb    %al, _param(PARAM_LFB_COLORS+0)
        movb    %al, _param(PARAM_LFB_COLORS+2)
        movb    %al, _param(PARAM_LFB_COLORS+4)
        movb    %al, _param(PARAM_LFB_COLORS+6)

# set color offsets to 0
        movb    $0, _param(PARAM_LFB_COLORS+1)
        movb    $0, _param(PARAM_LFB_COLORS+3)
        movb    $0, _param(PARAM_LFB_COLORS+5)
        movb    $0, _param(PARAM_LFB_COLORS+7)

dac_done:
# get protected mode interface informations
        movw    $0x4f0a, %ax
        xorw    %bx, %bx
        xorw    %di, %di
        int     $0x10
        cmp     $0x004f, %ax
        jnz     no_pm

        movw    %es, _param(PARAM_VESAPM_SEG)
        movw    %di, _param(PARAM_VESAPM_OFF)

no_pm:  pushw   %ds
        popw    %es
        ret

# The video mode menu
mode_menu:
        leaw    bootsym(keymsg), %si    # "Return/Space/Timeout" message
        call    prtstr
        call    flush
nokey:  call    getkt

        cmpb    $0x0d, %al              # ENTER ?
        je      listm                   # yes - manual mode selection

        cmpb    $0x20, %al              # SPACE ?
        je      defmd1                  # no - repeat

        call    beep
        jmp     nokey

defmd1: ret                             # No mode chosen? Default 80x25

listm:  call    mode_table              # List mode table
listm0: leaw    bootsym(name_bann), %si # Print adapter name
        call    prtstr
        movw    bootsym(card_name), %si
        orw     %si, %si
        jnz     an2

        leaw    bootsym(vga_name), %si
        jmp     an1

an2:    call    prtstr
        leaw    bootsym(svga_name), %si
an1:    call    prtstr
        leaw    bootsym(listhdr), %si   # Table header
        call    prtstr
        movb    $0x30, %dl              # DL holds mode number
        leaw    modelist, %si
lm1:    cmpw    $ASK_VGA, (%si)         # End?
        jz      lm2

        movb    %dl, %al                # Menu selection number
        call    prtchr
        call    prtsp2
        lodsw
        call    prthw                   # Mode ID
        call    prtsp2
        lodsw
        call    prtdec                  # Width
        movb    $0x78, %al              # the letter 'x'
        call    prtchr
        lodsw
        call    prtdec                  # Height
        testb   $0xff,(%si)
        jnz     1f
        push    %si
        leaw    bootsym(textmode), %si
        call    prtstr
        pop     %si
        lodsw
        jmp     2f
1:      movb    $0x78, %al              # the letter 'x'
        call    prtchr
        lodsw
        call    prtdec                  # Depth
2:      movb    $0x0d, %al              # New line
        call    prtchr
        movb    $0x0a, %al
        call    prtchr
        incb    %dl                     # Next character

        cmpb    $'z'+1, %dl
        jnz     skip_bail
        leaw    bootsym(menu_bail_msg), %si
        call    prtstr
        jmp     lm2

skip_bail:
        cmpb    $'i', %dl
        jnz     skip_pause
        push    %si
        push    %dx
        leaw    bootsym(menu_more_msg), %si  # '<press space>'
        call    prtstr
        call    flush
1:      call    getkey
        cmpb    $0x20, %al              # SPACE ?
        jne     1b                      # yes - manual mode selection
        leaw    bootsym(crlft), %si
        call    prtstr
        pop     %dx
        pop     %si

skip_pause:
        cmpb    $'9'+1, %dl
        jnz     lm1
        movb    $'a', %dl
        jmp     lm1

lm2:    leaw    bootsym(prompt), %si    # Mode prompt
        call    prtstr
        leaw    bootsym(edit_buf), %di  # Editor buffer
lm3:    call    getkey
        cmpb    $0x0d, %al              # Enter?
        jz      lment

        cmpb    $0x08, %al              # Backspace?
        jz      lmbs

        cmpb    $0x20, %al              # Printable?
        jc      lm3

        cmpw    $bootsym(edit_buf)+4, %di  # Enough space?
        jz      lm3

        stosb
        call    prtchr
        jmp     lm3

lmbs:   cmpw    $bootsym(edit_buf), %di # Backspace
        jz      lm3

        decw    %di
        movb    $0x08, %al
        call    prtchr
        call    prtspc
        movb    $0x08, %al
        call    prtchr
        jmp     lm3
        
lment:  movb    $0, (%di)
        leaw    bootsym(crlft), %si
        call    prtstr
        leaw    bootsym(edit_buf), %si
        cmpb    $0, (%si)               # Empty string = default mode
        jz      lmdef

        cmpb    $0, 1(%si)              # One character = menu selection
        jz      mnusel

        cmpw    $0x656d, (%si)          # 'me'
        jnz     lmhx
        cmpw    $0x756e, 2(%si)         # 'nu'
        jnz     lmhx
        jmp     listm

lmhx:   xorw    %bx, %bx                # Else => mode ID in hex
lmhex:  lodsb
        orb     %al, %al
        jz      lmuse1

        subb    $0x30, %al
        jc      lmbad

        cmpb    $10, %al
        jc      lmhx1

        subb    $7, %al
        andb    $0xdf, %al
        cmpb    $10, %al
        jc      lmbad

        cmpb    $16, %al
        jnc     lmbad

lmhx1:  shlw    $4, %bx
        orb     %al, %bl
        jmp     lmhex

lmuse1: movw    %bx, %ax
        jmp     lmuse

mnusel: lodsb                           # Menu selection
        xorb    %ah, %ah
        subb    $0x30, %al
        jc      lmbad

        cmpb    $10, %al
        jc      lmuse
        
        cmpb    $0x61-0x30, %al
        jc      lmbad
        
        subb    $0x61-0x30-10, %al
        cmpb    $36, %al
        jnc     lmbad

lmuse:  call    mode_set
        jc      lmdef

lmbad:  leaw    bootsym(unknt), %si
        call    prtstr
        jmp     mode_menu
lmdef:  ret

_setrec:    jmp setrec                  # Ugly...
_set_80x25: jmp set_80x25

# Setting of user mode (AX=mode ID) => CF=success
mode_set:
        movw    %ax, bootsym(boot_vid_mode)
        movw    %ax, %bx

        cmpw    $VIDEO_VESA_BY_SIZE, %ax
        je      setvesabysize

        testb   $VIDEO_RECALC>>8, %ah
        jnz     _setrec

        cmpb    $VIDEO_FIRST_SPECIAL>>8, %ah
        jz      setspc

        cmpb    $VIDEO_FIRST_VESA>>8, %ah
        jnc     check_vesa

        orb     %ah, %ah
        jnz     setbad

        jmp     setmenu

setbad: clc
        ret

setspc: xorb    %bh, %bh                # Set special mode
        cmpb    $VIDEO_LAST_SPECIAL-VIDEO_FIRST_SPECIAL, %bl
        jnc     setbad
        
        addw    %bx, %bx
        jmp     *bootsym(spec_inits)(%bx)

setmenu:
        orb     %al, %al                # 80x25 is an exception
        jz      _set_80x25
        
        pushw   %bx                     # Set mode chosen from menu
        call    mode_table              # Build the mode table
        popw    %ax
        shlw    $3, %ax
        addw    %ax, %si
        cmpw    %di, %si
        jnc     setbad
        
        movw    (%si), %ax              # Fetch mode ID
        jmp     mode_set

check_vesa:
        leaw    vesa_glob_info, %di
        movw    $0x4f00, %ax
        int     $0x10
        cmpw    $0x004f, %ax
        jnz     setbad

        leaw    vesa_mode_info, %di
        subb    $VIDEO_FIRST_VESA>>8, %bh
        movw    %bx, %cx                # Get mode information structure
        movw    $0x4f01, %ax
        int     $0x10
        addb    $VIDEO_FIRST_VESA>>8, %bh
        cmpw    $0x004f, %ax
        jnz     setbad

        movb    (%di), %al              # Check mode attributes.
        andb    $0x99, %al
        cmpb    $0x99, %al
        jnz     _setbad                 # Doh! No linear frame buffer.

        subb    $VIDEO_FIRST_VESA>>8, %bh
        orw     $0x4000, %bx            # Use linear frame buffer
        movw    $0x4f02, %ax            # VESA BIOS mode set call
        int     $0x10
        cmpw    $0x004f, %ax            # AL=4f if implemented
        jnz     _setbad                 # AH=0 if OK

        movb    $1, bootsym(graphic_mode)  # flag graphic mode
        stc
        ret

_setbad: jmp    setbad                  # Ugly...

# Recalculate vertical display end registers -- this fixes various
# inconsistencies of extended modes on many adapters. Called when
# the VIDEO_RECALC flag is set in the mode ID.

setrec: subb    $VIDEO_RECALC>>8, %ah   # Set the base mode
        call    mode_set
        jnc     rct3

        movw    %gs:(0x485), %ax        # Font size in pixels
        movb    %gs:(0x484), %bl        # Number of rows
        incb    %bl
        mulb    %bl                     # Number of visible
        decw    %ax                     # scan lines - 1
        movw    $0x3d4, %dx
        movw    %ax, %bx
        movb    $0x12, %al              # Lower 8 bits
        movb    %bl, %ah
        outw    %ax, %dx
        movb    $0x07, %al              # Bits 8 and 9 in the overflow register
        call    inidx
        xchgb   %al, %ah
        andb    $0xbd, %ah
        shrb    %bh
        jnc     rct1
        orb     $0x02, %ah
rct1:   shrb    %bh
        jnc     rct2
        orb     $0x40, %ah
rct2:   movb    $0x07, %al
        outw    %ax, %dx
        stc
rct3:   ret

inidx:  outb    %al, %dx                # Read from indexed VGA register
        incw    %dx                    # AL=index, DX=index reg port -> AL=data
        inb     %dx, %al
        decw    %dx
        ret

setvesabysize:
        call    mode_table
        leaw    modelist,%si
1:      add     $8,%si
        cmpw    $ASK_VGA,-8(%si)        # End?
        je      _setbad
        movw    -6(%si),%ax
        cmpw    %ax,bootsym(vesa_size)+0
        jne     1b
        movw    -4(%si),%ax
        cmpw    %ax,bootsym(vesa_size)+2
        jne     1b
        movw    -2(%si),%ax
        cmpw    %ax,bootsym(vesa_size)+4
        jne     1b
        movw    -8(%si),%ax
        movw    %ax,%bx
        movw    %ax,bootsym(boot_vid_mode)
        jmp     check_vesa

# Table of routines for setting of the special modes.
spec_inits:
        .word   bootsym(set_80x25)
        .word   bootsym(set_8pixel)
        .word   bootsym(set_80x43)
        .word   bootsym(set_80x28)
        .word   bootsym(set_current)
        .word   bootsym(set_80x30)
        .word   bootsym(set_80x34)
        .word   bootsym(set_80x60)

# Set the 80x25 mode. If already set, do nothing.
set_80x25:
        movw    $0x5019, bootsym(force_size)  # Override possibly broken BIOS
use_80x25:
        movw    $0x1202, %ax            # Force 400 scan lines
        movb    $0x30, %bl
        int     $0x10
        movw    $0x0003, %ax            # Mode 3
        int     $0x10
        stc
        ret

# Set the 80x50/80x43 8-pixel mode. Simple BIOS calls.
set_8pixel:
        call    use_80x25               # The base is 80x25
set_8pt:
        movw    $0x1112, %ax            # Use 8x8 font
        xorb    %bl, %bl
        int     $0x10
        movw    $0x1200, %ax            # Use alternate print screen
        movb    $0x20, %bl
        int     $0x10
        movw    $0x1201, %ax            # Turn off cursor emulation
        movb    $0x34, %bl
        int     $0x10
        movb    $0x01, %ah              # Define cursor scan lines 6-7
        movw    $0x0607, %cx
        int     $0x10
        stc
        ret

# Set the 80x28 mode. This mode works on all VGA's, because it's a standard
# 80x25 mode with 14-point fonts instead of 16-point.
set_80x28:
        call    use_80x25               # The base is 80x25
set14:  movw    $0x1111, %ax            # Use 9x14 font
        xorb    %bl, %bl
        int     $0x10
        movb    $0x01, %ah              # Define cursor scan lines 11-12
        movw    $0x0b0c, %cx
        int     $0x10
set_current:
        stc
        ret

# Set the 80x43 mode. This mode is works on all VGA's.
# It's a 350-scanline mode with 8-pixel font.
set_80x43:
        movw    $0x1201, %ax            # Set 350 scans
        movb    $0x30, %bl
        int     $0x10
        movw    $0x0003, %ax            # Reset video mode
        int     $0x10
        jmp     set_8pt                 # Use 8-pixel font

# Set the 80x30 mode (all VGA's). 480 scanlines, 16-pixel font.
set_80x30:
        call    use_80x25               # Start with real 80x25
        movw    $0x3cc, %dx             # Get CRTC port
        inb     %dx, %al
        movb    $0xd4, %dl
        rorb    %al                     # Mono or color?
        jc      set48a

        movb    $0xb4, %dl
set48a: movw    $0x0c11, %ax            # Vertical sync end (also unlocks CR0-7)
        call    outidx
        movw    $0x0b06, %ax            # Vertical total
        call    outidx
        movw    $0x3e07, %ax            # (Vertical) overflow
        call    outidx
        movw    $0xea10, %ax            # Vertical sync start
        call    outidx
        movw    $0xdf12, %ax            # Vertical display end
        call    outidx
        movw    $0xe715, %ax            # Vertical blank start
        call    outidx
        movw    $0x0416, %ax            # Vertical blank end
        call    outidx
        pushw   %dx
        movb    $0xcc, %dl              # Misc output register (read)
        inb     %dx, %al
        movb    $0xc2, %dl              # (write)
        andb    $0x0d, %al              # Preserve clock select bits and color bit
        orb     $0xe2, %al              # Set correct sync polarity
        outb    %al, %dx
        popw    %dx
        movw    $0x501e, bootsym(force_size)
        stc                             # That's all.
        ret

# Set the 80x34 mode (all VGA's). 480 scans, 14-pixel font.
set_80x34:
        call    set_80x30               # Set 480 scans
        call    set14                   # And 14-pt font
        movw    $0xdb12, %ax            # VGA vertical display end
        movw    $0x5022, bootsym(force_size)
setvde: call    outidx
        stc
        ret

# Set the 80x60 mode (all VGA's). 480 scans, 8-pixel font.
set_80x60:
        call    set_80x30               # Set 480 scans
        call    set_8pt                 # And 8-pt font
        movw    $0xdf12, %ax            # VGA vertical display end
        movw    $0x503c, bootsym(force_size)
        jmp     setvde

# Write to indexed VGA register (AL=index, AH=data, DX=index reg. port)
outidx: outb    %al, %dx
        pushw   %ax
        movb    %ah, %al
        incw    %dx
        outb    %al, %dx
        decw    %dx
        popw    %ax
        ret

# Build the table of video modes (stored after the setup.S code at the
# `modelist' label. Each video mode record looks like:
#        .word        MODE-ID             (our special mode ID (see above))
#        .byte        rows                (number of rows)
#        .byte        columns             (number of columns)
# Returns address of the end of the table in DI, the end is marked
# with a ASK_VGA ID.
mode_table:
        movw    bootsym(mt_end), %di    # Already filled?
        orw     %di, %di
        jnz     mtab1

        leaw    modelist, %di           # Store standard modes:
        movw    $VIDEO_80x25,(%di)      # The 80x25 mode (ALL)
        movw    $0x50,2(%di)
        movw    $0x19,4(%di)
        movw    $0x00,6(%di)
        addw    $8,%di

        leaw    bootsym(vga_modes), %si # All modes for std VGA
        movw    $vga_modes_end-vga_modes, %cx
        rep     movsb

        call    vesa_modes              # Detect VESA VGA modes

        movw    $ASK_VGA, (%di)         # End marker
        movw    %di, bootsym(mt_end)
mtab1:  leaw    modelist, %si           # SI=mode list, DI=list end
ret0:   ret

# Modes usable on all standard VGAs
vga_modes:
        .word   VIDEO_80x50, 0x50,0x32,0        # 80x50
        .word   VIDEO_80x43, 0x50,0x2b,0        # 80x43
        .word   VIDEO_80x28, 0x50,0x1c,0        # 80x28
        .word   VIDEO_80x30, 0x50,0x1e,0        # 80x30
        .word   VIDEO_80x34, 0x50,0x22,0        # 80x34
        .word   VIDEO_80x60, 0x50,0x3c,0        # 80x60
vga_modes_end:

# Detect VESA modes.
vesa_modes:
        movw    %di, %bp                # BP=original mode table end
        leaw    vesa_glob_info, %di
        movw    $0x4f00, %ax            # VESA Get card info call
        int     $0x10
        movw    %di, %si
        movw    %bp, %di
        cmpw    $0x004f, %ax            # Successful?
        jnz     ret0
        
        cmpw    $0x4556, (%si)          # 'VE'
        jnz     ret0
        
        cmpw    $0x4153, 2(%si)         # 'SA'
        jnz     ret0
        
        movw    $bootsym(vesa_name), bootsym(card_name) # Set name to "VESA VGA"
        pushw   %gs
        lgsw    0xe(%si), %si           # GS:SI=mode list
        movw    $128, %cx               # Iteration limit
vesa1:
        gs;     lodsw
        cmpw    $0xffff, %ax            # End of the table?
        jz      vesar
        
        cmpw    $0x0080, %ax            # Check validity of mode ID
        jc      vesa2
        
        orb     %ah, %ah                # Valid IDs 0x0000-0x007f/0x0100-0x07ff
        jz      vesan                   # Certain BIOSes report 0x80-0xff!

        cmpw    $0x0800, %ax
        jnc     vesae

vesa2:  pushw   %cx
        movw    %ax, %cx                # Get mode information structure
        movw    $0x4f01, %ax
        int     $0x10
        movw    %cx, %bx                # BX=mode number
        addb    $VIDEO_FIRST_VESA>>8, %bh
        popw    %cx
        cmpw    $0x004f, %ax
        jnz     vesan                   # Don't report errors (buggy BIOSES)

        movb    (%di), %al              # Check capabilities.
        andb    $0x9b, %al              # LFB gfx mode in color?
        cmpb    $0x9b, %al
        jnz     vesan

        movw    %bx, (%di)              # Store mode number
        movw    0x12(%di), %bx          # Width
        movw    %bx, 2(%di)
        movw    0x14(%di), %bx          # Height
        movw    %bx, 4(%di)
        xorw    %bx, %bx
        movb    0x19(%di), %bl          # Depth
        movw    %bx, 6(%di)

        addw    $8, %di                 # The mode is valid. Store it.
vesan:  loop    vesa1                   # Next mode. Limit exceeded => error
vesae:  leaw    bootsym(vesaer), %si
        call    prtstr
        movw    %bp, %di                # Discard already found modes.
vesar:  popw    %gs
        ret

# Read a key and return the ASCII code in al, scan code in ah
getkey: xorb    %ah, %ah
        int     $0x16
        ret

# Read a key with a timeout of 30 seconds.
# The hardware clock is used to get the time.
getkt:  call    gettime
        addb    $30, %al                # Wait 30 seconds
        cmpb    $60, %al
        jl      lminute

        subb    $60, %al
lminute:
        movb    %al, %cl
again:  movb    $0x01, %ah
        int     $0x16
        jnz     getkey                  # key pressed, so get it

        call    gettime
        cmpb    %cl, %al
        jne     again

        movb    $0x20, %al              # timeout, return `space'
        ret

# Flush the keyboard buffer
flush:  movb    $0x01, %ah
        int     $0x16
        jz      empty
        
        xorb    %ah, %ah
        int     $0x16
        jmp     flush

empty:  ret

# Print hexadecimal number.
prthw:  pushw   %ax
        movb    %ah, %al
        call    prthb
        popw    %ax
prthb:  pushw   %ax
        shrb    $4, %al
        call    prthn
        popw    %ax
        andb    $0x0f, %al
prthn:  cmpb    $0x0a, %al
        jc      prth1

        addb    $0x07, %al
prth1:  addb    $0x30, %al
        jmp     prtchr

# Print decimal number in ax
prtdec: pushw   %ax
        pushw   %cx
        pushw   %dx
        xorw    %dx, %dx
        movw    $0x0a, %cx
        divw    %cx
        testw   %ax, %ax
        jz      skip10
        cmpw    $0x09, %ax
        jbe     lt100

        call    prtdec
        jmp     skip10

lt100:  addb    $0x30, %al
        call    prtchr
skip10: movb    %dl, %al
        addb    $0x30, %al
        call    prtchr        
        popw    %dx
        popw    %cx
        popw    %ax
        ret

# Routine to print asciiz string at ds:si
prtstr:
        lodsb
        andb    %al, %al
        jz      fin

        call    prtchr
        jmp     prtstr

fin:    ret

# Space printing
prtsp2: call    prtspc                  # Print double space
prtspc: movb    $0x20, %al              # Print single space (note: fall-thru)

# Part of above routine, this one just prints ascii al
prtchr: pushw   %ax
        pushw   %cx
        movw    $7,%bx
        movw    $0x01, %cx
        movb    $0x0e, %ah
        int     $0x10
        popw    %cx
        popw    %ax
        ret

beep:   movb    $0x07, %al
        jmp     prtchr

# Read the cmos clock. Return the seconds in al
gettime:
        pushw   %cx
        movb    $0x02, %ah
        int     $0x1a
        movb    %dh, %al                # %dh contains the seconds
        andb    $0x0f, %al
        movb    %dh, %ah
        movb    $0x04, %cl
        shrb    %cl, %ah
        aad
        popw    %cx
        ret

store_edid:
#ifdef CONFIG_FIRMWARE_EDID
        pushw   %ax
        pushw   %bx
        pushw   %cx
        pushw   %dx
        pushw   %di

        cmpb    $1, bootsym(opt_edid)   # EDID disabled on cmdline (edid=no)?
        je      .Lno_edid

        leaw    vesa_glob_info, %di
        movw    $0x4f00, %ax
        int     $0x10
        cmpw    $0x004f, %ax
        jne     .Lno_edid
        cmpw    $0x0200, 4(%di)         # only do EDID on >= VBE2.0
        jb      .Lno_edid

        xorw    %di, %di                # Report Capability
        pushw   %di
        popw    %es                     # ES:DI must be 0:0
        movw    $0x4f15, %ax
        xorw    %bx, %bx
        xorw    %cx, %cx
        int     $0x10
        pushw   %ds
        popw    %es
        cmpw    $0x004f, %ax            # Call failed?
        jne     .Lno_edid

        movw    %bx, bootsym(boot_edid_caps)

        cmpb    $2, bootsym(opt_edid)   # EDID forced on cmdline (edid=force)?
        je      .Lforce_edid

        /* EDID not forced on cmdline, so perform further sanity checks. */
        testb   $3,%bl                  # No DDC capabilities?
        jz      .Lno_edid
        cmpb    $5,%bh                  # Longer than 5s to read EDID?
        ja      .Lno_edid

.Lforce_edid:
        movw    $0x4f15, %ax            # do VBE/DDC
        movw    $0x01, %bx
        movw    $0x00, %cx
        movw    $0x00, %dx
        movw    $bootsym(boot_edid_info), %di
        int     $0x10

.Lno_edid:
        popw    %di                     # restore all registers
        popw    %dx
        popw    %cx
        popw    %bx
        popw    %ax
#endif
        ret

opt_edid:       .byte   0       # EDID parsing option (force/no/default)
mt_end:         .word   0       # End of video mode table if built
edit_buf:       .space  6       # Line editor buffer
card_name:      .word   0       # Pointer to adapter name
graphic_mode:   .byte   0       # Graphic mode with a linear frame buffer
dac_size:       .byte   6       # DAC bit depth

# Status messages
keymsg:         .ascii  "Press <RETURN> to see video modes available,"
                .byte   0x0d, 0x0a
                .ascii  "<SPACE> to continue or wait 30 secs"
                .byte   0x0d, 0x0a, 0

listhdr:        .byte   0x0d, 0x0a
                .ascii  "MODE-KEY  MODE-ID  WIDTHxHEIGHTxDEPTH"

crlft:          .byte   0x0d, 0x0a, 0

prompt:         .byte   0x0d, 0x0a
                .asciz  "Enter mode number or 'menu': "

unknt:          .ascii  "Unknown mode ID. Try again."
                .byte   0x0d, 0x0a, 0

badmdt:         .ascii  "You passed an undefined mode number."
                .byte   0x0d, 0x0a, 0

vesaer:         .ascii  "Error: Scanning of VESA modes failed. Please "
                .ascii  "report to <mj@ucw.cz>."
                .byte   0x0d, 0x0a, 0

textmode:       .asciz  " (text)"
menu_more_msg:  .asciz  "<press SPACE for more>"
menu_bail_msg:  .ascii  "<menu truncated>"
                .byte   0x0d, 0x0a, 0

svga_name:      .ascii  " "

vga_name:       .asciz  "VGA"

vesa_name:      .asciz  "VESA"

name_bann:      .asciz  "Video adapter: "

force_size:     .word   0       # Use this size instead of the one in BIOS vars

vesa_size:      .word   0,0,0   # width x depth x height

/* If we don't run at all, assume basic video mode 3 at 80x25. */
GLOBAL(boot_vid_mode)
        .word   VIDEO_80x25
GLOBAL(boot_vid_info)
        .byte   0, 0    /* orig_x, orig_y */
        .byte   3       /* text mode 3    */
        .byte   80, 25  /* 80x25          */
        .byte   1       /* isVGA          */
        .word   16      /* 8x16 font      */
        .fill   0x28,1,0
GLOBAL(boot_edid_info)
        .fill   128,1,0x13
GLOBAL(boot_edid_caps)
        .word   0x1313