OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 8:17 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 31 posts ]  Go to page 1, 2, 3  Next
Author Message
 Post subject: Mixing 32bit and 16bit code (comming down into real mode)
PostPosted: Wed Apr 16, 2008 12:41 pm 
Offline
Member
Member
User avatar

Joined: Fri Mar 07, 2008 3:40 am
Posts: 179
Location: Arad, Romania
This is a beginner question on multiple levels, so bear with me. :)

My goal is to use APM to properly shut down the computer. My plan was to briefly jump out of protected mode into real mode, enable the real mode APM interface, and then set the power state for all devices to off, effectively shutting down the computer.

My problem is not with the APM code, rather I am having a problem coming down into real mode. I know next to nothing about real mode (I let GRUB boot my kernel into protected mode) so it is highly possible that I'm doing everything completely wrong.

First question, will this code safely bring me down into real mode or do I need to do more?
Code:
enter_real_mode:
   cli                  ;**switch to real mode**
   mov eax,cr0            ;get the cr0 regester
   and eax,0x7ffffffe   ;disable protected mode
   mov cr0,eax            ;set the new cr0 regester
   sti
   ret


My second question is if I need to do anything special after returning from this function, or even if I can return from it (being that all instructions after "mov cr0,eax" need to be 16bit AFAIK). As you can see, I'm really lost. Help is much appreciated.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 16, 2008 6:00 pm 
Offline
Member
Member

Joined: Sat Nov 18, 2006 9:11 am
Posts: 571
Well, firstly, you need to make sure that the EIP you are at is a valid 16-bit EIP. You also must first drop down to 16-bit pmode before making the switch, then you also have to make sure you are at a legitemate physical address (paging not available in real-mode). So, to recap:
Must be <1mb (well, below 0x100FEFF), in 16-bit pmode, and at a valid physical page (not virtual). If all these are met, you now have to make sure the function knows where it's located, and it must support 16 and 32-bit relocations (well, unless you drop into 16-bit pmode on entry, then it must only support 16-bit relocations). The easiest method would be like this:

Memory map bottom 1mb (or however much you need, probably 4k is plenty), setup a 16-bit code and data segment, then reserve a block of memory below 1mb for your program (binary is nice, because elf, coff, etc don't support 16-bit relocations, and it'll have to be written in ASM). So, now we write the entry code similar to this:

Code:
[bits 16]
[org 0x1000] ;Whatever you want here, 16-bit reserved location
Entry16:
  cli ;Disable interrupts
  mov eax, DATASEL16 ;16 bit data selector
  mov ds, eax
  mov es, eax
  mov fs, eax
  mov gs, eax
;Disable paging, works because it's a 1:1 mapping!
  mov eax, cr0
  and eax, 0x7FFFFFFe ;Disable paging bit & enable 16-bit pmode
  mov cr0, eax
  jump 0x0000:GoRMode ;Perform Far jump to setup CS :)
GoRMode:
  mov ax, 0x0000  ;Reset data selectors to 0x0000
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  lidt 0x000    ;Real move IVT is @ 0x0000
  sti               ;Restore interrupts, be careful, unhandled int's will kill it
;And finally we are ready to play in real mode!


This code was written in this window, I may have missed a step or so, but should get you pointed in the right direction. Be careful enabling ints, you probably want to disable your PIC or APIC before entering real-mode so you don't have unhandled interrupts firing. You can go back into p-mode after this, just remember to reset the IDT, re-enable everything (paging, pmode bit), do a retf (far return, so it pops the Code segment back off as well, and when you call this you need to do a far call (callf CODESEL16:0x1000) to the function. So, basically it's not difficult, just have to remember that real-mode and p-mode are very different (16-bit, no virtual memory, IVT instead of IDT, cannot access > 1mb, etc, etc). If you run into more problems, let us know, that should get you started pretty nicely though. The other option (not sure how APM works exactly, so this may or may not be viable) is v86 mode.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 16, 2008 11:19 pm 
Offline
Member
Member
User avatar

