; ; Copyright�(c)�2010, RISC OS Open Ltd ; All�rights�reserved. ; ; Redistribution and use in source and binary forms, with or without ; modification, are permitted provided that the following conditions are met: ; * Redistributions of source code must retain the above copyright ; notice, this list of conditions and the following disclaimer. ; * Redistributions in binary form must reproduce the above copyright ; notice, this list of conditions and the following disclaimer in the ; documentation and/or other materials provided with the distribution. ; * Neither the name of RISC OS Open Ltd nor the names of its contributors ; may be used to endorse or promote products derived from this software ; without specific prior written permission. ; ; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ; POSSIBILITY OF SUCH DAMAGE. ; AREA |!!!ModuleHeader|, CODE, READONLY, PIC Module_BaseAddr & 0 & InitModule - Module_BaseAddr & KillModule - Module_BaseAddr & 0 & ModuleTitle - Module_BaseAddr & HelpString - Module_BaseAddr & 0 & VFPSupportSWI_Base & SWIEntry - Module_BaseAddr & SWINameTable - Module_BaseAddr & 0 [ International_Help <> 0 DCD MessageFilename - Module_BaseAddr | DCD 0 ] & ModuleFlags - Module_BaseAddr HelpString = "VFPSupport", 9, "$Module_MajorVersion ($Module_Date)" [ Module_MinorVersion <> "" = " $Module_MinorVersion" ] = 0 ModuleTitle SWINameTable = "VFPSupport", 0 = "CheckContext", 0 = "CreateContext", 0 = "DestroyContext", 0 = "ChangeContext", 0 = "ExamineContext", 0 = "FastAPI", 0 = "ActiveContext", 0 = "Version", 0 = "Features", 0 = 0 ALIGN ModuleFlags & ModuleFlag_32bit ; Workspace ^ 0, wp MessageFile_Block # 16 MessageFile_Open # 4 ActiveContext # 4 ; Context that's actually active LazyContext # 4 ; Context awaiting lazy activation (==ActiveContext if none) NumVFPRegs # 1 ; Number of doubleword regs available on this hardware ARMVersion # 1 ; ARM architecture version from MIDR. <7=ARMv5, 7=ARMv6, &F=ARMv7 VFPVersion # 1 ; VFP subarchitecture field from FPSID CPEnabledFlag # 1 ; Nonzero if CP access is enabled OldHandler # 4 ; Original undefined instruction handler UndefinedHandler # 4 ; Undefined instruction handler code LazyHandler # 8*4 ; Code to call the lazy handler ChangeContext # 4 ; Address of SWI_ChangeContext CurrentRoutine # 4 ; OldHandler or LazyHandler SoftFPSID # 4 ; FPSID SoftMVFR0 # 4 ; MVFR0 SoftMVFR1 # 4 ; MVFR1 WSSize * :INDEX: @ ; Undefined instruction handler code - gets copied into workspace UndefinedHandlerTemplate LDR pc, UndefinedHandlerTemplateEnd+4 ; Branch to address in CurrentRoutine SUB lr, lr, #4 ; LazyHandler starts here STMDB r13!,{r0-r1,wp,lr} ADR wp, UndefinedHandlerTemplate-:INDEX:UndefinedHandler ; Get wp MOV r1, #0 LDR r0, LazyContext MOV lr, pc LDR pc, ChangeContext ; Reuse main code for simplicity LDMIA r13!,{r0-r1,wp,pc}^ ; Restart aborting instruction UndefinedHandlerTemplateEnd ASSERT LazyHandler-UndefinedHandler+?LazyHandler = UndefinedHandlerTemplateEnd-UndefinedHandlerTemplate ASSERT CurrentRoutine-UndefinedHandler=UndefinedHandlerTemplateEnd-UndefinedHandlerTemplate+4 ; Module code InitModule Entry "r7-r11" [ standalone ADRL R0,ResourceFSFiles SWI XResourceFS_RegisterFiles ] MOV r0, #ModHandReason_Claim LDR r3, =WSSize SWI XOS_Module BVS %FT20 STR r2, [r12] MOV r12, r2 MOV r0, #0 10 SUBS r3, r3, #4 STR r0, [r12, r3] BNE %BT10 BL CheckHardware BLVC InstallHandler 20 EXIT CheckHardware Push "lr" ; Check for any VFP hardware ; First step is to check what ARM architecture we're on MRC p15,0,r0,c0,c0,0 ; read main id register ANDS r1, r0, #&F000 TEQNE r1, #&7000 BEQ NoVFP ; ARM7 or below AND r0, r0, #&F0000 CMP r0, #&30000 BLT NoVFP ; pre ARMv5 MOV r0, r0, LSR #16 STRB r0, ARMVersion ; Interrupts off for the remainder of the tests MRS r1, CPSR ORR r2, r1, #I32_bit MSR CPSR_c, r2 CMP r0, #&7 BLT IsARMv5 ; ARMv6 and above have the coprocessor access control register, which allows us to poll for CP presence MRC p15,0,r2,c1,c0,2 ; read CPACR ORR r3, r2, #&F<<20 MCR p15,0,r3,c1,c0,2 MRC p15,0,r3,c1,c0,2 ; read it back to get status AND r4, r3, #&F<<20 CMP r4, #&F<<20 MCRNE p15,0,r2,c1,c0,2 ; restore original CPACR BNE NoVFP_v6v7 ; VFP coprocessors exist and are now enabled, read FPSID myISB ,r4 ; ISB to ensure that the coprocessors really are enabled myVMRS ,r4, FPSID B GotFPSID IsARMv5 ; No CPACR on ARMv5, so only way we can test for VFP is to try reading FPSID and seeing if we trigger an abort ; TODO - There are some extra things, e.g. ARMv5 CPACR as found on XScale Push "r1-r2" LDR r0, =&101 ADR r1, ARMv5Und SWI XOS_ClaimProcessorVector Pull "r1-r2",VS MSRVS CPSR_c, r1 Pull "pc",VS MOV r5, #0 myVMRS ,r4, FPSID MOV r0, #1 ADR r2, ARMv5Und SWI XOS_ClaimProcessorVector Pull "r1-r2" MSRVS CPSR_c, r1 Pull "pc",VS ; r5 will be nonzero if we aborted CMP r5, #0 LDREQB r0, ARMVersion BEQ GotFPSID NoVFP_v6v7 MSR CPSR_c, r1 ; restore interrupts NoVFP ADRL r0, ErrorBlock_NoVFP B ReturnError_Stacked ARMv5Und MOV r5, #1 MOVS pc, lr GotFPSID ; r0 = ARM version ; r1 = old PSR ; r2 = old CPACR if >ARMv5 ; r4 = FPSID TST r4, #1:SHL:23 ; Is the software bit set? BNE BadVFP ; The FPSID format has changed a bit between ARMv5, v6 and v7 CMP r0, #&7 BLT CheckFPSIDv5 BEQ CheckFPSIDv6 ; v7 version. Must be VFPv3 or above to be valid, but we currently don't allow anything other than vanilla VFPv3 with null subarchitecture due to lack of support code AND r3, r4, #&7F0000 CMP r3, #&30000 BNE BadVFP B GoodVFP CheckFPSIDv5 ; TODO CheckFPSIDv6 ; TODO BadVFP ; Restore CPACR if ARMv6+ CMP r0, #&7 MCRGE p15,0,r2,c1,c0,2 myISB GE,r0 ; Deal with pipelined CP15 ops on ARMv6+. TODO - ARMv5 ; Restore interrupts MSR CPSR_c, R1 ADRL r0, ErrorBlock_BadVFP B ReturnError_Stacked GoodVFP ; r0 = ARM version ; r1 = old PSR ; r2 = old CPACR if >ARMv5 ; r3 = FPSID subarchitecture field ; r4 = FPSID ; Read MVFR0/1 if they're available CMP r3, #&2<<16 BLT %FT10 myVMRS ,r5,MVFR0 myVMRS ,r6,MVFR1 10 ; Done for now, make sure VFP access is disabled ; For the moment we just disable access via the FPEXC.EN bit. This will disable everything except VMSR & VMRS from privileged modes MOV r7, #0 myVMSR ,FPEXC, r7 ; Restore interrupts MSR CPSR_c, R1 ; Store our results ; TODO - Calculate fake MVFR0/MVFR1 values for pre-VFPv3 MOV r3, r3, LSR #16 STRB r3, VFPVersion STR r4, SoftFPSID STR r5, SoftMVFR0 STR r6, SoftMVFR1 ; Work out how many registers there are ; Assumes we've faked up MVFR0! AND r5, r5, #&F CMP r5, #2 MOVEQ r3, #32 MOVNE r3, #16 STRB r3, NumVFPRegs Pull "pc" InstallHandler Entry ; Copy the handler code over ADR r0, UndefinedHandlerTemplate ADR r1, UndefinedHandler ASSERT UndefinedHandlerTemplateEnd-UndefinedHandlerTemplate = 9*4 LDMIA r0, {r2-r10} ASSERT ChangeContext = UndefinedHandler+(UndefinedHandlerTemplateEnd-UndefinedHandlerTemplate) ADR r11, SWI_ChangeContext STMIA r1, {r2-r11} ORR r0, r0, #1 ADD r2, r1, #UndefinedHandlerTemplateEnd-UndefinedHandlerTemplate SWI XOS_SynchroniseCodeAreas ; Install the handler. Interrupts disabled to ensure we don't get caught before we set up CurrentRoutine. MRS r4, CPSR ORR r3, r4, #I32_bit MSR CPSR_c, r3 LDR r0, =&101 SWI XOS_ClaimProcessorVector STRVC r1, OldHandler STRVC r1, CurrentRoutine MSR CPSR_c, r4 EXIT KillModule LDR wp, [r12] MOV r6, lr ; Remove undefined instruction handler MOV r0, #1 LDR r1, OldHandler ADR r2, UndefinedHandler SWI XOS_ClaimProcessorVector MOVVS pc, r6 ; Improperly nested handlers ; Disable VFP BL DisableCPAccess ; TODO - Disable in CPACR as well? ; TODO - Free any contexts? BL CloseMessages [ standalone ADRL R0,ResourceFSFiles SWI XResourceFS_DeregisterFiles ; ignore errors ] CLRV MOV pc, r6 SWIEntry LDR wp, [r12] CMP r11, #(EndOfJumpTable-JumpTable)/4 ADDLO pc, pc, r11, LSL #2 B UnknownSWI JumpTable B SWI_CheckContext B SWI_CreateContext B SWI_DestroyContext B SWI_ChangeContext B SWI_ExamineContext B SWI_FastAPI B SWI_ActiveContext B SWI_Version B SWI_Features EndOfJumpTable UnknownSWI ADRL r0, ErrorBlock_ModuleBadSWI B ReturnError_LR SWI_CheckContext ; in: R0 = flags ; b0 = user mode flag (0=user mode access not required, 1=user mode access required) ; other bits reserved, sbz ; R1 = number of doubleword registers required (1-32) ; out: R0 = required size of context save area ; Validate flags & reg count CMP r1,#0 BEQ %FT10 CMP r0,#1 LDRLSB r0,NumVFPRegs CMPLS r1,r0 MOVLS r0,#Context_RegDump ADDLS r0,r0,r1,LSL #3 MSRLS CPSR_f,#Z_bit ; Clear V while retaining LS state MOVLS pc,lr 10 ADRL r0, ErrorBlock_FeatureUnavailable B ReturnError_LR SWI_CreateContext ; in: R0 = flags ; b0 = user mode flag (0=user mode access not required, 1=user mode access required) ; b31 = activate flag (0=leave context inactive, 1=activate now) ; other bits reserved, sbz ; R1 = number of doubleword registers required (1-32) ; R2 = pointer to word-aligned context save area of the size indicated by VFPSupport_CheckContext, or 0 if VFPSupport is to allocate memory itself ; R3 = FPSCR value to initialise context with ; out: R0 = context ID ; R1 = previously active context ID/preserved Push "r0-r3,lr" CMP r2,#0 BIC r0,r0,#VFPSupport_Context_Activate BNE %FT10 BL SWI_CheckContext MOVVC r3,r0 MOVVC r0,#ModHandReason_Claim SWIVC XOS_Module ADDVS sp,sp,#4 Pull "r1-r3,pc",VS LDR r0,[sp] ORR r0,r0,#VFPSupport_Context_VFPMemory LDR r3,[sp,#12] 10 ; Initialise context contents ASSERT Context_Flags = 0 ASSERT Context_NumRegs = 4 ASSERT Context_FPSCR = 8 ASSERT Context_FPEXC = 12 MOV lr, #0 ; null FPEXC == no registers to restore STMIA r2,{r0,r1,r3,lr} ; Did they want the context activating? Pull "r1" ; Actually R0 on input MOV r0,r2 CLRV TST r1,#VFPSupport_Context_Activate Pull "r1-r3,pc",EQ STR r0,[sp] MOV r1,#0 ; activate non-lazily BL SWI_ChangeContext ; Assumes that ChangeContext won't return an error MOV r1,r0 Pull "r0,r2-r3,pc" SWI_DestroyContext ; in: R0 = context ID ; R1 = context ID to activate if R0 was the active context ; out: R0 = context ID that's now active Entry "r1-r4" MRS r4, CPSR ORR r3, r4, #I32_bit MSR CPSR_c, r3 ; Dereference R0 ; TODO - Improve this so it doesn't save the context we're about to delete ; Would need to make sure ActiveContext, LazyContext, coprocessor access & CurrentRoutine all stay in sync MOV r2, r0 CMP r0, r1 MOVEQ r1, #0 ; Don't activate R1 if we're destroying it! LDR r0, LazyContext CMP r0, r2 MOVEQ r0, r1 ; if dying context is active/lazily active, activate user's R1 LDRNE r1, ActiveContext CMPNE r1, r2 ; if dying context is really active, activate LazyContext MOVEQ r1, #0 ; Activate desired context non-lazily STREQ r1, [r2, #Context_NumRegs] ; ChangeContext will attempt to save the context we're deleting. But we can make things a little bit faster by skipping the main FP registers. BLEQ SWI_ChangeContext ; Ignore error? MSR CPSR_cf, r4 ; Restore interrupts LDR r1, [r2, #Context_Flags] TST r1, #VFPSupport_Context_VFPMemory MOV r0, #ModHandReason_Free SWINE XOS_Module CLRV ; Ignore error? LDR r0, LazyContext EXIT SWI_ChangeContext ; in: R0 = context ID to activate ; R1 = flags ; b0 = lazy activation (0=activate now, 1=use lazy activation) ; other bits reserved, sbz ; out: R0 = previously active context ID ; TODO - rewrite to use state machine based around array of function pointers? ; TODO - make use of user mode flag Entry "r1-r4" MRS r4, CPSR ORR r3, r4, #I32_bit MSR CPSR_c, r3 LDR r2, LazyContext TST r1, #VFPSupport_ChangeContext_Lazy LDR r1, ActiveContext STR r0, LazyContext BEQ ChangeContext_Now ; Lazy activation CMP r2, r0 BEQ ChangeContext_Exit_IRQ_R0 ; Already (lazily) active CMP r0, #0 LDREQ r0, OldHandler BEQ ChangeContext_Exit_IRQ_R2_SetHandler ; Lazy deactivation - clear CurrentRoutine and disable CP access ; Else some form of lazy activation. If r0 is actually active, enable CP access, else disable CMP r0, r1 ADRNE r0, LazyHandler BNE ChangeContext_Exit_IRQ_R2_SetHandler ; Enable lazy handler BL EnableCPAccess LDR r1, OldHandler ; Disable lazy handler, it's no longer needed STR r1, CurrentRoutine B ChangeContext_Exit_IRQ_R0 ChangeContext_Now ; Nonlazy activation ; Start by disabling the lazy handler LDR lr, OldHandler CMP r0, r1 STR r0, ActiveContext CMPEQ r0, #0 STR lr, CurrentRoutine BEQ ChangeContext_Exit_IRQ_R2_Disable ; We're turning it off and it's already off, so do nothing BL EnableCPAccess CMP r0, r1 BEQ ChangeContext_Exit_IRQ_R2_MaybeDisable ; Context is already loaded ; Save r1 if necessary CMP r1, #0 BLNE SaveContext_R1 ; Load r0 if necessary CMP r0, #0 BLNE LoadContext_R0 ChangeContext_Exit_IRQ_R2_MaybeDisable CMP r0, #0 BLEQ DisableCPAccess ChangeContext_Exit_IRQ_R2 MOV r0, r2 ChangeContext_Exit_IRQ_R0 MSR CPSR_c, r4 CLRV EXIT ChangeContext_Exit_IRQ_R2_SetHandler STR r0, CurrentRoutine ChangeContext_Exit_IRQ_R2_Disable BL DisableCPAccess MOV r0, r2 MSR CPSR_c, r4 CLRV EXIT SWI_ExamineContext ; in: R0 = context ID ; R1 = flags ; b0 = Serialise context ; out: R0 = flags: ; b0 = User mode flag (0=user mode access not required, 1=user mode access required) ; b29 = context is awaiting lazy activation (1=yes, 0=no) ; b30 = context status registers are active (1=active, 0=saved) ; b31 = memory allocation method (0=user allocated, 1=VFPSupport allocated) ; R1 = number of doubleword registers (may be greater than number requested upon context creation) ; R2 = register status. bit n is 1 if doubleword register is active, 0 if saved. ; R3 = pointer to dump format descriptor block ; R4 = pointer to context register dump CMP r0, #0 BNE %FT10 ADRL r0, ErrorBlock_BadContext B ReturnError_LR 10 TST r1, #VFPSupport_ExamineContext_Serialise BEQ %FT20 ; Serialise it the easy way ; TODO - Do something a bit more sophisticated! Push "r0-r1,lr" LDR r1, [r0, #Context_NumRegs] MOV r0, #VFPSupport_Context_Activate MOV r2, #0 MOV r3, #0 BL SWI_CreateContext BLVC SWI_DestroyContext ADDVS sp, sp, #4 Pull "pc",VS Pull "r0-r1,lr" 20 MOV r4, r0 ; Check the FPEXC value in the dump as a method of determining which format of descriptor block we should use ; This won't work too well if programs use this as a method of inserting fake exceptions! LDR r2, [r0, #Context_FPEXC] ASSERT FPEXC_EX = N_bit ASSERT FPEXC_FP2V = V_bit MSR CPSR_f, r2 ADRGE r3, FormatDescriptorBlock_FPINST2 ; EX=1 FP2V=1 (EX=1 enforced by PL check below) ADRLT r3, FormatDescriptorBlock_FPINST ; EX=1 FP2V=0 (EX=1 enforced by PL check below) ADRPL r3, FormatDescriptorBlock_NullSubarch ; EX=0 ; Compute LazyActivation flag LDR r2, LazyContext CMP r4, r2 LDR r0, [r4, #Context_Flags] ORREQ r0, r0, #VFPSupport_Context_LazyActivation ; Compute StatusRegsActive flag and R2 LDR r2, ActiveContext CMP r4, r2 BICEQ r0, r0, #VFPSupport_Context_LazyActivation ; If this context is active, then it shouldn't be waiting for lazy activation MOVEQ r2, #1 LDR r1, [r4, #Context_NumRegs] MOVNE r2, #0 ORREQ r0, r0, #VFPSupport_Context_StatusRegsActive RSBEQ r2, r2, r2, LSL r1 CLRV MOV pc, lr FormatDescriptorBlock_FPINST2 DCD VFPSupport_Field_FPINST2 + Context_FPINST2<<16 FormatDescriptorBlock_FPINST DCD VFPSupport_Field_FPINST + Context_FPINST<<16 FormatDescriptorBlock_NullSubarch DCD VFPSupport_Field_FPSCR + Context_FPSCR<<16 DCD VFPSupport_Field_FPEXC + Context_FPEXC<<16 DCD VFPSupport_Field_RegDump + Context_RegDump<<16 DCD -1 SWI_FastAPI ; out: R0 = Workspace pointer to pass in R12 ; R1 = CheckContext function pointer ; R2 = CreateContext function pointer ; R3 = DestroyContext function pointer ; R4 = ChangeContext function pointer MOV r0, wp ADR r1, SWI_CheckContext ADR r2, SWI_CreateContext ADR r3, SWI_DestroyContext ADR r4, SWI_ChangeContext CLRV MOV pc, lr SWI_ActiveContext ; out: R0 = currently active context ID (or ID of context pending lazy activation) LDR r0, LazyContext CLRV MOV pc, lr SWI_Version ; out: R0 = Module version number * 100 MOV r0, #Module_Version CLRV MOV pc, lr SWI_Features ; in: R0 = Reason code CMP r0, #(EndOfFeaturesJumpTable-FeaturesJumpTable)/4 ADDLO pc, pc, r0, LSL #2 B UnknownFeature FeaturesJumpTable B Feature_SystemRegs EndOfFeaturesJumpTable UnknownFeature ADRL r0, ErrorBlock_BadFeature B ReturnError_LR Feature_SystemRegs ; in: R0 = 0 ; out: R0 = FPSID ; R1 = MVFR0 ; R2 = MVFR1 ASSERT SoftMVFR0=SoftFPSID+4 ASSERT SoftMVFR1=SoftMVFR0+4 ADR r0, SoftFPSID LDMIA r0, {r0-r2} CLRV MOV pc, lr EnableCPAccess ; Enable VFP CP access Entry LDRB lr,CPEnabledFlag EORS lr,lr,#255 EXIT EQ STRB lr,CPEnabledFlag myVMRS ,lr,FPEXC ORR lr,lr,#FPEXC_EN myVMSR ,FPEXC,lr EXIT DisableCPAccess ; Disable VFP CP access Entry LDRB lr,CPEnabledFlag EORS lr,lr,#255 EXIT NE STRB lr,CPEnabledFlag myVMRS ,lr,FPEXC BIC lr,lr,#FPEXC_EN myVMSR ,FPEXC,lr EXIT SaveContext_R1 ; Save active context to R1 ; This should work with VFPv2/3, not sure about VFPv1 Entry "r2-r3" myVMRS ,r2, FPEXC ASSERT FPEXC_EX = N_bit ASSERT FPEXC_FP2V = V_bit MSR CPSR_f, r2 STR r2, [r1,#Context_FPEXC] BPL %FT10 ; Must store FPINST myVMRS ,r2, FPINST STR r2, [r1,#Context_FPINST] ; Might need to store FPINST2 myVMRS VS,r2, FPINST2 STRVS r2, [r1,#Context_FPINST2] 10 LDR lr, [r1,#Context_NumRegs] myVMRS ,r2, FPSCR ADD r3, r1, #Context_RegDump STR r2, [r1,#Context_FPSCR] ADD pc, pc, lr, LSL #4 NOP EXIT ; Don't malfunction if the context we're saving has 0 regs. Used by DeleteContext to make things a bit faster. NOP NOP NOP ; Generate VSTM jump table GBLA count count SETA 1 WHILE count < 33 [ count <= 16 DCI &EC830B00 + count*2 ; VSTMIA r3,{D0-Dn} EXIT NOP NOP | DCI &ECA30B20 ; VSTMIA r3!,{D0-D15} (we can only STM 16 at once) DCI &ECC30B00 + (count-16)*2 ; VSTMIA r3,{D16-Dn} EXIT NOP ] count SETA count+1 WEND LoadContext_R0 ; Load context from R0 ; This should work with VFPv2/3, not sure about VFPv1 Entry "r2-r3" LDR r2, [r0,#Context_FPEXC] ASSERT FPEXC_EX = N_bit ASSERT FPEXC_FP2V = V_bit ASSERT FPEXC_EN = Z_bit ; EN bit will be clear if there are no data registers to restore (actually, entire FPEXC will be clear) MSR CPSR_f, r2 myVMSR EQ,FPEXC, r2 ; Don't write FPEXC if EN isn't set BPL %FT10 ; Must restore FPINST LDR r2, [r0,#Context_FPINST] myVMSR ,FPINST, r2 ; Might need to restore FPINST2 LDRVS r2, [r0,#Context_FPINST2] myVMSR VS,FPINST2, r2 10 LDREQ lr, [r0,#Context_NumRegs] LDR r2, [r0,#Context_FPSCR] ADDEQ r3, r0, #Context_RegDump myVMSR ,FPSCR, r2 ; Will only work on first use of new context if we already have CP access (which we currently will) ADDEQ pc, pc, lr, LSL #4 ; If we're still here, we need to reset FPEXC to default and load 0 regs MOV r2, #FPEXC_EN myVMSR ,FPEXC, r2 EXIT NOP NOP ; Generate VLDM jump table GBLA count count SETA 1 WHILE count < 33 [ count <= 16 DCI &EC930B00 + count*2 ; VLDMIA r3,{D0-Dn} EXIT NOP NOP | DCI &ECB30B20 ; VLDMIA r3!,{D0-D15} (we can only LDM 16 at once) DCI &ECD30B00 + (count-16)*2 ; VLDMIA r3,{D16-Dn} EXIT NOP ] count SETA count+1 WEND LTORG [ standalone ResourceFSFiles ResourceFile $MergedMsgs, Resources.VFPSupport.Messages DCD 0 ] END