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 <gint/drivers.h>

* 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 <gint/drivers.h>
  and the driver's state structures in <gint/drivers/states.h>. 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 <gint/hardware.h>. 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.
This commit is contained in:
Lephe 2021-04-23 18:50:20 +02:00
parent 770b4e0117
commit c9264a06d5
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
34 changed files with 1264 additions and 1188 deletions

View file

@ -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

3
TODO
View file

@ -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

View file

@ -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:

View file

@ -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:

92
include/gint/cpu.h Normal file
View file

@ -0,0 +1,92 @@
//---
// gint:cpu - CPU registers and built-in functions
//---
#ifndef GINT_CPU
#define GINT_CPU
#include <gint/defs/types.h>
//---
// 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 */

View file

@ -5,9 +5,6 @@
#ifndef GINT_DMA
#define GINT_DMA
/* TODO: Enable DMA on fx-9860G */
#ifdef FXCG50
#include <gint/defs/types.h>
/* 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 */

View file

@ -8,87 +8,245 @@
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
/* 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(<OS world buffer>)
a5. hrestore(<gint world buffer>) 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(<gint world buffer>)
a4. hrestore(<OS world buffer>)
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 <gint/defs/attributes.h>. 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 */

View file

@ -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 <gint/mpu/dma.h>
/* 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 */

View file

@ -8,21 +8,33 @@
#include <gint/defs/types.h>
#include <gint/defs/call.h>
#include <gint/config.h>
#include <gint/intc.h>
/* 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 <src/core/inth.S> and <src/core/kernel.c>. 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

View file

@ -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 */

View file

@ -5,6 +5,8 @@
#ifndef GINT_INTC
#define GINT_INTC
#include <gint/defs/types.h>
//---
// 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 <src/kernel/inth.S> and <src/intc/intc.c>. 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 */

View file

@ -3,6 +3,7 @@
//---
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/hardware.h>
@ -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);

46
src/cpu/atomic.c Normal file
View file

@ -0,0 +1,46 @@
//---
// gint:cpu:atomic - Simulated atomic operations
//---
#include <gint/cpu.h>
/* 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);
}

67
src/cpu/cpu.c Normal file
View file

@ -0,0 +1,67 @@
//---
// gint:cpu - Driver for CPU built-in features
//---
#include <gint/cpu.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/hardware.h>
/* 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);

69
src/cpu/registers.s Normal file
View file

@ -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

View file

@ -2,15 +2,14 @@
#include <gint/mpu/dma.h>
#include <gint/mpu/power.h>
#include <gint/mpu/intc.h>
#include <gint/gint.h>
#include <gint/intc.h>
#include <gint/dma.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/exc.h>
#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);

View file

@ -1,8 +1,13 @@
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/hardware.h>
#include <gint/cpu.h>
#include <gint/mpu/intc.h>
#include <gint/std/string.h>
/* 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);

View file

@ -1,69 +0,0 @@
//---
// core:cpu - CPU registers and operation management
//---
#ifndef GINT_CORE_CPU
#define GINT_CORE_CPU
#include <gint/defs/types.h>
/* 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 */

View file

@ -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

View file

@ -1,23 +0,0 @@
//---
// core:drivers - Driver utilities for the kernel
//
// These are internal definitions; for general driver definitions, see
// <gint/drivers.h> instead.
//---
#ifndef GINT_CORE_DRIVERS
#define GINT_CORE_DRIVERS
#include <gint/drivers.h>
/* 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 */

View file

@ -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);

View file

@ -5,146 +5,57 @@
#include <gint/gint.h>
#include <gint/drivers.h>
#include <gint/std/string.h>
#include <gint/std/stdlib.h>
#include <gint/hardware.h>
#include <gint/mmu.h>
#include <gint/mpu/intc.h>
#include <gint/kmalloc.h>
#include <gint/cpu.h>
#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;
}

View file

@ -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));
}

134
src/kernel/world.c Normal file
View file

@ -0,0 +1,134 @@
#include <gint/drivers.h>
#include <gint/cpu.h>
#include <gint/gint.h>
#include <gint/defs/call.h>
#include <gint/std/stdlib.h>
//---
// 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));
}

View file

@ -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);

View file

@ -4,6 +4,7 @@
#include <gint/mmu.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/hardware.h>
//---
@ -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);

View file

@ -6,6 +6,7 @@
#include <gint/defs/util.h>
#include <gint/hardware.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/dma.h>
#include <gint/drivers/r61524.h>
@ -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);

View file

@ -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 <gint/display.h> */
uint32_t *gint_vram = fx_vram;

View file

@ -4,7 +4,7 @@
#include <gint/rtc.h>
#include <gint/drivers.h>
#include <gint/gint.h>
#include <gint/drivers/states.h>
#include <gint/intc.h>
#include <gint/defs/types.h>
@ -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);

View file

@ -2,6 +2,7 @@
#include <gint/mpu/cpg.h>
#include <gint/mpu/power.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/intc.h>
@ -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);

View file

@ -3,6 +3,7 @@
//---
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/drivers/t6k11.h>
#include <gint/defs/attributes.h>
@ -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);

View file

@ -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);
}

View file

@ -4,7 +4,7 @@
#include <gint/timer.h>
#include <gint/drivers.h>
#include <gint/gint.h>
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/intc.h>
#include <gint/mpu/tmu.h>
@ -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);

View file

@ -3,9 +3,9 @@
#include <gint/mpu/power.h>
#include <gint/mpu/cpg.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/intc.h>
#include <gint/gint.h>
#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);