From f56201af6c85743c05d093c89dea3b96d276b6c8 Mon Sep 17 00:00:00 2001 From: Lephe Date: Wed, 17 Jul 2019 12:59:17 -0400 Subject: [PATCH] some gint and performance tests --- Makefile | 29 ++-- include/gintctl/gint.h | 14 ++ include/gintctl/menu.h | 45 ++++++ include/gintctl/perf.h | 14 ++ include/gintctl/prof-contexts.h | 24 ++++ include/gintctl/util.h | 68 +++++++++ main.c | 131 +++++------------ res-cg/opt_main.png | Bin 0 -> 770 bytes resources/opt_main.png | Bin 0 -> 3089 bytes src/gint/hardware.c | 239 ++++++++++++++++++++++++++++++++ src/gint/timer.c | 109 +++++++++++++++ src/gintctl.c | 194 ++++++++++++++++++++++++-- src/menu.c | 76 ++++++++++ src/perf/libprof.c | 69 +++++++++ src/perf/render.c | 97 +++++++++++++ src/util.c | 193 ++++++++++++++++++++++++++ 16 files changed, 1184 insertions(+), 118 deletions(-) create mode 100644 include/gintctl/gint.h create mode 100644 include/gintctl/menu.h create mode 100644 include/gintctl/perf.h create mode 100644 include/gintctl/prof-contexts.h create mode 100644 include/gintctl/util.h create mode 100644 res-cg/opt_main.png create mode 100644 resources/opt_main.png create mode 100644 src/gint/hardware.c create mode 100644 src/gint/timer.c create mode 100644 src/menu.c create mode 100644 src/perf/libprof.c create mode 100644 src/perf/render.c create mode 100644 src/util.c diff --git a/Makefile b/Makefile index 934dc42..1217959 100755 --- a/Makefile +++ b/Makefile @@ -8,13 +8,13 @@ # Compiler flags cf := -mb -ffreestanding -nostdlib -Wall -Wextra -std=c11 -Os \ - -fstrict-volatile-bitfields + -fstrict-volatile-bitfields -I include cf-fx := $(cf) -m3 -DFX9860G cf-cg := $(cf) -m4-nofpu -DFXCG50 # Linker flags -lf-fx := -Tfx9860g.ld -lgint-fx -lgcc -Wl,-Map=build.fx/map -lf-cg := -Tfxcg50.ld -lgint-cg -lgcc -Wl,-Map=build.cg/map -L. -lfxcg +lf-fx := -Tfx9860g.ld -lprof -lgint-fx -lgcc -Wl,-Map=build.fx/map +lf-cg := -Tfxcg50.ld -lprof -lgint-cg -lgcc -Wl,-Map=build.cg/map -L. -lfxcg dflags = -MMD -MT $@ -MF $(@:.o=.d) -MP cpflags := -R .bss -R .gint_bss @@ -32,8 +32,8 @@ target-fx := gintctl.g1a target-cg := gintctl.g3a # Source and object files -src := $(wildcard *.c) -res := $(wildcard resources/*) +src := $(shell find src -name '*.c') +res := $(wildcard resources/*.png) obj-fx := $(src:%.c=build.fx/%.o) $(res:resources/%=build.fx/%.o) obj-cg := $(src:%.c=build.cg/%.o) $(res:resources/%=build.cg/%.o) @@ -63,21 +63,22 @@ $(target-cg): $(obj-cg) $(deps-cg) mkg3a $(g3af) $(bin) $@ # C sources -build.fx/%.o: %.c | build.fx/ +build.fx/%.o: %.c + @ mkdir -p $(dir $@) sh3eb-elf-gcc -c $< -o $@ $(cf-fx) $(dflags) -build.cg/%.o: %.c | build.cg/ +build.cg/%.o: %.c + @ mkdir -p $(dir $@) sh4eb-elf-gcc -c $< -o $@ $(cf-cg) $(dflags) # Images -build.fx/%.png.o: resources/%.png | build.fx/ +build.fx/%.png.o: resources/%.png + @ mkdir -p $(dir $@) fxconv -i $< -o $@ name:$* -build.cg/%.png.o: resources/%.png | build.cg/ +build.cg/%.png.o: resources/%.png @ echo -e "\e[31;1mWARNING: conversion for fxcg50 not supported yet\e[0m" + @ mkdir -p $(dir $@) fxconv -i $< -o $@ name:$* -%/: - @ mkdir -p $@ - # # Cleaning and utilities # @@ -96,8 +97,8 @@ distclean: clean install-fx: $(target-fx) p7 send -f $< install-cg: $(target-cg) - @ while [[ ! -h /dev/Prizm1 ]]; do sleep 1; done - @ mount /dev/Prizm1 + @ while [[ ! -h /dev/Prizm1 ]]; do sleep 0.25; done + @ while ! mount /dev/Prizm1; do sleep 0.25; done @ rm -f /mnt/prizm/$< @ cp $< /mnt/prizm @ umount /dev/Prizm1 diff --git a/include/gintctl/gint.h b/include/gintctl/gint.h new file mode 100644 index 0000000..b9c7480 --- /dev/null +++ b/include/gintctl/gint.h @@ -0,0 +1,14 @@ +//--- +// gintctl:gint - gint feature tests +//--- + +#ifndef GINTCTL_GINT +#define GINTCTL_GINT + +/* gintctl_gint_hardware(): Detected hardware configuration */ +void gintctl_gint_hardware(void); + +/* gintctl_gint_timer(): Show the timer status in real-time */ +void gintctl_gint_timer(void); + +#endif /* GINTCTL_GINT */ diff --git a/include/gintctl/menu.h b/include/gintctl/menu.h new file mode 100644 index 0000000..48998f5 --- /dev/null +++ b/include/gintctl/menu.h @@ -0,0 +1,45 @@ +//--- +// gintctl:menu - Interactive list menus +//--- + +#ifndef GINTCTL_MENU +#define GINTCTL_MENU + +#include +#include + +/* struct menuentry: Selectable list element */ +struct menuentry { + char const *name; + void (*function)(void); +}; + +struct menu { + char const *name; + + int len; + int offset; + int pos; + int visible; + + struct menuentry entries[]; +}; + +/* menu_init(): Initialize a menu list + @menu Any list menu, even unitialized + @visible Number of lines that can be shown simultaneously */ +void menu_init(struct menu *menu, int visible); + +/* menu_move(): Move the cursor in a menu + @menu Initialized list menu + @key Either KEY_UP, indicating up, or KEY_DOWN indicating down + @wrap Allow wrap-around */ +void menu_move(struct menu *menu, int key, int wrap); + +/* menu_show(): Render a list menu */ +void menu_show(struct menu const *menu); + +/* menu_exec(): Execute the currently-selected function of a menu */ +void menu_exec(struct menu const *menu); + +#endif /* GINTCTL_MENU */ diff --git a/include/gintctl/perf.h b/include/gintctl/perf.h new file mode 100644 index 0000000..51b9b53 --- /dev/null +++ b/include/gintctl/perf.h @@ -0,0 +1,14 @@ +//--- +// gintctl:perf - Performance and benchmarks +//--- + +#ifndef GINTCTL_PERF +#define GINTCTL_PERF + +/* gintctl_perf_libprof(): Basic libprof tests using timers */ +void gintctl_perf_libprof(void); + +/* gintctl_perf_render(): Profile the display primitives */ +void gintctl_perf_render(void); + +#endif /* GINTCTL_PERF */ diff --git a/include/gintctl/prof-contexts.h b/include/gintctl/prof-contexts.h new file mode 100644 index 0000000..8130ede --- /dev/null +++ b/include/gintctl/prof-contexts.h @@ -0,0 +1,24 @@ +//--- +// gintctl:prof-contexts - All contexts under which profiling is used +//--- + +#ifndef GINTCTL_PROF_CONTEXTS +#define GINTCTL_PROF_CONTEXTS + +/* libprof requires its user to declare the number of execution contexts that + are being profiled. The number and IDs is maintained here */ + +enum { + PROFCTX_BASICS = 0, + + PROFCTX_DCLEAR, + PROFCTX_DUPDATE, + PROFCTX_DRECT1, + PROFCTX_DRECT2, + PROFCTX_DRECT3, + + /* Last element and bound checker */ + PROFCTX_COUNT, +}; + +#endif /* GINTCTL_PROF_CONTEXTS */ diff --git a/include/gintctl/util.h b/include/gintctl/util.h new file mode 100644 index 0000000..5746add --- /dev/null +++ b/include/gintctl/util.h @@ -0,0 +1,68 @@ +//--- +// gintctl:util - Utility functions +//--- + +#ifndef GINTCTL_UTIL +#define GINTCTL_UTIL + +#include + +//--- +// Platform disambiguation functions +//--- + +#ifdef FX9860G +#define _(fx,cg) fx +#endif + +#ifdef FXCG50 +#define _(fx,cg) cg +#endif + +//--- +// Row manipulation functions +//--- + +/* row_title(): Render the main title */ +void row_title(char const *format, ...); + +/* row_print(): Formatted printing in a predefined row */ +void row_print(int row, int x, char const *format, ...); + +/* row_highlight(): Invert a row's pixels to highlight it */ +void row_highlight(int row); + +/* row_right(): Print at the last column of a row */ +void row_right(int row, char const *character); + +/* scrollbar(): Show a scrollbar */ +void scrollbar(int offset, int length); + +/* row_count(): Number of rows available to row_print() */ +int row_count(void); + +//--- +// General (x,y) printing +//--- + +/* print(): Formatted printing shorthand */ +void print(int x, int y, char const *format, ...); + +//--- +// F-key rendering +//--- + +#ifdef FXCG50 + +/* fkey_action(): A black-on-white F-key */ +void fkey_action(int position, char const *text); + +/* fkey_button(): A rectangular F-key */ +void fkey_button(int position, char const *text); + +/* fkey_menu(): A rectangular F-key with the bottom right corner removed */ +void fkey_menu(int position, char const *text); + +#endif /* FXCG50 */ + +#endif /* GINTCTL_UTIL */ diff --git a/main.c b/main.c index 1e33c52..c2d9278 100644 --- a/main.c +++ b/main.c @@ -18,27 +18,6 @@ extern void Bdisp_AllClr_VRAM(); #define color_white 0xffff #endif - -/* print() - formatted printing shorthand */ -static void print(int x, int y, const char *format, ...) -{ - char str[45]; - va_list args; - va_start(args, format); - - vsprintf(str + 2, format, args); - - #ifdef FX9860G - dtext(6 * (x - 1) + 1, 7 * (y - 1), str + 2, color_black, color_white); - #endif - - #ifdef FXCG50 - PrintXY(x, y, str, 0, 0); - #endif - - va_end(args); -} - void print_bin(int x, int y, uint32_t bin, int digits) { char str[33]; @@ -53,66 +32,7 @@ void print_bin(int x, int y, uint32_t bin, int digits) print(x, y, str); } -void debug(uint32_t event_code) -{ - print(1, 6, "%8x", event_code); - dupdate(); -} - -void debug_exc(uint32_t event_code) -{ - uint32_t spc; - __asm__("stc spc, %0": "=r"(spc)); - - print(1, 1, "EXCEPTION==%8x==", event_code); - print(1, 2, "SPC=%8x=========", spc); - - if(isSH4()) - { - volatile uint32_t *TEA = (void *)0xff00000c; - print(1, 3, "TEA=%8x=========", *TEA); - } - dupdate(); -} - #if 0 -volatile int delay_one_ended = 0; -int stop(__attribute__((unused)) void *arg) -{ - delay_one_ended = 1; - return 1; -} -void delay_one(void) -{ - int delay_us = 50000; - static int tid = 1; - -/* if(tid == (isSH3() ? 3 : 7)) tid = (tid + 1) % timer_count(); - - delay_one_ended = 0; - timer_setup(tid, timer_delay(tid,delay_us), timer_default, stop, NULL); - timer_start(tid); - - while(!delay_one_ended) __asm__("sleep"); - timer_free(tid); */ - -// tid = (tid + 1) % timer_count(); -} -void delay(int k) -{ - for(int i = k; i > 0; i--) - { -#ifdef FX9860G - Bdisp_ClearLineVRAM(127, 0, 127, 63); - Bdisp_DrawLineVRAM(127, 0, 127, (63 * i) / k); - Bdisp_PutDisp_DD(); -#endif - delay_one(); - } -} - -#include - int callback(void *arg) { volatile int *counter = arg; @@ -124,7 +44,7 @@ void test_clock(void) { const clock_frequency_t *freq = clock_freq(); - Bdisp_AllClr_VRAM(); + dclear(color_white); print(1, 1, "FLL: %8d", freq->FLL); print(1, 2, "PLL: %8d", freq->PLL); @@ -135,28 +55,27 @@ void test_clock(void) print(1, 6, "Iphi = %10d", freq->Iphi_f); print(1, 7, "Pphi = %10d", freq->Pphi_f); - Bdisp_PutDisp_DD(); - delay(100); + dupdate(); + getkey(); volatile unsigned int *FRQCRA = (void *)0xa4150000; volatile unsigned int *FRQCRB = (void *)0xa4150004; volatile unsigned int *PLLCR = (void *)0xa4150024; volatile unsigned int *FLLFRQ = (void *)0xa4150050; - Bdisp_AllClr_VRAM(); + dclear(color_white); print(1, 1, "%8x", *FRQCRA); print(1, 2, "%8x", *FRQCRB); print(1, 3, "%8x", *PLLCR); print(1, 4, "%8x", *FLLFRQ); - Bdisp_PutDisp_DD(); - delay(100); + dupdate(); + getkey(); } void test_timer_simultaneous(void) { volatile int counters[9] = { 0 }; int count = timer_count(); - Bdisp_AllClr_VRAM(); for(int tid = 0; tid < count; tid++) { @@ -176,12 +95,12 @@ void test_timer_simultaneous(void) for(int i = 0; i < limit; i++) { - Bdisp_AllClr_VRAM(); + dclear(color_white); for(int k = 0; k < 9; k++) print(2 * k + 1, 1, "%1x", counters[k]); print(1, 8, "%4d", i); - Bdisp_PutDisp_DD(); + dupdate(); } for(int tid = 0; tid < count; tid++) timer_free(tid); @@ -204,7 +123,7 @@ void test_rtc_time(void) "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", }; - Bdisp_AllClr_VRAM(); + dclear(color_white); rtc_get_time(&time); print(1, 1, "%2d:%2d:%2d", @@ -214,7 +133,7 @@ void test_rtc_time(void) days[time.week_day]); print(1, 8, "%4d", i); - Bdisp_PutDisp_DD(); + dupdate(); } } @@ -581,13 +500,37 @@ void fx_frame2(void) typedef void asm_text_t(uint32_t *v1, uint32_t *v2, uint32_t *op, int height); extern asm_text_t *topti_asm_text[8]; +static void show_bootlog(void) +{ + extern char gint_bootlog[22*9]; + + int i = 0; + for(int y = 0; y < 9; y++) + { + for(int x = 0; x < 21; x++) + { + if(!gint_bootlog[i]) gint_bootlog[i] = ' '; + i++; + } + gint_bootlog[i] = 0; + i++; + } + + dclear(color_white); + + for(int y = 0; y < 9; y++) + print(1, y + 1, gint_bootlog + 22 * y); + dupdate(); + + getkey(); +} + int main(GUNUSED int isappli, GUNUSED int optnum) { - getkey(); #ifdef FX9860G - extern image_t pattern; - extern image_t pattern2; +// extern image_t pattern; +// extern image_t pattern2; /* image_t *img = &pattern; diff --git a/res-cg/opt_main.png b/res-cg/opt_main.png new file mode 100644 index 0000000000000000000000000000000000000000..59bf4acd500680d9279829dbed69378d9efabb54 GIT binary patch literal 770 zcmV+d1O5DoP)RCwC$T*0!#APB5)`v0GtyhCO(jv%6fk#srQ*0_t{ zil*s2&y><9l%40vwgU)&0Ei4EqNSkYralNajQ|8dkf${E4TmS_jDf*mWA+CMU@%w! zCDo-_F4=W`TGPh)cbZx>|C*nst39z0}IJldGm*3_S}a<+XP!h^8$j>8bEEZTW?wgN6sT&pqkVjC_4H!-T6hmA!qH)XkKf z@|%Qe7@t|qx^iOvxcg+YXs_uDVR%+6vvT|tLf6t-RhxaXL^Zoz z>=!G~LP;5G&LUKVATe2#=3Tzk`qm3U&jW3=lDA_jgZ))a@ipNnA+HYy(cPTzHdzoEC_E_hi_7mVHVkP3hev{&_lt*Te>zDi^VC+>Jl@(iPuZTku{^L&jQCnY)6pI!h55}mHhnHh z>cdr_-?T;2XpL=mbMu|7$;*pl^7f2Z%RtHA458WB>pF07*qoM6N<$f{dPM A*8l(j literal 0 HcmV?d00001 diff --git a/resources/opt_main.png b/resources/opt_main.png new file mode 100644 index 0000000000000000000000000000000000000000..c81b9b41571a6f0c55d5be2389774bc877af65ad GIT binary patch literal 3089 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 fQSbk*`X+q$2^@*H500000NkvXXu0mjf>%RVc literal 0 HcmV?d00001 diff --git a/src/gint/hardware.c b/src/gint/hardware.c new file mode 100644 index 0000000..49e94bc --- /dev/null +++ b/src/gint/hardware.c @@ -0,0 +1,239 @@ +#include +#include +#include + +#include +#include + +#define put(...) row_print(++(*row), 1, __VA_ARGS__) + +#define load_barrier(x) { \ + if(!((x) & HW_LOADED)) { \ + put(" (not loaded)"); \ + return; \ + } \ +} + +/* MPU type and processor version */ +static void hw_mpucpu(int *row) +{ + char const *mpu_names[] = { + #ifdef FX9860G + "Unknown", + "SH-3 SH7337", + "SH-4A SH7305", + "SH-3 SH7355", + "SH-4A SH7724", + #else + "Unknown MPU product", + "SH-3-based SH7337", + "SH-4A-based SH7305", + "SH-3-based SH7355", + "SH-4A-based SH7724", + #endif + }; + int mpu = gint[HWMPU]; + + put(_("MPU and CPU","MPU product and CPU features:")); + if(mpu < 0 || mpu > 4) put(" ", mpu); + else put(" %s", mpu_names[mpu]); + + if(!isSH4()) return; + put(_(" PVR"," Processor Version Register") ": %08x", gint[HWCPUVR]); + put(_(" PRR"," Product Register") ": %08x", gint[HWCPUPR]); +} + +/* Memory */ +static void hw_memory(int *row) +{ + int mmu = gint[HWMMU]; + int rom = gint[HWROM]; + int ram = gint[HWRAM]; + int uram = gint[HWURAM]; + + put("Memory and MMU" _(,":")); + load_barrier(mmu); + + put(" ROM: %dM", rom >> 20); + + #ifdef FX9860G + put(" RAM: %dk (%dk user)", ram >> 10, uram >> 10); + #else + put(" RAM: %dM (%dk mapped in userspace)", ram >> 20, uram >> 10); + #endif + + if(mmu & HWMMU_UTLB) put(" TLB is unified"); + if(mmu & HWMMU_FITTLB) put( + _(" Add-in fits in TLB"," Add-in is fully mapped in the TLB")); +} + +/* Clock Pulse generator */ +static void hw_cpg(int *row) +{ + int cpg = gint[HWCPG]; + + put("Clock Pulse Generator" _(,":")); + load_barrier(cpg); + + if(cpg & HWCPG_COMP) put( + _(" Input freq known"," Input clock frequency is known")); + if(cpg & HWCPG_EXT) put( + _(" CPG is extended"," SH7724-style extended module")); +} + +/* Direct Memory Access Controller */ +static void hw_dma(int *row) +{ + int dma = gint[HWDMA]; + + put("Direct Memory Access" _(," Controller:")); + load_barrier(dma); + + put(" (loaded)"); +} + +/* Timer Unit */ +static void hw_tmu(int *row) +{ + int tmu = gint[HWTMU]; + + put("Timer Unit" _(,":")); + load_barrier(tmu); + + put(" (loaded)"); +} + +/* Extra Timer Unit */ +static void hw_etmu(int *row) +{ + int etmu = gint[HWETMU]; + + put("Extra Timer Unit" _(,":")); + load_barrier(etmu); + + if(etmu & HWETMU_1) + { + put(" Extra timers: 1"); + put(" Operational: %c", (etmu & HWETMU_OK0 ? 'y' : 'n')); + } + else if(etmu & HWETMU_6) + { + char operational[7] = { 0 }; + for(int i = 0; i < 6; i++) + operational[i] = etmu & (1 << (i + 2)) ? 'y' : 'n'; + + put(" Extra timers: 6"); + put(" Operational: %s", operational); + } +} + +/* Real-Time Clock */ +static void hw_rtc(int *row) +{ + int rtc = gint[HWRTC]; + + put("Real-Time Clock" _(,":"), rtc); + load_barrier(rtc); + + if(rtc & HWRTC_TIMER) put(" timer enabled"); +} + +/* Keyboard */ +static void hw_keyboard(int *row) +{ + int kbd = gint[HWKBD]; + + put("Keyboard" _(,":"), kbd); + load_barrier(kbd); + + if(kbd & HWKBD_IO) + { + put(_(" I/O driven","Driven by I/O port scanning")); + put(kbd & HWKBD_WDD + ? _(" Watchdog delay"," Watchdog timer I/O delays") + : _(" Active delay"," Active-waiting I/O delays")); + } + if(kbd & HWKBD_KSI) + { + put(_(" Key scan interface", + " Driven by SH7305-style key scan interface")); + } + + put(_(" Scans at %dHz"," Scans keys at %dHz"), gint[HWKBDSF]); +} + +/* Display driver */ +static void hw_display(int *row) +{ + int dd = gint[HWDD]; + + put("Display" _(,":")); + load_barrier(dd); + + #ifdef FXCG50 + if(dd & HWDD_KNOWN) put(" Known R61524-type model"); + if(dd & HWDD_FULL) put(" Fullscreen mode enabled (no borders)"); + #endif + + #ifdef FX9860G + if(dd & HWDD_CONTRAST) put(" Contrast known"); + #endif + + if(dd & HWDD_LIGHT) put(_(" Backlight supported", + " Backlight configuration is enabled")); +} + +static int display_data(int offset) +{ + int row_value = -offset; + int *row = &row_value; + + hw_mpucpu(row); + put(""); + + hw_memory(row); + put(""); + + hw_cpg(row); + put(""); + + hw_dma(row); + put(""); + + hw_tmu(row); + hw_etmu(row); + put(""); + + hw_rtc(row); + put(""); + + hw_keyboard(row); + put(""); + + hw_display(row); + + return row_value + offset; +} + +/* gintctl_hardware(): Show the hardware screen */ +void gintctl_gint_hardware(void) +{ + int offset = 0; + int max, key = 0; + + while(key != KEY_EXIT) + { + dclear(C_WHITE); + row_title(_("Hardware", "Hardware and loaded drivers")); + + max = display_data(offset); + scrollbar(offset, max); + + dupdate(); + + key = getkey().key; + + if(key == KEY_UP && offset > 0) offset--; + if(key == KEY_DOWN && max - offset > row_count()) offset++; + } +} diff --git a/src/gint/timer.c b/src/gint/timer.c new file mode 100644 index 0000000..f3bd3fa --- /dev/null +++ b/src/gint/timer.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include + +#include +#include + +/* tmu_print(): Print a TMU's details */ +void tmu_print(int x, int y, char const *name, tmu_t *tmu, int running) +{ + int dx = _(27,45), dy = _(8,14); + print(x, y, "%s:", name); + if(!tmu) + { + print(x, y + dy, "(null)"); + return; + } + + print(x, y + dy, "TCOR"); + print(x + dx, y + dy, "%08x", tmu->TCOR); + + print(x, y + 2 * dy, "TCNT"); + print(x + dx, y + 2 * dy, "%08x", tmu->TCNT); + + print(x, y + 3 * dy, "%s%s%s", + tmu->TCR.UNIE ? "UNIE " : "", + tmu->TCR.UNF ? "UNF " : "", + running ? "TSTR " : "" + ); +} + +/* etmu_print(): Print an ETMU's details */ +void etmu_print(int x, int y, char const *name, etmu_t *etmu) +{ + int dx = _(27,45), dy = _(8,14); + print(x, y, "%s:", name); + if(!etmu) + { + print(x, y + dy, "(null)"); + return; + } + + print(x, y + dy, "TCOR"); + print(x + dx, y + dy, "%08x", etmu->TCOR); + + print(x, y + 2 * dy, "TCNT"); + print(x + dx, y + 2 * dy, "%08x", etmu->TCNT); + + print(x, y + 3 * dy, "%s%s%s", + etmu->TCR.UNIE ? "UNIE " : "", + etmu->TCR.UNF ? "UNF " : "", + etmu->TSTR ? "TSTR " : "" + ); +} + +/* gintctl_gint_timer(): Show the timer status in real-time */ +void gintctl_gint_timer(void) +{ + /* In this program we'll display the timer status at "full speed" and + hence ask getkey() to never wait. (The processor is still sleeping + during the DMA transfer to the screen on fxcg50, limiting the + program to ~90 FPS.) */ + int key = 0, timeout = 1; + + while(key != KEY_EXIT) + { + dclear(C_WHITE); + + #ifdef FX9860G + #warning gintctl_gint_timer not implemented yet + #endif + + #ifdef FXCG50 + row_title("Timer status"); + + volatile uint8_t *TSTR; + timer_address(0, &TSTR); + + tmu_print(6, 24, "TMU0", timer_address(0, NULL),*TSTR & 0x1); + tmu_print(138, 24, "TMU1", timer_address(1, NULL),*TSTR & 0x2); + tmu_print(270, 24, "TMU2", timer_address(2, NULL),*TSTR & 0x4); + + etmu_print(6, 84, "ETMU0", timer_address(3, NULL)); + etmu_print(138, 84, "ETMU1", timer_address(4, NULL)); + etmu_print(270, 84, "ETMU2", timer_address(5, NULL)); + etmu_print(6, 144, "ETMU3", timer_address(6, NULL)); + etmu_print(138, 144, "ETMU4", timer_address(7, NULL)); + etmu_print(270, 144, "ETMU5", timer_address(8, NULL)); + + fkey_button(1, "SLEEP"); + #endif + + dupdate(); + key = getkey_opt(GETKEY_DEFAULT, &timeout).key; + + + /* On F1, pretend to sleep and just see what happens */ + if(key == KEY_F1) + { + volatile int flag = 0; + + int free = timer_setup(5, timer_delay(5, 1000000), 0, + timer_timeout, &flag); + if(free >= 0) timer_start(5); + } + } +} diff --git a/src/gintctl.c b/src/gintctl.c index 0c0ce51..b2e436e 100644 --- a/src/gintctl.c +++ b/src/gintctl.c @@ -1,4 +1,19 @@ +#define GINT_NEED_VRAM + #include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include /* Drawing functions: * ... @@ -8,16 +23,175 @@ * Do more in-depth things than previous application * Stress testing for number of interrupts * ... - Boot log: - * Displays boot log on full screen - * F1 for details (get live explanation and detect problems) - * F2 to save to file */ + TODO: + * Add-in size and mappings + * Interrupt controller state? + * Clock frequencies + * F2 to save hardware data to file */ -void locate(int x, int y, const char *str) +/* gint test menu */ +struct menu menu_gint = { + _("gint tests", "gint features and driver tests"), .entries = { + + { "Hardware", gintctl_gint_hardware }, + { "Boot log", NULL }, + { "Keyboard", NULL }, + { "Timers", gintctl_gint_timer }, + { "Real-time clock", NULL }, + { "Image rendering", NULL }, + { "Text rendering", NULL }, + #ifdef FX9860G + { "Gray engine", NULL }, + #endif + { NULL, NULL }, +}}; + +/* Performance menu */ +struct menu menu_perf = { + _("Performance", "Performance benchmarks"), .entries = { + + { "libprof basics", gintctl_perf_libprof }, + { "Rendering primitives", gintctl_perf_render }, + { NULL, NULL }, +}}; + +void exch_debug_thing(__attribute__((unused)) int code) { -#ifdef FX9860G - dtext(6*(x-1), 7*(y-1), str); -#else - PrintXY(x, y, str - 2, 0, 0); -#endif + #ifdef FXCG50 + + uint32_t TEA = *((volatile uint32_t *)0xff00000c); + uint32_t TRA = *((volatile uint32_t *)0xff000020); + uint32_t PC; + + __asm__("stc spc, %0" : "=r"(PC)); + TRA = TRA >> 2; + + dclear(C_WHITE); + + print(6, 3, "An exception occured! (System ERROR)"); + + uint32_t *long_vram = (void *)vram; + for(int i = 0; i < 198 * 16; i++) long_vram[i] = ~long_vram[i]; + + char const *name = ""; + if(code == 0x040) name = "TLB miss (nonexisting address) on read"; + if(code == 0x060) name = "TLB miss (nonexisting address) on write"; + if(code == 0x0e0) name = "Read address error (probably alignment)"; + if(code == 0x100) name = "Write address error (probably alignment)"; + if(code == 0x160) name = "Unconditional trap"; + if(code == 0x180) name = "Illegal instruction"; + if(code == 0x1a0) name = "Illegal delay slot instruction"; + + print(6, 25, "%03x %s", code, name); + + print(6, 45, "PC"); + print(38, 45, "= %08x", PC); + print(261, 45, "(Error location)"); + + print(6, 60, "TEA"); + print(38, 60, "= %08x", TEA); + print(234, 60, "(Offending address)"); + + print(6, 75, "TRA"); + print(38, 75, "= %#x", TRA); + print(281, 75, "(Trap number)"); + + print(6, 95, "An unrecoverable error ocurred in the add-in."); + print(6, 108, "Please press the RESET button to restart the"); + print(6, 121, "calculator."); + + dupdate_noint(); + #endif + + while(1); +} + +//--- +// Main application +//--- + +/* gintctl_main(): Show the main tab */ +void gintctl_main(void) +{ + #ifdef FX9860G + row_title("gint @%07x", GINT_VERSION); + row_print(2, 1, "F2:gint tests"); + row_print(3, 1, "F3:MPU registers"); + row_print(4, 1, "F4:Memory map/dump"); + row_print(5, 1, "F5:Performance"); + #endif /* FX9860G */ + + #ifdef FXCG50 + row_title("gint @%07x for fx-CG 50", GINT_VERSION); + row_print(1, 1, "F2: gint features and driver tests"); + row_print(2, 1, "F3: MPU register browser"); + row_print(3, 1, "F4: Hexadecimal memory editor"); + row_print(4, 1, "F5: Performance benchmarks"); + + #ifdef GINT_BOOTLOG + extern char gint_bootlog[22 * 8]; + extern font_t *gint_default_font; + + for(int i = 1; i < 8; i++) dtext(8, 85 + 13 * i, gint_bootlog + 22 * i, + C_BLACK, C_NONE); + #endif /* GINT_BOOTLOG */ + #endif /* FXCG50 */ +} + + +int main(GUNUSED int isappli, GUNUSED int optnum) +{ + /* Initialize menu metadata */ + menu_init(&menu_gint, row_count()); + menu_init(&menu_perf, row_count()); + + /* Start the profiling library */ + prof_init(PROFCTX_COUNT, 2); + + int tab = 1, key = 0; + struct menu *menu = NULL; + + while(key != KEY_EXIT) + { + dclear(C_WHITE); + + if(tab == 1) gintctl_main(); + else if(menu) menu_show(menu); + else row_title("Nothing, essentially"); + + #ifdef FX9860G + extern image_t opt_main; + dimage(0, 56, &opt_main); + #else + fkey_action(1, "INFO"); + fkey_menu(2, "GINT"); + fkey_menu(3, "REGS"); + fkey_button(4, "MEMORY"); + fkey_menu(5, "PERF"); + #endif + + dupdate(); + key = getkey().key; + + if(key == KEY_F1) + tab = 1, menu = NULL; + if(key == KEY_F2) + tab = 2, menu = &menu_gint; + if(key == KEY_F3) + tab = 3, menu = NULL; + if(key == KEY_F4) + /* TODO: Launch memory explorer */ { } + if(key == KEY_F5) + tab = 5, menu = &menu_perf; + + if(!menu) continue; + + if(key == KEY_UP || key == KEY_DOWN) + menu_move(menu, key, 0); + if(key == KEY_EXE) + menu_exec(menu); + } + + prof_quit(); + return 0; } diff --git a/src/menu.c b/src/menu.c new file mode 100644 index 0000000..3705420 --- /dev/null +++ b/src/menu.c @@ -0,0 +1,76 @@ +#include + +#include +#include + +/* menu_init(): Initialize a menu list */ +void menu_init(struct menu *menu, int visible) +{ + menu->len = 0; + while(menu->entries[menu->len].name) menu->len++; + + menu->offset = 0; + menu->pos = 0; + menu->visible = visible; +} + +/* menu_move(): Move the cursor in a menu */ +void menu_move(struct menu *menu, int key, int wrap) +{ + int max_offset = max(menu->len - menu->visible, 0); + + if(key == KEY_UP && menu->pos > 0) + { + menu->pos--; + menu->offset = min(menu->offset, menu->pos); + } + else if(key == KEY_UP && !menu->pos && wrap) + { + menu->pos = menu->len - 1; + menu->offset = max_offset; + } + + if(key == KEY_DOWN && menu->pos + 1 < menu->len) + { + menu->pos++; + if(menu->pos > menu->offset + menu->visible - 1 + && menu->offset + 1 < max_offset) + { + menu->offset++; + } + } + else if(key == KEY_DOWN && menu->pos + 1 == menu->len && wrap) + { + menu->pos = 0; + menu->offset = 0; + } +} + +/* menu_show(): Render a list menu */ +void menu_show(struct menu const *menu) +{ + struct menuentry const *items = menu->entries; + int offset = menu->offset, pos = menu->pos; + int i = 0; + + row_title(menu->name); + + while(i+1 <= menu->visible && items[offset+i].name) + { + row_print(i+1, 2, items[offset+i].name); + i++; + } + + if(offset > 0) row_right(1, "^"); + if(items[offset+i].name) row_right(row_count(), "v"); + + int selected = pos - offset + 1; + if(selected >= 1 && selected <= menu->visible) row_highlight(selected); +} + +/* menu_exec(): Execute the currently-selected function of a menu */ +void menu_exec(struct menu const *menu) +{ + void (*fun)(void) = menu->entries[menu->pos].function; + if(fun) fun(); +} diff --git a/src/perf/libprof.c b/src/perf/libprof.c new file mode 100644 index 0000000..23bd6ef --- /dev/null +++ b/src/perf/libprof.c @@ -0,0 +1,69 @@ +#include +#include +#include + +#include +#include +#include + +#include + +/* Waits 1s and returns the libprof output in microseconds */ +static uint32_t run_test(void) +{ + int ctx = PROFCTX_BASICS; + + prof_clear(ctx); + + prof_enter(ctx); + + static int delay = 100000; + sleep_us(1, delay); + prof_leave(ctx); + + delay += 256; + return prof_time(ctx); +} + +/* gintctl_perf_libprof(): Test the libprof implementation */ +void gintctl_perf_libprof(void) +{ + int key = 0, test = 0; + uint32_t elapsed = 0; + + while(key != KEY_EXIT) + { + dclear(C_WHITE); + row_title("libprof basics"); + + #ifdef FX9860G + #warning gintctl_perf_libprof incomplete on fx9860g + + row_print(2, 2, "Checks that libprof"); + row_print(3, 2, "Press F1 to start."); + row_print(5, 2, "Delay: 1s"); + + if(test == 1) row_print(6, 2, "Elapsed: %8xus", elapsed); + #endif + + #ifdef FXCG50 + row_print(1, 1, "This program shows the execution time " + "measured"); + row_print(2, 1, "by libprof for a 100 ms sleep, with 256us " + "added"); + row_print(3, 1, "each time."); + row_print(5, 1, "Press F1 to start the test."); + + if(test) row_print(7, 1, "Elapsed: %.1j ms (%#08x us)", + elapsed / 100, elapsed); + if(test) row_print(8, 1, "Tests: %d", test); + + fkey_button(1, "START"); + #endif + + dupdate(); + key = getkey().key; + + if(key == KEY_F1) elapsed = run_test(), test++; + } +} diff --git a/src/perf/render.c b/src/perf/render.c new file mode 100644 index 0000000..bb197a0 --- /dev/null +++ b/src/perf/render.c @@ -0,0 +1,97 @@ +#include +#include + +#include +#include +#include + +#include + +struct elapsed { + uint32_t clear; + uint32_t update; + uint32_t rect1, rect2, rect3; +}; + +static void run_test(struct elapsed *time) +{ + #define test(ctx, out, command) { \ + prof_clear(ctx); \ + prof_enter(ctx); \ + command; \ + prof_leave(ctx); \ + out = prof_time(ctx); \ + } + + test(PROFCTX_DUPDATE, time->update, + dupdate() + ); + test(PROFCTX_DCLEAR, time->clear, + dclear(C_WHITE) + ); + test(PROFCTX_DRECT1, time->rect1, + drect(0, 0, 31, 31, C_WHITE) + ); + test(PROFCTX_DRECT2, time->rect2, + drect(1, 1, 32, 32, C_WHITE) + ); + test(PROFCTX_DRECT3, time->rect3, + drect(0, 0, 395, 223, C_WHITE) + ); + + #undef test +} + +/* gintctl_perf_render(): Profile the display primitives */ +void gintctl_perf_render(void) +{ + int key = 0, test = 0; + struct elapsed time = {}; + + while(key != KEY_EXIT) + { + dclear(C_WHITE); + row_title("Rendering primitives"); + + #ifdef FX9860G + #warning gintctl_perf_render not implemented on fx9860g + #endif + + #ifdef FXCG50 + row_print(1, 1, "This program measures the execution time of"); + row_print(2, 1, "common drawing functions."); + + row_print(4, 1, "Press F1 to start the test."); + + if(test) + { + print(6, 90, "dclear:"); + print(6, 105, "%.1j ms", time.clear / 100); + print(6, 120, "%05x us", time.clear); + + print(83, 90, "dupdate:"); + print(83, 105, "%.1j ms", time.update / 100); + print(83, 120, "%05x us", time.update); + + print(160, 90, "rect1:"); + print(160, 105, "%.1j ms", time.rect1 / 100); + print(160, 120, "%05x us", time.rect1); + + print(237, 90, "rect2:"); + print(237, 105, "%.1j ms", time.rect2 / 100); + print(237, 120, "%05x us", time.rect2); + + print(314, 90, "rect3:"); + print(314, 105, "%.1j ms", time.rect3 / 100); + print(314, 120, "%05x us", time.rect3); + } + + fkey_button(1, "START"); + #endif + + dupdate(); + key = getkey().key; + + if(key == KEY_F1) run_test(&time), test = 1; + } +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..c71398f --- /dev/null +++ b/src/util.c @@ -0,0 +1,193 @@ +#define GINT_NEED_VRAM +#include +#include + +#include + +/* Short-shorthand for calling out vsprintf() */ +#define shortprint(str, format) { \ + va_list _args; \ + va_start(_args, format); \ + vsprintf(str, format, _args); \ + va_end(_args); \ +} + +//--- +// Row manipulation functions +//--- + +#ifdef FX9860G +#define ROW_X 1 +#define ROW_W 6 +#define ROW_Y 8 +#define ROW_YPAD 0 +#define ROW_H 8 +#define ROW_COUNT 6 +#endif /* FX9860G */ + +#ifdef FXCG50 +#define ROW_X 6 +#define ROW_W 0 +#define ROW_Y 20 +#define ROW_YPAD 2 +#define ROW_H 14 +#define ROW_COUNT 14 +#endif /* FXCG50 */ + +/* row_title(): Render the main title */ +void row_title(char const *format, ...) +{ + char str[80]; + shortprint(str, format); + + #ifdef FX9860G + dtext(1, 0, str, C_BLACK, C_NONE); + #endif + + #ifdef FXCG50 + dtext(ROW_X, 3, str, C_BLACK, C_NONE); + uint32_t *long_vram = (void *)vram; + for(int i = 0; i < 198 * 16; i++) long_vram[i] = ~long_vram[i]; + #endif +} + +/* row_print(): Formatted printing in a predefined row */ +void row_print(int row, int x, char const *format, ...) +{ + if(row < 1 || row > ROW_COUNT) return; + + char str[80]; + shortprint(str, format); + + dtext(ROW_X + ROW_W * (x - 1), ROW_Y + ROW_H * (row - 1) + ROW_YPAD, + str, C_BLACK, C_NONE); +} + +/* row_highlight(): Invert a row's pixels to highlight it */ +void row_highlight(int row) +{ + int y1 = ROW_Y + ROW_H * (row - 1); + int y2 = y1 + ROW_H; + + #ifdef FX9860G + drect(0, y1, 127, y2 - 1, C_INVERT); + #endif + + #ifdef FXCG50 + uint32_t *long_vram = (void *)vram; + for(int i = 198 * y1; i < 198 * y2; i++) long_vram[i] = ~long_vram[i]; + #endif +} + +/* row_right(): Print at the last column of a row */ +void row_right(int row, char const *character) +{ + #ifdef FX9860G + row_print(row, 21, character); + #endif + + #ifdef FXCG50 + dtext(370, ROW_Y + ROW_H * (row - 1) + ROW_YPAD, character, + C_BLACK, C_NONE); + #endif +} + +/* scrollbar(): Show a scrollbar */ +void scrollbar(int offset, int length) +{ + int area_x = _(127, 391); + int area_width = _(1, 2); + int area_top = ROW_Y; + int area_height = ROW_H * ROW_COUNT; + + int bar_top = (offset * area_height) / length; + int bar_height = (ROW_COUNT * area_height) / length; + + drect(area_x, area_top + bar_top, area_x + area_width - 1, + area_top + bar_top + bar_height, C_BLACK); +} + +/* row_count(): Number of rows available to row_print() */ +int row_count(void) +{ + return ROW_COUNT; +} + +//--- +// General (x,y) printing +//--- + +/* print(): Formatted printing shorthand */ +void print(int x, int y, char const *format, ...) +{ + char str[80]; + shortprint(str, format); + dtext(x, y, str, C_BLACK, C_NONE); +} + + +#ifdef FXCG50 + +/* printw(): Print in white */ +void printw(int x, int y, char const *format, ...) +{ + char str[80]; + va_list args; + va_start(args, format); + vsprintf(str, format, args); + + dtext(x, y, str, C_WHITE, C_NONE); + + va_end(args); +} + +/* fkey_action(): A black-on-white F-key */ +void fkey_action(int position, char const *text) +{ + int width; + dsize(text, NULL, &width, NULL); + + int x = 4 + 65 * (position - 1); + int y = 207; + int w = 63; + + dline(x + 1, y, x + w - 2, y, C_BLACK); + dline(x + 1, y + 14, x + w - 2, y + 14, C_BLACK); + drect(x, y + 1, x + 1, y + 13, C_BLACK); + drect(x + w - 2, y + 1, x + w - 1, y + 13, C_BLACK); + + dtext(x + ((w - width) >> 1), y + 3, text, C_BLACK, C_NONE); +} + +/* fkey_button(): A rectangular F-key */ +void fkey_button(int position, char const *text) +{ + int width; + dsize(text, NULL, &width, NULL); + + int x = 4 + 65 * (position - 1); + int y = 207; + int w = 63; + + dline(x + 1, y, x + w - 2, y, C_BLACK); + dline(x + 1, y + 14, x + w - 2, y + 14, C_BLACK); + drect(x, y + 1, x + w - 1, y + 13, C_BLACK); + + dtext(x + ((w - width) >> 1), y + 3, text, C_WHITE, C_NONE); +} + +/* fkey_menu(): A rectangular F-key with the bottom right corner removed */ +void fkey_menu(int position, char const *text) +{ + int x = 4 + 65 * (position - 1); + int y = 207; + int w = 63; + + fkey_button(position, text); + dline(x + w - 1, y + 10, x + w - 5, y + 14, C_WHITE); + dline(x + w - 1, y + 11, x + w - 4, y + 14, C_WHITE); + dline(x + w - 1, y + 12, x + w - 3, y + 14, C_WHITE); + dline(x + w - 1, y + 13, x + w - 2, y + 14, C_WHITE); +} + +#endif /* FXCG50 */