https://github.com/akkartik/mu/blob/main/baremetal/boot.hex
   1 # Code for the first few disk sectors that all programs in this directory need:
   2 #   - load sectors past the first (using BIOS primitives) since only the first is available by default
   3 #     - if this fails, print 'D' at top-left of screen and halt
   4 #   - initialize a minimal graphics mode
   5 #   - switch to 32-bit mode (giving up access to BIOS primitives)
   6 #   - set up a handler for keyboard events
   7 #   - jump to start of program
   8 #
   9 # To convert to a disk image, first prepare a realistically sized disk image:
  10 #   dd if=/dev/zero of=disk.img count=20160  # 512-byte sectors, so 10MB
  11 # Create initial sectors from this file:
  12 #   ./bootstrap run apps/hex < baremetal/boot.hex > boot.bin
  13 # Translate other sectors into a file called a.img
  14 # Load all sectors into the disk image:
  15 #   cat boot.bin a.img > disk.bin
  16 #   dd if=disk.bin of=disk.img conv=notrunc
  17 # To run:
  18 #   qemu-system-i386 disk.img
  19 # Or:
  20 #   bochs -f baremetal/boot.bochsrc  # boot.bochsrc loads disk.img
  21 #
  22 # Since we start out in 16-bit mode, we need instructions SubX doesn't
  23 # support.
  24 # This file contains just lowercase hex bytes and comments. Programming it
  25 # requires liberal use of:
  26 #   - comments documenting expected offsets
  27 #   - size checks on the emitted file (currently: 6144 bytes)
  28 #   - xxd to spot-check contents of specific offsets in the generated output
  29 #
  30 # Programs using this initialization:
  31 #   - can't use any syscalls
  32 #   - can't print text to video memory (past these boot sectors)
  33 #   - must only print raw pixels (256 colors) to video memory (resolution 1024x768)
  34 #   - must start executing immediately after this file (see outline below)
  35 #
  36 # Don't panic! This file doesn't contain any loops or function calls. 80% of
  37 # it is data. One pass through less than 1KB of code (there's lots of
  38 # padding), and then we jump into a better notation. The rest of the stack
  39 # (really only in a couple of slightly higher-level places) needs to know just
  40 # a few magic constants:
  41 #   Video memory: start is stored at 0x8128
  42 #   Keyboard buffer: starts at 0x8028
  43 #
  44 # No mouse support. _That_ would require panicking.
  45 
  46 # Outline of this file with offsets and the addresses they map to at run-time:
  47 # -- 16-bit mode code
  48 #   offset    0 (address 7c00): boot code
  49 # -- 16-bit mode data
  50 #            e0 (address 7c80) global descriptor table
  51 #            f8 (address 7ca0) <== gdt_descriptor
  52 # -- 32-bit mode code
  53 #   offset  100 (address 7d00): boot code
  54 #           1fe (address 7dfe) boot sector marker (2 bytes)
  55 #   offset  200 (address 7e00): interrupt handler code
  56 # -- 32-bit mode data
  57 #   offset  400 (address 8000): handler data
  58 #           410 (address 8010): keyboard handler data
  59 #           428 (address 8028) <== keyboard buffer
  60 #   offset  500 (address 8100): video mode data (256 bytes)
  61 #           528 (address 8128) <== start of video RAM stored here
  62 #   offset  600 (address 8200): interrupt descriptor table (1KB)
  63 #   offset  a00 (address 8600): keyboard mappings (1.5KB)
  64 #   offset 1000 (address 8c00): bitmap font (2KB)
  65 #   offset 1800 (address 9400): entrypoint for applications (don't forget to adjust survey_baremetal if this changes)
  66 
  67 # Other details of the current memory map:
  68 #   code: 4 tracks of disk to [0x00007c00, 0x00027400)
  69 #   stack grows down from 0x00070000
  70 #     see below
  71 #   heap: [0x01000000, 0x02000000)
  72 #     see baremetal/120allocate.subx
  73 # Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of this.
  74 
  75 ## 16-bit entry point
  76 
  77 # Upon reset, the IBM PC:
  78 #   - loads the first sector (512 bytes)
  79 #     from some bootable image (see the boot sector marker at the end of this file)
  80 #     to the address range [0x7c00, 0x7e00)
  81 #   - starts executing code at address 0x7c00
  82 
  83 # offset 00 (address 0x7c00):
  84   # disable interrupts for this initialization
  85   fa  # cli
  86 
  87   # initialize segment registers
  88   # this isn't always needed, but the recommendation is to not make assumptions
  89   b8 00 00  # ax <- 0
  90   8e d8  # ds <- ax
  91   8e c0  # es <- ax
  92   8e e0  # fs <- ax
  93   8e e8  # gs <- ax
  94 
  95   # initialize stack to 0x00070000
  96   # We don't read or write the stack before we get to 32-bit mode, but BIOS
  97   # calls do. We need to move the stack in case BIOS initializes it to some
  98   # low address that we want to write code into.
  99   b8 00 70  # ax <- 0x7000
 100   8e d0  # ss <- ax
 101   bc 00 00  # sp <- 0x0000
 102 
 103 # 14:
 104   # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
 105   b4 02  # ah <- 2  # read sectors from disk
 106   # dl comes conveniently initialized at boot time with the index of the device being booted
 107   b5 00  # ch <- 0  # cylinder 0
 108   b6 00  # dh <- 0  # track 0
 109   b1 02  # cl <- 2  # second sector, 1-based
 110   b0 7d  # al <- 125  # number of sectors to read = 2*63 - 1
 111   # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
 112   bb 00 00  # bx <- 0
 113   8e c3  # es <- bx
 114   bb 00 7e  # bx <- 0x7e00 [label]
 115   cd 13  # int 13h, BIOS disk service
 116   0f 82 a3 00  # jump-if-carry disk_error [label]
 117 
 118 # 2c:
 119   # load two more tracks of disk into addresses [0x17800, 0x27400)
 120   b4 02  # ah <- 2  # read sectors from disk
 121   # dl comes conveniently initialized at boot time with the index of the device being booted
 122   b5 00  # ch <- 0  # cylinder 0
 123   b6 02  # dh <- 2  # track 0
 124   b1 01  # cl <- 1  # first sector, 1-based
 125   b0 7e  # al <- 126  # number of sectors to read = 2*63
 126   # address to write sectors to = es:bx = 0x17800
 127   bb 80 17  # bx <- 0x1780 [label]
 128   8e c3  # es <- bx
 129   bb 00 00  # bx <- 0
 130   cd 13  # int 13h, BIOS disk service
 131   0f 82 9b 00  # jump-if-carry disk_error [label]
 132   # reset es
 133   bb 00 00  # bx <- 0
 134   8e c3  # es <- bx
 135 
 136 # 39:
 137   # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
 138   # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
 139   # seta20.1:
 140   e4 64  # al <- port 0x64
 141   a8 02  # set zf if bit 1 (second-least significant) is not set
 142   75 fa  # if zf not set, goto seta20.1 (-6)
 143 
 144   b0 d1  # al <- 0xd1
 145   e6 64  # port 0x64 <- al
 146 
 147 # 43:
 148   # seta20.2:
 149   e4 64  # al <- port 0x64
 150   a8 02  # set zf if bit 1 (second-least significant) is not set
 151   75 fa  # if zf not set, goto seta20.2 (-6)
 152 
 153   b0 df  # al <- 0xdf
 154   e6 64  # port 0x64 <- al
 155 
 156 # 4d:
 157   # adjust video mode
 158   b4 4f  # ah <- 4f (VBE)
 159   b0 02  # al <- 02 (set video mode)
 160   bb 05 41  # bx <- 0x0105 (graphics 1024x768x256
 161             #               0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively)
 162             # fallback mode: 0x0101 (640x480x256)
 163   cd 10  # int 10h, Vesa BIOS extensions
 164 
 165 # 56:
 166   # load information for the (hopefully) current video mode
 167   # mostly just for the address to the linear frame buffer
 168   b4 4f  # ah <- 4f (VBE)
 169   b0 01  # al <- 01 (get video mode info)
 170   b9 07 01  # cx <- 0x0107 (mode we requested)
 171   bf 00 81  # di <- 0x8100 (video mode info) [label]
 172   cd 10
 173 
 174 # 62:
 175   # switch to 32-bit mode
 176   0f 01 16  # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16
 177     f8 7c  # *gdt_descriptor [label]
 178   0f 20 c0  # eax <- cr0
 179   66 83 c8 01  # eax <- or 0x1
 180   0f 22 c0  # cr0 <- eax
 181   ea 00 7d 08 00  # far jump to initialize_32bit_mode after setting cs to the record at offset 8 in the gdt (gdt_code) [label]
 182 
 183 # padding
 184 # 76:
 185                   00 00 00 00 00 00 00 00 00 00
 186 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 187 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 188 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 189 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 190 
 191 # cf:
 192 # disk_error:
 193   # print 'D' to top-left of screen to indicate disk error
 194   # *0xb8000 <- 0x0f44
 195   # bx <- 0xb800
 196   bb 00 b8
 197   # ds <- bx
 198   8e db  # 11b/mod 011b/reg/ds 011b/rm/bx
 199   # al <- 'D'
 200   b0 44
 201   # ah <- 0x0f  # white on black
 202   b4 0f
 203   # bx <- 0
 204   bb 00 00
 205   # *ds:bx <- ax
 206   89 07  # 00b/mod/indirect 000b/reg/ax 111b/rm/bx
 207 
 208 e9 fd ff  # loop forever
 209 
 210 ## GDT: 3 records of 8 bytes each
 211 
 212 # e0:
 213 # gdt_start:
 214 # gdt_null:  mandatory null descriptor
 215   00 00 00 00 00 00 00 00
 216 # gdt_code:  (offset 8 from gdt_start)
 217   ff ff  # limit[0:16]
 218   00 00 00  # base[0:24]
 219   9a  # 1/present 00/privilege 1/descriptor type = 1001b
 220       # 1/code 0/conforming 1/readable 0/accessed = 1010b
 221   cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
 222       # limit[16:20] = 1111b
 223   00  # base[24:32]
 224 # gdt_data:  (offset 16 from gdt_start)
 225   ff ff  # limit[0:16]
 226   00 00 00  # base[0:24]
 227   92  # 1/present 00/privilege 1/descriptor type = 1001b
 228       # 0/data 0/conforming 1/readable 0/accessed = 0010b
 229   cf  # same as gdt_code
 230   00  # base[24:32]
 231 # gdt_end:
 232 
 233 # f8:
 234 # gdt_descriptor:
 235   17 00  # final index of gdt = gdt_end - gdt_start - 1
 236   e0 7c 00 00  # start = gdt_start [label]
 237 
 238 # padding
 239 # fe:
 240                                           00 00
 241 
 242 ## 32-bit code from this point (still some instructions not in SubX)
 243 
 244 # offset 100 (address 0x7d00):
 245 # initialize_32bit_mode:
 246   66 b8 10 00  # ax <- offset 16 from gdt_start
 247   8e d8  # ds <- ax
 248   8e d0  # ss <- ax
 249   8e c0  # es <- ax
 250   8e e0  # fs <- ax
 251   8e e8  # gs <- ax
 252 
 253 # 10e:
 254   bc 00 00 07 00  # esp <- 0x00070000
 255 
 256 # 113:
 257   # load interrupt handlers
 258   0f 01 1d  # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32
 259     00 80 00 00  # *idt_descriptor [label]
 260 
 261   # For now, not bothering reprogramming the IRQ to not conflict with software
 262   # exceptions.
 263   #   https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
 264   #
 265   # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
 266   # debugger.
 267   # Reference:
 268   #   https://wiki.osdev.org/Exceptions
 269 
 270 # 11a:
 271   # enable keyboard IRQ (1)
 272   b0 fd  # al <- 0xfd  # disable mask for IRQ1
 273   e6 21  # port 0x21 <- al
 274 
 275 # 11e:
 276   fb  # enable interrupts
 277   db e3  # initialize FPU
 278   # eax <- cr4
 279   0f 20  # copy cr4 to rm32
 280     e0  # 11/mod/direct 100/r32/CR4 000/rm32/eax
 281   # eax <- or bit 9
 282   0f ba
 283     e8  # 11/mod/direct 101/subop/bit-test-and-set 000/rm32/eax
 284     09  # imm8
 285   # cr4 <- eax
 286   0f 22  # copy rm32 to cr4
 287     e0  # 11/mod/direct 100/r32/CR4 000/rm32/eax
 288   e9 d0 16 00 00  # jump to 0x9400 [label]
 289 
 290 # padding
 291 # 130:
 292 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 293 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 294 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 295 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 296 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 297 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 298 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 299 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 301 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 302 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 303 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 304 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 305 
 306 # 1fe:
 307 # final 2 bytes of boot sector
 308 55 aa
 309 
 310 ## sector 2 onwards loaded by load_disk, not automatically on boot
 311 
 312 # offset 200 (address 0x7e00):
 313 # null interrupt handler:
 314   cf  # iret
 315 
 316 # padding
 317 # 201:
 318    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 319 
 320 # 210:
 321 # keyboard interrupt handler:
 322   # prologue
 323   fa  # disable interrupts
 324   60  # push all registers to stack
 325   # acknowledge interrupt
 326   b0 20  # al <- 0x20
 327   e6 20  # port 0x20 <- al
 328   31 c0  # eax <- xor eax;  11/direct 000/r32/eax 000/rm32/eax
 329   # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
 330   e4 64  # al <- port 0x64
 331   a8 01  # set zf if bit 0 (least significant) is not set
 332   74 89  # jump to epilogue if 0 bit is not set [label]
 333 # 21e:
 334   # - if keyboard buffer is full, return
 335   31 c9  # ecx <- xor ecx;  11/direct 001/r32/ecx 001/rm32/ecx
 336   # var index/ecx: byte
 337   8a  # copy m8 at r32 to r8
 338     0d  # 00/mod/indirect 001/r8/cl 101/rm32/use-disp32
 339     28 80 00 00  # disp32 [label]
 340   # al = *(keyboard buffer + index)
 341   8a  # copy m8 at r32 to r8
 342     81  # 10/mod/*+disp32 000/r8/al 001/rm32/ecx
 343     30 80 00 00  # disp32 [label]
 344   # if (al != 0) return
 345   3c 00  # compare al, 0
 346   75 77  # jump to epilogue if != [label]
 347 # 230:
 348   # - read keycode
 349   e4 60  # al <- port 0x60
 350   # - key released
 351   # if (al == 0xaa) shift = false  # left shift is being lifted
 352   3c aa  # compare al, 0xaa
 353   75 0a  # jump to $1 if != [label]
 354   # *shift = 0
 355   c7  # copy imm32 to rm32
 356     05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
 357     10 80 00 00  # disp32 [label]
 358     00 00 00 00  # imm32
 359 # 240:
 360 # $1:
 361   # if (al == 0xb6) shift = false  # right shift is being lifted
 362   3c b6  # compare al, 0xb6
 363   75 0a  # jump to $1 if != [label]
 364   # *shift = 0
 365   c7  # copy imm32 to rm32
 366     05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
 367     10 80 00 00  # disp32 [label]
 368     00 00 00 00  # imm32
 369 # $2:
 370   # if (al & 0x80) a key is being lifted; return
 371   50  # push eax
 372   24 80  # al <- and 0x80
 373   3c 00  # compare al, 0
 374   58  # pop to eax (without touching flags)
 375   75 51  # jump to epilogue if != [label]
 376 # 256:
 377   # - key pressed
 378   # if (al == 0x2a) shift = true, return  # left shift pressed
 379   3c 2a  # compare al, 0x2a
 380   75 0c  # jump to $3 if != [label]
 381   # *shift = 1
 382   c7  # copy imm32 to rm32
 383     05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
 384     10 80 00 00  # disp32 [label]
 385     01 00 00 00  # imm32
 386   eb 41 # jump to epilogue [label]
 387 # 266:
 388 # $3:
 389   # if (al == 0x36) shift = true, return  # right shift pressed
 390   3c 36  # compare al, 0x36
 391   75 0c  # jump to $4 if != [label]
 392   # *shift = 1
 393   c7  # copy imm32 to rm32
 394     05  # 00/mod/indirect 000/subop/copy 101/rm32/use-disp32
 395     10 80 00 00  # disp32 [label]
 396     01 00 00 00  # imm32
 397   eb 31 # jump to epilogue [label]
 398 # 276:
 399 # $4:
 400   # - convert key to character
 401   # if (shift) use keyboard normal map
 402   81  # operate on rm32 and imm32
 403     3d  # 00/mod/indirect 111/subop/compare 101/rm32/use-disp32
 404     10 80 00 00  # disp32 = shift [label]
 405     00 00 00 00  # imm32
 406   74 08  # jump to $5 if = [label]
 407   # otherwise use keyboard shift map
 408   # al <- *(keyboard shift map + eax)
 409   8a  # copy m8 at rm32 to r8
 410     80  # 10/mod/*+disp32 000/r8/al 000/rm32/eax
 411     00 87 00 00  # disp32 [label]
 412   eb 06  # jump to $6 [label]
 413 # 28a:
 414 # $5:
 415   # al <- *(keyboard normal map + eax)
 416   8a  # copy m8 at rm32 to r8
 417     80  # 10/mod/*+disp32 000/r8/al 000/rm32/eax
 418     00 86 00 00  # disp32 [label]
 419 # $6:
 420   # - if there's no character mapping, return
 421   3c 00  # compare al, 0
 422   74 13  # jump to epilogue if = [label]
 423 # 294:
 424   # - store al in keyboard buffer
 425   88  # copy r8 to m8 at r32
 426     81  # 10/mod/*+disp32 000/r8/al 001/rm32/ecx
 427     30 80 00 00  # disp32 [label]
 428   # increment index
 429   fe  # increment byte
 430     05  # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32
 431     28 80 00 00  # disp32 [label]
 432   # clear top nibble of index (keyboard buffer is circular)
 433   80  # and byte
 434     25  # 00/mod/indirect 100/subop/and 101/rm32/use-disp32
 435     28 80 00 00  # disp32 [label]
 436     0f  # imm8
 437 # 2a9:
 438   # epilogue
 439   61  # pop all registers
 440   fb  # enable interrupts
 441   cf  # iret
 442 
 443 # padding
 444 # 2aa:
 445                               00 00 00 00 00 00
 446 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 447 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 448 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 449 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 450 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 451 # 300:
 452 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 453 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 454 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 455 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 456 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 457 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 458 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 459 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 461 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 462 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 463 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 464 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 465 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 466 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 467 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 468 
 469 # offset 400 (address 0x8000): interrupt handler data
 470 # idt_descriptor:
 471   ff 03  # idt_end - idt_start - 1
 472   00 82 00 00  # start = idt_start [label]
 473 
 474 # padding
 475 # 406:
 476                   00 00 00 00 00 00 00 00 00 00
 477 
 478 # 410:
 479 # var shift: boolean
 480   00 00 00 00
 481 
 482 # padding
 483 # 414:
 484             00 00 00 00 00 00 00 00 00 00 00 00
 485 00 00 00 00 00 00 00 00
 486 
 487 # 428:
 488 # var keyboard circular buffer
 489 # write index: nibble
 490 # still take up 4 bytes so SubX can handle it
 491   00 00 00 00
 492 # 42c:
 493 # read index: nibble
 494 # still take up 4 bytes so SubX can handle it
 495   00 00 00 00
 496 # 430:
 497 # circular buffer: byte[16]
 498   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 499 
 500 # padding
 501 # 440:
 502 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 503 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 504 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 505 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 506 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 507 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 508 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 509 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 510 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 511 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 512 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 513 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 514 
 515 # offset 500 (address 0x8100):
 516 +-- 53 lines: # video mode info ---------------------------------------------------------------------------------------------------------------------------------------------------------
 569 
 570 # offset 600 (address 0x8200):
 571 +--161 lines: # interrupt descriptor table ----------------------------------------------------------------------------------------------------------------------------------------------
 732 
 733 ## the rest of this file has data
 734 
 735 # offset a00 (address 0x8600):
 736 +--152 lines: # translating keys to ASCII -----------------------------------------------------------------------------------------------------------------------------------------------
 888 
 889 # offset 1000 (address 0x8c00)
 890 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------
1126 
1127 # offset 1800 (address 0x9400)
1128 
1129 # vim:ft=subx