514 lines
14 KiB
C
514 lines
14 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: x_func.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $
|
|
.context : SATURN, Saturn CPU / HPxx emulator
|
|
.title : $RCSfile: x_func.c,v $
|
|
.kind : C source
|
|
.author : Ivan Cibrario B.
|
|
.site : CSTV-CNR
|
|
.creation : 3-Nov-2000
|
|
.keywords : *
|
|
.description :
|
|
This module implements the emulator's extended functions, that is,
|
|
functions that the real machine has not.
|
|
|
|
References:
|
|
|
|
Private communications with Prof. B. Parisse
|
|
|
|
.include : config.h, machdep.h, cpu.h, x_func.h
|
|
|
|
.notes :
|
|
$Log: x_func.c,v $
|
|
Revision 4.1 2000/12/11 09:54:19 cibrario
|
|
Public release.
|
|
|
|
Revision 3.15 2000/11/15 14:16:45 cibrario
|
|
GUI enhancements and assorted bug fixes:
|
|
- Implemented command-line option -batchXfer
|
|
|
|
Revision 3.14 2000/11/13 11:11:01 cibrario
|
|
Implemented fast load/save; improved keyboard interface emulation at
|
|
high emulated CPU speed:
|
|
|
|
- Added credits in file doc
|
|
- Implemented CPU status access functions ByteFromAddress(),
|
|
NameFromD1()
|
|
- Implemented new static function BinaryHeader(), to select an header
|
|
of binary files appropriate for the current hw configuration
|
|
- Implemented new static functions Kget()/KgetContinuation(), to load
|
|
a file from disk into the calculator, and Send/SendContinuation(),
|
|
to save a file from the calculator's memory into a disk file.
|
|
- Removed test functions TestFSB() and TestFSBContinuation()
|
|
- Implemented static helper function SetupXfer(), to setup a disk
|
|
transfer.
|
|
- Updated static function[] table
|
|
|
|
Revision 3.13 2000/11/09 11:42:22 cibrario
|
|
*** empty log message ***
|
|
|
|
|
|
.- */
|
|
|
|
#ifndef lint
|
|
static char rcs_id[] = "$Id: x_func.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $";
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <setjmp.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#include <X11/Xlib.h> /* Main X header */
|
|
#include <X11/Intrinsic.h> /* Main Xt header */
|
|
|
|
#include "config.h"
|
|
#include "machdep.h"
|
|
#include "cpu.h"
|
|
#include "modules.h"
|
|
#include "disk_io.h"
|
|
#include "x11.h" /* ActivateFSB() */
|
|
#include "x_func.h"
|
|
#include "args.h"
|
|
#include "debug.h"
|
|
|
|
#define CHF_MODULE_ID X_FUNC_CHF_MODULE_ID
|
|
#include <Chf.h>
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Private functions: CPU access
|
|
---------------------------------------------------------------------------*/
|
|
|
|
/* Return the A field of a DataRegister as an integer. */
|
|
static int R2int(const Nibble *r)
|
|
{
|
|
return(
|
|
((int)r[0] ) |
|
|
((int)r[1] << 4) |
|
|
((int)r[2] << 8) |
|
|
((int)r[3] << 12) |
|
|
((int)r[4] << 16)
|
|
);
|
|
}
|
|
|
|
|
|
/* Return the contents of the byte pointed by addr.
|
|
Memory is accessed through ReadNibble()
|
|
*/
|
|
static int ByteFromAddress(Address addr)
|
|
{
|
|
return (int)ReadNibble(addr) + (int)ReadNibble(addr+1) * 16;
|
|
}
|
|
|
|
|
|
/* Return a dynamically-allocated copy of the contents of the IDNT
|
|
object pointed by D1. D1 points to the *body* of the
|
|
RPL object, that is, to the IDNT length byte directly and *not*
|
|
to the prologue.
|
|
*/
|
|
static char *NameFromD1(void)
|
|
{
|
|
Address addr = cpu_status.D1; /* Points to the IDNT body */
|
|
int len = ByteFromAddress(addr); /* IDNT length */
|
|
char *name = XtMalloc(len+1); /* IDNT name buffer */
|
|
int c;
|
|
|
|
/* Read the name; toascii() is there to avoid 'strange' characters */
|
|
for(c=0; c<len; c++)
|
|
{
|
|
addr += 2;
|
|
name[c] = (char)toascii(ByteFromAddress(addr));
|
|
}
|
|
|
|
name[c] = '\0'; /* Terminate and return the name */
|
|
return name;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Private functions: action routines
|
|
---------------------------------------------------------------------------*/
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
|
/* Set the emulator speed to the given value (in MHz); the desired speed
|
|
value is held in the A field of the C CPU register. No handshake.
|
|
*/
|
|
static void SetSpeed(Nibble function_code)
|
|
{
|
|
debug1(DEBUG_C_TRACE, X_FUNC_I_CALLED, "SetSpeed");
|
|
|
|
#ifndef REAL_CPU_SPEED
|
|
ChfCondition X_FUNC_E_NO_SPEED, CHF_ERROR ChfEnd;
|
|
ChfSignal();
|
|
|
|
#else
|
|
{
|
|
int new_speed;
|
|
|
|
/* Get new_speed from A field of C register */
|
|
new_speed = R2int(cpu_status.C);
|
|
|
|
/* Compute inner loop limit; 4 is the real CPU speed in MHz when
|
|
the limit is set to INNER_LOOP_MAX. No overflow checks,
|
|
because new_speed is >=0, has an architectural upper limit of 2^20,
|
|
and int are at least 2^31.
|
|
*/
|
|
cpu_status.inner_loop_max = (new_speed * INNER_LOOP_MAX) / 4;
|
|
|
|
/* Notify the user about the speed change */
|
|
if(cpu_status.inner_loop_max)
|
|
ChfCondition X_FUNC_I_SET_SPEED, CHF_INFO, new_speed ChfEnd;
|
|
|
|
else
|
|
ChfCondition X_FUNC_I_MAX_SPEED, CHF_INFO ChfEnd;
|
|
|
|
ChfSignal();
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
|
/* This array holds the binary headers for all known hw configurations;
|
|
here, '?' is a wildcard character when reading from file
|
|
(see ReadObjectFromFile()) and is replaced by 'S' when writing
|
|
to file (see WriteObjectToFile()).
|
|
*/
|
|
struct BinHdrMapping
|
|
{
|
|
char *hw;
|
|
char *hdr;
|
|
};
|
|
|
|
static const struct BinHdrMapping bin_hdr_mapping[] =
|
|
{
|
|
{ "hp48", "HPHP48-?" },
|
|
{ "hp49", "HPHP49-?" }
|
|
};
|
|
|
|
#define N_BIN_HDR_MAPPING (sizeof(bin_hdr_mapping)/sizeof(bin_hdr_mapping[0]))
|
|
|
|
|
|
/* Return the header of binary files for current hw configuration;
|
|
return NULL if the header cannot be determined. In the latter case,
|
|
generate an appropriate condition.
|
|
*/
|
|
static const char *BinaryHeader(void)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<N_BIN_HDR_MAPPING; i++)
|
|
if(strcmp(args.hw, bin_hdr_mapping[i].hw) == 0)
|
|
return bin_hdr_mapping[i].hdr;
|
|
|
|
ChfCondition X_FUNC_E_NO_BIN_HDR, CHF_ERROR, args.hw ChfEnd;
|
|
return (char *)NULL;
|
|
}
|
|
|
|
|
|
/* This function is the continuation of Kget(); it is invoked when the
|
|
user interaction with the FSB ends.
|
|
*/
|
|
static void KgetContinuation(int proceed, char *file_name)
|
|
{
|
|
/* Check whether continuation should proceed */
|
|
if(!proceed)
|
|
{
|
|
ChfCondition X_FUNC_W_ABORTED, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
else
|
|
{
|
|
/* Ok to proceed; read:
|
|
- target start address from A[A]
|
|
- target end address from C[A]
|
|
- binary header with BinaryHeader()
|
|
*/
|
|
int start_addr = R2int(cpu_status.A);
|
|
int end_addr = R2int(cpu_status.C);
|
|
const char *bin_hdr = BinaryHeader();
|
|
|
|
debug1(DEBUG_C_X_FUNC, X_FUNC_I_FILE_NAME, file_name);
|
|
debug3(DEBUG_C_X_FUNC, X_FUNC_I_KGET, start_addr, end_addr, bin_hdr);
|
|
|
|
if(bin_hdr == (const char *)NULL
|
|
|| ReadObjectFromFile(file_name, bin_hdr, (Address)start_addr,
|
|
(Address)end_addr))
|
|
{
|
|
ChfCondition X_FUNC_W_FAILED, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
|
|
CpuRunRequest();
|
|
}
|
|
|
|
|
|
/* This function is the continuation of Send(); it is invoked when the
|
|
user interaction with the FSB ends.
|
|
*/
|
|
static void SendContinuation(int proceed, char *file_name)
|
|
{
|
|
if(!proceed)
|
|
{
|
|
ChfCondition X_FUNC_W_ABORTED, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
else
|
|
{
|
|
/* Ok to proceed; read:
|
|
- source start address from A[A]
|
|
- source end address from C[A]
|
|
- binary header with BinaryHeader()
|
|
*/
|
|
int start_addr = R2int(cpu_status.A);
|
|
int end_addr = R2int(cpu_status.C);
|
|
const char *bin_hdr = BinaryHeader();
|
|
|
|
debug1(DEBUG_C_X_FUNC, X_FUNC_I_FILE_NAME, file_name);
|
|
debug3(DEBUG_C_X_FUNC, X_FUNC_I_SEND, start_addr, end_addr, bin_hdr);
|
|
|
|
if(bin_hdr == (const char *)NULL
|
|
|| WriteObjectToFile((Address)start_addr,
|
|
(Address)end_addr, bin_hdr, file_name))
|
|
{
|
|
ChfCondition X_FUNC_W_FAILED, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
|
|
CpuRunRequest();
|
|
}
|
|
|
|
|
|
/* This function does the setup of a transfer, performing the following
|
|
actions:
|
|
|
|
- If CPU halt requests are not allowed, it signals X_FUNC_E_NO_HALT
|
|
- Gets the FSB title from 'msg' and 'def_msg', using ChfGetMessage()
|
|
- Gets the default file name using NameFromD1()
|
|
- Invokes ActivateFSB() to pop the FSB up; continuation 'cont' will
|
|
be invoked when the user interaction ends
|
|
- Halts the CPU
|
|
*/
|
|
static void SetupXfer(
|
|
int msg, const char *def_msg, FsbContinuation cont)
|
|
{
|
|
debug1(DEBUG_C_TRACE, X_FUNC_I_CALLED, "SetupXfer");
|
|
|
|
if(CpuHaltAllowed())
|
|
{
|
|
char *fsb_title =
|
|
XtNewString(ChfGetMessage(CHF_MODULE_ID, msg, def_msg));
|
|
|
|
char *fsb_file =
|
|
NameFromD1();
|
|
|
|
ActivateFSB(fsb_title, fsb_file, cont);
|
|
|
|
/* Free *before* CpuHaltRequest() because it does not return, and
|
|
ActivateFSB() copied its argument when necessary.
|
|
*/
|
|
XtFree(fsb_title);
|
|
XtFree(fsb_file);
|
|
|
|
(void)CpuHaltRequest();
|
|
}
|
|
|
|
else
|
|
{
|
|
ChfCondition X_FUNC_E_NO_HALT, CHF_ERROR ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
|
|
|
|
/* This is the emulator's extended function for 'kget': this function
|
|
transfers a file from disk into the calculator's memory.
|
|
*/
|
|
static void Kget(Nibble function_code)
|
|
{
|
|
debug1(DEBUG_C_TRACE, X_FUNC_I_CALLED, "Kget");
|
|
|
|
/* Setup File Selection Box if transfers are *not* in batch mode */
|
|
if(! args.batchXfer)
|
|
SetupXfer(X_FUNC_M_KGET, "Kget", KgetContinuation);
|
|
|
|
else
|
|
{
|
|
/* Ok to proceed; read:
|
|
- file name from @D1
|
|
- target start address from A[A]
|
|
- target end address from C[A]
|
|
- binary header with BinaryHeader()
|
|
*/
|
|
char *file_name = NameFromD1();
|
|
int start_addr = R2int(cpu_status.A);
|
|
int end_addr = R2int(cpu_status.C);
|
|
const char *bin_hdr = BinaryHeader();
|
|
|
|
debug1(DEBUG_C_X_FUNC, X_FUNC_I_FILE_NAME, file_name);
|
|
debug3(DEBUG_C_X_FUNC, X_FUNC_I_KGET, start_addr, end_addr, bin_hdr);
|
|
|
|
if(bin_hdr == (const char *)NULL
|
|
|| ReadObjectFromFile(file_name, bin_hdr, (Address)start_addr,
|
|
(Address)end_addr))
|
|
{
|
|
ChfCondition X_FUNC_W_FAILED, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* This is the emulator's extended function for 'send': this function
|
|
transfers an object from the calculator's memory into a disk file.
|
|
*/
|
|
static void Send(Nibble function_code)
|
|
{
|
|
debug1(DEBUG_C_TRACE, X_FUNC_I_CALLED, "Send");
|
|
|
|
/* Setup File Selection Box if transfers are *not* in batch mode */
|
|
if(! args.batchXfer)
|
|
SetupXfer(X_FUNC_M_SEND, "Send", SendContinuation);
|
|
|
|
else
|
|
{
|
|
/* Ok to proceed; read:
|
|
- file name from @D1
|
|
- source start address from A[A]
|
|
- source end address from C[A]
|
|
- binary header with BinaryHeader()
|
|
*/
|
|
char *file_name = NameFromD1();
|
|
int start_addr = R2int(cpu_status.A);
|
|
int end_addr = R2int(cpu_status.C);
|
|
const char *bin_hdr = BinaryHeader();
|
|
|
|
debug1(DEBUG_C_X_FUNC, X_FUNC_I_FILE_NAME, file_name);
|
|
debug3(DEBUG_C_X_FUNC, X_FUNC_I_SEND, start_addr, end_addr, bin_hdr);
|
|
|
|
if(bin_hdr == (const char *)NULL
|
|
|| WriteObjectToFile((Address)start_addr,
|
|
(Address)end_addr, bin_hdr, file_name))
|
|
{
|
|
ChfCondition X_FUNC_W_FAILED, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Dispatch table of emulator's extended functions, indexed by function code;
|
|
the function code is propagated to functions in the table.
|
|
---------------------------------------------------------------------------*/
|
|
|
|
typedef void (*XFunc)(Nibble);
|
|
|
|
static const XFunc function[] =
|
|
{
|
|
SetSpeed, /* Function code 0 */
|
|
Kget, /* 1 */
|
|
Send /* 2 */
|
|
};
|
|
|
|
#define N_X_FUNC (sizeof(function)/sizeof(function[0]))
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Public functions
|
|
---------------------------------------------------------------------------*/
|
|
|
|
/* .+
|
|
|
|
.title : ExtendedFunction
|
|
.kind : C function
|
|
.creation : 3-Nov-2000
|
|
.description :
|
|
This function executes the emulator's extended function identified
|
|
by 'function_code'; communications with the triggering code on
|
|
the calculator side are made through CPU registers.
|
|
|
|
.call :
|
|
ExtendedFunction(function_code);
|
|
.input :
|
|
Nibble function_code, function code
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
X_FUNC_I_CALLED
|
|
X_FUNC_I_CODE
|
|
X_FUNC_W_BAD_CODE
|
|
* Any other condition code generated by action functions
|
|
.notes :
|
|
3.13, 3-Nov-2000, creation
|
|
|
|
.- */
|
|
void ExtendedFunction(Nibble function_code)
|
|
{
|
|
debug1(DEBUG_C_TRACE, X_FUNC_I_CALLED, "ExtendedFunction");
|
|
debug1(DEBUG_C_X_FUNC, X_FUNC_I_CODE, function_code);
|
|
|
|
/* Some sanity checks, first */
|
|
if(function_code < 0
|
|
|| function_code >= N_X_FUNC
|
|
|| function[(int)function_code] == (XFunc)NULL)
|
|
{
|
|
ChfCondition X_FUNC_W_BAD_CODE, CHF_WARNING, function_code ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
/* Dispatch */
|
|
else
|
|
function[(int)function_code](function_code);
|
|
}
|