Page 1 of 1

FPU stub

Posted: Sun Nov 09, 2008 10:58 pm
by 01000101
I decided to start a page on the x87 FPU. I didn't see any documents on it in the wiki and I think it should at least have a stub (which is what I did, and I will get around to make it a little more robust later). It is an important part of the x86 arch and can provide extremely powerful functionality.

btw, out of curiousity due to the lack of wiki-info, how many of you guys actually implement FPU integers (float, double, long double)?

Re: FPU stub

Posted: Mon Nov 10, 2008 12:59 pm
by piranha
If I knew more about the FPU, I'd add some (and I'd research it if I had time).

In my opinion, the code you've given is good, but it needs more descriptions (I do know what the instructions mean, but if I was just learning about the FPU for the first time, I'd have some questions):
  • I can see that you're setting a bit in CR4, ok.
    finit must initialize the FPU, alright.
    How did you get that mode? What does it mean? What other modes are there? What does fldcw mean, or whats that instruction do?
The code you gave doesn't compile on my computer. I get "Error: suffix or operands invalid for `movq'".
After changing it to 'mov' it works and executes without dying, but is this ok?

Good start, the wiki needed a page for the FPU.

-JL

Re: FPU stub

Posted: Mon Nov 10, 2008 6:40 pm
by Combuster
My kernel has FPU support, including SSE and friends, and with lazy task switching. Basically my approach is to create an FPU image in the task state, drop in the constants needed for SSE to work, then just reload cr0 with the TS bit upon task switch.

Then when the FPU is actually needed, the previous state is stored and the new state loaded.

You are free to post these snippets on the wiki. Probably best if someone doublechecked them in case I misinterpreted something from the manuals - I don't have working 386s and 486s to check if all code paths do what they should.

Code: Select all

DetectCoprocessor:      CPU 386 FPU
                        MOV EDX, CR0                            ; CR0 has a bit on the matter as well
                        AND EDX, CR0_ET                         ; Check Extension Type
                        JZ .nofpu                               ; The processor supports no FPU
                        MOV EDX, CR0                            ; Start probe, get CR0
                        AND EDX, (-1) - (CR0_TS + CR0_EM)       ; clear TS and EM to force fpu access
                        OR EDX, CR0_NE                          ; set NE (workaround no-wait bug)
                        MOV CR0, EDX                            ; store control word
                        FNINIT                                  ; load defaults to FPU
                        FNSTSW [.testword]                      ; store status word. If there's no coprocessor, nothing happens
                        CMP word [.testword], 0                 ; compare the written status
                        JNE .nofpu
.hasfpu:                ; do something when there is an FPU installed

.nofpu:                 ; do something when there isn't an FPU installed
                        RET 
.testword:              DW 0x55AA
Useful for those machines when there's no CPUID to ask, or the coprocessor isn't on-chip

Anyway, if you have a CPU without on-chip FPU, you might want to do paranoia checks on the interrupt handling that it indeed works, otherwise, you should set CR0.NE since that doesn't require your chipset to have that wiring in place.

If you want to enable SSE, you should check the CPUID bits for SSE and Fast save-restore functionality. If your processor can handle that, you can set CR4.OSFXSR and CR4.OSXMMEXCPT.

To switch FPU states you'll have to store the context. Note that this differs when SSE is enabled. FNSAVE/FNRSTOR doesn't store SSE state, FXSAVE/FXRSTOR do but you'll get errors when you try that on a non-sse-enabled machine

Code: Select all

                        ; EDX -> store data
                        ; EAX -> get data

                        CMP dword [sse_enabled], 0
                        JE .fpuonly

                        CPU 686
.fpuandsse:             CLTS
                        FXSAVE [EDX]
                        FXRSTOR [EAX]
                        JMP .done2

.fpuonly:               CLTS
                        FNSAVE [EDX]
                        FRSTOR [EAX]
                        JMP .done2
                        CPU 386
For FPU support, you should write the #NM handler (for handling lazy FPU switches) and #MF handler (for dealing with FPU exceptions)
For SSE, you should write the XF handler as well (for SIMD exceptions).

Re: FPU stub

Posted: Mon Nov 10, 2008 8:27 pm
by 01000101
Thanks for the ideas & code. I'll sift through and hopefully come up with an 'article-like' entry. Also, the reason the 'movq' instruction issued you a warning/error is because I am working in 64-bit mode and was using the 'quad' suffix, using a plain 'mov' instruction will just default to the (largest?) general-purpose register size (which is fine for this). the FLDCW (IIRC) stands for FPU LoaD Control Word and has basic operations such as setting the default precision value.

I'll keep adding on to this wiki article, but I'd really like it if some more FPU-knowledgeable people could contribute as I have a feeling that my contributions will be somewhat limited.

Re: FPU stub

Posted: Tue Nov 11, 2008 9:47 am
by 01000101
Ok, I added a few explanations and commented the code.
I also threw in a reference page with alot of good info on the FPU.

Re: FPU stub

Posted: Tue Nov 11, 2008 9:49 am
by AJ
Combuster wrote:Then when the FPU is actually needed, the previous state is stored and the new state loaded.
I haven't done this yet, but out of interest, how do you know where to store the previous state? Do you simply keep an active pointer to the PCB of the last task that used the FPU?

Cheers,
Adam

Re: FPU stub

Posted: Mon Nov 17, 2008 12:02 pm
by Laksen
AJ wrote:
Combuster wrote:Then when the FPU is actually needed, the previous state is stored and the new state loaded.
I haven't done this yet, but out of interest, how do you know where to store the previous state? Do you simply keep an active pointer to the PCB of the last task that used the FPU?
That's what the intel documents recommend. You can set the processor up so it only throws a #NM if the TS(Task Switched) bit is set. You can simply clear this bit when you change tasks if you use software multitasking. Then if you get an #NM and the task is another than the last one to invoke a #NM exception then you simply reload the math state from the new task after saving the old tasks mathstate and clear TS before returning from the interrupt

That's what I do and it works fine. You'll have to clear TS as the first operation in the interrupt procedure or else you'll get loads of problems(at least I had, #GPF's, #NM's, etc). Another thing. The 512 byte area where you save the mathstate to must be 16 byte aligned