Joined: Fri Mar 07, 2008 3:40 am
Posts: 179
Location: Arad, Romania
So, if I'm following you correctly I need to compile some (valid) 16bit code into my kernel, then memcpy() the code to somewhere below 1mb, and then jump to it. A lot of real mode memory maps show that 0x00000500 - 0x00007BFF is unused, would be a good place to copy the code?

Also, when I set up paging I identity map the whole first megabyte + the size of my kernel so that shouldn't be a problem.

Ready4Dis wrote:
The other option (not sure how APM works exactly, so this may or may not be viable) is v86 mode.
Is v86 mode any "easier" to enter from protected mode? I ask this because I only intend to drop as far as needed out of protected mode in order to call the BIOS routines for APM.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 4:52 am 
Offline
Member
Member

Joined: Sat Nov 18, 2006 9:11 am
Posts: 571
Well, I guess that all depends on your idea of "easier" :). If you have proper multitasking and interrupt handlers it may be easier (when I say proper I mean, ring 3- >ring 0 with a TSS, etc). The good thing about v86 is that you can still use virtual memory, so you can 'virtually' put your code anywhere below 1mb, which makes it simpler than allocating real memory under 1mb, although like you said, that is a perfectly valid memroy range, I tend to start at 0x1000 rather than 0x500 (page boundary, a bit out of the way so I don't accidentally mess something up, etc). One thing to be concerned with is that certain things need to be handled in v86 mode (like priveledged calls), where in real mode these this don't exist. The supporting code for v86 is more, but it is better overall: you don't have to disable interrupts so nothing is missed, virtual memory still works so you can get the interrupts to write to anywhere in physical memory with no unreal mode tricks, the switch is a simple iret to the task, etc.

I forgot in my little writeup to set esp to a valid real-mode address, so be sure you do that as well, probably make the origin 0x1000 and then set esp = 0x2000 so it has 4096-functionsize bytes for the stack. Then restore ESP on exit. I use a similar method in my kernel where I have a function to call an interrupt from real-mode, it drops from pmode->rmode loads the registers with the values off the stack, sets the interrupt #, calls interrupt, stores return value, goes back to pmode, restores the return values into the registers, and returns :).


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 7:15 am 
Offline
Member
Member
User avatar

Joined: Mon Feb 04, 2008 3:58 am
Posts: 232
I believe the next question would be: "How do you call real mode interrupts from V86 mode without restoring the IDT"?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 9:31 am 
Offline
Member
Member
User avatar

Joined: Wed Oct 18, 2006 3:45 am
Posts: 9301
Location: On the balcony, where I can actually keep 1½m distance
zaleschiemilgabriel wrote:
I believe the next question would be: "How do you call real mode interrupts from V86 mode without restoring the IDT"?

The IDT needs absolutely no change at all.

_________________
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 10:08 am 
Offline
Member
Member
User avatar

Joined: Fri Mar 07, 2008 3:40 am
Posts: 179
Location: Arad, Romania
Ready4Dis wrote:
Well, I guess that all depends on your idea of "easier" :). If you have proper multitasking and interrupt handlers it may be easier (when I say proper I mean, ring 3- >ring 0 with a TSS, etc).

Right, I have multitasking in my kernel but apparently not "proper" multitasking... :) From reading your description of v86 mode, I think I'll stick to plain real mode. After entering real mode, I'm not planning on going back to pmode anyway (it is "shutdown" code after all...).

Ready4Dis wrote:
I forgot in my little writeup to set esp to a valid real-mode address, so be sure you do that as well, probably make the origin 0x1000 and then set esp = 0x2000 so it has 4096-functionsize bytes for the stack.
My problem right now is actually getting the the code compiled into my kernel with [ORG 0x1000] at the top of "apm.asm" (the code file). I believe this is because ELF (I compile my kernel to ELF) doesn't support the "org" directive. Is this correct? (If I compile "apm.asm" into a binary object file it works fine but then I can't link it with the rest of my kernel...) Is there another way to link binary object code with an ELF kernel or do I need to load the code in a different way? (I am using NASM btw.)


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 11:40 am 
Offline
Member
Member

