From c9264a06d5be3c396694ea45f14fcbcad9662a93 Mon Sep 17 00:00:00 2001 From: Lephe Date: Fri, 23 Apr 2021 18:50:20 +0200 Subject: [PATCH] kernel: driver and world system overhaul Changes in the driver and world system: * Rewrite driver logic to include more advanced concepts. The notion of binding a driver to a device is introduced to formalize wait(); power management is now built-in instead of being handled by the drivers (for instance DMA). The new driver model is described in great detail in * Formalized the concept of "world switch" where the hardware state is saved and later restored. As a tool, the world switch turns out to be very stable, and allows a lot of hardware manipulation that would be edgy at best when running in the OS world. * Added a GINT_DRV_SHARED flag for drivers to specify that their state is shared between worlds and not saved/restored. This has a couple of uses. * Exposed a lot more of the internal driver/world system as their is no particular downside to it. This includes stuff in and the driver's state structures in . This is useful for debugging and for cracked concepts, but there is no API stability guarantee. * Added a more flexible driver level system that allows any 2-digit level to be used. Feature changes: * Added a CPU driver that provides the VBR change as its state save. Because the whole context switch relied on interrupts being disabled anyway, there is no longer an inversion of control when setting the VBR; this is just part of the CPU driver's configuration. The CPU driver may also support other features such as XYRAM block transfer in the future. * Moved gint_inthandler() to the INTC driver under the name intc_handler(), pairing up again with intc_priority(). * Added a reentrant atomic lock based on the test-and-set primitive. Interrupts are disabled with IMASK=15 for the duration of atomic operations. * Enabled the DMA driver on SH7305-based fx-9860G. The DMA provides little benefit on this platform because the RAM is generally faster and buffers are ultimately small. The DMA is still not available on SH3-based fx-9860G models. * Solved an extremely obnoxious bug in timer_spin_wait() where the timer is not freed, causing the callback to be called when interrupts are re-enabled. This increments a random value on the stack. As a consequence of the change, removed the long delays in the USB driver since they are not actually needed. Minor changes: * Deprecated some of the elements in . There really is no good way to "enumerate" devices yet. * Deprecated gint_switch() in favor of a new function gint_world_switch() which uses the GINT_CALL abstraction. * Made the fx-9860G VRAM 32-aligned so that it can be used for tests with the DMA. Some features of the driver and world systems have not been implemented yet, but may be in the future: * Some driver flags should be per-world in order to create multiple gint worlds. This would be useful in Yatis' hypervisor. * A GINT_DRV_LAZY flag would be useful for drivers that don't want to be started up automatically during a world switch. This is relevant for drivers that have a slow start/stop sequence. However, this is tricky to do correctly as it requires dynamic start/stop and also tracking which world the current hardware state belongs to. --- CMakeLists.txt | 15 +- TODO | 3 - fx9860g.ld | 12 +- fxcg50.ld | 12 +- include/gint/cpu.h | 92 ++++++++++ include/gint/dma.h | 5 - include/gint/drivers.h | 333 ++++++++++++++++++++++++++-------- include/gint/drivers/states.h | 94 ++++++++++ include/gint/gint.h | 80 +++----- include/gint/hardware.h | 39 +--- include/gint/intc.h | 52 ++++++ src/cpg/cpg.c | 35 ++-- src/cpu/atomic.c | 46 +++++ src/cpu/cpu.c | 67 +++++++ src/cpu/registers.s | 69 +++++++ src/dma/dma.c | 105 +++++------ src/intc/intc.c | 132 ++++++++++---- src/kernel/cpu.h | 69 ------- src/kernel/cpu.s | 99 ---------- src/kernel/drivers.h | 23 --- src/kernel/exch.c | 2 + src/kernel/kernel.c | 259 ++++++-------------------- src/kernel/osmenu.c | 2 +- src/kernel/world.c | 134 ++++++++++++++ src/keysc/keysc.c | 11 +- src/mmu/mmu.c | 44 ++--- src/r61524/r61524.c | 130 +------------ src/render-fx/dupdate.c | 2 +- src/rtc/rtc.c | 57 ++---- src/spu/spu.c | 51 ++---- src/t6k11/t6k11.c | 82 +++------ src/tmu/sleep.c | 5 +- src/tmu/tmu.c | 109 ++++------- src/usb/usb.c | 182 +++++++------------ 34 files changed, 1264 insertions(+), 1188 deletions(-) create mode 100644 include/gint/cpu.h create mode 100644 include/gint/drivers/states.h create mode 100644 src/cpu/atomic.c create mode 100644 src/cpu/cpu.c create mode 100644 src/cpu/registers.s delete mode 100644 src/kernel/cpu.h delete mode 100644 src/kernel/cpu.s delete mode 100644 src/kernel/drivers.h create mode 100644 src/kernel/world.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 51a4aab..26614e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,14 @@ configure_file(include/gint/config.h.in include/gint/config.h) set(SOURCES_COMMON src/cpg/cpg.c + src/cpu/atomic.c + src/cpu/cpu.c + src/cpu/registers.s + src/dma/dma.c + src/dma/inth.s + src/dma/memcpy.c + src/dma/memset.c src/intc/intc.c - src/kernel/cpu.s src/kernel/exch.c src/kernel/exch.s src/kernel/hardware.c @@ -29,6 +35,7 @@ set(SOURCES_COMMON src/kernel/start.c src/kernel/syscalls.S src/kernel/tlbh.S + src/kernel/world.c src/keysc/getkey.c src/keysc/iokbd.c src/keysc/keycodes.c @@ -101,10 +108,6 @@ set(SOURCES_FX src/t6k11/t6k11.c ) set(SOURCES_CG - src/dma/dma.c - src/dma/inth.s - src/dma/memcpy.c - src/dma/memset.c src/r61524/r61524.c src/render-cg/bopti-asm.s src/render-cg/bopti.c @@ -127,7 +130,7 @@ include_directories( "${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/include" "${FXSDK_COMPILER_INSTALL}/include/openlibm") -add_compile_options(-Wall -Wextra -std=c11 -Os -fstrict-volatile-bitfields) +add_compile_options(-Wall -Wextra -std=c11 -Os -fstrict-volatile-bitfields -mtas) # Silence extended warnings on Grisu2b code set_source_files_properties(src/3rdparty/grisu2b_59_56/grisu2b_59_56.c PROPERTIES diff --git a/TODO b/TODO index 70e3fc0..0f4f411 100644 --- a/TODO +++ b/TODO @@ -2,11 +2,9 @@ Extensions on existing code: * bfile: implement the optimization-restart as realized by Kbd2 * kernel: use GINT_CALL() for all callbacks, without breaking the timer API * kernel: better restore to userspace before panic (ensure BL=0 IMASK=0) -* kernel: check if cpu_setVBR() really needs to be perma-mapped * project: add license file * kernel: group linker script symbols in a single header file * bopti: try to display fullscreen images with TLB access + DMA on fxcg50 -* dma: fx9860g support (need to switch it on and update the Makefile) * core: try to leave add-in without reset in case of panic * core: use cmp/str for memchr() * r61524: brightness control and clean the file @@ -22,5 +20,4 @@ Future directions. * USB communication, using Yatis' reverse-engineering of the module * Make fx9860g projects work out of the box on fxcg50 * Use the DSP to enhance parallel computation -* Dynamic memory allocation * Base for Yatis' threads library diff --git a/fx9860g.ld b/fx9860g.ld index 4e65efd..b081ea1 100644 --- a/fx9860g.ld +++ b/fx9860g.ld @@ -97,15 +97,9 @@ SECTIONS The driver information is required to start and configure the driver, even if the symbols are not referenced */ .gint.drivers : { - _bdrv = . ; - KEEP(*(.gint.drivers.0)); - KEEP(*(.gint.drivers.1)); - KEEP(*(.gint.drivers.2)); - KEEP(*(.gint.drivers.3)); - KEEP(*(.gint.drivers.4)); - KEEP(*(.gint.drivers.5)); - KEEP(*(.gint.drivers.6)); - _edrv = . ; + _gint_drivers = . ; + KEEP(*(SORT_BY_NAME(.gint.drivers.*))); + _gint_drivers_end = . ; } > rom /* Read-only data going to ROM: diff --git a/fxcg50.ld b/fxcg50.ld index 459f124..67cabbe 100644 --- a/fxcg50.ld +++ b/fxcg50.ld @@ -74,15 +74,9 @@ SECTIONS The driver information is required to start and configure the driver, even if the symbols are not referenced */ .gint.drivers : { - _bdrv = . ; - KEEP(*(.gint.drivers.0)); - KEEP(*(.gint.drivers.1)); - KEEP(*(.gint.drivers.2)); - KEEP(*(.gint.drivers.3)); - KEEP(*(.gint.drivers.4)); - KEEP(*(.gint.drivers.5)); - KEEP(*(.gint.drivers.6)); - _edrv = . ; + _gint_drivers = . ; + KEEP(*(SORT_BY_NAME(.gint.drivers.*))); + _gint_drivers_end = . ; } > rom /* Read-only data going to ROM: diff --git a/include/gint/cpu.h b/include/gint/cpu.h new file mode 100644 index 0000000..3c16b1c --- /dev/null +++ b/include/gint/cpu.h @@ -0,0 +1,92 @@ +//--- +// gint:cpu - CPU registers and built-in functions +//--- + +#ifndef GINT_CPU +#define GINT_CPU + +#include + +//--- +// Atomic operations +//--- + +/* cpu_atomic_start(): Enter atomic mode + + This function enters "atomic mode", a mode where distractions to the CPU + (mainly interrupts) are disabled. This is useful when doing critical + operations on the hardware, because it ensures that no other code will see + any intermediate state between the start and end of the atomic mode, thereby + making the sequence atomic to other code. + + Atomic mode disables interrupts with IMASK=15, however it does not set BL=1 + because exceptions never occur on their own (and it is desirable to have + panic reports if the atomic code is buggy), and TLB misses are almost always + desirable. If you want to set BL=1, you can do so with cpu_setSR(). + + This function uses a mutex so atomic mode can be started within atomic code; + every cpu_atomic_start() must be paired with exactly one cpu_atomic_end(). + Entering atomic mode several times does not affect the CPU state, however + atomic mode will be exited only after all exits have been completed. + + Once atomic mod is exited the original value of IMASK at the first call to + cpu_atomic_start() is restored. */ +void cpu_atomic_start(void); + +/* cpu_atomic_end(): Exit atomic mode + There should be exactly one cpu_atomic_end() for each cpu_atomic_start(). */ +void cpu_atomic_end(void); + +//--- +// Access to CPU registers +//--- + +/* Read and write the VBR register */ +uint32_t cpu_getVBR(void); +void cpu_setVBR(uint32_t VBR); + +/* Read and write the CPU Operation Mode register. After a write, the register + is re-read and an (icbi) instruction is executed to apply the change. Non- + writable bits should be left to their initial value during a write. */ +uint32_t cpu_getCPUOPM(void); +void cpu_setCPUOPM(uint32_t CPUOPM); + +/* cpu_str_t: Bits of the Status Register */ +typedef lword_union(cpu_sr_t, + uint32_t :1; + uint32_t MD :1; + uint32_t RB :1; + uint32_t BL :1; + uint32_t RC :12; + uint32_t :3; + uint32_t DSP :1; + uint32_t DMY :1; + uint32_t DMX :1; + uint32_t M :1; + uint32_t Q :1; + uint32_t IMASK :4; + uint32_t RF :2; + uint32_t S :1; + uint32_t T :1; +); + +/* Read and write the SR register. When writing, only "permanent" bits are set: + * MD, RB, BL, DSP, IMASK are set. + * M, Q, S and T are not set to preserve the behavior of ongoing divisions + and tests. You can change T with (sett) and (clrt). + * RC, DMY, DMX and DF are not set: use (setrc), (setdmx), (setdmy), and + (clrdmxy). DF is preserved for old-style (setrc) loops to work. */ +cpu_sr_t cpu_getSR(void); +void cpu_setSR(cpu_sr_t sr); + +//--- +// Configuration +//--- + +/* cpu_configure_vbr(): Select the VBR address to be loaded in the driver + This function can be used before the driver is configure()'d. It sets the + VBR address that will be used in the next world to be initialized. After a + configure(), this is reset to 0. */ +void cpu_configure_VBR(uint32_t VBR); + +#endif /* GINT_CPU */ diff --git a/include/gint/dma.h b/include/gint/dma.h index 9ea2226..6dc89ca 100644 --- a/include/gint/dma.h +++ b/include/gint/dma.h @@ -5,9 +5,6 @@ #ifndef GINT_DMA #define GINT_DMA -/* TODO: Enable DMA on fx-9860G */ -#ifdef FXCG50 - #include /* dma_size_t - Transfer block size */ @@ -109,6 +106,4 @@ void *dma_memset(void *dst, uint32_t pattern, size_t size); @size Size of region (32-aligned) */ void *dma_memcpy(void * restrict dst, const void * restrict src, size_t size); -#endif /* FXCG50 */ - #endif /* GINT_DMA */ diff --git a/include/gint/drivers.h b/include/gint/drivers.h index 6a2d599..f21b672 100644 --- a/include/gint/drivers.h +++ b/include/gint/drivers.h @@ -8,87 +8,245 @@ #include #include -/* Driver procedure flow +/* Device drivers and driver cycles - Drivers are initialized in priority order, and in linking order within the - the same priority level (which is pretty much undefined). Every driver's - priority level must be higher than those of its dependencies; the numbers - are fixed, see the documentation for level assignments. + A driver is any part of the program that manages some piece of hardware. + Because gint coexists with the default operating system, special care has to + be taken in manipulating the hardware to avoid compatibility problems, and + this is implemented by the drivers. - At initialization, drivers are first called to wait for the hardware to - become available before initialization. Then the system state is saved. We - still support SH3-based SH7705-like MPUs, so a function init_sh3() is called - for every driver that need to make adjustments to support them. Finally, the - driver is initialized. The calls are as follow; every function pointer can - be NULL in which case it is ignored. + [Driver state vocabulary] - 1. wait() - 2. ctx_save(sys_ctx) - 3. driver_sh3() [SH3-based fx9860g] - 4. init() + There are several states of interest for a driver and its device: + * The device is said to be *powered* if the clock is supplied to the module + and the device can operate normally. + * The driver is said to be *bound* to the device if is has exclusive access + to the hardware and can operate on it. + * The device is said to be *configured* if it is powered and the driver has + initialized the hardware to start running its API. + * The driver is said to be *active* if (1) it is bound, or (2) it is planned + to be bound the next time gint takes over. A driver can be inactive if it + delays its initialization until it is needed by the add-in. This is + relevant for drivers which have an expensive start/stop sequence. + * The device is said to be *shared* if it doesn't require its hardware state + to be preserved when switching between gint and the OS. Usually this is + specified by the add-in when the add-in knows that the driver will not be + used over a certain period of time. - During the execution, gint_switch() can be called to temporarily give back - control to the OS. In this case, the state of each driver is saved to a - context from gint, then restored from there afterwards. + For consistency in gint (related to world switches), only devices that are + powered can be bound. It would be possible to have drivers power devices on + and off while bound, but it complicates the design; therefore, if a device + is shut down by the user, the driver will be unbound first. - 5. wait() - 6. ctx_save(gint_ctx) - 7. ctx_restore(sys_ctx) - (stuff happening outside of gint) - 8. wait() - 9. ctx_save(sys_ctx) - 10. ctx_restore(gint_ctx) + [Hardware sharing and driver state machine] - When finally the driver is unloaded, the system context is restored. + gint's drivers are special because (1) gint is not the only kernel running + on the machine, and (2) the other kernel doesn't know about it. To ensure + stability driving the hardware ourselves, we must guarantee that OS code is + oblivious to hardware changes, so we save and restore hardware state + whenever gint takes control over or gives it back. This makes gint sort of + a hypervisor while also being one of its guests. (Yatis once materialized + this idea and used gint's world switch mechanic to build a true hypervisor.) - 11. wait() - 12. ctx_restore(sys_ctx) + If the built-in OS were aware of the hardware sharing, it would implement + mechanisms to unbind its drivers from the hardware in order to allow gint to + take over, and we would do so in return. However this process is not + implemented in the OS, which means that we need to cover it ourselves. gint + calls this operation a "foreign unbind" and it is mostly used in modules + with asynchronous APIs where operations must finish before gint can take + control. - The wait() function is called both when gint has control and when the OS has - control; thus, it must not rely on any internal state other than the - hardware itself. + This means that drivers in gint have to handle three types of tasks: + * "Foreign" tasks: unbinding the OS driver from the device. + * "Hypervisor" tasks: handling the transition between OS and gint. + * "Normal" tasks: driving the device for the gint add-in. - The ctx_save() and ctx_restore() function are called with interrupts - disabled (IMASK=15) so you should not rely on interrupts. However, TLB - misses are still enabled so you can rely on TLB updates. */ + Foreign tasks comprise everything that happens while the OS driver is bound, + normal tasks is everything that happens while the gint driver is bound, and + the "hypervisor" tasks are everything in-between. Driver functions for + foreign tasks start with an "f" and functions for "hypervisor" tasks start + with an "h". -/* gint_driver_t: Metadata and interface of kernel drivers */ -typedef struct -{ + The state machine for driver binding and device power is as follows. Power + management in gint occurs in the middle level; a foreign unbind does not + change the device power, and the device power from the last foreign unbind + is restored before running OS code. + + Device is owned and operated by the OS + (power is either ON or OFF) + | ^ + funbind() | | Running any OS code + v | + Device is not bound + Power <-- hpoweron() ---- Power + is ON --- hpoweroff() --> is OFF + | ^ + bind() | | unbind() + v | + Device is powered and operated by gint + + For safety, the device is considered bound to the OS whenever OS code is + run, even when it is not powered. The reason why gint unbinds its drivers + before unpowering a device is to make sure that power management is kept in + the "hypervisor" section of the code. The unbind() and funbind() functions + should ensure that the device is idle with no interrupts pending, which + allows proper shutdown and a clean hardware save state. + + [World switch] + + When handing back hardware control to the OS, gint restores devices to their + state at the last control takeover (mostly). The drivers provide two + "hypervisor" calls for this feature, hsave() and hrestore(). The combined + saved states of all devices are called a *world*. The action of switching + hardware states to isolate the execution of the two kernels is called a + *world switch*. + + gint exposes a handful of OS features via world switches, such as + return-to-main-menu (commonly used in getkey()) and BFile access to the + filesystem. A stable world switch mitigates a lot of the drawbacks of using + a custom kernel, up to (1) the inability to run gint code and OS code + simultaneously (ie. timers during a BFile operation), and (2) the small + runtime cost of a world switch. Note that (1) is more of a policy question, + as it is always possible to access hardware while the OS runs (which mostly + works but offers limited stability, whether gint is used or not). + + The world switch mechanism can be customized to a certain extent, allowing + to not restore drivers during world transitions (such drivers are called + "shared"). This is important in Yatis' tracer/debugger, which uses a + gint-driven User Break Controller while in the OS world to control and + monitor the execution of syscalls, performing world transitions to gint when + breakpoints are hit to display and analyze code without affecting it. This + can also be used to keep some profiling timers alive during OS operations. + + A switch from the OS world to the gint world will start like this; + 1. funbind() (with OS-handled interrupts still enabled) + 2. SR.IMASK=15 + Then, for most drivers: + a3. hpoweron() if the device was not powered in the OS + a4. hsave() + a5. hrestore() if not running for the first time + a6. bind() + a7. configure() if running for the first time + a8. SR.IMASK=0 + There is an exception if the driver is shared: + b3. hpoweron() if the device was not powered in the OS + b4. bind() + b5. configure() if running for the first time + b6. SR.IMASK=0 + + A switch from the gint world to the OS world will execute this sequence: + a1. unbind() (with gint-handled interrupts still enabled) + a2. SR.IMASK=15 + a3. hsave() + a4. hrestore() + a5. hpoweroff() if the device was powered off at the last funbind() + a6. SR.IMASK=0 + There is again an exception if the device is shared: + b1. unbind() (with gint-handled interrupts still enabled) + b2. SR.IMASK=15 + b3. hpoweroff() if the device was powered off at the last funbind() + b4. SR.IMASK=0 + + [Driver settings] + + Each driver has a *level* which indicates its relationship with other + drivers. Specifically, a driver is only allowed to use functions from + drivers of a lower level. gint makes sure that functions of the "hypervisor" + and normal category are executed while all drivers of lower levels are + bound (unless the user explicitly disables them). + + The driver can also initialize the following flags in the driver definition + and customize them at runtime: + + * GINT_DRV_SHARED: Makes the driver shared, meaning that its hardware state + is not saved and restored during world switches. Note that the CPU and + INTC drivers are not shared so interrupts will not be available in a + foreign world. */ +typedef struct { /* Driver name */ char const *name; + /* General constructor, is called before any interaction with the driver. + This can be used to adjust settings based on detected hardware. */ + void (*constructor)(void); - /* SH3-specific initialization step. May be NULL. */ - void (*driver_sh3)(void); - /* Should initialize the hardware so that the driver can start working. - Usually installs interrupt handlers and configures interrupts. Only - called once when the add-in starts. May be NULL. */ - void (*init)(void); + // Foreign calls - /* Should wait for the hardware to become available. Called both under - gint control and OS control every time control is passed around. It - is used for instance to wait for DMA transfers. May be NULL. */ - void (*wait)(void); + /* Foreign unbind: separate the hardware from the OS driver. If NULL, the + OS driver is always considered idle. */ + void (*funbind)(void); - /* System's context and gint's context. These should point to enough - memory to store a full driver state each. Used when switching from - the system to gint and back to the main menu. If they don't need to - be initialized, put them in gint's uninitialized BSS section using - the GBSS macro of . May be NULL only if both - ctx_save() and ctx_restore() are NULL. */ - void *sys_ctx; - void *gint_ctx; + // "Hypervisor" calls - /* Must save the state of as much driver-controlled hardware as - possible (memory-mapped MPU registers, port state, etc). This - function is called to save the system's hardware state and gint's - hardware state when moving from one into the other. The parameter - is always either sys_ctx or gint_ctx. */ - void (*ctx_save)(void *ctx); - /* Must restore the state of the driver as saved by ctx_save(). */ - void (*ctx_restore)(void *ctx); + /* Determine whether the device is powered. If NULL, the device is assumed + to be permanently powered. */ + bool (*hpowered)(void); + /* Power on the device; this should allow register access to save the + peripheral state, with minimal state changes. Cannot be NULL if + hpowered() can return false. */ + void (*hpoweron)(void); + /* Power off the device; cannot be NULL if hpowered() can return false. */ + void (*hpoweroff)(void); -} GPACKED(4) gint_driver_t; + /* Save the hardware state; the (state) pointer points to a 4-aligned + region of (state_size) bytes. */ + void (*hsave)(void *state); + /* Restore a hardware state previously saved by hsave(). */ + void (*hrestore)(void const *state); + + // Standard calls + + /* Bind the driver to acquire control of the device. May be NULL. */ + void (*bind)(void); + /* Unbind the driver from the hardware. Usually sleeps until processes that + block world switches terminate, like bind(). May be NULL. */ + void (*unbind)(void); + + /* Initialize the hardware for the driver to work in gint. Usually installs + interrupt handlers and configures registers. May be NULL. */ + void (*configure)(void); + + /* Size of the peripheral's hardware state (assumed 4-aligned) */ + uint16_t state_size; + + /* Initial flags */ + uint8_t flags; + +} gint_driver_t; + +enum { + /* Driver is clean (needs to be configured before running) */ + GINT_DRV_CLEAN = 0x01, + /* Device was powered during the last foreign unbind */ + GINT_DRV_FOREIGN_POWERED = 0x02, + + /* Driver does not require hardware state saves during world switches */ + GINT_DRV_SHARED = 0x10, + + /* Flags that can be set in the (flags) attribute of the driver struct */ + GINT_DRV_INIT_ = 0x10, +}; + +/* gint_world_t: World state capture + + The world state is a copy of the (almost) complete hardware state, which can + be used to switch between several kernels running in parallel on the same + machine. gint runs in a different world than the OS, allowing it to control + peripheral modules in ways incompatible with the OS without compromising the + stability of either program. + + The world state is a sequence of 4-aligned buffers each holding a copy of a + module's state, as saved (and restored) by a driver. It is prefixed with an + array of pointers, one for each driver, specifying the driver's spot within + the sequence. + + The layout is as follows: + * An array of (void *), with one entry per driver, in priority order. Each + pointer is to a buffer in the sequence. + * A sequence of buffers of size (state_size), rounded up to a multiple of 4 + bytes, for each driver in priority order. + + The world is returned as a (void *) array but allocated in one block. */ +typedef void **gint_world_t; /* GINT_DECLARE_DRIVER(): Declare a driver to the kernel @@ -99,18 +257,47 @@ typedef struct The level argument represents the priority level: lower numbers mean that drivers will be loaded sooner. This numbering allows a primitive form of dependency for drivers. You need to specify a level which is strictly - higher than the level of all the drivers you depend on. */ + higher than the level of all the drivers you depend on. + + The level number *MUST HAVE EXACTLY 2 DIGITS*, as it is used as a string in + the section name and the linker then sorts by name. If your driver has a + level lower than 10, you must add a leading 0. */ #define GINT_DECLARE_DRIVER(level, name) \ GSECTION(".gint.drivers." #level) extern gint_driver_t name; -/* GINT_DRIVER_SH3(): Declare an init_sh3() function - This macro is NULL on fxcg50, so that the named function can be defined - under #ifdef FX9860G while keeping the structure clean. */ +//--- +// Internal driver control +// +// The following data is exposed for introspection and debugging purposes; it +// is not part of the gint API. There is *no stability guarantee* that the +// following types and functions will remain unchanged in future minor and +// patch versions. +//--- -#ifdef FXCG50 -#define GINT_DRIVER_SH3(name) NULL -#else -#define GINT_DRIVER_SH3(name) name -#endif +/* Drivers in order of increasing priority level, provided by linker script */ +extern gint_driver_t gint_drivers[]; +/* End of array; see also gint_driver_count() */ +extern gint_driver_t gint_drivers_end[]; +/* Current flags for all drivers */ +extern uint8_t *gint_driver_flags; + +/* Number of drivers in the (gint_drivers) array */ +#define gint_driver_count() \ + ((gint_driver_t *)&gint_drivers_end - (gint_driver_t *)&gint_drivers) + +/* Allocate a new world buffer (single block), returns NULL on error */ +gint_world_t gint_world_alloc(void); + +/* Free a world buffer */ +void gint_world_free(gint_world_t world); + +/* The world buffers of gint and the OS */ +extern gint_world_t gint_world_addin, gint_world_os; + +/* Switch from the OS world to a gint-managed world */ +void gint_world_switch_in(gint_world_t world_os, gint_world_t world_addin); + +/* Switch from a gint-managed world to the OS world */ +void gint_world_switch_out(gint_world_t world_addin, gint_world_t world_os); #endif /* GINT_DRIVERS */ diff --git a/include/gint/drivers/states.h b/include/gint/drivers/states.h new file mode 100644 index 0000000..4f72d90 --- /dev/null +++ b/include/gint/drivers/states.h @@ -0,0 +1,94 @@ +//--- +// gint:drivers:states - State structures for drivers +// +// The state structures in this header are exposed for introspection and driver +// debugging purposes. This is not part of the gint API, and there is *no +// stability guarantee* across minor and patch versions of gint. +//--- + +#ifndef GINT_DRIVERS_STATES +#define GINT_DRIVERS_STATES + +#include + +/* Clock Pulse Generator (see cpg/cpg.c) */ +typedef struct { + uint32_t SSCGCR; +} cpg_state_t; + +/* CPU (see cpu/cpu.c) */ +typedef struct { + uint32_t SR; + uint32_t VBR; + uint32_t CPUOPM; +} cpu_state_t; + +/* Direct Memory Access controller (see dma/dma.c) */ +typedef struct { + sh7305_dma_channel_t ch[6]; + uint16_t OR; +} dma_state_t; + +/* Interrupt Controller (see intc/intc.c) */ +typedef struct { + uint16_t IPR[12]; + uint8_t MSK[13]; +} intc_state_t; + +/* Memory Manager Unit (see mmu/mmu.c) */ +typedef struct { + uint32_t PASCR; + uint32_t IRMCR; +} mmu_state_t; + +/* R61524 display (see r61524/r61524.c) */ +typedef struct { + /* Graphics RAM range */ + uint16_t HSA, HEA, VSA, VEA; +} r61524_state_t; + +/* Real-time Clock (see rtc/rtc.c) */ +typedef struct { + uint8_t RCR1, RCR2; +} rtc_state_t; + +/* Sound Processing Unit (see spu/spu.c) */ +typedef struct { + uint32_t PBANKC0, PBANKC1; + uint32_t XBANKC0, XBANKC1; +} spu_state_t; + +/* T6K11 display (see t6k11/t6k11.c) */ +typedef struct { + /* Some status bits, obtained by using the STRD command. There are other + parameters that cannot be read */ + uint8_t STRD; +} t6k11_state_t; + +/* Timer Unit (see tmu/tmu.c) */ +typedef struct { + /* Individual timers; TSTR is used for ETMU */ + struct tmu_state_stored_timer { + uint32_t TCOR; + uint32_t TCNT; + uint16_t TCR; + uint16_t TSTR; + } t[9]; + /* TSTR value for TMU */ + uint8_t TSTR; +} tmu_state_t; + +/* USB 2.0 function module (see usb/usb.c) */ +typedef struct { + /* Control and power-up. We don't save power-related registers from other + modules nor UPONCR, because they must be changed to use the module */ + uint16_t SYSCFG, DVSTCTR, TESTMODE, REG_C2; + /* FIFO configuration */ + uint16_t CFIFOSEL, D0FIFOSEL, D1FIFOSEL; + /* Interrupt configuration */ + uint16_t INTENB0, BRDYENB, NRDYENB, BEMPENB, SOFCFG; + /* Default Control Pipe (maybe not needed) */ + uint16_t DCPCFG, DCPMAXP, DCPCTR; +} usb_state_t; + +#endif /* GINT_DRIVERS_STATES */ diff --git a/include/gint/gint.h b/include/gint/gint.h index fd8e418..cd99c03 100644 --- a/include/gint/gint.h +++ b/include/gint/gint.h @@ -8,21 +8,33 @@ #include #include #include +#include -/* gint_switch(): Switch out of gint to execute a function +/* gint_world_switch(): Switch out of gint to execute a function - This function can be used to leave gint, restore the system's driver - context, and execute code there before returning to gint. By doing this one - can effectively interleave gint with the standard OS execution. The - limitations are quite extreme though, so unless you know precisely what - you're doing that requires getting out of gint (eg. BFile), be careful. + This function can be used to leave gint, restore the OS's hardware state, + and execute code there before returning to gint. By doing this one can + effectively interleave gint with the standard OS execution. gint drivers + will be inactive during this time but OS features such as BFile or the + main menu are available. This main uses for this switch are going back to the main menu and using BFile function. You can go back to the main menu easily by calling getkey() (or getkey_opt() with the GETKEY_MENU flag set) and pressing the MENU key, or by calling gint_osmenu() below which uses this switch. - @function Function to call in OS mode */ + The code to execute while in OS mode is passed as a gint call; you can use + GINT_CALL() to create one. This allows you to pass arguments to your + function, as well as return an int. + + @function A GINT_CALL() to execute while in OS mode + -> Returns the return value of (function), if any, 0 if function is NULL. */ +int gint_world_switch(gint_call_t function); + +/* This function is an older version of gint_world_switch() which only accepts + functions with no arguments and no return value. It will be removed in + gint 3. */ +__attribute__((deprecated("Use gint_world_switch() instead"))) void gint_switch(void (*function)(void)); /* gint_osmenu(): Call the calculator's main menu @@ -48,55 +60,11 @@ void gint_osmenu(void); @restart 0 to exit, 1 to restart by using gint_osmenu() */ void gint_setrestart(int restart); -/* gint_inthandler(): Install interrupt handlers - - This function installs (copies) interrupt handlers in the VBR space of the - application. Each handler is a 32-byte block aligned on a 32-byte boundary. - When an interrupt request is accepted, the hardware jumps to a specific - interrupt handler at an address that depends on the interrupt source. - - For safety, interrupt handlers should avoid referring to data from other - blocks because the arrangement of blocks at runtime depends on event codes. - The assembler program will assume that consecutive blocks in the source code - will be consecutive in memory, which is not always true. Avoiding cross- - references is a practical rule to avoid problems. (gint breaks this rule - quite often but does it safely.) - - This function allows anyone to replace any interrupt handler so make sure - you're not interfering with interrupt assignments from gint or a library. - - The first parameter event_code represents the event code associated with the - interrupt. If it's not a multiple of 0x20 then you're doing something wrong. - The codes are normally platform-dependent, but gint always uses SH7305 - codes. SH3 platforms have a different, compact VBR layout. gint_inthandler() - translates the provided SH7305 codes to the compact layout and the interrupt - handler translates the hardware SH3 codes to the compact layout as well. See - gint's source in and . Please note that - gint_inthandler() uses a table that must be modified for every new SH3 - interrupt code to extend the compact scheme. - - The handler function is run in the kernel register bank with interrupts - disabled and must end with 'rts' (not 'rte') as the main interrupt handler - saves some registers for you. By default, user bank registers are not saved - except for gbr/mach/macl; if you want to call back to arbitrary code safely, - use gint_inth_callback() in your handler. - - For convenience gint allows any block size to be loaded as an interrupt - handler, but it should really be a multiple of 0x20 bytes and not override - other handlers. If it's not written in assembler, then you're likely doing - something wrong. Using __attribute__((interrupt_handler)), which uses rte, - is especially wrong. - - It is common for interrupt handlers to have a few bytes of data, such as the - address of a callback function. gint often stores this data in the last - bytes of the block. This function returns the VBR address of the block which - has just been installed, to allow the caller to edit the parameters later. - - @event_code Identifier of the interrupt block - @handler Address of handler function - @size How many bytes to copy - Returns the VBR address where the handler was installed. */ -void *gint_inthandler(int event_code, void const *handler, size_t size); +/* This function has been moved to the INTC driver */ +__attribute__((deprecated("Use intc_handler() instead"))) +static GINLINE void *gint_inthandler(int code, void const *h, size_t size) { + return intc_handler(code, h, size); +} /* gint_inth_callback(): Call back arbitrary code from an interrupt handler diff --git a/include/gint/hardware.h b/include/gint/hardware.h index 6ea39dd..0f5258b 100644 --- a/include/gint/hardware.h +++ b/include/gint/hardware.h @@ -62,10 +62,10 @@ void hw_detect(void); #define HWRAM 4 /* Amount of RAM */ #define HWROM 5 /* Amount of ROM */ #define HWURAM 6 /* Userspace RAM */ -#define HWETMU 7 /* Extra Timer Units */ +#define HWETMU /* Deprecated: use timer_count() (ETMU always load) */ #define HWKBD 8 /* Keyboard */ #define HWKBDSF /* Deprecated: use keysc_scan_frequency() */ -#define HWDD 10 /* Display Driver */ +#define HWDD /* Deprecated: use the T6K11/R61524 API */ /* ** MPU type @@ -101,24 +101,6 @@ void hw_detect(void); /* fx-CG 50 emulator, hardcoded in kernel/inth.S */ #define HWCALC_FXCG_MANAGER 6 -/* -** Extra Timer Units -*/ - -/* A single-timer ETMU unit was found. Correlated with SH3 */ -#define HWETMU_1 0x01 -/* A 6-timer ETMU unit was found. Correlated with SH4 */ -#define HWETMU_6 0x02 -/* Individual timer status. Not all timers might be operational after setting - up the driver due to seemingly limitless behavioral differences with the - TMU. Operational here means TCNT=TCOR=-1, interrupt disabled and cleared. */ -#define HWETMU_OK0 0x04 -#define HWETMU_OK1 0x08 -#define HWETMU_OK2 0x10 -#define HWETMU_OK3 0x20 -#define HWETMU_OK4 0x40 -#define HWETMU_OK5 0x80 - /* ** Keyboard */ @@ -132,21 +114,4 @@ void hw_detect(void); /* The keyboard uses a KEYSC-based scan method. This is only possible on SH4 */ #define HWKBD_KSI 0x04 -/* -** Display Driver -*/ - -/* Display driver is known. This cannot be determined on fx9860g as the Toshiba - T6K11 and its Graph 35+E II variant don't seem to have an identification - command. It is set to 0 on fx9860g and used on fxcg50. */ -#define HWDD_KNOWN 0x01 -/* The display driver was configured to use the full screen, instead of leaving - bands on the side. [fxcg50] */ -#define HWDD_FULL 0x02 -/* The contrast address for this OS version is known. [fx9860g] */ -#define HWDD_CONTRAST 0x04 -/* Backlight management is supported. This is used both on fx9860g models with - back-lit screen (although that very fact cannot be detected) and fxcg50. */ -#define HWDD_LIGHT 0x08 - #endif /* GINT_HARDWARE */ diff --git a/include/gint/intc.h b/include/gint/intc.h index 959e13d..34d7019 100644 --- a/include/gint/intc.h +++ b/include/gint/intc.h @@ -5,6 +5,8 @@ #ifndef GINT_INTC #define GINT_INTC +#include + //--- // Interrupt names //--- @@ -65,4 +67,54 @@ enum { Returns the interrupt level that was assigned before the call. */ int intc_priority(int intname, int level); +/* intc_handler(): Install interrupt handlers + + This function installs (copies) interrupt handlers in the VBR space of the + application. Each handler is a 32-byte block aligned on a 32-byte boundary. + When an interrupt request is accepted, the hardware jumps to a specific + interrupt handler at an address that depends on the interrupt source. + + For safety, interrupt handlers should avoid referring to data from other + blocks because the arrangement of blocks at runtime depends on event codes. + The assembler program will assume that consecutive blocks in the source code + will be consecutive in memory, which is not always true. Avoiding cross- + references is a practical rule to avoid problems. (gint breaks this rule + quite often but does it safely.) + + This function allows anyone to replace any interrupt handler so make sure + you're not interfering with interrupt assignments from gint or a library. + + The first parameter event_code represents the event code associated with the + interrupt. If it's not a multiple of 0x20 then you're doing something wrong. + The codes are normally platform-dependent, but gint always uses SH7305 + codes. SH3 platforms have a different, compact VBR layout. gint_inthandler() + translates the provided SH7305 codes to the compact layout and the interrupt + handler translates the hardware SH3 codes to the compact layout as well. See + gint's source in and . Please note that + intc_handler() uses a table that must be modified for every new SH3 + interrupt code to extend the compact scheme. + + The handler function is run in the kernel register bank with interrupts + disabled and must end with 'rts' (not 'rte') as the main interrupt handler + saves some registers for you. By default, user bank registers are not saved + except for gbr/mach/macl; if you want to call back to arbitrary code safely, + use gint_inth_callback() in your handler. + + For convenience gint allows any block size to be loaded as an interrupt + handler, but it should really be a multiple of 0x20 bytes and not override + other handlers. If it's not written in assembler, then you're likely doing + something wrong. Using __attribute__((interrupt_handler)), which uses rte, + is especially wrong. + + It is common for interrupt handlers to have a few bytes of data, such as the + address of a callback function. gint often stores this data in the last + bytes of the block. This function returns the VBR address of the block which + has just been installed, to allow the caller to edit the parameters later. + + @event_code Identifier of the interrupt block + @handler Address of handler function + @size How many bytes to copy + Returns the VBR address where the handler was installed. */ +void *intc_handler(int event_code, void const *handler, size_t size); + #endif /* GINT_INTC */ diff --git a/src/cpg/cpg.c b/src/cpg/cpg.c index e942182..21a30ac 100644 --- a/src/cpg/cpg.c +++ b/src/cpg/cpg.c @@ -3,6 +3,7 @@ //--- #include +#include #include #include @@ -115,10 +116,10 @@ static void sh7305_probe(void) #undef CPG //--- -// Initialization, contexts and driver metadata +// Initialization //--- -static void init(void) +static void configure(void) { /* Disable spread spectrum in SSGSCR */ if(isSH4()) @@ -134,31 +135,25 @@ static void init(void) sh7305_probe(); } -typedef struct { - uint32_t SSCGCR; -} ctx_t; +//--- +// State and driver metadata +//--- -static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *ctx) +static void hsave(cpg_state_t *s) { - ctx_t *c = ctx; - if(isSH4()) c->SSCGCR = SH7305_CPG.SSCGCR.lword; + if(isSH4()) s->SSCGCR = SH7305_CPG.SSCGCR.lword; } -static void ctx_restore(void *ctx) +static void hrestore(cpg_state_t const *s) { - ctx_t *c = ctx; - if(isSH4()) SH7305_CPG.SSCGCR.lword = c->SSCGCR; + if(isSH4()) SH7305_CPG.SSCGCR.lword = s->SSCGCR; } gint_driver_t drv_cpg = { .name = "CPG", - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(cpg_state_t), }; - -GINT_DECLARE_DRIVER(1, drv_cpg); +GINT_DECLARE_DRIVER(05, drv_cpg); diff --git a/src/cpu/atomic.c b/src/cpu/atomic.c new file mode 100644 index 0000000..9ae24e2 --- /dev/null +++ b/src/cpu/atomic.c @@ -0,0 +1,46 @@ +//--- +// gint:cpu:atomic - Simulated atomic operations +//--- + +#include + +/* Value of IMASK when atomic mode is entered */ +static int saved_IMASK = 0; +/* Number of atomic mode levels (sort of mutex) */ +static unsigned int atomic_level = 0; +/* Lock on (atomic_level) */ +static char atomic_level_lock = 0; + +void cpu_atomic_start(void) +{ + /* Get the lock on (atomic_level) */ + while(__atomic_test_and_set(&atomic_level_lock, __ATOMIC_RELAXED)) {} + + if(atomic_level == 0) { + cpu_sr_t SR = cpu_getSR(); + saved_IMASK = SR.IMASK; + SR.IMASK = 15; + cpu_setSR(SR); + } + + atomic_level++; + + /* Release the lock */ + __atomic_clear(&atomic_level_lock, __ATOMIC_RELAXED); +} + +void cpu_atomic_end(void) +{ + while(__atomic_test_and_set(&atomic_level_lock, __ATOMIC_RELAXED)) {} + + atomic_level--; + + if(atomic_level == 0) { + cpu_sr_t SR = cpu_getSR(); + SR.IMASK = saved_IMASK; + saved_IMASK = 0; + cpu_setSR(SR); + } + + __atomic_clear(&atomic_level_lock, __ATOMIC_RELAXED); +} diff --git a/src/cpu/cpu.c b/src/cpu/cpu.c new file mode 100644 index 0000000..1a196a4 --- /dev/null +++ b/src/cpu/cpu.c @@ -0,0 +1,67 @@ +//--- +// gint:cpu - Driver for CPU built-in features +//--- + +#include +#include +#include +#include + +/* VBR address to be used in the next world's configure() */ +static uint32_t configure_VBR = 0; + +void cpu_configure_VBR(uint32_t VBR) +{ + configure_VBR = VBR; +} + +static void configure(void) +{ + cpu_setVBR(configure_VBR); + configure_VBR = 0; + + if(isSH4()) { + /* Set CPUOPM.INTMU. On the fx-CG 50 emulator it is available but + ignored by the emulator, so additional checks still need to be done + in interrupt handlers. */ + cpu_setCPUOPM(cpu_getCPUOPM() | 0x00000008); + + /* Enable DSP instructions */ + cpu_sr_t SR = cpu_getSR(); + SR.DSP = 1; + cpu_setSR(SR); + } +} + +//--- +// Device state and driver metadata +//--- + +static void hsave(cpu_state_t *s) +{ + s->VBR = cpu_getVBR(); + + if(isSH4()) { + s->CPUOPM = cpu_getCPUOPM(); + s->SR = cpu_getSR().lword; + } +} + +static void hrestore(cpu_state_t const *s) +{ + cpu_setVBR(s->VBR); + + if(isSH4()) { + cpu_setCPUOPM(s->CPUOPM); + cpu_setSR((cpu_sr_t)s->SR); + } +} + +gint_driver_t drv_cpu = { + .name = "CPU", + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(cpu_state_t), +}; +GINT_DECLARE_DRIVER(00, drv_cpu); diff --git a/src/cpu/registers.s b/src/cpu/registers.s new file mode 100644 index 0000000..3329070 --- /dev/null +++ b/src/cpu/registers.s @@ -0,0 +1,69 @@ +/* +** gint:cpu:registers - Access to primary registers used in the CPU +*/ + +.global _cpu_getVBR +.global _cpu_setVBR +.global _cpu_setCPUOPM +.global _cpu_getCPUOPM +.global _cpu_getSR +.global _cpu_setSR +.text + +/* cpu_setVBR(): Change VBR address */ +_cpu_setVBR: + ldc r4, vbr + rts + nop + +_cpu_getVBR: + stc vbr, r0 + rts + nop + +_cpu_setCPUOPM: + /* Set CPUOPM as requested */ + mov.l 1f, r0 + mov.l r4, @r0 + + /* Read CPUOPM again */ + mov.l @r0, r5 + + /* Invalidate a cache address */ + mov #-96, r0 + shll16 r0 + shll8 r0 + icbi @r0 + + rts + nop + +_cpu_getCPUOPM: + mov.l 1f, r0 + rts + mov.l @r0, r0 + +.align 4 +1: .long 0xff2f0000 + +_cpu_getSR: + stc sr, r0 + rts + nop + +_cpu_setSR: + /* Set only MD, RB, BL, DSP and IMASK */ + mov.l 1f, r0 + not r0, r1 + stc sr, r2 + + and r1, r2 + and r0, r4 + or r4, r2 + + ldc r2, sr + rts + nop + +.align 4 +1: .long 0x700010f0 diff --git a/src/dma/dma.c b/src/dma/dma.c index 45db054..5b72133 100644 --- a/src/dma/dma.c +++ b/src/dma/dma.c @@ -2,15 +2,14 @@ #include #include #include -#include #include #include #include +#include #include #include #define DMA SH7305_DMA -#define INTC SH7305_INTC #define POWER SH7305_POWER typedef volatile sh7305_dma_channel_t channel_t; @@ -174,9 +173,8 @@ void dma_transfer_noint(int channel, dma_size_t size, uint blocks, // Initialization //--- -static void init(void) +static void configure(void) { - /* This driver is not implemented on SH3 */ if(isSH3()) return; /* Install the interrupt handler from dma/inth.s */ @@ -186,7 +184,7 @@ static void init(void) for(int i = 0; i < 6; i++) { /* Install interrupt handler */ - void *h = gint_inthandler(codes[i], inth_dma_te, 32); + void *h = intc_handler(codes[i], inth_dma_te, 32); channel_t *ch = dma_channel(i); /* Set its CHCR address */ @@ -197,7 +195,7 @@ static void init(void) /* Install the address error gate */ extern void inth_dma_ae(void); - gint_inthandler(0xbc0, inth_dma_ae, 32); + intc_handler(0xbc0, inth_dma_ae, 32); /* Set interrupt priority to 3 (IPRE[15..12] for first three channels, IPRF[11..8] for last two and error gate */ @@ -215,55 +213,52 @@ static void init(void) DMA.OR.DME = 1; } -static void wait(void) +static void universal_unbind(void) { /* Make sure any DMA transfer is finished before leaving the app */ dma_transfer_wait(-1); } +static bool hpowered(void) +{ + if(isSH3()) return false; + return (POWER.MSTPCR0.DMAC0 == 0); +} + +static void hpoweron(void) +{ + if(isSH3()) return; + POWER.MSTPCR0.DMAC0 = 0; +} + +static void hpoweroff(void) +{ + if(isSH3()) return; + POWER.MSTPCR0.DMAC0 = 1; +} + //--- -// Context system for this driver +// State and driver metadata //--- -typedef struct +static void hsave(dma_state_t *s) { - channel_t ch[6]; - int clock; - uint16_t OR; - -} GPACKED(4) ctx_t; - -/* One buffer for the system state will go in gint's .bss section */ -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; + if(isSH3()) return; for(int i = 0; i < 6; i++) { channel_t *ch = dma_channel(i); - ctx->ch[i].SAR = ch->SAR; - ctx->ch[i].DAR = ch->DAR; - ctx->ch[i].TCR = ch->TCR; - ctx->ch[i].CHCR.lword = ch->CHCR.lword; + s->ch[i].SAR = ch->SAR; + s->ch[i].DAR = ch->DAR; + s->ch[i].TCR = ch->TCR; + s->ch[i].CHCR.lword = ch->CHCR.lword; } - - ctx->OR = DMA.OR.word; - - /* Save the supply status of the DMA0 clock */ - ctx->clock = POWER.MSTPCR0.DMAC0; + s->OR = DMA.OR.word; } -static void ctx_restore(void *buf) +static void hrestore(dma_state_t const *s) { - ctx_t *ctx = buf; - - /* Restore the supply status of the DMA0 clock first. If the DMA was - originally on standby, the context is basically trash so we don't - want to write that back. If it was originally running, we need to - power it up again for the writes to registers to have any effect */ - POWER.MSTPCR0.DMAC0 = ctx->clock; + if(isSH3()) return; /* Disable the DMA while editing */ DMA.OR.DME = 0; @@ -271,26 +266,24 @@ static void ctx_restore(void *buf) for(int i = 0; i < 6; i++) { channel_t *ch = dma_channel(i); - ch->SAR = ctx->ch[i].SAR; - ch->DAR = ctx->ch[i].DAR; - ch->TCR = ctx->ch[i].TCR; - ch->CHCR.lword = ctx->ch[i].CHCR.lword; + ch->SAR = s->ch[i].SAR; + ch->DAR = s->ch[i].DAR; + ch->TCR = s->ch[i].TCR; + ch->CHCR.lword = s->ch[i].CHCR.lword; } - DMA.OR.word = ctx->OR; + DMA.OR.word = s->OR; } -//--- -// Driver structure definition -//--- - gint_driver_t drv_dma0 = { - .name = "DMA0", - .init = init, - .wait = wait, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .name = "DMA", + .configure = configure, + .funbind = universal_unbind, + .unbind = universal_unbind, + .hpowered = hpowered, + .hpoweron = hpoweron, + .hpoweroff = hpoweroff, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(dma_state_t), }; - -GINT_DECLARE_DRIVER(2, drv_dma0); +GINT_DECLARE_DRIVER(05, drv_dma0); diff --git a/src/intc/intc.c b/src/intc/intc.c index 9755cfc..ee3b821 100644 --- a/src/intc/intc.c +++ b/src/intc/intc.c @@ -1,8 +1,13 @@ #include +#include #include +#include #include +#include -/* Interrupt controllers */ +//--- +// Interrupt controllers +//--- GDATA3 sh7705_intc_t SH7705_INTC = { .IPR = { @@ -63,12 +68,40 @@ static struct info { { IPRF, 0x00f0, IMR9, 0x02, _ /* Driver not SH3-compatible yet */ }, }; +/* Compact SH3 VBR-space scheme + + Due to the low amount of memory available on SH3, event codes that are + translated to SH4 are further remapped into the VBR space to eliminate gaps + and save space. Each entry in this table represents a 32-byte block after + the VBR + 0x200. It shows the SH4 event code whose gate is placed on that + block (some of gint's SH4 event codes are invented to host helper blocks). + + For instance, the 5th block after the entry gate hosts the interrupt handler + for SH4 event 0x9e0, which is ETMU0 underflow. + + The _inth_remap table in src/kernel/inth.S combines the SH3-SH4 translation + with the compact translation, hence its entry for 0xf00 (the SH3 event code + for ETMU0 underflow) is the offset in this table where 0x9e0 (the SH4 event + code for the same event) is stored, which is 4. */ +static const uint16_t sh3_vbr_map[] = { + 0x400, /* TMU0 underflow */ + 0x420, /* TMU1 underflow */ + 0x440, /* TMU2 underflow */ + 0x460, /* (gint custom: TMU helper) */ + 0x9e0, /* ETMU0 underflow */ + 0xd00, /* ETMU4 underflow (used as helper on SH3) */ + 0xd20, /* (gint custom: ETMU helper) */ + 0xd40, /* (gint custom: ETMU helper) */ + 0xaa0, /* RTC Periodic Interrupt */ + 1, /* (Filler to maintain the gap between 0xaa0 and 0xae0) */ + 0xae0, /* (gint custom: RTC helper) */ + 0 +}; //--- -// Interrupt controller functions +// Interrupt controller functions //--- -/* intc_priority(): Configure the level of interrupts */ int intc_priority(int intname, int level) { struct info const *i = &info[intname]; @@ -99,11 +132,52 @@ int intc_priority(int intname, int level) return oldlevel; } +void *intc_handler(int event_code, const void *handler, size_t size) +{ + void *dest; + + /* Normalize the event code */ + if(event_code < 0x400) return NULL; + event_code &= ~0x1f; + + /* Prevent writing beyond the end of the VBR space on SH4. Using code + 0xfc0 into the interrupt handler space (which starts 0x540 bytes + into VBR-reserved memory) would reach byte 0x540 + 0xfc0 - 0x400 = + 0x1100, which is out of gint's reserved VBR area. */ + if(isSH4() && event_code + size > 0xfc0) return NULL; + + /* On SH3, make VBR compact. Use this offset specified in the VBR map + above to avoid gaps */ + if(isSH3()) + { + int index = 0; + while(sh3_vbr_map[index]) + { + if((int)sh3_vbr_map[index] == event_code) break; + index++; + } + + /* This happens if the event has not beed added to the table, + ie. the compact VBR scheme does not support this code */ + if(!sh3_vbr_map[index]) return NULL; + + dest = (void *)cpu_getVBR() + 0x200 + index * 0x20; + } + /* On SH4, just use the code as offset */ + else + { + /* 0x40 is the size of the entry gate */ + dest = (void *)cpu_getVBR() + 0x640 + (event_code - 0x400); + } + + return memcpy(dest, handler, size); +} + //--- -// Initialization +// State and driver metadata //--- -static void init(void) +static void configure(void) { /* Just disable everything, drivers will enable what they support */ if(isSH3()) for(int i = 0; i < 8; i++) @@ -112,51 +186,35 @@ static void init(void) SH7305_INTC.IPR[2 * i] = 0x0000; } -//--- -// Driver context -//--- - -typedef struct +static void hsave(intc_state_t *s) { - uint16_t IPR[12]; - uint8_t MSK[13]; -} ctx_t; - -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; - if(isSH3()) { for(int i = 0; i < 8; i++) - ctx->IPR[i] = *(SH7705_INTC.IPR[i]); + s->IPR[i] = *(SH7705_INTC.IPR[i]); } else { for(int i = 0; i < 12; i++) - ctx->IPR[i] = SH7305_INTC.IPR[2 * i]; + s->IPR[i] = SH7305_INTC.IPR[2 * i]; uint8_t *IMR = (void *)SH7305_INTC.MSK; for(int i = 0; i < 13; i++, IMR += 4) - ctx->MSK[i] = *IMR; + s->MSK[i] = *IMR; } } -static void ctx_restore(void *buf) +static void hrestore(intc_state_t const *s) { - ctx_t *ctx = buf; - if(isSH3()) { for(int i = 0; i < 8; i++) - *(SH7705_INTC.IPR[i]) = ctx->IPR[i]; + *(SH7705_INTC.IPR[i]) = s->IPR[i]; } else { for(int i = 0; i < 12; i++) - SH7305_INTC.IPR[2 * i] = ctx->IPR[i]; + SH7305_INTC.IPR[2 * i] = s->IPR[i]; /* Setting masks it a bit more involved than reading them */ uint8_t *IMCR = (void *)SH7305_INTC.MSKCLR; @@ -164,22 +222,16 @@ static void ctx_restore(void *buf) for(int i = 0; i < 13; i++, IMR += 4, IMCR += 4) { *IMCR = 0xff; - *IMR = ctx->MSK[i]; + *IMR = s->MSK[i]; } } } -//--- -// Driver structure definition -//--- - gint_driver_t drv_intc = { .name = "INTC", - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(intc_state_t), }; - -GINT_DECLARE_DRIVER(0, drv_intc); +GINT_DECLARE_DRIVER(01, drv_intc); diff --git a/src/kernel/cpu.h b/src/kernel/cpu.h deleted file mode 100644 index f138110..0000000 --- a/src/kernel/cpu.h +++ /dev/null @@ -1,69 +0,0 @@ -//--- -// core:cpu - CPU registers and operation management -//--- - -#ifndef GINT_CORE_CPU -#define GINT_CORE_CPU - -#include - -/* cpu_setVBR(): Change VBR address - - Blocks interrupts then changes the VBR address and calls the provided INTC - configuration function before restoring interrupts. This function must - configure the INTC in a way that is safe for the new VBR controller, - including disabling all interrupts that it cannot handle. - - This function is loaded to a platform-dependent address determined at - runtime; call it indirectly through the function pointer. - - @vbr New VBR address - @conf_intc Configuration function - @arg Additional argument for conf_intc - Returns the previous VBR address. */ -extern uint32_t (*cpu_setVBR)(uint32_t vbr, void (*conf_intc)(int arg), - int arg); - -/* cpu_getVBR(): Query the current VBR address */ -uint32_t cpu_getVBR(void); - -/* cpu_setCPUOPM(): Change the CPU Operation Mode register - - Updates the CPU Operation Mode with the specified settings, then performs a - read and an ICBI to register the change. Only writable bits of CPUOPM should - be changed, other bits must be left at the value given by cpu_getcpuopm(). - - @CPUOPM New operation mode */ -void cpu_setCPUOPM(uint32_t CPUOPM); - -/* cpu_getCPUOPM(): Get the CPU OperatioN Mode register */ -uint32_t cpu_getCPUOPM(void); - -//--- -// Status Register -//--- - -/* Status Register bits */ -typedef lword_union(sr_t, - uint32_t :1; - uint32_t MD :1; - uint32_t RB :1; - uint32_t BL :1; - uint32_t RC :12; - uint32_t :3; - uint32_t DSP :1; - uint32_t DMY :1; - uint32_t DMX :1; - uint32_t M :1; - uint32_t Q :1; - uint32_t IMASK :4; - uint32_t RF :2; - uint32_t S :1; - uint32_t T :1; -); - -/* Get and set sr through the sr_t type */ -sr_t cpu_getSR(void); -void cpu_setSR(sr_t sr); - -#endif /* GINT_CORE_CPU */ diff --git a/src/kernel/cpu.s b/src/kernel/cpu.s deleted file mode 100644 index 7e63ad4..0000000 --- a/src/kernel/cpu.s +++ /dev/null @@ -1,99 +0,0 @@ -/* -** gint:core:vbr - Assembler-level VBR management -*/ - -.global _cpu_getVBR -.global _cpu_setVBR -.global _cpu_setCPUOPM -.global _cpu_getCPUOPM -.global _cpu_getSR -.global _cpu_setSR - -/* cpu_setVBR(): Change VBR address */ - -.section .gint.mapped, "ax" -_cpu_setVBR_reloc: - mov.l r8, @-r15 - mov.l r9, @-r15 - sts.l pr, @-r15 - - /* Block all interrupts by setting IMASK=15 */ - mov #0xf, r9 - shll2 r9 - shll2 r9 - stc sr, r0 - or r9, r0 - ldc r0, sr - - /* Set the new VBR address */ - stc vbr, r8 - ldc r4, vbr - - /* Call the configuration function */ - jsr @r5 - mov r6, r4 - - /* Enable interrupts again */ - stc sr, r0 - not r9, r9 - and r9, r0 - ldc r0, sr - - /* Return the previous VBR address */ - mov r8, r0 - - lds.l @r15+, pr - mov.l @r15+, r9 - rts - mov.l @r15+, r8 - -.section .gint.mappedrel, "aw" -_cpu_setVBR: - .long _cpu_setVBR_reloc - -.text - -/* cpu_getVBR(): Query the current VBR address */ -_cpu_getVBR: - stc vbr, r0 - rts - nop - -/* cpu_setCPUOPM(): Change the CPU Operation Mode register */ -_cpu_setCPUOPM: - /* Set CPUOPM as requested */ - mov.l 1f, r0 - mov.l r4, @r0 - - /* Read CPUOPM again */ - mov.l @r0, r5 - - /* Invalidate a cache address */ - mov #-96, r0 - shll16 r0 - shll8 r0 - icbi @r0 - - rts - nop - -/* cpu_getCPUOPM(): Get the CPU OperatioN Mode register */ -_cpu_getCPUOPM: - mov.l 1f, r0 - rts - mov.l @r0, r0 - -.align 4 -1: .long 0xff2f0000 - -/* cpu_getSR(): Get status register */ -_cpu_getSR: - stc sr, r0 - rts - nop - -/* cpu_setSR(): Set status register */ -_cpu_setSR: - ldc r4, sr - rts - nop diff --git a/src/kernel/drivers.h b/src/kernel/drivers.h deleted file mode 100644 index 5b13008..0000000 --- a/src/kernel/drivers.h +++ /dev/null @@ -1,23 +0,0 @@ -//--- -// core:drivers - Driver utilities for the kernel -// -// These are internal definitions; for general driver definitions, see -// instead. -//--- - -#ifndef GINT_CORE_DRIVERS -#define GINT_CORE_DRIVERS - -#include - -/* Linker script symbols for drivers by increasing levels of priority */ -extern gint_driver_t bdrv, edrv; - -/* Iterate on drivers in increasing level of priority */ -#define driver_asc(var) \ - (gint_driver_t *var = &bdrv; var < &edrv; var++) -/* Iterate on drivers in decreasing level of priority */ -#define driver_dsc(var) \ - (gint_driver_t *var = &edrv; (--var) >= &bdrv;) - -#endif /* GINT_CORE_DRIVERS */ diff --git a/src/kernel/exch.c b/src/kernel/exch.c index 2098d7b..7ecbed5 100644 --- a/src/kernel/exch.c +++ b/src/kernel/exch.c @@ -49,6 +49,7 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code) /* Custom gint codes for convenience */ if(code == 0x1020) name = "DMA address error"; if(code == 0x1040) name = "Add-in too large"; + if(code == 0x1060) name = "Memory init failed"; if(name[0]) dtext(1, 9, name); else dprint(1, 9, "%03x", code); @@ -80,6 +81,7 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code) /* Custom gint codes for convenience */ if(code == 0x1020) name = "DMA address error"; if(code == 0x1040) name = "Add-in not fully mapped (too large)"; + if(code == 0x1060) name = "Memory initialization failed (heap)"; dprint(6, 25, "%03x %s", code, name); diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index 91dd028..bf23881 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -5,146 +5,57 @@ #include #include #include +#include #include #include #include #include +#include -#include "cpu.h" #include "vbr.h" -#include "drivers.h" #include "kernel.h" -static void kinit_cpu(void); - -/* Forcefully pull in the INTC driver which gint cannot run without */ -extern gint_driver_t drv_intc; +/* Reference the CPU and INTC drivers which are required for gint to work */ +extern gint_driver_t drv_intc, drv_cpu; +GUNUSED gint_driver_t *gint_required_cpu = &drv_cpu; GUNUSED gint_driver_t *gint_required_intc = &drv_intc; -//--- -// Context for the CPU and registers not directly managed by a driver -//--- +/* World buffers for the OS and gint */ +gint_world_t gint_world_os = NULL; +gint_world_t gint_world_addin = NULL; -typedef struct -{ - sr_t SR; - uint32_t VBR; - uint32_t CPUOPM; -} ctx_t; - -/* System context and gint context for the CPU and VBR */ -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(ctx_t *ctx) -{ - if(isSH4()) - { - ctx->CPUOPM = cpu_getCPUOPM(); - ctx->SR = cpu_getSR(); - } -} -static void ctx_restore(ctx_t *ctx) -{ - if(isSH4()) - { - cpu_setCPUOPM(ctx->CPUOPM); - cpu_setSR(ctx->SR); - } -} - -//--- -// Driver control -//--- - -static void drivers_wait(void) -{ - for driver_asc(d) - { - if(d->wait) d->wait(); - } -} - -static void drivers_save_and_init(GUNUSED int zero) -{ - /* Initialize the CPU, which is done here instead of in a driver */ - ctx_save(&sys_ctx); - kinit_cpu(); - - for driver_asc(d) - { - if(isSH3() && d->driver_sh3) d->driver_sh3(); - if(d->ctx_save) d->ctx_save(d->sys_ctx); - if(d->init) d->init(); - } -} - -static void drivers_restore(int who) -{ - for driver_dsc(d) - { - if(d->ctx_restore) d->ctx_restore(who?d->gint_ctx:d->sys_ctx); - } - - ctx_restore(who ? &gint_ctx : &sys_ctx); -} - -static void drivers_switch(int who) -{ - /* Save all drivers in reverse order */ - for driver_dsc(d) - { - if(!d->ctx_save || !d->ctx_restore) continue; - d->ctx_save(who ? d->gint_ctx : d->sys_ctx); - } - ctx_save(who ? &gint_ctx : &sys_ctx); - - /* Restore the other context */ - ctx_restore(who ? &sys_ctx : &gint_ctx); - for driver_dsc(d) - { - if(!d->ctx_save || !d->ctx_restore) continue; - d->ctx_restore(who ? d->sys_ctx : d->gint_ctx); - } -} +/* Dynamic flags for all drivers */ +uint8_t *gint_driver_flags = NULL; //--- // Initialization and unloading //--- -static void kinit_cpu(void) -{ - if(isSH4()) - { - cpu_setCPUOPM(cpu_getCPUOPM() | 0x00000008); - - /* Enable DSP mode on the CPU */ - sr_t SR = cpu_getSR(); - SR.DSP = 1; - cpu_setSR(SR); - } -} - /* kinit(): Install and start gint */ void kinit(void) { + uint32_t VBR = 0; + #ifdef FX9860G - /* On fx-9860G, VBR is loaded at the end of the user RAM. */ - uint32_t uram_end = (uint32_t)mmu_uram() + mmu_uram_size(); - /* On SH4, stack is at the end of the region, leave 8k */ - if(isSH4()) uram_end -= 0x2000; - /* VBR size differs with models. On SH3, only 0x600 bytes are used due - to the compact scheme. On SH4, 0x1100 bytes are needed to cover the + /* On fx-9860G, VBR is loaded at the end of the user RAM. On SH4, the + end of the user RAM hosts the stack, for which we leave 8 kB + (0x2000 bytes). The VBR space takes about 0x600 bytes on SH3 due to + the compact scheme, while it uses about 0x1100 bytes for the whole expanded region. */ + uint32_t uram_end = (uint32_t)mmu_uram() + mmu_uram_size(); + if(isSH4()) uram_end -= 0x2000; uram_end -= (isSH3() ? 0x600 : 0x1100); - /* There are 0x100 unused bytes at the start of the VBR area */ - gint_ctx.VBR = uram_end - 0x100; + /* VBR is advanced 0x100 bytes because of an unused gap */ + VBR = uram_end - 0x100; #endif /* FX9860G */ #ifdef FXCG50 - /* On fx-CG 50, VBR is loaded at the start of the user RAM. */ - gint_ctx.VBR = (uint32_t)mmu_uram(); - /* All of the user RAM can be used, except for some 16k of stack */ + /* On fx-CG 50, VBR is loaded at the start of the user RAM; the linker + script leaves 5 kB (0x1400 bytes) before the start of the data + segment. The stack is again placed at the end of the region, and we + leave 16 kB. */ + VBR = (uint32_t)mmu_uram(); uint32_t uram_end = (uint32_t)mmu_uram() + mmu_uram_size() - 0x4000; #endif @@ -154,14 +65,9 @@ void kinit(void) uint32_t tlbh_size = (uint32_t)&gint_tlbh_size; /* Load the event handler entry points into memory */ - void *vbr = (void *)gint_ctx.VBR; - memcpy(vbr + 0x100, gint_exch, exch_size); - memcpy(vbr + 0x400, gint_tlbh, tlbh_size); - memcpy(vbr + 0x600, inth_entry, 64); - - /* Take control of the VBR and roll! */ - drivers_wait(); - sys_ctx.VBR = (*cpu_setVBR)(gint_ctx.VBR, drivers_save_and_init, 0); + memcpy((void *)VBR + 0x100, gint_exch, exch_size); + memcpy((void *)VBR + 0x400, gint_tlbh, tlbh_size); + memcpy((void *)VBR + 0x600, inth_entry, 64); /* Initialize memory allocators */ kmalloc_init(); @@ -176,99 +82,44 @@ void kinit(void) kmalloc_init_arena(&static_ram, true); kmalloc_add_arena(&static_ram); -} -/* Due to dire space restrictions on SH3, event codes that are translated to - SH4 are further remapped into the VBR space to eliminate gaps and save - space. Each entry in this table represents a 32-byte block after the - interrupt handler's entry gate. It shows the SH4 event code whose gate is - placed on that block (some of gint's SH4 event codes are invented to host - helper blocks). + /* Allocate world buffers for the OS and for gint */ + gint_world_os = gint_world_alloc(); + gint_world_addin = gint_world_alloc(); + gint_driver_flags = malloc(gint_driver_count()); - For instance, the 5th block after the entry gate hosts the interrupt handler - for SH4 event 0x9e0, which is ETMU0 underflow. - - The _inth_remap table in src/core/inth.S combines the SH3-SH4 translation - with the compact translation, hence its entry for 0xf00 (the SH3 event code - for ETMU0 underflow) is the offset in this table where 0x9e0 is stored, - which is 4. */ -static const uint16_t sh3_vbr_map[] = { - 0x400, /* TMU0 underflow */ - 0x420, /* TMU1 underflow */ - 0x440, /* TMU2 underflow */ - 0x460, /* (gint custom: TMU helper) */ - 0x9e0, /* ETMU0 underflow */ - 0xd00, /* ETMU4 underflow (used as helper on SH3) */ - 0xd20, /* (gint custom: ETMU helper) */ - 0xd40, /* (gint custom: ETMU helper) */ - 0xaa0, /* RTC Periodic Interrupt */ - 1, /* (Filler to maintain the gap between 0xaa0 and 0xae0) */ - 0xae0, /* (gint custom: RTC helper) */ - 0 -}; - -/* gint_inthandler(): Install interrupt handlers */ -void *gint_inthandler(int event_code, const void *handler, size_t size) -{ - void *dest; - - /* Normalize the event code */ - if(event_code < 0x400) return NULL; - event_code &= ~0x1f; - - /* Prevent writing beyond the end of the VBR space on SH4. Using code - 0xfc0 into the interrupt handler space (which starts 0x540 bytes - into VBR-reserved memory) would reach byte 0x540 + 0xfc0 - 0x400 = - 0x1100, which is out of gint's reserved VBR area. */ - if(isSH4() && event_code + size > 0xfc0) return NULL; - - /* On SH3, make VBR compact. Use this offset specified in the VBR map - above to avoid gaps */ - if(isSH3()) + if(!gint_world_os || !gint_world_addin || !gint_driver_flags) { - int index = 0; - while(sh3_vbr_map[index]) - { - if((int)sh3_vbr_map[index] == event_code) break; - index++; - } - - /* This happens if the event has not beed added to the table, - ie. the compact VBR scheme does not support this code */ - if(!sh3_vbr_map[index]) return NULL; - - /* Gates are placed starting at VBR + 0x200 to save space */ - dest = (void *)gint_ctx.VBR + 0x200 + index * 0x20; - } - /* On SH4, just use the code as offset */ - else - { - /* 0x40 is the size of the entry gate */ - dest = (void *)gint_ctx.VBR + 0x640 + (event_code - 0x400); + extern void gint_panic(uint32_t code); + gint_panic(0x1060); } - return memcpy(dest, handler, size); -} + /* Initialize drivers */ + for(int i = 0; i < gint_driver_count(); i++) + { + gint_driver_t *d = &gint_drivers[i]; + if(d->constructor) d->constructor(); -/* gint_switch(): Temporarily switch out of gint */ -void gint_switch(void (*function)(void)) -{ - /* Switch from gint to the OS after a short wait */ - drivers_wait(); - (*cpu_setVBR)(sys_ctx.VBR, drivers_switch, 1); + uint8_t *f = &gint_driver_flags[i]; + *f = (d->flags & GINT_DRV_INIT_) | GINT_DRV_CLEAN; + } - if(function) function(); + /* Select the VBR address for this world before configuring */ + cpu_configure_VBR(VBR); - /* Then switch back to gint once the OS finishes working */ - drivers_wait(); - (*cpu_setVBR)(gint_ctx.VBR, drivers_switch, 0); + gint_world_switch_in(gint_world_os, gint_world_addin); } /* kquit(): Quit gint and give back control to the system */ void kquit(void) { - /* Wait for hardware tasks then restore all of the drivers' state and - return the VBR space to the OS */ - drivers_wait(); - (*cpu_setVBR)(sys_ctx.VBR, drivers_restore, 0); + gint_world_switch_out(gint_world_addin, gint_world_os); + + gint_world_free(gint_world_os); + gint_world_free(gint_world_addin); + free(gint_driver_flags); + + gint_world_os = NULL; + gint_world_addin = NULL; + gint_driver_flags = NULL; } diff --git a/src/kernel/osmenu.c b/src/kernel/osmenu.c index 0c325b4..e5cdaaa 100644 --- a/src/kernel/osmenu.c +++ b/src/kernel/osmenu.c @@ -69,5 +69,5 @@ static void __osmenu(void) /* gint_osmenu() - switch out of gint and call the calculator's main menu */ void gint_osmenu(void) { - gint_switch(__osmenu); + gint_world_switch(GINT_CALL(__osmenu)); } diff --git a/src/kernel/world.c b/src/kernel/world.c new file mode 100644 index 0000000..5e04bf0 --- /dev/null +++ b/src/kernel/world.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +//--- +// World buffer +//--- + +gint_world_t gint_world_alloc(void) +{ + size_t header_size = gint_driver_count() * sizeof(void *); + size_t data_size = 0; + + for(int i = 0; i < gint_driver_count(); i++) + data_size += (gint_drivers[i].state_size + 3) & ~3; + + void *buffer = malloc(header_size + data_size); + if(!buffer) return NULL; + + gint_world_t world = buffer; + buffer += header_size; + + for(int i = 0; i < gint_driver_count(); i++) + { + world[i] = buffer; + buffer += (gint_drivers[i].state_size + 3) & ~3; + } + + return world; +} + +void gint_world_free(gint_world_t world) +{ + free(world); +} + +//--- +// World switch with driver state saves +//--- + +void gint_world_switch_in(gint_world_t world_os, gint_world_t world_addin) +{ + /* Unbind from the OS driver and complete foreign asynchronous tasks */ + for(int i = gint_driver_count() - 1; i >= 0; i--) + { + gint_driver_t *d = &gint_drivers[i]; + if(d->funbind) d->funbind(); + } + + cpu_atomic_start(); + + for(int i = 0; i < gint_driver_count(); i++) + { + gint_driver_t *d = &gint_drivers[i]; + uint8_t *f = &gint_driver_flags[i]; + + bool foreign_powered = (!d->hpowered || d->hpowered()); + if(foreign_powered) + *f |= GINT_DRV_FOREIGN_POWERED; + else + *f &= ~GINT_DRV_FOREIGN_POWERED; + + /* Power the device if it was unpowered previously */ + if(!foreign_powered && d->hpoweron) d->hpoweron(); + + /* For non-shared devices, save previous device state and + consider restoring the preserved one */ + if(!(*f & GINT_DRV_SHARED)) + { + if(d->hsave) + d->hsave(world_os[i]); + if(!(*f & GINT_DRV_CLEAN) && d->hrestore) + d->hrestore(world_addin[i]); + } + + /* Bind the driver, configure if needed. Note that we either + configure or restore the new world's state, not both */ + if(d->bind) d->bind(); + + if(*f & GINT_DRV_CLEAN) + { + if(d->configure) d->configure(); + *f &= ~GINT_DRV_CLEAN; + } + } + + cpu_atomic_end(); +} + +void gint_world_switch_out(gint_world_t world_addin, gint_world_t world_os) +{ + for(int i = gint_driver_count() - 1; i >= 0; i--) + { + gint_driver_t *d = &gint_drivers[i]; + if(d->unbind) d->unbind(); + } + + cpu_atomic_start(); + + for(int i = gint_driver_count() - 1; i >= 0; i--) + { + gint_driver_t *d = &gint_drivers[i]; + uint8_t *f = &gint_driver_flags[i]; + + /* For non-shared devices, save previous device state and + consider restoring the preserved one */ + if(!(*f & GINT_DRV_SHARED)) + { + if(d->hsave) d->hsave(world_addin[i]); + if(d->hrestore) d->hrestore(world_os[i]); + } + + /* Restore the power state of the device */ + if(!(*f & GINT_DRV_FOREIGN_POWERED) && d->hpoweroff) + d->hpoweroff(); + } + + cpu_atomic_end(); +} + +int gint_world_switch(gint_call_t call) +{ + gint_world_switch_out(gint_world_addin, gint_world_os); + int rc = call.function ? gint_call(call) : 0; + gint_world_switch_in(gint_world_os, gint_world_addin); + return rc; +} + +void gint_switch(void (*function)(void)) +{ + gint_world_switch(GINT_CALL(function)); +} diff --git a/src/keysc/keysc.c b/src/keysc/keysc.c index dda3fa8..b09028d 100644 --- a/src/keysc/keysc.c +++ b/src/keysc/keysc.c @@ -159,7 +159,7 @@ int keydown_any(int key, ...) // Driver initialization //--- -static void init(void) +static void configure(void) { keydev_init(&dev_keysc); @@ -174,12 +174,11 @@ static void init(void) } //--- -// Driver structure definition +// State and driver metadata //--- gint_driver_t drv_keysc = { - .name = "KEYSC", - .init = init, + .name = "KEYSC", + .configure = configure, }; - -GINT_DECLARE_DRIVER(4, drv_keysc); +GINT_DECLARE_DRIVER(23, drv_keysc); diff --git a/src/mmu/mmu.c b/src/mmu/mmu.c index 04a4165..90c0389 100644 --- a/src/mmu/mmu.c +++ b/src/mmu/mmu.c @@ -4,6 +4,7 @@ #include #include +#include #include //--- @@ -175,11 +176,7 @@ uint32_t utlb_translate(uint32_t page, uint32_t *size) return -1; } -//--- -// Initialization -//--- - -static void init(void) +static void configure(void) { /* Make writes to the control register area synchronous; this is needed for the SPU to operate properly */ @@ -187,41 +184,28 @@ static void init(void) } //--- -// Context management +// State and driver metadata //--- -typedef struct { - uint32_t PASCR; - uint32_t IRMCR; -} ctx_t; - -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) +static void hsave(mmu_state_t *s) { if(isSH3()) return; - - ctx_t *ctx = buf; - ctx->PASCR = SH7305_MMU.PASCR.lword; - ctx->IRMCR = SH7305_MMU.IRMCR.lword; + s->PASCR = SH7305_MMU.PASCR.lword; + s->IRMCR = SH7305_MMU.IRMCR.lword; } -static void ctx_restore(void *buf) +static void hrestore(mmu_state_t const *s) { if(isSH3()) return; - - ctx_t *ctx = buf; - SH7305_MMU.PASCR.lword = ctx->PASCR; - SH7305_MMU.IRMCR.lword = ctx->IRMCR; + SH7305_MMU.PASCR.lword = s->PASCR; + SH7305_MMU.IRMCR.lword = s->IRMCR; } gint_driver_t drv_mmu = { .name = "MMU", - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(mmu_state_t), }; - -GINT_DECLARE_DRIVER(1, drv_mmu); +GINT_DECLARE_DRIVER(02, drv_mmu); diff --git a/src/r61524/r61524.c b/src/r61524/r61524.c index 1002aeb..1ba7a2f 100644 --- a/src/r61524/r61524.c +++ b/src/r61524/r61524.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -119,85 +120,6 @@ void r61524_win_set(uint16_t HSA, uint16_t HEA, uint16_t VSA, uint16_t VEA) // Driver functions //--- -/* void r61524_test(void) -{ - uint16_t device_name; - uint16_t doc; - int SM, SS; - entry_mode_t em; - uint16_t dc2; - int FP, BP; - uint16_t lpc; - int VEM, COL; - - //--- - - select(device_code_read); - device_name = read(); - - Bdisp_AllClr_VRAM(); - print(1, 1, "Name=????"); - print_hex(6, 1, device_name, 4); - - if(device_name != 0x1524) - { - print(1, 2, "Aborting."); - Bdisp_PutDisp_DD(); - getkey(); - return; - } - - //--- - - select(driver_output_control); - doc = read(); - SM = (doc >> 10) & 1; - SS = (doc >> 8) & 1; - - select(entry_mode); - em.word = read(); - - select(display_control_2); - dc2 = read(); - FP = (dc2 >> 8) & 0xf; - BP = dc2 & 0xf; - - select(low_power_control); - lpc = read(); - VEM = (lpc >> 4) & 1; - COL = lpc & 1; - - //--- - - print(15, 4, " SM=?"); - print_hex(19, 4, SM, 1); - print(15, 5, " SS=?"); - print_hex(19, 5, SS, 1); - - print(1, 2, "TRI=? DFM=? BGR=?"); - print_hex(5, 2, em.TRI, 1); - print_hex(12, 2, em.DFM, 1); - print_hex(19, 2, em.BGR, 1); - print(1, 3, "HWM=? ORG=? ID=?"); - print_hex(5, 3, em.HWM, 1); - print_hex(12, 3, em.DFM, 1); - print_hex(19, 3, em.ID, 1); - print(1, 4, " AM=? EPF=?"); - print_hex(5, 4, em.AM, 1); - print_hex(12, 4, em.EPF, 1); - - print(1, 5, " FP=? BP=?"); - print_hex(5, 5, FP, 1); - print_hex(12, 5, BP, 1); - - print(1, 6, "VEM=? COL=?"); - print_hex(5, 6, VEM, 1); - print_hex(12, 6, COL, 1); - - Bdisp_PutDisp_DD(); - getkey(); -} */ - /* TODO: r61524: update, backlight, brightness, gamma */ void r61524_display(uint16_t *vram, int start, int height, int method) @@ -243,55 +165,23 @@ void r61524_display(uint16_t *vram, int start, int height, int method) } //--- -// Context system for this driver +// State and driver metadata //--- -typedef struct +static void hsave(r61524_state_t *s) { - /* Graphics RAM range */ - uint16_t HSA, HEA, VSA, VEA; - -} GPACKED(2) ctx_t; - -/* Allocate one buffer in gint's storage section */ -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; - r61524_win_get(&ctx->HSA, &ctx->HEA, &ctx->VSA, &ctx->VEA); + r61524_win_get(&s->HSA, &s->HEA, &s->VSA, &s->VEA); } -static void ctx_restore(void *buf) +static void hrestore(r61524_state_t const *s) { - ctx_t *ctx = buf; - r61524_win_set(ctx->HSA, ctx->HEA, ctx->VSA, ctx->VEA); + r61524_win_set(s->HSA, s->HEA, s->VSA, s->VEA); } -//--- -// Driver initialization -//--- - -static void init(void) -{ - select(device_code_read); - uint16_t devname = read(); - - gint[HWDD] = HW_LOADED | HWDD_FULL; - if(devname == 0x1524) gint[HWDD] |= HWDD_KNOWN; -} - -//--- -// Driver structure definition -//--- - gint_driver_t drv_r61524 = { .name = "R61524", - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(r61524_state_t), }; - -GINT_DECLARE_DRIVER(5, drv_r61524); +GINT_DECLARE_DRIVER(26, drv_r61524); diff --git a/src/render-fx/dupdate.c b/src/render-fx/dupdate.c index 1ec7d85..d9fb71b 100644 --- a/src/render-fx/dupdate.c +++ b/src/render-fx/dupdate.c @@ -3,7 +3,7 @@ #include "render-fx.h" /* Standard video RAM for fx9860g is 1 bit per pixel */ -GSECTION(".bss") static uint32_t fx_vram[256]; +GSECTION(".bss") GALIGNED(32) static uint32_t fx_vram[256]; /* Here is the definition of the VRAM pointer, exposed in */ uint32_t *gint_vram = fx_vram; diff --git a/src/rtc/rtc.c b/src/rtc/rtc.c index 5d92b29..9fa39e6 100644 --- a/src/rtc/rtc.c +++ b/src/rtc/rtc.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -134,15 +134,13 @@ void rtc_stop_timer(void) // Driver initialization //--- -#if defined(FX9860G) || (!defined(FX9860G) && !defined(FXCG50)) -static void driver_sh3(void) +static void constructor(void) { /* Adjust the address of the RTC */ - RTC = &SH7705_RTC; + if(isSH3()) RTC = &SH7705_RTC; } -#endif -static void init(void) +static void configure(void) { /* Disable the carry and alarm interrupts (they share their IPR bits with the periodic interrupt, which we want to enable) */ @@ -156,8 +154,8 @@ static void init(void) /* Install the RTC interrupt handler */ GUNUSED void *h0, *h1; - h0 = gint_inthandler(0xaa0, inth_rtc_pri, 32); - h1 = gint_inthandler(0xae0, inth_rtc_pri_helper, 32); + h0 = intc_handler(0xaa0, inth_rtc_pri, 32); + h1 = intc_handler(0xae0, inth_rtc_pri_helper, 32); timer_params = h0 + 20; @@ -172,44 +170,27 @@ static void init(void) } //--- -// Context system for this driver +// State and driver metadata //--- -typedef struct +static void hsave(rtc_state_t *s) { - uint8_t RCR1; - uint8_t RCR2; -} ctx_t; - -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; - ctx->RCR1 = RTC->RCR1.byte; - ctx->RCR2 = RTC->RCR2.byte; + s->RCR1 = RTC->RCR1.byte; + s->RCR2 = RTC->RCR2.byte; } -static void ctx_restore(void *buf) +static void hrestore(rtc_state_t const *s) { - ctx_t *ctx = buf; - - RTC->RCR1.byte = ctx->RCR1 & 0x18; - RTC->RCR2.byte = ctx->RCR2 & 0x7f; + RTC->RCR1.byte = s->RCR1 & 0x18; + RTC->RCR2.byte = s->RCR2 & 0x7f; } -//--- -// Driver structure definition -//--- - gint_driver_t drv_rtc = { .name = "RTC", - .driver_sh3 = GINT_DRIVER_SH3(driver_sh3), - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .constructor = constructor, + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(rtc_state_t), }; - -GINT_DECLARE_DRIVER(2, drv_rtc); +GINT_DECLARE_DRIVER(13, drv_rtc); diff --git a/src/spu/spu.c b/src/spu/spu.c index 7372b1e..e8345f6 100644 --- a/src/spu/spu.c +++ b/src/spu/spu.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -11,7 +12,7 @@ #define CPG SH7305_CPG #define POWER SH7305_POWER -static void init(void) +static void configure(void) { /* Block SPU interrupts from DSP0, DSP1, and their DMA */ intc_priority(INTC_SPU_DSP0, 0); @@ -53,46 +54,30 @@ int spu_zero(void) } //--- -// Hardware context +// State and driver metadata //--- -typedef struct +static void hsave(spu_state_t *s) { - uint32_t PBANKC0, PBANKC1; - uint32_t XBANKC0, XBANKC1; -} ctx_t; - -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; - ctx->PBANKC0 = SPU.PBANKC0; - ctx->PBANKC1 = SPU.PBANKC1; - ctx->XBANKC0 = SPU.XBANKC0; - ctx->XBANKC1 = SPU.XBANKC1; + s->PBANKC0 = SPU.PBANKC0; + s->PBANKC1 = SPU.PBANKC1; + s->XBANKC0 = SPU.XBANKC0; + s->XBANKC1 = SPU.XBANKC1; } -static void ctx_restore(void *buf) +static void hrestore(spu_state_t const *s) { - ctx_t *ctx = buf; - SPU.PBANKC0 = ctx->PBANKC0; - SPU.PBANKC1 = ctx->PBANKC1; - SPU.XBANKC0 = ctx->XBANKC0; - SPU.XBANKC1 = ctx->XBANKC1; + SPU.PBANKC0 = s->PBANKC0; + SPU.PBANKC1 = s->PBANKC1; + SPU.XBANKC0 = s->XBANKC0; + SPU.XBANKC1 = s->XBANKC1; } -//--- -// Driver structure definition -//--- - gint_driver_t drv_spu = { .name = "SPU", - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(spu_state_t), }; - -GINT_DECLARE_DRIVER(3, drv_spu); +GINT_DECLARE_DRIVER(16, drv_spu); diff --git a/src/t6k11/t6k11.c b/src/t6k11/t6k11.c index fa52625..89fbc82 100644 --- a/src/t6k11/t6k11.c +++ b/src/t6k11/t6k11.c @@ -3,6 +3,7 @@ //--- #include +#include #include #include @@ -181,68 +182,39 @@ void t6k11_backlight(int setting) if(setting < 0) *port ^= mask; } -//--- -// Context system for this driver -//--- - -typedef struct +static void constructor(void) { - /* Some status bits, obtained by using the STRD command */ - uint8_t strd; - - /* There *are* other parameters that are affected by the driver, but - they cannot be read, so I can't determine the system's setting */ - -} GPACKED(1) ctx_t; - -/* Pre-allocate a context in gint's uninitialized section */ -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - if(gint[HWCALC] == HWCALC_G35PE2) return; - - ctx_t *ctx = buf; - ctx->strd = status(); -} - -static void ctx_restore(void *buf) -{ - if(gint[HWCALC] == HWCALC_G35PE2) return; - - ctx_t *ctx = buf; - - /* Set an X-address of 0 with the original display mode */ - uint8_t nf = (ctx->strd & 0x04) >> 2; - command(reg_xaddr, 0x80 | (nf << 6)); - - /* Restore the counter mode */ - uint8_t cnt = (ctx->strd & 0x03); - command(reg_counter, cnt); -} - -//--- -// Driver initialization -//--- - -static void init(void) -{ - gint[HWDD] = HW_LOADED | HWDD_LIGHT; - if(gint[HWCALC] == HWCALC_G35PE2) t6k11_version = 2; } //--- -// Driver structure definition +// State and driver metadata //--- +static void hsave(t6k11_state_t *s) +{ + if(t6k11_version == 2) return; + s->STRD = status(); +} + +static void hrestore(t6k11_state_t const *s) +{ + if(t6k11_version == 2) return; + + /* Set an X-address of 0 with the original display mode */ + uint8_t nf = (s->STRD & 0x04) >> 2; + command(reg_xaddr, 0x80 | (nf << 6)); + + /* Restore the counter mode */ + uint8_t cnt = (s->STRD & 0x03); + command(reg_counter, cnt); +} + gint_driver_t drv_t6k11 = { .name = "T6K11", - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .constructor = constructor, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(t6k11_state_t), }; - -GINT_DECLARE_DRIVER(5, drv_t6k11); +GINT_DECLARE_DRIVER(26, drv_t6k11); diff --git a/src/tmu/sleep.c b/src/tmu/sleep.c index c24670b..46d7c12 100644 --- a/src/tmu/sleep.c +++ b/src/tmu/sleep.c @@ -13,7 +13,10 @@ static void do_sleep(uint64_t delay_us, int spin) if(timer < 0) return; timer_start(timer); - if(spin) timer_spinwait(timer); + if(spin) { + timer_spinwait(timer); + timer_stop(timer); + } else timer_wait(timer); } diff --git a/src/tmu/tmu.c b/src/tmu/tmu.c index 0b710ea..4f15e1d 100644 --- a/src/tmu/tmu.c +++ b/src/tmu/tmu.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include #include @@ -39,8 +39,8 @@ static volatile uint8_t *TSTR = &SH7305_TMU.TSTR; // Local functions //--- -/* configure(): Configure a fixed timer */ -static void configure(int id, uint32_t delay, int clock, void *f, uint32_t arg) +/* conf(): Configure a fixed timer */ +static void conf(int id, uint32_t delay, int clock, void *f, uint32_t arg) { if(id < 3) { @@ -169,7 +169,7 @@ int timer_setup(int spec, uint64_t delay, timer_callback_t function, ...) /* Find the delay constant for that timer and clock */ if(spec < 0) delay = timer_delay(id, delay, clock); - configure(id, delay, clock, function.v, arg); + conf(id, delay, clock, function.v, arg); return id; } @@ -314,22 +314,23 @@ extern void inth_tmu(void); extern void inth_etmu4(void); extern void inth_etmux(void); -#ifdef FX9860G -static void driver_sh3(void) +static void constructor(void) { - TMU = SH7705_TMU.TMU; - ETMU = SH7705_ETMU; - TSTR = &SH7705_TMU.TSTR; + if(isSH3()) + { + TMU = SH7705_TMU.TMU; + ETMU = SH7705_ETMU; + TSTR = &SH7705_TMU.TSTR; + } } -#endif /* FX9860G */ -static void init(void) +static void configure(void) { uint16_t etmu_event[6] = { 0x9e0, 0xc20, 0xc40, 0x900, 0xd00, 0xfa0 }; *TSTR = 0; /* Install the standard TMU's interrupt handlers */ - void *h = gint_inthandler(0x400, inth_tmu, 128); + void *h = intc_handler(0x400, inth_tmu, 128); timers[0] = h + 84; timers[1] = h + 104; timers[2] = h + 116; @@ -367,7 +368,7 @@ static void init(void) /* Install the extra timers. On SH3, only ETMU0 is available */ for(int i = 3; i < timer_count(); i++) if(i != 7) { - void *h = gint_inthandler(etmu_event[i-3], inth_etmux, 32); + void *h = intc_handler(etmu_event[i-3], inth_etmux, 32); timers[i] = h + 20; /* On SH3, the ETMU handler is not at an offset of 0x900 (event @@ -383,7 +384,7 @@ static void init(void) } /* Also install ETMU4, even on SH3, because it contains common code */ - h = gint_inthandler(etmu_event[4], inth_etmu4, 96); + h = intc_handler(etmu_event[4], inth_etmu4, 96); timers[7] = h + 84; *(uint32_t *)(h + 92) = (uint32_t)&ETMU[4].TCR; @@ -402,58 +403,25 @@ static void init(void) intc_priority(INTC_ETMU_TUNI4, 7); intc_priority(INTC_ETMU_TUNI5, 7); } - - /* Record details in gint's hardware information interface */ - - gint[HWETMU] = HW_LOADED | (isSH3() ? HWETMU_1 : HWETMU_6); - - for(int i = 0; i < timer_count() - 3; i++) - { - etmu_t *T = &ETMU[i]; - int v = !(T->TCOR + 1) - && !(T->TCNT + 1) - && !(T->TSTR) - && !(T->TCR.UNF) - && !(T->TCR.UNIE); - gint[HWETMU] |= v << (i + 2); - } } //--- -// Context system for this driver +// State and driver metadata //--- -struct stored_timer { - uint32_t TCOR; - uint32_t TCNT; - uint16_t TCR; - uint16_t TSTR; -}; - -typedef struct +static void hsave(tmu_state_t *s) { - struct stored_timer t[9]; - uint8_t TSTR; -} ctx_t; - -/* Allocate a system buffer in gint's BSS area */ -GBSS static ctx_t sys_ctx, gint_ctx; - -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; - ctx->TSTR = *TSTR; + s->TSTR = *TSTR; for(int i = 0; i < 3; i++) { - struct stored_timer *c = &ctx->t[i]; - c->TCOR = TMU[i].TCOR; - c->TCNT = TMU[i].TCNT; - c->TCR = TMU[i].TCR.word; + s->t[i].TCOR = TMU[i].TCOR; + s->t[i].TCNT = TMU[i].TCNT; + s->t[i].TCR = TMU[i].TCR.word; } for(int i = 3; i < timer_count(); i++) { - struct stored_timer *c = &ctx->t[i]; + struct tmu_state_stored_timer *c = &s->t[i]; etmu_t *T = &ETMU[i-3]; /* Don't snapshot an interrupt state, because the timer state @@ -465,21 +433,19 @@ static void ctx_save(void *buf) } } -static void ctx_restore(void *buf) +static void hrestore(tmu_state_t const *s) { - ctx_t *ctx = buf; *TSTR = 0; for(int i = 0; i < 3; i++) { - struct stored_timer *c = &ctx->t[i]; - TMU[i].TCOR = c->TCOR; - TMU[i].TCNT = c->TCNT; - TMU[i].TCR.word = c->TCR; + TMU[i].TCOR = s->t[i].TCOR; + TMU[i].TCNT = s->t[i].TCNT; + TMU[i].TCR.word = s->t[i].TCR; } for(int i = 3; i < timer_count(); i++) { - struct stored_timer *c = &ctx->t[i]; + struct tmu_state_stored_timer const *c = &s->t[i]; etmu_t *T = &ETMU[i-3]; do T->TCOR = c->TCOR; @@ -487,7 +453,6 @@ static void ctx_restore(void *buf) T->TSTR = c->TSTR; - /* Remember that TCNT and TCR take time to write to */ do T->TCNT = c->TCNT; while(T->TCNT != c->TCNT); @@ -495,21 +460,15 @@ static void ctx_restore(void *buf) while(T->TCR.byte != c->TCR); } - *TSTR = ctx->TSTR; + *TSTR = s->TSTR; } -//--- -// Driver structure definition -//--- - gint_driver_t drv_tmu = { .name = "TMU", - .driver_sh3 = GINT_DRIVER_SH3(driver_sh3), - .init = init, - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .constructor = constructor, + .configure = configure, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(tmu_state_t), }; - -GINT_DECLARE_DRIVER(2, drv_tmu); +GINT_DECLARE_DRIVER(13, drv_tmu); diff --git a/src/usb/usb.c b/src/usb/usb.c index 5fa8d67..b469a95 100644 --- a/src/usb/usb.c +++ b/src/usb/usb.c @@ -3,9 +3,9 @@ #include #include #include +#include #include #include -#include #include "usb_private.h" #define USB SH7305_USB @@ -52,43 +52,33 @@ void usb_log(char const *format, ...) // Module powering and depowering //--- -static bool usb_module_active(void) +static bool hpowered(void) { return (SH7305_CPG.USBCLKCR.CLKSTP == 0) && (SH7305_POWER.MSTPCR2.USB0 == 0); } -/* Start the clock of the USB module, without enabling operations or notifying - the host. This procedure does just enough to allow reading registers, and - writing to SYSCFG and BUSWAIT. If (enable_write == true), it also turns on - SCKE so that registers can be written to. */ -static void usb_module_start(bool enable_write) +static void hpoweron(void) { - if(!usb_module_active()) - { - /* TODO: USB: Proper handling of MSELCRA and MSELCRB */ - uint16_t volatile *MSELCRA = (void *)0xa4050180; - uint16_t volatile *MSELCRB = (void *)0xa4050182; - *MSELCRA &= 0xff3f; - *MSELCRB &= 0x3fff; + if(hpowered()) return; - /* Leave some delay for the clock to settle (like OS). I have - observed that after a reset, 20 ms are *NOT ENOUGH* for the - clock to start properly. 35 ms seemed fine. (?) */ - SH7305_CPG.USBCLKCR.CLKSTP = 0; - sleep_us_spin(50000); + /* TODO: USB: Proper handling of MSELCRA and MSELCRB */ + uint16_t volatile *MSELCRA = (void *)0xa4050180; + uint16_t volatile *MSELCRB = (void *)0xa4050182; + *MSELCRA &= 0xff3f; + *MSELCRB &= 0x3fff; - SH7305_POWER.MSTPCR2.USB0 = 0; - SH7305_USB_UPONCR.word = 0x0600; - } + /* Leave some delay for the clock to settle. The OS leaves + 100 ms, but it just never seems necessary. */ + SH7305_CPG.USBCLKCR.CLKSTP = 0; + sleep_us_spin(1000); - if(!enable_write) return; + SH7305_POWER.MSTPCR2.USB0 = 0; + SH7305_USB_UPONCR.word = 0x0600; - /* Turn on SCKE, which activates all other registers. Because the clock - for the USB module is 48 MHz and the processor runs at a higher - frequency, wait for a little bit before modifying registers. Writing - within 3-4 CPU cycles is 100% unsafe and has been observed to cause - freezes during testing. */ + /* Turn on SCKE, which activates all other registers. The existing + BUSWAIT delay might not be high enough, so wait a little bit before + modifying registers; a couple CPU cycles is enough. */ USB.SYSCFG.SCKE = 1; for(int i = 0; i < 10; i++) __asm__ volatile("nop"); @@ -96,8 +86,7 @@ static void usb_module_start(bool enable_write) USB.BUSWAIT.word = 15; } -/* Stop the clock of the USB module. */ -static void usb_module_stop(bool restore_mselcr) +static void hpoweroff(void) { uint16_t volatile *MSELCRA = (void *)0xa4050180; uint16_t volatile *MSELCRB = (void *)0xa4050182; @@ -110,9 +99,7 @@ static void usb_module_stop(bool restore_mselcr) SH7305_POWER.MSTPCR2.USB0 = 1; SH7305_CPG.USBCLKCR.CLKSTP = 1; - sleep_us_spin(50000); - - if(!restore_mselcr) return; + sleep_us_spin(1000); /* The values used by the OS (a PFC driver could do better) */ *MSELCRB = (*MSELCRB & 0x3fff) | 0xc000; @@ -133,7 +120,7 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback) } usb_open_callback = callback; - usb_module_start(true); + if(!hpowered()) hpoweron(); *(uint16_t volatile *)0xa4d800c2 = 0x0020; @@ -178,7 +165,7 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback) USB.NRDYENB.word = 0x0000; USB.BEMPENB.word = 0x0000; - gint_inthandler(0xa20, inth_usb, 32); + intc_handler(0xa20, inth_usb, 32); intc_priority(INTC_USB, 15); usb_open_status = true; @@ -193,7 +180,7 @@ void usb_open_wait(void) void usb_close(void) { intc_priority(INTC_USB, 0); - usb_module_stop(false); + hpoweroff(); usb_log("---- usb_close ----\n"); usb_open_callback = GINT_CALL_NULL; @@ -267,91 +254,53 @@ void usb_interrupt_handler(void) } //--- -// Hardware context +// State and driver metadata //--- -typedef struct +void hsave(usb_state_t *s) { - /* Control and power-up. We don't try to save power-related registers - from other modules nor UPONCR, because (1) they have to be changed - to access the module, so the OS cannot realy on their value being - preserved, and (2) numerous timing and order issues mean in practice - that changing them less makes program more stable. */ - uint16_t SYSCFG, DVSTCTR, TESTMODE, REG_C2; - /* FIFO configuration */ - uint16_t CFIFOSEL, D0FIFOSEL, D1FIFOSEL; - /* Interrupt configuration */ - uint16_t INTENB0, BRDYENB, NRDYENB, BEMPENB, SOFCFG; - /* Default Control Pipe (maybe not needed) */ - uint16_t DCPCFG, DCPMAXP, DCPCTR; - /* Whether the module is running at this time */ - bool active; -} ctx_t; + s->SYSCFG = USB.SYSCFG.word; + s->DVSTCTR = USB.DVSTCTR.word; + s->TESTMODE = USB.TESTMODE.word; + s->REG_C2 = USB.REG_C2; -GBSS static ctx_t sys_ctx, gint_ctx; + s->CFIFOSEL = USB.CFIFOSEL.word; + s->D0FIFOSEL = USB.D0FIFOSEL.word; + s->D1FIFOSEL = USB.D1FIFOSEL.word; -static void ctx_save(void *buf) -{ - ctx_t *ctx = buf; + s->INTENB0 = USB.INTENB0.word; + s->BRDYENB = USB.BRDYENB.word; + s->NRDYENB = USB.NRDYENB.word; + s->BEMPENB = USB.BEMPENB.word; + s->SOFCFG = USB.SOFCFG.word; - /* We only save the OS context once to avoid unnecessary quick power - cycles (which are the most unstable things in this driver) */ - static bool has_saved_sys = false; - if(buf == &sys_ctx && has_saved_sys) return; - if(buf == &sys_ctx) has_saved_sys = true; - - ctx->active = usb_module_active() || (ctx == &gint_ctx); - - /* Power ON the USB module clock so that the registers can be saved. - We don't enable writing since we only want to observe registers */ - usb_module_start(false); - - ctx->SYSCFG = USB.SYSCFG.word; - ctx->DVSTCTR = USB.DVSTCTR.word; - ctx->TESTMODE = USB.TESTMODE.word; - ctx->REG_C2 = USB.REG_C2; - - ctx->CFIFOSEL = USB.CFIFOSEL.word; - ctx->D0FIFOSEL = USB.D0FIFOSEL.word; - ctx->D1FIFOSEL = USB.D1FIFOSEL.word; - - ctx->INTENB0 = USB.INTENB0.word; - ctx->BRDYENB = USB.BRDYENB.word; - ctx->NRDYENB = USB.NRDYENB.word; - ctx->BEMPENB = USB.BEMPENB.word; - ctx->SOFCFG = USB.SOFCFG.word; - - ctx->DCPCFG = USB.DCPCFG.word; - ctx->DCPMAXP = USB.DCPMAXP.word; - ctx->DCPCTR = USB.DCPCTR.word; + s->DCPCFG = USB.DCPCFG.word; + s->DCPMAXP = USB.DCPMAXP.word; + s->DCPCTR = USB.DCPCTR.word; /* Leave the module open for gint to use it, or for the next restore to proceed more quickly (if during a world switch) */ } -static void ctx_restore(void *buf) +static void hrestore(usb_state_t const *s) { - ctx_t *ctx = buf; + USB.DVSTCTR.word = s->DVSTCTR; + USB.TESTMODE.word = s->TESTMODE; + USB.REG_C2 = s->REG_C2; - usb_module_start(true); + USB.CFIFOSEL.word = s->CFIFOSEL; + USB.D0FIFOSEL.word = s->D0FIFOSEL; + USB.D1FIFOSEL.word = s->D1FIFOSEL; - USB.DVSTCTR.word = ctx->DVSTCTR; - USB.TESTMODE.word = ctx->TESTMODE; - USB.REG_C2 = ctx->REG_C2; + USB.INTENB0.word = s->INTENB0; + USB.BRDYENB.word = s->BRDYENB; + USB.NRDYENB.word = s->NRDYENB; + USB.BEMPENB.word = s->BEMPENB; + USB.SOFCFG.word = s->SOFCFG; - USB.CFIFOSEL.word = ctx->CFIFOSEL; - USB.D0FIFOSEL.word = ctx->D0FIFOSEL; - USB.D1FIFOSEL.word = ctx->D1FIFOSEL; - - USB.INTENB0.word = ctx->INTENB0; - USB.BRDYENB.word = ctx->BRDYENB; - USB.NRDYENB.word = ctx->NRDYENB; - USB.BEMPENB.word = ctx->BEMPENB; - USB.SOFCFG.word = ctx->SOFCFG; - - USB.DCPCFG.word = ctx->DCPCFG; - USB.DCPMAXP.word = ctx->DCPMAXP; - USB.DCPCTR.word = ctx->DCPCTR; + USB.DCPCFG.word = s->DCPCFG; + USB.DCPMAXP.word = s->DCPMAXP; + USB.DCPCTR.word = s->DCPCTR; /* Clear remaining interrupts. Read-only bits will be ignored */ USB.INTSTS0.word = 0; @@ -360,21 +309,16 @@ static void ctx_restore(void *buf) USB.BEMPSTS.word = 0; /* Restore SYSCFG last as clearing SCKE disables writing */ - USB.SYSCFG.word = ctx->SYSCFG; - - if(!ctx->active) usb_module_stop(true); + USB.SYSCFG.word = s->SYSCFG; } -//--- -// Driver structure definition -//--- - gint_driver_t drv_usb = { .name = "USB", - .sys_ctx = &sys_ctx, - .gint_ctx = &gint_ctx, - .ctx_save = ctx_save, - .ctx_restore = ctx_restore, + .hpowered = hpowered, + .hpoweron = hpoweron, + .hpoweroff = hpoweroff, + .hsave = (void *)hsave, + .hrestore = (void *)hrestore, + .state_size = sizeof(usb_state_t), }; - -GINT_DECLARE_DRIVER(3, drv_usb); +GINT_DECLARE_DRIVER(16, drv_usb);