summaryrefslogtreecommitdiff
path: root/sgabios.S
blob: 96c4e2310ab8e12936c55eeee6b565d3c688658f (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
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sgabios.h"
#define BUILD_CL  "$Id$"

.code16
.text
  .section ".init","ax"
  .globl _start
  .type _start,@object
_start:
  /* option rom header */
  .byte 0x55
  .byte 0xaa
  .byte _rom_size_byte
  .size _start, .-_start

  .globl legacy_entry
  .type legacy_entry,@function
legacy_entry:
  jmp sga_init
  /* pnp entry here to avoid changing PnP table as code moves */
pnp_init:
  jmp pnp_sga_init

/*
 * do_old_int10h
 *
 * Patched at option rom init to be a far jump to old int 10h isr
 *
 */
do_old_int10h:
  .byte 0xea                           /* jmp absolute segment:offset */
old_int10h:                            /* store what was at offset 0x40 */
  .word 0xf065                         /* placeholder for chained ISR offset */
  /* if the chained segment is detected as 0xc000, use 80 cols only */
  /* since it's assumed that a vga card is attached and 80 cols max */
old_int10h_seg:
  .word 0xf000                         /* placeholder for chained ISR segment */
/*
 * do_old_int16h
 *
 * Patched at option rom init to be a far jump to old int 16h isr
 *
 */
do_old_int16h:
  .byte 0xea                           /* jmp absolute segment:offset */
old_int16h:                            /* store what was at offset 0x58 */
  .word 0xe82e                         /* placeholder for chained ISR offset */
  .word 0xf000                         /* placeholder for chained ISR segment */
.org 0x18
  .word 0                              /* offset to PCI data, 0 = none */
  .word pnp_table                      /* offset to PnP expansion header */
.org 0x20
pnp_table:
  /* FIXME: **** PnP header currently disabled by PoO **** */
  /* legacy entry only called once, PnP entry called multiple times */
  /* The code isn't yet written to deal with multiple inits properly */
  .ascii "$PoO"                        /* PnP expansion header signature */
  .byte 1                              /* structure revision */
  .byte 2                              /* length in 16-byte increments */
  .word 0                              /* offset of next header, 0 if none */
  .byte 0                              /* reserved */
  .byte 0x52                           /* checksum - update manually! FIXME */
  .long 0                              /* device identifier */
  .word mfg_string                     /* pointer to manufacturer string */
  .word prod_string                    /* pointer to product name string */
  .byte 3, 0x80, 0x80                  /* device type code = other display */
  .byte 0xe3                           /* device indicators, kbd/display dev */
  .word 0                              /* boot connection vector, 0 if none */
  .word 0                              /* disconnect vector, 0 if none */
  .word pnp_init                       /* bootstrap entry vector */
  .word 0                              /* reserved */
  .word 0                              /* static resource information vector */

  /* WARNING: changing mfg_string / prod_string locations will */
  /* affect pnp table above -- recalculate checksum manually! */
mfg_string:
  .asciz "Google, Inc."
prod_string:
  .ascii "Serial Graphics Adapter "
build_date:
  .asciz BUILD_SHORT_DATE
long_version:
  .ascii "SGABIOS "
  .ascii BUILD_CL
  .ascii " ("
  .ascii BUILD_USER
  .ascii "@"
  .ascii BUILD_HOST
  .ascii ") "
  .asciz BUILD_DATE
term_cols:
  .byte 80             /* overwritten at rom init with detected value */
term_rows:
  .byte 24             /* overwritten at rom init with detected value */
term_init_string:      /* terminal reply: \033[n;mR n=row, m=col */
  .asciz "\033[1;256r\033[256;256H\033[6n"
  /* reset the scroll, move to col 256, row 256, ask current position */
  /* bios cursor positions >255 rows or cols can't be used anyway */
term_info:
  .asciz "Term: "
ebda_info:
  .asciz "EBDA: "

/*
 * do_old_irq3 - exception 0x0b, int 0x0a
 *
 * Patched at option rom init to be a far jump to old irq 3 isr
 *
 */
do_old_irq3:
  .byte 0xea                           /* jmp absolute segment:offset */
old_irq3:          /* store what was at offset 0x28 */
  .word 0xeef3                         /* placeholder for chained ISR offset */
  .word 0xf000                         /* placeholder for chained ISR segment */

/*
 * do_old_irq4 - exception 0x0c, int 0x0b
 *
 * Patched at option rom init to be a far jump to old irq 4 isr
 *
 */
do_old_irq4:
  .byte 0xea                           /* jmp absolute segment:offset */
old_irq4:          /* store what was at offset 0x2c */
  .word 0xeef3                         /* placeholder for chained ISR offset */
  .word 0xf000                         /* placeholder for chained ISR segment */

/*
 * do_old_int14h
 *
 * Patched at option rom init to be a far jump to old int 14h isr
 *
 */
do_old_int14h:
  .byte 0xea                           /* jmp absolute segment:offset */
old_int14h:                            /* store what was at offset 0x50 */
  .word 0xe739                         /* placeholder for chained ISR offset */
  .word 0xf000                         /* placeholder for chained ISR segment */

.align 16, 0xff    /* aligning this table only makes hexdump prettier */
/* ascii -> scancode, bit 7=shifted, char < 32 = +ctrl */
/* except chars 8, 9, 13, 27 (bs, tab, enter, esc) */
/* most int16h consumers will probably never use */
ascii2scan:
/*00*/  .byte 0x00, 0x1e, 0x30, 0x2e, 0x20, 0x12, 0x21, 0x22
/*08*/  .byte 0x0e, 0x17, 0x24, 0x25, 0x26, 0x1c, 0x31, 0x18
/*10*/  .byte 0x19, 0x0f, 0x13, 0x1f, 0x14, 0x16, 0x2f, 0x11
/*18*/  .byte 0x2d, 0x15, 0x2c, 0x01, 0x2b, 0x1b, 0x87, 0x8c
/*20*/  .byte 0x39, 0x82, 0xa8, 0x84, 0x85, 0x86, 0x88, 0x28
/*28*/  .byte 0x8a, 0x8b, 0x89, 0x8d, 0x33, 0x0c, 0x34, 0x35
/*30*/  .byte 0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
/*38*/  .byte 0x09, 0x0a, 0xa7, 0x27, 0xb3, 0x0d, 0x34, 0xb5
/*40*/  .byte 0x83, 0x9e, 0xb0, 0xae, 0xa0, 0x92, 0xa1, 0xa2
/*48*/  .byte 0xa3, 0x97, 0xa4, 0xa5, 0xa6, 0xb2, 0xb1, 0x98
/*50*/  .byte 0x99, 0x90, 0x93, 0x9f, 0x94, 0x96, 0xaf, 0x91
/*58*/  .byte 0xad, 0x95, 0xac, 0x1a, 0x2b, 0x1b, 0x87, 0x8c
/*60*/  .byte 0x29, 0x1e, 0x30, 0x2e, 0x20, 0x12, 0x21, 0x22
/*68*/  .byte 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18
/*70*/  .byte 0x19, 0x10, 0x13, 0x1f, 0x14, 0x16, 0x2f, 0x11
/*78*/  .byte 0x2d, 0x15, 0x2c, 0x9a, 0xab, 0x9b, 0xa9, 0x0e

/* TABLES FOR NON-ASCII VGA CHARACTERS (CP437) TO ASCII */
/* Unicode at: http://en.wikipedia.org/wiki/Code_page_437 */

ctrl2ascii:
/* translate vga (CP437) first 32 characters to ascii */
/* for char 0, update the cursor position, but output nothing */
/* lilo uses this "trick" for a background attribute update */
  .ascii "\0@@v***........*><|!PS-|^v><L-^v"
high2ascii:
/* translate vga (CP437) chars 0x80 to 0xff to ascii */
/* these characters are mostly to visually approximate */
/* line art characters will probably need tweaking */
/*80*/  .ascii "CueaaaaceeeiiiAAEaAooouuyOUcLYPf"
/*a0*/  .ascii "aiounNao?--24!<>###||||++||+++++"
/*c0*/  .ascii "+--|-+||++--|-+----++++++++#-||-"
/*e0*/  .ascii "abgpesut00osiye^=+><||-=...vn2* "

colortable:
/* vga text color is IRGB, ansi color is BGR */
/* this table is effectively a nibble bit-reverse */
  .byte 0, 4, 2, 6, 1, 5, 3, 7

serial_port_base_address:
  .word COM_BASE_ADDR

/* in-memory console log
 *
 * It's expected that the EBDA contains a magic signature
 * like 0xdeadbabe, followed by a byte of flags, followed
 * by a 32-bit buffer pointer, followed by a 16-bit start
 * index, followed by a 16-bit end index, followed by 16-
 * bit logged character count, followed by an 8-bit flag.
 */

#define MEMCONSOLE_BUFFER_SIZE  32768
#define MEMCONSOLE_SIGNATURE  0xdeadbabe
#define MEMCONSOLE_ENDINDEX_OFF  0x0b
#define SGABIOS_EBDA_SIGNATURE  0x00414753

memconsole_buffer_start:               /* pulled from ebda struct */
  .long  0x00000000                    /* 0 = not found/no logging */
memconsole_ebda_deadbabe_offset:       /* bytes from start of ebda */
  .word  0x0000                        /* 40:0e contains ebda seg */
sgabios_ebda_logbuf_offset:            /* bytes from start of ebda */
  .word  0x0000                        /* 40:0e contains ebda seg */

/*
 * setup_memconsole
 *
 * Initialize the option rom variables associated with logging
 * of the legacy console output
 *
 * If these variables are left at zero, no logging will occur
 *
 * There are no parameters
 * All registers except flags should be preserved
 */

setup_memconsole:
  pushaw
  pushw %ds
  pushw %es
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  pushw BDA_EBDA                       /* push word at 0x0e */
  popw %es                             /* es = EBDA_SEG */
  /* search for memconsole signature in ebda */
  movl $MEMCONSOLE_SIGNATURE, %eax
  xorw %di, %di                        /* start at zero */
  movzbw %es:(%di), %cx                /* cx = size of EBDA in KB */
  shlw $8, %cx                         /* cx = (cx * 1024) / 4 */
  cld
  repnz
  scasl                                /* search until sig found */
  subw $4, %di                         /* scasl always increments di, undo */
  cmpl %eax, %es:(%di)                 /* is signature here? */
  jnz setup_memconsole_end             /* bail if so */
  movw %di, %cs:memconsole_ebda_deadbabe_offset  /* save offset */
  movl %es:5(%di), %eax                /* get 32-bit buffer base address */
  movl %eax, %cs:memconsole_buffer_start
setup_memconsole_end:
  popw %es
  popw %ds
  popaw
  ret

/*
 * memconsole_log_char
 *
 * Log the character passed in %al to the next available memory
 * console log position, if any.
 *
 * If memconsole_buffer_start is zero, no logging will occur
 *
 * %al = character to be logged
 * All registers except flags should be preserved
 */

memconsole_log_char:
  pushaw
  pushw %ds
  pushw %es
  pushw %fs
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  pushw BDA_EBDA                       /* push word at 0x0e */
  popw %es                             /* es = EBDA_SEG */
  movw %ax, %si                        /* %si = %al = byte to write */
  movl %cs:memconsole_buffer_start, %ebp
  movw %cs:memconsole_ebda_deadbabe_offset, %di
  addw $MEMCONSOLE_ENDINDEX_OFF, %di   /* %di points to char pos */
  orl %ebp, %ebp
  jz memconsole_log_tail               /* bufptr==0, no logging */
  movw %es:(%di), %bx                  /* bx = current position in buffer */
  cmpw $MEMCONSOLE_BUFFER_SIZE, %bx    /* at end of buffer? */
  jnc memconsole_log_tail              /* don't log any more if so */
  cmpb $0xd, %al                       /* is the char CR? */
  jz memconsole_log_tail               /* if so, ignore it */
  cmpb $0x8, %al                       /* is the char backspace? */
  jnz memconsole_update_fsbase         /* if not, log char as usual... */
  orw %bx, %bx                         /* make sure ptr isn't already zero */
  jz memconsole_log_tail               /* if so, bail */
  decw %bx                             /* else point to previous character */
  jmp memconsole_update_end_ptr        /* and go directly to save it */
memconsole_update_fsbase:
  movl $0xc0000100, %ecx               /* ecx = IA32_FS_BASE (AMD64+) */
  rdmsr                                /* read what was there before */
  pushl %eax                           /* save away previous FS_BASE eax */
  pushl %edx                           /* save away previous FS_BASE edx */
  xorl %edx, %edx                      /* clear high 32 bits */
  movl %ebp, %eax                      /* eax = memconsole buffer start */
  wrmsr                                /* fs_base = memconsole buffer start */
  movw %si, %ax                        /* %ax = saved value on entry */
  movb %al, %fs:(%bx)                  /* log character */
  popl %edx                            /* restore previous FS_BASE edx */
  popl %eax                            /* restore previous FS_BASE eax */
  wrmsr                                /* write what was there before */
  incw %bx                             /* update character count */
memconsole_update_end_ptr:
  movw %bx, %es:(%di)                  /* save new end pointer */
  addw $2, %di                         /* numchars stored at next word */
  movw %bx, %es:(%di)                  /* save new numchar value */
memconsole_log_tail:
  popw %fs
  popw %es
  popw %ds
  popaw
  ret

/* sgabioslog_setup_ebda
 *
 * SGABIOS makes its own 1KB EBDA allocation to save non-
 * translated characters with associated cursor positions
 * for the last 256 characters output.  This is organized
 * with 256 bytes reserved for houskeeping, 256 bytes for
 * the raw character codes, and 512 bytes of 16bit cursor
 * positions to record the associated position for each.
 *
 * The first 4 bytes contain "SGA\0" followed by a 16-bit
 * size of the allocation in bytes,  followed by a 16-bit
 * index indicating the next spot to be overwritten.
 * 
 * There are no parameters
 * All registers should be preserved
 */

sgabioslog_setup_ebda:
  pushf
  pushaw
  pushw %ds
  pushw %es
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movw BDA_EBDA, %ax                   /* ax = old ebda segment from 0x0e */
  subw $SGABIOS_EBDA_DELTA, %ax
  movw %ax, %es                        /* es = new EBDA segment start */
  cmpw $EBDA_MIN_SEG, %ax              /* is there room for the allocation? */
  jc sgabioslog_setup_ebda_tail        /* if not, don't change anything */
  cli                                  /* paranoid in case irq uses EBDA */
  movw %ax, BDA_EBDA                   /* save new EBDA segment start */
  subw $SGABIOS_EBDA_KB, BDA_MEM_SIZE  /* subtract extra allocation */
  movw %ax, %ds                        /* ds = new EBDA segment start */
  movw $SGABIOS_EBDA_BYTES, %si        /* si = offset of first byte to move */
  movzbw (%si), %cx                    /* cx = number of KB in EBDA */
  addb $SGABIOS_EBDA_KB, (%si)         /* update EBDA size in kb */
  shlw $10, %cx                        /* cx = KB * 1024 = bytes in EBDA */
  movw %cx, %cs:sgabios_ebda_logbuf_offset  /* new ebda space */
  xorw %di, %di                        /* di = new EBDA start */
  cld
  rep
  movsb                                /* move ebda by SGABIOS_EBDA_BYTES */
  movw %cs:sgabios_ebda_logbuf_offset, %bx  /* bx = new buffer */
  movl $SGABIOS_EBDA_SIGNATURE, (%bx)  /* setup signature */
  movw $SGABIOS_EBDA_BYTES, 4(%bx)     /* bytes in new ebda buffer */
  movw $0, 6(%bx)                      /* next log index, new ebda buffer */
sgabioslog_setup_ebda_tail:
  popw %es
  popw %ds
  popaw
  popf
  ret

/*
 * sgabioslog_save_char
 *
 * Like memconsole_log_char, except the original, untranslated
 * character is expected to be given in the %al register.
 *
 * The original character and its corresponding cursor position
 * are logged to the sgabios ebda memory allocation.
 *
 * %al = character to be logged
 * All registers except flags should be preserved
 */

sgabioslog_save_char:
  pushaw
  pushw %ds
  pushw %es
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  pushw BDA_EBDA                       /* push word at 0x0e */
  popw %es                             /* es = EBDA_SEG */
  movw %cs:sgabios_ebda_logbuf_offset, %di
  orw %di, %di                         /* is offset zero? */
  jz sgabioslog_save_tail              /* if so, bail */
  cmpl $SGABIOS_EBDA_SIGNATURE, %es:(%di)
  jnz sgabioslog_save_tail             /* bail if magic not found */
  movw %es:6(%di), %bx                 /* bx = index of next char output */
  movb %al, %es:SGABIOS_EBDA_LOG_START(%bx,%di)  /* store character */
  movzbw %bl, %ax                      /* %ax = next cursor buffer index */
  shlw $1, %ax                         /* %ax = offset to cursor storage */
  call get_current_cursor              /* %dh = row, %dl = column */
  addw $SGABIOS_EBDA_POS_START, %di    /* cursor storage */
  addw %ax, %di                        /* %di = next cursor storage offset */
  movw %dx, %es:(%di)                  /* save position for logged char */
  incw %bx                             /* point to next char to log */
  cmpw $SGABIOS_EBDA_LOG_SIZE, %bx
  jnz sgabioslog_save_index
  xorw %bx, %bx                        /* wrap around to start */
sgabioslog_save_index:
  movw %cs:sgabios_ebda_logbuf_offset, %di
  movw %bx, %es:6(%di)                 /* save new index */
sgabioslog_save_tail:
  popw %es
  popw %ds
  popaw
  ret

/*
 * sgabioslog_get_char
 *
 * Return the character at current cursor position, last recorded
 * to sgabios ebda allocation, if available.
 *
 * If the current cursor postition contains one of the last 256 characters
 * written to the ebda buffer, return that character, else return 0.
 *
 * If sgabios_ebdda_logbuf_offset is zero, %al will be 0 and zf set
 *
 * All registers except flags and %al should be preserved
 */

sgabioslog_get_char:
  pushaw
  movw %sp, %bp
  movb $0, 14(%bp)                     /* %al on stack = 0 */
  pushw %ds
  pushw %es
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  pushw BDA_EBDA                       /* push word at 0x0e */
  popw %es                             /* es = EBDA_SEG */
  movw %cs:sgabios_ebda_logbuf_offset, %di
  orw %di, %di
  jz sgabioslog_get_tail               /* offset==0, no logging */
  cmpl $SGABIOS_EBDA_SIGNATURE, %es:(%di)
  jnz sgabioslog_get_tail              /* bail if magic not found */
  call get_current_cursor              /* dh = row, dl = col */
  std                                  /* scan backwards in mem */
  movw %es:6(%di), %bx                 /* bx = index of next char output */
  decw %bx                             /* %bx = offset of last char in buf */
  jnc sgabioslog_got_pos
  addw $SGABIOS_EBDA_LOG_SIZE, %bx     /* bx position wrap around */
sgabioslog_got_pos:
  movw %bx, %ax                        /* %ax = last cursor pos written */
  shlw $1, %ax                         /* %ax = offset of last cursor pos */
  addw $SGABIOS_EBDA_POS_START, %di    /* %di = first cursor position */
  addw %ax, %di                        /* %di = offset in ebda */
  movw %dx, %ax                        /* %ax = cursor pos to compare */
  movw %bx, %cx                        /* %cx = positions before wrap */
  jcxz sgabioslog_cmp_wrap             /* if zero, try from end next */
  repnz
  scasw                                /* search until position match */
  addw $2, %di                         /* scasd always decrements di, undo */
  cmpw %ax, %es:(%di)                  /* did it really match? */
  jz sgabioslog_cursor_match           /* if so, do something */
sgabioslog_cmp_wrap:
  movw %cs:sgabios_ebda_logbuf_offset, %di
  addw $SGABIOS_EBDA_POS_LAST, %di     /* %di = last cursor storage */
  movw $SGABIOS_EBDA_LOG_SIZE, %cx     /* %cx = compare all positions */
  repnz
  scasw                                /* search until position match */
  addw $2, %di                         /* scasd always decrements di, undo */
  cmpw %ax, %es:(%di)                  /* did it really match? */
  jnz sgabioslog_get_tail              /* if not, bail */
sgabioslog_cursor_match:
  /* %di contains the EBDA offset of the matching position */
  /* convert this into a memconsole offset */
  subw $512, %di                       /* take off the storage offset */
  subw %cs:sgabios_ebda_logbuf_offset, %di  /* and ebda offset */
  shrw $1, %di                         /* %di = char position index */
  addw %cs:sgabios_ebda_logbuf_offset, %di  /* add back ebda offset */
  addw $SGABIOS_EBDA_LOG_START, %di    /* and add back log offset */
  movb %es:(%di), %al                  /* get related saved character */
  movb %al, 14(%bp)                    /* %al on stack = logged char */
sgabioslog_get_tail:
  popw %es
  popw %ds
  popaw
  ret

/*
 * multibyteinput
 *
 * When an escape key is detected, the input routines will attempt to
 * capture as many characters as arrive up until a timeout, or six,
 * whichever is less.
 *
 * This table is intended to decide what the characters after the
 * initial escape key translate to in terms of high and low bytes
 * that go into the keyboard buffer the high byte is the scancode,
 * the low byte is ascii, but for special keys this is usually 0xe0
 * or 0x00.
 *
 * This table is formatted so that the first word is a scancode +
 * ascii pair (as returned by int 16h, ah = 10h or 11h).  Immediately
 * following is a nul-terminated ascii string to match in order to
 * use the corresponding scancode+ascii word.
 *
 * The search through this table is terminated by a match or finding
 * a 0 scancode+ascii word.
 *
 * FIXME: all the low bytes are now zero, get rid of them?
 */
multibyteinput:
  .byte 0x3b                           /* F1 */
  .asciz "[[A"                         /* F1/screen */

  .byte 0x3b                           /* F1 */
  .asciz "OP"                          /* F1/xterm/ansi */

  .byte 0x3b                           /* F1 */
  .asciz "[11~"                        /* F1/vt400 */

  .byte 0x3c                           /* F2 */
  .asciz "[[B"                         /* F2/screen */

  .byte 0x3c                           /* F2 */
  .asciz "OQ"                          /* F2/xterm/ansi */

  .byte 0x3c                           /* F2 */
  .asciz "[12~"                        /* F2/vt400 */

  .byte 0x3d                           /* F3 */
  .asciz "[[C"                         /* F3/screen */

  .byte 0x3d                           /* F3 */
  .asciz "OR"                          /* F3/xterm/ansi */

  .byte 0x3d                           /* F3 */
  .asciz "[13~"                        /* F3/vt400 */

  .byte 0x3e                           /* F4 */
  .asciz "[[D"                         /* F4/screen */

  .byte 0x3e                           /* F4 */
  .asciz "OS"                          /* F4/xterm/ansi */

  .byte 0x3e                           /* F4 */
  .asciz "[14~"                        /* F4/vt400 */

  .byte 0x3f                           /* F5 */
  .asciz "[[E"                         /* F5/screen */

  .byte 0x3f                           /* F5 */
  .asciz "[15~"                        /* F5/xterm */

  .byte 0x3f                           /* F5 */
  .asciz "OT"                          /* F5/ansi */

  .byte 0x40                           /* F6 */
  .asciz "[17~"                        /* F6/screen/vt220/xterm/vt400 */

  .byte 0x40                           /* F6 */
  .asciz "OU"                          /* F6/ansi */

  .byte 0x41                           /* F7 */
  .asciz "[18~"                        /* F7/screen/vt220/xterm/vt400 */

  .byte 0x41                           /* F7 */
  .asciz "OV"                          /* F7/ansi */

  .byte 0x42                           /* F8 */
  .asciz "[19~"                        /* F8/screen/vt220/xterm/vt400 */

  .byte 0x42                           /* F8 */
  .asciz "OW"                          /* F8/ansi */

  .byte 0x43                           /* F9 */
  .asciz "[20~"                        /* F9/screen/vt220/xterm/vt400 */

  .byte 0x43                           /* F9 */
  .asciz "OX"                          /* F9/ansi */

  .byte 0x44                           /* F10 */
  .asciz "[21~"                        /* F10/screen/vt220/xterm/vt400 */

  .byte 0x44                           /* F10 */
  .asciz "OY"                          /* F10/ansi */

  .byte 0x85                           /* F11 */
  .asciz "[23~"                        /* F11/screen/xterm/vt400 */

  .byte 0x85                           /* F11 */
  .asciz "OZ"                          /* F11/ansi */

  .byte 0x86                           /* F12 */
  .asciz "[24~"                        /* F12/screen/xterm/vt400 */

  .byte 0x52                           /* Insert */
  .asciz "[2~"                         /* Insert/screen/vt102/xterm */

  .byte 0x53                           /* Delete */
  .asciz "[3~"                         /* Delete/screen/vt102/xterm */

  .byte 0x4b                           /* Left */
  .asciz "OD"                          /* Left/screen/vt102 */

  .byte 0x4b                           /* Left */
  .asciz "[D"                          /* Left/xterm */

  .byte 0x47                           /* Home */
  .asciz "[1~"                         /* Home/screen/vt102 */

  .byte 0x47                           /* Home */
  .asciz "[H"                          /* Home/xterm */

  .byte 0x4f                           /* End */
  .asciz "[4~"                         /* End/screen/vt102 */

  .byte 0x4f                           /* End */
  .asciz "[F"                          /* End/xterm */

  .byte 0x48                           /* Up */
  .asciz "OA"                          /* Up/screen/vt102 app */

  .byte 0x48                           /* Up */
  .asciz "[A"                          /* Up/xterm/vt102 ansi */

  .byte 0x50                           /* Down */
  .asciz "OB"                          /* Down/screen/vt102 app */

  .byte 0x50                           /* Down */
  .asciz "[B"                          /* Down/xterm/vt102 ansi */

  .byte 0x49                           /* PageUp */
  .asciz "[5~"                         /* PageUp/screen/vt102/xterm */

  .byte 0x51                           /* PageDown */
  .asciz "[6~"                         /* PageDown/screen/vt102/xterm */

  .byte 0x4d                           /* Right */
  .asciz "OC"                          /* Right/screen/vt102 app */

  .byte 0x4d                           /* Right */
  .asciz "[C"                          /* Right/xterm/vt102 ansi */

  .byte 0                              /* end of table marker */

/* init_serial_port
 *
 * Initialize serial port to 115200,8n1
 * Serial interrupts disabled
 *
 * All registers except flags preserved
 */

init_serial_port:
  pushw %ax
  pushw %dx
  pushw %bx
  movw %cs:serial_port_base_address, %dx
  addw $IER_OFFSET, %dx
  xorb %al, %al
  outb %al, %dx                        /* disable all serial interrupts */
  addw $(LCR_OFFSET - IER_OFFSET), %dx /* LCR */
  movb $(LCR_VALUE|LCR_DLAB), %al
  outb %al, %dx                        /* enable divisor access */
  movw %cs:serial_port_base_address, %dx
  movw $(PORT_DIVISOR/PORT_SPEED), %bx
  movb %bl, %al                        /* al = lsb of divisor */
  outb %al, %dx                        /* set divisor latch lsb */
  movb %bh, %al                        /* al = msb of divisor */
  incw %dx
  outb %al, %dx                        /* set divisor latch msb */
  movw %cs:serial_port_base_address, %dx
  addw $LCR_OFFSET, %dx
  movb $LCR_VALUE, %al
  outb %al, %dx                        /* disable divisor access */
  addw $(MCR_OFFSET - LCR_OFFSET), %dx /* MCR */
  movb $MCR_DTRRTS, %al
  outb %al, %dx                        /* enable DTR + RTS */
  movw %cs:serial_port_base_address, %dx
  addw $FCR_OFFSET, %dx
  movb $FCR_FIFO_ENABLE, %al
  outb %al, %dx                        /* enable FIFOs */
  popw %bx
  popw %dx
  popw %ax
  ret


/* get_serial_lsr
 *
 * return serial line status register in %al
 * return offset to serial port line status register io port in %dx
 * all other registers except flags unchanged
 *
 * if status == 0xff  return ZF=1, else return ZF=0
 */

get_serial_lsr:
  movw %cs:serial_port_base_address, %dx
  addw $LSR_OFFSET, %dx
  inb %dx, %al
  cmpb $0xff, %al
  ret
  
/*
 * get_byte
 *
 * get serial byte in %al, scancode in %ah [FIXME: EFI console input]
 *
 * all registers except %ax preserved
 *
 */

get_byte:
  pushw %dx
  pushw %bx
next_serial_char:
  call get_serial_lsr                  /* get serial lsr in %al */
  jz get_byte_tail                     /* no port present... */
  testb $1, %al                        /* bit 0 of LSR = 1 = data available */
  jz get_byte_tail                     /* no input waiting */
  /* new character found on serial port */
  /* convert it to a scancode */
  movw %cs:serial_port_base_address, %dx
  inb %dx, %al                         /* al = serial input char */
  testb $0x80, %al                     /* non-ascii char received? */
  jnz next_serial_char                 /* throw char away */
  movb %al, %dl                        /* dl = character read */
  pushw %ds
  pushw %cs
  popw %ds                             /* ds = cs */
  movw $ascii2scan, %bx                /* table to translate ascii->scan */
  xlatb                                /* translate char to scancode */
  popw %ds
  /* shift status is ignored at this point, may be used later */
  andb $0x7f, %al                      /* strip shift status from table */
  movb %al, %ah                        /* scancode goes in high byte */
  movb %dl, %al                        /* "translated" ascii in lower byte */
  cmpb $0x7f, %al                      /* Did the user transmit ascii DEL? */
  jnz get_byte_not_del                 /* if not, don't do anything to al */
  movb $0x08, %al                      /* else delete becomes backspace */
get_byte_not_del:
  testw %ax, %ax                       /* clear zero flag */
get_byte_tail:
  popw %bx
  popw %dx
  ret

/*
 * poll_byte
 *
 * get serial byte in %al, scancode in %ah [FIXME: EFI console input]
 * retry up to 65536 times for an expected input byte
 *
 * all registers except %ax preserved
 *
 */

poll_byte:
  pushw %cx
  xorw %cx, %cx
poll_byte_retry:
  inb $0xed, %al
  call get_byte
  loopz poll_byte_retry                /* repeat while zf set or cx != 0 */
  popw %cx
  ret

/*
 * get_multibyte
 *
 * after an escape character, poll for terminal keys that generate
 * an escape code plus multiple bytes (up to four).
 *
 * if no byte is waiting, all registers preserved except flags
 * if more bytes are waiting, all registers preserved except %ax and flags
 *
 */
get_multibyte:
  pushw %bp                            /* bp points to temp buffer on stack */
  pushw %bx                            /* bx points to multibyteinput table */
  pushw %cx                            /* cx will count chars */
  pushw %ax                            /* ax will receive chars */
  pushl $0                             /* make space on stack for 4 chars */
  xorw %cx, %cx                        /* cx = 0 */
  movw %sp, %bp                        /* point bp at temp data */
  call poll_byte                       /* is a character waiting? */
  jz get_multibyte_tail                /* if not, bail */
get_multibyte_store:
  movb %al, (%bp)                      /* store char received */
  incb %cl                             /* mark one char received */
  incw %bp                             /* point to next char */
  cmpb $4, %cl                         /* got enough chars? */
  jz got_multibyte                     /* no strings longer than 4 chars */
  call poll_byte                       /* is another char waiting? */
  jnz get_multibyte_store              /* store a new one if it's there */
got_multibyte:
  movw $multibyteinput, %bx            /* point to first scancode */
got_multibyte_findkey:
  movw %sp, %bp                        /* bp = start of buffer */
  movb %cs:(%bx), %ah                  /* ah = scancode */
  incw %bx                             /* bx = start of test string */
  orb %ah, %ah                         /* is it zero? */
  jz get_multibyte_tail                /* if so, bail, key not found */
got_multibyte_nextchar:
  movb %cs:(%bx), %ch                  /* ch = test char to compare */
  incw %bx                             /* point to next char */
  orb %ch, %ch                         /* is char to compare NUL? */
  jz got_multibyte_key                 /* matched to end of a string! */
  cmpb %ch, (%bp)                      /* input tmp buf equal to test char? */
  jnz got_multibyte_try_next_key
  /* note: expected that test string will be nul before input string */
  /* no attempt is made to ensure no more than 4 bytes stack read */
  incw %bp                             /* point to next input */
  jmp got_multibyte_nextchar
got_multibyte_try_next_key:  /* align to next scancode/ascii pair */
  movb %cs:(%bx), %ch                  /* ch = test char to compare */
  incw %bx                             /* point to next char */
  orb %ch, %ch                         /* is char to compare NUL? */
  jnz got_multibyte_try_next_key
  jmp got_multibyte_findkey
got_multibyte_key:
  xorb %al, %al                        /* ascii value = 0 for special keys */
  movw %sp, %bp
  movw %ax, 4(%bp)                     /* overwrite old %ax value with key */
get_multibyte_tail:
  addw $4, %sp                         /* pop temp space */
  popw %ax
  popw %cx
  popw %bx
  popw %bp
  ret

/*
 * send_byte
 *
 * send character in %al to serial port [FIXME: EFI console out]
 *
 * all registers preserved except flags
 *
 */

send_byte:
  pushw %ax
  pushw %dx
  pushw %cx
  testb $0x80, %al                     /* don't send non-ascii chars */
  jnz send_tail                        /* these should be translated earlier */
  movb %al, %ah                        /* save char to output in %ah */
  movw $0xFFF0, %cx                    /* only retry 65520 times */
serial_ready_test:
  call get_serial_lsr                  /* get serial lsr in %al */
  testb $TRANSMIT_READY_BIT, %al
  loopz serial_ready_test              /* if !tx ready, loop while cx!=0 */
  movb %ah, %al
  movw %cs:serial_port_base_address, %dx
  outb %al, %dx
send_tail:
  popw %cx
  popw %dx
  popw %ax
  ret

/*
 * translate_char
 *
 * translate vga character in %al to ascii
 *
 * returns:
 * al = translated character
 *
 * all registers except %al preserved
 *
 */

translate_char:
  pushw %bx
  pushw %ds
  pushw %cs
  popw %ds                             /* ds = cs */
  testb $0x80, %al
  jz translate_char_ctrl
  andb $0x7f, %al
  movw $high2ascii, %bx
  xlatb
translate_char_ctrl:
  cmpb $0x20, %al
  jnc translate_char_tail
  movw $ctrl2ascii, %bx
  xlatb
translate_char_tail:
  popw %ds
  popw %bx
  ret

/*
 * translate_char_tty
 *
 * translate vga character in %al to ascii
 * unless %al == 7, 8, 10, or 13 (bell, bs, lf, cr)
 *
 * returns:
 * al = translated character
 *
 * all registers except %al preserved
 *
 */

translate_char_tty:
  cmpb $0x07, %al                      /* bell */
  jz translate_char_tty_tail
  cmpb $0x08, %al                      /* backspace */
  jz translate_char_tty_tail
  cmpb $0x0a, %al                      /* LF */
  jz translate_char_tty_tail
  cmpb $0x0d, %al                      /* CR */
  jz translate_char_tty_tail
  call translate_char
translate_char_tty_tail:
  ret

/*
 * send_char
 *
 * send character 0 - 255 in %al out through serial port
 * increment cursor position without control processing
 *
 * send_byte is used for data that isn't tracked
 *
 * send_char is used for text that should be tracked
 * send_char outputs all characters as non-control chars
 *
 * returns:
 * al = translated character
 *
 * all registers except %al preserved
 *
 */

send_char:
  call sgabioslog_save_char            /* save original char+pos */
  call translate_char
  jmp send_char_tty_out
  /* after ctrl translation, same as send_char_tty */

/*
 * send_char_tty
 *
 * send character 0 - 255 in %al out through serial port
 * increment cursor position *with* control processing
 * for bell, linefeed, cr, and backspace (others all printable)
 *
 * send_byte is used for data that isn't tracked
 *
 * send_char_tty is used for text that should be tracked
 *
 * returns:
 * al = translated character
 *
 * all registers except %al preserved
 *
 */


/* send character 0 - 255 in %al out through serial port */
/* increment cursor position with CR/LF/Backspace processing */
send_char_tty:
  call sgabioslog_save_char            /* save original char+pos */
  call translate_char_tty
send_char_tty_out:
  pushw %dx
  call update_serial_cursor
  call get_current_cursor              /* vga cursor in %dx */
  cmpb $0x0d, %al                      /* CR */
  jnz send_char_tty_nul                /* if not CR, check for NUL */
  orb %dl, %dl                         /* already at col 0? */
  jz send_char_tty_tail                /* no need to re-send CR */
send_char_tty_nul:
  orb %al, %al                         /* %al == 0 ? (nul) */
  /* more than likely, we have NUL at this point because the caller */
  /* tried to read a char using int $0x10, %ah=8, and is trying */
  /* to re-output it with different attributes - for now send nothing */
  jz send_char_tty_tail
send_char_tty_write:
  call memconsole_log_char             /* log character sent */
  call send_byte
  cmpb $0x07, %al                      /* bell */
  jz send_char_tty_tail                /* no cursor update for bell */
  cmpb $0x08, %al                      /* backspace */
  jz send_char_tty_backspace
  cmpb $0x0a, %al                      /* LF */
  jz send_char_tty_lf
  cmpb $0x0d, %al                      /* CR */
  jz send_char_tty_cr
  incb %dl
  jmp send_char_tty_tail
send_char_tty_backspace:
  orb %dl, %dl
  jz send_char_tty_tail
  decb %dl
  jmp send_char_tty_tail
send_char_tty_lf:
  incb %dh
  jmp send_char_tty_tail
send_char_tty_cr:
  xorb %dl, %dl
send_char_tty_tail:
  cmpb %cs:term_cols, %dl
  jc send_char_tty_check_rows
  movb %cs:term_cols, %dl
  decb %dl                             /* dl = cols - 1 */
send_char_tty_check_rows:
  cmpb %cs:term_rows, %dh
  jc send_char_tty_save_cursor
  movb %cs:term_rows, %dh
  decb %dh                             /* dh = rows - 1 */
send_char_tty_save_cursor:
  call set_current_cursor
  pushw %ds
  pushw $BDA_SEG
  popw %ds
  /* save current position as the serial terminal position */
  /* since a character was just output at that position */
  movw %dx, BDA_SERIAL_POS
  popw %ds
  popw %dx
  ret

/*
 * send_asciz_out
 *
 * send nul terminated string pointed to by %ds:%si
 * to serial port without text tracking
 *
 * indended to be used for multi-byte send_byte
 *
 * all registers preserved except flags
 */

send_asciz_out:
  pushw %ax
  pushw %si
  cld
send_asciz_loop:
  lodsb
  test %al,%al
  jz send_asciz_end
  call send_byte
  jmp send_asciz_loop
send_asciz_end:
  popw %si
  popw %ax
  ret

/*
 * send_string
 *
 * send cx chars in string pointed to by %ds:%si
 * to serial port with tty tracking
 * 
 * indended to be used for multi-byte send_char_tty
 *
 * all registers preserved except flags
 */

send_string:
  pushw %ax
  pushw %si
  cld
send_string_loop:
  lodsb
  call send_char_tty
  loop send_string_loop
  popw %si
  popw %ax
  ret

/*
 * send_string
 *
 * send cx chars in string pointed to by %ds:%si
 * with interleaved attribute data
 * 
 * indended to be used for multi-byte send_char_tty
 * with interleaved vga attribute updates
 *
 * all registers preserved except flags
 */

send_attr_string:
  pushw %ax
  pushw %bx
  pushw %si
  cld
send_attr_string_loop:
  lodsb
  call send_char_tty
  lodsb
  movb %al, %bl
  call send_attribute                  /* send attribute in %bl */
  loop send_attr_string_loop
  popw %si
  popw %bx
  popw %ax
  ret

/*
 * send_number
 *
 * send ascii version of number in %al to serial port
 * 
 * intended for ansi cursor positions and attributes,
 * so cursor position is not tracked/updated
 *
 * all registers preserved except flags
 */

send_number:
  pushw %ax
  pushw %bx
  aam                                  /* ah = al/10, al = al mod 10 */
  movw %ax, %bx                        /* bh = al/10, bl = al mod 10 */
  movb %bh, %al
  aam                                  /* ah = bh/10, al = bh mod 10 */
  movb %al, %bh                        /* bh = 10s digit, bl = 1s digit */
  movb %ah, %al                        /* ah = al = 100s digit */
  testb %al, %al                       /* is there a 100s digit? */
  jz send_tens                         /* move to tens if not */
  orb $0x30, %al                       /* al = ascii value of digit */
  call send_byte
send_tens:
  orb %bh, %ah                         /* bh = 10s, ah = 100s digits */
  jz send_ones                         /* non-zero = must send tens */
  movb %bh, %al                        /* al = bh = 10s digit */
  orb $0x30, %al                       /* al = ascii value of digit */
  call send_byte
send_ones:
  movb %bl, %al                        /* al = bl = 1s digit */
  orb $0x30, %al                       /* al = ascii value of digit */
  call send_byte
  popw %bx
  popw %ax
  ret

/*
 * send_crlf
 *
 * send CRLF to serial port
 * 
 * FIXME: used at vga init and for scrolling terminal
 * so position is not tracked.  Callers of this routine
 * predate the code that does smart tty/cursor output.
 *
 * Callers should probably be changed to use those
 * routines or send_crlf changed to use them and
 * terminal scrolling fixed to use linefeed only.
 *
 * all registers preserved except flags
 */

send_crlf:
  pushw %ax
  movb $0x0d, %al
  call send_byte
  movb $0x0a, %al
  call send_byte
  popw %ax
  ret
/*
 * send_ansi_csi
 *
 * send ESCAPE [ to serial port
 * 
 * output is not tracked since these are control sequences
 *
 * all registers preserved except flags
 */

send_ansi_csi:      /* transmit ESC [ */
  pushw %ax
  movb $0x1b, %al                      /* escape */
  call send_byte
  movb $0x5b, %al                      /* [ */
  call send_byte
  popw %ax
  ret
/*
 * send_ansi_csi_2num
 *
 * send ESC [ %dh ; %dl to serial port
 * 
 * since both position and attribute updates generally have
 * two parameters, this function converts values in dx to
 * two ascii numbers.  It's expected that the caller will
 * output the final trailing H or m or whatever is required.
 *
 * output is not tracked since these are control sequences
 *
 * all registers preserved except flags
 */

send_ansi_csi_2num:
/* send ESC [ %dh ; %dl */
  pushw %ax
  call send_ansi_csi                   /* esc [ */
  movb %dh, %al
  call send_number
  movb $0x3b, %al                      /* semicolon */
  call send_byte
  movb %dl, %al
  call send_number
  popw %ax
  ret

/*
 * send_ansi_cursor_pos
 *
 * send ESC [ %dh+1 ; %dl+1 to serial port to position
 * cursor
 * 
 * since both position and attribute updates generally have
 * two parameters, this function converts values in dx to
 * two ascii numbers, after adding 1 to both dh and dl.
 *
 * output is not tracked since this is a control sequence
 *
 * all registers preserved except flags
 */

send_ansi_cursor_pos:
  pushw %ax
  pushw %dx
  addw $0x0101, %dx                    /* dh += 1, dl += 1 */
  call send_ansi_csi_2num              /* send esc [ %dh+1;%dl+1 */
  movb $0x48, %al                      /* H */
  call send_byte
  popw %dx
  popw %ax
  ret

/*
 * send_attribute
 *
 * send ansi attribute change ESC [ 4x ; 3y ; (1|22)m
 * if the attribute has changed since last sent (stored in bda)
 * 
 * output is not tracked since this is a control sequence
 *
 * all registers preserved except flags
 */

send_attribute:
  andb $0x7f, %bl                      /* ansi has no bright bg */
  pushw %ds
  pushw %es
  pushw %ax
  pushw %bx
  pushw %dx
  pushw $BDA_SEG
  popw %es                             /* es = 0x40 */
  pushw %cs
  popw %ds                             /* ds = cs */
  cmpb %es:BDA_COLOR_VAL, %bl
  jz send_attribute_tail
  cmpb $0x07, %bl                      /* is it white on black? */
  jnz send_attribute_color
  /* for white on black, send esc [ m */
  call send_ansi_csi
  jmp send_attribute_m                 /* send the m, return */
send_attribute_color:
  movb %bl, %ah                        /* ah = attribute */
  movw $colortable, %bx
  movb %ah, %al
  andb $7, %al                         /* al = fg attr */
  xlatb                                /* al = fg ansi num */
  movb %al, %dl                        /* dl = fg ansi num */
  movb %ah, %al
  shrb $4, %al                         /* al = bg attr */
  xlatb                                /* al = bg ansi num */
  movb %al, %dh                        /* dh = bg ansi num */
  addw $0x281e, %dx                    /* 3x=setfg, 4x=setbg */
  call send_ansi_csi_2num
  movb $0x3b, %al                      /* semicolon */
  call send_byte
  shlb $4, %ah                         /* bright text? */
  sets %al                             /* if bit 7, al = 1 */
  js send_attribute_intensity
  movb $22, %al                        /* 22 = normal intensity */
send_attribute_intensity:
  call send_number                     /* either 22 or 1 */
send_attribute_m:
  movb $0x6d, %al                      /* m */
  call send_byte
send_attribute_tail:
  popw %dx
  popw %bx
  /* mark attribute in %bl the current one */
  movb %bl, %es:BDA_COLOR_VAL
  popw %ax
  popw %es
  popw %ds
  ret

/*
 * serial_get_input
 *
 * common code for both interrupt-driven and non-interrupt
 * driven serial input.   Called only when LSR bit 1 is set.
 *
 * No parameters, no return values
 *
 * Preserves all registers
 */

serial_get_input:
  pushf
  /* be paranoid about int 9h happening during update */
  cli
  pushaw
  pushw %ds
  /* next char input buffer is at 0x40:0x1c */
  pushw $BDA_SEG
  popw %ds                             /* es = 0x40 */
  call get_byte                        /* next scancode/byte in %ax */
  cmpb $0x1b, %al                      /* look for escape */
  jnz serial_gotkey                    /* not escape, don't look for more bytes */
  call get_multibyte                   /* look for any chars after escape */
serial_gotkey:
  movw KBD_TAIL, %bx                   /* bx = keyboard tail pointer */
  movw %ax, (%bx)                      /* store key in buffer */
  addw $2, %bx                         /* point to next location */
  cmpw $KBD_BUF_END, %bx               /* did the buffer wrap? */
  jb kbd_buf_no_wrap
  movw $KBD_BUF_START, %bx
kbd_buf_no_wrap:
  movw %bx, KBD_TAIL                   /* update tail pointer to show key */
  popw %ds
  popaw
  popf
  ret

/*
 * irq3_isr
 *
 * entry point for irq 3 / int 0x0b / exception 11
 *
 * Called when COM2 or COM4 have characters pending
 *
 * The segment not present exception should never happen
 * in real mode 16-bit code like this, but just to be safe,
 * if this interrupt is invoked and no characters are
 * pending on the port found in serial_port_base_address,
 * this routine will chain to the original handler.
 *
 * If characters are found pending, they will be processed
 * and control returned via iret.
 */

irq3_isr:
#if 0
  pushw %ax
  pushw %dx
  /* placeholder, this shouldn't ever happen */
  /* no interrupts are configured outside COM1 */
  call get_serial_lsr                  /* get serial lsr in %al */
  jz chain_irq3                        /* no port present... */
  testb $1, %al                        /* bit 0 of LSR = 1 = data available */
  jz chain_irq3                        /* no input waiting */
  call serial_get_input                /* get input and stuff kbd buffer */
  movb $0x20, %al
  outb %al, $0x20                      /* send non-specific EOI */
  popw %dx
  popw %ax
  iret
chain_irq3:
  popw %dx
  popw %ax
#endif
  jmp do_old_irq3

/*
 * irq4_isr
 *
 * entry point for irq 4 / int 0x0c / exception 12
 *
 * Called when COM1 or COM3 have characters pending
 *
 * The stack fault exception may occur if code attempts to
 * read from sp:0xffff, so if this interrupt is invoked and
 * no characters are pending on the port found in
 * serial_port_base_address, this routine will chain to the
 * original handler.
 *
 * If characters are found pending, they will be processed
 * and control returned via iret.
 */

irq4_isr:
#if 0
  pushw %ax
  pushw %dx
  call get_serial_lsr                  /* get serial lsr in %al */
  jz chain_irq4                        /* no port present... */
  testb $1, %al                        /* bit 0 of LSR = 1 = data available */
  jz chain_irq4                        /* no input waiting */
  call serial_get_input                /* get input and stuff kbd buffer */
  movb $0x20, %al
  outb %al, $0x20                      /* send non-specific EOI */
  popw %dx
  popw %ax
  iret
chain_irq4:
  popw %dx
  popw %ax
#endif
  jmp do_old_irq4

/*
 * int14h_isr
 *
 * entry point for int 14h
 *
 */
int14h_isr:
  pushaw
  movw %sp, %bp
  addw $16, %bp                        /* bp points to return address */
  orb %ah, %ah                         /* fn 0x00, initialize port */
  jz int14h_init_port
  cmpb $0x04, %ah                      /* fn 0x04, extended intialize */
  jnz chain_isr14h
int14h_init_port:
  /* check for init port = current port */
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movw %dx, %bx                        /* bx = port number */
  shlw $1, %bx                         /* bx = port number * 2 */
  andw $7, %bx                         /* bx = bda offset of serial io addr */
  movw (%bx), %cx                      /* cx = io address of port to init */
  popw %ds                             /* restore original ds */
  cmpw %cx, %cs:serial_port_base_address
  jnz chain_isr14h                     /* if different, don't get in the way */
  /* init port == current port */
  pushw %ds
  /* LILO 22.6 HACK STARTS HERE */
  movw (%bp), %bx                      /* return address for int 14h call */
  movw 2(%bp), %ds                     /* return segment for int 14h call */
  cmpl $0x4f4c494c, 0x06               /* does segment have lilo signature? */
  jnz int14h_init_tail                 /* not lilo, bail on hack */
  cmpw $0x0616, 0x0a                   /* does version match lilo 22.6? */
  jnz int14h_init_tail                 /* unknown lilo release, bail on hack */
  movb $0, 0x12                        /* set lilo com port = 0 */
  movl $0x90c3585a, (%bx)              /* return code= pop dx;pop ax;ret;nop */
  /* now lilo 22.6's own serial out is permanently disabled */
  /* this prevents double-character output from int10h + serial */
  /* this also prevents lilo from stealing serial input chars */
  /* END LILO 22.6 HACK */
int14h_init_tail:
  popw %ds
  popaw
  pushw %dx                            /* get_serial_lsr trashes %dx */
  call get_serial_lsr                  /* return serial status in %al */
  xorb %ah, %ah                        /* return serial status in %ax */
  popw %dx                             /* restore %dx */
  iret
chain_isr14h:
  popaw
  jmp do_old_int14h

/*
 * int16h_isr
 *
 * entry point for int 16h
 *
 * keyboard characters are usually retrieved by calling
 * int 16h, generally placed in the keyboard buffer by
 * irq 1 (int 9h).  Poll serial port for new data before
 * chaining to int 16h to fake irq 1 behavior
 *
 * all registers preserved except flags (later iret will restore)
 * bda updated with a new keypress if available
 *
 * FIXME: handle multi-byte keypresses like cursor up/down
 * to send proper scancodes for navigating lilo menus
 */

int16h_isr:
  pushw %ax
  pushw %dx
  /* each time int 16h is invoked, fake an int 9h */
  /* except read the serial input buffer */
  /* then chain to the original int 16h for processing */
  call get_serial_lsr
  jz chain_isr16h                      /* no port present... */
  testb $1, %al                        /* bit 0 of LSR = 1 = data available */
  jz chain_isr16h                      /* no input waiting */
  call serial_get_input                /* get input and stuff kbd buffer */
  /* for now, leave remaining chars pending in serial fifo */
  /* int 16h callers only get one char at a time anyway */
chain_isr16h:
  popw %dx
  popw %ax
  jmp do_old_int16h

/*
 * update serial_cursor
 *
 * figure out where the cursor was, and where it's going
 * use the minimal amount of serial output to get it there
 * input: vga cursor and serial cursor positions stored in BDA
 *
 * all registers preserved except flags
 * bda updated with new position for serial console cursor
 */
update_serial_cursor:
  pushw %ax
  pushw %bx
  pushw %dx
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  call get_current_cursor              /* dh = row, dl = col */
  movw BDA_SERIAL_POS, %bx             /* bh = row, bl = col */
  subb %dl, %bl                        /* -col update */
  negb %bl                             /* col update */
  subb %dh, %bh                        /* -row update */
  negb %bh                             /* row update */
  /* handle a few special movement cases */
  /* cr, lf, bs, bs+bs, space, else send full ansi position */
  orb %dl, %dl                         /* column zero? */
  jnz update_serial_cursor_lf
  movb $0x0d, %al                      /* CR */
  call send_byte
  xorb %bl, %bl                        /* mark no diff in col */
update_serial_cursor_lf:
  cmpb $1, %bh                         /* +1 row? */
  jnz update_serial_cursor_bs
  movb $0x0a, %al                      /* LF */
  call send_byte
  xorb %bh, %bh                        /* mark no diff in row */
update_serial_cursor_bs:
  cmpb $-1, %bl                        /* one char back */
  jz update_serial_cursor_one_bs
  cmpb $-2, %bl                        /* two chars back */
  jnz update_serial_cursor_space       /* check for space */
  movb $0x08, %al                      /* BS */
  call send_byte
update_serial_cursor_one_bs:
  movb $0x08, %al                      /* BS */
  call send_byte
  xorb %bl, %bl                        /* mark no diff in col */
update_serial_cursor_space:
  cmpb $1, %bl                         /* one char forward */
  jnz update_serial_cursor_up
  movb $0x20, %al                      /* space */
  call send_byte
  xorb %bl, %bl                        /* mark no diff in col */
update_serial_cursor_up:
  cmpb $-1, %bh                        /* -1 row? */
  jnz update_serial_cursor_full        /* do full ansi pos update */
  call send_ansi_csi                   /* send ESC [ A (cursor up) */
  movb $0x41, %al                      /* A */
  call send_byte
  xorb %bh, %bh                        /* mark no diff in row */
update_serial_cursor_full:
  orw %bx, %bx                         /* diff = 0? */
  jz update_serial_cursor_done
  call send_ansi_cursor_pos            /* set cursor pos from dh,dl */
update_serial_cursor_done:
  movw %dx, BDA_SERIAL_POS
  popw %ds
  popw %dx
  popw %bx
  popw %ax
  ret

/*
 * write_teletype
 *
 * handle int 10h, function 0eh
 *
 * ah = 0x0e write teletype character
 * al = character ascii code
 * bh = display page number
 *
 * all registers except %al preserved
 * caller will restore all registers
 */

write_teletype:
  pushw %bx
  movb $0x07, %bl                      /* black bg, white fg */
  call send_attribute
  popw %bx
  call send_char_tty
  ret

/*
 * write_attr_char
 *
 * handle int 10h, function 09h
 *
 * ah = 0x09 write attribute/character at current cursor position
 * al = character ascii code
 * bh = display page number
 * bl = character attribute
 * cx = repetition count
 *
 * does not update cursor position
 * all registers except %cx and %al preserved
 * caller will restore all registers
 */

write_attr_char:
  call send_attribute                  /* send attribute in %bl */
  jmp write_char_common

/*
 * write_char
 *
 * handle int 10h, function 0ah
 *
 * ah = 0x0a write character at current cursor position
 * al = character ascii code
 * bh = display page number
 * cx = repetition count
 *
 * does not update cursor position
 * all registers except %cx and %al preserved
 * caller will restore all registers
 */

write_char:
  pushw %bx
  movb $0x07, %bl                      /* black bg, white fg */
  call send_attribute
  popw %bx
write_char_common:
  call get_current_cursor
  call send_char
  /* make cx=0 and cx=1 only output one char */
  cmpw $1, %cx
  jbe write_char_tail
  decw %cx
  jmp write_char
write_char_tail:
  /* put cursor back where it was on entry */
  call set_current_cursor
  ret

/*
 * write_string
 *
 * handle int 10h, function 13h
 *
 * ah = 0x13 write character at current cursor position
 * al = 0, data = char, ..., no cursor update
 * al = 1, data = char, ..., cursor at end of string
 * al = 2, data = char+attr, ..., no cursor update
 * al = 3, data = char+attr, ..., cursor at end of string
 * bh = display page number
 * bl = character attribute for all chars (if al = 0 or 1)
 * cx = characters in string (attributes don't count)
 * dh = cursor row start
 * dl = cursor column start
 * es:bp = pointer to source text string in memory
 *
 * all registers preserved except flags
 * caller will restore all registers
 */
write_string:
  call set_cursor_position
  pushw %ds
  pushw %es
  pushw %es
  popw %ds                             /* ds = es */
  movw %bp, %si                        /* si = bp */
  testb $2, %al
  jnz write_attr_string
  call send_attribute                  /* send attribute in %bl */
  test %cx, %cx
  jz write_string_empty
  call send_string                     /* plaintext out */
write_string_empty:
  jmp write_string_update_cursor
write_attr_string:
  call send_attr_string                /* text+attrib out */
write_string_update_cursor:
  testb $1, %al                        /* cursor update? */
  jnz write_string_tail                /* yes?  already happened */
  /* restore entry cursor position if no update */
  call set_cursor_position
write_string_tail:
  popw %es
  popw %ds
  ret

/*
 * set_cursor_position
 *
 * handle int 10h, function 02h
 *
 * ah = 0x02 set cursor position
 * bh = display page number
 * dh = cursor row
 * dl = cursor column
 *
 * update bda cursor position with value in %dx
 * serial console cursor only updated on text output
 * this routine also called by set_current_cursor
 * which won't bother setting ah = 2
 *
 * all registers preserved except flags
 */

set_cursor_position:
  pushw %ax
  pushw %bx
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movzbw %bh, %ax                      /* ax = page number */
  andb $0x07, %al                      /* prevent invalid page number */
  shlb $1, %al                         /* calculate word offset */
  addb $BDA_CURSOR_BUF, %al            /* ax = cursor save offset */
  movw %ax, %bx                        /* bx = cursor save offset */
  movw %dx, (%bx)                      /* save new cursor value */
  popw %ds
  popw %bx
  popw %ax
  ret

/*
 * set_current_cursor
 *
 * get current display page number and call set_cursor_positon
 * to store the row/column value in dx to the bda
 *
 * all registers preserved except flags
 */

set_current_cursor:
  pushw %ds
  pushw %bx
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movb BDA_ACTIVE_PAGE, %bh
  call set_cursor_position
  popw %bx
  popw %ds
  ret

/*
 * get_cursor_common
 *
 * read cursor position for page %bh from bda into %dx
 *
 * returns:
 * dh = cursor row
 * dl = cursor column
 * ch = cursor start scanline
 * cl = cursor end scanline
 *
 * all registers except %dx, %cx preserved
 */
get_cursor_common:
  pushw %bx
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movzbw %bh, %bx                      /* dx = current page */
  andb $7, %bl
  shlb $1, %bl
  addb $BDA_CURSOR_BUF, %bl
  movw (%bx), %dx                      /* get cursor pos */
  movw BDA_CURSOR_SCAN, %cx
  popw %ds
  popw %bx
  ret

/*
 * get_current_cursor
 *
 * read cursor position for current page from bda into %dx
 *
 * returns:
 * dh = cursor row
 * dl = cursor column
 *
 * all registers except %dx preserved
 */

get_current_cursor:
  pushw %ds
  pushw %bx
  pushw %cx
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movb BDA_ACTIVE_PAGE, %bh
  call get_cursor_common
  popw %cx
  popw %bx
  popw %ds
  ret

/*
 * get_cursor_position
 *
 * handle int 10h, function 03h
 *
 * ah = 0x02 get cursor position
 * bh = display page number
 *
 * returns:
 * ax = 0
 * ch = cursor start scanline
 * ch = cursor end scanline
 * dh = cursor row
 * dl = cursor column
 *
 * all registers except %ax, %cx, %dx preserved
 */

get_cursor_position:
  call bail_if_vga_attached            /* does not return if vga attached */
  popw %ax             /* not chaining, pop fake return address */
  popw %dx             /* not chaining, pop saved cursor position */
  popaw                /* not chaining to old int 10h, pop saved state */
  call get_cursor_common
  xorw %ax, %ax
  iret

/*
 * return_current_video_state
 *
 * handle int 10h, function 0fh
 *
 * ah = 0x0f return current video state
 *
 * returns:
 * ah = number of columns on screen (from 40:4a)
 * al = current video mode setting (from 40:49)
 * bh = active display page number (from 40:62)
 *
 * all registers except %ax and %bh preserved
 */

read_current_video_state:
  call bail_if_vga_attached            /* does not return if vga attached */
  popw %ax             /* not chaining, pop fake return address */
  popw %dx             /* not chaining, pop saved cursor position */
  popaw                /* not chaining to old int 10h, pop saved state */
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movb BDA_COLS, %ah
  movb BDA_MODE_NUM, %al
  movb BDA_ACTIVE_PAGE, %bh
  popw %ds
  iret

/*
 * read_attr_char
 *
 * handle int 10h, function 08h
 *
 * ah = 0x08 read character/attribute from screen
 *
 * returns:
 * ah = attribute at current cursor position
 * al = character read from current cursor position
 *
 * all registers preserved except %ax and flags
 */

read_attr_char:
  call bail_if_vga_attached            /* does not return if vga attached */
  popw %ax             /* not chaining, pop fake return address */
  popw %dx             /* not chaining, pop saved cursor position */
  popaw                /* not chaining to old int 10h, pop saved state */
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movb BDA_COLOR_VAL, %ah              /* return last color value */
  call sgabioslog_get_char
  popw %ds
  iret

/*
 * set_video_mode
 *
 * handle int 10h, function 00h
 *
 * ah = 0x00 set video mode
 * al = video mode
 *
 * unless bit 7 of al = 1, setting mode clears screen
 *
 * all registers preserved except %bh, %dx, flags
 */

set_video_mode:
  testb $0x80, %al                     /* preserve screen flag? */
  jnz set_video_mode_tail
  call send_ansi_csi
  movb $0x32, %al                      /* 2 */
  call send_byte
  movb $0x4a, %al                      /* J */
  call send_byte
set_video_mode_tail:
  movb $0x07, %bl                      /* white on black text */
  call send_attribute                  /* send attribute in %bl */
  /* set cursor position to 0,0 */
  xorb %bh, %bh                        /* page 0 */
  xorw %dx, %dx
  jmp set_cursor_position

/*
 * scroll_page_up
 *
 * handle int 10h, function 06h
 *
 * ah = 0x06 scroll current page up
 * al = scroll distance in character rows (0 blanks entire area)
 * bh = attribute to used on blanked lines
 * ch = top row (upper left corner) of window
 * cl = left-most column (upper left corner) of window
 * dh = bottom row (lower right corner) of window
 * dl = right-most column (lower right corner) of window
 *
 * all registers preserved except flags
 */

scroll_page_up:
  pushw %si
  pushw %dx
  call get_current_cursor              /* save current cursor */
  movw %dx, %si                        /* si = vga cursor pos */
  popw %dx
  cmpb $0, %al                         /* al = 0 = clear window */
  jz scroll_common_clear
  pushw %ax
  call send_ansi_csi                   /* CSI [ %al S */
  call send_number
  movb $0x53, %al                      /* S */
  call send_byte
  popw %dx
  popw %si
  ret

/*
 * scroll_common_clear
 *
 * common tail for up/down scrolls to clear window specified
 * in %cx and %dx.
 *
 * stack should contain saved copy of si
 * si = original vga cursor position on service entry
 * 
 * bh = attribute to used on blanked lines
 * ch = top row (upper left corner) of window
 * cl = left-most column (upper left corner) of window
 * dh = bottom row (lower right corner) of window
 * dl = right-most column (lower right corner) of window
 */
scroll_common_clear:
  pushw %ax
  xchgb %bl, %bh                       /* bl = attribute, bh = old bl */
  call send_attribute                  /* send attribute in %bl */
  xchgb %bl, %bh                       /* restore bx */
  pushw %ds
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  /* check to see if region is full screen, and attribute default */
  orw %cx, %cx                         /* is top left 0,0? */
  jnz scroll_common_window             /* no, handle window */
  cmpb $0x07, %bh                      /* is attribute white on black? */
  jnz scroll_common_window             /* no, must write spaces */
#ifdef LILO_CLEAR_WORKAROUND_NOT_REQUIRED
  cmpb %cs:term_cols, %dl              /* is right less than cols ? */
  jc scroll_common_window              /* if so, handle window */
  cmpb %cs:term_rows, %dh              /* is bottom less than rows ? */
  jc scroll_common_window              /* if so, handle window */
#endif
  /* safe to send standard clear screen sequence */
  call send_ansi_csi                   /* send ESC [ */
  movb $0x32, %al                      /* 2 */
  call send_byte
  movb $0x4a, %al                      /* J */
  call send_byte
  jmp scroll_common_tail
scroll_common_window:
  pushw %dx
  movw %cx, %dx                        /* dx = upper right */
  call set_current_cursor
  popw %dx
  pushw %cx
  /* setup cx with count of chars to clear per row */
  xorb %ch, %ch
  negb %cl
  addb %dl, %cl                        /* cl = dl - cl */
  incb %cl                             /* start = end col = clear 1 col */
  cmpb %cs:term_cols, %cl              /* is count < cols? */
  jc scroll_common_row_ok              /* if so then skip limit */
  movb %cs:term_cols, %cl              /* limit count to cols */
scroll_common_row_ok:
  jz scroll_common_row_done            /* count == 0 ? */
  movb $0x20, %al                      /* space */
scroll_common_space_loop:
  call send_char
  loop scroll_common_space_loop        /* send cx spaces */
scroll_common_row_done:
  popw %cx
  incb %ch                             /* top left now next row */
  cmpb %dh, %ch
  jbe scroll_common_window             /* do next row */
scroll_common_tail:
  popw %ds
  popw %ax
  pushw %dx
  movw %si, %dx                        /* dx = saved vga cursor pos */
  call set_current_cursor              /* restore saved cursor */
  popw %dx
  popw %si
  ret

/*
 * scroll_page_down
 *
 * handle int 10h, function 07h
 *
 * ah = 0x07 scroll current page down
 * al = scroll distance in character rows (0 blanks entire area)
 * bh = attribute to used on blanked lines
 * ch = top row (upper left corner) of window
 * cl = left-most column (upper left corner) of window
 * dh = bottom row (lower right corner) of window
 * dl = right-most column (lower right corner) of window
 *
 * FIXME: this routine doesn't handle windowing, it currently
 * only handles one line screen scrolls and erasing entire screen
 *
 * all registers preserved except flags
 */

scroll_page_down:
  pushw %si
  pushw %dx
  call get_current_cursor              /* save current cursor */
  movw %dx, %si                        /* si = vga cursor pos */
  popw %dx
  cmpb $0, %al                         /* al = 0 = clear window */
  jz scroll_common_clear
  pushw %ax
  call send_ansi_csi                   /* CSI [ %al T */
  call send_number
  movb $0x54, %al                      /* T */
  call send_byte
  popw %dx
  popw %si
  ret

/*
 * bail_if_vga_attached
 *
 * Check for vga installed, if not, return to caller.
 * If so, pop return address, return to chain_isr_10h
 *
 * expected that routine calling this one has chain_isr_10h
 * as the next item on the stack
 *
 * all registers except flags and sp preserved
 */

bail_if_vga_attached:
  cmpw $0xc000, %cs:old_int10h_seg     /* vga attached? */
  jnz bail_tail                        /* if not, don't modify stack */
  addw $2, %sp                         /* else drop first return address */
bail_tail:
  ret                                  /* return to caller or chain_isr_10h */

/*
 * int10h_isr
 *
 * entry point for int 10h
 *
 * save all registers, force return to chain to previous int10h isr
 * decide which function in ah needs to be dispatched
 *
 * ah = 0x00 set mode
 * ah = 0x01 set cursor type
 * ah = 0x02 set cursor position
 * ah = 0x03 read cursor position
 * ah = 0x04 read light pen position
 * ah = 0x05 set active display page
 * ah = 0x06 scroll active page up
 * ah = 0x07 scroll active page down
 * ah = 0x08 read attribute/character at cursor
 * ah = 0x09 write attribute/character at cursor
 * ah = 0x0a write character at cursor position
 * ah = 0x0b set color palette
 * ah = 0x0c write pixel
 * ah = 0x0d read pixel
 * ah = 0x0e write teletype
 * ah = 0x0f read current video state
 * ah = 0x10 set individual palette registers
 * ah = 0x11 character generation (font control/info)
 * ah = 0x12 alternate select (video control/info)
 * ah = 0x13 write string
 * ah = 0x1a read/write display combination code
 * ah = 0x1b return functionality/state information
 * ah = 0x1c save/restore video state
 * ah = 0x4f vesa bios calls
 * all registers preserved except flags (later iret will restore)
 */

int10h_isr:
  pushaw
  call get_current_cursor
  pushw %dx                            /* save current cursor */
  pushw %bp                            /* need bp for indexing off stack */
  movw %sp, %bp                        /* bp = sp */
  movw 14(%bp), %dx                    /* restore dx from earlier pushaw */
  popw %bp                             /* restore old bp */
  pushw $chain_isr10h                  /* force return to chain_isr10h */
  testb %ah, %ah
  jnz int10h_02
  jmp set_video_mode
int10h_02:
  cmpb $0x02, %ah
  jnz int10h_03
  jmp set_cursor_position
int10h_03:
  cmpb $0x03, %ah
  jnz int10h_06
  jmp get_cursor_position
int10h_06:
  cmpb $0x06, %ah
  jnz int10h_07
  jmp scroll_page_up
int10h_07:
  cmpb $0x07, %ah
  jnz int10h_08
  jmp scroll_page_down
int10h_08:
  cmpb $0x08, %ah
  jnz int10h_09
  jmp read_attr_char
int10h_09:
  cmpb $0x09, %ah
  jnz int10h_0a
  jmp write_attr_char
int10h_0a:
  cmpb $0x0a, %ah
  jnz int10h_0e
  jmp write_char
int10h_0e:
  cmpb $0x0e, %ah
  jnz int10h_0f
  jmp write_teletype
int10h_0f:
  cmpb $0x0f, %ah
  jnz int10h_13
  jmp read_current_video_state
int10h_13:
  cmpb $0x13, %ah
  jnz int10h_default
  jmp write_string
int10h_default:
  popw %ax                             /* pop chain_isr10h return address */
chain_isr10h:
  popw %dx                             /* pop saved cursor */
  cmpw $0xc000, %cs:old_int10h_seg     /* vga attached? */
  jnz chain_post_cursor                /* if not, don't restore the cursor */
  call set_current_cursor              /* restore cursor if vga attached */
chain_post_cursor:
  popaw
  jmp do_old_int10h

/*
 * pnp_sga_init
 *
 * handle PnP initialization of option rom
 *
 * es:di = pointer to PnP structure
 * ax = indication as to which vectors should be hooked
 *      by specifying th type of boot device this has
 *      been selected as
 *      bit 7..3= reserved(0)
 *      bit 2   = 1 = connect as IPL (int 13h)
 *      bit 1   = 1 = connect as primary video (int 10h)
 *      bit 0   = 1 = connect as primary input (int 9h)
 * bx = card select number (probably 0xffff)
 * dx = read data port address (probably 0xffff)
 *
 * return:
 * ax = initialization status
 * bit 8    = 1 = IPL device supports int 13h block dev format
 * bit 7    = 1 = Output device supports int 10h char output
 * bit 6    = 1 = Input device supports int 9h char input
 * bit 5..4 = 00 = no IPL device attached
 *            01 = unknown whether or not IPL device attached
 *            10 = IPL device attached (RPL devices have connection)
 *            11 = reserved
 * bit 3..2 = 00 = no display device attached
 *            01 = unknown whether or not display device attached
 *            10 = display device attached
 *            11 = reserved
 * bit 1..0 = 00 = no input device attached
 *            01 = unknown whether or not input device attached
 *            10 = input device attached
 *            11 = reserved
 *
 * all registers preserved except %ax
 */

pnp_sga_init:
  /* FIXME: this is *wrong* -- init only what bios says to init */
  movw $0xca, %ax      /* 0xca = attached int 10h, 9h display, input */

/*
 * sga_init
 *
 * legacy option rom entry point
 *
 * all registers preserved
 */

sga_init:
  /* this is probably paranoid about register preservation */
  pushfw
  cli                                  /* more paranoia */
  pushaw
  pushw %ds
  pushw %es
  pushw $0
  popw %es                             /* es = 0 */
  pushw %cs
  popw %ds                             /* ds = cs */
  /* get original ISR */
  movl %es:0x28, %eax                  /* eax = old irq 3/int 0bh */
  movl %eax, old_irq3                  /* save away old irq 4/int 0bh */
  movl %es:0x2c, %eax                  /* eax = old irq 4/int 0ch */
  movl %eax, old_irq4                  /* save away old irq 4/int 0ch */
  movl %es:0x40, %eax                  /* eax = old int 10h */
  movl %eax, old_int10h                /* save away old int 10h */
  movl %es:0x50, %eax                  /* eax = old int 14h */
  movl %eax, old_int14h                /* save away old int 14h */
  movl %es:0x58, %eax                  /* eax = old int 16h */
  movl %eax, old_int16h                /* save away old int 16h */
  movw $irq3_isr, %es:0x28             /* new irq 3 offset */
  movw %cs, %es:0x2a                   /* write new irq 3 seg */
  movw $irq4_isr, %es:0x2c             /* new irq 4 offset */
  movw %cs, %es:0x2e                   /* write new irq 4 seg */
  movw $int10h_isr, %es:0x40           /* new int 10h offset */
  movw %cs, %es:0x42                   /* write new int10h seg */
  movw $int14h_isr, %es:0x50           /* new int 14h offset */
  movw %cs, %es:0x52                   /* write new int14h seg */
  movw $int16h_isr, %es:0x58           /* new int 16h offset */
  movw %cs, %es:0x5a                   /* write new int16h seg */
  /* empty input buffer to prepare for terminal sizing */
  call init_serial_port
input_clear_loop:
  call get_byte
  jnz input_clear_loop
  movw $term_init_string, %si
  call send_asciz_out
  push $BDA_SEG
  push $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  popw %es                             /* es = 0x40 */
  movw $BDA_CURSOR_BUF, %di
input_timeout_loop:
  /* get input from terminal until timeout found */
  /* store input at 40:50 - 40:5e (cursor pos) */
  call poll_byte
  jz input_timeout
  stosb                                /* es:di */
  cmpw $0x5f, %di                      /* 14 characters max */
  jnz input_timeout_loop               /* good for more data */
input_timeout:
  xorb %al, %al                        /* nul terminate input */
  stosb
  cmpw $0x58, %di                      /* less than 8 chars? */
  jc resize_end                        /* too small to have valid data */
  movw $BDA_CURSOR_BUF, %si            /* point to start */
  lodsw                                /* ax = first 2 chars */
  cmpw $0x5b1b, %ax                    /* was it "ESC[" ? */
  jnz resize_end                       /* reply starts ESC[row;colR */
  xorb %bl, %bl                        /* bl = ascii->int conversion */
input_first_number:
  lodsb                                /* al = next char */
  cmpb $0x30, %al
  jc resize_end                        /* char < 0x30 invalid */
  cmpb $0x3a, %al                      /* is char < 0x3a */
  jnc input_semicolon
  andb $0x0f, %al                      /* al = 0 - 9 */
  movb %bl, %ah                        /* ah = last conversion */
  aad                                  /* ax = (al + ah * 10) & 0xff */
  movb %al, %bl                        /* bl = row ascii->int conversion */
  jmp input_first_number
input_semicolon:
  /* at this point bl should contain rows, al = ; */
  /* sanity check, bail if invalid */
  cmpb $0x3b, %al
  jnz resize_end                       /* invalid input found */
  cmpb $0x0a, %bl                      /* less than 10 rows? */
  jc suspect_loopback                  /* consider input invalid */
  xorb %bh, %bh                        /* bh = col ascii->int conversion */
input_second_number:
  lodsb                                /* al = next char */
  cmpb $0x30, %al
  jc resize_end                        /* char < 0x30 invalid */
  cmpb $0x3a, %al                      /* is char < 0x3a */
  jnc input_final_r
  andb $0x0f, %al                      /* al = 0 - 9 */
  movb %bh, %ah                        /* ah = last conversion */
  aad                                  /* ax = (al + ah * 10) & 0xff */
  movb %al, %bh                        /* bh = ascii->int conversion */
  jmp input_second_number
input_final_r:
  cmpb $0x52, %al                      /* is al = 'R' ? */
  jnz suspect_loopback                 /* invalid input found */
  movb %bl, %cs:term_rows              /* save away bl rows value */
  cmpw $0xc000, %cs:old_int10h_seg     /* vga attached? */
  jz resize_end                        /* if so, leave term_cols at 80 */
  movb %bh, %cs:term_cols              /* save away bh cols value */
  jmp resize_end
suspect_loopback:
  /*
   * characters were received that look like what we sent out
   * at this point, assume that a loopback device was plugged in
   * and disable any future serial port reads or writes, by pointing
   * output to port 0x2e8 (COM4) instead of 0x3f8 -- it's expected
   * that this is safe since a real port responds correctly and a
   * missing port will respond with 0xff which will terminate the
   * loop that waits for the "right" status on the port.
   */
  movw $0x2e8, %cs:serial_port_base_address
resize_end:
  /* clear (hopefully) overwritten cursor position buffer */
  xorb %al, %al
  movw $BDA_CURSOR_BUF, %di
  movw $0x10, %cx
  cld
  rep
  stosb                                /* fill 40:50 - 40:5f with 0 */
  pushw %cs
  popw %ds                             /* ds = cs */
  call get_byte                        /* flush any remaining "wrong" input */
  jnz resize_end
  call send_crlf                       /* place cursor on start of last line */
  movw $mfg_string, %si
  call send_asciz_out
  call send_crlf
  movw $prod_string, %si
  call send_asciz_out
  call send_crlf
  movw $long_version, %si
  call send_asciz_out
  call send_crlf
  /* if vga attached, skip terminal message and bda setup... */
  cmpw $0xc000, %cs:old_int10h_seg     /* vga attached? */
  jz post_bda_init_tail                /* if so, don't modify BDA */
  /* show detected terminal size, or default if none detected */
  movw $term_info, %si
  call send_asciz_out
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movb %cs:term_cols, %al
  movb %al, BDA_COLS                   /* 40:4a = number of character cols */
  movb $0, BDA_CURSOR_COL              /* 40:51 = cursor0 col */
  call send_number
  movb $0x78, %al                      /* x */
  call send_byte
  movb %cs:term_rows, %al
  movb %al, %ah
  decb %ah                             /* ah = rows-1 */
  movb %ah, BDA_ROWS                   /* 40:84 = num character rows - 1 */
  movb %ah, BDA_CURSOR_ROW             /* 40:50 = cursor0 row */
  call send_number
  call send_crlf
  movb $3, BDA_MODE_NUM
  movb $0x29, BDA_MODE_SEL
  movw $VGA_IO_BASE, BDA_6845_ADDR
  movw $0x4000, BDA_PAGE_SIZE          /* 16KB per video page */
  /* to avoid ansi colors every character, store last attribute */
  movb $0x07, BDA_COLOR_VAL            /* 07 = black bg, white fg */
  movw %cs, %ax
  movw $_start, BDA_ROM_OFF
  movw %ax, BDA_ROM_SEG
post_bda_init_tail:
  /* copy BDA rows/cols to sgabios location... */
  /* if vga card is installed, reuse those values... */
  /* if no vga card is installed, this shouldn't change anything */
  pushw $BDA_SEG
  popw %ds                             /* ds = 0x40 */
  movb BDA_ROWS, %al
  incb %al                             /* bda holds rows-1 */
  movb %al, %cs:term_rows              /* sgabios rows */
  movb BDA_COLS, %ah
  movb %ah, %cs:term_cols              /* sgabios cols */
  /* setup in-memory logging of console if desired... */
  call setup_memconsole
  /* setup logging of last 256 characters output, if ebda has room */
  call sgabioslog_setup_ebda
  movw $ebda_info, %si
  call send_asciz_out
  movw %cs:sgabios_ebda_logbuf_offset, %ax
  xchgb %ah, %al
  call send_number
  movb $0x20, %al
  call send_byte
  movb %ah, %al
  call send_number
  call send_crlf
  popw %es
  popw %ds
  popaw
  popf
  lret

_end_sgabios: