https://bitbucket.org/daniel_fort/magic-lantern
Tip revision: 1df67d1a6ff5d7126bb1f4966384250cd3f6609e authored by a1ex on 21 May 2014, 05:38:00 UTC
Close branch shutter_display
Close branch shutter_display
Tip revision: 1df67d1
fpu_emu.c
/**
* fpu_emu: Emulate ARM FPA code in software, uses illegal instruction trap
*/
#include <module.h>
#include <dryos.h>
#include <property.h>
#include <bmp.h>
#include <menu.h>
#include <tasks.h>
#include <config.h>
/* this file uses FPA11 structure, so include the definition */
#include <nwfpe/fpa11.h>
#define FPU_EMU_STACK_SIZE 512
#define FPU_EMU_MAX_TASKS 256
/* the stack pointer is defined in assembly code, declare it here */
extern uint32_t fpu_emu_stack;
/* this buffer stores both stack and the context of the trapping task */
uint32_t fpu_emu_context_buffer[FPU_EMU_STACK_SIZE + 18 /* context */];
void (*fpu_emu_orig_undef_handler)(void) = NULL;
uint32_t fpu_emu_undef_handler_addr = 0;
/* pointer to the trapping task ctx, will point into fpu_emu_context_buffer */
uint32_t *fpu_emu_current_ctx = NULL;
/* counters and task information */
uint32_t fpu_emu_call_count = 0;
uint32_t fpu_emu_error_count = 0;
uint32_t fpu_emu_signal_count = 0;
uint32_t fpu_emu_trap_addr = 0;
int32_t fpu_emu_trap_task = 0;
/* for every task we have a separate FPU register set */
static FPA11 *fpu_emu_fp_ctx[FPU_EMU_MAX_TASKS];
/* this is the C code of our undefined instr handler. it may alter registers given in ctx
context save format:
[R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 SP LR PC CPSR]
*/
void fpu_emu_handler(uint32_t *ctx)
{
fpu_emu_trap_addr = ctx[15];
fpu_emu_trap_task = get_current_task() & 0xFF;
uint32_t opcode = MEM(fpu_emu_trap_addr);
/* store task context */
fpu_emu_current_ctx = ctx;
/* check condition (NE, LT, ...) */
if(checkCondition(opcode, ctx[16]))
{
if(!EmulateAll(opcode))
{
fpu_emu_error_count++;
}
else
{
fpu_emu_call_count++;
}
}
ctx[15] += 4;
/* we could try the next instruction for a massive speedup, but play safe for now */
fpu_emu_current_ctx = NULL;
}
void __attribute__((naked)) fpu_emu_undef_handler()
{
/* undefined instruction exception occurred. switch stacks, call handler and return */
asm(
/* keep space for CPSR */
"LDR SP, fpu_emu_stack\n"
"SUB SP, #0x04\n"
/* then store PC */
"SUB LR, #0x04\n"
"STMFD SP!, {LR}\n"
/* then LR and SP - but this is not possible yet, so just decrease SP */
"SUB SP, #0x08\n"
/* then store shared registers */
"STMFD SP!, {R0-R12}\n"
/* get R15(PC) and SPSR(CPSR_last) */
"MRS R3, SPSR\n"
/* switch back to last mode, disbling IRQ/FIQ and get SP and LR */
"ORR R5, R3, #0xC0\n"
"MRS R6, CPSR\n"
"MSR CPSR_cxsf, R5\n"
"MOV R0, SP\n"
"MOV R1, LR\n"
"MSR CPSR_cxsf, R6\n"
/* and store SP, LR and then CPSR */
"ADD R4, SP, #0x3C\n"
"STMFD R4, {R0-R1}\n"
"ADD R4, SP, #0x44\n"
"STMFD R4, {R3}\n"
"MOV R0, SP\n"
"BL fpu_emu_handler\n"
/* all the way back. get CPSR */
"ADD R4, SP, #0x40\n"
"LDMFD R4, {R3}\n"
/* restore SP and LR */
"ADD R4, SP, #0x34\n"
"LDMFD R4, {R0-R1}\n"
/* switch back to last mode, disbling IRQ/FIQ and set SP and LR */
"ORR R5, R3, #0xC0\n"
"MRS R6, CPSR\n"
"MSR CPSR_cxsf, R5\n"
"MOV SP, R0\n"
"MOV LR, R1\n"
"MSR CPSR_cxsf, R6\n"
/* restore SPSR */
"MSR SPSR_cxsf, R3\n"
"LDMFD SP!, {R0-R12}\n"
/* skip SP and LR, get PC then skip CPSR again as these are restored alredy */
"ADD SP, #0x08\n"
"LDMFD SP!, {LR}\n"
"ADD SP, #0x04\n"
/* jump back */
"MOVS PC, LR\n"
"fpu_emu_stack:\n"
".word 0x00000000\n"
);
}
static uint32_t fpu_emu_install()
{
uint32_t undef_handler_opcode = *(uint32_t*)0x04;
/* make sure abort handler address is loaded into PC using PC relative LDR */
if((undef_handler_opcode & 0xFFFFF000) != 0xE59FF000)
{
bmp_printf(FONT(FONT_MED, COLOR_RED, COLOR_BLACK), 0, 0, "fpu_emu: Failed to find hook point");
return -1;
}
/* extract offset from LDR */
fpu_emu_undef_handler_addr = (undef_handler_opcode & 0x00000FFF) + 0x04 + 0x08;
/* first install stack etc */
fpu_emu_stack = (uint32_t)&fpu_emu_context_buffer[FPU_EMU_STACK_SIZE + 17];
fpu_emu_context_buffer[17] = 0xDEADBEEF;
/* then patch handler */
fpu_emu_orig_undef_handler = (void*)MEM(fpu_emu_undef_handler_addr);
MEM(fpu_emu_undef_handler_addr) = (uint32_t)&fpu_emu_undef_handler;
if(MEM(fpu_emu_undef_handler_addr) != (uint32_t)&fpu_emu_undef_handler)
{
bmp_printf(FONT(FONT_MED, COLOR_RED, COLOR_BLACK), 0, 0, "fpu_emu: Failed to install trap handler");
return -1;
}
return 0;
}
static uint32_t fpu_emu_uninstall()
{
/* just remove undefined instruction trap pointer */
MEM(fpu_emu_undef_handler_addr) = (uint32_t)fpu_emu_orig_undef_handler;
return 0;
}
static MENU_UPDATE_FUNC(fpu_emu_update_count)
{
MENU_SET_VALUE("OK:%d S:%d E:%d", fpu_emu_call_count, fpu_emu_signal_count, fpu_emu_error_count);
}
static MENU_UPDATE_FUNC(fpu_emu_update_addr)
{
MENU_SET_VALUE("0x%08X", fpu_emu_trap_addr);
}
static MENU_UPDATE_FUNC(fpu_emu_update_task)
{
if(fpu_emu_trap_task)
{
char *name = (char*)get_task_name_from_id(fpu_emu_trap_task);
MENU_SET_VALUE("%d: %s", fpu_emu_trap_task, name);
}
else
{
MENU_SET_VALUE("(none)");
}
}
static uint32_t __attribute__((naked)) fpu_emu_test(uint32_t a, uint32_t b)
{
asm("\
MCR p1, 0, R0,c0,c0, 0 ; /* FLTS F0, R0; */ \
MCR p1, 0, R1,c1,c0, 0 ; /* FLTS F1, R1; */ \
MCR p1, 0, R1,c2,c0, 0 ; /* FLTS F2, R1; */ \
CDP p1, 0, c0,c0,c1, 0 ; /* ADFS F0, F0, F1; */ \
CDP p1, 1, c0,c2,c0, 0 ; /* MUFS F0, F2, F0; */ \
CDP p1, 1, c0,c2,c0, 0 ; /* MUFS F0, F2, F0; */ \
CDP p1, 4, c0,c0,c13, 0 ; /* DVFS F0, F0, #5; */ \
MRC p1, 0, R0,c0,c0, 0 ; /* FIX R0, F0; */ \
MOV PC, LR ;\
");
}
static uint32_t fpu_emu_check()
{
/* make sure the range doesn't exceed unsigned integer range and is dividable by 5 */
uint32_t var0 = (rand() & 0x3F) + 1;
uint32_t var1 = ((rand() & 0x3F) + 1) * 5;
uint32_t ret_int = ((var0 + var1) * var1 * var1) / 5;
return (fpu_emu_test(var0, var1) == ret_int);
}
static MENU_SELECT_FUNC(fpu_emu_select)
{
for(uint32_t loop = 0; loop < 100; loop++)
{
if(!fpu_emu_check())
{
NotifyBox(2000, "FPU check error");
return;
}
}
NotifyBox(2000, "FPU check successful");
}
static struct menu_entry fpu_emu_menu[] =
{
{
.name = "FPU Emu",
.select = menu_open_submenu,
.children = (struct menu_entry[])
{
{
.name = "Test FPU",
.update = fpu_emu_update_count,
.select = fpu_emu_select,
},
{
.name = "Last PC",
.update = fpu_emu_update_addr,
},
{
.name = "Task",
.update = fpu_emu_update_task,
},
MENU_EOL,
}
}
};
static unsigned int fpu_emu_init()
{
/* initialize FPU states for every possible task */
for(int pos = 0; pos < COUNT(fpu_emu_fp_ctx); pos++)
{
fpu_emu_fp_ctx[pos] = malloc(sizeof(FPA11));
nwfpe_init_fpa(fpu_emu_fp_ctx[pos]);
}
fpu_emu_install();
menu_add("Debug", fpu_emu_menu, COUNT(fpu_emu_menu));
return 0;
}
static unsigned int fpu_emu_deinit()
{
fpu_emu_uninstall();
return 0;
}
/* accessor functions for NWFPE code to read/write tasks CPU registers */
uint32_t fpu_emu_read_reg(uint32_t reg)
{
return fpu_emu_current_ctx[reg];
}
void fpu_emu_write_reg(uint32_t reg, uint32_t val)
{
fpu_emu_current_ctx[reg] = val;
}
/* this function must return a pointer to the current tasks FPU state */
FPA11 *fpu_emu_get_fpa11()
{
return fpu_emu_fp_ctx[fpu_emu_trap_task];
}
/* the libraries were designed for linux with MMU systems where user memory maps differently
and is not directly accessible from kernel context. not needed for our MMU-less system */
void put_user(uint32_t val, void *addr)
{
MEM(addr) = val;
}
void get_user(uint32_t *val, void *addr)
{
*val = MEM(addr);
}
/* just count the signals, but dont handle yet. do we need signalling? */
void float_raise(signed char flags)
{
fpu_emu_signal_count++;
}
/* those are missing, i hope they are correct */
void __attribute__((naked)) __ashldi3()
{
asm("\
subs r3, r2, #32 ;\
rsb r12, r2, #32 ;\
movmi r1, r1, lsl r2 ;\
movpl r1, r0, lsl r3 ;\
orrmi r1, r1, r0, lsr r12 ;\
mov r0, r0, lsl r2 ;\
mov pc, lr ;\
");
}
void __attribute__((naked)) __lshrdi3()
{
asm("\
subs r3, r2, #32 ;\
rsb r12, r2, #32 ;\
movmi r0, r0, lsr r2 ;\
movpl r0, r1, lsr r3 ;\
orrmi r0, r0, r1, lsl r12 ;\
mov r1, r1, lsr r2 ;\
mov pc, lr ;\
");
}
MODULE_INFO_START()
MODULE_INIT(fpu_emu_init)
MODULE_DEINIT(fpu_emu_deinit)
MODULE_INFO_END()
MODULE_CBRS_START()
MODULE_CBRS_END()
MODULE_CONFIGS_START()
MODULE_CONFIGS_END()