saturnng/src/modules.c
2024-10-06 11:54:43 +02:00

1612 lines
51 KiB
C

/* -------------------------------------------------------------------------
saturn - A poor-man's emulator of some HP calculators
Copyright (C) 1998-2000 Ivan Cibrario Bertolotti
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the documentation of this program; if not, write to
the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
For more information, please contact the author, preferably by email,
at the following address:
Ivan Cibrario Bertolotti
IRITI - National Research Council
c/o IEN "Galileo Ferraris"
Strada delle Cacce, 91
10135 - Torino (ITALY)
email: cibrario@iriti.cnr.it
------------------------------------------------------------------------- */
/* +-+ */
/* .+
.identifier : $Id: modules.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $
.context : SATURN, Saturn CPU / HP48 emulator
.title : $RCSfile: modules.c,v $
.kind : C source
.author : Ivan Cibrario B.
.site : CSTV-CNR
.creation : 26-Jan-1998
.keywords : *
.description :
This module contains the peripheral module configuration and access
functions. All memory and i/o device accesses made by the Saturn CPU
are handled by this module. References:
SASM.DOC by HP (HORN disk 4)
Guide to the Saturn Processor Rev. 0.00f by Matthew Mastracci
entries.srt by Mika Heiskanen (mheiskan@vipunen.hut.fi)
x48 source code by Eddie C. Dost (ecd@dressler.de)
NOTE: Preliminary profiling information on Digital UNIX shows that,
without module config/unconfig cache (revision 1.1), while the
Saturn CPU is doing bank switching over 48% of the CPU time is
spent in RebuildPageTable(), with a peak of 60% when opening
the PLOT input form.
With the module config/unconfig cache enabled (revision 2.7),
RebuildPageTable() drops to the 45th place in the gprof's
listing of per-function cpu time.
Moreover, even during intensive calculations, over 30% of the CPU
time is spent in either FetchNibble() or RomRead() in either case.
.include : config.h, machdep.h, cpu.h, modules.h
.notes :
$Log: modules.c,v $
Revision 4.1 2000/12/11 09:54:19 cibrario
Public release.
Revision 3.10 2000/10/24 16:14:47 cibrario
Added/Replaced GPL header
Revision 3.5 2000/10/02 09:50:29 cibrario
Linux support:
- initialized 'winner' to an illegal value in RebuildPageTable(),
to track 'impossible' module configurations.
Revision 3.3 2000/09/26 15:16:31 cibrario
Revised to implement Flash ROM write access:
- in RebuildPageTable(), if the MOD_MAP_FLAGS_ABS is set in the
.map_flags field of a modules description, the page table rebuild
algorithm is modified to pass absolute (instead of relative
addresses to the module read/write functions.
* Revision 3.2 2000/09/22 13:55:33 cibrario
* Implemented preliminary support of HP49 hw architecture:
* - The module description table can now be registered dynamically; its
* definition has been moved into hw_config.c
* - New function ModRegisterDescription(), to register a module
* description table dynamically.
* - ModInit() now refuses to work if no module description table has
* been registered yet.
* - Conditionally (#ifdef HP49_SUPPORT) enabled forced alignment of
* module configuration sizes and addresses in ModConfig()
*
* Revision 3.1 2000/09/20 14:00:02 cibrario
* Minor updates and fixes to avoid gcc compiler warnings on Solaris
* when -ansi -pedantic -Wall options are selected.
*
* Revision 2.7 2000/09/19 11:11:08 cibrario
* Deeply revised to implement module config/unconfig cache.
*
* Revision 1.1 1998/02/18 08:16:35 cibrario
* Initial revision
*
.- */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "machdep.h"
#include "cpu.h"
#include "modules.h"
#include "disk_io.h"
#include "debug.h"
#define CHF_MODULE_ID MOD_CHF_MODULE_ID
#include "libChf/src/Chf.h"
/*---------------------------------------------------------------------------
Static/Global variables
---------------------------------------------------------------------------*/
struct ModStatus mod_status; /* Status information - global */
/* 2.7: Replaced the statically-allocated module mapping structure with a
pointer to a dynamically-allocated structure, to be able to switch
between different structures fast. The mod_map macro can be used to
refer to the current module mapping structure, and mod_map_ptr
points to it.
*/
static struct ModMap* mod_map_ptr; /* Module mapping information */
#define mod_map ( *mod_map_ptr )
/* 2.7: All dynamically-allocated module mapping structures are linked
together; cache_head points to the head of the list. The list
is used to flush the cache when necessary.
*/
static struct ModMap* cache_head = ( struct ModMap* )NULL;
/* 3.2: The ModDescription table is now configured invoking
ModRegisterDescription() before invoking any other function in this
module.
*/
static const struct ModDescriptionEntry* mod_description;
/*---------------------------------------------------------------------------
Debugging & performance analysis data
---------------------------------------------------------------------------*/
#ifdef DEBUG
static int alloc_c = 0; /* Counter of live AllocModMap() invocations */
static int flush_c = 0; /* Counter of FlushCache() invocations */
static int hit_c = 0; /* Cache hit counter */
static int lhit_c = 0; /* Cache late unconfig hit counter */
static int miss_c = 0; /* Cache miss (without replacement) counter */
static int repl_c = 0; /* Entry replacement counter */
# define IncPerfCtr( x ) x++
# define DecPerfCtr( x ) x--
# define PrintPerfCtr( x ) debug2( DEBUG_C_MOD_CACHE, MOD_I_PERF_CTR, #x, x )
# define PrintCacheStats \
{ \
PrintPerfCtr( alloc_c ); \
PrintPerfCtr( flush_c ); \
PrintPerfCtr( hit_c ); \
PrintPerfCtr( lhit_c ); \
PrintPerfCtr( miss_c ); \
PrintPerfCtr( repl_c ); \
}
#else
# define IncPerfCtr( x )
# define DecPerfCtr( x )
# define PrintPerfCtr( x )
# define PrintCacheStats
#endif
/*---------------------------------------------------------------------------
Private functions
---------------------------------------------------------------------------*/
/* .+
.title : BadRead
.kind : C function
.creation : 26-Jan-1998
.description :
This function is called when a read access is attempted for an unmapped
address.
It signals a warning and returns 0x0 to the caller. Since two consecutive
zeros represent a RTNSXM instruction, this is an easy way to detect when
a gosub transfers control to an unmapped address.
.call :
d = BadRead(addr);
.input :
Address addr, address
.output :
Nibble d, datum
.status_codes :
MOD_E_BAD_READ
.notes :
1.1, 26-Jan-1998, creation
.- */
static Nibble BadRead( Address addr )
{
ChfCondition( MOD_CHF_MODULE_ID ) MOD_E_BAD_READ, CHF_ERROR, addr ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
return ( Nibble )0x0;
}
/* .+
.title : BadWrite
.kind : C function
.creation : 26-Jan-1998
.description :
This function is called when a write access is attempted for an unmapped
address.
It signals a warning and does not execute the write.
.call :
BadWrite(addr, datum);
.input :
Address addr, address
Nibble datum, datum to be written
.output :
void
.status_codes :
MOD_E_BAD_WRITE
.notes :
1.1, 26-Jan-1998, creation
.- */
static void BadWrite( Address addr, Nibble datum )
{
ChfCondition( MOD_CHF_MODULE_ID ) MOD_E_BAD_WRITE, CHF_ERROR, addr, datum ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
/* .+
.title : RebuildPageTable
.kind : C function
.creation : 26-Jan-1998
.description :
This function rebuilds the module page table from page 'lo' to page 'hi',
inclusive, using the information contained in the current module
mapping structure (mod_map.map_info).
.call :
RebuildPageTable(lo, hi);
.input :
int lo, first page table entry to rebuild
int hi, last page table entry to rebuild
.output :
void
.status_codes :
MOD_I_CALLED
.notes :
1.1, 26-Jan-1998, creation
3.3, 25-Sep-2000, update
- implemented MOD_MAP_FLAGS_ABS flag in mod_description[].map_flags,
to allow module read/write functions to receive absolute addresses
instead of relative ones.
3.5, 2-Oct-2000, update
- initialized 'winner' to an illegal value to track 'impossible'
module configurations.
.- */
static void RebuildPageTable( int lo, int hi )
{
int page;
int mod;
Address page_addr;
int prio;
int winner = -1;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "RebuildPageTable" );
/* Scan all pages in the [lo, hi] range */
for ( page = lo; page <= hi; page++ ) {
/* Calculate the base page address for the current page */
page_addr = ModAddress( page );
/* Scan the module mapping information table, searching for the module
with the highest access priority that responds to the address
page_addr. If succesful, 'prio' contains the access priority of the
winner, and 'winner' contains its index, otherwise prio is set
to MOD_MIN_ACCESS_PRIO.
*/
prio = MOD_MIN_ACCESS_PRIO;
for ( mod = 0; mod < N_MOD; mod++ ) {
if ( mod_map.map_info[ mod ].config == MOD_CONFIGURED && page_addr >= mod_map.map_info[ mod ].abs_base_addr &&
page_addr < mod_map.map_info[ mod ].abs_base_addr + mod_map.map_info[ mod ].size &&
prio < mod_description[ mod ].access_prio ) {
winner = mod;
prio = mod_description[ mod ].access_prio;
}
}
if ( prio == MOD_MIN_ACCESS_PRIO ) {
/* The page is unmapped; set the module index to the special value
MOD_NO_MOD_INDEX, the relative base address to zero and the
read/write functions to BadRead/BadWrite, to catch accesses to
unmapped addresses.
*/
mod_map.page_table[ page ].index = MOD_NO_MOD_INDEX;
mod_map.page_table[ page ].rel_base_addr = 0x00000;
mod_map.page_table[ page ].read = BadRead;
mod_map.page_table[ page ].write = BadWrite;
} else {
/* The page is mapped
3.3: If the MOD_MAP_FLAGS_ABS is set in the winner's module
description, the base address of the page is set to its
absolute address; this way, the module read/write functions
will receive absolute addresses instead of relative ones.
*/
mod_map.page_table[ page ].index = winner;
mod_map.page_table[ page ].rel_base_addr = ( mod_description[ winner ].map_flags & MOD_MAP_FLAGS_ABS )
? page_addr
: page_addr - mod_map.map_info[ winner ].abs_base_addr;
mod_map.page_table[ page ].read = mod_description[ winner ].read;
mod_map.page_table[ page ].write = mod_description[ winner ].write;
}
}
}
/* .+
.title : ClearCachingInfo
.kind : C function
.creation : 15-Sep-2000
.description :
This function clears all caching information associated with the
struct ModMap pointed by its argument, and returns a pointer to
the same structure.
.call :
d = ClearCachingInfo(d);
.input :
struct ModMap *d, ptr to the structure to be wiped off
.output :
struct ModMap *d, ptr to affected structure
.status_codes :
MOD_I_CALLED
.notes :
2.7, 15-Sep-2000, creation
.- */
static struct ModMap* ClearCachingInfo( struct ModMap* d )
{
static const struct ModCacheTableEntry empty = { ( Address )0, ( struct ModMap* )NULL };
int i;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ClearCachingInfo" );
for ( i = 0; i < N_MOD_CACHE_ENTRIES; i++ )
d->cache.config[ i ] = empty;
d->cache.victim = 0;
for ( i = 0; i < N_MOD; i++ )
d->cache.unconfig[ i ] = ( struct ModMap* )NULL;
d->cache.config_point = 0;
d->cache.ref_count = 0;
return d;
}
/* .+
.title : NewModMap
.kind : C function
.creation : 15-Sep-2000
.description :
This function allocates a new struct ModMap, links it into the list of
cached ModMap , and returns a pointer to it; the function
signals a fatal condition if the allocation fails.
Notice that this function does not initialize the struct ModMap in any
way; in particular, it does not clear the caching information.
If DEBUG is appropriately enabled, this function prints out the
current value of all cache performance counters (PrintCacheStats).
.call :
p = NewModMap();
.input :
void
.output :
struct ModMap *p, pointer to the new struct ModMap
.status_codes :
MOD_I_CALLED
MOD_I_PERF_CTR, performance counter: %s value: %d
MOD_F_MAP_ALLOC, allocation of new map failed
.notes :
2.7, 15-Sep-2000, creation
.- */
static struct ModMap* NewModMap( void )
{
struct ModMap* new;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "NewModMap" );
if ( ( new = ( struct ModMap* )malloc( sizeof( struct ModMap ) ) ) == ( struct ModMap* )NULL ) {
ChfErrnoCondition;
ChfCondition( MOD_CHF_MODULE_ID ) MOD_F_MAP_ALLOC, CHF_FATAL ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
/* Link new structure to the cache list */
new->cache.link = cache_head;
cache_head = new;
IncPerfCtr( alloc_c );
PrintCacheStats;
return new;
}
/* .+
.title : CopyModMap
.kind : C function
.creation : 15-Sep-2000
.description :
This function copies the contents of a struct ModMap into another,
and clears the caching information of the destination structure.
Returns a pointer to the destination structure.
The linkage of the destination structure in the cached struct ModMap
list (.link field) is preserved.
.call :
d = CopyModMap(d, s);
.input :
const struct ModMap *s, ptr to source structure
.output :
struct ModMap *d, ptr to destination structure
.status_codes :
MOD_I_CALLED
.notes :
2.7, 15-Sep-2000, creation
.- */
static struct ModMap* CopyModMap( struct ModMap* d, const struct ModMap* s )
{
struct ModMap* link = d->cache.link; /* Save .link of dest. */
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "CopyModMap" );
*d = *s;
d->cache.link = link; /* Restore .link */
return ClearCachingInfo( d );
}
/* .+
.title : ReplaceModMap
.kind : C function
.creation : 15-Sep-2000
.description :
This function replaces the struct ModMap pointed by *d with a copy of
the struct ModMap pointed by s; if *d is currently a NULL pointer,
a new struct ModMap is dynamically allocated before doing the copy
and a pointer to the new structure is stored into *d, else the
existing struct ModMap is overwritten.
This function signals a fatal condition if the allocation of a new
struct ModMap is required, and fails.
This function always clears the caching information in the
destination structure.
.call :
ReplaceModMap(d, s);
.input :
struct ModMap **d, ptr to destination structure ptr
const struct ModMap *s, ptr to source structure
.output :
struct ModMap **d, ptr to destination structure ptr;
updated when original value of *d was NULL
.status_codes :
MOD_I_CALLED
MOD_I_PERF_CTR, performance counter: %s value: %d
MOD_F_MAP_ALLOC, allocation of new map failed
.notes :
2.7, 15-Sep-2000, creation
.- */
static void ReplaceModMap( struct ModMap** d, const struct ModMap* s )
{
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ReplaceModMap" );
if ( *d == ( struct ModMap* )NULL )
/* Allocation needed; cache cleared after allocation */
*d = CopyModMap( NewModMap(), s );
else {
CopyModMap( *d, s );
IncPerfCtr( repl_c );
}
}
/* .+
.title : FlushCache
.kind : C function
.creation : 15-Sep-2000
.description :
This function flushes the whole cache, freeing all structures previously
allocated by AllocModMap(), except that pointed by save. This function
also clears the caching information contained in save, because this
information is no longer valid after the flush.
.call :
FlushCache(save);
.input :
struct ModMap *save
.output :
void
.status_codes :
MOD_I_CALLED
MOD_F_BAD_ALLOC_C, bad alloc_c (%d) after FlushCache()
.notes :
2.7, 15-Sep-2000, creation
.- */
static void FlushCache( struct ModMap* save )
{
struct ModMap *p, *n;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "FlushCache" );
/* Scan the cache list; free all elements except that pointed by 'save' */
p = cache_head;
while ( p != ( struct ModMap* )NULL ) {
n = p->cache.link;
if ( p != save ) {
free( p );
DecPerfCtr( alloc_c );
}
p = n;
}
/* The cache list now contains only 'save' */
save->cache.link = ( struct ModMap* )NULL;
cache_head = save;
/* Clear the caching information in 'save' */
ClearCachingInfo( save );
IncPerfCtr( flush_c );
#ifdef DEBUG
/* The alloc_c performance counter must be exactly 1 now */
if ( alloc_c != 1 ) {
ChfCondition( MOD_CHF_MODULE_ID ) MOD_F_BAD_ALLOC_C, CHF_ERROR, alloc_c ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
#endif
}
/* .+
.title : AccessConfigCache
.kind : C function
.creation : 15-Sep-2000
.description :
This function checks if there is an entry in the module configuration cache
associated with the current struct ModMap with tag 'tag';
if this is the case, it returns a pointer to the cached struct ModMap
just found, otherwise it returns NULL (cache miss).
.call :
p = AccessConfigCache(tag);
.input :
Address tag, cache tag
.output :
struct ModMap *p, cached pointer, or NULL
.status_codes :
MOD_I_CALLED
.notes :
2.7, 15-Sep-2000, creation
.- */
static struct ModMap* AccessConfigCache( Address tag )
{
int i;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "AccessConfigCache" );
for ( i = 0; i < N_MOD_CACHE_ENTRIES; i++ )
if ( mod_map.cache.config[ i ].tag == tag )
return mod_map.cache.config[ i ].map_ptr;
return ( struct ModMap* )NULL;
}
/* .+
.title : AccessUnconfigCache
.kind : C function
.creation : 15-Sep-2000
.description :
This function follows the .unconfig cache links with index i, starting
from the current struct ModMap, until it finds a struct ModMap
whose .cache.config_point is set, or stumbles into a NULL pointer.
When successful, this function returns a pointer to the cached
struct ModMap just found, otherwise it returns NULL (cache miss).
.call :
p = AccessUnconfigCache(i);
.input :
int i, unconfig cache index (unconfigured module index)
.output :
struct ModMap *p, cached pointer, or NULL
.status_codes :
MOD_I_CALLED
.notes :
2.7, 15-Sep-2000, creation
.- */
static struct ModMap* AccessUnconfigCache( int i )
{
struct ModMap* p;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "AccessUnconfigCache" );
p = mod_map.cache.unconfig[ i ];
while ( p != ( struct ModMap* )NULL && !p->cache.config_point )
p = p->cache.unconfig[ i ];
return p;
}
/* .+
.title : SelectConfigVictim
.kind : C function
.creation : 19-Sep-2000
.description :
This function selects a victim entry in the module configuration
cache table of the current struct ModMap, updates the victim selection
info associated with the current map, and returns a pointer to the
victim entry.
If the search fails and the 'retry' argument is non-zero, this
function signals a warning (MOD_W_NO_VICTIM), flushes the whole cache
(by means of FlushCache()), and retries the search. If even the
second attempt fails, it signals a fatal condition (MOD_F_NO_VICTIM).
If the search fails and the 'retry' argument is zero, this
function immediately signals the fatal condition MOD_F_NO_VICTIM.
.call :
victim = SelectConfigVictim();
.input :
void
.output :
struct ModCacheTableEntry *victim, pointer to victim entry
.status_codes :
MOD_I_CALLED
MOD_W_NO_VICTIM
MOD_F_NO_VICTIM
MOD_F_BAD_ALLOC_C, bad alloc_c (%d) after FlushCache()
.notes :
2.7, 15-Sep-2000, creation
.- */
struct ModCacheTableEntry* SelectConfigVictim( int retry )
{
int v = mod_map.cache.victim;
struct ModCacheTableEntry* victim = ( struct ModCacheTableEntry* )NULL;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "AccessUnconfigCache" );
/* Scan the config cache entries, starting at .cache.victim,
until a suitable one is found or the index loops around
*/
do {
/* A config cache entry is suitable for use if:
- it is empty (map_ptr == NULL)
- or the reference count of the associated map is 0
*/
if ( ( mod_map.cache.config[ v ].map_ptr == ( struct ModMap* )NULL ) || mod_map.cache.config[ v ].map_ptr->cache.ref_count == 0 )
victim = &( mod_map.cache.config[ v ] );
v = ( v + 1 ) % N_MOD_CACHE_ENTRIES;
} while ( victim == ( struct ModCacheTableEntry* )NULL && v != mod_map.cache.victim );
if ( victim == ( struct ModCacheTableEntry* )NULL ) {
if ( retry ) {
/* Unable to find a victim; flush the cache and retry */
ChfCondition( MOD_CHF_MODULE_ID ) MOD_W_NO_VICTIM, CHF_WARNING ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
FlushCache( mod_map_ptr );
victim = SelectConfigVictim( 0 );
} else {
/* Unable to find a victim; retry is not an option; give up */
ChfCondition( MOD_CHF_MODULE_ID ) MOD_F_NO_VICTIM, CHF_FATAL ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
} else
/* Found a victim; update next-victim index */
mod_map.cache.victim = v;
return victim;
}
/* .+
.title : CheckForLateHit
.kind : C function
.creation : 19-Sep-2000
.description :
This function checks if there is in the cache a struct ModMap
containing the same module configuration information (.map_info field)
as the current one.
If it founds a matching structure, this function returns a pointer
to it, otherwise it returns NULL.
This function should be used after execution of an unconfig instruction
that encountered an early cache miss.
.call :
p = CheckForLateHit();
.input :
void
.output :
struct ModMap *p, cached pointer, or NULL
.status_codes :
MOD_I_CALLED
.notes :
2.7, 19-Sep-2000, creation
.- */
static struct ModMap* CheckForLateHit( void )
{
struct ModMap* p;
int i;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "AccessUnconfigCache" );
p = cache_head;
/* Scan the cache to find an entry with the same modules configuration
as the current one; return a pointer to it if successful
*/
while ( p != ( struct ModMap* )NULL ) {
/* Don't attempt to match an entry against itself */
if ( p != mod_map_ptr ) {
/* only .map_info contents must match */
for ( i = 0; i < N_MOD; i++ ) {
/* Break the for if a difference was found */
if ( ( mod_map_ptr->map_info[ i ].config != p->map_info[ i ].config ) ||
( mod_map_ptr->map_info[ i ].abs_base_addr != p->map_info[ i ].abs_base_addr ) ||
( mod_map_ptr->map_info[ i ].size != p->map_info[ i ].size ) )
break;
}
/* Break the while if we found a match ('for' was not broken) */
if ( i == N_MOD )
break;
}
/* Go to the next cache entry */
p = p->cache.link;
}
return p;
}
/* .+
.title : FreeModMap
.kind : C function
.creation : 19-Sep-2000
.description :
This function frees the cached struct ModMap pointed by p, preserving
cache list linkage.
It is responsibility of the caller to ensure that the structure is no
longer referenced.
.call :
p = CheckForLateHit();
.input :
void
.output :
struct ModMap *p, cached pointer, or NULL
.status_codes :
MOD_I_CALLED
MOD_F_CHAIN_CORRUPTED
.notes :
2.7, 15-Sep-2000, creation
.- */
static void FreeModMap( struct ModMap* p )
{
struct ModMap* n;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "FreeModMap" );
/* Free the struct ModMap pointed by p, preserving the linkage of
other entries. The caller must ensure that the entry is not
referenced by any other entry through cache pointers.
*/
if ( p == cache_head ) {
/* Free the list head */
cache_head = p->cache.link;
free( p );
DecPerfCtr( alloc_c );
} else {
/* Scan the cache; at end, n is either null (!) or points to the
cache entry that immediately precedes p
*/
n = cache_head;
while ( ( n != ( struct ModMap* )NULL ) && n->cache.link != p )
n = n->cache.link;
/* Should never happen */
if ( n == ( struct ModMap* )NULL ) {
ChfCondition( MOD_CHF_MODULE_ID ) MOD_F_CHAIN_CORRUPTED, CHF_FATAL ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
/* Bypass element pointed by p and free it */
n->cache.link = p->cache.link;
free( p );
DecPerfCtr( alloc_c );
}
}
/*---------------------------------------------------------------------------
Public functions
---------------------------------------------------------------------------*/
/* .+
.title : ModRegisterDescription
.kind : C function
.creation : 21-Sep-2000
.description :
This function registers the ModDescription table pointed by 'p'; all
other module emulation functions will refer to that table in the future.
It is mandatory to invoke this function with a valid ModDescription
table pointer as argument *before* using any other module emulation
function, either directly or indirectly.
All error conditions are signalled using the Chf facility; the function
returns 'void' to the caller.
.call :
ModRegisterDescription(p);
.input :
ModDescription p, module description table to be registered
.output :
void
.status_codes :
MOD_I_CALLED
.notes :
3.2, 21-Sep-2000, creation
.- */
void ModRegisterDescription( ModDescription p )
{
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModRegisterDescription" );
mod_description = p;
}
/* .+
.title : ModInit
.kind : C function
.creation : 23-Jan-1998
.description :
This function initializes all peripheral modules, calling its initialization
entry point; it must be called exactly once during startup. Then, it
initializes the modules mapping information either reading it from file or
resetting all modules.
All error conditions are signalled using the Chf facility; the function
returns 'void' to the caller.
.call :
ModInit();
.input :
void
.output :
void
.status_codes :
MOD_I_CALLED
MOD_I_REVISION
MOD_I_INITIALIZING
MOD_I_PERF_CTR, performance counter: %s value: %d
MOD_W_RESETTING_ALL
MOD_F_MAP_ALLOC
MOD_F_NO_DESCRIPTION
NOTE: This function can also (indirectly) report any condition
code generated and/or signalled by the module initialization
functions.
.notes :
1.1, 23-Jan-1998, creation
2.7, 15-Sep-2000, update
- revised to implement module config/unconfig cache
3.2, 21-Sep-2000, update
- added sanity check on mod_description
.- */
void ModInit( void )
{
int mod;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModInit" );
debug1( DEBUG_C_REVISION, MOD_I_REVISION, MOD_RCS_INFO );
/* First, a little sanity check on mod_description: ensure that
ModRegisterDescription() has been called at least once with a
non-NULL argument.
*/
if ( mod_description == NULL ) {
ChfCondition( MOD_CHF_MODULE_ID ) MOD_F_NO_DESCRIPTION, CHF_FATAL ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
/* Scan the mod_description table, initializing all modules */
for ( mod = 0; mod < N_MOD; mod++ ) {
debug1( DEBUG_C_MODULES, MOD_I_INITIALIZING, mod_description[ mod ].name );
mod_description[ mod ].init();
}
/* Allocate the root struct ModMap and set it as the current one;
the structure can be accessed using either mod_map_ptr or mod_map.
*/
mod_map_ptr = ClearCachingInfo( NewModMap() );
/* Attempt to restore the mod_map from file; reset modules if the read
fails.
*/
if ( ReadStructFromFile( config.mod_file_name, sizeof( mod_map.map_info ), &mod_map.map_info ) ) {
ChfCondition( MOD_CHF_MODULE_ID ) MOD_W_RESETTING_ALL, CHF_WARNING ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
/* Reset all modules */
ModReset();
} else {
/* Rebuild page table (not saved on disk) */
RebuildPageTable( 0, N_PAGE_TABLE_ENTRIES - 1 );
}
}
/* .+
.title : ModSave
.kind : C function
.creation : 11-Feb-1998
.description :
This function saves the status of all peripheral modules, calling its
'save' entry point. Then, it saves the modules mapping information, too.
All error conditions are signalled using the Chf facility; the function
returns 'void' to the caller.
.call :
ModSave();
.input :
void
.output :
void
.status_codes :
MOD_I_CALLED
MOD_I_SAVING
MOD_W_RESETTING
NOTE: This function can also (indirectly) report any condition
code generated and/or signalled by the module initialization
functions.
.notes :
1.1, 11-Feb-1998, creation
.- */
void ModSave( void )
{
int mod;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModSave" );
/* Scan the mod_description table, initializing all modules */
for ( mod = 0; mod < N_MOD; mod++ ) {
debug1( DEBUG_C_MODULES, MOD_I_SAVING, mod_description[ mod ].name );
mod_description[ mod ].save();
}
/* Attempt to save the mod_map from file */
if ( WriteStructToFile( &mod_map.map_info, sizeof( mod_map.map_info ), config.mod_file_name ) ) {
ChfCondition( MOD_CHF_MODULE_ID ) MOD_F_MAP_SAVE, CHF_FATAL ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
}
}
/* .+
.title : ModGetID
.kind : C function
.creation : 26-Jan-1998
.description :
This function returns the ID of the next module to be configured and the
last module configuration address. Returns 0x00000 if all modules are
configured.
Module ID structure
Bits Meaning
19..8 Three most-significant nibbles of the last configuration
address specified for this module.
7..3 F: The next CONFIG data will specify a module base address
0: The next CONFIG data will specify a module size
3..0 Module ID data
If the size of the module has already been configured, the
base ID of the module is increased by one.
.call :
id = ModGetId();
.input :
void
.output :
Address id, ID of the next module to be configured.
.status_codes :
MOD_I_CALLED
MOD_I_GET_ID
.notes :
1.1, 26-Jan-1998, creation
.- */
Address ModGetID( void )
{
int mod;
Address id;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModGetID" );
/* Scan the module information table searching for either an unconfigured
or a partially configured module
*/
for ( mod = 0; mod < N_MOD && mod_map.map_info[ mod ].config == MOD_CONFIGURED; mod++ )
;
if ( mod == N_MOD )
/* All modules are configured */
id = ( Address )0x00000;
else
/* Build the module id */
id = ( mod_map.map_info[ mod ].abs_base_addr & 0xFFF00 ) |
( mod_map.map_info[ mod ].config == MOD_UNCONFIGURED ? 0x00000 : 0x000F0 ) |
( mod_description[ mod ].id + ( mod_map.map_info[ mod ].config == MOD_UNCONFIGURED ? 0 : 1 ) );
debug1( DEBUG_C_MODULES, MOD_I_GET_ID, id );
return id;
}
/* .+
.title : ModReset
.kind : C function
.creation : 26-Jan-1998
.description :
This function resets all peripheral modules and rebuilds the module page
table used for module access.
.call :
ModReset();
.input :
void
.output :
void
.status_codes :
MOD_I_CALLED
MOD_I_RESETTING
MOD_F_BAD_ALLOC_C, bad alloc_c (%d) after FlushCache()
.notes :
1.1, 26-Jan-1998, creation
2.7, 15-Sep-2000, update
- revised to implement module config/unconfig cache
.- */
void ModReset( void )
{
int mod;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModReset" );
/* Scan the mod_description table, initializing the module
mapping information mod_map.map_info.
*/
for ( mod = 0; mod < N_MOD; mod++ ) {
debug1( DEBUG_C_MODULES, MOD_I_RESETTING, mod_description[ mod ].name );
/* Set the module configuration status */
mod_map.map_info[ mod ].config = mod_description[ mod ].r_config;
mod_map.map_info[ mod ].abs_base_addr = mod_description[ mod ].r_abs_base_addr;
mod_map.map_info[ mod ].size = mod_description[ mod ].r_size;
}
/* Rebuild the module page table */
RebuildPageTable( 0, N_PAGE_TABLE_ENTRIES - 1 );
/* Flush the whole struct ModMap cache, preserving the current map */
FlushCache( mod_map_ptr );
/* Mark the current struct ModMap to be a configuration point;
this flag is used by the unconfig cache code to correctly
undo the last config
*/
mod_map.cache.config_point = 1;
}
/* .+
.title : ModConfig
.kind : C function
.creation : 26-Jan-1998
.description :
This function configures a module, using the given 'config_info'.
The target module will be the first unconfigured or partially configured
module found in the mod_map.map_info table.
If the target module is unconfigured, ModConfig sets the size of its
address space to 0x100000 - 'config_info'; the module then becomes
partially configured.
If the target module is already partially configured, ModConfig sets
its base address to 'config_info', completing the configuration process.
In the latter case, ModConfig rebuilds the page table used for module access
to reflect the visibility of the new module in the CPU address space.
.call :
void ModConfig(config_info);
.input :
Address config_info, configuration information
.output :
void
.status_codes :
MOD_I_CALLED
MOD_I_CONFIG
MOD_I_CACHED_CONFIG
MOD_I_PERF_CTR, performance counter: %s value: %d
MOD_W_BAD_CONFIG
MOD_W_NO_VICTIM
MOD_F_MAP_ALLOC
MOD_F_NO_VICTIM
.notes :
1.1, 26-Jan-1998, creation
2.7, 15-Sep-2000, update
- implemented module config/unconfig cache
3.2, 22-Sep-2000, update
- conditionally (#ifdef HP49_SUPPORT) enabled forced alignment
of config_info
.- */
void ModConfig( Address config_info )
{
struct ModMap *old, *nxt;
struct ModCacheTableEntry* victim;
int mod;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModConfig" );
#ifdef HP49_SUPPORT
/* 3.2: The HP49 firmware (1.19-4) can generate misaligned config
addresses, that is, addresses that are not a multiple of 0x100;
silently align them here.
*/
config_info &= ~0xFF;
#endif
/* ACCESS CONFIG CACHE */
if ( ( nxt = AccessConfigCache( config_info ) ) != ( struct ModMap* )NULL ) {
/* CACHE HIT; switch mod_map_ptr */
mod_map_ptr = nxt;
IncPerfCtr( hit_c );
debug1( DEBUG_C_MOD_CACHE, MOD_I_CACHED_CONFIG, config_info );
return;
}
/* CACHE MISS */
IncPerfCtr( miss_c );
/* Select a 'victim' cache table entry and update victim
selection info; retry after flushing the cache if necessary.
Initialize victim's tag to current config_info.
Initialize victim's map_ptr; this can be either a new
allocation if the pointer was NULL, or a replacement.
This clears the caching information of the map_ptr's pointee.
Switch mod_map_ptr to the new structure
The unconfig cache pointer of the new structure will be set when
the index of the module being configured will be known.
*/
victim = SelectConfigVictim( 1 );
victim->tag = config_info;
ReplaceModMap( &( victim->map_ptr ), mod_map_ptr );
old = mod_map_ptr;
mod_map_ptr = victim->map_ptr;
/* Scan the module information table searching for either an unconfigured
or a partially configured module
*/
for ( mod = 0; mod < N_MOD && mod_map.map_info[ mod ].config == MOD_CONFIGURED; mod++ )
;
if ( mod == N_MOD ) {
/* All modules are configured - Signal a warning */
// FIXME: 48gx bugs here when running VERSION
ChfCondition( MOD_CHF_MODULE_ID ) MOD_W_BAD_CONFIG, CHF_WARNING, config_info ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
} else {
if ( mod_map.map_info[ mod ].config == MOD_UNCONFIGURED ) {
/* The module was unconfigured; configure its size */
mod_map.map_info[ mod ].size = 0x100000 - config_info;
mod_map.map_info[ mod ].config = MOD_SIZE_CONFIGURED;
} else {
/* The module size was already configured; configure its base address */
mod_map.map_info[ mod ].abs_base_addr = config_info;
mod_map.map_info[ mod ].config = MOD_CONFIGURED;
/* Rebuild the page table */
RebuildPageTable( ModPage( mod_map.map_info[ mod ].abs_base_addr ),
ModPage( mod_map.map_info[ mod ].abs_base_addr + mod_map.map_info[ mod ].size - 1 ) );
/* Mark the current struct ModMap to be a configuration point;
this flag is used by the unconfig cache code to correctly
undo the last config
*/
mod_map.cache.config_point = 1;
debug3( DEBUG_C_MODULES | DEBUG_C_MOD_CACHE, MOD_I_CONFIG, mod_description[ mod ].name, mod_map.map_info[ mod ].abs_base_addr,
mod_map.map_info[ mod ].size );
}
/* Set the unconfig cache pointer of module 'mod' to the old ModMap,
and increment its reference counter, to avoid freeing it
improperly.
*/
mod_map.cache.unconfig[ mod ] = old;
old->cache.ref_count++;
}
}
/* .+
.title : ModUnconfig
.kind : C function
.creation : 26-Jan-1998
.description :
This function unconfigures the module currently configured at address
'unconfig_info' and returns it to its after-reset configuration status.
ModUnconfig also rebuilds the page table used for module access
to reflect the loss of visibility of the module in the CPU address space.
.call :
ModUnconfig(unconfig_info);
.input :
Address unconfig_info, Unconfig information
.output :
void
.status_codes :
MOD_I_CALLED
MOD_I_UNCONFIG
MOD_I_CACHED_UNCONFIG
MOD_I_PERF_CTR, performance counter: %s value: %d
MOD_W_BAD_UNCONFIG
MOD_F_MAP_ALLOC
MOD_F_CHAIN_CORRUPTED
.notes :
1.1, 26-Jan-1998, creation
2.7, 15-Sep-2000, update
- implemented module config/unconfig cache
.- */
void ModUnconfig( Address unconfig_info )
{
struct ModMap *nxt, *old;
int mod;
debug1( DEBUG_C_TRACE, MOD_I_CALLED, "ModUnconfig" );
/* Determine the module to unconfigure */
if ( ( mod = mod_map.page_table[ ModPage( unconfig_info ) ].index ) == MOD_NO_MOD_INDEX ) {
/* There isn't any module configured at the given address -
Signal a warning
*/
ChfCondition( MOD_CHF_MODULE_ID ) MOD_W_BAD_UNCONFIG, CHF_WARNING, unconfig_info ChfEnd;
ChfSignal( MOD_CHF_MODULE_ID );
} else if ( mod_description[ mod ].r_config == MOD_CONFIGURED ) {
/* The module is automatically configured after reset; it can never
be unconfigured.
*/
} else {
/* Unconfiguring module 'mod': ACCESS UNCONFIG CACHE */
if ( ( nxt = AccessUnconfigCache( mod ) ) != ( struct ModMap* )NULL ) {
/* CACHE HIT; switch mod_map_ptr */
mod_map_ptr = nxt;
IncPerfCtr( hit_c );
debug0( DEBUG_C_MOD_CACHE, MOD_I_CACHED_UNCONFIG );
return;
}
/* CACHE MISS
A clone of the current struct ModMap is allocated and updated
according to the unconfig instruction being executed.
Then, CheckForLateHit() is called to check whether in the
module mapping cache there is a struct ModMap identical to
the updated one.
- If there is, the .unconfig[i] link is updated to point to
the cache entry just found.
- If there is not, the whole cache is flushed and all cached
ModMap structures allocated so far are freed, except the
current one. I hope this occurrence is rare.
*/
/* Save pointer to the old map and switch to a temporary one */
old = mod_map_ptr;
mod_map_ptr = CopyModMap( NewModMap(), mod_map_ptr );
/* Update the mapping information table */
mod_map.map_info[ mod ].config = mod_description[ mod ].r_config;
/* Rebuild the page table */
RebuildPageTable( ModPage( mod_map.map_info[ mod ].abs_base_addr ),
ModPage( mod_map.map_info[ mod ].abs_base_addr + mod_map.map_info[ mod ].size - 1 ) );
/* Reset the module configuration status; the abs_base_addr of the module
is not reset because its old value is still needed by ModGetId()
The size is reset for the modules that are already MOD_SIZE_CONFIGURED
immediately after reset.
*/
mod_map.map_info[ mod ].size = mod_description[ mod ].r_size;
if ( ( nxt = CheckForLateHit() ) != ( struct ModMap* )NULL ) {
/* Update pointer from the old map to the new one, and increment
reference counter of the referenced structure
*/
old->cache.unconfig[ mod ] = nxt;
nxt->cache.ref_count++;
/* Discard the temporary map and switch to the cached one */
FreeModMap( mod_map_ptr );
mod_map_ptr = nxt;
IncPerfCtr( lhit_c );
debug0( DEBUG_C_MOD_CACHE, MOD_I_UNCONFIG_L_HIT );
} else {
/* Continue to use the new map with no caching information,
and hope that further configuration activities will link it
back in the immediate future.
*/
/* Mark the current struct ModMap to be a configuration point;
this flag is used by the unconfig cache code to correctly
undo the last config
*/
mod_map.cache.config_point = 1;
IncPerfCtr( miss_c );
debug0( DEBUG_C_MOD_CACHE, MOD_I_UNCONFIG_L_MISS );
debug3( DEBUG_C_MODULES | DEBUG_C_MOD_CACHE, MOD_I_UNCONFIG, mod_description[ mod ].name, mod_map.map_info[ mod ].abs_base_addr,
mod_map.map_info[ mod ].size );
}
}
}
/* .+
.title : FetchNibble
.kind : C function
.creation : 26-Jan-1998
.description :
This function fetches a nibble from the address 'addr' and returns it.
NOTE: This function DOES NOT update the hardware CRC register.
.call :
d = FetchNibble(addr);
.input :
Address addr, address
.output :
Nibble *d, datum
.status_codes :
NOTE: This function indirectly reports and condition generated
and/or signalled by the module read function.
.notes :
1.1, 26-Jan-1998, creation
.- */
Nibble FetchNibble( Address addr )
{
register int page = ModPage( addr );
return mod_map.page_table[ page ].read( mod_map.page_table[ page ].rel_base_addr | ModOffset( addr ) );
}
/* .+
.title : ReadNibble
.kind : C function
.creation : 26-Jan-1998
.description :
This function reads a nibble from the address 'addr' and returns it.
NOTE: This function updates the hardware CRC register if the target of the
read operation is not the HDW module. The current (1.1) implementation
of this feature is inefficient because the .index field of the
addressed page must be checked against MOD_HDW_INDEX for each
access.
.call :
d = ReadNibble(addr);
.input :
Address addr, address
.output :
Nibble *d, datum
.status_codes :
NOTE: This function indirectly reports and condition generated
and/or signalled by the module read function.
.notes :
1.1, 26-Jan-1998, creation
.- */
Nibble ReadNibble( Address addr )
{
register int page = ModPage( addr );
register Nibble d;
/* Read the nibble from the peripheral module */
d = mod_map.page_table[ page ].read( mod_map.page_table[ page ].rel_base_addr | ModOffset( addr ) );
/* Update the crc register, if appropriate */
if ( mod_map.page_table[ page ].index != MOD_HDW_INDEX )
mod_status.hdw.crc = ( mod_status.hdw.crc >> 4 ) ^ ( ( ( mod_status.hdw.crc ^ d ) & 0x0F ) * 0x1081 );
/* Return to the caller */
return d;
}
/* .+
.title : WriteNibble
.kind : C function
.creation : 26-Jan-1998
.description :
This function writes the nibble 'datum' to the address 'addr'
NOTE: This function DOES NOT update the hardware CRC register.
.call :
WriteNibble(addr, datum);
.input :
Address addr, destination address
Nibble datum, nibble to be written
.output :
void
.status_codes :
NOTE: This function indirectly reports and condition generated
and/or signalled by the module write function.
.notes :
1.1, 26-Jan-1998, creation
.- */
void WriteNibble( Address addr, Nibble datum )
{
register int page = ModPage( addr );
mod_map.page_table[ page ].write( mod_map.page_table[ page ].rel_base_addr | ModOffset( addr ), datum );
}
/*---------------------------------------------------------------------------
Monitor functions
---------------------------------------------------------------------------*/
/* .+
.title : ModMapCheck
.kind : C function
.creation : 26-Jan-1998
.description :
This function fills the string 'ob' with the current mapping information
for address 'addr'; it is used by the emulator monitor only.
.call :
ModMapCheck(addr, char ob[MOD_MAP_CHECK_OB_SIZE]);
.input :
Address addr;
.output :
void
.status_codes :
*
.notes :
1.1, 26-Jan-1998, creation
.- */
void ModMapCheck( Address addr, char ob[ MOD_MAP_CHECK_OB_SIZE ] )
{
int page;
Address offset;
int mod;
page = ModPage( addr );
offset = ModOffset( addr );
if ( ( mod = mod_map.page_table[ page ].index ) == MOD_NO_MOD_INDEX )
sprintf( ob, ChfGetMessage( CHF_MODULE_ID, MOD_M_NOT_MAPPED, "" ), addr );
else {
Address rel_addr;
rel_addr = mod_map.page_table[ page ].rel_base_addr | offset;
sprintf( ob, ChfGetMessage( CHF_MODULE_ID, MOD_M_MAPPED, "" ), addr, mod_description[ mod ].name, rel_addr );
}
ChfSignal( MOD_CHF_MODULE_ID );
}
/* .+
.title : ModMapTable
.kind : C function
.creation : 26-Jan-1998
.description :
This function fills the string 'ob' with the current mapping table for
all modules; it is used by the emulator monitor only.
.call :
ModMapTable(char ob[MOD_MAP_TABLE_OB_SIZE]);
.input :
Address addr;
.output :
void
.status_codes :
*
.notes :
1.1, 26-Jan-1998, creation
.- */
void ModMapTable( char ob[ MOD_MAP_TABLE_OB_SIZE ] )
{
int mod;
sprintf( ob, "%s\n", ChfGetMessage( CHF_MODULE_ID, MOD_M_MAP_TABLE_TITLE, "" ) );
ob += strlen( ob );
for ( mod = 0; mod < N_MOD; mod++ ) {
sprintf( ob, ChfGetMessage( CHF_MODULE_ID, MOD_M_MAP_TABLE_ROW, "" ), mod_description[ mod ].name,
mod_map.map_info[ mod ].abs_base_addr, mod_map.map_info[ mod ].size,
mod_map.map_info[ mod ].config == MOD_CONFIGURED ? ChfGetMessage( CHF_MODULE_ID, MOD_M_MAP_CONFIGURED, "C" )
: ( mod_map.map_info[ mod ].config == MOD_SIZE_CONFIGURED
? ChfGetMessage( CHF_MODULE_ID, MOD_M_MAP_SZ_CONFIGURED, "S" )
: ChfGetMessage( CHF_MODULE_ID, MOD_M_MAP_UNCONFIGURED, "U" ) ) );
strcat( ob, "\n" );
ob += strlen( ob );
}
}