Joined: Sat Nov 18, 2006 9:11 am
Posts: 571
Well, Elf does not support 16-bit relocations, so you can throw linking with your kernel out the window. The 3 easiest ways, is to either A.) insert the binary into your kernel (maybe via a hex editor and copy/paste the hex codes), B.) append it to the end of your kernel and know the kernels end, so you can figure out the start of the code to copy, C.) have it be a seperate file that gets loaded seperately.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 2:28 pm 
Offline
Member
Member
User avatar

Joined: Fri Mar 07, 2008 3:40 am
Posts: 179
Location: Arad, Romania
Ready4Dis wrote:
Well, Elf does not support 16-bit relocations...
I wonder why? Not that it really matters, I just wonder.

Ready4Dis wrote:
C.) have it be a seperate file that gets loaded seperately.
I decided to go with this option and have GRUB load the binary object file as a module. I now have it loaded an memcpy()'d to the address 0x1000 putting the entry point for the code at 0x2000 (I have verified this from within my kernel). My question now is, how do I "jump" to the code? I've tried doing what I thought was a correct far jump to 0x2000 (the known entry point) but it gives me a general protection fault. The Bochs output is "fetch_raw_descriptor: GDT: index (1007)200 > limit (17) " I do the jump like this: "jmp 0x1000:0x1000".


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 17, 2008 7:40 pm 
Offline
Member
Member

Joined: Sat Nov 18, 2006 9:11 am
Posts: 571
Well, firstly let me explain a few things because you seem slightly confused.

protect mode:
A long jump is a jump to a location via a segment descriptor... so unless you have a segment descriptor 0x1000 in your GDT, it will not work properly. Typically a far jump in pmode looks like this jmp 0x08:0x1000. It means, using selector located at 0x08 in my GDT, jump to relative offset 0x1000. So, if we have a flat memory model, 0x08 means starting at 0x0000 jump to offset 0x1000, so 0x0000+0x1000 = 0x1000 :). However, if you're already in protected mode and in a flat memory model, there is no reason to do a far jump, a regular jump will suffice, or just a regular call... call dword 0x1000 (call if you plan on returning). The error makes sense, because you are doing 0x1000, each entry is 8 bytes, so 0x1000/8 = index 200 (ala, your error). Also, if you have the code linked as a binary @ 0x1000, and memcopy it to 0x1000, then a simple jmp or call to 0x1000 should work (not sure what the entry point code is about).

Elf, Coff, etc don't support 16-bit, a.out is one of the few that support multiple sized relocations in one file.
/rant
I think that any relocation format that is supposedly 'generic' (elf, coff) should include multiple sized relocations, it just doesn't make sense to have multiple versions of the same file, if I want to write a 64-bit and 32-bit OS, I need to have 2 seperate everything, i can't make one elf file that has 32-bit and 64-bit code to check for 64-bit compatibility and start it up, i need a 16-bit stage, then a 32-bit stage, then a 64-bit stage, what a pain :). I know why they do it, because where do you stop? 64-bit, 128-bit, 256-bit? Whatever you choose, the default pointer size in the file must all be of this size, so if you are using 256-bit pointers to write a 16-bit r-mode OS, it seems like a big waste of space, but i would have thought something like 64/32 bit support in one file (elf) wouldn't have been asking the world of anyone, especially since 64-bit CPU's where available when the file format was 'invented'. /end rant


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 18, 2008 12:30 am 
Offline
Member
Member
User avatar

