Page 1 of 2

Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 3:48 am
by max
Hey guys :)

I've decided to implement Vm8086 tasks for my kernel, basically only to switch to a decent graphics mode ^^'
It would be nice if one of you could review my implementation plan before I start, so I don't mess it up totally :P
First some information, my kernel is set up like this:
- the linker script links the kernel so that the binary is loaded to 0x100000 (first entry of my SECTIONS is ". = 0x100000;")
- i let GRUB load the kernel and the ramdisk modules. the kernel and the modules are then at addresses above 1MiB
- in my kernel, there is only one page directory, identity-mapping the lower memory + the space used by the kernel (for example up to 0x105000), and making a contiguous area above that using all remaining free memory
- all the processes are running in kernel mode
- i have a flat GDT set up, with to entries for code and data covering the entire memory
- i use preemptive multitasking to switch tasks on timer interrupts, storing the registers on the processes stack

Now my plan to integrate a vm8086 task to my system would be to simply do the following:
- creating a "lower memory allocator" that uses the area from 0x00007E00 to 0x0007FFFF (by now I assume that this exists, later I'll check it from the memory map), this allocator serves for all vm8086 tasks (i'll use system calls to keep it reentrant-safe) - and I doubt that I will have so many tasks that I'll need more memory than there is available down there
- when setting up a task, ill set the VM-Bit in the flags register of the CPU
- compile a 16 bit object and load it as a GRUB module, than allocate a chunk of that size with my lower memory allocator, copy the object there and set the IP of my vm task to the start address
- allocate a stack with my lower memory allocator and set it as the stack for my task

Then, once the task has it's turn, the IRET should jump into that task and automatically switch to v86 mode. The only thing I'll have to do then, is add a routine to my GPF handler that treats GPFs from a vm86 task by handling them somehow.
Will it work this way? Or is there anything else I have to consider when switching to the v86 mode?

Thank you :)

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 4:25 am
by Bender
I sadly do not use V8086 mode, so I can't comment on your design, but have you considered using an emulator (like libx86), and run the BIOS ROM inside the emulator. It's much easier and a portable solution. IIRC V8086 requires User Mode support.

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 4:32 am
by alexfru
I'd leave 1MB + 64KB to v86 because if A20 is enabled (and I supposed it is, otherwise you wouldn't be able to use odd-numbered megabytes of RAM), 16-bit code can access those 64KB just above 1MB.

Another thing to note is that the BIOS code must be accessible (=mapped) if you plan to use int 0x10. And not just the code, but also the BIOS data area(s). And, likely, the realmode interrupt vector table. In fact, I'd suggest to identity-map the entire 1MB+64KB and not mess with the unknown. And it's a good thing to simulate the timer interrupts as if they were still occurring every 55 ms.

Also, have a look at http://members.tripod.com/protected_mod ... mtuts.html, specifically TUT16.

Another thing to consider is the virtual 8086 mode extensions/enhancements (available since Pentium).

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 4:45 am
by max
Bender wrote:I sadly do not use V8086 mode, so I can't comment on your design, but have you considered using an emulator (like libx86), and run the BIOS ROM inside the emulator. It's much easier and a portable solution. IIRC V8086 requires User Mode support.
I thought about that, but by now I havent found such a thing as libx86 (and writing my own emulator would be like, another project). Thanks, I'll take a look at it :)
alexfru wrote:I'd leave 1MB + 64KB to v86 because if A20 is enabled (and I supposed it is, otherwise you wouldn't be able to use odd-numbered megabytes of RAM), 16-bit code can access those 64KB just above 1MB.

Another thing to note is that the BIOS code must be accessible (=mapped) if you plan to use int 0x10. And not just the code, but also the BIOS data area(s). And, likely, the realmode interrupt vector table. In fact, I'd suggest to identity-map the entire 1MB+64KB and not mess with the unknown. And it's a good thing to simulate the timer interrupts as if they were still occurring every 55 ms.
Okay, that would not be a problem. Everything below the end of the kernel + ramdisks is currently identity-mapped, for example if the kernel would be 1MiB in size, everything from 0x00000000 to 0x00020000 is identity-mapped by my virtual memory manager. So I could simply move my kernel up using the linker script and that part would be done, everything necessary would be accessible as if paging was not there.
alexfru wrote: Also, have a look at http://members.tripod.com/protected_mod ... mtuts.html, specifically TUT16.

