mbr.S 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*++
  2. Copyright (c) 2014 Minoca Corp.
  3. This file is licensed under the terms of the GNU General Public License
  4. version 3. Alternative licensing terms are available. Contact
  5. info@minocacorp.com for details. See the LICENSE file at the root of this
  6. project for complete licensing information.
  7. Module Name:
  8. mbr.S
  9. Abstract:
  10. This module implements the Master Boot Record code used to bootstrap the
  11. operating system. This code lives on the first sector of the disk.
  12. Author:
  13. Evan Green 4-Feb-2014
  14. Environment:
  15. MBR
  16. --*/
  17. //
  18. // ---------------------------------------------------------------- Definitions
  19. //
  20. //
  21. // Define the segment where the BIOS loads this MBR code.
  22. //
  23. .equ BOOT_ADDRESS, 0x7C00
  24. //
  25. // Define the destination address the MBR copies itself to.
  26. //
  27. .equ RELOCATED_ADDRESS, 0x0600
  28. //
  29. // Define the size of a disk sector.
  30. //
  31. .equ SECTOR_SIZE, 0x0200
  32. //
  33. // Define the (relocated) location of the partition table.
  34. //
  35. .equ PARTITION_TABLE_ADDRESS, (RELOCATED_ADDRESS + 0x1BE)
  36. //
  37. // Define the number of partition table entries.
  38. //
  39. .equ PARTITION_ENTRY_COUNT, 4
  40. //
  41. // Define the size of a partition table entry.
  42. //
  43. .equ PARTITION_ENTRY_SIZE, 0x10
  44. //
  45. // Define the number of times to try and read the disk.
  46. //
  47. .equ DISK_RETRY_COUNT, 0x06
  48. //
  49. // Define offsets into a partition table entry.
  50. //
  51. .equ PARTITION_START_HEAD, 0x1
  52. .equ PARTITION_START_SECTOR, 0x2
  53. .equ PARTITION_START_CYLINDER, 0x3
  54. .equ PARTITION_LBA_OFFSET, 0x8
  55. //
  56. // Define the address of the boot signature.
  57. //
  58. .equ BOOT_SIGNATURE_ADDRESS, (BOOT_ADDRESS + 0x1FE)
  59. //
  60. // ----------------------------------------------------------------------- Code
  61. //
  62. //
  63. // .text specifies that this code belongs in the executable section. This is
  64. // the only section in the MBR code, data also lives in the text section.
  65. //
  66. // .code16 specifies that this is 16-bit real mode code.
  67. //
  68. .text
  69. .code16
  70. //
  71. // Stick this in the .init section so it ends up at the correct address, since
  72. // the build machinery sets the .init section address rather than .text.
  73. //
  74. .section .init
  75. //
  76. // .globl allows this label to be visible to the linker.
  77. //
  78. .globl _start
  79. _start:
  80. //
  81. // Skip the space for the BIOS parameter block in case things get altered
  82. // there.
  83. //
  84. jmp AfterBiosParameterBlock
  85. nop
  86. .org 0x40
  87. AfterBiosParameterBlock:
  88. //
  89. // Copy this code to a location less in the way.
  90. //
  91. xorw %ax, %ax # Zero out AX.
  92. movw %ax, %ds # Zero out DS.
  93. movw %ax, %es # Zero out ES.
  94. movw %ax, %ss # Zero out SS.
  95. movw $BOOT_ADDRESS, %si # Load SI with the source address.
  96. movw $BOOT_ADDRESS, %sp # Also make this the top of the stack.
  97. movw $RELOCATED_ADDRESS, %di # Loads DI with the destination address.
  98. movw $SECTOR_SIZE, %cx # Load the number of bytes to copy.
  99. cld # Decrement CX on repeats.
  100. rep movsb # Copy CX bytes of DS:[SI] to ES:[DI].
  101. //
  102. // Jump to the relocated code.
  103. //
  104. push %ax # Push a zero code segment.
  105. push $AfterMove # Push the destination.
  106. retf # "Return" to the relocated code.
  107. AfterMove:
  108. sti # Enable interrupts so disk reads work.
  109. //
  110. // Search for the active partition.
  111. //
  112. movw $PARTITION_ENTRY_COUNT, %cx # Load the number of table entries.
  113. movw $PARTITION_TABLE_ADDRESS, %bp # Load the first partition entry.
  114. BootPartitionLoop:
  115. cmpb $0, (%bp) # Compare the boot flag to zero.
  116. jl DriveReadLoop # Jump if 0x80 or above (ie negative).
  117. jnz InvalidPartitionTable # If it was 0x1-0x79, that's invalid.
  118. addw $PARTITION_ENTRY_SIZE, %bp # Move to the next table entry.
  119. loop BootPartitionLoop # Loop to compare the next entry.
  120. //
  121. // No entries were found to be bootable. Call INT 0x18, which is "load
  122. // ROM-BASIC". This doesn't exist, so most BIOSes just display something
  123. // like "press a key to reboot".
  124. //
  125. int $0x18
  126. DriveReadLoop:
  127. movb %dl, 0x12(%bp) # Write boot drive number into a local.
  128. pushw %bp # Save the base pointer.
  129. movb $DISK_RETRY_COUNT, 0x11(%bp) # Initialize the retry count.
  130. movb $0, 0x10(%bp) # Mark extensions as not supported.
  131. //
  132. // Check to see if INT 13 extensions are supported. If CF is cleared, BX
  133. // changes to 0xAA55, and the lowest bit in CX is set, then they are
  134. // supported.
  135. //
  136. movb $0x41, %ah # Set AH.
  137. movw $0x55AA, %bx # Set BX to the magic value.
  138. int $0x13 # Call the BIOS.
  139. popw %bp # Restore the original BP.
  140. jb AfterInt13ExtensionsCheck # Jump over the rest of the check.
  141. cmpw $0xAA55, %bx # See if BX changed to 0xAA55.
  142. jnz AfterInt13ExtensionsCheck # Jump over the rest of the check.
  143. test $0x0001, %cx # See if the lowest bit of CX is set.
  144. jz AfterInt13ExtensionsCheck # Jump over if not.
  145. incb 0x10(%bp) # Set the extensions flag as enabled.
  146. AfterInt13ExtensionsCheck:
  147. pushal # Push all 32 bit registers.
  148. cmpb $0, 0x10(%bp) # Check the INT13 extensions flag.
  149. jz ReadWithoutExtensions # If not set, go to the old read.
  150. //
  151. // Perform an INT 13 extended read of the first sector of the partition,
  152. // and load it into memory where the MBR was loaded. The extended read
  153. // takes a disk packet that looks like this:
  154. // Offset 0, size 1: Size of packet (16 or 24 bytes).
  155. // Offset 1, size 1: Reserved (0).
  156. // Offset 2, size 2: Number of blocks to transfer.
  157. // Offset 4, size 4: Transfer buffer.
  158. // Offset 8, size 8: Absolute starting sector.
  159. // Offset 0x10, size 8: 64-bit flat address of transfer buffer. Only used
  160. // if the value at offset 4 is 0xFFFFFFFF (which it is not in this case).
  161. //
  162. // Remember that things need to be pushed on in reverse.
  163. //
  164. pushl $0 # Push starting sector high.
  165. pushl PARTITION_LBA_OFFSET(%bp) # Push the starting sector low.
  166. pushl $BOOT_ADDRESS # Push the transfer buffer.
  167. pushw $1 # Push the sector count.
  168. pushw $0x0010 # Push reserved and packet size.
  169. movb $0x42, %ah # Function 0x42, extended read.
  170. movb 0x12(%bp), %dl # Load the drive number.
  171. movw %sp, %si # SI points to the disk packet address.
  172. int $0x13 # Read the sector from the disk.
  173. //
  174. // Check the status of the extended read.
  175. //
  176. lahf # Load the flags into AH.
  177. addw $0x10, %sp # Pop the disk packet off the stack.
  178. sahf # Restore the flags into AH.
  179. jmp AfterDiskRead # Jump to the next part.
  180. //
  181. // Perform an INT 13 regular read because extensions are not supported.
  182. // AH: Function 2 (read sectors).
  183. // AL: Sector count.
  184. // ES:BX: Transfer buffer.
  185. // DL: Disk drive number.
  186. // DH: Head number.
  187. // CL: Sector number plus low two bits of the cylinder number.
  188. // CH: High bits of the cylinder number.
  189. //
  190. ReadWithoutExtensions:
  191. movw $0x0201, %ax # Load function 2, sector count 1.
  192. movw $BOOT_ADDRESS, %bx # Load the buffer address.
  193. movb 0x12(%bp), %dl # Load the drive number.
  194. movb PARTITION_START_HEAD(%bp), %dh # Load the head number.
  195. movb PARTITION_START_SECTOR(%bp), %cl # Load the sector number.
  196. movb PARTITION_START_CYLINDER(%bp), %ch # Load the cylinder number.
  197. int $0x13
  198. AfterDiskRead:
  199. popal # Restore all 32 bit registers.
  200. jnb DiskReadSuccess # If ok, jump out of the disk read loop.
  201. //
  202. // The disk read failed. Decrement the retry counter and try again.
  203. //
  204. decb 0x11(%bp) # Decrement retry counter.
  205. jnz ResetDrive # Try again if not zero.
  206. //
  207. // If this was already drive 0x80, then fail. If it wasn't already 0x80,
  208. // then try this all again with drive 0x80, the default BIOS boot disk.
  209. //
  210. cmpb $0x80, 0x12(%bp) # Compare the drive number to 0x80.
  211. jz DiskReadError # Give up if it's already 0x80.
  212. movb $0x80, %dl # Set the drive to 0x80.
  213. jmp DriveReadLoop # Try it all again.
  214. //
  215. // Call INT 13, function 0 to reset the drive. This takes the drive number
  216. // in dl.
  217. //
  218. ResetDrive:
  219. pushw %bp # Save BP.
  220. xorb %ah, %ah # Zero out AH.
  221. movb 0x12(%bp), %dl # Load the drive number.
  222. int $0x13 # Reset the drive.
  223. popw %bp # Restore BP.
  224. jmp AfterInt13ExtensionsCheck # Try the read again.
  225. DiskReadSuccess:
  226. cmpw $0xAA55, BOOT_SIGNATURE_ADDRESS # See if the partition can boot.
  227. jnz PartitionNotBootable # Fail if the signature is not there.
  228. pushw 0x12(%bp) # Push the drive number.
  229. //
  230. // Enable the A20 address line. The 8088 processor in the original PC only
  231. // had 20 address lines, and when it reached its topmost address at 1MB it
  232. // would silently wrap around. When the 80286 was released it was designed
  233. // to be compatible with programs written for the 8088, which had come to
  234. // rely on this wrapping behavior. The 8042 keyboard controller had an
  235. // extra pin, so the gate to control the A20 line behavior on newer (286)
  236. // processors was stored there. The keyboard status register is read from
  237. // port 0x64. If bit 2 is set (00000010b) the input buffer is full, and the
  238. // controller is not accepting commands. The CPU can control the keyboard
  239. // controller by writing to port 0x64. Writing 0xd1 corresponds to "Write
  240. // Output port". Writing 0xDF to port 0x60, the keyboard command register,
  241. // tells the controller to enable the A20 line (as it sets bit 2, which
  242. // controlled that line).
  243. //
  244. call WaitForKeyboardController # Wait for not busy.
  245. jnz AfterA20Line # Jump out if stuck.
  246. cli # Disable interrupts.
  247. movb $0xD1, %al # Send command 'Write output port'.
  248. outb %al, $0x64 # Write 0xD1 to port 0x64.
  249. call WaitForKeyboardController # Wait for not busy.
  250. movb $0xDF, %al # Set the mask.
  251. outb %al, $0x60 # Write 0xDF to port 0x60.
  252. call WaitForKeyboardController # Wait for not busy again.
  253. movb $0xFF, %al # Clear the command.
  254. outb %al, $0x64 # Write 0xFF to port 0x60.
  255. call WaitForKeyboardController # One last time.
  256. sti # Re-enable interrupts.
  257. //
  258. // Jump to the Volume Boot Record this code worked so hard to load. Set
  259. // DL to the drive number and DS:SI to point to the partition table entry.
  260. //
  261. AfterA20Line:
  262. popw %dx # Restore drive number.
  263. xorb %dh, %dh # Clear DH.
  264. movw %bp, %si # Set SI.
  265. jmp $0, $BOOT_ADDRESS # Blow this popsicle stand.
  266. //
  267. // In error cases down here, print a message and die sadly.
  268. //
  269. DiskReadError:
  270. movw $ReadFailureMessage, %si
  271. jmp PrintStringAndDie
  272. InvalidPartitionTable:
  273. movw $InvalidPartitionTableMessage, %si
  274. jmp PrintStringAndDie
  275. PartitionNotBootable:
  276. movw $NoOsMessage, %si
  277. jmp PrintStringAndDie
  278. //
  279. // Print a null-terminated string, and then end it.
  280. //
  281. PrintStringAndDie:
  282. cld # Clear the direction flag.
  283. lodsb # Load SI into AL.
  284. cmp $0, %al # Is this the null terminator?
  285. jz Die # If so, then go quietly into the night.
  286. movw $0x0007, %bx # Display normal white on black.
  287. movb $0x0E, %ah # Print character function.
  288. int $0x10 # Print the character.
  289. jmp PrintStringAndDie # Loop printing more characters.
  290. //
  291. // This is the end of the line.
  292. //
  293. Die:
  294. hlt # Stop.
  295. jmp Die # Die again forever.
  296. //
  297. // This routine waits for the keyboard controller's busy bit to clear.
  298. //
  299. WaitForKeyboardController:
  300. xorw %cx, %cx # Zero out CX.
  301. WaitForKeyboardControllerLoop:
  302. inb $0x64, %al # Read port 0x64.
  303. jmp WaitForKeyboardControllerDelay # Add in a tiny bit of delay.
  304. WaitForKeyboardControllerDelay:
  305. and $0x2, %al # Isolate the second bit.
  306. loopne WaitForKeyboardControllerLoop # Loop if it's still set.
  307. and $0x2, %al # Set the flags for return.
  308. ret # Return.
  309. //
  310. // Define the messages that are printed from the MBR.
  311. //
  312. ReadFailureMessage:
  313. .asciz "MBR Read Error"
  314. NoOsMessage:
  315. .asciz "No OS"
  316. InvalidPartitionTableMessage:
  317. .asciz "Invalid partition table"
  318. .if 0
  319. PrintShort:
  320. pushw %ax
  321. movb %ah, %al
  322. call PrintByte
  323. popw %ax
  324. call PrintByte
  325. movb $' ', %al
  326. call PrintCharacter
  327. ret
  328. PrintByte:
  329. pushw %ax
  330. shr $4, %al
  331. cmp $0xA, %al
  332. sbb $0x69, %al
  333. das
  334. call PrintCharacter
  335. popw %ax
  336. ror $4, %al
  337. shr $4, %al
  338. cmp $0xA, %al
  339. sbb $0x69, %al
  340. das
  341. PrintCharacter:
  342. movw $0x0007, %bx # Display normal white on black.
  343. movb $0x0E, %ah # Print character function.
  344. int $0x10 # Print the character.
  345. ret
  346. .endif
  347. //
  348. // Define the signature that the BIOS uses to determine this disk is bootable.
  349. //
  350. .org 0x1FE
  351. .byte 0x55
  352. .byte 0xAA