Joined: Fri Mar 07, 2008 3:40 am
Posts: 179
Location: Arad, Romania
Ready4Dis wrote:
protect mode:
A long jump is a jump to a location via a segment descriptor... so unless you have a segment descriptor 0x1000 in your GDT, it will not work properly. Typically a far jump in pmode looks like this jmp 0x08:0x1000. It means, using selector located at 0x08 in my GDT, jump to relative offset 0x1000. So, if we have a flat memory model, 0x08 means starting at 0x0000 jump to offset 0x1000, so 0x0000+0x1000 = 0x1000 :). However, if you're already in protected mode and in a flat memory model, there is no reason to do a far jump, a regular jump will suffice, or just a regular call... call dword 0x1000 (call if you plan on returning). The error makes sense, because you are doing 0x1000, each entry is 8 bytes, so 0x1000/8 = index 200 (ala, your error). Also, if you have the code linked as a binary @ 0x1000, and memcopy it to 0x1000, then a simple jmp or call to 0x1000 should work (not sure what the entry point code is about).
I reserve 0x1000 bytes at the beginning of "apm.asm" for the data segment and the stack, like you suggested. This means that the actual code starts at 0x2000. Also, I've tried doing both "jmp 0x2000" and "jmp 0x08:0x2000" like you pointed out but it still gives me the same error. I also tried "jumping" in C using a function pointer:
Code:
void (*shutdown2)(void*);
   shutdown2 = 0x2000;
   void* arg = 0x00;
   shutdown2(arg);

and that gives me the exact same GPF. This makes me think that something might be screwy elsewhere...


Top
 Profile  
 
 Post subject: Re: Mixing 32bit and 16bit code (comming down into real mode
PostPosted: Fri Apr 18, 2008 3:32 am 
Offline
Member
Member

Joined: Wed Oct 25, 2006 5:12 am
Posts: 94
Location: Barcelona España
This is the code i use:
Code:
#real_mode
   cli pushad
   lidt d[IDT_real_mode]
   eax=cr0 [cr0_pm]=eax
   and eax,7fff_fffeh cr0=eax jmp >1,0 #
   xor ax,ax ss=ax ds=ax es=ax fs=ax gs=ax
   popad sti ret

but many other things are needed, like correct segment descriptors,interrupt tables,irq routines for real mode,the code must be compatible betwen 16bits pmode and 16 bit real mode so it must be loaded at low memory adresses (i use the first 64KB)
virtual memory= physical memory.
have you read intel manuals?
if not read them twice.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 18, 2008 4:48 am 
Offline
Member
Member
User avatar

Joined: Thu Nov 16, 2006 12:01 pm
Posts: 7612
Location: Germany
Ready4Dis wrote:
I know why they do it, because where do you stop? 64-bit, 128-bit, 256-bit?


I did some maths once to answer that question, "where do you stop?". It's quite easy, actually.

Reasoning goes as follows:

You need a pointer to address something. Let's go deep into SciFi land and say you are able to address individual atoms in a solid block of carbon. How large a block could you address using 64bit, 128bit, or 256bit pointers?
  • A cubic centimeter of diamond weights ~ 3.51 gramms.
  • 12 gramms of Carbon-12 (1 mol) contain ~ 6.02 * 10^23 atoms.
  • ergo, 1 cubic centimeter of diamond contains ~ 1.71 * 10^23 atoms.
Doing some conversions, that gives (very roughly):
  • 0,0001 cubic cm addressable by 2^64,
  • 1 cubic kilometer addressable by 2^128,
  • ~ 10^39 cubic km addressable by 2^256.

That last number would mean a block of carbon significantly (by several orders of magnitude) larger than our solar system.

I'd say you're on the safe side with 2^128.

_________________
Every good solution is obvious once you've found it.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 18, 2008 5:04 am 
Offline
Member
Member
User avatar

Joined: Sun Oct 22, 2006 7:01 am
Posts: 2646
Location: Devon, UK
Solar wrote:
Let's go deep into SciFi land and say you are able to address individual atoms in a solid block of carbon.


Why stop there? What about encoding on quarks and leptons? :twisted:

Cheers,
Adam


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 18, 2008 5:10 am 
Offline
Member
Member
User avatar

Joined: Mon Feb 04, 2008 3:58 am
Posts: 232
To infinity and beyond! ;)


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 31 posts ]  Go to page 1, 2, 3  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: Google [Bot] and 125 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group