Another thing to consider is the virtual 8086 mode extensions/enhancements (available since Pentium).
Okay I'll look at that.

What I forgot to say in my initial post, is it mandatory to create a TSS, and when do i update the values for it? Or better, to which kernel stack should it point? Because, when a GPF happens due to a interrupt in the 16 bit code, my 32 bit interrupt handler is executed, but this must be set up with the TSS, right?

Also, do I have to change anything in my GDT or the segment registers when setting up the task?

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 5:30 am
by alexfru
AFAIR, you do need a TSS in order to switch the stack during a ring3 to ring0 transition on IRQs and exceptions occurring while in v86 (exception/interrupt handlers will be handled in ring0, see SS0, ESP0 fields of TSS and the stack layout during privilege level change). Also there is the I/O permission map in the TSS that tells the CPU which ports are accessible and which are not.

Anyhow, read the official docs and see the code. I'm not going to restate what's already been written. Ask specific questions when you have an actual problem.

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 5:31 am
by Kevin
max wrote:- all the processes are running in kernel mode
You need to change that then. VM86 is ring 3 by definition, it won't work with a ring 0 code segment. (This also means that you need a TSS, like for any user mode support.)
- compile a 16 bit object and load it as a GRUB module, than allocate a chunk of that size with my lower memory allocator, copy the object there and set the IP of my vm task to the start address
What code do you want to run? Typically VM86 is only used for running BIOS code (mostly VBE mode switching). If this is what you're looking for, you don't need any 16 bit code of your own. Simply have the initial cs:ip of your VM point to the interrupt handler entry point as specified in the IVT.
alexfru wrote:I'd leave 1MB + 64KB to v86 because if A20 is enabled (and I supposed it is, otherwise you wouldn't be able to use odd-numbered megabytes of RAM), 16-bit code can access those 64KB just above 1MB.
Paging takes care of protecting memory if you set your user bits correctly.

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 5:44 am
by max
Kevin wrote:You need to change that then. VM86 is ring 3 by definition, it won't work with a ring 0 code segment. (This also means that you need a TSS, like for any user mode support.)
Okay, I can change that. So, my GDT will effectively contain the null-entry, kernel code, kernel data, user code, user data (all 0x00000000 to 0xFFFFFFFF) and tss entry, does that suffice for vm86?

