By Deepak Sirone on
This is the second blog post about implementing a FAT32 filesystem in Redox. In the previous blog post the future work was detailed as follows:
- Add printing/debug capability to the stub [done]
- Add MBR/GPT parsing functionality to the stub [done]
- Begin work on the bootloader FAT32 module [done]
- Decide on things like where to load the module in memory, location of the kernel on disk etc. [TODO]
- Write a blog post on Redox’s memory map and related stuff [TODO]
Initially the idea was to write a stub from the ground up and the reason behind this was to minimize the code size in order to have a fast boot. But it turned out to be very tedious and a lot of the work was already done in the Redox kernel. The fully fledged Redox kernel is about 8.6 MB in size. So the effort was on to strip down the Redox kernel to just support what was necessary to have disk reads.
Stripping down the kernel started with the serial console debug support. Qemu maps the serial console to stdio with the
-serial mon:stdio option. The serial console code depended on the arch submodule and the syscall crate and they were pulled in. After the arch submodule was pulled in I realized that all the subsystems initialized in the kstart function in start.rs would be useful at some point. So I began to get each of the subsystems working.
Getting the paging and the GDT initializations to work was straightforward. Setting up the IDT required that all the external interrupt handlers be disabled. Syscalls are completely disabled as the INT 80h handler is mostly commented out.
With the base stub ready I started working on getting disk reads working. Initially the idea was to port the ahci driver such that it dosen’t make use of Redox schemes. Again that meant a near complete rewrite of the driver and after discussions with @jackpot51 it was decided that the disks should be read after dropping to real mode, using BIOS interrupts.
To do this I referred to the OsDev article here. The code should be loaded to a location which can be addressed in real mode i.e. a usable chunk of memory below the 1MB mark. The pages where the code is loaded should be both executable and writable as there is an interleaving of code and data. The stub adaptation of this code is here.
Roughly the code works as follows:
- Disable interrupts as we are going to alter the IDT pointer
- Save the stack pointer, the present IDTR, the GDTR and the stack arguments
- Load the address of a new GDT into the GDTR and the address of the real mode IDT into the IDTR
- The new GDT has a 16-bit protected mode code descriptor as well as the 64-bit Redox code descriptor
- Do a far return into 16-bit protected mode by executing a 64-bit
- In 16-bit protected mode, paging and protected mode is turned off and the stack is set to the real mode location
- All the segment registers are loaded with 0 so they can address from
0xffffthat is the first segment
- Do a far jmp to real mode code
- In real mode the disk interrupt
INT 13his called and is tested if the read worked
- After the operations in real mode are complete, paging and protected mode is enabled
- A far jmp is used to get back to kernel mode code
- The original GDT is restored and all the data segments are restored with their appropriate descriptors
The real mode is loaded at address
0xb000 during booting. The pages starting from
0xa000 are used for the real mode stack. The pages starting from
0x70000 can be used to read in disk data. This gives a total of 11 pages(each page being 4096 bytes) worth of disk data per drop into real mode. After some more testing the code can be moved further towards the bootsector ending address of
0x7e00 giving more disk data per drop.
Before dropping to real mode from the stub, all the pages from
0x70000 are identity mapped and given write permission in the init_real_mode function. The function which drops to real mode can be found here
The drop works as follows:
- Cast the address
0xb000as an extern “C” function using the
- [TODO] Compute the amount of data needed to be read in the drop
- Save all the registers in the stack and call the function
- [TODO] Return and copy all the data that is read into the supplied buffer from the pages mentioned above
- [TODO] Jump to step 2 till no more bytes are left to be read
The stub is currently about 250K in size. There is still a lot of code which can be eliminated to make the code even smaller. For some reason printing in real mode using
INT 10h causes the far jmp into long mode to fail.
- Write a clean read() and if possible a write() API
- Decide on a FAT32 implementation
- Write adapters/wrappers over the read/write API to support the FAT32 implementation
- Decide on where to put the kernel in the filesystem
- Read in the kernel and transfer execution to it
- Cleanup unnecessary code in the stub to make the code even smaller