Page 1 of 2
"bootloader" in C?
Posted: Thu Nov 01, 2007 9:41 pm
by 01000101
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?
edit: I have a prototype Enable_A20 function in C, can someone tell me if this has any sort of substance or if it is just not possible to do from C.
Code: Select all
void Enable_A20()
{
ERR:
char port_60_status = 0x00;
__asm__ __volatile__ ("cli"); // Disable Interrupts
// Wait for the controller to be ready for a command
while(((inb(0x64) >> 0x01) & 0x01)== 0x01){} // Test bit 1 of 0x64
outb(0xD0, 0x64); // send 0xD0 to port 0x64
// Wait for the controller to be ready with a byte of data
while((inb(0x64) & 0x01) == 0x00){}
port_60_status = inb(0x60); // Read & Save the current port status from port 60h
// Wait for the controller to be ready for a command
while(((inb(0x64) >> 0x01) & 0x01)== 0x01){} // Test bit 1 of 0x64
outb(0xD1, 0x64); // send 0xD1 to port 0x64
// Wait for the controller to be ready for the data
while(((inb(0x64) >> 0x01) & 0x01)== 0x01){} // Test bit 1 of 0x64
port_60_status &= 0x02;
outb(port_60_status, 0x60); // Turn on the A20 enable bit
// Finally, we will attempt to read back the A20 status
// to ensure it was enabled.
// Wait for the controller to be ready for a command
while(((inb(0x64) >> 0x01) & 0x01)== 0x01){} // Test bit 1 of 0x64
outb(0xD0, 0x64); // send 0xD1 to port 0x64
// Wait for the controller to be ready with a byte of data
while((inb(0x64) & 0x01) == 0x00){}
if(((inb(0x60) >> 0x01) & 0x01) == 0x01) // if true, A20 is ENABLED!
{
__asm__ __volatile__ ("sti"); // Enable interrupts
}
else{goto ERR;} // A20 is NOT enabled, restart the process
}
Posted: Fri Nov 02, 2007 6:02 am
by Combuster
Theoretically its possible. However:
most use 32-bit C code and since you want to use your own boot loader you'll most likely end up below the 1MB mark, which can make things difficult due to the lack of space you find there, or you'd be using 16-bit C code for which it is difficult to find a good toolchain.
Which means that IMHO the relevant question is: Is there any reason to not do it in assembly?
P.S. You are already using the goto's
Posted: Fri Nov 02, 2007 8:09 am
by os64dev
ok, for a small lesson in C:
if you have in assembly the construct:
Code: Select all
label: <do stuff>
test <condition>
jcc label
<continue>
in C you use
Code: Select all
while(<condition> == false) <do stuff>
<continue>
Re: "bootloader" in C?
Posted: Fri Nov 02, 2007 9:43 am
by Brendan
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
Posted: Fri Nov 02, 2007 10:13 am
by phioust
I am not sure if this will be helpful for your project but I will pretend it is anyway
http://kernelnewbies.org/LinuxChanges#h ... 90ef8f7d2c
2.16. Rewrite the x86 asm setup in C
In 2.6.23 the x86 setup code, which is currently all in assembly, is replaced with a version written in C, using the ".code16gcc" feature of binutils. The new code is vastly easier to read and debug. It should be noted that a fair number of minor bugs were found while going through this code, but new ones could have been created, due to the extreme fragility of a part of the kernel like this. During testing, it has showed to be very stable.
Posted: Fri Nov 02, 2007 10:56 am
by 01000101
That was indeed a helful link phioust.
could someone tell me why the afore mentioned code doesnt enable the A20 gate? because, if it does, or is just a little buggy, then I would like to persue this a bit further.
The link phioust gave me has different methods in C to do things like mask the master and slave PIC IRQ's so that they dont signal interrupts for the duration of setting up the A20.
also, because I am working in C for the enable_A20 function, is the pusha/popa irrelevant because I am not directly accessing registers the traditional way?
Posted: Fri Nov 02, 2007 11:15 am
by frank
You don't need the pusha and the popa because the C compiler will automatically take care of preserving all important registers. Of course this is an assumption that only holds true if you don't have any asm("") blocks in your code that access the registers directly which I don't see.
It is possible to enable A20 from with C code. The code you posted, not the cleanest code I've ever seen, may in fact enable the A20 gate. I haven't had time to look over it. However, I think that a good deal of people enable in assembly as part of the routine to goto protected mode. The code you posted would most likely have to be run in protected mode unless you can find a good 16 bit C compiler. That wouldn't be that big of a problem as long as you remembered that before you called the function you wouldn't be able to access every other megabyte of memory.
If I were doing a boot loader in C I would enable A20 then enable protected mode and then do all BIOS accesses through a special assembly function. Just like what Brendan said in his post.
01000101 wrote:
The link phioust gave me has different methods in C to do things like mask the master and slave PIC IRQ's so that they dont signal interrupts for the duration of setting up the A20.
I don't believe that is necessary as long as you keep interrupts disabled while messing with A20.
Posted: Fri Nov 02, 2007 11:43 am
by 01000101
I cleaned up the code above with the afore-mentioned asm->C "good practices"
what BIOS interrupts must I use prior to enterring P-Mode?
I already have set up things like direct video-memory usage for writing in text mode. I don't require HDD or Floppy (besides booting off of initially).
Posted: Fri Nov 02, 2007 12:11 pm
by frank
The only BIOS interrupts in my second stage bootloader are 0x15 to get the memory map and 0x13 to load the kernel and its modules from the disk. Those are the only two interrupts I use in my bootloader.
Posted: Fri Nov 02, 2007 11:19 pm
by 01000101
I have an assembly mini-kernel written in [bits 32] that takes care of the A20 and entering PMode. I thought the only way to use the [bits 32] was if you were already in PMode or Unreal mode??? I'm guessing GRUB has handled that part and I am re-setting up the GDT and all the other fun stuff after its already been done by GRUB???
All I want to do is have a verrrrry simple bootloader load my mini-kernel, then the mini-kernel in asm will take care of the PMode startup.
Posted: Sat Nov 03, 2007 2:53 pm
by frank
When GRUB finishes and jumps to your kernel you are already in protected mode with A20 enabled and you have a temporary GDT that you should replace with your own as soon as possible.
At least at first I recommend sticking to GRUB as your bootloader. It will be a lot easier that way.
Posted: Sat Nov 03, 2007 3:28 pm
by 01000101
I'm already past the 'at first' stage. I'm at the point where I need to write my own bootsector so that I can troubleshoot any issues that will arise early instead of later when the OS is more robust and harder to debug.
Posted: Sat Nov 03, 2007 4:04 pm
by AndrewAPrice
You could declare your variables are register instead of auto, and make everything local. It might help to reduce size.
Posted: Sat Nov 03, 2007 4:30 pm
by Brendan
Hi,
frank wrote:The only BIOS interrupts in my second stage bootloader are 0x15 to get the memory map and 0x13 to load the kernel and its modules from the disk. Those are the only two interrupts I use in my bootloader.
Mine uses several Int 0x15 functions to detect memory, one Int 0x15 function to (attempt to) enable A20, a PCI BIOS function to detect the configuration space access mechanism, and video services to initialize a default video mode.
On top of this, the floppy boot loader uses disk services to load a boot image (int 0x15), and the network boot loader uses the boot ROM's functions to load a boot image. Boot loaders for hard disk, CD-ROM and USB would also use disk services (when I write them).
Sooner or later I'll probably also use APM functions in real mode to detect if APM is supported and what it supports (e.g. so the OS can decide if it wants to use APM or not after boot).
I can't think of any other BIOS functions you might want (except for keyboard services if you have some sort of boot menu).
Cheers,
Brendan
Posted: Sat Nov 03, 2007 5:39 pm
by frank
01000101 wrote:I'm already past the 'at first' stage. I'm at the point where I need to write my own bootsector so that I can troubleshoot any issues that will arise early instead of later when the OS is more robust and harder to debug.
Well in that case go for it. What exactly is it that you need from a bootloader that GRUB cannot provide? I'm just curious, I'm not trying to change your mind or anything. I just finished working on a second stage bootloader that will load my OS just as GRUB does and acts just like it except for the fact that it will only work for my OS. Of course I made it so that GRUB or my bootloader can load my OS with the same results. I just want it too be easy for people to use my OS.