So, when creating a vm86 process I do
- create a stack in the 16 bit address space
- create a kernel stack anywhere in address space
- set EIP to the address of the BIOS code for the interrupt, found in the IVT
- set the general purpose register values to whatever the interrupt needs
- set CS and SS to the user-mode registers i created in the GDT
and when the scheduler is about to switch to a vm86 process, i always set ESP0 in my TSS to the current processes kernel stack, right? Or is there something I missed?
Kevin wrote:What code do you want to run? Typically VM86 is only used for running BIOS code (mostly VBE mode switching). If this is what you're looking for, you don't need any 16 bit code of your own. Simply have the initial cs:ip of your VM point to the interrupt handler entry point as specified in the IVT.
Oh, that's a good idea, i think I would have created some perverted stuff :D
So then if i want to perform a BIOS call, I start a new vm86 task, set the registers (like AX for video modes) and then this process only does that one specific BIOS call and then dies?

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 5:51 am
by alexfru
Kevin wrote:
max wrote:- all the processes are running in kernel mode
You need to change that then. VM86 is ring 3 by definition, it won't work with a ring 0 code segment. (This also means that you need a TSS, like for any user mode support.)
Code running in V86 does not involve GDT at all. That code uses the same segment_selector*16+offset scheme as in real mode to transform logical addresses into linear/virtual ones (which then are subject to further translation into physical ones if page translation is enabled). And that's it.

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 6:52 am
by Kevin
alexfru wrote:Code running in V86 does not involve GDT at all. That code uses the same segment_selector*16+offset scheme as in real mode to transform logical addresses into linear/virtual ones (which then are subject to further translation into physical ones if page translation is enabled). And that's it.
Right, didn't think about that when I talked about ring 0 code segments. You can't even get the CPU into such a state. My point that VM86 is always ring 3 stands, though.
max wrote:Okay, I can change that. So, my GDT will effectively contain the null-entry, kernel code, kernel data, user code, user data (all 0x00000000 to 0xFFFFFFFF) and tss entry, does that suffice for vm86?
Yes, that set is definitely enough. As alexfru mentioned, you may even be able to do without the user code/data segments.
So, when creating a vm86 process I do
- create a stack in the 16 bit address space
Yes. This is also the only allocation you need to do for running a new VM86 task. If you use the memory in the first MB for other allocations as well, you may want to reserve some memory specifically for VM86, or you may fail to allocate memory there.
- create a kernel stack anywhere in address space
- set EIP to the address of the BIOS code for the interrupt, found in the IVT
- set the general purpose register values to whatever the interrupt needs
- set CS and SS to the user-mode registers i created in the GDT
No, you set cs and ss to their RM values, i.e. cs from the IVT and ss depending on where you allocated the VM86 user stack. (eip is also really just ip, we're running 16 bit code here.) Setting means putting them on the kernel stack so that iret can fetch them. You also need to push the values for es, ds, fs and gs there.
and when the scheduler is about to switch to a vm86 process, i always set ESP0 in my TSS to the current processes kernel stack, right? Or is there something I missed?
Right (where current process = VM86 task, I think), and you need ss0 set to your kernel data segment selector, too.
So then if i want to perform a BIOS call, I start a new vm86 task, set the registers (like AX for video modes) and then this process only does that one specific BIOS call and then dies?
Your instruction emulator needs to have a check in its iret emulation that determines whether it needs to return from a nested interrupt, or whether it was the outermost one and the task can exit.

Re: Adding Vm8086 to a flat memory model kernel

Posted: Wed Apr 23, 2014 7:08 am
by bluemoon
max wrote:Or is there anything else I have to consider when switching to the v86 mode?
You can't use v86 in long mode, that might be a problem if you plan to have decent app after you gain decent video.

Re: Adding Vm8086 to a flat memory model kernel

Posted: Fri Apr 25, 2014 6:10 am
by max
Thank you for the help so far guys.
I've now rewritten a few parts (it's a litte complicated, because my normal tasks now only have a kernel stack, and the vm86 comes in and needs a user stack :D). Now I have prepared the kernel stack for the initial switch to a Vm86 like this (it's a debug output, and shows what values I set to the registers):

(don't wonder, my stacks have a size of 64KB at the moment, thats why stackfoot + 0x10000 => 0x17E0C here.)

Code: Select all

VM86-Process 1 created
  user stack foot:  0x00007E0C // the starting address of the stack area allocated in lower memory
  kernel stack foot:  0x0036D580 // the starting address of the stack area allocated in normal memory
  user esp (FP):  0x17E0000C // the ESP of the userstack (start of the stack + stack-size) converted to a far pointer
  interrupt code entry (FP):  0xC0008310 // the interrupt entry point (comes from the IVT) as far pointer

 registers set to:
   ss: 0x000017E0  // Segment from the user stack far pointer
   sp: 0x0000000C  // Offset from the user stack far pointer
   eflags: 0x00020202  // Eflags with VM enabled
   cs: 0x0000C000  // Segment from IVT
   ip: 0x00008310  // Offset from IVT
   ax: 0x00004F01  // AX for running VBE-interrupt (just test-wise)
   bx: 0x00000000
   cx: 0x0000006B
   dx: 0x00000036
   si: 0x00000000
   di: 0x00000000
   gs: 0x00000000  // These segments should be popped/pushed by the CPU - es and ds can be passed to my Vm86-task-start-function
   fs: 0x00000000
   es: 0x00000000
   ds: 0x00000000
Could one of you have a short look if these values look correct? What happens now is when switching to the Vm86 i get a general protection fault - so I guess something is wrong with my values.^^'

My GDT is still setup normally but I added the TSS, so I now have null descriptor, kernel code, kernel data and TSS.
(oh man, once I managed to do this I think I'll add any of my misconceptions to the OSDev wiki^^)

Thank you! <3

Re: Adding Vm8086 to a flat memory model kernel

Posted: Fri Apr 25, 2014 6:46 am
by Kevin
Your user stack isn't quite right. 0x7e0c + 64k = 0x17e0c, which is indeed the same as 17e0:000c. But the VM86 task will soon overflow its stack because it is only 12 bytes in size. You want to use something like 07e0:0000, so that the first pushed value is stored a 07e0:fffe. And better use an address that is at least aligned to paragraphs (16 bytes), so that ss contains the whole start address of the stack and sp is the offset on the stack - the natural thing is probably page alignment.

Anyway, all of this "only" causes memory corruption, but no #GP. Are you sure that the exception occurs while switching to VM86, i.e. eip is still on the iret instruction? Because the expected result is that you get a #GP shortly after the switch, when the CPU executes an instruction that must be emulated by the VM monitor (like I/O operations, pushf, popf, int, iret, etc.) If this is what you're seeing, your VM entry was already successful. :)

Re: Adding Vm8086 to a flat memory model kernel

Posted: Fri Apr 25, 2014 7:04 am
by max
Kevin wrote:Your user stack isn't quite right. 0x7e0c + 64k = 0x17e0c, which is indeed the same as 17e0:000c. But the VM86 task will soon overflow its stack because it is only 12 bytes in size. You want to use something like 07e0:0000, so that the first pushed value is stored a 07e0:fffe. And better use an address that is at least aligned to paragraphs (16 bytes), so that ss contains the whole start address of the stack and sp is the offset on the stack - the natural thing is probably page alignment.

Anyway, all of this "only" causes memory corruption, but no #GP. Are you sure that the exception occurs while switching to VM86, i.e. eip is still on the iret instruction? Because the expected result is that you get a #GP shortly after the switch, when the CPU executes an instruction that must be emulated by the VM monitor (like I/O operations, pushf, popf, int, iret, etc.) If this is what you're seeing, your VM entry was already successful. :)

Oh man!!! :D
That stack thing is really ugly, f**kin segmentation..^^
And - yes, you are right. I already am inside the Vm86. In my exception handler, I was looking for the last active process, and if that process was a Vm86 I wanted to print out a message - what was actually wrong, was my test if the last process was marked as Vm86. ^^
I will add a little more information about what I did wrong once I fixed this. :)

Re: Adding Vm8086 to a flat memory model kernel

Posted: Sat Apr 26, 2014 2:17 pm
by max
Okay, guys - I've got it working. AWESOME :D
Image

Just a little question - this is now what I am doing to perform a Vm86 call, for example to get the VbeInfoBlock. I'm starting a process that runs completely in Vm86, and wait for it to finish. When it finishs, it copies it's register values to the "out" parameter I pass. The thread that wants to call the BIOS, must then simply yield until the Vm task has finished.
My question is, how are you handling this in your systems?

In my kernel it looks like this:

Code: Select all

Vm86Registers out;
Vm86Registers regs;

VbeInfoBlock* vbeInfoBlock = (VbeInfoBlock*) LowerMemoryAllocator::allocate(512);
FARPTR vbeInfoBlockFp = LINEAR_TO_FP((uint32_t ) vbeInfoBlock);

regs.ax = 0x4F00;
regs.es = FP_SEG(vbeInfoBlockFp);
regs.di = FP_OFF(vbeInfoBlockFp);
uint16_t requestVbeInfoProcess = Scheduler::createProcessVm86(0x10, regs, &out);
while (Scheduler::getProcessById(requestVbeInfoProcess)) {
    Scheduler::yield();
}

LowerMemoryAllocator::free(vbeInfoBlock);

Re: Adding Vm8086 to a flat memory model kernel

Posted: Sat Apr 26, 2014 3:02 pm
by alexfru
It shouldn't be much different from how you implement regular IPC and synchronization primitives. Send a message and block until reception of a response. Or block while waiting for an event which your v86 code will fire when done. That sort of stuff.

While you could do all v86 stuff in the context of the calling thread without IPC and additional thread/processes, you probably shouldn't or shouldn't rush into it. Unless you're certain there isn't going to be any kind of race condition between multiple v86 tasks, accessing shared resources, perform everything in a single 86 task and serialize accesses to it.

Btw, is it expected to look like Malevich's black square? :)