735 lines
26 KiB
C
735 lines
26 KiB
C
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Name: z80ctrl.c
|
|
// Created: Oct 2022
|
|
// Author(s): Philip Smart
|
|
// Description: Z80 Control Interface
|
|
// This file contains a command line utility tool for controlling the z80drv device
|
|
// driver. The tool allows manipulation of the emulated Z80, inspection of its
|
|
// memory and data, transmission of adhoc commands to the underlying CPLD-Z80
|
|
// gateway and loading/saving of programs and data to/from the Z80 virtual and
|
|
// host memory.
|
|
//
|
|
// Credits: Zilog Z80 CPU Emulator v0.2 written by Manuel Sainz de Baranda y Goñi
|
|
// The Z80 CPU Emulator is the heart of the Z80 device driver.
|
|
// Copyright: (c) 2019-2022 Philip Smart <philip.smart@net2net.org>
|
|
// (c) 1999-2022 Manuel Sainz de Baranda y Goñi
|
|
//
|
|
// History: Oct 2022 - Initial write of the z80 kernel driver software.
|
|
//
|
|
// Notes: See Makefile to enable/disable conditional components
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// This source file 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 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This source file 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
#include <sys/mman.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/select.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <Z/constants/pointer.h>
|
|
#include <Z/macros/member.h>
|
|
#include <Z/macros/array.h>
|
|
#include <Z80.h>
|
|
#include "z80driver.h"
|
|
|
|
#define VERSION "1.0"
|
|
#define AUTHOR "P.D.Smart"
|
|
#define COPYRIGHT "(c) 2018-22"
|
|
|
|
// Getopt_long is buggy so we use optparse.
|
|
#define OPTPARSE_IMPLEMENTATION
|
|
#define OPTPARSE_API static
|
|
#include "optparse.h"
|
|
|
|
// Device driver name.
|
|
#define DEVICE_FILENAME "/dev/z80drv"
|
|
|
|
// Constants for the Sharp MZ80A MZF file format.
|
|
#define MZF_HEADER_SIZE 128 // Size of the MZF header.
|
|
#define MZF_ATTRIBUTE 0x00 // Code Type, 01 = Machine Code.
|
|
#define MZF_FILENAME 0x01 // Title/Name (17 bytes).
|
|
#define MZF_FILENAME_LEN 17 // Length of the filename, it is not NULL terminated, generally a CR can be taken as terminator but not guaranteed.
|
|
#define MZF_FILESIZE 0x12 // Size of program.
|
|
#define MZF_LOADADDR 0x14 // Load address of program.
|
|
#define MZF_EXECADDR 0x16 // Exec address of program.
|
|
#define MZF_COMMENT 0x18 // Comment, used for details of the file or startup code.
|
|
#define MZF_COMMENT_LEN 104 // Length of the comment field.
|
|
#define CMT_TYPE_OBJCD 0x001 // MZF contains a binary object.
|
|
#define CMT_TYPE_BTX1CD 0x002 // MZF contains a BASIC program.
|
|
#define CMT_TYPE_BTX2CD 0x005 // MZF contains a BASIC program.
|
|
#define CMT_TYPE_TZOBJCD0 0x0F8 // MZF contains a TZFS binary object for page 0.
|
|
#define CMT_TYPE_TZOBJCD1 0x0F9
|
|
#define CMT_TYPE_TZOBJCD2 0x0FA
|
|
#define CMT_TYPE_TZOBJCD3 0x0FB
|
|
#define CMT_TYPE_TZOBJCD4 0x0FC
|
|
#define CMT_TYPE_TZOBJCD5 0x0FD
|
|
#define CMT_TYPE_TZOBJCD6 0x0FE
|
|
#define CMT_TYPE_TZOBJCD7 0x0FF // MZF contains a TZFS binary object for page 7.
|
|
#define MZ_CMT_ADDR 0x10F0
|
|
|
|
// Structure to define a Sharp MZ80A MZF directory structure. This header appears at the beginning of every Sharp MZ80A tape (and more recently archived/emulator) images.
|
|
//
|
|
typedef struct __attribute__((__packed__)) {
|
|
uint8_t attr; // MZF attribute describing the file.
|
|
uint8_t fileName[MZF_FILENAME_LEN]; // Each directory entry is the size of an MZF filename.
|
|
uint16_t fileSize; // Size of file.
|
|
uint16_t loadAddr; // Load address for the file.
|
|
uint16_t execAddr; // Execution address where the Z80 starts processing.
|
|
uint8_t comment[MZF_COMMENT_LEN]; // Text comment field but often contains a startup machine code program.
|
|
} t_svcDirEnt;
|
|
|
|
// Possible commands to be issued to the Z80 driver.
|
|
enum CTRL_COMMANDS {
|
|
Z80_CMD_STOP = 0,
|
|
Z80_CMD_START = 1,
|
|
Z80_CMD_PAUSE = 2,
|
|
Z80_CMD_CONTINUE = 3,
|
|
Z80_CMD_RESET = 4,
|
|
Z80_CMD_SPEED = 5,
|
|
Z80_CMD_HOST_RAM = 6,
|
|
Z80_CMD_VIRTUAL_RAM = 7,
|
|
Z80_CMD_DUMP_MEMORY = 8,
|
|
Z80_CMD_MEMORY_TEST = 9,
|
|
CPLD_CMD_SEND_CMD = 10,
|
|
CPLD_CMD_SPI_TEST = 11,
|
|
CPLD_CMD_PRL_TEST = 12
|
|
};
|
|
|
|
|
|
// Shared memory between this process and the Z80 driver.
|
|
static t_Z80Ctrl *Z80Ctrl = NULL;
|
|
|
|
// Method to obtain and return the output screen width.
|
|
//
|
|
uint8_t getScreenWidth(void)
|
|
{
|
|
return(MAX_SCREEN_WIDTH);
|
|
}
|
|
|
|
struct termios orig_termios;
|
|
|
|
void reset_terminal_mode()
|
|
{
|
|
tcsetattr(0, TCSANOW, &orig_termios);
|
|
}
|
|
|
|
void set_conio_terminal_mode()
|
|
{
|
|
struct termios new_termios;
|
|
|
|
/* take two copies - one for now, one for later */
|
|
tcgetattr(0, &orig_termios);
|
|
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
|
|
|
|
/* register cleanup handler, and set the new terminal mode */
|
|
atexit(reset_terminal_mode);
|
|
cfmakeraw(&new_termios);
|
|
tcsetattr(0, TCSANOW, &new_termios);
|
|
}
|
|
|
|
int kbhit()
|
|
{
|
|
struct timeval tv = { 0L, 0L };
|
|
fd_set fds;
|
|
FD_ZERO(&fds);
|
|
FD_SET(0, &fds);
|
|
return select(1, &fds, NULL, NULL, &tv) > 0;
|
|
}
|
|
|
|
int getch(uint8_t wait)
|
|
{
|
|
int r;
|
|
unsigned char c;
|
|
|
|
if(wait != 0 || (wait == 0 && kbhit()))
|
|
{
|
|
if ((r = read(0, &c, sizeof(c))) < 0) {
|
|
return r;
|
|
} else {
|
|
return c;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void delay(int number_of_seconds)
|
|
{
|
|
// Converting time into milli_seconds
|
|
int milli_seconds = 1000 * number_of_seconds;
|
|
|
|
// Storing start time
|
|
clock_t start_time = clock();
|
|
|
|
// looping till required time is not achieved
|
|
while (clock() < start_time + milli_seconds);
|
|
}
|
|
|
|
// Function to dump out a given section of memory via the UART.
|
|
//
|
|
int memoryDump(uint32_t memaddr, uint32_t memsize, uint8_t memoryFlag, uint32_t memwidth, uint32_t dispaddr, uint8_t dispwidth)
|
|
{
|
|
uint8_t displayWidth = dispwidth;;
|
|
uint32_t pnt = memaddr;
|
|
uint32_t endAddr = memaddr + memsize;
|
|
uint32_t addr = dispaddr;
|
|
uint32_t i = 0;
|
|
//uint32_t data;
|
|
int8_t keyIn;
|
|
int result = -1;
|
|
char c = 0;
|
|
|
|
// Sanity check. memoryFlag == 0 required kernel driver to dump so we exit as it cannot be performed here.
|
|
if(memoryFlag == 0)
|
|
return(-1);
|
|
|
|
// Reconfigure terminal to allow non-blocking key input.
|
|
//
|
|
set_conio_terminal_mode();
|
|
|
|
// If not set, calculate output line width according to connected display width.
|
|
//
|
|
if(displayWidth == 0)
|
|
{
|
|
switch(getScreenWidth())
|
|
{
|
|
case 40:
|
|
displayWidth = 8;
|
|
break;
|
|
case 80:
|
|
displayWidth = 16;
|
|
break;
|
|
default:
|
|
displayWidth = 32;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
printf("%08lX", addr); // print address
|
|
printf(": ");
|
|
|
|
// print hexadecimal data
|
|
for (i=0; i < displayWidth; )
|
|
{
|
|
switch(memwidth)
|
|
{
|
|
case 16:
|
|
if(pnt+i < endAddr)
|
|
printf("%04X", memoryFlag == 1 ? (uint16_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint16_t)Z80Ctrl->page[pnt+i] : (uint16_t)Z80Ctrl->iopage[pnt+i]);
|
|
else
|
|
printf(" ");
|
|
i++;
|
|
break;
|
|
|
|
case 32:
|
|
if(pnt+i < endAddr)
|
|
printf("%08lX", memoryFlag == 1 ? (uint32_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint32_t)Z80Ctrl->page[pnt+i] : (uint32_t)Z80Ctrl->iopage[pnt+i]);
|
|
else
|
|
printf(" ");
|
|
i++;
|
|
break;
|
|
|
|
case 8:
|
|
default:
|
|
if(pnt+i < endAddr)
|
|
printf("%02X", memoryFlag == 1 ? (uint8_t)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (uint8_t)Z80Ctrl->page[pnt+i] : (uint8_t)Z80Ctrl->iopage[pnt+i]);
|
|
else
|
|
printf(" ");
|
|
i++;
|
|
break;
|
|
}
|
|
fputc((char)' ', stdout);
|
|
}
|
|
|
|
// print ascii data
|
|
printf(" |");
|
|
|
|
// print single ascii char
|
|
for (i=0; i < displayWidth; i++)
|
|
{
|
|
c = memoryFlag == 1 ? (char)Z80Ctrl->memory[pnt+i] : memoryFlag == 2 ? (char)Z80Ctrl->page[pnt+i] : (char)Z80Ctrl->iopage[pnt+i];
|
|
if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~'))
|
|
fputc((char)c, stdout);
|
|
else
|
|
fputc((char)' ', stdout);
|
|
}
|
|
|
|
printf("|\r\n");
|
|
fflush(stdout);
|
|
|
|
// Move on one row.
|
|
pnt += displayWidth;
|
|
addr += displayWidth;
|
|
|
|
// User abort (ESC), pause (Space) or all done?
|
|
//
|
|
keyIn = getch(0);
|
|
if(keyIn == ' ')
|
|
{
|
|
do {
|
|
keyIn = getch(0);
|
|
} while(keyIn != ' ' && keyIn != 0x1b);
|
|
}
|
|
// Escape key pressed, exit with 0 to indicate this to caller.
|
|
if (keyIn == 0x1b)
|
|
{
|
|
sleep(1);
|
|
result = 0;
|
|
goto memoryDumpExit;
|
|
}
|
|
|
|
// End of buffer, exit the loop.
|
|
if(pnt >= (memaddr + memsize))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Normal exit, return -1 to show no key pressed.
|
|
memoryDumpExit:
|
|
reset_terminal_mode();
|
|
return(result);
|
|
}
|
|
|
|
// Method to load a program or data file into the Z80 memory. First load into Virtual memory and then trigger a sync to bring Host RAM in line.
|
|
//
|
|
int z80load(int fdZ80, char *fileName)
|
|
{
|
|
// Locals.
|
|
struct ioctlCmd ioctlCmd;
|
|
int ret = 0;
|
|
t_svcDirEnt mzfHeader;
|
|
|
|
// Pause the Z80.
|
|
//
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_PAUSE;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
|
|
// Open the file and read directly into the Virtual memory via the share.
|
|
FILE *ptr;
|
|
ptr = fopen(fileName, "rb");
|
|
if(ptr)
|
|
{
|
|
printf("File:%s\n", fileName);
|
|
// First the header.
|
|
fread((uint8_t *)&mzfHeader, MZF_HEADER_SIZE, 1, ptr);
|
|
printf("Load:%x\n", mzfHeader.loadAddr);
|
|
if(mzfHeader.loadAddr > 0x1000)
|
|
{
|
|
printf("Memcpy:%x,%x\n", mzfHeader.loadAddr, mzfHeader.fileSize);
|
|
// Copy in the header.
|
|
memcpy((uint8_t *)&Z80Ctrl->memory[MZ_CMT_ADDR], (uint8_t *)&mzfHeader, MZF_HEADER_SIZE);
|
|
|
|
printf("Memcpy:%x,%x\n", mzfHeader.loadAddr, mzfHeader.fileSize);
|
|
// Now read in the data.
|
|
fread(&Z80Ctrl->memory[mzfHeader.loadAddr], mzfHeader.fileSize, 1, ptr);
|
|
printf("Memcpy:%x,%x\n", mzfHeader.loadAddr, mzfHeader.fileSize);
|
|
printf("Loaded %s, Size:%04x, Addr:%04x, Exec:%04x\n", fileName, mzfHeader.fileSize, mzfHeader.loadAddr, mzfHeader.execAddr);
|
|
}
|
|
|
|
// Sync the loaded image from Virtual memory to hard memory.
|
|
ioctlCmd.cmd = IOCTL_CMD_SYNC_TO_HOST_RAM;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
|
|
// Resume Z80 processing.
|
|
//
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_CONTINUE;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
}
|
|
else
|
|
printf("Couldnt open file\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Method to request basic Z80 operations.
|
|
//
|
|
int ctrlCmd(int fdZ80, enum CTRL_COMMANDS cmd, long param1, long param2, long param3)
|
|
{
|
|
// Locals.
|
|
struct ioctlCmd ioctlCmd;
|
|
uint32_t idx;
|
|
int ret = 0;
|
|
|
|
switch(cmd)
|
|
{
|
|
case Z80_CMD_STOP:
|
|
// Use IOCTL to request Z80 to Stop (power off) processing.
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_STOP;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_START:
|
|
// Use IOCTL to request Z80 to Start (power on) processing.
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_START;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_PAUSE:
|
|
// Use IOCTL to request Z80 to pause processing.
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_PAUSE;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_CONTINUE:
|
|
// Use IOCTL to request Z80 continue processing.
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_CONTINUE;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_RESET:
|
|
// Use IOCTL to request Z80 reset.
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_RESET;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_SPEED:
|
|
// Check value is in range.
|
|
for(idx=1; idx < 256; idx+=idx)
|
|
{
|
|
if((uint32_t)param1 == idx) break;
|
|
}
|
|
if(idx == 256)
|
|
{
|
|
printf("Speed factor is illegal. It must be a multiple value of the original CPU clock, ie. 1x, 2x, 4x etc\n");
|
|
ret = -1;
|
|
} else
|
|
{
|
|
// Use IOCTL to request Z80 cpu freq change.
|
|
ioctlCmd.speed.speedMultiplier = (uint32_t)param1;
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_CPU_FREQ;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
}
|
|
break;
|
|
case CPLD_CMD_SEND_CMD:
|
|
// Build up the IOCTL command to request the given data is sent to the CPLD.
|
|
ioctlCmd.cmd = IOCTL_CMD_CPLD_CMD;
|
|
ioctlCmd.cpld.cmd = (uint32_t)param1;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_DUMP_MEMORY:
|
|
// If virtual memory, we can dump it via the shared memory segment.
|
|
if((uint8_t)param1)
|
|
{
|
|
memoryDump((uint32_t)param2, (uint32_t)param3, (uint8_t)param1, (uint8_t)param1 == 2 || (uint8_t)param1 == 3 ? 32 : 8, (uint32_t)param2, 0);
|
|
} else
|
|
{
|
|
// Build an IOCTL command to get the driver to dump the memory.
|
|
ioctlCmd.cmd = IOCTL_CMD_DUMP_MEMORY;
|
|
ioctlCmd.addr.start = (uint32_t)param2;
|
|
ioctlCmd.addr.end = (uint32_t)param2+(uint32_t)param3;
|
|
ioctlCmd.addr.size = (uint32_t)param3;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
}
|
|
break;
|
|
case Z80_CMD_HOST_RAM:
|
|
// Use IOCTL to request change to host RAM.
|
|
ioctlCmd.cmd = IOCTL_CMD_USE_HOST_RAM;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_VIRTUAL_RAM:
|
|
// Use IOCTL to request change to host RAM.
|
|
ioctlCmd.cmd = IOCTL_CMD_USE_VIRTUAL_RAM;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case Z80_CMD_MEMORY_TEST:
|
|
// Send command to test the SPI.
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_MEMTEST;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case CPLD_CMD_PRL_TEST:
|
|
// Send command to test the SPI.
|
|
ioctlCmd.cmd = IOCTL_CMD_PRL_TEST;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
case CPLD_CMD_SPI_TEST:
|
|
// Send command to test the SPI.
|
|
ioctlCmd.cmd = IOCTL_CMD_SPI_TEST;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
break;
|
|
|
|
default:
|
|
printf("Command not supported!\n");
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Method to perform some simple tests on the Z80 emulator.
|
|
//
|
|
int z80test(int fdZ80)
|
|
{
|
|
// Locals.
|
|
struct ioctlCmd ioctlCmd;
|
|
int ret = 0;
|
|
|
|
// Stop the Z80.
|
|
//
|
|
printf("Send STOP\n");
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_STOP;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
|
|
FILE *ptr;
|
|
ptr = fopen("/customer/mz700.rom", "rb");
|
|
if(ptr)
|
|
{
|
|
fread(&Z80Ctrl->memory, 65536, 1, ptr);
|
|
} else printf("Couldnt open file\n");
|
|
|
|
// Configure the Z80.
|
|
//
|
|
printf("Send SETPC\n");
|
|
ioctlCmd.z80.pc = 0;
|
|
ioctl(fdZ80, IOCTL_CMD_SETPC, &ioctlCmd);
|
|
|
|
memoryDump(0 , 65536, 1, 8, 0, 0);
|
|
|
|
// Start the Z80.
|
|
//
|
|
printf("Send START\n");
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_START;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
|
|
delay(10);
|
|
|
|
printf("Send STOP\n");
|
|
ioctlCmd.cmd = IOCTL_CMD_Z80_STOP;
|
|
ioctl(fdZ80, IOCTL_CMD_SEND, &ioctlCmd);
|
|
|
|
memoryDump(0, 65536, 1, 8, 0, 0);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
// Output usage screen. So mamy commands you do need to be prompted!!
|
|
void showArgs(char *progName, struct optparse *options)
|
|
{
|
|
printf("%s %s %s %s\n\n", progName, VERSION, COPYRIGHT, AUTHOR);
|
|
printf("Synopsis:\n");
|
|
printf("%s --help # This help screen.\n", progName);
|
|
printf(" --cmd <command> = RESET # Reset the Z80\n");
|
|
printf(" = STOP # Stop and power off the Z80\n");
|
|
printf(" = START # Power on and start the Z80\n");
|
|
printf(" = PAUSE # Pause running Z80\n");
|
|
printf(" = CONTINUE # Continue Z80 execution\n");
|
|
printf(" = HOSTRAM # Use HOST DRAM\n");
|
|
printf(" = VIRTRAM # Use Virtual RAM\n");
|
|
printf(" = SPEED --speed <1, 2, 4, 8, 16, 32, 64, 128> # In Virtual RAM mode, set CPU speed to base clock x factor.\n");
|
|
printf(" = LOADMZF --file <mzf filename> # Load MZF file into memory.\n");
|
|
printf(" = DUMP --start <24bit addr> --end <24bit addr> --virtual <0 - Host RAM, 1 = Virtual RAM, 2 = PageTable, 3 = IOPageTable>\n");
|
|
printf(" = CPLDCMD --data <32bit command> # Send adhoc 32bit command to CPLD.\n");
|
|
printf(" = Z80TEST # Perform various debugging tests\n");
|
|
printf(" = SPITEST # Perform SPI testing\n");
|
|
printf(" = PRLTEST # Perform Parallel Bus testing\n");
|
|
printf(" = Z80MEMTEST # Perform HOST memory tests.\n");
|
|
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int fdZ80;
|
|
char buff[64];
|
|
char cmd[64] = { 0 };
|
|
char fileName[256] = { 0 };
|
|
int opt;
|
|
long hexData = 0;
|
|
long speedMultiplier = 1;
|
|
long startAddr = 0x0000;
|
|
long endAddr = 0x1000;
|
|
int virtualMemory = 0;
|
|
int helpFlag = 0;
|
|
int verboseFlag = 0;
|
|
|
|
// Define parameters to be processed.
|
|
struct optparse options;
|
|
static struct optparse_long long_options[] =
|
|
{
|
|
{"help", 'h', OPTPARSE_NONE},
|
|
{"cmd", 'c', OPTPARSE_REQUIRED},
|
|
{"file", 'f', OPTPARSE_REQUIRED},
|
|
{"data", 'd', OPTPARSE_REQUIRED},
|
|
{"speed", 'S', OPTPARSE_REQUIRED},
|
|
{"virtual", 'V', OPTPARSE_REQUIRED},
|
|
{"start", 's', OPTPARSE_REQUIRED},
|
|
{"end", 'e', OPTPARSE_REQUIRED},
|
|
{"verbose", 'v', OPTPARSE_NONE},
|
|
{0}
|
|
};
|
|
|
|
// Parse the command line options.
|
|
//
|
|
optparse_init(&options, argv);
|
|
while((opt = optparse_long(&options, long_options, NULL)) != -1)
|
|
{
|
|
switch(opt)
|
|
{
|
|
// Hex data.
|
|
case 'd':
|
|
hexData = strtol(options.optarg, NULL, 0);
|
|
//printf("Hex data:%08x\n", hexData);
|
|
break;
|
|
|
|
// Start address for memory operations.
|
|
case 's':
|
|
startAddr = strtol(options.optarg, NULL, 0);
|
|
//printf("Start Addr:%04x\n", startAddr);
|
|
break;
|
|
|
|
// Speed multiplication factor for CPU governor when running in virtual memory.
|
|
case 'S':
|
|
speedMultiplier = strtol(options.optarg, NULL, 0);
|
|
//printf("Speed = base freq x %d\n", speedFactor);
|
|
break;
|
|
|
|
// End address for memory operations.
|
|
case 'e':
|
|
endAddr = strtol(options.optarg, NULL, 0);
|
|
//printf("End Addr:%04x\n", endAddr);
|
|
break;
|
|
|
|
// Virtual memory flag, 0 = host, 1 = virtual memory, 2 = page table, 3 = iopage table.
|
|
case 'V':
|
|
virtualMemory = atoi(options.optarg);
|
|
break;
|
|
|
|
// Filename.
|
|
case 'f':
|
|
strcpy(fileName, options.optarg);
|
|
break;
|
|
|
|
// Command to execute.
|
|
case 'c':
|
|
strcpy(cmd, options.optarg);
|
|
break;
|
|
|
|
// Verbose mode.
|
|
case 'v':
|
|
verboseFlag = 1;
|
|
break;
|
|
|
|
// Command help needed.
|
|
case 'h':
|
|
helpFlag = 1;
|
|
showArgs(argv[0], &options);
|
|
break;
|
|
|
|
// Unrecognised, show synopsis.
|
|
case '?':
|
|
showArgs(argv[0], &options);
|
|
printf("%s: %s\n", argv[0], options.errmsg);
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
// Open the z80drv driver and attach to its shared memory, basically the Z80 control structure which includes the virtual Z80 memory.
|
|
fdZ80 = open(DEVICE_FILENAME, O_RDWR|O_NDELAY);
|
|
if(fdZ80 >= 0)
|
|
{
|
|
Z80Ctrl = (t_Z80Ctrl *)mmap(0, sizeof(t_Z80Ctrl), PROT_READ | PROT_WRITE, MAP_SHARED, fdZ80, 0);
|
|
if(Z80Ctrl == (void *)-1)
|
|
{
|
|
printf("Failed to attach to the Z80 Control structure, cannot continue, exitting....\n");
|
|
close(fdZ80);
|
|
exit(1);
|
|
}
|
|
} else
|
|
{
|
|
printf("Failed to open the Z80 Driver, exitting...\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Basic string to method mapping. Started off with just 1 or two but has grown, may need a table!
|
|
if(strcasecmp(cmd, "LOADMZF") == 0)
|
|
{
|
|
z80load(fdZ80, fileName);
|
|
} else
|
|
if(strcasecmp(cmd, "RESET") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_RESET, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "STOP") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_STOP, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "START") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_START, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "PAUSE") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_PAUSE, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "CONTINUE") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_CONTINUE, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "SPEED") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_SPEED, speedMultiplier, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "DUMP") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_DUMP_MEMORY, virtualMemory, startAddr, (endAddr - startAddr));
|
|
} else
|
|
if(strcasecmp(cmd, "HOSTRAM") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_HOST_RAM, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "VIRTRAM") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_VIRTUAL_RAM, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "CPLDCMD") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, CPLD_CMD_SEND_CMD, hexData, 0, 0);
|
|
} else
|
|
|
|
// Test methods, if the code is built-in to the driver.
|
|
if(strcasecmp(cmd, "Z80TEST") == 0)
|
|
{
|
|
z80test(fdZ80);
|
|
} else
|
|
if(strcasecmp(cmd, "SPITEST") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, CPLD_CMD_SPI_TEST, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "PRLTEST") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, CPLD_CMD_PRL_TEST, 0, 0, 0);
|
|
} else
|
|
if(strcasecmp(cmd, "Z80MEMTEST") == 0)
|
|
{
|
|
ctrlCmd(fdZ80, Z80_CMD_MEMORY_TEST, 0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
showArgs(argv[0], &options);
|
|
printf("No command given, nothing done!\n");
|
|
}
|
|
|
|
// Unmap shared memory and close the device.
|
|
munmap(Z80Ctrl, sizeof(t_Z80Ctrl));
|
|
close(fdZ80);
|
|
|
|
return(0);
|
|
}
|