Hi,
01000101 wrote:I am just getting around to weening my OS off of GRUB. I've been looking over some bootstraps out there and I have a decent idea on how to enable the A20 line. What I am wondering is if one can do these things in C instead of ASM? If so, how should I go about doing it?
It's impossible to do it entirely in pure C - you must use assembly (or inline assembly) for things like accessing BIOS functions, enabling protected mode, accessing I/O ports, enabling/disabling interrupts, etc. As others have mentioned, finding a good compiler for 16-bit/real mode code can also be difficult.
However, with about 200 bytes of assembly you can do almost everything else in 32-bit pure C code.
The way I'd do it (if I wasn't an assembly programmer) would be to have some startup code written in assembly that disable interrupts, enables 32-bit protected mode and calls the main C function. Then I'd have a function (written in assembly) that switches back to real mode (or unreal mode), enables interrupts, loads input parameters/registers from a structure, calls a BIOS interrupt, stores return parameters/registers in a structure, disables interrupts, enables 32-bit protected mode and returns to the 32-bit C code that called it.
The only thing you'd need to be careful of is that you don't spend too much time in protected mode while you're still using the BIOS and the BIOS's real mode IRQ handlers (if you spend too much time in protected mode you can lose IRQs). Of course you could have a "return to real mode to handle any pending IRQs" function that you can call throughout your code to avoid this problem (if necessary).
The other problem is getting the linking and address ranges right (for e.g. - if your "enable A20" code is written in C, then your C code will need to be below 1 MB).
Once it all works you'd might end up with something that looks slightly like:
Code: Select all
struct RealMode_Regs {
int myEAX;
int myEBX;
int myECX;
int myEDX;
int myEBP;
int myDS;
int myES;
int myFlags;
}
void main(void) {
/* Set 80*25 video mode and clear the screen using the BIOS */
RealMode_Regs.eax = 0x0003;
DO_BIOS_INTERRUPT(0x10);
if( (RealMode_Regs.flags &= FLG_CARRY) != 0) {
/* Failed to change video mode - assume headless?? */
for(;;) { }
}
/* Lock up (need to implement more code here instead) */
for(;;) { }
}
int readSector(void *buffer, int device, int head, int cylinder, int sector, int sectorCount) {
int diskAttempts = 0;
for(;;) {
diskAttempts++;
RealMode_Regs.eax = (0x02 << 8) | sectorCount;
RealMode_Regs.ecx = (cylinder << 8) | sector;
RealMode_Regs.edx = (head << 8) | device;
RealMode_Regs.ebx = (unsigned int) (buffer & 0x000F);
RealMode_Regs.es = (unsigned int) (buffer >> 4);
DO_BIOS_INTERRUPT(0x13); // Load sector
if( (RealMode_Regs.flags &= FLG_CARRY) == 0) return;
if( diskAttempts >= 4 ) return (RealMode_Regs.eax >> 8) & 0xFF;
RealMode_Regs.eax = 0x0000;
RealMode_Regs.edx = 0x0000;
DO_BIOS_INTERRUPT(0x13); // Reset disk system
}
}
IMHO this sort of thing would suck (overhead and code size), but because it's a relatively small amount of early boot code (that's only run once) performance won't matter.
Of course even though you'd be using C, you still need to worry about CPU registers and the BIOS's API...
Note: Using "
__asm__ __volatile__ ("pusha");" and "
__asm__ __volatile__ ("popa");" in C code will make it impossible for the C compiler to correctly find function arguments and local variables. Don't do it.
Cheers,
Brendan