diff --git a/.gitignore b/.gitignore index 09bcab0..e1aa2c4 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,10 @@ apps/*/*.k64 common/old2 include.bak libraries/umlibc/misc2 +apps/tzpu/tzpu.c.1605 +apps/tzpu/tzpu.h.1605 +c +teensy3/TeensyThreads/ +x +zOS/main.hex + diff --git a/apps/Makefile b/apps/Makefile index 6de5041..12c06fc 100755 --- a/apps/Makefile +++ b/apps/Makefile @@ -36,41 +36,43 @@ ######################################################################################################### ifeq ($(__K64F__),1) - TOOLSPATH = $(CURDIR)/../../tools - COMPILERPATH = $(TOOLSPATH)/arm/bin - BASE = $(abspath $(COMPILERPATH))/arm-none-eabi + TOOLSPATH = $(CURDIR)/../../tools + COMPILERPATH = $(TOOLSPATH)/arm/bin + BASE = $(abspath $(COMPILERPATH))/arm-none-eabi else - TOOLSPATH = /opt/zpu - COMPILERPATH = $(TOOLSPATH)/bin - BASE = zpu-elf + TOOLSPATH = /opt/zpu + COMPILERPATH = $(TOOLSPATH)/bin + BASE = zpu-elf endif -CC = $(BASE)-gcc -CXX = $(BASE)-g++ -LD = $(BASE)-gcc -AS = $(BASE)-as -CP = $(BASE)-objcopy -DUMP = $(BASE)-objdump -OBJCOPY = $(BASE)-objcopy -SIZE = $(BASE)-size +CC = $(BASE)-gcc +CXX = $(BASE)-g++ +LD = $(BASE)-gcc +AS = $(BASE)-as +CP = $(BASE)-objcopy +DUMP = $(BASE)-objdump +OBJCOPY = $(BASE)-objcopy +SIZE = $(BASE)-size -FS_SUBDIRS := falloc fattr fcat fcd fclose fconcat fcp fdel fdir fdrive fdump finspect flabel fmkdir -FS_SUBDIRS += fmkfs fopen fread frename fsave fseek fshowdir fstat ftime ftrunc fwrite fxtract -DISK_SUBDIRS := ddump dstat -BUFFER_SUBDIRS:= bdump bedit bread bwrite bfill blen -MEM_SUBDIRS := mclear mcopy mdiff mdump meb meh mew mperf msrch mtest -HW_SUBDIRS := hr ht tcpu -TST_SUBDIRS := dhry coremark -MISC_SUBDIRS := help time -APP_SUBDIRS := tbasic mbasic kilo ed +FS_SUBDIRS := falloc fattr fcat fcd fclose fconcat fcp fdel fdir fdrive fdump finspect flabel fmkdir +FS_SUBDIRS += fmkfs fopen fread frename fsave fseek fshowdir fstat ftime ftrunc fwrite fxtract +DISK_SUBDIRS := ddump dstat +BUFFER_SUBDIRS := bdump bedit bread bwrite bfill blen +MEM_SUBDIRS := mclear mcopy mdiff mdump meb meh mew mperf msrch mtest +HW_SUBDIRS := hr ht tcpu +TST_SUBDIRS := dhry coremark +MISC_SUBDIRS := help time +APP_SUBDIRS := tbasic mbasic kilo ed ifeq ($(__K64F__),1) - TZPU_SUBDIRS:= tzpu tzload + ifeq ($(__TRANZPUTER__),1) + TZPU_SUBDIRS := tzpu tzload tzdump tzclear tzreset + endif else - TZPU_SUBDIRS:= + TZPU_SUBDIRS := endif -SUBDIRS := $(FS_SUBDIRS) $(DISK_SUBDIRS) $(BUFFER_SUBDIRS) $(MEM_SUBDIRS) $(HW_SUBDIRS) $(TST_SUBDIRS) $(MISC_SUBDIRS) $(APP_SUBDIRS) $(TZPU_SUBDIRS) -BASEDIR = ../.. -TARGETS := all clean install +SUBDIRS := $(FS_SUBDIRS) $(DISK_SUBDIRS) $(BUFFER_SUBDIRS) $(MEM_SUBDIRS) $(HW_SUBDIRS) $(TST_SUBDIRS) $(MISC_SUBDIRS) $(APP_SUBDIRS) $(TZPU_SUBDIRS) +BASEDIR = ../.. +TARGETS := all clean install # Our target. $(TARGETS): $(SUBDIRS) diff --git a/apps/common/pins_teensy.c b/apps/common/pins_teensy.c index 412459e..592bb98 100644 --- a/apps/common/pins_teensy.c +++ b/apps/common/pins_teensy.c @@ -604,9 +604,9 @@ void _init_Teensyduino_internal_(void) // https://forum.pjrc.com/threads/36606-startup-time-(400ms)?p=113980&viewfull=1#post113980 // https://forum.pjrc.com/threads/31290-Teensey-3-2-Teensey-Loader-1-24-Issues?p=87273&viewfull=1#post87273 - delay(TEENSY_INIT_USB_DELAY_BEFORE); + //delay(TEENSY_INIT_USB_DELAY_BEFORE); //usb_init(); - delay(TEENSY_INIT_USB_DELAY_AFTER); + //delay(TEENSY_INIT_USB_DELAY_AFTER); } diff --git a/apps/common/tranzputer.c b/apps/common/tranzputer.c deleted file mode 100644 index edf1c11..0000000 --- a/apps/common/tranzputer.c +++ /dev/null @@ -1,1165 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: tranzputer.c -// Created: May 2020 -// Author(s): Philip Smart -// Description: The TranZPUter library. -// This file contains methods which allow applications to access and control the -// traZPUter board and the underlying Sharp MZ80A host. -// I had considered writing this module in C++ but decided upon C as speed is more -// important and C++ always adds a little additional overhead. Some of the methods need -// to be written as embedded assembler but this is for a later time when using the -// tranZPUter on faster motherboards. -// Credits: -// Copyright: (c) 2019-2020 Philip Smart -// -// History: May 2020 - Initial write of the TranZPUter 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 . -///////////////////////////////////////////////////////////////////////////////////////////////////////// - -#ifdef __cplusplus - extern "C" { -#endif - -#if defined(__K64F__) - #include - #include - #include - #include - #include - #include - #include - #include "k64f_soc.h" - #include <../../libraries/include/stdmisc.h> -#elif defined(__ZPU__) - #include - #include - #include "zpu_soc.h" - #include - #include - #include -#else - #error "Target CPU not defined, use __ZPU__ or __K64F__" -#endif - -#include "ff.h" /* Declarations of FatFs API */ -#include "utils.h" -#include - - -// Global scope variables, used by pin assignment and configuration macros. -volatile uint32_t *ioPin[MAX_TRANZPUTER_PINS]; -uint8_t pinMap[MAX_TRANZPUTER_PINS]; -uint32_t volatile *ms; -t_z80Control z80Control; - - -// Dummy function to override a weak definition in the Teensy library. Currently the yield functionality is not -// needed within apps running on the K64F it is only applicable in the main OS. -// -void yield(void) -{ - return; -} - -// Method to setup the pins and the pin map to power up default. -// The OS millisecond counter address is passed into this library to gain access to time without the penalty of procedure calls. -// Time is used for timeouts and seriously affects pulse width of signals when procedure calls are made. -// -void setupPins(volatile uint32_t *millisecondTick) -{ - // Locals. - // - static uint8_t firstCall = 0; - - // This method can be called more than once as a convenient way to return all pins to default. On first call though - // the teensy library needs initialising, hence the static check. - // - if(firstCall == 0) - { - firstCall = 1; - _init_Teensyduino_internal_(); - - ms = millisecondTick; - } - - // Setup the map of a loop useable array with its non-linear Pin Number. - // - pinMap[Z80_A0] = Z80_A0_PIN; - pinMap[Z80_A1] = Z80_A1_PIN; - pinMap[Z80_A2] = Z80_A2_PIN; - pinMap[Z80_A3] = Z80_A3_PIN; - pinMap[Z80_A4] = Z80_A4_PIN; - pinMap[Z80_A5] = Z80_A5_PIN; - pinMap[Z80_A6] = Z80_A6_PIN; - pinMap[Z80_A7] = Z80_A7_PIN; - pinMap[Z80_A8] = Z80_A8_PIN; - pinMap[Z80_A9] = Z80_A9_PIN; - pinMap[Z80_A10] = Z80_A10_PIN; - pinMap[Z80_A11] = Z80_A11_PIN; - pinMap[Z80_A12] = Z80_A12_PIN; - pinMap[Z80_A13] = Z80_A13_PIN; - pinMap[Z80_A14] = Z80_A14_PIN; - pinMap[Z80_A15] = Z80_A15_PIN; - pinMap[Z80_A16] = Z80_A16_PIN; - pinMap[Z80_A17] = Z80_A17_PIN; - pinMap[Z80_A18] = Z80_A18_PIN; - - pinMap[Z80_D0] = Z80_D0_PIN; - pinMap[Z80_D1] = Z80_D1_PIN; - pinMap[Z80_D2] = Z80_D2_PIN; - pinMap[Z80_D3] = Z80_D3_PIN; - pinMap[Z80_D4] = Z80_D4_PIN; - pinMap[Z80_D5] = Z80_D5_PIN; - pinMap[Z80_D6] = Z80_D6_PIN; - pinMap[Z80_D7] = Z80_D7_PIN; - - pinMap[Z80_MEM0] = Z80_MEM0_PIN; - pinMap[Z80_MEM1] = Z80_MEM1_PIN; - pinMap[Z80_MEM2] = Z80_MEM2_PIN; - pinMap[Z80_MEM3] = Z80_MEM3_PIN; - pinMap[Z80_MEM4] = Z80_MEM4_PIN; - - pinMap[Z80_IORQ] = Z80_IORQ_PIN; - pinMap[Z80_MREQ] = Z80_MREQ_PIN; - pinMap[Z80_RD] = Z80_RD_PIN; - pinMap[Z80_WR] = Z80_WR_PIN; - pinMap[Z80_WAIT] = Z80_WAIT_PIN; - pinMap[Z80_BUSACK] = Z80_BUSACK_PIN; - - pinMap[Z80_NMI] = Z80_NMI_PIN; - pinMap[Z80_INT] = Z80_INT_PIN; - - pinMap[CTL_BUSACK] = CTL_BUSACK_PIN; - pinMap[CTL_BUSRQ] = CTL_BUSRQ_PIN; - pinMap[CTL_RFSH] = CTL_RFSH_PIN; - pinMap[CTL_HALT] = CTL_HALT_PIN; - pinMap[CTL_M1] = CTL_M1_PIN; - pinMap[CTL_CLK] = CTL_CLK_PIN; - pinMap[CTL_CLKSLCT] = CTL_CLKSLCT_PIN; - - // Now build the config array for all the ports. This aids in more rapid function switching as opposed to using - // the pinMode and digitalRead/Write functions provided in the Teensy lib. - // - for(uint8_t idx=0; idx < MAX_TRANZPUTER_PINS; idx++) - { - ioPin[pinMap[idx]] = portConfigRegister(pinMap[idx]); - - // Set to input, will change as functionality dictates. - pinInput(idx); - } - - // Initialise control structure. - z80Control.refreshAddr = 0x00; - z80Control.runCtrlLatch = readCtrlLatch(); - z80Control.ctrlMode = Z80_RUN; - z80Control.busDir = TRISTATE; - - return; -} - - -// Method to request the Z80 Bus. This halts the Z80 and sets all main signals to tri-state. -// Return: 0 - Z80 Bus obtained. -// 1 - Failed to obtain the Z80 bus. -uint8_t reqZ80Bus(uint32_t timeout) -{ - // Locals. - // - uint8_t result = 0; - uint32_t startTime = *ms; - - // Set the control pins in order to request the bus. - // - pinOutputSet(CTL_BUSRQ, HIGH); - pinInput(Z80_BUSACK); - - // Set BUSRQ low which sets the Z80 BUSRQ low. - pinLow(CTL_BUSRQ); - - // Wait for the Z80 to acknowledge the request. - while((*ms - startTime) < timeout && pinGet(Z80_BUSACK)); - - // If we timed out, reset the pins and return error. - // - if((*ms - startTime) >= timeout) - { - pinInput(CTL_BUSRQ); - result = 1; - } - - // Store the control latch state before we start modifying anything enabling a return to the pre bus request state. - z80Control.runCtrlLatch = readCtrlLatch(); - - return(result); -} - -// Simple method to release the Z80 Bus. -// -void relinquishZ80Bus(void) -{ - pinInput(CTL_BUSACK); - pinInput(CTL_BUSRQ); - return; -} - -// Method to request access to the main motherboard circuitry. This involves requesting the Z80 bus and then -// setting the Z80_RD and Z80_WR signals to low, this maps to a ENABLE_BUS signal which ensures that one half of -// the Mainboard BUSACK request signal is disabled (BUSACK to the motherboard has the opposite meaning, when active -// the mainboard is tri-stated and signals do not pass from th e Z80 and local memory to the mainboard). The other -// half of the mainboard request signal is disabled by setting CTL_BUSACK high. -// -uint8_t reqMainboardBus(uint32_t timeout) -{ - // Locals. - // - uint8_t result = 0; - - // Set the CTL BUSACK signal high so we dont assert the mainboard BUSACK signal. - pinOutputSet(CTL_BUSACK, HIGH); - - // Requst the Z80 Bus to tri-state the Z80. - if((result=reqZ80Bus(timeout)) == 0) - { - // Now request the mainboard by setting BUSACK high and Z80_RD/Z80_WR low. - pinOutput(Z80_RD); - pinOutput(Z80_WR); - - // A special mode in the FlashRAM decoder detects both RD and WR being low at the same time, this is not feasible under normal Z80 operating conditions, but what it signals - // here is to raise ENABLE_BUS which ensures that BUSACK on the mainboard is deasserted. This is for the case where the Z80 may be running in tranZPUter memory with the - // mainboard disabled. - pinLow(Z80_RD); - pinLow(Z80_WR); - - // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. - for(volatile uint32_t pulseWidth=0; pulseWidth < 1; pulseWidth++); - - // Immediately return the RD/WR to HIGH to complete the ENABLE_BUS latch action. - pinHigh(Z80_RD); - pinHigh(Z80_WR); - - // Store the mode. - z80Control.ctrlMode = MAINBOARD_ACCESS; - - // For mainboard access, MEM4:0 should be 0 so no activity is made to the tranZPUter circuitry except the control latch. - z80Control.curCtrlLatch = 0b00000000; - } else - { - printf("Failed to request Mainboard Bus\n"); - } - - return(result); -} - -// Method to request the local tranZPUter bus. This involves making a Z80 bus request and when relinquished, setting -// the CTL_BUSACK signal to low which disables (tri-states) the mainboard circuitry. -// -uint8_t reqTranZPUterBus(uint32_t timeout) -{ - // Locals. - // - uint8_t result = 0; - - // Set the CTL BUSACK signal high so we dont assert the mainboard BUSACK signal. - pinOutputSet(CTL_BUSACK, HIGH); - - // Requst the Z80 Bus to tri-state the Z80. - if((result=reqZ80Bus(timeout)) == 0) - { - // Now disable the mainboard by setting BUSACK low. - pinLow(CTL_BUSACK); - - // Store the mode. - z80Control.ctrlMode = TRANZPUTER_ACCESS; - - // For tranZPUter access, MEM4:0 should be 1 so no activity is made to the mainboard circuitry. - z80Control.curCtrlLatch = 0b00011111; - } - - return(result); -} - -// Method to set all the pins to be able to perform a transaction on the Z80 bus. -// -void setupSignalsForZ80Access(enum BUS_DIRECTION dir) -{ - // Address lines (apart from the upper bank address lines A18:16) need to be outputs. - for(uint8_t idx=Z80_A0; idx <= Z80_A15; idx++) - { - pinOutput(idx); - } - pinInput(Z80_A16); // The upper address bits can only be changed via the 273 latch IC which requires a Z80 IO Write transaction. - pinInput(Z80_A17); - pinInput(Z80_A18); - - // Control signals need to be output and deasserted. - // - pinOutputSet(Z80_IORQ, HIGH); - pinOutputSet(Z80_MREQ, HIGH); - pinOutputSet(Z80_RD, HIGH); - pinOutputSet(Z80_WR, HIGH); - - // Additional control lines need to be outputs and deasserted. These lines are for the main motherboard and not used on the tranZPUter. - // - pinOutputSet(CTL_HALT, HIGH); - pinOutputSet(CTL_RFSH, HIGH); - pinOutputSet(CTL_M1, HIGH); - - // Setup bus direction. - // - setZ80Direction(dir); - return; -} - -// Method to release the Z80, set all signals to input and disable BUSRQ. -// -void releaseZ80(void) -{ - // All address lines to inputs, upper A18:16 are always defined as inputs as they can only be read by the K64F, they are driven by a - // latch on the tranZPUter board. - // - for(uint8_t idx=Z80_A0; idx <= Z80_A15; idx++) - { - pinInput(idx); - } - // Same for data lines, revert to being inputs. - for(uint8_t idx=Z80_D0; idx <= Z80_D7; idx++) - { - pinInput(idx); - } - - // All control signals to inputs. - pinInput(CTL_HALT); - pinInput(CTL_RFSH); - pinInput(CTL_M1); - pinInput(Z80_IORQ); - pinInput(Z80_MREQ); - pinInput(Z80_RD); - pinInput(Z80_WR); - - // Finally release the Z80 by deasserting the CTL_BUSRQ signal. - relinquishZ80Bus(); - - // Store the mode. - z80Control.ctrlMode = Z80_RUN; - - // Indicate bus direction. - z80Control.busDir = TRISTATE; - return; -} - - -// Method to write a memory mapped byte onto the Z80 bus. -// As the underlying motherboard is 2MHz we keep to its timing best we can in C, for faster motherboards this method may need to -// be coded in assembler. -// -uint8_t writeZ80Memory(uint16_t addr, uint8_t data) -{ - // Locals. - uint32_t startTime = *ms; - - // Set the data and address on the bus. - // - setZ80Addr(addr); - setZ80Data(data); - pinLow(Z80_MREQ); - - // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait - // signals and faster memory. - // - if(z80Control.ctrlMode == MAINBOARD_ACCESS) - { - // If WAIT has been asserted, loop. Set a timeout to prevent a total lockup. Wait shouldnt exceed 100mS, it it does there is a - // hardware fault and components such as DRAM will lose data due to no refresh. - while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); - - // Start the write cycle, MREQ and WR go low. - pinLow(Z80_WR); - - // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. - //for(volatile uint32_t pulseWidth=0; pulseWidth < 2; pulseWidth++); - - // Another wait loop check as the Z80 can assert wait at the time of Write or anytime before it is deasserted. - while((*ms - startTime) < 200 && pinGet(Z80_WAIT) == 0); - } else - { - // Start the write cycle, MREQ and WR go low. - pinLow(Z80_WR); - } - - // Complete the write cycle. - // - pinHigh(Z80_WR); - pinHigh(Z80_MREQ); - - return(0); -} - -// Method to read a memory mapped byte from the Z80 bus. -// Keep to the Z80 timing diagram, but for better accuracy of matching the timing diagram this needs to be coded in assembler. -// -uint8_t readZ80Memory(uint16_t addr) -{ - // Locals. - uint32_t startTime = *ms; - uint8_t data; - - // Set the address on the bus and assert MREQ and RD. - // - setZ80Addr(addr); - pinLow(Z80_MREQ); - pinLow(Z80_RD); - - // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait - // signals and faster memory. - // - if(z80Control.ctrlMode == MAINBOARD_ACCESS) - { - // A wait loop check as the Z80 can assert wait during the Read operation to request more time. Set a timeout in case of hardware lockup. - while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); - - // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. This gives time for the addressed device to present the data - // on the data bus. - for(volatile uint32_t pulseWidth=0; pulseWidth < 1; pulseWidth++); - } - - // Fetch the data before deasserting the signals. - // - data = readDataBus(); - - // Complete the read cycle. - // - pinHigh(Z80_RD); - pinHigh(Z80_MREQ); - - // Finally pass back the byte read to the caller. - return(data); -} - - -// Method to write a byte onto the Z80 I/O bus. This method is almost identical to the memory mapped method but are kept seperate for different -// timings as needed, the more code will create greater delays in the pulse width and timing. -// -// As the underlying motherboard is 2MHz we keep to its timing best we can in C, for faster motherboards this method may need to -// be coded in assembler. -// -uint8_t writeZ80IO(uint16_t addr, uint8_t data) -{ - // Locals. - uint32_t startTime = *ms; - - // Set the data and address on the bus. - // - setZ80Addr(addr); - setZ80Data(data); - pinLow(Z80_IORQ); - - // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait - // signals and faster memory. - // - if(z80Control.ctrlMode == MAINBOARD_ACCESS) - { - // If WAIT has been asserted, loop. Set a timeout to prevent a total lockup. Wait shouldnt exceed 100mS, it it does there is a - // hardware fault and components such as DRAM will lose data due to no refresh. - while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); - - // Start the write cycle, MREQ and WR go low. - pinLow(Z80_WR); - - // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. - //for(volatile uint32_t pulseWidth=0; pulseWidth < 2; pulseWidth++); - - // Another wait loop check as the Z80 can assert wait at the time of Write or anytime before it is deasserted. - while((*ms - startTime) < 200 && pinGet(Z80_WAIT) == 0); - } else - { - // Start the write cycle, MREQ and WR go low. - pinLow(Z80_WR); - } - - // Complete the write cycle. - // - pinHigh(Z80_WR); - pinHigh(Z80_IORQ); - - return(0); -} - -// Method to read a byte from the Z80 I/O bus. This method is almost identical to the memory mapped method but are kept seperate for different -// timings as needed, the more code will create greater delays in the pulse width and timing. -// -// As the underlying motherboard is 2MHz we keep to its timing best we can in C, for faster motherboards this method may need to -// be coded in assembler. -uint8_t readZ80IO(uint16_t addr) -{ - // Locals. - uint32_t startTime = *ms; - uint8_t data; - - // Set the address on the bus and assert MREQ and RD. - // - setZ80Addr(addr); - pinLow(Z80_IORQ); - pinLow(Z80_RD); - - // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait - // signals and faster memory. - // - if(z80Control.ctrlMode == MAINBOARD_ACCESS) - { - // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. This gives time for the addressed device to present the data - // on the data bus. - //for(volatile uint32_t pulseWidth=0; pulseWidth < 2; pulseWidth++); - - // A wait loop check as the Z80 can assert wait during the Read operation to request more time. Set a timeout in case of hardware lockup. - while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); - } - - // Fetch the data before deasserting the signals. - // - data = readDataBus(); - - // Complete the read cycle. - // - pinHigh(Z80_RD); - pinHigh(Z80_IORQ); - - // Finally pass back the byte read to the caller. - return(data); -} - -// Method to perform a refresh cycle on the z80 mainboard bus to ensure dynamic RAM contents are maintained during extended bus control periods. -// -// Under normal z80 processing a refresh cycle is issued every instruction during the T3/T4 cycles. As we arent reading instructions but just reading/writing -// then the refresh cycles are made after a cnofigurable set number of bus transactions (ie. 128 read/writes). This has to occur when either of the busses -// are under K64F control so a call to this method should be made frequently. -// -void refreshZ80(void) -{ - // Locals. - volatile uint8_t idx = 0; - - // Set 7 bits on the address bus. - setZ80RefreshAddr(z80Control.refreshAddr); - - // If we are controlling the tranZPUter bus then a switch to the mainboard needs to be made first. - // - if(z80Control.ctrlMode == TRANZPUTER_ACCESS) - { - // Quick way to gain mainboard access, force an enable of the Z80_BUSACK on the mainboard by setting RD/WR low, this fires an enable pulse at the 279 RS Flip Flop - // and setting CTL_BUSACK high enables the second component forming the Z80_BUSACK signal. - pinLow(Z80_RD); - pinLow(Z80_WR); - pinHigh(Z80_RD); - pinHigh(Z80_WR); - pinHigh(CTL_BUSACK); - } - - // Assert Refresh. - pinLow(CTL_RFSH); - pinLow(Z80_MREQ); - idx++; // Increase the pulse width of the MREQ signal. - pinHigh(Z80_MREQ); - pinHigh(CTL_RFSH); - - // Restore access to tranZPUter bus if this was the original mode. - if(z80Control.ctrlMode == TRANZPUTER_ACCESS) - { - // Restore tranZPUter bus control. - pinLow(CTL_BUSACK); - } - - // Increment refresh address to complete. - z80Control.refreshAddr++; - z80Control.refreshAddr &= 0x7f; - return; -} - -// Method to perform a full row refresh on the dynamic DRAM. This method writes out a full 7bit address so that all rows are refreshed. -// -void refreshZ80AllRows(void) -{ - // Locals. - volatile uint8_t idx; - - // If we are controlling the tranZPUter bus then a switch to the mainboard needs to be made first. - // - if(z80Control.ctrlMode == TRANZPUTER_ACCESS) - { - // Quick way to gain mainboard access, force an enable of the Z80_BUSACK on the mainboard by setting RD/WR low, this fires an enable pulse at the 279 RS Flip Flop - // and setting CTL_BUSACK high enables the second component forming the Z80_BUSACK signal. - pinLow(Z80_RD); - pinLow(Z80_WR); - pinHigh(Z80_RD); - pinHigh(Z80_WR); - pinHigh(CTL_BUSACK); - } - - // Loop through all 7 bits of refresh rows. - idx = 0; - while(idx < 0x80) - { - // Set 7 bits on the address bus. - setZ80RefreshAddr(idx); - - // Assert Refresh. - pinLow(CTL_RFSH); - pinLow(Z80_MREQ); - idx++; - pinHigh(Z80_MREQ); - pinHigh(CTL_RFSH); - } - - // Restore access to tranZPUter bus if this was the original mode. - if(z80Control.ctrlMode == TRANZPUTER_ACCESS) - { - // Restore tranZPUter bus control. - pinLow(CTL_BUSACK); - } - return; -} - -// Method to fill memory under the Z80 control, either the mainboard or tranZPUter memory. -// -void fillZ80Memory(uint32_t addr, uint32_t size, uint8_t data, uint8_t mainBoard) -{ - // Locals. - - if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) - { - // Setup the pins to perform a read operation (after setting the latch to starting value). - // - setupSignalsForZ80Access(WRITE); - writeCtrlLatch(z80Control.curCtrlLatch); - - // Fill the memory but every FILL_RFSH_BYTE_CNT perform a DRAM refresh. - // - for(uint32_t idx=addr; idx < (addr+size); idx++) - { - // If the address changes the upper address bits, update the latch to reflect the change. - if((uint8_t)(idx >> 16) != readUpperAddr()) - { - // Write the upper address bits to the 273 control latch. The lower 5 bits remain as set by the bus select methods. - writeCtrlLatch( (uint8_t)((idx >> 11) | (z80Control.curCtrlLatch & 0b00011111)) ); - } - - if(idx % FILL_RFSH_BYTE_CNT == 0) - { - // Perform a full row refresh to maintain the DRAM. - refreshZ80AllRows(); - } - writeZ80Memory((uint16_t)idx, data); - } - - // Restore the control latch to its original configuration. - // - setZ80Direction(WRITE); - writeCtrlLatch(z80Control.runCtrlLatch); - releaseZ80(); - } - return; -} - -// A method to read the full video frame buffer from the Sharp MZ80A and store it in local memory (control structure). -// No refresh cycles are needed as we grab the frame but between frames a full refresh is performed. -// -void captureVideoFrame(enum VIDEO_FRAMES frame, uint8_t noAttributeFrame) -{ - // Locals. - - if(reqMainboardBus(100) == 0) - { - // Setup the pins to perform a read operation (after setting the latch to starting value). - // - setupSignalsForZ80Access(WRITE); - writeCtrlLatch(z80Control.curCtrlLatch); - setZ80Direction(READ); - - // No need for refresh as we take less than 2ms time, just grab the video frame. - for(uint16_t idx=0; idx < MZ_VID_RAM_SIZE; idx++) - { - z80Control.videoRAM[frame][idx] = readZ80Memory((uint16_t)idx+MZ_VID_RAM_ADDR); - } - - // Perform a full row refresh to maintain the DRAM. - refreshZ80AllRows(); - - // If flag not set capture the attribute RAM. This is normally not present on a standard Sharp MZ80A only present on models - // with the MZ80A Colour Board upgrade. - // - if(noAttributeFrame == 0) - { - // Same for the attribute frame, no need for refresh as we take 2ms or less, just grab it. - for(uint16_t idx=0; idx < MZ_ATTR_RAM_SIZE; idx++) - { - z80Control.attributeRAM[frame][idx] = readZ80Memory((uint16_t)idx+MZ_ATTR_RAM_ADDR); - } - - // Perform a full row refresh to maintain the DRAM. - refreshZ80AllRows(); - } - - // Restore the control latch to its original configuration. - // - setZ80Direction(WRITE); - writeCtrlLatch(z80Control.runCtrlLatch); - releaseZ80(); - } - return; -} - -// Method to refresh the video frame buffer on the Sharp MZ80A with the data held in local memory. -// -void refreshVideoFrame(enum VIDEO_FRAMES frame, uint8_t scrolHome, uint8_t noAttributeFrame) -{ - // Locals. - - if(reqMainboardBus(100) == 0) - { - // Setup the pins to perform a write operation. - // - setupSignalsForZ80Access(WRITE); - writeCtrlLatch(z80Control.curCtrlLatch); - - // No need for refresh as we take less than 2ms time, just write the video frame. - for(uint16_t idx=0; idx < MZ_VID_RAM_SIZE; idx++) - { - writeZ80Memory((uint16_t)idx+MZ_VID_RAM_ADDR, z80Control.videoRAM[frame][idx]); - } - - // Perform a full row refresh to maintain the DRAM. - refreshZ80AllRows(); - - // If flag not set write out to the attribute RAM. This is normally not present on a standard Sharp MZ80A only present on models - // with the MZ80A Colour Board upgrade. - // - if(noAttributeFrame == 0) - { - // No need for refresh as we take less than 2ms time, just write the video frame. - for(uint16_t idx=0; idx < MZ_ATTR_RAM_SIZE; idx++) - { - writeZ80Memory((uint16_t)idx+MZ_ATTR_RAM_ADDR, z80Control.attributeRAM[frame][idx]); - } - - // Perform a full row refresh to maintain the DRAM. - refreshZ80AllRows(); - } - - // If the Scroll Home flag is set, this means execute a read against the hardware scoll register to restore the position 0,0 to location MZ_VID_RAM_ADDR. - if(scrolHome) - { - setZ80Direction(READ); - readZ80Memory((uint16_t)MZ_SCROL_BASE); - } - - // Restore the control latch to its original configuration. - // - setZ80Direction(WRITE); - writeCtrlLatch(z80Control.runCtrlLatch); - releaseZ80(); - } - return; -} - -// Method to load up the local video frame buffer from a file. -// -FRESULT loadVideoFrameBuffer(char *src, enum VIDEO_FRAMES frame) -{ - // Locals. - // - FIL File; - unsigned int readSize; - FRESULT fr0; - - // Sanity check on filenames. - if(src == NULL) - return(FR_INVALID_PARAMETER); - - // Try and open the source file. - fr0 = f_open(&File, src, FA_OPEN_EXISTING | FA_READ); - - // If no errors in opening the file, proceed with reading and loading into memory. - if(!fr0) - { - memset(z80Control.videoRAM[frame], MZ_VID_DFLT_BYTE, MZ_VID_RAM_SIZE); - fr0 = f_read(&File, z80Control.videoRAM[frame], MZ_VID_RAM_SIZE, &readSize); - if (!fr0) - { - memset(z80Control.attributeRAM[frame], MZ_ATTR_DFLT_BYTE, MZ_ATTR_RAM_SIZE); - fr0 = f_read(&File, z80Control.attributeRAM[frame], MZ_ATTR_RAM_SIZE, &readSize); - } - - // Close to sync files. - f_close(&File); - } else - { - printf("File not found:%s\n", src); - } - - return(fr0 ? fr0 : FR_OK); -} - -// Method to save the local video frame buffer into a file. -// -FRESULT saveVideoFrameBuffer(char *dst, enum VIDEO_FRAMES frame) -{ - // Locals. - // - FIL File; - unsigned int writeSize; - FRESULT fr0; - - // Sanity check on filenames. - if(dst == NULL) - return(FR_INVALID_PARAMETER); - - // Try and create the destination file. - fr0 = f_open(&File, dst, FA_CREATE_ALWAYS | FA_WRITE); - - // If no errors in opening the file, proceed with reading and loading into memory. - if(!fr0) - { - // Write the entire framebuffer to the SD file, video then attribute. - // - fr0 = f_write(&File, z80Control.videoRAM[frame], MZ_VID_RAM_SIZE, &writeSize); - if (!fr0 && writeSize == MZ_VID_RAM_SIZE) - { - fr0 = f_write(&File, z80Control.attributeRAM[frame], MZ_ATTR_RAM_SIZE, &writeSize); - } - - // Close to sync files. - f_close(&File); - } else - { - printf("Cannot create file:%s\n", dst); - } - - return(fr0 ? fr0 : FR_OK); -} - - -// Method to load a file from the SD card directly into the tranZPUter static RAM or mainboard RAM. -// -FRESULT loadZ80Memory(char *src, uint32_t addr, uint8_t mainBoard, uint8_t releaseBus) -{ - // Locals. - // - FIL File; - uint32_t loadSize = 0L; - uint32_t memPtr = addr; - unsigned int readSize; - unsigned char buf[SECTOR_SIZE]; - FRESULT fr0; - - // Sanity check on filenames. - if(src == NULL) - return(FR_INVALID_PARAMETER); - - // Try and open the source file. - fr0 = f_open(&File, src, FA_OPEN_EXISTING | FA_READ); - - // If no errors in opening the file, proceed with reading and loading into memory. - if(!fr0) - { - // If the Z80 is in RUN mode, request the bus. - // This mechanism allows for the load command to leave the BUS under the tranZPUter control for upload of multiple files. - // Care needs to be taken though that no more than 2-3ms passes without a call to refreshZ80AllRows() otherwise memory loss - // may occur. - // - if(z80Control.ctrlMode == Z80_RUN) - { - // Request the board according to the mainboard flag, mainboard = 1 then the mainboard is controlled otherwise the tranZPUter board. - if(mainBoard == 0) - { - reqTranZPUterBus(100); - } else - { - reqMainboardBus(100); - } - - // If successful, setup the control pins for upload mode. - // - if(z80Control.ctrlMode != Z80_RUN) - { - // Setup the pins to perform a write operation. - // - setupSignalsForZ80Access(WRITE); - - // Setup the control latch to the required starting configuration. - writeCtrlLatch(z80Control.curCtrlLatch); - } - } else - { - // See if the bus needs changing. - // - enum CTRL_MODE newMode = (mainBoard == 0) ? TRANZPUTER_ACCESS : MAINBOARD_ACCESS; - reqZ80BusChange(newMode); - } - - // If we have taken control of the bus, commence upload. - // - if(z80Control.ctrlMode != Z80_RUN) - { - // Loop, reading a sector at a time from SD file and writing it directly into the Z80 tranZPUter RAM or mainboard RAM. - // - loadSize = 0; - memPtr = addr; - for (;;) { - // Wrap a disk read with two full refresh periods to counter for the amount of time taken to read disk. - refreshZ80AllRows(); - fr0 = f_read(&File, buf, SECTOR_SIZE, &readSize); - refreshZ80AllRows(); - if (fr0 || readSize == 0) break; /* error or eof */ - - // Go through each byte in sector and send to Z80 bus. - for(unsigned int idx=0; idx < readSize; idx++) - { - // If the address changes the upper address bits, update the latch to reflect the change. - if((uint8_t)(memPtr >> 16) != readUpperAddr()) - { - // Write the upper address bits to the 273 control latch. The lower 5 bits remain as set by the bus select methods. - writeCtrlLatch( (uint8_t)((memPtr >> 11) | (z80Control.curCtrlLatch & 0b00011111)) ); - } - - // At the halfway mark perform a full refresh. - if(idx == (SECTOR_SIZE/2)) - { - refreshZ80AllRows(); - } - - // And now write the byte and to the next address! - writeZ80Memory((uint16_t)memPtr, buf[idx]); - memPtr++; - } - loadSize += readSize; - } - } else - { - printf("Failed to request Z80 access.\n"); - fr0 = FR_INT_ERR; - } - - // Close to sync files. - f_close(&File); - } else - { - printf("File not found:%s\n", src); - } - - // If requested or an error occurs, then release the Z80 bus as no more uploads will be taking place in this batch. - // - if(releaseBus == 1 || fr0) - { - // Restore the control latch to its original configuration. - // - writeCtrlLatch(z80Control.runCtrlLatch); - releaseZ80(); - } - - return(fr0 ? fr0 : FR_OK); -} - -// Method to read a section of the tranZPUter/mainboard memory and store it in an SD file. -// -FRESULT saveZ80Memory(char *dst, uint32_t addr, uint32_t size, uint8_t mainBoard) -{ - // Locals. - // - FIL File; - uint32_t saveSize = 0L; - uint32_t sizeToWrite; - uint32_t memPtr = addr; - uint32_t endAddr = addr + size; - unsigned int writeSize; - unsigned char buf[SECTOR_SIZE]; - FRESULT fr0; - - // Sanity check on filenames. - if(dst == NULL || size == 0) - return(FR_INVALID_PARAMETER); - - // Try and create the destination file. - fr0 = f_open(&File, dst, FA_CREATE_ALWAYS | FA_WRITE); - - // If no errors in opening the file, proceed with reading and loading into memory. - if(!fr0) - { - if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) - { - // Setup the pins to perform a read operation (after setting the latch to starting value). - // - setupSignalsForZ80Access(WRITE); - writeCtrlLatch(z80Control.curCtrlLatch); - setZ80Direction(READ); - - // Loop, reading a sector worth of data (or upto limit remaining) from the Z80 tranZPUter RAM or mainboard RAM and writing it into the open SD card file. - // - saveSize = 0; - for (;;) { - - // Work out how many bytes to write in the sector then fetch from the Z80. - sizeToWrite = (endAddr-saveSize) > SECTOR_SIZE ? SECTOR_SIZE : endAddr - saveSize; - for(unsigned int idx=0; idx < sizeToWrite; idx++) - { - // If the address changes the upper address bits, update the latch to reflect the change. - if((uint8_t)(memPtr >> 16) != readUpperAddr()) - { - // Write the upper address bits to the 273 control latch. The lower 5 bits remain as set by the bus select methods. - setZ80Direction(WRITE); - writeCtrlLatch( (uint8_t)((memPtr >> 11) | (z80Control.curCtrlLatch & 0b00011111)) ); - setZ80Direction(READ); - } - - // At the halfway mark perform a full refresh. - if(idx == (SECTOR_SIZE/2)) - { - refreshZ80AllRows(); - } - - // And now read the byte and to the next address! - buf[idx] = readZ80Memory((uint16_t)memPtr); - memPtr++; - } - - // Wrap disk write with two full refresh periods to counter for the amount of time taken to write to disk. - refreshZ80AllRows(); - fr0 = f_write(&File, buf, sizeToWrite, &writeSize); - refreshZ80AllRows(); - saveSize += writeSize; - if (fr0 || writeSize < sizeToWrite || saveSize >= size) break; // error, disk full or range written. - } - - // Restore the control latch to its original configuration. - // - setZ80Direction(WRITE); - writeCtrlLatch(z80Control.runCtrlLatch); - releaseZ80(); - printf("Saved %ld bytes, final address:%lx\n", saveSize, memPtr); - - } else - { - printf("Failed to request Z80 access.\n"); - } - - // Close to sync files. - f_close(&File); - - } else - { - printf("Cannot create file:%s\n", dst); - } - - return(fr0 ? fr0 : FR_OK); -} - -#if defined DEBUG -// Simple method to output the Z80 signals to the console - feel good factor. To be of real use the signals need to be captured against the system -// clock and the replayed, perhaps a todo! -// -void displaySignals(void) -{ - uint32_t ADDR = 0; - uint8_t DATA = 0; - uint8_t RD = 0; - uint8_t WR = 0; - uint8_t IORQ = 0; - uint8_t MREQ = 0; - uint8_t NMI = 0; - uint8_t INT = 0; - uint8_t M1 = 0; - uint8_t RFSH = 0; - uint8_t WAIT = 0; - uint8_t BUSRQ = 0; - uint8_t BUSACK = 0; - uint8_t ZBUSACK = 0; - uint8_t HALT = 0; - uint8_t CLKSLCT = 0; - - setupPins(NULL); - - printf("Z80 Bus Signals:\r\n"); - while(1) - { - ADDR = (pinGet(Z80_A18) & 0x1) << 18; - ADDR |= (pinGet(Z80_A17) & 0x1) << 17; - ADDR |= (pinGet(Z80_A16) & 0x1) << 16; - ADDR |= (pinGet(Z80_A15) & 0x1) << 15; - ADDR |= (pinGet(Z80_A14) & 0x1) << 14; - ADDR |= (pinGet(Z80_A13) & 0x1) << 13; - ADDR |= (pinGet(Z80_A12) & 0x1) << 12; - ADDR |= (pinGet(Z80_A11) & 0x1) << 11; - ADDR |= (pinGet(Z80_A10) & 0x1) << 10; - ADDR |= (pinGet(Z80_A9) & 0x1) << 9; - ADDR |= (pinGet(Z80_A8) & 0x1) << 8; - ADDR |= (pinGet(Z80_A7) & 0x1) << 7; - ADDR |= (pinGet(Z80_A6) & 0x1) << 6; - ADDR |= (pinGet(Z80_A5) & 0x1) << 5; - ADDR |= (pinGet(Z80_A4) & 0x1) << 4; - ADDR |= (pinGet(Z80_A3) & 0x1) << 3; - ADDR |= (pinGet(Z80_A2) & 0x1) << 2; - ADDR |= (pinGet(Z80_A1) & 0x1) << 1; - ADDR |= (pinGet(Z80_A0) & 0x1); - DATA = (pinGet(Z80_D7) & 0x1) << 7; - DATA |= (pinGet(Z80_D6) & 0x1) << 6; - DATA |= (pinGet(Z80_D5) & 0x1) << 5; - DATA |= (pinGet(Z80_D4) & 0x1) << 4; - DATA |= (pinGet(Z80_D3) & 0x1) << 3; - DATA |= (pinGet(Z80_D2) & 0x1) << 2; - DATA |= (pinGet(Z80_A1) & 0x1) << 1; - DATA |= (pinGet(Z80_D0) & 0x1); - RD=pinGet(Z80_RD); - WR=pinGet(Z80_WR); - MREQ=pinGet(Z80_MREQ); - IORQ=pinGet(Z80_IORQ); - NMI=pinGet(Z80_NMI); - INT=pinGet(Z80_INT); - M1=pinGet(CTL_M1); - RFSH=pinGet(CTL_RFSH); - WAIT=pinGet(Z80_WAIT); - BUSRQ=pinGet(CTL_BUSRQ); - BUSACK=pinGet(CTL_BUSACK); - ZBUSACK=pinGet(Z80_BUSACK); - HALT=pinGet(CTL_HALT); - CLKSLCT=pinGet(CTL_CLKSLCT); - - printf("\rADDR=%06lx %08x %3s %3s %3s %3s %3s %3s %2s %4s %4s %2s %2s %3s %4s %4s", ADDR, DATA, - (RD == 0 && MREQ == 0 && WR == 1 && IORQ == 1) ? "MRD" : " ", - (RD == 0 && IORQ == 0 && WR == 1 && MREQ == 1) ? "IRD" : " ", - (WR == 0 && MREQ == 0 && RD == 1 && IORQ == 1) ? "MWR" : " ", - (WR == 0 && IORQ == 0 && RD == 1 && MREQ == 1) ? "IWR" : " ", - (NMI == 0) ? "NMI" : " ", - (INT == 0) ? "INT" : " ", - (M1 == 0) ? "M1" : " ", - (RFSH == 0) ? "RFSH" : " ", - (WAIT == 0) ? "WAIT" : " ", - (BUSRQ == 0) ? "BR" : " ", - (BUSACK == 0) ? "BA" : " ", - (ZBUSACK == 0) ? "ZBA" : " ", - (HALT == 0) ? "HALT" : " ", - (CLKSLCT == 0) ? "CLKS" : " " - ); - } - - return; -} -#endif - - -#ifdef __cplusplus -} -#endif diff --git a/apps/include/tranzputer.h b/apps/include/tranzputer.h deleted file mode 100755 index c5af993..0000000 --- a/apps/include/tranzputer.h +++ /dev/null @@ -1,293 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Name: tranzputer.h -// Created: May 2020 -// Author(s): Philip Smart -// Description: The TranZPUter library. -// This file contains methods which allow applications to access and control the traZPUter board and the underlying Sharp MZ80A host. -// Credits: -// Copyright: (c) 2019-2020 Philip Smart -// -// History: May 2020 - Initial write of the TranZPUter 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 . -///////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef TRANZPUTER_H -#define TRANZPUTER_H - -#ifdef __cplusplus - extern "C" { -#endif - -// Configurable constants. -// -#define REFRESH_BYTE_COUNT 8 // This constant controls the number of bytes read/written to the z80 bus before a refresh cycle is needed. -#define FILL_RFSH_BYTE_CNT 256 // Number of bytes we can write before needing a full refresh for the DRAM. - -// IO addresses on the tranZPUter or mainboard. -// -#define IO_TZ_CTRLLATCH 0x60 - - -// SHarp MZ80A constants. -// -#define MZ_VID_RAM_ADDR 0xD000 // Start of Video RAM -#define MZ_VID_RAM_SIZE 2048 // Size of Video RAM. -#define MZ_VID_DFLT_BYTE 0x00 // Default character (SPACE) for video RAM. -#define MZ_ATTR_RAM_ADDR 0xD800 // On machines with the upgrade, the start of the Attribute RAM. -#define MZ_ATTR_RAM_SIZE 2048 // Size of the attribute RAM. -#define MZ_ATTR_DFLT_BYTE 0x07 // Default colour (White on Black) for the attribute. -#define MZ_SCROL_BASE 0xE200 // Base address of the hardware scroll registers. - - -// Pin Constants - Pins assigned at the hardware level to specific tasks/signals. -// -#define MAX_TRANZPUTER_PINS 47 -#define Z80_MEM0_PIN 46 -#define Z80_MEM1_PIN 47 -#define Z80_MEM2_PIN 48 -#define Z80_MEM3_PIN 49 -#define Z80_MEM4_PIN 50 -#define Z80_WR_PIN 10 -#define Z80_RD_PIN 12 -#define Z80_IORQ_PIN 8 -#define Z80_MREQ_PIN 9 -#define Z80_A0_PIN 39 -#define Z80_A1_PIN 38 -#define Z80_A2_PIN 37 -#define Z80_A3_PIN 36 -#define Z80_A4_PIN 35 -#define Z80_A5_PIN 34 -#define Z80_A6_PIN 33 -#define Z80_A7_PIN 32 -#define Z80_A8_PIN 31 -#define Z80_A9_PIN 30 -#define Z80_A10_PIN 29 -#define Z80_A11_PIN 28 -#define Z80_A12_PIN 27 -#define Z80_A13_PIN 26 -#define Z80_A14_PIN 25 -#define Z80_A15_PIN 24 -#define Z80_A16_PIN 23 -#define Z80_A17_PIN 22 -#define Z80_A18_PIN 21 -#define Z80_D0_PIN 0 -#define Z80_D1_PIN 1 -#define Z80_D2_PIN 2 -#define Z80_D3_PIN 3 -#define Z80_D4_PIN 4 -#define Z80_D5_PIN 5 -#define Z80_D6_PIN 6 -#define Z80_D7_PIN 7 -#define Z80_WAIT_PIN 13 -#define Z80_BUSACK_PIN 17 -#define Z80_NMI_PIN 43 -#define Z80_INT_PIN 44 -#define CTL_RFSH_PIN 45 -#define CTL_HALT_PIN 14 -#define CTL_M1_PIN 20 -#define CTL_BUSRQ_PIN 15 -#define CTL_BUSACK_PIN 16 -#define CTL_CLK_PIN 18 -#define CTL_CLKSLCT_PIN 19 - -// Customised pin manipulation methods implemented as stripped down macros. The original had too much additional overhead with procedure call and validation tests, -// speed is of the essence for this project as pins change mode and value constantly. -// -#define pinLow(a) *portClearRegister(pinMap[a]) = 1 -#define pinHigh(a) *portSetRegister(pinMap[a]) = 1 -#define pinSet(a, b) if(b) { *portSetRegister(pinMap[a]) = 1; } else { *portClearRegister(pinMap[a]) = 1; } -#define pinGet(a) *portInputRegister(pinMap[a]) -#define pinInput(a) { *portModeRegister(pinMap[a]) = 0; *ioPin[pinMap[a]] = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; } -#define pinOutput(a) { *portModeRegister(pinMap[a]) = 1;\ - *ioPin[pinMap[a]] = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);\ - *ioPin[pinMap[a]] &= ~PORT_PCR_ODE; } -#define pinOutputSet(a,b) { *portModeRegister(pinMap[a]) = 1;\ - *ioPin[pinMap[a]] = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);\ - *ioPin[pinMap[a]] &= ~PORT_PCR_ODE;\ - if(b) { *portSetRegister(pinMap[a]) = 1; } else { *portClearRegister(pinMap[a]) = 1; } } - -#define setZ80Data(a) { pinSet(Z80_D7, ((a >> 7) & 0x1)); pinSet(Z80_D6, ((a >> 6) & 0x1));\ - pinSet(Z80_D5, ((a >> 5) & 0x1)); pinSet(Z80_D4, ((a >> 4) & 0x1));\ - pinSet(Z80_D3, ((a >> 3) & 0x1)); pinSet(Z80_D2, ((a >> 2) & 0x1));\ - pinSet(Z80_D1, ((a >> 1) & 0x1)); pinSet(Z80_D0, ((a ) & 0x1)); } -#define setZ80Addr(a) { pinSet(Z80_A15, ((a >> 15) & 0x1)); pinSet(Z80_A14, ((a >> 14) & 0x1));\ - pinSet(Z80_A13, ((a >> 13) & 0x1)); pinSet(Z80_A12, ((a >> 12) & 0x1));\ - pinSet(Z80_A11, ((a >> 11) & 0x1)); pinSet(Z80_A10, ((a >> 10) & 0x1));\ - pinSet(Z80_A9, ((a >> 9) & 0x1)); pinSet(Z80_A8, ((a >> 8) & 0x1));\ - pinSet(Z80_A7, ((a >> 7) & 0x1)); pinSet(Z80_A6, ((a >> 6) & 0x1));\ - pinSet(Z80_A5, ((a >> 5) & 0x1)); pinSet(Z80_A4, ((a >> 4) & 0x1));\ - pinSet(Z80_A3, ((a >> 3) & 0x1)); pinSet(Z80_A2, ((a >> 2) & 0x1));\ - pinSet(Z80_A1, ((a >> 1) & 0x1)); pinSet(Z80_A0, ((a ) & 0x1)); } -#define setZ80RefreshAddr(a) { pinSet(Z80_A6, ((a >> 6) & 0x1)); pinSet(Z80_A5, ((a >> 5) & 0x1));\ - pinSet(Z80_A4, ((a >> 4) & 0x1)); pinSet(Z80_A3, ((a >> 3) & 0x1));\ - pinSet(Z80_A2, ((a >> 2) & 0x1)); pinSet(Z80_A1, ((a >> 1) & 0x1));\ - pinSet(Z80_A0, ((a ) & 0x1)); } -#define readDataBus() ( pinGet(Z80_D7) << 7 | pinGet(Z80_D6) << 6 | pinGet(Z80_D5) << 5 | pinGet(Z80_D4) << 4 |\ - pinGet(Z80_D3) << 3 | pinGet(Z80_D2) << 2 | pinGet(Z80_D1) << 1 | pinGet(Z80_D0) ) -//#define readCtrlLatch() ( pinGet(Z80_A18) << 7 | pinGet(Z80_A17) << 6 | pinGet(Z80_A16) << 5 | pinGet(Z80_MEM4) << 4 |\ -// pinGet(Z80_MEM3) << 3 | pinGet(Z80_MEM2) << 2 | pinGet(Z80_MEM1) << 1 | pinGet(Z80_MEM0) ) -// Special case during development where the pins for the MEM4:1 are not connected. -#define readCtrlLatch() ((pinGet(Z80_A18) << 7 | pinGet(Z80_A17) << 6 | pinGet(Z80_A16) << 5) & 0b11100000) -#define writeCtrlLatch(a) { writeZ80IO(IO_TZ_CTRLLATCH, a); } -#define readUpperAddr() ((pinGet(Z80_A18) << 2 | pinGet(Z80_A17) << 1 | pinGet(Z80_A16)) & 0b00000111) -#define setZ80Direction(a) { for(uint8_t idx=Z80_D0; idx <= Z80_D7; idx++) { if(a == WRITE) { pinOutput(idx); } else { pinInput(idx); } }; z80Control.busDir = a; } -#define reqZ80BusChange(a) { if(a == MAINBOARD_ACCESS && z80Control.ctrlMode == TRANZPUTER_ACCESS) \ - {\ - pinHigh(CTL_BUSACK);\ - z80Control.ctrlMode = MAINBOARD_ACCESS;\ - z80Control.curCtrlLatch = 0b00000000;\ - writeCtrlLatch(z80Control.curCtrlLatch);\ - } else if(a == TRANZPUTER_ACCESS && z80Control.ctrlMode == MAINBOARD_ACCESS)\ - {\ - pinLow(CTL_BUSACK);\ - z80Control.ctrlMode = TRANZPUTER_ACCESS;\ - z80Control.curCtrlLatch = 0b00011111;\ - writeCtrlLatch(z80Control.curCtrlLatch);\ - } } - - -// Enumeration of the various pins on the project. These enums make it easy to refer to a signal and they are mapped -// to the actual hardware pin via the pinMap array. -// One of the big advantages is that a swath of pins, such as the address lines, can be switched in a tight loop rather than -// individual pin assignments or clunky lists. -// -enum pinIdxToPinNumMap { - Z80_A0 = 0, - Z80_A1 = 1, - Z80_A2 = 2, - Z80_A3 = 3, - Z80_A4 = 4, - Z80_A5 = 5, - Z80_A6 = 6, - Z80_A7 = 7, - Z80_A8 = 8, - Z80_A9 = 9, - Z80_A10 = 10, - Z80_A11 = 11, - Z80_A12 = 12, - Z80_A13 = 13, - Z80_A14 = 14, - Z80_A15 = 15, - Z80_A16 = 16, - Z80_A17 = 17, - Z80_A18 = 18, - - Z80_D0 = 19, - Z80_D1 = 20, - Z80_D2 = 21, - Z80_D3 = 22, - Z80_D4 = 23, - Z80_D5 = 24, - Z80_D6 = 25, - Z80_D7 = 26, - - Z80_MEM0 = 27, - Z80_MEM1 = 28, - Z80_MEM2 = 29, - Z80_MEM3 = 30, - Z80_MEM4 = 31, - - Z80_IORQ = 32, - Z80_MREQ = 33, - Z80_RD = 34, - Z80_WR = 35, - Z80_WAIT = 36, - Z80_BUSACK = 37, - - Z80_NMI = 38, - Z80_INT = 39, - - CTL_BUSACK = 40, - CTL_BUSRQ = 41, - CTL_RFSH = 42, - CTL_HALT = 43, - CTL_M1 = 44, - CTL_CLK = 45, - CTL_CLKSLCT = 46 -}; - -// Possible control modes that the K64F can be in, do nothing where the Z80 runs normally, control the Z80 and mainboard, or control the Z80 and tranZPUter. -enum CTRL_MODE { - Z80_RUN = 0, - TRANZPUTER_ACCESS = 1, - MAINBOARD_ACCESS = 2 -}; - -// Possible bus directions that the K64F can setup for controlling the Z80. -enum BUS_DIRECTION { - READ = 0, - WRITE = 1, - TRISTATE = 2 -}; - -// Possible video frames stored internally. -// -enum VIDEO_FRAMES { - SAVED = 0, - WORKING = 1 -}; - -// Structure to maintain all the control and management variables so that the state of run is well known by any called method. -// -typedef struct { - uint8_t refreshAddr; // Refresh address for times when the K64F must issue refresh cycles on the Z80 bus. - uint8_t runCtrlLatch; // Latch value the Z80 is running with. - uint8_t curCtrlLatch; // Latch value set during tranZPUter access of the Z80 bus. - uint8_t videoRAM[2][2048]; // Two video memory buffer frames, allows for storage of original frame in [0] and working frame in [1]. - uint8_t attributeRAM[2][2048]; // Two attribute memory buffer frames, allows for storage of original frame in [0] and working frame in [1]. - enum CTRL_MODE ctrlMode; // Mode of control, ie normal Z80 Running, controlling mainboard, controlling tranZPUter. - enum BUS_DIRECTION busDir; // Direction the bus has been configured for. -} t_z80Control; - -// Application execution constants. -// - - -// References to variables within the main library code. -extern volatile uint32_t *ioPin[MAX_TRANZPUTER_PINS]; -extern uint8_t pinMap[MAX_TRANZPUTER_PINS]; - -// Prototypes. -// -void yield(void); -void setupPins(volatile uint32_t *); -uint8_t reqZ80Bus(uint32_t); -void relinquishZ80Bus(void); -uint8_t reqMainboardBus(uint32_t); -uint8_t reqTranZPUterBus(uint32_t); -void setupSignalsForZ80Access(enum BUS_DIRECTION); -void releaseZ80(void); -void refreshZ80(void); -uint8_t writeZ80Memory(uint16_t, uint8_t); -uint8_t readZ80Memory(uint16_t); -uint8_t writeZ80IO(uint16_t, uint8_t); -uint8_t readZ80IO(uint16_t); -void fillZ80Memory(uint32_t, uint32_t, uint8_t, uint8_t); -void captureVideoFrame(enum VIDEO_FRAMES, uint8_t); -void refreshVideoFrame(enum VIDEO_FRAMES, uint8_t, uint8_t); -FRESULT loadVideoFrameBuffer(char *, enum VIDEO_FRAMES); -FRESULT saveVideoFrameBuffer(char *, enum VIDEO_FRAMES); -FRESULT loadZ80Memory(char *, uint32_t, uint8_t, uint8_t); -FRESULT saveZ80Memory(char *, uint32_t, uint32_t, uint8_t); -// Debug methods. -void displaySignals(void); - -#ifdef __cplusplus -} -#endif -#endif // TRANZPUTER_H diff --git a/apps/tzclear/Makefile b/apps/tzclear/Makefile new file mode 100755 index 0000000..2bb4101 --- /dev/null +++ b/apps/tzclear/Makefile @@ -0,0 +1,84 @@ +######################################################################################################### +## +## Name: Makefile +## Created: July 2019 +## Author(s): Philip Smart +## Description: App Makefile - Build an App for the ZPU Test Application (zputa) or the zOS +## operating system. +## This makefile builds an app which is stored on an SD card and called by ZPUTA/zOS +## The app is for testing some component where the code is not built into ZPUTA or +## a user application for zOS. +## +## Credits: +## Copyright: (c) 2019-20 Philip Smart +## +## History: July 2019 - Initial Makefile created for template use. +## April 2020 - Added K64F as an additional target and resplit ZPUTA into zOS. +## +## Notes: Optional component enables: +## USELOADB - The Byte write command is implemented in hw#sw so use it. +## USE_BOOT_ROM - The target is ROM so dont use initialised data. +## MINIMUM_FUNTIONALITY - Minimise functionality to limit code size. +## +######################################################################################################### +## 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 . +######################################################################################################### + +APP_NAME = tzclear +APP_DIR = $(CURDIR)/.. +APP_COMMON_DIR = $(CURDIR)/../common +COMMON_DIR = $(CURDIR)/../../common +BASEDIR = ../../.. +TEENSYDIR = ../../teensy3/ + +# Override values given by parent make for this application as its memory usage differs from the standard app. +ifeq ($(__K64F__),1) + #override HEAPADDR = 0x2002f000 + #override HEAPSIZE = 0x00000000 + #override STACKADDR = 0x2002f000 + #override STACKSIZE = 0x00000000 + + # Modules making up tzpu. + APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(COMMON_DIR)/tranzputer.c + CFLAGS = + CPPFLAGS = + LDFLAGS = + LIBS = +else + + # Modules making up tcpu. + APP_C_SRC = #$(APP_COMMON_DIR)/sysutils.c $(APP_COMMON_DIR)/ctypelocal.c + CFLAGS = + CPPFLAGS = + LDFLAGS = -nostdlib + LIBS = -lumansi-zpu -limath-zpu +endif + +# Filter out the standard HEAP address and size, replacing with the ones required for this application. +# Useful for sub-makes +FILTER1 = $(filter-out $(filter HEAPADDR=%,$(MAKEFLAGS)), $(MAKEFLAGS)) +FILTER2 = $(filter-out $(filter HEAPSIZE=%,$(FILTER1)), $(FILTER1)) +NEWMAKEFLAGS = $(FILTER2) HEAPADDR=$(HEADADDR) HEAPSIZE=$(HEAPSIZE) + +ifeq ($(__K64F__),1) +include $(APP_DIR)/Makefile.k64f +else + +# There currently is no code for the ZPU, all development being done on the K64F for this app. +all: + +clean: + +install: +endif diff --git a/apps/tzclear/tzclear.c b/apps/tzclear/tzclear.c new file mode 100644 index 0000000..7675512 --- /dev/null +++ b/apps/tzclear/tzclear.c @@ -0,0 +1,258 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tzclear.c +// Created: May 2020 +// Author(s): Philip Smart +// Description: A TranZPUter helper utility, allowing the realtime clearing of the tranZPUter +// or host mainboard memory. +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(__K64F__) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include "k64f_soc.h" + #include <../../libraries/include/stdmisc.h> +#elif defined(__ZPU__) + #include + #include + #include "zpu_soc.h" + #include + #include + #include +#else + #error "Target CPU not defined, use __ZPU__ or __K64F__" +#endif +#include "interrupts.h" +#include "ff.h" /* Declarations of FatFs API */ +#include "utils.h" +// +#if defined __ZPUTA__ + #include "zputa_app.h" +#elif defined __ZOS__ + #include "zOS_app.h" +#else + #error OS not defined, use __ZPUTA__ or __ZOS__ +#endif +// +#include +#include +#include "tzclear.h" + +// Utility functions. +#include + +// Version info. +#define VERSION "v1.0" +#define VERSION_DATE "15/05/2020" +#define APP_NAME "TZCLEAR" + +// Simple help screen to remmber how this utility works!! +// +void usage(void) +{ + printf("%s %s\n", APP_NAME, VERSION); + printf("\nCommands:-\n"); + printf(" -h | --help This help text.\n"); + printf(" -a | --start Start address.\n"); + printf("\nOptions:-\n"); + printf(" -e | --end End address (alternatively use --size).\n"); + printf(" -s | --size Size of memory block to clear (alternatively use --end).\n"); + printf(" -b | --byte Byte value to place into each cleared memory location, defaults to 0x00.\n"); + printf(" -m | --mainboard Operations will take place on the MZ80A mainboard. Default without this flag is to target the tranZPUter memory.\n"); + printf(" -v | --verbose Output more messages.\n"); + + printf("\nExamples:\n"); + printf(" tzclear -a 0x000000 -s 0x200 -b 0xAA # Clears memory locations in the tranZPUter memory from 0x000000 to 0x000200 using value 0xAA.\n"); +} + +// Main entry and start point of a zOS/ZPUTA Application. Only 2 parameters are catered for and a 32bit return code, additional parameters can be added by changing the appcrt0.s +// startup code to add them to the stack prior to app() call. +// +// Return code for the ZPU is saved in _memreg by the C compiler, this is transferred to _memreg in zOS/ZPUTA in appcrt0.s prior to return. +// The K64F ARM processor uses the standard register passing conventions, return code is stored in R0. +// +uint32_t app(uint32_t param1, uint32_t param2) +{ + // Initialisation. + // + uint32_t startAddr = 0xFFFFFFFF; + uint32_t endAddr = 0xFFFFFFFF; + uint32_t memSize = 0xFFFFFFFF; + uint8_t byte = 0x00; + int argc = 0; + int help_flag = 0; + int mainboard_flag = 0; + int verbose_flag = 0; + int opt; + int option_index = 0; + long val = 0; + char *argv[20]; + char *ptr = strtok((char *)param1, " "); + + // Initialisation. + + // If the invoking command is given, add it to argv at the start. + // + if(param2 != 0) + { + argv[argc++] = (char *)param2; + } + + // Now convert the parameter line into argc/argv suitable for getopt to use. + while (ptr && argc < 20-1) + { + argv[argc++] = ptr; + ptr = strtok(0, " "); + } + argv[argc] = 0; + + // Define parameters to be processed. + static struct option long_options[] = + { + {"help", no_argument, 0, 'h'}, + {"start", required_argument, 0, 'a'}, + {"end", required_argument, 0, 'e'}, + {"size", required_argument, 0, 's'}, + {"byte", required_argument, 0, 'b'}, + {"mainboard", no_argument, 0, 'm'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + // Parse the command line options. + // + while((opt = getopt_long(argc, argv, ":hs:e:s:mv", long_options, &option_index)) != -1) + { + switch(opt) + { + case 'h': + help_flag = 1; + break; + + case 'm': + mainboard_flag = 1; + break; + + case 'a': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(5); + } + startAddr = (uint32_t)val; + break; + + case 'e': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(6); + } + endAddr = (uint32_t)val; + break; + + case 's': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(7); + } + memSize = (uint32_t)val; + break; + + case 'b': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(6); + } + byte = (uint8_t)val; + break; + + case 'v': + verbose_flag = 1; + break; + + case ':': + printf("Option %s needs a value\n", argv[optind-1]); + break; + case '?': + printf("Unknown option: %s, ignoring!\n", argv[optind-1]); + break; + } + } + + // Validate the input. + if(help_flag == 1) + { + usage(); + return(0); + } + if(startAddr == 0xFFFFFFFF) + { + printf("Please define the start address, size will default to 0x100.\n"); + return(10); + } + if(endAddr == 0xFFFFFFFF && memSize == 0xFFFFFFFF) + { + memSize = 0x100; + } else if(memSize == 0xFFFFFFFF) + { + memSize = endAddr - startAddr; + } + if(mainboard_flag == 1 && (startAddr > 0x10000 || startAddr + memSize > 0x10000)) + { + printf("Mainboard only has 64K, please change the address or size.\n"); + return(11); + } + if(mainboard_flag == 0 && (startAddr >= 0x80000 || startAddr + memSize > 0x80000)) + { + printf("tranZPUter board only has 512K, please change the address or size.\n"); + return(12); + } + + // Initialise the IO. + setupZ80Pins(1, G->millis); + + // Call the fill utility to clear memory. + // + fillZ80Memory(startAddr, memSize, byte, mainboard_flag); + + return(0); +} + +#ifdef __cplusplus +} +#endif diff --git a/apps/tzclear/tzclear.h b/apps/tzclear/tzclear.h new file mode 100755 index 0000000..ac411b6 --- /dev/null +++ b/apps/tzclear/tzclear.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tzclear.h +// Created: May 2020 +// Author(s): Philip Smart +// Description: A TranZPUter helper utility, allowing the realtime clearing of the tranZPUter +// or host mainboard memory. +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef TZCLEAR_H +#define TZCLEAR_H + +#ifdef __cplusplus + extern "C" { +#endif + +// Components to be embedded in the program. +// +// Filesystem components to be embedded in the program. + +// Application execution constants. +// + +#ifdef __cplusplus +} +#endif +#endif // TZCLEAR_H diff --git a/apps/tzdump/Makefile b/apps/tzdump/Makefile new file mode 100755 index 0000000..24955c5 --- /dev/null +++ b/apps/tzdump/Makefile @@ -0,0 +1,84 @@ +######################################################################################################### +## +## Name: Makefile +## Created: July 2019 +## Author(s): Philip Smart +## Description: App Makefile - Build an App for the ZPU Test Application (zputa) or the zOS +## operating system. +## This makefile builds an app which is stored on an SD card and called by ZPUTA/zOS +## The app is for testing some component where the code is not built into ZPUTA or +## a user application for zOS. +## +## Credits: +## Copyright: (c) 2019-20 Philip Smart +## +## History: July 2019 - Initial Makefile created for template use. +## April 2020 - Added K64F as an additional target and resplit ZPUTA into zOS. +## +## Notes: Optional component enables: +## USELOADB - The Byte write command is implemented in hw#sw so use it. +## USE_BOOT_ROM - The target is ROM so dont use initialised data. +## MINIMUM_FUNTIONALITY - Minimise functionality to limit code size. +## +######################################################################################################### +## 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 . +######################################################################################################### + +APP_NAME = tzdump +APP_DIR = $(CURDIR)/.. +APP_COMMON_DIR = $(CURDIR)/../common +COMMON_DIR = $(CURDIR)/../../common +BASEDIR = ../../.. +TEENSYDIR = ../../teensy3/ + +# Override values given by parent make for this application as its memory usage differs from the standard app. +ifeq ($(__K64F__),1) + #override HEAPADDR = 0x2002f000 + #override HEAPSIZE = 0x00000000 + #override STACKADDR = 0x2002f000 + #override STACKSIZE = 0x00000000 + + # Modules making up tzpu. + APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(COMMON_DIR)/tranzputer.c + CFLAGS = + CPPFLAGS = + LDFLAGS = + LIBS = +else + + # Modules making up tcpu. + APP_C_SRC = #$(APP_COMMON_DIR)/sysutils.c $(APP_COMMON_DIR)/ctypelocal.c + CFLAGS = + CPPFLAGS = + LDFLAGS = -nostdlib + LIBS = -lumansi-zpu -limath-zpu +endif + +# Filter out the standard HEAP address and size, replacing with the ones required for this application. +# Useful for sub-makes +FILTER1 = $(filter-out $(filter HEAPADDR=%,$(MAKEFLAGS)), $(MAKEFLAGS)) +FILTER2 = $(filter-out $(filter HEAPSIZE=%,$(FILTER1)), $(FILTER1)) +NEWMAKEFLAGS = $(FILTER2) HEAPADDR=$(HEADADDR) HEAPSIZE=$(HEAPSIZE) + +ifeq ($(__K64F__),1) +include $(APP_DIR)/Makefile.k64f +else + +# There currently is no code for the ZPU, all development being done on the K64F for this app. +all: + +clean: + +install: +endif diff --git a/apps/tzdump/tzdump.c b/apps/tzdump/tzdump.c new file mode 100644 index 0000000..f16e5f4 --- /dev/null +++ b/apps/tzdump/tzdump.c @@ -0,0 +1,247 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tzdump.c +// Created: May 2020 +// Author(s): Philip Smart +// Description: A TranZPUter helper utility, allowing the realtime display of the tranZPUter +// or host mainboard memory. +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(__K64F__) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include "k64f_soc.h" + #include <../../libraries/include/stdmisc.h> +#elif defined(__ZPU__) + #include + #include + #include "zpu_soc.h" + #include + #include + #include +#else + #error "Target CPU not defined, use __ZPU__ or __K64F__" +#endif +#include "interrupts.h" +#include "ff.h" /* Declarations of FatFs API */ +#include "utils.h" +// +#if defined __ZPUTA__ + #include "zputa_app.h" +#elif defined __ZOS__ + #include "zOS_app.h" +#else + #error OS not defined, use __ZPUTA__ or __ZOS__ +#endif +// +#include +#include +#include "tzdump.h" + +// Utility functions. +#include + +// Version info. +#define VERSION "v1.0" +#define VERSION_DATE "15/05/2020" +#define APP_NAME "TZDUMP" + +// Simple help screen to remmber how this utility works!! +// +void usage(void) +{ + printf("%s %s\n", APP_NAME, VERSION); + printf("\nCommands:-\n"); + printf(" -h | --help This help text.\n"); + printf(" -a | --start Start address.\n"); + printf("\nOptions:-\n"); + printf(" -e | --end End address (alternatively use --size).\n"); + printf(" -s | --size Size of memory block to dump (alternatively use --end).\n"); + printf(" -m | --mainboard Operations will take place on the MZ80A mainboard. Default without this flag is to target the tranZPUter memory.\n"); + printf(" -v | --verbose Output more messages.\n"); + + printf("\nExamples:\n"); + printf(" tzdump -a 0x000000 -s 0x200 # Dump tranZPUter memory from 0x000000 to 0x000200.\n"); + +} + +// Main entry and start point of a zOS/ZPUTA Application. Only 2 parameters are catered for and a 32bit return code, additional parameters can be added by changing the appcrt0.s +// startup code to add them to the stack prior to app() call. +// +// Return code for the ZPU is saved in _memreg by the C compiler, this is transferred to _memreg in zOS/ZPUTA in appcrt0.s prior to return. +// The K64F ARM processor uses the standard register passing conventions, return code is stored in R0. +// +uint32_t app(uint32_t param1, uint32_t param2) +{ + // Initialisation. + // + uint32_t startAddr = 0xFFFFFFFF; + uint32_t endAddr = 0xFFFFFFFF; + uint32_t memSize = 0xFFFFFFFF; + int argc = 0; + int help_flag = 0; + int mainboard_flag = 0; + int verbose_flag = 0; + int opt; + int option_index = 0; + long val = 0; + char *argv[20]; + char *ptr = strtok((char *)param1, " "); + + // Initialisation. + + // If the invoking command is given, add it to argv at the start. + // + if(param2 != 0) + { + argv[argc++] = (char *)param2; + } + + // Now convert the parameter line into argc/argv suitable for getopt to use. + while (ptr && argc < 20-1) + { + argv[argc++] = ptr; + ptr = strtok(0, " "); + } + argv[argc] = 0; + + // Define parameters to be processed. + static struct option long_options[] = + { + {"help", no_argument, 0, 'h'}, + {"start", required_argument, 0, 'a'}, + {"end", required_argument, 0, 'e'}, + {"size", required_argument, 0, 's'}, + {"mainboard", no_argument, 0, 'm'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + // Parse the command line options. + // + while((opt = getopt_long(argc, argv, ":hs:e:s:mv", long_options, &option_index)) != -1) + { + switch(opt) + { + case 'h': + help_flag = 1; + break; + + case 'm': + mainboard_flag = 1; + break; + + case 'a': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(5); + } + startAddr = (uint32_t)val; + break; + + case 'e': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(6); + } + endAddr = (uint32_t)val; + break; + + case 's': + if(xatoi(&argv[optind-1], &val) == 0) + { + printf("Illegal numeric:%s\n", argv[optind-1]); + return(7); + } + memSize = (uint32_t)val; + break; + + case 'v': + verbose_flag = 1; + break; + + case ':': + printf("Option %s needs a value\n", argv[optind-1]); + break; + case '?': + printf("Unknown option: %s, ignoring!\n", argv[optind-1]); + break; + } + } + + // Validate the input. + if(help_flag == 1) + { + usage(); + return(0); + } + if(startAddr == 0xFFFFFFFF) + { + printf("Please define the start address, size will default to 0x100.\n"); + return(10); + } + if(endAddr == 0xFFFFFFFF && memSize == 0xFFFFFFFF) + { + memSize = 0x100; + } else if(memSize == 0xFFFFFFFF) + { + memSize = endAddr - startAddr; + } + if(mainboard_flag == 1 && (startAddr > 0x10000 || startAddr + memSize > 0x10000)) + { + printf("Mainboard only has 64K, please change the address or size.\n"); + return(11); + } + if(mainboard_flag == 0 && (startAddr >= 0x80000 || startAddr + memSize > 0x80000)) + { + printf("tranZPUter board only has 512K, please change the address or size.\n"); + return(12); + } + + // Initialise the IO. + setupZ80Pins(1, G->millis); + + // Call the dump utility to list out memory. + // + memoryDumpZ80(startAddr, memSize, startAddr, 32, mainboard_flag); + + return(0); +} + +#ifdef __cplusplus +} +#endif diff --git a/apps/tzdump/tzdump.h b/apps/tzdump/tzdump.h new file mode 100755 index 0000000..1414a23 --- /dev/null +++ b/apps/tzdump/tzdump.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tzdump.h +// Created: May 2020 +// Author(s): Philip Smart +// Description: A TranZPUter helper utility, allowing the realtime display of the tranZPUter +// or host mainboard memory. +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef TZDUMP_H +#define TZDUMP_H + +#ifdef __cplusplus + extern "C" { +#endif + +// Components to be embedded in the program. +// +// Filesystem components to be embedded in the program. + +// Application execution constants. +// + +#ifdef __cplusplus +} +#endif +#endif // TZDUMP_H diff --git a/apps/tzload/Makefile b/apps/tzload/Makefile index 08861a8..5eabcc4 100755 --- a/apps/tzload/Makefile +++ b/apps/tzload/Makefile @@ -38,6 +38,7 @@ APP_NAME = tzload APP_DIR = $(CURDIR)/.. APP_COMMON_DIR = $(CURDIR)/../common +COMMON_DIR = $(CURDIR)/../../common BASEDIR = ../../.. TEENSYDIR = ../../teensy3/ @@ -49,7 +50,7 @@ ifeq ($(__K64F__),1) #override STACKSIZE = 0x00000000 # Modules making up tzpu. - APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(APP_COMMON_DIR)/tranzputer.c #$(TEENSYDIR)/yield.cpp + APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(COMMON_DIR)/tranzputer.c CFLAGS = CPPFLAGS = LDFLAGS = diff --git a/apps/tzload/tzload.c b/apps/tzload/tzload.c index 8198965..c98553e 100644 --- a/apps/tzload/tzload.c +++ b/apps/tzload/tzload.c @@ -89,13 +89,13 @@ void usage(void) printf(" -u | --upload File whose contents are uploaded into the traZPUter memory.\n"); printf(" -U | --uploadset :,...,:\n"); printf(" Upload a set of files at the specified locations. --mainboard specifies mainboard is target, default is tranZPUter.\n"); - printf(" -f | --fill Fill the memory specified by --addr, --size and [--mainboard] with the value .\n"); printf(" -V | --video The specified input file is uploaded into the video frame buffer or the specified output file is filled with the video frame buffer.\n"); printf("\nOptions:-\n"); printf(" -a | --addr Memory address to read/write.\n"); printf(" -l | --size Size of memory block to read. This option is only used when reading tranZPUter memory, for writing, the file size is used.\n"); printf(" -s | --swap Read tranZPUter memory and store in then write out to the same memory location.\n"); printf(" -m | --mainboard Operations will take place on the MZ80A mainboard. Default without this flag is to target the tranZPUter memory.\n"); + printf(" -z | --mzf File operations are to process the file as an MZF format file, --addr and --size will override the MZF header values if needed.\n"); printf(" -v | --verbose Output more messages.\n"); printf("\nExamples:\n"); @@ -115,17 +115,17 @@ uint32_t app(uint32_t param1, uint32_t param2) // uint32_t memAddr = 0xFFFFFFFF; uint32_t memSize = 0xFFFFFFFF; - uint16_t fillByte = 0xFFFF; int argc = 0; int help_flag = 0; int mainboard_flag = 0; + int mzf_flag = 0; int swap_flag = 0; int verbose_flag = 0; int video_flag = 0; int opt; int option_index = 0; - int uploadFNLen = 0; - int downloadFNLen = 0; + int uploadFNLen = 0; + int downloadFNLen = 0; int uploadCnt = 0; long val; char *argv[20]; @@ -162,8 +162,8 @@ uint32_t app(uint32_t param1, uint32_t param2) {"uploadset", required_argument, 0, 'U'}, {"addr", required_argument, 0, 'a'}, {"size", required_argument, 0, 'l'}, - {"fill", required_argument, 0, 'f'}, {"mainboard", no_argument, 0, 'm'}, + {"mzf", no_argument, 0, 'z'}, {"swap", no_argument, 0, 's'}, {"verbose", no_argument, 0, 'v'}, {"video", no_argument, 0, 'V'}, @@ -172,7 +172,7 @@ uint32_t app(uint32_t param1, uint32_t param2) // Parse the command line options. // - while((opt = getopt_long(argc, argv, ":ha:l:mvU:Vsd:u:", long_options, &option_index)) != -1) + while((opt = getopt_long(argc, argv, ":ha:l:mvU:Vzsd:u:", long_options, &option_index)) != -1) { switch(opt) { @@ -188,15 +188,6 @@ uint32_t app(uint32_t param1, uint32_t param2) swap_flag = 1; break; - case 'f': - if(xatoi(&argv[optind-1], &val) == 0) - { - printf("Illegal numeric:%s\n", argv[optind-1]); - return(5); - } - fillByte = (uint16_t)val; - break; - case 'a': if(xatoi(&argv[optind-1], &val) == 0) { @@ -230,15 +221,12 @@ uint32_t app(uint32_t param1, uint32_t param2) case 'U': // Extract an array of :,... sets for upload. // - printf("This line:%s\n", argv[optind-1]); ptr = strtok((char *)argv[optind-1], ","); while (ptr && uploadCnt < 20-1) { - printf("Split:%s\n", ptr); uploadArr[uploadCnt++] = ptr; ptr = strtok(0, ","); } - printf("Final count=%d\n", uploadCnt); if(uploadCnt == 0) { printf("Upload set command should use format :,...\n"); @@ -254,6 +242,10 @@ uint32_t app(uint32_t param1, uint32_t param2) video_flag = 1; break; + case 'z': + mzf_flag = 1; + break; + case ':': printf("Option %s needs a value\n", argv[optind-1]); break; @@ -264,25 +256,20 @@ uint32_t app(uint32_t param1, uint32_t param2) } // Validate the input. - if(uploadCnt && (help_flag == 1 || uploadFNLen > 0 || downloadFNLen > 0 || swap_flag == 1 || video_flag == 1 || fillByte != 0xFFFF || memAddr != 0xFFFFFFFF || memSize != 0xFFFFFFFF)) + if(uploadCnt && (help_flag == 1 || uploadFNLen > 0 || downloadFNLen > 0 || swap_flag == 1 || video_flag == 1 || memAddr != 0xFFFFFFFF || memSize != 0xFFFFFFFF)) { - printf("Illegal combination of flags, --upload can only be used with --mainboard.\n"); + printf("Illegal combination of flags, --uploadset can only be used with --mainboard.\n"); return(10); } - if(video_flag == 1 && (help_flag == 1 || swap_flag == 1 || fillByte != 0xFFFF || mainboard_flag == 1 || memAddr != 0xFFFFFFFF || memSize != 0xFFFFFFFF)) + if(video_flag == 1 && (help_flag == 1 || swap_flag == 1 || mainboard_flag == 1 || memAddr != 0xFFFFFFFF || memSize != 0xFFFFFFFF)) { - printf("Illegal combination of flags, --video can only be used with --infile, --outfile and --mainboard.\n"); + printf("Illegal combination of flags, --video can only be used with --upload, --download and --mainboard.\n"); return(11); } - if(fillByte != 0xFFFF && (help_flag == 1 || uploadFNLen > 0 || downloadFNLen > 0 || swap_flag == 1 || video_flag == 1 || memAddr == 0xFFFFFFFF || memSize == 0xFFFFFFFF)) - { - printf("Illegal combination of flags, --fill can only be used with --addr, --size and --mainboard.\n"); - return(12); - } // If the Video and Upload modes arent required, check other argument combinations. // - if(uploadCnt == 0 && video_flag == 0 && fillByte == 0xFFFF) + if(uploadCnt == 0 && video_flag == 0) { if(help_flag == 1) { @@ -307,28 +294,33 @@ uint32_t app(uint32_t param1, uint32_t param2) printf("Please define the size of memory you wish to read.\n"); return(16); } - if(memAddr == 0xFFFFFFFF) + if(mzf_flag == 1 && downloadFNLen > 0) + { + printf("MZF Format can currently only be used for file uploading.\n"); + return(17); + } + if(memAddr == 0xFFFFFFFF && mzf_flag == 0) { printf("Please define the target address.\n"); - return(17); + return(18); } } if(uploadCnt == 0 && video_flag == 0) { - if(mainboard_flag == 1 && (memAddr > 0x10000 || memAddr + memSize > 0x10000)) + if(mainboard_flag == 1 && mzf_flag == 0 && (memAddr > 0x10000 || memAddr + memSize > 0x10000)) { printf("Mainboard only has 64K, please change the address and size.\n"); - return(17); + return(19); } - if(mainboard_flag == 0 && (memAddr >= 0x80000 || memAddr + memSize > 0x80000)) + if(mainboard_flag == 0 && mzf_flag == 0 && (memAddr >= 0x80000 || memAddr + memSize > 0x80000)) { printf("tranZPUter board only has 512K, please change the address and size.\n"); - return(18); + return(20); } } // Initialise the IO. - setupPins(G->millis); + setupZ80Pins(1, G->millis); // Bulk file upload command (used to preload a file set). // @@ -343,13 +335,19 @@ uint32_t app(uint32_t param1, uint32_t param2) if(xatoi(&ptr, &val) == 0) { printf("Illegal numeric in upload list:%s\n", ptr); - return(20); + return(30); } memAddr = (uint32_t)val; // Now we have the input file and the address where it should be loaded, call the load function. // - loadZ80Memory(uploadFile, memAddr, mainboard_flag, (idx == uploadCnt-1) ? 1 : 0); + if(mzf_flag == 0) + { + loadZ80Memory(uploadFile, 0, memAddr, 0, mainboard_flag, (idx == uploadCnt-1) ? 1 : 0); + } else + { + loadMZFZ80Memory(uploadFile, memAddr, mainboard_flag, (idx == uploadCnt-1) ? 1 : 0); + } } } @@ -368,13 +366,6 @@ uint32_t app(uint32_t param1, uint32_t param2) } } - // Fill tranZPUter memory or mainboard memory with a fixed value to initialise memory before an upload? - // - else if(fillByte != 0xFFFF) - { - fillZ80Memory(memAddr, memSize, (uint8_t)fillByte, mainboard_flag); - } - else { if(downloadFNLen > 0) @@ -383,15 +374,22 @@ uint32_t app(uint32_t param1, uint32_t param2) { printf("Saving %s memory at address:%06lx into file:%s\n", (mainboard_flag == 0 ? "tranZPUter" : "mainboard"), memAddr, downloadFile); } - saveZ80Memory(downloadFile, memAddr, memSize, mainboard_flag); + saveZ80Memory(downloadFile, memAddr, memSize, 0, mainboard_flag); } if(uploadFNLen > 0) { if(verbose_flag) { - printf("Loading file:%s into the %s at address:%06lx\n", uploadFile, (mainboard_flag == 0 ? "tranZPUter" : "mainboard"), memAddr); + printf("Loading file:%s into the %s memory\n", uploadFile, (mainboard_flag == 0 ? "tranZPUter" : "mainboard")); + } + + if(mzf_flag == 0) + { + loadZ80Memory(uploadFile, 0, memAddr, 0, mainboard_flag, 1); + } else + { + loadMZFZ80Memory(uploadFile, memAddr, mainboard_flag, 1); } - loadZ80Memory(uploadFile, memAddr, mainboard_flag, 1); } } diff --git a/apps/tzpu/Makefile b/apps/tzpu/Makefile index b3dfde3..0b5f4e1 100755 --- a/apps/tzpu/Makefile +++ b/apps/tzpu/Makefile @@ -38,6 +38,7 @@ APP_NAME = tzpu APP_DIR = $(CURDIR)/.. APP_COMMON_DIR = $(CURDIR)/../common +COMMON_DIR = $(CURDIR)/../../common BASEDIR = ../../.. TEENSYDIR = ../../teensy3/ @@ -49,8 +50,8 @@ ifeq ($(__K64F__),1) #override STACKSIZE = 0x00000000 # Modules making up tzpu. - APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(APP_COMMON_DIR)/tranzputer.c #$(TEENSYDIR)/yield.cpp - CFLAGS = + APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(COMMON_DIR)/tranzputer.c + CFLAGS = #-D__TZPU_DEBUG__ CPPFLAGS = LDFLAGS = LIBS = diff --git a/apps/tzpu/tzpu.c b/apps/tzpu/tzpu.c index f0090d7..8df1f71 100644 --- a/apps/tzpu/tzpu.c +++ b/apps/tzpu/tzpu.c @@ -41,7 +41,7 @@ #include #include #include -#include + #include #include #include "k64f_soc.h" #include <../../libraries/include/stdmisc.h> @@ -123,26 +123,18 @@ uint32_t app(uint32_t param1, uint32_t param2) //{ // printf("Usage: tzpu \n"); //} - _init_Teensyduino_internal_(); - setupPins(G->millis); + // _init_Teensyduino_internal_(); + // setupZ80Pins(1, G->millis); printf("Loading Monitor ROM\n"); - loadZ80Memory("SA1510.rom", 0x00000000, 0, 1); + loadZ80Memory("SA1510.rom", 0, 0x00000000, 0, 0, 1); printf("Loading Floppy ROM\n"); - loadZ80Memory("1Z-013A.rom", 0x0000F000, 0, 1); + loadZ80Memory("1Z-013A.rom", 0, 0x0000F000, 0, 0, 1); printf("Testing Display\n"); testBus(); - displaySignals(); - - pinMode(13, OUTPUT); - while (1) { - digitalWriteFast(13, HIGH); - delay(500); - digitalWriteFast(13, LOW); - delay(500); - } + //displaySignals(); return(retCode); } diff --git a/apps/tzreset/Makefile b/apps/tzreset/Makefile new file mode 100755 index 0000000..7d0864a --- /dev/null +++ b/apps/tzreset/Makefile @@ -0,0 +1,84 @@ +######################################################################################################### +## +## Name: Makefile +## Created: July 2019 +## Author(s): Philip Smart +## Description: App Makefile - Build an App for the ZPU Test Application (zputa) or the zOS +## operating system. +## This makefile builds an app which is stored on an SD card and called by ZPUTA/zOS +## The app is for testing some component where the code is not built into ZPUTA or +## a user application for zOS. +## +## Credits: +## Copyright: (c) 2019-20 Philip Smart +## +## History: July 2019 - Initial Makefile created for template use. +## April 2020 - Added K64F as an additional target and resplit ZPUTA into zOS. +## +## Notes: Optional component enables: +## USELOADB - The Byte write command is implemented in hw#sw so use it. +## USE_BOOT_ROM - The target is ROM so dont use initialised data. +## MINIMUM_FUNTIONALITY - Minimise functionality to limit code size. +## +######################################################################################################### +## 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 . +######################################################################################################### + +APP_NAME = tzreset +APP_DIR = $(CURDIR)/.. +APP_COMMON_DIR = $(CURDIR)/../common +COMMON_DIR = $(CURDIR)/../../common +BASEDIR = ../../.. +TEENSYDIR = ../../teensy3/ + +# Override values given by parent make for this application as its memory usage differs from the standard app. +ifeq ($(__K64F__),1) + #override HEAPADDR = 0x2002f000 + #override HEAPSIZE = 0x00000000 + #override STACKADDR = 0x2002f000 + #override STACKSIZE = 0x00000000 + + # Modules making up tzpu. + APP_C_SRC = $(APP_COMMON_DIR)/pins_teensy.c $(APP_COMMON_DIR)/analog.c $(COMMON_DIR)/tranzputer.c + CFLAGS = + CPPFLAGS = + LDFLAGS = + LIBS = +else + + # Modules making up tcpu. + APP_C_SRC = #$(APP_COMMON_DIR)/sysutils.c $(APP_COMMON_DIR)/ctypelocal.c + CFLAGS = + CPPFLAGS = + LDFLAGS = -nostdlib + LIBS = -lumansi-zpu -limath-zpu +endif + +# Filter out the standard HEAP address and size, replacing with the ones required for this application. +# Useful for sub-makes +FILTER1 = $(filter-out $(filter HEAPADDR=%,$(MAKEFLAGS)), $(MAKEFLAGS)) +FILTER2 = $(filter-out $(filter HEAPSIZE=%,$(FILTER1)), $(FILTER1)) +NEWMAKEFLAGS = $(FILTER2) HEAPADDR=$(HEADADDR) HEAPSIZE=$(HEAPSIZE) + +ifeq ($(__K64F__),1) +include $(APP_DIR)/Makefile.k64f +else + +# There currently is no code for the ZPU, all development being done on the K64F for this app. +all: + +clean: + +install: +endif diff --git a/apps/tzreset/tzreset.c b/apps/tzreset/tzreset.c new file mode 100644 index 0000000..363e0d9 --- /dev/null +++ b/apps/tzreset/tzreset.c @@ -0,0 +1,201 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tzreset.c +// Created: May 2020 +// Author(s): Philip Smart +// Description: A TranZPUter helper utility, allowing a remote hardware reset of the tranZPUter +// board and host (not K64F). +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(__K64F__) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include "k64f_soc.h" + #include <../../libraries/include/stdmisc.h> +#elif defined(__ZPU__) + #include + #include + #include "zpu_soc.h" + #include + #include + #include +#else + #error "Target CPU not defined, use __ZPU__ or __K64F__" +#endif +#include "interrupts.h" +#include "ff.h" /* Declarations of FatFs API */ +#include "utils.h" +// +#if defined __ZPUTA__ + #include "zputa_app.h" +#elif defined __ZOS__ + #include "zOS_app.h" +#else + #error OS not defined, use __ZPUTA__ or __ZOS__ +#endif +// +#include +#include +#include "tzreset.h" + +// Utility functions. +#include + +// Version info. +#define VERSION "v1.0" +#define VERSION_DATE "15/05/2020" +#define APP_NAME "TZRESET" + +// Simple help screen to remmber how this utility works!! +// +void usage(void) +{ + printf("%s %s\n", APP_NAME, VERSION); + printf("\nCommands:-\n"); + printf(" -h | --help This help text.\n"); + printf(" -r | --reset Perform a hardware reset.\n"); + printf(" -l | --load Reload the default ROMS.\n"); + printf("\nOptions:-\n"); + printf(" -v | --verbose Output more messages.\n"); + + printf("\nExamples:\n"); + printf(" tzreset -r # Resets the Z80 and associated tranZPUter logic..\n"); +} + +// Main entry and start point of a zOS/ZPUTA Application. Only 2 parameters are catered for and a 32bit return code, additional parameters can be added by changing the appcrt0.s +// startup code to add them to the stack prior to app() call. +// +// Return code for the ZPU is saved in _memreg by the C compiler, this is transferred to _memreg in zOS/ZPUTA in appcrt0.s prior to return. +// The K64F ARM processor uses the standard register passing conventions, return code is stored in R0. +// +uint32_t app(uint32_t param1, uint32_t param2) +{ + // Initialisation. + // + int argc = 0; + int help_flag = 0; + int load_flag = 0; + int reset_flag = 0; + int verbose_flag = 0; + int opt; + int option_index = 0; + long val = 0; + char *argv[20]; + char *ptr = strtok((char *)param1, " "); + + // Initialisation. + + // If the invoking command is given, add it to argv at the start. + // + if(param2 != 0) + { + argv[argc++] = (char *)param2; + } + + // Now convert the parameter line into argc/argv suitable for getopt to use. + while (ptr && argc < 20-1) + { + argv[argc++] = ptr; + ptr = strtok(0, " "); + } + argv[argc] = 0; + + // Define parameters to be processed. + static struct option long_options[] = + { + {"help", no_argument, 0, 'h'}, + {"load", no_argument, 0, 'l'}, + {"reset", no_argument, 0, 'r'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + // Parse the command line options. + // + while((opt = getopt_long(argc, argv, ":hlrv", long_options, &option_index)) != -1) + { + switch(opt) + { + case 'h': + help_flag = 1; + break; + + case 'l': + load_flag = 1; + break; + + case 'r': + reset_flag = 1; + break; + + case 'v': + verbose_flag = 1; + break; + + case ':': + printf("Option %s needs a value\n", argv[optind-1]); + break; + case '?': + printf("Unknown option: %s, ignoring!\n", argv[optind-1]); + break; + } + } + + // Validate the input. + if(help_flag == 1 || reset_flag == 0) + { + usage(); + return(0); + } + + // Initialise the IO. + setupZ80Pins(1, G->millis); + + // Call the reset method to do the hard work. + // + resetZ80(); + + // Reload the memory on the tranZPUter to boot default. + if(load_flag) + { + loadTranZPUterDefaultROMS(); + } + + return(0); +} + +#ifdef __cplusplus +} +#endif diff --git a/apps/tzreset/tzreset.h b/apps/tzreset/tzreset.h new file mode 100755 index 0000000..30bbe74 --- /dev/null +++ b/apps/tzreset/tzreset.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tzreset.h +// Created: May 2020 +// Author(s): Philip Smart +// Description: A TranZPUter helper utility, allowing a remote hardware reset of the tranZPUter +// and host (not K64F). +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef TZRESET_H +#define TZRESET_H + +#ifdef __cplusplus + extern "C" { +#endif + +// Components to be embedded in the program. +// +// Filesystem components to be embedded in the program. + +// Application execution constants. +// + +#ifdef __cplusplus +} +#endif +#endif // TZRESET_H diff --git a/build.sh b/build.sh index dc9a43a..1874b0f 100755 --- a/build.sh +++ b/build.sh @@ -23,6 +23,7 @@ # -s = Required size of application stack # -a = Maximum size of an app, defaults to (BRAM SIZE - App Start Address - Stack Size) # if the App Start is located within BRAM otherwise defaults to 0x10000. +# -T = TranZPUter specific build, adds initialisation and setup code. # -d = Debug mode. # -x = Shell trace mode. # -h = This help screen. @@ -247,12 +248,13 @@ APP_HEAP_SIZE=0x1000; APP_STACK_SIZE=0x400; OS_HEAP_SIZE=0x4000; OS_STACK_SIZE=0x1000; +TRANZPUTER=0 OSVER=2; # Process parameters, loading up variables as necessary. # if [ $# -gt 0 ]; then - while getopts ":hC:I:O:o:M:B:A:N:n:S:s:da:x" opt; do + while getopts ":hC:I:O:o:M:B:A:N:n:S:s:da:xT" opt; do case $opt in d) DEBUGMODE=1;; C) CPU=`echo ${OPTARG} | tr 'a-z' 'A-Z'`;; @@ -267,6 +269,7 @@ if [ $# -gt 0 ]; then S) getHex ${OPTARG} OS_STACK_SIZE;; s) getHex ${OPTARG} APP_STACK_SIZE;; a) getHex ${OPTARG} APP_LEN;; + T) TRANZPUTER=1;; x) set -x; TRACEMODE=1;; h) Usage;; \?) FatalUsage "Unknown option: -${OPTARG}";; @@ -313,6 +316,13 @@ if [ "${CPU}" = "K64F" -a "${OSVER}" != "2" ]; then Fatal "-o has no meaning for the K64F, base is in Flash RAM and applications, if SD card enabled, are in RAM." fi +# Setup any specific build options. +if [ ${TRANZPUTER} -eq 1 ]; then + if [ "${CPU}" = "K64F" -a "${OS}" = "ZOS" ]; then + BUILDFLAGS="__TRANZPUTER__=1" + fi +fi + # Clear out the build target directory. rm -fr ${BUILDPATH}/build mkdir -p ${BUILDPATH}/build @@ -483,7 +493,7 @@ else # Calculate the heap, stack and RAM start address vars. OS_RAM_ENDADDR=0x20030000 OS_RAM_MASK=0x3FFFF000 - OS_RAM_OSMEM=0x004000 + OS_RAM_OSMEM=0x005000 subHex ${OS_RAM_ENDADDR} 8 OS_STACK_ENDADDR subHex ${OS_RAM_ENDADDR} ${OS_STACK_SIZE} OS_STACK_STARTADDR roundHex ${OS_STACK_STARTADDR} ${OS_RAM_MASK} OS_STACK_STARTADDR @@ -546,17 +556,17 @@ if [ "${OS}" = "ZPUTA" ]; then -e "s/STACK_ADDR/${OS_STACK_STARTADDR}/g" > ${BUILDPATH}/startup/${OSBUILDSTR}.ld cd ${BUILDPATH}/zputa - make ${CPUTYPE}=1 ${OSTYPE}=1 clean + make ${CPUTYPE}=1 ${OSTYPE}=1 ${BUILDFLAGS} clean if [ $? != 0 ]; then Fatal "Aborting, failed to clean ZPUTA build environment!" fi if [ "${CPUTYPE}" = "__ZPU__" ]; then - Log "make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU}" - make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} + Log "make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} ${BUILDFLAGS}" + make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} ${BUILDFLAGS} else - Log "make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU}" - make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} + Log "make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} ${BUILDFLAGS}" + make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} ${BUILDFLAGS} fi if [ $? != 0 ]; then Fatal "Aborting, failed to build ZPUTA!" @@ -592,11 +602,11 @@ elif [ "${OS}" = "ZOS" ]; then Fatal "Aborting, failed to clean zOS build environment!" fi if [ "${CPUTYPE}" = "__ZPU__" ]; then - Log "make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE}" - make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE} + Log "make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE} ${BUILDFLAGS}" + make ${OSBUILDSTR} ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE} ${BUILDFLAGS} else - Log "make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE}" - make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE} + Log "make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE} ${BUILDFLAGS}" + make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} HEAPADDR=${OS_HEAP_STARTADDR} HEAPSIZE=${OS_HEAP_SIZE} STACKADDR=${OS_STACK_STARTADDR} STACKENDADDR=${OS_STACK_ENDADDR} STACKSIZE=${OS_STACK_SIZE} ${BUILDFLAGS} fi if [ $? != 0 ]; then Fatal "Aborting, failed to build zOS!" @@ -617,20 +627,20 @@ else fi cd ${BUILDPATH}/apps -Log "make ${CPUTYPE}=1 ${OSTYPE}=1 clean" -make ${CPUTYPE}=1 ${OSTYPE}=1 clean +Log "make ${CPUTYPE}=1 ${OSTYPE}=1 ${BUILDFLAGS} clean" +make ${CPUTYPE}=1 ${OSTYPE}=1 ${BUILDFLAGS} clean if [ $? != 0 ]; then Fatal "Aborting, failed to clean Apps build environment!" fi -Log "make ${CPUTYPE} ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} TMPLFILE=${TMPLFILE} BASEADDR=${APP_BASEADDR} BASELEN=${APP_BOOTLEN} HEAPADDR=${APP_HEAP_STARTADDR} HEAPSIZE=${APP_HEAP_SIZE} STACKADDR=${APP_STACK_STARTADDR} STACKENDADDR=${APP_STACK_ENDADDR} STACKSIZE=${APP_STACK_SIZE} APPSTART=${APP_STARTADDR} APPSIZE=${APP_LEN}" -make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} TMPLFILE=${TMPLFILE} BASEADDR=${APP_BASEADDR} BASELEN=${APP_BOOTLEN} HEAPADDR=${APP_HEAP_STARTADDR} HEAPSIZE=${APP_HEAP_SIZE} STACKADDR=${APP_STACK_STARTADDR} STACKENDADDR=${APP_STACK_ENDADDR} STACKSIZE=${APP_STACK_SIZE} APPSTART=${APP_STARTADDR} APPSIZE=${APP_LEN} +Log "make ${CPUTYPE} ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} TMPLFILE=${TMPLFILE} BASEADDR=${APP_BASEADDR} BASELEN=${APP_BOOTLEN} HEAPADDR=${APP_HEAP_STARTADDR} HEAPSIZE=${APP_HEAP_SIZE} STACKADDR=${APP_STACK_STARTADDR} STACKENDADDR=${APP_STACK_ENDADDR} STACKSIZE=${APP_STACK_SIZE} APPSTART=${APP_STARTADDR} APPSIZE=${APP_LEN} ${BUILDFLAGS}" +make ${CPUTYPE}=1 ${OSTYPE}=1 OS_BASEADDR=${OS_BOOTADDR} OS_APPADDR=${APP_BASEADDR} CPU=${CPU} TMPLFILE=${TMPLFILE} BASEADDR=${APP_BASEADDR} BASELEN=${APP_BOOTLEN} HEAPADDR=${APP_HEAP_STARTADDR} HEAPSIZE=${APP_HEAP_SIZE} STACKADDR=${APP_STACK_STARTADDR} STACKENDADDR=${APP_STACK_ENDADDR} STACKSIZE=${APP_STACK_SIZE} APPSTART=${APP_STARTADDR} APPSIZE=${APP_LEN} ${BUILDFLAGS} if [ $? != 0 ]; then Fatal "Aborting, failed to build Apps!" fi mkdir -p bin rm -f bin/* -Log "make ${CPUTYPE}=1 ${OSTYPE}=1 install" -make ${CPUTYPE}=1 ${OSTYPE}=1 install +Log "make ${CPUTYPE}=1 ${OSTYPE}=1 ${BUILDFLAGS} install" +make ${CPUTYPE}=1 ${OSTYPE}=1 ${BUILDFLAGS} install if [ $? != 0 ]; then Fatal "Aborting, failed to install generated binaries!" fi diff --git a/common/FatFS/ffconf.h b/common/FatFS/ffconf.h index 16f7fa5..7ecf7dc 100644 --- a/common/FatFS/ffconf.h +++ b/common/FatFS/ffconf.h @@ -96,8 +96,14 @@ / 0 - Include all code pages above and configured by f_setcp() */ +// zOS running on the tranZPUter uses LFN. +// +//#if defined __TRANZPUTER__ +#define FF_USE_LFN 1 +//#else +//#define FF_USE_LFN 0 +//#endif -#define FF_USE_LFN 0 #define FF_MAX_LFN 255 /* The FF_USE_LFN switches the support for LFN (long file name). / diff --git a/common/tranzputer.c b/common/tranzputer.c new file mode 100644 index 0000000..00d4e18 --- /dev/null +++ b/common/tranzputer.c @@ -0,0 +1,3036 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tranzputer.c +// Created: May 2020 +// Author(s): Philip Smart +// Description: The TranZPUter library. +// This file contains methods which allow applications to access and control the +// traZPUter board and the underlying Sharp MZ80A host. +// I had considered writing this module in C++ but decided upon C as speed is more +// important and C++ always adds a little additional overhead. Some of the methods need +// to be written as embedded assembler but this is for a later time when using the +// tranZPUter on faster motherboards. +// +// NB. This library is NOT thread safe. In zOS, one thread is running continually in +// this code but is suspended if zOS launches an application which will call this +// functionality. +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__K64F__) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include "k64f_soc.h" + #include <../../libraries/include/stdmisc.h> +#elif defined(__ZPU__) + #include + #include + #include "zpu_soc.h" + #include + #include + #include +#else + #error "Target CPU not defined, use __ZPU__ or __K64F__" +#endif + +#include "ff.h" /* Declarations of FatFs API */ +#include "utils.h" +#include + +#ifndef __APP__ // Protected methods which should only reside in the kernel. + +// Global scope variables used within the zOS kernel. +// +volatile uint32_t *ioPin[MAX_TRANZPUTER_PINS]; +uint8_t pinMap[MAX_TRANZPUTER_PINS]; +uint32_t volatile *ms; +t_z80Control z80Control; +t_osControl osControl; +t_svcControl svcControl; + +// Mapping table to map Sharp MZ80A Ascii to Standard ASCII. +// +static t_asciiMap asciiMap[] = { + { 0x00 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x00 }, { 0x20 }, { 0x20 }, // 0x0F + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0x1F + { 0x20 }, { 0x21 }, { 0x22 }, { 0x23 }, { 0x24 }, { 0x25 }, { 0x26 }, { 0x27 }, { 0x28 }, { 0x29 }, { 0x2A }, { 0x2B }, { 0x2C }, { 0x2D }, { 0x2E }, { 0x2F }, // 0x2F + { 0x30 }, { 0x31 }, { 0x32 }, { 0x33 }, { 0x34 }, { 0x35 }, { 0x36 }, { 0x37 }, { 0x38 }, { 0x39 }, { 0x3A }, { 0x3B }, { 0x3C }, { 0x3D }, { 0x3E }, { 0x3F }, // 0x3F + { 0x40 }, { 0x41 }, { 0x42 }, { 0x43 }, { 0x44 }, { 0x45 }, { 0x46 }, { 0x47 }, { 0x48 }, { 0x49 }, { 0x4A }, { 0x4B }, { 0x4C }, { 0x4D }, { 0x4E }, { 0x4F }, // 0x4F + { 0x50 }, { 0x51 }, { 0x52 }, { 0x53 }, { 0x54 }, { 0x55 }, { 0x56 }, { 0x57 }, { 0x58 }, { 0x59 }, { 0x5A }, { 0x5B }, { 0x5C }, { 0x5D }, { 0x5E }, { 0x5F }, // 0x5F + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0x6F + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0x7F + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0x8F + { 0x20 }, { 0x20 }, { 0x65 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x74 }, { 0x67 }, { 0x68 }, { 0x20 }, { 0x62 }, { 0x78 }, { 0x64 }, { 0x72 }, { 0x70 }, { 0x63 }, // 0x9F + { 0x71 }, { 0x61 }, { 0x7A }, { 0x77 }, { 0x73 }, { 0x75 }, { 0x69 }, { 0x20 }, { 0x4F }, { 0x6B }, { 0x66 }, { 0x76 }, { 0x20 }, { 0x75 }, { 0x42 }, { 0x6A }, // 0XAF + { 0x6E }, { 0x20 }, { 0x55 }, { 0x6D }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x6F }, { 0x6C }, { 0x41 }, { 0x6F }, { 0x61 }, { 0x20 }, { 0x79 }, { 0x20 }, { 0x20 }, // 0xBF + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0XCF + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0XDF + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, // 0XEF + { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 }, { 0x20 } // 0XFF +}; + +// This method is called everytime an active irq triggers on Port D. For this design, this means the IORQ line. +// Store all the ports as they are needed to capture the Address and Data of the asserted z80 I/O transaction. +// +static void __attribute((naked, noinline)) irqPortD(void) +{ + // Save register we use. + asm volatile ("push {r0-r6,lr}"); + + // Capture GPIO ports - this is necessary in order to make a clean capture and then decode. + asm volatile ("ldr r5, =0x400ff010"); // GPIOA_PDIR + asm volatile ("ldr r0, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff050"); // GPIOB_PDIR + asm volatile ("ldr r1, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff090"); // GPIOC_PDIR + asm volatile ("ldr r2, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff0d0"); // GPIOD_PDIR + asm volatile ("ldr r3, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff110"); // GPIOE_PDIR + asm volatile ("ldr r4, [r5, #0]"); + + // Now save the GPIO values into the structure. + asm volatile ("str r0, %0" : "+m" (z80Control.portA) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r1, %0" : "+m" (z80Control.portB) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r2, %0" : "+m" (z80Control.portC) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r3, %0" : "+m" (z80Control.portD) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r4, %0" : "+m" (z80Control.portE) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + + // Is Z80_RESET active, set flag and exit. + asm volatile ("lsls r5, r3, #16" ); + asm volatile goto("bmi.n %l0" ::: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12" : cbr0); + asm volatile ("movs r3, #1"); + asm volatile ("strb r3, %0" : "+m" (z80Control.resetEvent) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile goto("b %l0" :::: irqPortD_Exit); + +cbr0: + // Convert lower 8 address bits into a byte and store. + asm volatile ("lsrs r3, r1, #4"); // (z80Control.portB >> 4)&0x80) + asm volatile ("and.w r3, r3, #128"); + asm volatile ("lsrs r5, r4, #18"); // (z80Control.portE >> 18)&0x40) + asm volatile ("and.w r5, r5, #64"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r4, #20"); // (z80Control.portE >> 20)&0x20) + asm volatile ("and.w r5, r5, #32"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r2, #4"); // (z80Control.portC >> 4)&0x10) + asm volatile ("and.w r5, r5, #16"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r2, #6"); // (z80Control.portC >> 6)&0x08) + asm volatile ("and.w r5, r5, #8"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r2, #8"); // (z80Control.portC >> 8)&0x04) + asm volatile ("and.w r5, r5, #4"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r2, #10"); // (z80Control.portC >> 10)&0x02) + asm volatile ("and.w r5, r5, #2"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r0, #17"); // (z80Control.portA >> 17)&0x01) + asm volatile ("and.w r5, r5, #1"); + asm volatile ("orrs r3, r5"); + asm volatile ("str r3, %0" : "=m" (z80Control.ioAddr) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + + // Convert data port bits into a byte and store. + asm volatile ("ldr.w r3, %0 " : "+m" (z80Control.portD) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r2, r3, lsl #5"); // (z80Control.portD << 5)&0x80) + asm volatile ("and.w r2, r2, #128"); + asm volatile ("lsls r5, r3, #2"); // (z80Control.portD << 2)&0x40) + asm volatile ("and.w r5, r5, #64"); + asm volatile ("orrs r2, r5"); + asm volatile ("lsrs r5, r3, #2"); // (z80Control.portD >> 2)&0x20) + asm volatile ("and.w r5, r5, #32"); + asm volatile ("orrs r2, r5"); + asm volatile ("lsrs r5, r0, #9"); // (z80Control.portA >> 9)&0x10) + asm volatile ("and.w r5, r5, #16"); + asm volatile ("orrs r2, r5"); + asm volatile ("lsrs r5, r0, #9"); // (z80Control.portA >> 9)&0x08) + asm volatile ("and.w r5, r5, #8"); + asm volatile ("orrs r2, r5"); + asm volatile ("lsls r5, r3, #2"); // (z80Control.portD << 2)&0x04) + asm volatile ("and.w r5, r5, #4"); + asm volatile ("orrs r2, r5"); + asm volatile ("lsrs r5, r1, #16"); // (z80Control.portB >> 16)&0x02) + asm volatile ("and.w r5, r5, #2"); + asm volatile ("orrs r2, r5"); + asm volatile ("lsrs r5, r1, #16"); // (z80Control.portB >> 16)&0x01) + asm volatile ("and.w r5, r5, #1"); + asm volatile ("orrs r2, r5"); + asm volatile ("str r2, %0" : "=m" (z80Control.ioData) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + + // Process the IO request by setting the ioEvent flag. + asm volatile ("movs r3, #1"); + asm volatile ("str r3, %0" : "=m" (z80Control.ioEvent) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + +irqPortD_Exit: + // Reset the interrupt, PORTD_ISFR <= PORTD_ISFR + asm volatile ("ldr r3, =0x4004c0a0"); + asm volatile ("ldr r2, [r3, #0]"); + asm volatile ("str r2, [r3, #0]"); + + asm volatile ("pop {r0-r6,pc}"); + return; +} + +// This method is called everytime an active irq triggers on Port C. For this design, this means the MREQ line. +// Store all the ports as they are needed to capture the Address and Data of the asserted z80 I/O transaction. +// +static void __attribute((naked, noinline)) irqPortC(void) +{ + // Save register we use. + asm volatile ("push {r0-r6,lr}"); + + // Capture GPIO ports - this is necessary in order to make a clean capture and then decode. + asm volatile ("ldr r5, =0x400ff010"); // GPIOA_PDIR + asm volatile ("ldr r0, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff050"); // GPIOB_PDIR + asm volatile ("ldr r1, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff090"); // GPIOC_PDIR + asm volatile ("ldr r2, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff0d0"); // GPIOD_PDIR + asm volatile ("ldr r3, [r5, #0]"); + asm volatile ("ldr r5, =0x400ff110"); // GPIOE_PDIR + asm volatile ("ldr r4, [r5, #0]"); + + // Now save the GPIO values into the structure. + asm volatile ("str r0, %0" : "+m" (z80Control.portA) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r1, %0" : "+m" (z80Control.portB) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r2, %0" : "+m" (z80Control.portC) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r3, %0" : "+m" (z80Control.portD) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("str r4, %0" : "+m" (z80Control.portE) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + + // Is Z80_RFSH active, exit if so as we dont want to consider refresh cycles. + asm volatile ("lsls r5, r1, #8"); + asm volatile goto("bpl %l0" :::: irqPortC_Exit); + + // Is Z80_M1 active, exit if so as we dont want to consider instruction cycles. + asm volatile ("lsls r5, r3, #26"); + asm volatile goto("bpl %l0" :::: irqPortC_Exit); + + // Is Z80_WR active, exit if so as we dont want to consider write cycles. + asm volatile ("lsls r5, r2, #27"); + asm volatile goto("bpl %l0" :::: irqPortC_Exit); + + // Is the memory address in the 0xE000 region? + // Only interested in certain locations (memory mapped IO), exit if not in range. + asm volatile ("lsrs r3, r4, #11"); // (z80Control.portE >> 11)&0x8000) + asm volatile ("and.w r3, r3, #32768"); + asm volatile ("lsls r5, r0, #9"); // (z80Control.portA << 9)&0x4000) + asm volatile ("and.w r5, r5, #16384"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r0, #1"); // (z80Control.portA >> 1)&0x2000) + asm volatile ("and.w r5, r5, #8192"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r0, #3"); // (z80Control.portA >> 3)&0x1000) + asm volatile ("and.w r5, r5, #4096"); + asm volatile ("orrs r3, r5"); + asm volatile ("lsrs r5, r0, #5"); // (z80Control.portA >> 5)&0x0800) + asm volatile ("and.w r5, r5, #2048"); + asm volatile ("orrs r3, r5"); + asm volatile ("cmp.w r3, #57344"); // == 0xE000 + asm volatile goto("bne %l0" :::: irqPortC_Exit); + + // Get the lower part of the address into R0 which we compare with the various registers and update. + // + asm volatile ("ldr.w r0, %0 " : "+m" (z80Control.portB) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r0, r0, lsr #8"); // (z80Control.portB >> 8)&0x0400) + asm volatile ("and.w r0, r0, #1024"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portB) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #10"); // (z80Control.portB >> 10)&0x0200) + asm volatile ("and.w r1, r1, #512"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portB) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #2"); // (z80Control.portB >> 2)&0x0100) + asm volatile ("and.w r1, r1, #256"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portB) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #4"); // (z80Control.portB >> 2)&0x0080) + asm volatile ("and.w r1, r1, #128"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portE) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #18"); // (z80Control.portE >> 2)&0x0040) + asm volatile ("and.w r1, r1, #64"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portE) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #20"); // (z80Control.portE >> 2)&0x0020) + asm volatile ("and.w r1, r1, #32"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portC) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #4"); // (z80Control.portC >> 4)&0x0010) + asm volatile ("and.w r1, r1, #16"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portC) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #6"); // (z80Control.portC >> 6)&0x0008) + asm volatile ("and.w r1, r1, #8"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portC) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #8"); // (z80Control.portC >> 8)&0x0004) + asm volatile ("and.w r1, r1, #4"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portC) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #8"); // (z80Control.portC >> 10)&0x0002) + asm volatile ("and.w r1, r1, #2"); + asm volatile ("orr.w r0, r1"); + asm volatile ("ldr.w r1, %0 " : "+m" (z80Control.portA) :: "r0","r1","r2","r3","r4","r5","r7","r8","r9","r10","r11","r12"); + asm volatile ("mov.w r1, r1, lsr #17"); // (z80Control.portA >> 17)&0x0001) + asm volatile ("and.w r1, r1, #1"); + asm volatile ("orr.w r0, r1"); + + // A memory swap event. + asm volatile ("movw r1, " XSTR(MZ_MEMORY_SWAP) ""); + asm volatile ("cmp r0, r1"); + asm volatile goto("bne %l0" :::: br0); + asm volatile ("movs r2, #1"); + asm volatile goto("b.n %l0" :::: br1); +br0: + // A memory reset event. + asm volatile ("movw r1, " XSTR(MZ_MEMORY_RESET) ""); + asm volatile ("cmp r0, r1"); + asm volatile goto("bne %l0" :::: br2); + asm volatile ("movs r2, #0"); +br1: + // Store to memorySwap + asm volatile ("strb r2, %0" : "+m" (z80Control.memorySwap) :: "r0","r2"); + asm volatile goto("b.n %l0" :::: irqPortC_Exit); +br2: + // A CRT to normal mode event. + asm volatile ("movw r1, " XSTR(MZ_CRT_NORMAL) ""); + asm volatile ("cmp r0, r1"); + asm volatile goto("bne %l0" :::: br3); + asm volatile ("movs r2, #0"); + asm volatile goto("b.n %l0" :::: br4); +br3: + // A CRT to inverse mode event. + asm volatile ("movw r1, " XSTR(MZ_CRT_INVERSE) ""); + asm volatile ("cmp r0, r1"); + asm volatile goto("bne %l0" :::: br5); + asm volatile ("movs r2, #0"); +br4: + // Store to crtMode. + asm volatile ("strb r2, %0" : "+m" (z80Control.crtMode) :: "r0","r2"); + asm volatile goto("b.n %l0" :::: irqPortC_Exit); +br5: + // Memory address in SCROLL region? + asm volatile ("sub.w r1, r0, " XSTR(MZ_SCROL_END - MZ_SCROL_BASE) ""); + asm volatile ("cmp r1, #255"); + asm volatile goto("bhi.n %l0" :::: irqPortC_Exit); + asm volatile ("strb r0, %0" : "+m" (z80Control.scroll) :: "r0"); + +irqPortC_Exit: + // Reset the interrupt, PORTC_ISFR <= PORTC_ISFR + asm volatile ("ldr r3, =0x4004b0a0"); + asm volatile ("ldr r2, [r3, #0]"); + asm volatile ("str r2, [r3, #0]"); + + asm volatile( "pop {r0-r6,pc}"); + return; +} + + +// Method to install the interrupt vector and enable it to capture Z80 memory/IO operations. +// +static void setupIRQ(void) +{ + __disable_irq(); + // Install the method to be called when PortD triggers. + _VectorsRam[IRQ_PORTD + 16] = irqPortD; + + // Install the method to be called when PortC triggers. + _VectorsRam[IRQ_PORTC + 16] = irqPortC; + __enable_irq(); + + // Setup the IRQ for Z80_IORQ. + installIRQ(Z80_IORQ, IRQ_MASK_FALLING); + + // Setup the IRQ for Z80_MREQ. + installIRQ(Z80_MREQ, IRQ_MASK_FALLING); + + // Setup the IRQ for Z80_RESET. + installIRQ(Z80_RESET, IRQ_MASK_FALLING); +} + +// Method to restore the interrupt vector when the pin mode is changed and restored to default input mode. +// +static void restoreIRQ(void) +{ + // Setup the IRQ for Z80_IORQ. + installIRQ(Z80_IORQ, IRQ_MASK_FALLING); + + // Setup the IRQ for Z80_MREQ. + installIRQ(Z80_MREQ, IRQ_MASK_FALLING); + + // Setup the IRQ for Z80_RESET. + installIRQ(Z80_RESET, IRQ_MASK_FALLING); +} + +// Method to setup the pins and the pin map to power up default. +// The OS millisecond counter address is passed into this library to gain access to time without the penalty of procedure calls. +// Time is used for timeouts and seriously affects pulse width of signals when procedure calls are made. +// +void setupZ80Pins(uint8_t initTeensy, volatile uint32_t *millisecondTick) +{ + // Locals. + // + static uint8_t firstCall = 1; + + // This method can be called more than once as a convenient way to return all pins to default. On first call though + // the teensy library needs initialising, hence the static check. + // + if(firstCall == 1) + { +printf("FirstCall\n"); + if(initTeensy) + _init_Teensyduino_internal_(); + + // Setup the pointer to the millisecond tick value updated by K64F interrupt. + ms = millisecondTick; + } + + // Setup the map of a loop useable array with its non-linear Pin Number. + // + pinMap[Z80_A0] = Z80_A0_PIN; + pinMap[Z80_A1] = Z80_A1_PIN; + pinMap[Z80_A2] = Z80_A2_PIN; + pinMap[Z80_A3] = Z80_A3_PIN; + pinMap[Z80_A4] = Z80_A4_PIN; + pinMap[Z80_A5] = Z80_A5_PIN; + pinMap[Z80_A6] = Z80_A6_PIN; + pinMap[Z80_A7] = Z80_A7_PIN; + pinMap[Z80_A8] = Z80_A8_PIN; + pinMap[Z80_A9] = Z80_A9_PIN; + pinMap[Z80_A10] = Z80_A10_PIN; + pinMap[Z80_A11] = Z80_A11_PIN; + pinMap[Z80_A12] = Z80_A12_PIN; + pinMap[Z80_A13] = Z80_A13_PIN; + pinMap[Z80_A14] = Z80_A14_PIN; + pinMap[Z80_A15] = Z80_A15_PIN; + pinMap[Z80_A16] = Z80_A16_PIN; + pinMap[Z80_A17] = Z80_A17_PIN; + pinMap[Z80_A18] = Z80_A18_PIN; + + pinMap[Z80_D0] = Z80_D0_PIN; + pinMap[Z80_D1] = Z80_D1_PIN; + pinMap[Z80_D2] = Z80_D2_PIN; + pinMap[Z80_D3] = Z80_D3_PIN; + pinMap[Z80_D4] = Z80_D4_PIN; + pinMap[Z80_D5] = Z80_D5_PIN; + pinMap[Z80_D6] = Z80_D6_PIN; + pinMap[Z80_D7] = Z80_D7_PIN; + + pinMap[Z80_MEM0] = Z80_MEM0_PIN; + pinMap[Z80_MEM1] = Z80_MEM1_PIN; + pinMap[Z80_MEM2] = Z80_MEM2_PIN; + pinMap[Z80_MEM3] = Z80_MEM3_PIN; + pinMap[Z80_MEM4] = Z80_MEM4_PIN; + + pinMap[Z80_IORQ] = Z80_IORQ_PIN; + pinMap[Z80_MREQ] = Z80_MREQ_PIN; + pinMap[Z80_RD] = Z80_RD_PIN; + pinMap[Z80_WR] = Z80_WR_PIN; + pinMap[Z80_WAIT] = Z80_WAIT_PIN; + pinMap[Z80_BUSACK] = Z80_BUSACK_PIN; + + pinMap[Z80_NMI] = Z80_NMI_PIN; + pinMap[Z80_INT] = Z80_INT_PIN; + pinMap[Z80_RESET] = Z80_RESET_PIN; + pinMap[MB_SYSCLK] = SYSCLK_PIN; + pinMap[TZ_BUSACK] = TZ_BUSACK_PIN; + + pinMap[CTL_BUSACK] = CTL_BUSACK_PIN; + pinMap[CTL_BUSRQ] = CTL_BUSRQ_PIN; + pinMap[CTL_RFSH] = CTL_RFSH_PIN; + pinMap[CTL_HALT] = CTL_HALT_PIN; + pinMap[CTL_M1] = CTL_M1_PIN; + pinMap[CTL_CLK] = CTL_CLK_PIN; + pinMap[CTL_CLKSLCT] = CTL_CLKSLCT_PIN; + + // Now build the config array for all the ports. This aids in more rapid function switching as opposed to using + // the pinMode and digitalRead/Write functions provided in the Teensy lib. + // + for(uint8_t idx=0; idx < MAX_TRANZPUTER_PINS; idx++) + { + ioPin[idx] = portConfigRegister(pinMap[idx]); + + // Set to input, will change as functionality dictates. + if(idx != CTL_CLK && idx != CTL_BUSRQ && idx != CTL_BUSACK) + { + pinInput(idx); + } else + { + if(idx == CTL_BUSRQ || idx == CTL_BUSACK) + { + pinOutputSet(idx, HIGH); + } else + { + // Setup the alternative clock frequency on the CTL_CLK pin. + // + analogWriteFrequency(CTL_CLK_PIN, 6000000); + analogWrite(CTL_CLK_PIN, 128); + } + } + } + + // Initialise control structure. + z80Control.refreshAddr = 0x00; + z80Control.disableRefresh = 0; + z80Control.runCtrlLatch = readCtrlLatch(); + z80Control.ctrlMode = Z80_RUN; + z80Control.busDir = TRISTATE; + + // Final part of first call initialisation, now that the pins are configured, setup the interrupts. + // + if(firstCall == 1) + { + firstCall = 0; + + // Control structure elements only in zOS. + // + z80Control.resetEvent = 0; + z80Control.ioAddr = 0; + z80Control.ioData = 0; + z80Control.ioEvent = 0; + z80Control.memorySwap = 0; + z80Control.crtMode = 0; + z80Control.scroll = 0; + + // Setup the Interrupts for IORQ and MREQ. + setupIRQ(); + } + + return; +} + +// Method to reset the Z80 CPU. +// +void resetZ80(void) +{ + // Locals. + // + uint32_t startTime = *ms; + + // Simply change the Z80_RESET pin to an output, assert low, reset high and reset to input to create the hardware reset event. On the original hardware the + // reset pulse width is 90uS, the ms timer is only accurate to 100uS so we apply a 100uS pulse. + // + __disable_irq(); + pinOutputSet(Z80_RESET, LOW); + for(volatile uint32_t pulseWidth=0; pulseWidth < 100; pulseWidth++); + //while((*ms - startTime) < 1); + pinHigh(Z80_RESET); + pinInput(Z80_RESET); + __enable_irq(); + + // Wait a futher settling period before reinstating the interrupt. + // + while((*ms - startTime) < 250); + + // Restore the Z80 RESET IRQ as we have changed the pin mode. + // + installIRQ(Z80_RESET, IRQ_MASK_FALLING); + + // Clear the event flag as it is set + //z80Control.resetEvent = 0; +} + +// Method to request the Z80 Bus. This halts the Z80 and sets all main signals to tri-state. +// Return: 0 - Z80 Bus obtained. +// 1 - Failed to obtain the Z80 bus. +uint8_t reqZ80Bus(uint32_t timeout) +{ + // Locals. + // + uint8_t result = 0; + uint32_t startTime = *ms; + + // Set BUSRQ low which sets the Z80 BUSRQ low. + pinLow(CTL_BUSRQ); + + // Wait for the Z80 to acknowledge the request. + while((*ms - startTime) < timeout && pinGet(Z80_BUSACK)); + + // If we timed out, deassert BUSRQ and return error. + // + if((*ms - startTime) >= timeout) + { + pinHigh(CTL_BUSRQ); + result = 1; + } + + // Store the control latch state before we start modifying anything enabling a return to the pre bus request state. + z80Control.runCtrlLatch = readCtrlLatch(); + + return(result); +} + +// Method to request access to the main motherboard circuitry. This involves requesting the Z80 bus and then +// setting the Z80_RD and Z80_WR signals to low, this maps to a ENABLE_BUS signal which ensures that one half of +// the Mainboard BUSACK request signal is disabled (BUSACK to the motherboard has the opposite meaning, when active +// the mainboard is tri-stated and signals do not pass from th e Z80 and local memory to the mainboard). The other +// half of the mainboard request signal is disabled by setting CTL_BUSACK high. +// +uint8_t reqMainboardBus(uint32_t timeout) +{ + // Locals. + // + uint8_t result = 0; + + // Ensure the CTL BUSACK signal high so we dont assert the mainboard BUSACK signal. + pinHigh(CTL_BUSACK); + + // Requst the Z80 Bus to tri-state the Z80. + if((result=reqZ80Bus(timeout)) == 0) + { + // Now request the mainboard by setting BUSACK high and Z80_RD/Z80_WR low. + pinOutput(Z80_RD); + pinOutput(Z80_WR); + + // A special mode in the FlashRAM decoder detects both RD and WR being low at the same time, this is not feasible under normal Z80 operating conditions, but what it signals + // here is to raise ENABLE_BUS which ensures that BUSACK on the mainboard is deasserted. This is for the case where the Z80 may be running in tranZPUter memory with the + // mainboard disabled. + pinLow(Z80_RD); + pinLow(Z80_WR); + + // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. + for(volatile uint32_t pulseWidth=0; pulseWidth < 1; pulseWidth++); + + // Immediately return the RD/WR to HIGH to complete the ENABLE_BUS latch action. + pinHigh(Z80_RD); + pinHigh(Z80_WR); + + // Store the mode. + z80Control.ctrlMode = MAINBOARD_ACCESS; + + // For mainboard access, MEM4:0 should be 0 so no activity is made to the tranZPUter circuitry except the control latch. + z80Control.curCtrlLatch = TZMM_ORIG; + } else + { + printf("Failed to request Mainboard Bus\n"); + } + + return(result); +} + +// Method to request the local tranZPUter bus. This involves making a Z80 bus request and when relinquished, setting +// the CTL_BUSACK signal to low which disables (tri-states) the mainboard circuitry. +// +uint8_t reqTranZPUterBus(uint32_t timeout) +{ + // Locals. + // + uint8_t result = 0; + + // Ensure the CTL BUSACK signal high so we dont assert the mainboard BUSACK signal. + pinHigh(CTL_BUSACK); + + // Requst the Z80 Bus to tri-state the Z80. + if((result=reqZ80Bus(timeout)) == 0) + { + // Now disable the mainboard by setting BUSACK low. + pinLow(CTL_BUSACK); + + // Store the mode. + z80Control.ctrlMode = TRANZPUTER_ACCESS; + + // For tranZPUter access, MEM4:0 should be TZMM[0-7] so no activity is made to the mainboard circuitry. + z80Control.curCtrlLatch = TZMM_TZPU0; + } + + return(result); +} + +// Method to set all the pins to be able to perform a transaction on the Z80 bus. +// +void setupSignalsForZ80Access(enum BUS_DIRECTION dir) +{ + // Address lines (apart from the upper bank address lines A18:16) need to be outputs. + for(uint8_t idx=Z80_A0; idx <= Z80_A15; idx++) + { + pinOutput(idx); + } + pinInput(Z80_A16); // The upper address bits can only be changed via the 273 latch IC which requires a Z80 IO Write transaction. + pinInput(Z80_A17); + pinInput(Z80_A18); + + // Control signals need to be output and deasserted. + // + pinOutputSet(Z80_IORQ, HIGH); + pinOutputSet(Z80_MREQ, HIGH); + pinOutputSet(Z80_RD, HIGH); + pinOutputSet(Z80_WR, HIGH); + + // Additional control lines need to be outputs and deasserted. These lines are for the main motherboard and not used on the tranZPUter. + // + pinOutputSet(CTL_HALT, HIGH); + pinOutputSet(CTL_RFSH, HIGH); + pinOutputSet(CTL_M1, HIGH); + + // Setup bus direction. + // + setZ80Direction(dir); + return; +} + +// Method to release the Z80, set all signals to input and disable BUSRQ. +// +void releaseZ80(void) +{ + // All address lines to inputs, upper A18:16 are always defined as inputs as they can only be read by the K64F, they are driven by a + // latch on the tranZPUter board. + // + for(uint8_t idx=Z80_A0; idx <= Z80_A15; idx++) + { + pinInput(idx); + } + + // If we were in write mode then set the data bus to input. + // + if(z80Control.busDir == WRITE) + { + // Same for data lines, revert to being inputs. + for(uint8_t idx=Z80_D0; idx <= Z80_D7; idx++) + { + pinInput(idx); + } + } + + // All control signals to inputs. + pinInput(CTL_HALT); + pinInput(CTL_RFSH); + pinInput(CTL_M1); + pinInput(Z80_IORQ); + pinInput(Z80_MREQ); + pinInput(Z80_RD); + pinInput(Z80_WR); + + // Finally release the Z80 by deasserting the CTL_BUSRQ signal. + pinHigh(CTL_BUSACK); + pinHigh(CTL_BUSRQ); + + // Store the mode. + z80Control.ctrlMode = Z80_RUN; + + // Indicate bus direction. + z80Control.busDir = TRISTATE; + + // Restore interrupt monitoring on pins. + // + restoreIRQ(); + return; +} + + +// Method to write a memory mapped byte onto the Z80 bus. +// As the underlying motherboard is 2MHz we keep to its timing best we can in C, for faster motherboards this method may need to +// be coded in assembler. +// +uint8_t writeZ80Memory(uint16_t addr, uint8_t data) +{ + // Locals. + uint32_t startTime = *ms; + volatile uint32_t pulseWidth; + + // Set the data and address on the bus. + // + setZ80Addr(addr); + setZ80Data(data); + + // Setup time before applying control signals. + for(pulseWidth = 0; pulseWidth < 5; pulseWidth++); + pinLow(Z80_MREQ); + + // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait + // signals and faster memory. + // + if(z80Control.ctrlMode == MAINBOARD_ACCESS) + { + // If WAIT has been asserted, loop. Set a timeout to prevent a total lockup. Wait shouldnt exceed 100mS, it it does there is a + // hardware fault and components such as DRAM will lose data due to no refresh. + while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); + + // Start the write cycle, MREQ and WR go low. + pinLow(Z80_WR); + + // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. + //for(volatile uint32_t pulseWidth=0; pulseWidth < 2; pulseWidth++); + + // Another wait loop check as the Z80 can assert wait at the time of Write or anytime before it is deasserted. + while((*ms - startTime) < 200 && pinGet(Z80_WAIT) == 0); + } else + { + // Start the write cycle, MREQ and WR go low. + pinLow(Z80_WR); + } + + // Hold time for the WR signal before clearing it. + for(pulseWidth = 0; pulseWidth < 5; pulseWidth++); + + // Complete the write cycle. + // + pinHigh(Z80_WR); + pinHigh(Z80_MREQ); + return(0); +} + +// Method to read a memory mapped byte from the Z80 bus. +// Keep to the Z80 timing diagram, but for better accuracy of matching the timing diagram this needs to be coded in assembler. +// +uint8_t readZ80Memory(uint16_t addr) +{ + // Locals. + uint32_t startTime = *ms; + uint8_t data; + + // Set the address on the bus and assert MREQ and RD. + // + setZ80Addr(addr); + pinLow(Z80_MREQ); + pinLow(Z80_RD); + + // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait + // signals and faster memory. + // + if(z80Control.ctrlMode == MAINBOARD_ACCESS) + { + // A wait loop check as the Z80 can assert wait during the Read operation to request more time. Set a timeout in case of hardware lockup. + while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); + + // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. This gives time for the addressed device to present the data + // on the data bus. + for(volatile uint32_t pulseWidth=0; pulseWidth < 1; pulseWidth++); + } + + // Fetch the data before deasserting the signals. + // + data = readDataBus(); + + // Complete the read cycle. + // + pinHigh(Z80_RD); + pinHigh(Z80_MREQ); + + // Finally pass back the byte read to the caller. + return(data); +} + + +// Method to write a byte onto the Z80 I/O bus. This method is almost identical to the memory mapped method but are kept seperate for different +// timings as needed, the more code will create greater delays in the pulse width and timing. +// +// As the underlying motherboard is 2MHz we keep to its timing best we can in C, for faster motherboards this method may need to +// be coded in assembler. +// +uint8_t writeZ80IO(uint16_t addr, uint8_t data) +{ + // Locals. + uint32_t startTime = *ms; + + // Set the data and address on the bus. + // + setZ80Addr(addr); + setZ80Data(data); + pinLow(Z80_IORQ); + + // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait + // signals and faster memory. + // + if(z80Control.ctrlMode == MAINBOARD_ACCESS) + { + // If WAIT has been asserted, loop. Set a timeout to prevent a total lockup. Wait shouldnt exceed 100mS, it it does there is a + // hardware fault and components such as DRAM will lose data due to no refresh. + while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); + + // Start the write cycle, MREQ and WR go low. + pinLow(Z80_WR); + + // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. + //for(volatile uint32_t pulseWidth=0; pulseWidth < 2; pulseWidth++); + + // Another wait loop check as the Z80 can assert wait at the time of Write or anytime before it is deasserted. + while((*ms - startTime) < 200 && pinGet(Z80_WAIT) == 0); + } else + { + // Start the write cycle, MREQ and WR go low. + pinLow(Z80_WR); + } + + // Complete the write cycle. + // + pinHigh(Z80_WR); + pinHigh(Z80_IORQ); + + return(0); +} + +// Method to read a byte from the Z80 I/O bus. This method is almost identical to the memory mapped method but are kept seperate for different +// timings as needed, the more code will create greater delays in the pulse width and timing. +// +// As the underlying motherboard is 2MHz we keep to its timing best we can in C, for faster motherboards this method may need to +// be coded in assembler. +uint8_t readZ80IO(uint16_t addr) +{ + // Locals. + uint32_t startTime = *ms; + uint8_t data; + + // Set the address on the bus and assert MREQ and RD. + // + setZ80Addr(addr); + pinLow(Z80_IORQ); + pinLow(Z80_RD); + + // Different logic according to what is being accessed. The mainboard needs to uphold timing and WAIT signals whereas the Tranzputer logic has no wait + // signals and faster memory. + // + if(z80Control.ctrlMode == MAINBOARD_ACCESS) + { + // On a Teensy3.5 K64F running at 120MHz this delay gives a pulsewidth of 760nS. This gives time for the addressed device to present the data + // on the data bus. + //for(volatile uint32_t pulseWidth=0; pulseWidth < 2; pulseWidth++); + + // A wait loop check as the Z80 can assert wait during the Read operation to request more time. Set a timeout in case of hardware lockup. + while((*ms - startTime) < 100 && pinGet(Z80_WAIT) == 0); + } + + // Fetch the data before deasserting the signals. + // + data = readDataBus(); + + // Complete the read cycle. + // + pinHigh(Z80_RD); + pinHigh(Z80_IORQ); + + // Finally pass back the byte read to the caller. + return(data); +} + +// Method to perform a refresh cycle on the z80 mainboard bus to ensure dynamic RAM contents are maintained during extended bus control periods. +// +// Under normal z80 processing a refresh cycle is issued every instruction during the T3/T4 cycles. As we arent reading instructions but just reading/writing +// then the refresh cycles are made after a cnofigurable set number of bus transactions (ie. 128 read/writes). This has to occur when either of the busses +// are under K64F control so a call to this method should be made frequently. +// +void refreshZ80(void) +{ + // Locals. + volatile uint8_t idx = 0; + + // Check to see if a refresh operation is needed. + // + if(z80Control.disableRefresh == 1) + return; + + // Set 7 bits on the address bus. + setZ80RefreshAddr(z80Control.refreshAddr); + + // If we are controlling the tranZPUter bus then a switch to the mainboard needs to be made first. + // + if(z80Control.ctrlMode == TRANZPUTER_ACCESS) + { + // Quick way to gain mainboard access, force an enable of the Z80_BUSACK on the mainboard by setting RD/WR low, this fires an enable pulse at the 279 RS Flip Flop + // and setting CTL_BUSACK high enables the second component forming the Z80_BUSACK signal. + pinLow(Z80_RD); + pinLow(Z80_WR); + pinHigh(Z80_RD); + pinHigh(Z80_WR); + pinHigh(CTL_BUSACK); + } + + // Assert Refresh. + pinLow(CTL_RFSH); + pinLow(Z80_MREQ); + idx++; // Increase the pulse width of the MREQ signal. + pinHigh(Z80_MREQ); + pinHigh(CTL_RFSH); + + // Restore access to tranZPUter bus if this was the original mode. + if(z80Control.ctrlMode == TRANZPUTER_ACCESS) + { + // Restore tranZPUter bus control. + pinLow(CTL_BUSACK); + } + + // Increment refresh address to complete. + z80Control.refreshAddr++; + z80Control.refreshAddr &= 0x7f; + return; +} + +// Method to perform a full row refresh on the dynamic DRAM. This method writes out a full 7bit address so that all rows are refreshed. +// +void refreshZ80AllRows(void) +{ + // Locals. + volatile uint8_t idx; + + // Check to see if a refresh operation is needed. + // + if(z80Control.disableRefresh == 1) + return; + + // If we are controlling the tranZPUter bus then a switch to the mainboard needs to be made first. + // + if(z80Control.ctrlMode == TRANZPUTER_ACCESS) + { + // Quick way to gain mainboard access, force an enable of the Z80_BUSACK on the mainboard by setting RD/WR low, this fires an enable pulse at the 279 RS Flip Flop + // and setting CTL_BUSACK high enables the second component forming the Z80_BUSACK signal. + pinLow(Z80_RD); + pinLow(Z80_WR); + pinHigh(Z80_RD); + pinHigh(Z80_WR); + pinHigh(CTL_BUSACK); + } + + // Loop through all 7 bits of refresh rows. + idx = 0; + while(idx < 0x80) + { + // Set 7 bits on the address bus. + setZ80RefreshAddr(idx); + + // Assert Refresh. + pinLow(CTL_RFSH); + pinLow(Z80_MREQ); + idx++; + pinHigh(Z80_MREQ); + pinHigh(CTL_RFSH); + } + + // Restore access to tranZPUter bus if this was the original mode. + if(z80Control.ctrlMode == TRANZPUTER_ACCESS) + { + // Restore tranZPUter bus control. + pinLow(CTL_BUSACK); + } + return; +} + +// Method to explicitly set the Memory model/mode of the tranZPUter. +// +void setCtrlLatch(uint8_t latchVal) +{ + // Gain control of the bus then set the latch value. + // + if(reqTranZPUterBus(100) == 0) + { + // Setup the pins to perform a read operation (after setting the latch to starting value). + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(latchVal); + releaseZ80(); + } + return; +} + +// Method to copy memory from the K64F to the Z80. +// +uint8_t copyFromZ80(uint8_t *dst, uint32_t src, uint32_t size, uint8_t mainBoard) +{ + // Locals. + uint8_t result = 0; + uint8_t upperAddrBits = 0; + + // Sanity checks. + // + if((mainBoard == 1 && (src+size) > 0x10000) || (mainBoard == 0 && (src+size) > 0x80000)) + return(1); + + // Request the correct bus. + // + if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) + { + // Setup the pins to perform a read operation (after setting the latch to starting value). + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + setZ80Direction(READ); + + for(uint32_t idx=0; idx < size && result == 0; idx++) + { + // If the address changes the upper address bits, update the latch to reflect the change. + if((uint8_t)(src >> 16) != upperAddrBits) + { + // Write the upper address bits to the 273 control latch to select the next memory model which allows access to the subsequent 64K bank. + setZ80Direction(WRITE); + upperAddrBits = (uint8_t)(src >> 16); + writeCtrlLatch(TZMM_TZPU0 + upperAddrBits); + setZ80Direction(READ); + } + + // Perform a refresh on the main memory every 2ms. + // + if(idx % RFSH_BYTE_CNT == 0) + { + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + } + + // And now read the byte and store. + *dst = readZ80Memory((uint16_t)src); + src++; + dst++; + } + + // Restore the control latch to its original configuration. + // + setZ80Direction(WRITE); + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + + return(result); +} + +// Method to copy memory to the K64F from the Z80. +// +uint8_t copyToZ80(uint32_t dst, uint8_t *src, uint32_t size, uint8_t mainBoard) +{ + // Locals. + uint8_t result = 0; + uint8_t upperAddrBits = 0; + + // Sanity checks. + // + if((mainBoard == 1 && (dst+size) > 0x10000) || (mainBoard == 0 && (dst+size) > 0x80000)) + return(1); + + // Request the correct bus. + // + if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) + { + // Setup the pins to perform a write operation. + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + + for(uint32_t idx=0; idx < size && result == 0; idx++) + { + // If the address changes the upper address bits, update the latch to reflect the change. + if((uint8_t)(dst >> 16) != upperAddrBits) + { + // Write the upper address bits to the 273 control latch to select the next memory model which allows access to the subsequent 64K bank. + upperAddrBits = (uint8_t)(dst >> 16); + writeCtrlLatch(TZMM_TZPU0 + upperAddrBits); + } + + // Perform a refresh on the main memory every 2ms. + // + if(idx % RFSH_BYTE_CNT == 0) + { + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + } + + // And now write the byte and to the next address! + writeZ80Memory((uint16_t)dst, *src); + src++; + dst++; + } + + // Restore the control latch to its original configuration. + // + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + + return(result); +} + + +// Method to fill memory under the Z80 control, either the mainboard or tranZPUter memory. +// +void fillZ80Memory(uint32_t addr, uint32_t size, uint8_t data, uint8_t mainBoard) +{ + // Locals. + uint8_t upperAddrBits = 0; + + if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) + { + // Setup the pins to perform a read operation (after setting the latch to starting value). + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + + // Fill the memory but every RFSH_BYTE_CNT perform a DRAM refresh. + // + for(uint32_t idx=addr; idx < (addr+size); idx++) + { + // If the address changes the upper address bits, update the latch to reflect the change. + if((uint8_t)(idx >> 16) != upperAddrBits) + { + // Write the upper address bits to the 273 control latch to select the next memory model which allows access to the subsequent 64K bank. + upperAddrBits = (uint8_t)(idx >> 16); + writeCtrlLatch(TZMM_TZPU0 + upperAddrBits); + } + + if(idx % RFSH_BYTE_CNT == 0) + { + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + } + writeZ80Memory((uint16_t)idx, data); + } + + // Restore the control latch to its original configuration. + // + setZ80Direction(WRITE); + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + return; +} + +// A method to read the full video frame buffer from the Sharp MZ80A and store it in local memory (control structure). +// No refresh cycles are needed as we grab the frame but between frames a full refresh is performed. +// +void captureVideoFrame(enum VIDEO_FRAMES frame, uint8_t noAttributeFrame) +{ + // Locals. + + if(reqMainboardBus(100) == 0) + { + // Setup the pins to perform a read operation (after setting the latch to starting value). + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + setZ80Direction(READ); + + // No need for refresh as we take less than 2ms time, just grab the video frame. + for(uint16_t idx=0; idx < MZ_VID_RAM_SIZE; idx++) + { + z80Control.videoRAM[frame][idx] = readZ80Memory((uint16_t)idx+MZ_VID_RAM_ADDR); + } + + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + + // If flag not set capture the attribute RAM. This is normally not present on a standard Sharp MZ80A only present on models + // with the MZ80A Colour Board upgrade. + // + if(noAttributeFrame == 0) + { + // Same for the attribute frame, no need for refresh as we take 2ms or less, just grab it. + for(uint16_t idx=0; idx < MZ_ATTR_RAM_SIZE; idx++) + { + z80Control.attributeRAM[frame][idx] = readZ80Memory((uint16_t)idx+MZ_ATTR_RAM_ADDR); + } + + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + } + + // Restore the control latch to its original configuration. + // + setZ80Direction(WRITE); + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + return; +} + +// Method to refresh the video frame buffer on the Sharp MZ80A with the data held in local memory. +// +void refreshVideoFrame(enum VIDEO_FRAMES frame, uint8_t scrolHome, uint8_t noAttributeFrame) +{ + // Locals. + + if(reqMainboardBus(100) == 0) + { + // Setup the pins to perform a write operation. + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + + // No need for refresh as we take less than 2ms time, just write the video frame. + for(uint16_t idx=0; idx < MZ_VID_RAM_SIZE; idx++) + { + writeZ80Memory((uint16_t)idx+MZ_VID_RAM_ADDR, z80Control.videoRAM[frame][idx]); + } + + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + + // If flag not set write out to the attribute RAM. This is normally not present on a standard Sharp MZ80A only present on models + // with the MZ80A Colour Board upgrade. + // + if(noAttributeFrame == 0) + { + // No need for refresh as we take less than 2ms time, just write the video frame. + for(uint16_t idx=0; idx < MZ_ATTR_RAM_SIZE; idx++) + { + writeZ80Memory((uint16_t)idx+MZ_ATTR_RAM_ADDR, z80Control.attributeRAM[frame][idx]); + } + + // Perform a full row refresh to maintain the DRAM. + refreshZ80AllRows(); + } + + // If the Scroll Home flag is set, this means execute a read against the hardware scoll register to restore the position 0,0 to location MZ_VID_RAM_ADDR. + if(scrolHome) + { + setZ80Direction(READ); + readZ80Memory((uint16_t)MZ_SCROL_BASE); + } + + // Restore the control latch to its original configuration. + // + setZ80Direction(WRITE); + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + return; +} + +// Method to load up the local video frame buffer from a file. +// +FRESULT loadVideoFrameBuffer(char *src, enum VIDEO_FRAMES frame) +{ + // Locals. + // + FIL File; + unsigned int readSize; + FRESULT result; + + // Sanity check on filenames. + if(src == NULL) + return(FR_INVALID_PARAMETER); + + // Try and open the source file. + result = f_open(&File, src, FA_OPEN_EXISTING | FA_READ); + + // If no errors in opening the file, proceed with reading and loading into memory. + if(!result) + { + memset(z80Control.videoRAM[frame], MZ_VID_DFLT_BYTE, MZ_VID_RAM_SIZE); + result = f_read(&File, z80Control.videoRAM[frame], MZ_VID_RAM_SIZE, &readSize); + if (!result) + { + memset(z80Control.attributeRAM[frame], MZ_ATTR_DFLT_BYTE, MZ_ATTR_RAM_SIZE); + result = f_read(&File, z80Control.attributeRAM[frame], MZ_ATTR_RAM_SIZE, &readSize); + } + + // Close to sync files. + f_close(&File); + } else + { + printf("File not found:%s\n", src); + } + + return(result ? result : FR_OK); +} + +// Method to save the local video frame buffer into a file. +// +FRESULT saveVideoFrameBuffer(char *dst, enum VIDEO_FRAMES frame) +{ + // Locals. + // + FIL File; + unsigned int writeSize; + FRESULT result; + + // Sanity check on filenames. + if(dst == NULL) + return(FR_INVALID_PARAMETER); + + // Try and create the destination file. + result = f_open(&File, dst, FA_CREATE_ALWAYS | FA_WRITE); + + // If no errors in opening the file, proceed with reading and loading into memory. + if(!result) + { + // Write the entire framebuffer to the SD file, video then attribute. + // + result = f_write(&File, z80Control.videoRAM[frame], MZ_VID_RAM_SIZE, &writeSize); + if (!result && writeSize == MZ_VID_RAM_SIZE) + { + result = f_write(&File, z80Control.attributeRAM[frame], MZ_ATTR_RAM_SIZE, &writeSize); + } + + // Close to sync files. + f_close(&File); + } else + { + printf("Cannot create file:%s\n", dst); + } + + return(result ? result : FR_OK); +} + +// Simple method to pass back the address of a video frame for local manipulation. +// +char *getVideoFrame(enum VIDEO_FRAMES frame) +{ + return((char *)&z80Control.videoRAM[frame]); +} + +// Simple method to pass back the address of an attribute frame for local manipulation. +// +char *getAttributeFrame(enum VIDEO_FRAMES frame) +{ + return((char *)&z80Control.attributeRAM[frame]); +} + +// Method to load a file from the SD card directly into the tranZPUter static RAM or mainboard RAM. +// +FRESULT loadZ80Memory(const char *src, uint32_t fileOffset, uint32_t addr, uint32_t size, uint8_t mainBoard, uint8_t releaseBus) +{ + // Locals. + // + FIL File; + uint8_t upperAddrBits = 0; + uint32_t loadSize = 0L; + uint32_t sizeToRead = 0L; + uint32_t memPtr = addr; + unsigned int readSize; + unsigned char buf[SECTOR_SIZE]; + FRESULT fr0; + + // Sanity check on filenames. + if(src == NULL) + return(FR_INVALID_PARAMETER); + + // Try and open the source file. + fr0 = f_open(&File, src, FA_OPEN_EXISTING | FA_READ); + + // If no size given get the file size. + // + if(size == 0) + { + if(!fr0) + fr0 = f_lseek(&File, f_size(&File)); + if(!fr0) + size = (uint32_t)f_tell(&File); +printf("Size:%08lx\n", size); + } + + // Seek to the correct location. + // + if(!fr0) + fr0 = f_lseek(&File, fileOffset); + + // If no errors in opening the file, proceed with reading and loading into memory. + if(!fr0) + { + // If the Z80 is in RUN mode, request the bus. + // This mechanism allows for the load command to leave the BUS under the tranZPUter control for upload of multiple files. + // Care needs to be taken though that no more than 2-3ms passes without a call to refreshZ80AllRows() otherwise memory loss + // may occur. + // + if(z80Control.ctrlMode == Z80_RUN) + { + // Request the board according to the mainboard flag, mainboard = 1 then the mainboard is controlled otherwise the tranZPUter board. + if(mainBoard == 0) + { + reqTranZPUterBus(100); + } else + { + reqMainboardBus(100); + } + + // If successful, setup the control pins for upload mode. + // + if(z80Control.ctrlMode != Z80_RUN) + { + // Setup the pins to perform a write operation. + // + setupSignalsForZ80Access(WRITE); + + // Setup the control latch to the required starting configuration. + writeCtrlLatch(z80Control.curCtrlLatch); + } + } else + { + // See if the bus needs changing. + // + enum CTRL_MODE newMode = (mainBoard == 0) ? TRANZPUTER_ACCESS : MAINBOARD_ACCESS; + reqZ80BusChange(newMode); + } + + // If we have taken control of the bus, commence upload. + // + if(z80Control.ctrlMode != Z80_RUN) + { + // Loop, reading a sector at a time from SD file and writing it directly into the Z80 tranZPUter RAM or mainboard RAM. + // + loadSize = 0; + memPtr = addr; + do { + // Wrap a disk read with two full refresh periods to counter for the amount of time taken to read disk. + refreshZ80AllRows(); + sizeToRead = (size-loadSize) > SECTOR_SIZE ? SECTOR_SIZE : size - loadSize; + fr0 = f_read(&File, buf, sizeToRead, &readSize); + refreshZ80AllRows(); + if (fr0 || readSize == 0) break; /* error or eof */ + + // Go through each byte in sector and send to Z80 bus. + for(unsigned int idx=0; idx < readSize; idx++) + { + // If the address changes the upper address bits, update the latch to reflect the change. + if((uint8_t)(memPtr >> 16) != upperAddrBits) + { + // Write the upper address bits to the 273 control latch to select the next memory model which allows access to the subsequent 64K bank. + upperAddrBits = (uint8_t)(memPtr >> 16); + writeCtrlLatch(TZMM_TZPU0 + upperAddrBits); + } + + // At the halfway mark perform a full refresh. + if(idx == (SECTOR_SIZE/2)) + { + refreshZ80AllRows(); + } + + // And now write the byte and to the next address! + writeZ80Memory((uint16_t)memPtr, buf[idx]); + memPtr++; + } + loadSize += readSize; + } while(loadSize < size); + } else + { + printf("Failed to request Z80 access.\n"); + fr0 = FR_INT_ERR; + } + + // Close to sync files. + f_close(&File); + } else + { + printf("File not found:%s\n", src); + } + + // If requested or an error occurs, then release the Z80 bus as no more uploads will be taking place in this batch. + // + if(releaseBus == 1 || fr0) + { + // Restore the control latch to its original configuration. + // + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + + return(fr0 ? fr0 : FR_OK); +} + + +// Method to load an MZF format file from the SD card directly into the tranZPUter static RAM or mainboard RAM. +// If the load address is specified then it overrides the MZF header value, otherwise load addr is taken from the header. +// +FRESULT loadMZFZ80Memory(const char *src, uint32_t addr, uint8_t mainBoard, uint8_t releaseBus) +{ + // Locals. + FIL File; + unsigned int readSize; + unsigned char buf[128]; + FRESULT fr0; + + // Sanity check on filenames. + if(src == NULL) + return(FR_INVALID_PARAMETER); + + // Try and open the source file. + fr0 = f_open(&File, src, FA_OPEN_EXISTING | FA_READ); + + // If no error occurred, read in the header. + // + if(!fr0) + fr0 = f_read(&File, buf, MZF_HEADER_SIZE, &readSize); + + // No errors, process. + if(!fr0 && readSize == MZF_HEADER_SIZE) + { + // Firstly, close the file, no longer needed. + f_close(&File); + + // Save the header into the CMT area for reference, some applications expect it. + // + copyToZ80(MZ_CMT_ADDR, (uint8_t *)buf, MZF_HEADER_SIZE, 0); + + // Now obtain the parameters. + // + if(addr == 0xFFFFFFFF) + addr = (uint32_t)(buf[MZF_LOADADDR+1] << 8) | (uint32_t)buf[MZF_LOADADDR]; + + // Ok, load up the file into Z80 memory. + fr0 = loadZ80Memory(src, MZF_HEADER_SIZE, addr, 0, mainBoard, releaseBus); + } + + return(fr0 ? fr0 : FR_OK); +} + + +// Method to read a section of the tranZPUter/mainboard memory and store it in an SD file. +// +FRESULT saveZ80Memory(const char *dst, uint32_t addr, uint32_t size, t_svcDirEnt *mzfHeader, uint8_t mainBoard) +{ + // Locals. + // + FIL File; + uint8_t upperAddrBits = 0; + uint32_t saveSize = 0L; + uint32_t sizeToWrite; + uint32_t memPtr = addr; + unsigned int writeSize = 0; + unsigned char buf[SECTOR_SIZE]; + FRESULT fr0; + + // Sanity check on filenames. + if(dst == NULL || size == 0) + return(FR_INVALID_PARAMETER); + + // Try and create the destination file. + fr0 = f_open(&File, dst, FA_CREATE_ALWAYS | FA_WRITE); + printf("SAVE:%8lx,%8lx\n",addr,size); + // If no errors in opening the file, proceed with reading and loading into memory. + if(!fr0) + { + // If an MZF header has been passed, we are saving an MZF file, write out the MZF header first prior to the data. + // + if(mzfHeader) + { + fr0 = f_write(&File, (char *)mzfHeader, MZF_HEADER_SIZE, &writeSize); + } + + if(!fr0) + { + if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) + { + // Setup the pins to perform a read operation (after setting the latch to starting value). + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + setZ80Direction(READ); + + // Loop, reading a sector worth of data (or upto limit remaining) from the Z80 tranZPUter RAM or mainboard RAM and writing it into the open SD card file. + // + saveSize = 0; + for (;;) { + + // Work out how many bytes to write in the sector then fetch from the Z80. + sizeToWrite = (size-saveSize) > SECTOR_SIZE ? SECTOR_SIZE : size - saveSize; + for(unsigned int idx=0; idx < sizeToWrite; idx++) + { + // If the address changes the upper address bits, update the latch to reflect the change. + if((uint8_t)(memPtr >> 16) != upperAddrBits) + { + // Write the upper address bits to the 273 control latch to select the next memory model which allows access to the subsequent 64K bank. + setZ80Direction(WRITE); + upperAddrBits = (uint8_t)(memPtr >> 16); + writeCtrlLatch(TZMM_TZPU0 + upperAddrBits); + setZ80Direction(READ); + } + + // At the halfway mark perform a full refresh. + if(idx == (SECTOR_SIZE/2)) + { + refreshZ80AllRows(); + } + + // And now read the byte and to the next address! + buf[idx] = readZ80Memory((uint16_t)memPtr); + memPtr++; + } + + // Wrap disk write with two full refresh periods to counter for the amount of time taken to write to disk. + refreshZ80AllRows(); + fr0 = f_write(&File, buf, sizeToWrite, &writeSize); + refreshZ80AllRows(); + saveSize += writeSize; + + if (fr0 || writeSize < sizeToWrite || saveSize >= size) break; // error, disk full or range written. + } + + // Restore the control latch to its original configuration. + // + setZ80Direction(WRITE); + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + printf("Saved %ld bytes, final address:%lx\n", saveSize, memPtr); + + } else + { + printf("Failed to request Z80 access.\n"); + } + } else + { + printf("Failed to write the MZF header.\n"); + } + + // Close to sync files. + f_close(&File); + + } else + { + printf("Cannot create file:%s\n", dst); + } + + return(fr0 ? fr0 : FR_OK); +} + +// Function to dump out a given section of the Z80 memory on the tranZPUter board or mainboard. +// +int memoryDumpZ80(uint32_t memaddr, uint32_t memsize, uint32_t dispaddr, uint8_t dispwidth, uint8_t mainBoard) +{ + uint8_t data; + uint8_t upperAddrBits = 0; + int8_t keyIn = 0; + uint32_t pnt = memaddr; + uint32_t endAddr = memaddr + memsize; + uint32_t addr = dispaddr; + uint32_t i = 0; + char c = 0; + + // Sanity checks. + // + if((mainBoard == 1 && (memaddr+memsize) > 0x10000) || (mainBoard == 0 && (memaddr+memsize) > 0x80000)) + return(1); + + // Request the correct bus. + // + if( (mainBoard == 0 && reqTranZPUterBus(100) == 0) || (mainBoard != 0 && reqMainboardBus(100) == 0) ) + { + // Setup the pins to perform a read operation (after setting the latch to starting value). + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + setZ80Direction(READ); + + while (1) + { + // If the address changes the upper address bits, update the latch to reflect the change. + if((uint8_t)(pnt >> 16) != upperAddrBits) + { + // Write the upper address bits to the 273 control latch to select the next memory model which allows access to the subsequent 64K bank. + setZ80Direction(WRITE); + upperAddrBits = (uint8_t)(pnt >> 16); + writeCtrlLatch(TZMM_TZPU0 + upperAddrBits); + setZ80Direction(READ); + } + + // Print the current address. + printf("%06lX", addr); + printf(": "); + + // print hexadecimal data + for (i=0; i < dispwidth; i++) + { + if(pnt+i < endAddr) + { + data = readZ80Memory(pnt+i); + printf("%02X", data); + } + else + printf(" "); + + fputc((char)' ', stdout); + } + + // print ascii data + printf(" |"); + + // print single ascii char + for (i=0; i < dispwidth; i++) + { + c = (char)readZ80Memory(pnt+i); + if ((pnt+i < endAddr) && (c >= ' ') && (c <= '~')) + fputc((char)c, stdout); + else + fputc((char)' ', stdout); + } + + puts("|"); + + // Move on one row. + pnt += dispwidth; + addr += dispwidth; + + // Refresh Z80 DRAM at end of each line. + // + refreshZ80(); + + // User abort (ESC), pause (Space) or all done? + // + #if defined __K64F__ + keyIn = usb_serial_getchar(); + #elif defined __ZPU__ + keyIn = getserial_nonblocking(); + #else + #error "Target CPU not defined, use __ZPU__ or __K64F__" + #endif + if(keyIn == ' ') + { + do { + // Keep refreshing the Z80 DRAM whilst we wait for a key. + // + refreshZ80(); + + #if defined __K64F__ + keyIn = usb_serial_getchar(); + #elif defined __ZPU__ + keyIn = getserial_nonblocking(); + #else + #error "Target CPU not defined, use __ZPU__ or __K64F__" + #endif + } while(keyIn != ' ' && keyIn != 0x1b); + } + // Escape key pressed, exit with 0 to indicate this to caller. + if (keyIn == 0x1b) + { + break; + } + + // End of buffer, exit the loop. + if(pnt >= (memaddr + memsize)) + { + break; + } + } + + // Restore the control latch to its original configuration. + // + setZ80Direction(WRITE); + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + } + + // Normal exit, return -1, escape exit return 0. + return(keyIn == 0x1b ? 0 : -1); +} + +/////////////////////////////////////////////////////// +// Getter/Setter methods to keep z80Control private. // +/////////////////////////////////////////////////////// + +// Method to test if a reset event has occurred, ie. the user pressed the RESET button. +uint8_t isZ80Reset(void) +{ + // Return the value which would have been updated in the interrupt routine. + return(z80Control.resetEvent == 1); +} + +// Method to test to see if the main memory has been swapped from 0000-0FFF to C000-CFFF +uint8_t isZ80MemorySwapped(void) +{ + // Return the value which would have been updated in the interrupt routine. + return(z80Control.memorySwap == 1); +} + +// Method to get an IO instruction event should one have occurred since last poll. +// +uint8_t getZ80IO(uint8_t *addr, uint8_t *data) +{ + // Locals. + uint8_t retcode = z80Control.ioEvent; + + // Load up the variables if an event has occurred. + if(retcode == 1) + { + *addr = z80Control.ioAddr; + *data = z80Control.ioData; + z80Control.ioEvent = 0; + } + + return(retcode); +} + +// Method to clear a Z80 RESET event. +// +void clearZ80Reset(void) +{ + z80Control.resetEvent = 0; +} + +// Method to convert a Sharp filename into an Ascii filename. +// +void convertSharpFilenameToAscii(char *dst, char *src, uint8_t size) +{ + // Loop through and convert each character via the mapping table. + for(uint8_t idx=0; idx < size; idx++) + { + *dst = (char)asciiMap[(int)*src].asciiCode; + src++; + dst++; + } + // As the Sharp Filename does not always contain a terminator, set a NULL at the end of the string (size+1) so that it can be processed + // by standard routines. This means dst must always be size+1 in length. + // + *dst = 0x00; + return; +} + +////////////////////////////////////////////////////////////// +// tranZPUter i/f methods for zOS - handling and control // +// // +////////////////////////////////////////////////////////////// + +// Method to load the default ROMS into the tranZPUter RAM ready for start. +// If the autoboot flag is set, perform autoboot by wiping the STACK area +// of the SA1510 - this has the effect of JP 00000H. +// +void loadTranZPUterDefaultROMS(void) +{ + // Locals. + FRESULT result; + + // Start off by clearing memory, the AS6C4008 chip holds random values at power on. + fillZ80Memory(0x000000, 0x80000, 0x00, 0); + + // Now load the necessary images into memory. + if((result=loadZ80Memory((const char *)MZ_ROM_SA1510_40C, 0, MZ_MROM_ADDR, 0, 0, 1)) != FR_OK) + { + printf("Error: Failed to load %s into tranZPUter memory.\n", MZ_ROM_SA1510_40C); + } + if(!result && (result=loadZ80Memory((const char *)MZ_ROM_TZFS, 0, MZ_UROM_ADDR, 0x1800, 0, 1) != FR_OK)) + { + printf("Error: Failed to load bank 1 of %s into tranZPUter memory.\n", MZ_ROM_TZFS); + } + if(!result && (result=loadZ80Memory((const char *)MZ_ROM_TZFS, 0x1800, MZ_BANKRAM_ADDR+0x10000, 0x1000, 0, 1) != FR_OK)) + { + printf("Error: Failed to load page 2 of %s into tranZPUter memory.\n", MZ_ROM_TZFS); + } + if(!result && (result=loadZ80Memory((const char *)MZ_ROM_TZFS, 0x2800, MZ_BANKRAM_ADDR+0x20000, 0x1000, 0, 0) != FR_OK)) + { + printf("Error: Failed to load page 3 of %s into tranZPUter memory.\n", MZ_ROM_TZFS); + } + if(!result && (result=loadZ80Memory((const char *)MZ_ROM_TZFS, 0x3800, MZ_BANKRAM_ADDR+0x30000, 0x1000, 0, 1) != FR_OK)) + { + printf("Error: Failed to load page 4 of %s into tranZPUter memory.\n", MZ_ROM_TZFS); + } + + // If all was ok loading the roms, complete the startup. + // + if(!result) + { + // Set the memory model to BOOT so we can bootstrap TZFS. + setCtrlLatch(TZMM_BOOT); + + // If autoboot flag set, force a restart to the ROM which will call User ROM startup code. + if(osControl.tzAutoBoot) + { + fillZ80Memory(MZ_MROM_STACK_ADDR, MZ_MROM_STACK_SIZE, 0x00, 1); + } + z80Control.disableRefresh = 1; + } +} + +// Method to set the service status flag on the Z80 (and duplicated in the internal +// copy). +// +uint8_t setZ80SvcStatus(uint8_t status) +{ + // Locals + uint8_t result = 1; + + // Request the tranZPUter bus. + // + if(reqTranZPUterBus(100) == 0) + { + // Setup the pins to perform a write operation. + // + setupSignalsForZ80Access(WRITE); + writeCtrlLatch(z80Control.curCtrlLatch); + + // Update the memory location. + // + result=writeZ80Memory(TZSVC_CMD_STRUCT_ADDR+TZSVC_RESULT_OFFSET, status); + + // Release the Z80. + writeCtrlLatch(z80Control.runCtrlLatch); + releaseZ80(); + + // Update in-memory copy of the result variable. + svcControl.result = status; + } else + { + result = 1; + } + return(result); +} + +// Simple method to set defaults in the service structure if not already set by the Z80. +// +void svcSetDefaults(void) +{ + // If there is no directory path, use the inbuilt default. + if(svcControl.directory[0] == '\0') + { + strcpy((char *)svcControl.directory, TZSVC_DEFAULT_DIR); + } + + // If there is no wildcard matching, use default. + if(svcControl.wildcard[0] == '\0') + { + strcpy((char *)svcControl.wildcard, TZSVC_DEFAULT_WILDCARD); + } +} + +// Helper method for matchFileWithWildcard. +// Get the next character from the filename or pattern and modify it if necessary to match the Sharp character set. +static uint32_t getNextChar(const char** ptr) +{ + uint8_t chr; + + // Get a byte + do { + chr = (uint8_t)*(*ptr)++; + + // Skip over CR. + } while(chr == 0x0D); + + // To upper ASCII char + if(islower(chr)) chr -= 0x20; + + return chr; +} + +// Match an MZF name with a given wildcard. +// This method originated from the private method in FatFS but adapted to work with MZF filename matching. +// Input: wildcard - Pattern to match +// fileName - MZF fileName, either CR or NUL terminated or MZF_FILENAME_LEN chars long. +// skip - Number of characters to skip due to ?'s +// infinite - Infinite search as * specified. +// Output: 0 - No match +// 1 - Match +// +static int matchFileWithWildcard(const char *pattern, const char *fileName, int skip, int infinite) +{ + const char *pp, *np; + uint32_t pc, nc; + int nm, nx; + + // Pre-skip name chars + while (skip--) + { + // Branch mismatched if less name chars + if (!getNextChar(&fileName)) return 0; + } + // (short circuit) + if (*pattern == 0 && infinite) return 1; + + do { + // Top of pattern and name to match + pp = pattern; np = fileName; + for (;;) + { + // Wildcard? + if (*pp == '?' || *pp == '*') + { + nm = nx = 0; + + // Analyze the wildcard block + do { + if (*pp++ == '?') nm++; else nx = 1; + } while (*pp == '?' || *pp == '*'); + + // Test new branch (recurs upto number of wildcard blocks in the pattern) + if (matchFileWithWildcard(pp, np, nm, nx)) return 1; + + // Branch mismatched + nc = *np; break; + } + + // End of filename, Sharp filenames can be terminated with 0x00, CR or size. If we get to the end of the name then it is + // a match. + // + if((np - fileName) == MZF_FILENAME_LEN) return 1; + + // Get a pattern char + pc = getNextChar(&pp); + + // Get a name char + nc = getNextChar(&np); + + // Branch mismatched? + if (pc != nc) break; + + // Branch matched? (matched at end of both strings) + if (pc == 0) return 1; + } + + // fileName++ + getNextChar(&fileName); + + /* Retry until end of name if infinite search is specified */ + } while (infinite && nc); + + return 0; +} + +// Method to open/read a directory listing. +// This method opens a given directory on the SD card (the z80 provides the directory name or it defaults to MZF). The directory +// is read and a unique incrementing number is given to each entry, this number can be used in a later request to open the file to save on +// name entry and matching. +// A basic pattern filter is applied to the results returned, ie A* will return only files starting with A. +// +// The parameters are passed in the svcControl block. +// +uint8_t svcReadDir(uint8_t mode) +{ + // Locals - dont use global as this is a seperate thread. + // + static DIR dirFp; + static uint8_t dirOpen = 0; // Seperate flag as their is no public way to validate that dirFp is open and valid, the method in FatFS is private for this functionality. + static FILINFO fno; + static uint8_t dirSector = 0; // Virtual directory sector. + FRESULT result = FR_OK; + unsigned int readSize; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + FIL File; + t_svcCmpDirBlock *dirBlock = (t_svcCmpDirBlock *)&svcControl.sector; + + // Request to open? Validate that we dont already have an open directory then open the requested one. + if(mode == TZSVC_OPEN) + { + // Close if previously open. + if(dirOpen == 1) + svcReadDir(TZSVC_CLOSE); + + // Setup the defaults + // + svcSetDefaults(); + + // Open the directory. + result = f_opendir(&dirFp, (char *)&svcControl.directory); + if(result == FR_OK) + { + // Reentrant call to actually read the data. + // + dirOpen = 1; + dirSector = 0; + result = (FRESULT)svcReadDir(TZSVC_NEXT); + } + } + + // Read a block of directory entries into the z80 service buffer sector. + else if(mode == TZSVC_NEXT && dirOpen == 1) + { + // If the Z80 is requesting a non sequential directory sector then we have to start from the beginning as each sector is built up not read. + // + if(dirSector != svcControl.dirSector) + { + // If the current sector is after the requested sector, rewind by re-opening. + // + if(dirSector < svcControl.dirSector) + { + result=svcReadDir(TZSVC_OPEN); + } + if(!result) + { + // Now get the sector by advancement. + for(uint8_t idx=dirSector; idx < svcControl.dirSector && result == FR_OK; idx++) + { + result=svcReadDir(TZSVC_NEXT); + } + } + } + + // Proceed if no errors have occurred. + // + if(!result) + { + // Zero the directory entry block - unused entries will then appear as NULLS. + memset(dirBlock, 0x00, TZSVC_SECTOR_SIZE); + + // Loop the required number of times to fill a sector full of entries. + // + uint8_t idx=0; + while(idx < TZVC_MAX_CMPCT_DIRENT_BLOCK && result == FR_OK) + { + // Read an SD directory entry then open the returned SD file so that we can to read out the important MZF data which is stored in it as it will be passed to the Z80. + // + result = f_readdir(&dirFp, &fno); + + // If an error occurs or we are at the end of the directory listing close the sector and pass back. + if(result != FR_OK || fno.fname[0] == 0) break; + + // Check to see if this is a valid MZF file. + const char *ext = strrchr(fno.fname, '.'); + if(!ext || strcasecmp(++ext, TZSVC_DEFAULT_EXT) != 0) + continue; + + // Build filename. + // + sprintf(fqfn, "0:\\%s\\%s", svcControl.directory, fno.fname); + + // Open the file so we can read out the MZF header which is the information TZFS/CPM needs. + // + result = f_open(&File, fqfn, FA_OPEN_EXISTING | FA_READ); + + // If no error occurred, read in the header. + // + if(!result) result = f_read(&File, (char *)&dirBlock->dirEnt[idx], TZSVC_CMPHDR_SIZE, &readSize); + + // No errors, read the header. + if(!result && readSize == TZSVC_CMPHDR_SIZE) + { + // Close the file, no longer needed. + f_close(&File); + + // Check to see if the file matches any given wildcard. + // + if(matchFileWithWildcard((char *)&svcControl.wildcard, (char *)&dirBlock->dirEnt[idx].fileName, 0, 0)) + { + // Valid so find next entry. + idx++; + } else + { + // Scrub the entry, not valid. + memset((char *)&dirBlock->dirEnt[idx], 0x00, TZSVC_CMPHDR_SIZE); + } + } + } + } + + // Increment the virtual directory sector number as the Z80 expects a sector of directory entries per call. + if(!result) + dirSector++; + } + // Close the currently open directory. + else if(mode == TZSVC_CLOSE) + { + if(dirOpen) + f_closedir(&dirFp); + dirOpen = 0; + } + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + + +// A method to find a file either using the Sharp MZ80A name or a number assigned to a directory listing. +// It is a bit long winded as each file that matches the filename specification has to be opened and the MZF header filename +// has to be checked. Cacheing would help here but wasteful in resources for number of times it would be called. +// +uint8_t svcFindFile(char *file, char *searchFile, uint32_t searchNo) +{ + // Locals + uint8_t fileNo = 0; + uint8_t found = 0; + unsigned int readSize; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + FIL File; + FILINFO fno; + DIR dirFp; + FRESULT result = FR_OK; + t_svcCmpDirEnt dirEnt; + + // Setup the defaults + // + svcSetDefaults(); + + // Open the directory. + result = f_opendir(&dirFp, (char *)&svcControl.directory); + if(result == FR_OK) + { + fileNo = 0; + + do { + // Read an SD directory entry then open the returned SD file so that we can to read out the important MZF data for name matching. + // + result = f_readdir(&dirFp, &fno); + + // If an error occurs or we are at the end of the directory listing close the sector and pass back. + if(result != FR_OK || fno.fname[0] == 0) break; + + // Check to see if this is a valid MZF file. + const char *ext = strrchr(fno.fname, '.'); + if(!ext || strcasecmp(++ext, TZSVC_DEFAULT_EXT) != 0) + continue; + + // Build filename. + // + sprintf(fqfn, "0:\\%s\\%s", svcControl.directory, fno.fname); + + // Open the file so we can read out the MZF header which is the information TZFS/CPM needs. + // + result = f_open(&File, fqfn, FA_OPEN_EXISTING | FA_READ); + + // If no error occurred, read in the header. + // + if(!result) result = f_read(&File, (char *)&dirEnt, TZSVC_CMPHDR_SIZE, &readSize); + + // No errors, read the header. + if(!result && readSize == TZSVC_CMPHDR_SIZE) + { + // Close the file, no longer needed. + f_close(&File); + + // Check to see if the file matches any given wildcard. If we dont have a match loop to next directory entry. + // + if(matchFileWithWildcard((char *)&svcControl.wildcard, (char *)&dirEnt.fileName, 0, 0)) + { + // If a filename has been given, see if this file matches it. + if(searchFile != NULL) + { + // Check to see if the file matches the name given with wildcard expansion if needed. + // + if(matchFileWithWildcard(searchFile, (char *)&dirEnt.fileName, 0, 0)) + { + found = 2; + } + } + + // If we are searching on file number and the latest directory entry retrieval matches, exit and return the filename. + if(searchNo != 0xFFFFFFFF && fileNo == (uint8_t)searchNo) + { + found = 1; + } else + { + fileNo++; + } + } + } + } while(!result && !found); + + // If the file was found, copy the FQFN into its buffer. + // + if(found) + { + strcpy(file, fqfn); + } + } + + // Return 1 if file was found, 0 for all other cases. + // + return(result == FR_OK ? (found == 0 ? 0 : 1) : 0); +} + +// Method to read the current directory from the cache. If the cache is invalid resort to using the standard direct method. +// +uint8_t svcReadDirCache(uint8_t mode) +{ + // Locals - dont use global as this is a seperate thread. + // + static uint8_t dirOpen = 0; // Seperate flag as their is no public way to validate that dirFp is open and valid, the method in FatFS is private for this functionality. + static uint8_t dirSector = 0; // Virtual directory sector. + static uint8_t dirEntry = 0; // Last cache entry number processed. + FRESULT result = FR_OK; + t_svcCmpDirBlock *dirBlock = (t_svcCmpDirBlock *)&svcControl.sector; + + // Setup the defaults + // + svcSetDefaults(); + + // If there is no cache revert to direct directory read. + // + if(!osControl.dirMap.valid) + { + result = svcReadDir(mode); + } else + { + // Request to open? No need with cache, just return next block. + if(mode == TZSVC_OPEN) + { + // Reentrant call to actually read the data. + // + dirOpen = 1; + dirSector = 0; + dirEntry = 0; + result = (FRESULT)svcReadDirCache(TZSVC_NEXT); + } + + // Read a block of directory entries into the z80 service buffer sector. + else if(mode == TZSVC_NEXT && dirOpen == 1) + { + // If the Z80 is requesting a non sequential directory sector then calculate the new cache entry position. + // + if(dirSector != svcControl.dirSector) + { + dirEntry = svcControl.dirSector * TZVC_MAX_CMPCT_DIRENT_BLOCK; + dirSector = svcControl.dirSector; + if(dirEntry > osControl.dirMap.entries) + { + dirEntry = osControl.dirMap.entries; + dirSector = osControl.dirMap.entries / TZVC_MAX_CMPCT_DIRENT_BLOCK; + } + } + + // Zero the directory entry block - unused entries will then appear as NULLS. + memset(dirBlock, 0x00, TZSVC_SECTOR_SIZE); + + // Loop the required number of times to fill a sector full of entries. + // + uint8_t idx=0; + while(idx < TZVC_MAX_CMPCT_DIRENT_BLOCK && dirEntry < osControl.dirMap.entries && result == FR_OK) + { + // Check to see if the file matches any given wildcard. + // + if(matchFileWithWildcard((char *)&svcControl.wildcard, (char *)&osControl.dirMap.file[dirEntry]->mzfHeader.fileName, 0, 0)) + { + // Valid so store and find next entry. + // + memcpy((char *)&dirBlock->dirEnt[idx], (char *)&osControl.dirMap.file[dirEntry]->mzfHeader, TZSVC_CMPHDR_SIZE); + idx++; + } else + { + // Scrub the entry, not valid. + memset((char *)&dirBlock->dirEnt[idx], 0x00, TZSVC_CMPHDR_SIZE); + } + dirEntry++; + } + + // Increment the virtual directory sector number as the Z80 expects a sector of directory entries per call. + if(!result) + dirSector++; + } + // Close the currently open directory. + else if(mode == TZSVC_CLOSE) + { + dirOpen = 0; + } + } + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + +// A method to find a file using the cached directory. If the cache is not available (ie. no memory) use the standard method. +// +uint8_t svcFindFileCache(char *file, char *searchFile, uint32_t searchNo) +{ + // Locals + uint8_t fileNo = 0; + uint8_t found = 0; + uint8_t idx = 0; + FRESULT result = FR_OK; + + // If there is no cache revert to direct search. + // + if(!osControl.dirMap.valid) + { + result = svcFindFile(file, searchFile, searchNo); + } else + { + // If we are searching on file number and there is no filter in place, see if it is valid and exit with data. + if(searchNo != 0xFFFFFFFF && strcmp((char *)svcControl.wildcard, TZSVC_DEFAULT_WILDCARD) == 0) + { + if(searchNo < osControl.dirMap.entries && osControl.dirMap.file[searchNo]) + { + found = 1; + idx = searchNo; + } else + { + result = FR_NO_FILE; + } + } else + { + do { + // Check to see if the file matches any given wildcard. If we dont have a match loop to next directory entry. + // + if(matchFileWithWildcard((char *)&svcControl.wildcard, (char *)&osControl.dirMap.file[idx]->mzfHeader.fileName, 0, 0)) + { + // If a filename has been given, see if this file matches it. + if(searchFile != NULL) + { + // Check to see if the file matches the name given with wildcard expansion if needed. + // + if(matchFileWithWildcard(searchFile, (char *)&osControl.dirMap.file[idx]->mzfHeader.fileName, 0, 0)) + { + found = 2; + } + } + + // If we are searching on file number then see if it matches (after filter has been applied above). + if(searchNo != 0xFFFFFFFF && fileNo == (uint8_t)searchNo) + { + found = 1; + } else + { + fileNo++; + } + } + if(!found) + { + idx++; + } + } while(!result && !found && idx < osControl.dirMap.entries); + } + + // If the file was found, copy the FQFN into its buffer. + // + if(found) + { + // Build filename. + // + sprintf(file, "0:\\%s\\%s", osControl.dirMap.directory, osControl.dirMap.file[idx]->sdFileName); + } + } + + // Return 1 if file was found, 0 for all other cases. + // + return(result == FR_OK ? (found == 0 ? 0 : 1) : 0); +} + +// Method to build up a cache of all the files on the SD card in a given directory along with the Sharp MZ80A header to which they map. +// This involves scanning each file to extract the MZF header and creating a map. +// +uint8_t svcCacheDir(const char *directory, uint8_t force) +{ + // Locals + uint8_t fileNo = 0; + unsigned int readSize; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + FIL File; + FILINFO fno; + DIR dirFp; + FRESULT result = FR_OK; + t_svcCmpDirEnt dirEnt; + + // No need to cache directory if we have already cached it. + if(force == 0 && osControl.dirMap.valid && strcasecmp(directory, osControl.dirMap.directory) == 0) + return(1); + + // Invalidate the map and free existing memory incase of errors. + // + osControl.dirMap.valid = 0; + for(uint8_t idx=0; idx < osControl.dirMap.entries; idx++) + { + if(osControl.dirMap.file[idx]) + { + free(osControl.dirMap.file[idx]->sdFileName); + free(osControl.dirMap.file[idx]); + osControl.dirMap.file[idx] = 0; + } + } + osControl.dirMap.entries = 0; +printf("Opendir\n"); + // Open the directory and extract all files. + result = f_opendir(&dirFp, directory); + if(result == FR_OK) + { +printf("Dir Open\n"); + fileNo = 0; + + do { + // Read an SD directory entry then open the returned SD file so that we can to read out the important MZF data for name matching. + // + result = f_readdir(&dirFp, &fno); +printf("Read entry\n"); + // If an error occurs or we are at the end of the directory listing close the sector and pass back. + if(result != FR_OK || fno.fname[0] == 0) break; + +printf("Read entry:%s\n", fno.fname); + // Check to see if this is a valid MZF file. + const char *ext = strrchr(fno.fname, '.'); + if(!ext || strcasecmp(++ext, TZSVC_DEFAULT_EXT) != 0) + continue; +printf("After continue\n"); + + // Build filename. + // + sprintf(fqfn, "0:\\%s\\%s", directory, fno.fname); +printf("After fqfn\n"); + + // Open the file so we can read out the MZF header which is the information TZFS/CPM needs. + // + result = f_open(&File, fqfn, FA_OPEN_EXISTING | FA_READ); +printf("After open\n"); + + // If no error occurred, read in the header. + // + if(!result) result = f_read(&File, (char *)&dirEnt, TZSVC_CMPHDR_SIZE, &readSize); +printf("After read\n"); + + // No errors, read the header. + if(!result && readSize == TZSVC_CMPHDR_SIZE) + { +printf("In alloc\n"); + // Close the file, no longer needed. + f_close(&File); + + // Cache this entry. The SD filename is dynamically allocated as it's size can be upto 255 characters for LFN names. The Sharp name is + // fixed at 17 characters as you cant reliably rely on terminators and the additional data makes it a constant 32 chars long. + osControl.dirMap.file[fileNo] = (t_sharpToSDMap *)malloc(sizeof(t_sharpToSDMap)); + osControl.dirMap.file[fileNo]->sdFileName = (uint8_t *)malloc(strlen(fno.fname)+1); +printf("Mem alloc:%08lx, %08lx\n", osControl.dirMap.file[fileNo], osControl.dirMap.file[fileNo]); + if(osControl.dirMap.file[fileNo] == NULL || osControl.dirMap.file[fileNo]->sdFileName == NULL) + { + printf("Out of memory cacheing directory:%s\n", directory); + for(uint8_t idx=0; idx <= fileNo; idx++) + { + if(osControl.dirMap.file[idx]) + { + free(osControl.dirMap.file[idx]->sdFileName); + free(osControl.dirMap.file[idx]); + osControl.dirMap.file[idx] = 0; + } + } + result = FR_NOT_ENOUGH_CORE; + } else + { + // Copy in details into this maps node. + strcpy((char *)osControl.dirMap.file[fileNo]->sdFileName, fno.fname); + memcpy((char *)&osControl.dirMap.file[fileNo]->mzfHeader, (char *)&dirEnt, TZSVC_CMPHDR_SIZE); + fileNo++; + } + } + } while(!result && fileNo < TZSVC_MAX_DIR_ENTRIES); + } + + // Success? + if(result == FR_OK && (fno.fname[0] == 0 || fileNo == TZSVC_MAX_DIR_ENTRIES)) + { + // Validate the cache. + osControl.dirMap.valid = 1; + osControl.dirMap.entries = fileNo; + strcpy(osControl.dirMap.directory, directory); + } + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + +// Method to open a file for reading and return requested sectors. +// +uint8_t svcReadFile(uint8_t mode) +{ + // Locals - dont use global as this is a seperate thread. + // + static FIL File; + static uint8_t fileOpen = 0; // Seperate flag as their is no public way to validate that File is open and valid, the method in FatFS is private for this functionality. + static uint8_t fileSector = 0; // Sector number being read. + FRESULT result = FR_OK; + unsigned int readSize; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + + // Find the required file. + // Request to open? Validate that we dont already have an open file then find and open the file. + if(mode == TZSVC_OPEN) + { + // Close if previously open. + if(fileOpen == 1) + svcReadFile(TZSVC_CLOSE); + + // Setup the defaults + // + svcSetDefaults(); + + // Find the file using the given file number or file name. + // + if(svcFindFileCache(fqfn, (char *)&svcControl.filename, svcControl.fileNo)) + { + // Open the file, fqfn has the FQFN of the correct file on the SD drive. + result = f_open(&File, fqfn, FA_OPEN_EXISTING | FA_READ); + + if(result == FR_OK) + { + // Reentrant call to actually read the data. + // + fileOpen = 1; + fileSector = 0; + result = (FRESULT)svcReadFile(TZSVC_NEXT); + } + } + } + + // Read the next sector from the file. + else if(mode == TZSVC_NEXT && fileOpen == 1) + { + // If the Z80 is requesting a non sequential sector then seek to the correct location prior to the read. + // + if(fileSector != svcControl.fileSector) + { + result = f_lseek(&File, (svcControl.fileSector * TZSVC_SECTOR_SIZE)); + fileSector = svcControl.fileSector; + } + + // Proceed if no errors have occurred. + // + if(!result) + { + // Read the required sector. + result = f_read(&File, (char *)&svcControl.sector, TZSVC_SECTOR_SIZE, &readSize); + } + + // Move onto next sector. + fileSector++; + } + + // Close the currently open file. + else if(mode == TZSVC_CLOSE) + { + if(fileOpen) + f_close(&File); + fileOpen = 0; + } + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + +// Method to load a file from SD directly into the tranZPUter memory. +// +uint8_t svcLoadFile(void) +{ + // Locals - dont use global as this is a seperate thread. + // + FRESULT result = FR_OK; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + + // Setup the defaults + // + svcSetDefaults(); + + // Find the file using the given file number or file name. + // + if(svcFindFileCache(fqfn, (char *)&svcControl.filename, svcControl.fileNo)) + { + // Call method to load an MZF file. + result = loadMZFZ80Memory(fqfn, 0xFFFFFFFF, 0, 1); + } else + { + result = FR_NO_FILE; + } + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + +// Method to save a file from tranZPUter memory directly into a file on the SD card. +// +uint8_t svcSaveFile(void) +{ + // Locals - dont use global as this is a seperate thread. + // + FRESULT result = FR_OK; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + char asciiFileName[MZF_FILENAME_LEN+1]; + t_svcDirEnt mzfHeader; + + // Setup the defaults + // + svcSetDefaults(); + + // Get the MZF header which contains the details of the file to save. + copyFromZ80((uint8_t *)&mzfHeader, MZ_CMT_ADDR, MZF_HEADER_SIZE, 0); + + // Need to extract and convert the filename to create a file. + // + convertSharpFilenameToAscii(asciiFileName, (char *)mzfHeader.fileName, MZF_FILENAME_LEN); + + // Build filename. + // + sprintf(fqfn, "0:\\%s\\%s.%s", svcControl.directory, asciiFileName, TZSVC_DEFAULT_EXT); + + // Call the main method to save memory passing in the correct MZF details and header. + result = saveZ80Memory(fqfn, (mzfHeader.loadAddr < MZ_CMT_DEFAULT_LOAD_ADDR-3 ? MZ_CMT_DEFAULT_LOAD_ADDR : mzfHeader.loadAddr), mzfHeader.fileSize, &mzfHeader, 0); + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + + +// Method to erase a file on the SD card. +// +uint8_t svcEraseFile(void) +{ + // Locals - dont use global as this is a seperate thread. + // + FRESULT result = FR_OK; + char fqfn[FF_SFN_BUF + 13]; // 0:\12345678\ + + // Setup the defaults + // + svcSetDefaults(); + + // Find the file using the given file number or file name. + // + if(svcFindFileCache(fqfn, (char *)&svcControl.filename, svcControl.fileNo)) + { + // Call method to load an MZF file. + result = f_unlink(fqfn); + } else + { + result = FR_NO_FILE; + } + + // Return values: 0 - Success : maps to TZSVC_STATUS_OK + // 1 - Fail : maps to TZSVC_STATUS_FILE_ERROR + return(result == FR_OK ? TZSVC_STATUS_OK : TZSVC_STATUS_FILE_ERROR); +} + + +// Method to process a service request from the z80 running TZFS or CPM. +// +void processServiceRequest(void) +{ + // Locals. + // + uint8_t refreshCacheDir = 0; + uint8_t status = 0; + uint32_t copySize = TZSVC_CMD_STRUCT_SIZE; + + // Get the command and associated parameters. + copyFromZ80((uint8_t *)&svcControl, TZSVC_CMD_STRUCT_ADDR, TZSVC_CMD_SIZE, 0); + + // Set status to processing. Z80 can use this to decide if the K64F received its request after a given period of time. + setZ80SvcStatus(TZSVC_STATUS_PROCESSING); + + // Action according to command given. + // + switch(svcControl.cmd) + { + // Open a directory stream and return the first block. + case TZSVC_CMD_READDIR: + status=svcReadDirCache(TZSVC_OPEN); + break; + + // Read the next block in the directory stream. + case TZSVC_CMD_NEXTDIR: + status=svcReadDirCache(TZSVC_NEXT); + break; + + // Open a file stream and return the first block. + case TZSVC_CMD_READFILE: + status=svcReadDir(TZSVC_OPEN); + break; + + // Read the next block in the file stream. + case TZSVC_CMD_MEXTREADFILE: + status=svcReadFile(TZSVC_NEXT); + break; + + // Create or write a file stream and save the passed block of data into it. + case TZSVC_CMD_WRITEFILE: + case TZSVC_CMD_NEXTWRITEFILE: + // Need to get the remainder of the data for the write operation. + copyFromZ80((uint8_t *)&svcControl.sector, TZSVC_CMD_STRUCT_ADDR, TZSVC_SECTOR_SIZE, 0); + + // No need to copy the full record back to the Z80 for a write operation, just the command section. + // + copySize = TZSVC_CMD_SIZE; + break; + + // Close an open dir/file. + case TZSVC_CMD_CLOSE: + svcReadDir(TZSVC_CLOSE); + svcReadFile(TZSVC_CLOSE); + + // No need to copy the full record back to the Z80 for a close operation, just the command section. + // + copySize = TZSVC_CMD_SIZE; + break; + + // Load a file directly into target memory. + case TZSVC_CMD_LOADFILE: + status=svcLoadFile(); + break; + + // Save a file directly from target memory. + case TZSVC_CMD_SAVEFILE: + status=svcSaveFile(); + refreshCacheDir = 1; + break; + + // Erase a file from the SD Card. + case TZSVC_CMD_ERASEFILE: + status=svcEraseFile(); + break; + + // Change active directory. Do this immediately to validate the directory name given. + case TZSVC_CMD_CHANGEDIR: + status=svcCacheDir((const char *)svcControl.directory, 0); + break; + + default: + break; + } + + // Update the status in the service control record then copy it across to the Z80. + // + svcControl.result = status; + copyToZ80(TZSVC_CMD_STRUCT_ADDR, (uint8_t *)&svcControl, copySize, 0); + + // Return status. + setZ80SvcStatus(status); + + // Need to refresh the directory? Do this at the end of the routine so the Sharp MZ80A isnt held up. + if(refreshCacheDir) + svcCacheDir((const char *)svcControl.directory, 1); + + return; +} + +// Method to test if the autoboot TZFS flag file exists on the SD card. If the file exists, set the autoboot flag. +// +uint8_t testTZFSAutoBoot(void) +{ + // Locals. + uint8_t result = 0; + FIL File; + + // Detect if the autoboot tranZPUter TZFS flag is set. This is a file called TZFSBOOT in the SD root directory. + if(f_open(&File, "TZFSBOOT.FLG", FA_OPEN_EXISTING | FA_READ) == FR_OK) + { + result = 1; + f_close(&File); + } + + return(result); +} + +// Method to configure the hardware and events to operate the tranZPUter SW upgrade. +// +void setupTranZPUter(void) +{ + // Setup the pins to default mode and configure IRQ's. + setupZ80Pins(0, &systick_millis_count); + + // Check to see if autoboot is needed. + osControl.tzAutoBoot = testTZFSAutoBoot(); + + // Ensure the machine is ready by performing a RESET. + resetZ80(); +} + +////////////////////////////////////////////////////////////// +// End of tranZPUter i/f methods for zOS // +////////////////////////////////////////////////////////////// +#endif // Protected methods which reside in the kernel. + + +#if defined __APP__ +// Dummy function to override a weak definition in the Teensy library. Currently the yield functionality is not +// needed within apps running on the K64F it is only applicable in the main OS. +// +void yield(void) +{ + return; +} +#endif // __APP__ + +#if defined __APP__ && defined __TZPU_DEBUG__ +// Simple method to output the Z80 signals to the console - feel good factor. To be of real use the signals need to be captured against the system +// clock and the replayed, perhaps a todo! +// +void displaySignals(void) +{ + uint32_t ADDR = 0; + uint8_t DATA = 0; + uint8_t RD = 0; + uint8_t WR = 0; + uint8_t IORQ = 0; + uint8_t MREQ = 0; + uint8_t NMI = 0; + uint8_t INT = 0; + uint8_t M1 = 0; + uint8_t RFSH = 0; + uint8_t WAIT = 0; + uint8_t BUSRQ = 0; + uint8_t BUSACK = 0; + uint8_t ZBUSACK = 0; + uint8_t MBCLK = 0; + uint8_t HALT = 0; + uint8_t CLKSLCT = 0; + uint8_t latch = 0; + + setupZ80Pins(0, NULL); + + printf("Z80 Bus Signals:\r\n"); + while(1) + { + ADDR = (pinGet(Z80_A18) & 0x1) << 18; + ADDR |= (pinGet(Z80_A17) & 0x1) << 17; + ADDR |= (pinGet(Z80_A16) & 0x1) << 16; + ADDR |= (pinGet(Z80_A15) & 0x1) << 15; + ADDR |= (pinGet(Z80_A14) & 0x1) << 14; + ADDR |= (pinGet(Z80_A13) & 0x1) << 13; + ADDR |= (pinGet(Z80_A12) & 0x1) << 12; + ADDR |= (pinGet(Z80_A11) & 0x1) << 11; + ADDR |= (pinGet(Z80_A10) & 0x1) << 10; + ADDR |= (pinGet(Z80_A9) & 0x1) << 9; + ADDR |= (pinGet(Z80_A8) & 0x1) << 8; + ADDR |= (pinGet(Z80_A7) & 0x1) << 7; + ADDR |= (pinGet(Z80_A6) & 0x1) << 6; + ADDR |= (pinGet(Z80_A5) & 0x1) << 5; + ADDR |= (pinGet(Z80_A4) & 0x1) << 4; + ADDR |= (pinGet(Z80_A3) & 0x1) << 3; + ADDR |= (pinGet(Z80_A2) & 0x1) << 2; + ADDR |= (pinGet(Z80_A1) & 0x1) << 1; + ADDR |= (pinGet(Z80_A0) & 0x1); + DATA = (pinGet(Z80_D7) & 0x1) << 7; + DATA |= (pinGet(Z80_D6) & 0x1) << 6; + DATA |= (pinGet(Z80_D5) & 0x1) << 5; + DATA |= (pinGet(Z80_D4) & 0x1) << 4; + DATA |= (pinGet(Z80_D3) & 0x1) << 3; + DATA |= (pinGet(Z80_D2) & 0x1) << 2; + DATA |= (pinGet(Z80_A1) & 0x1) << 1; + DATA |= (pinGet(Z80_D0) & 0x1); + RD=pinGet(Z80_RD); + WR=pinGet(Z80_WR); + MREQ=pinGet(Z80_MREQ); + IORQ=pinGet(Z80_IORQ); + NMI=pinGet(Z80_NMI); + INT=pinGet(Z80_INT); + M1=pinGet(CTL_M1); + RFSH=pinGet(CTL_RFSH); + WAIT=pinGet(Z80_WAIT); + BUSRQ=pinGet(CTL_BUSRQ); + BUSACK=pinGet(CTL_BUSACK); + ZBUSACK=pinGet(Z80_BUSACK); + MBCLK=pinGet(MB_SYSCLK); + HALT=pinGet(CTL_HALT); + CLKSLCT=pinGet(CTL_CLKSLCT); + latch=readCtrlLatch(); + + printf("\rADDR=%06lx %08x %02x %3s %3s %3s %3s %3s %3s %2s %4s %4s %2s %2s %3s %3s %4s %4s", ADDR, DATA, latch, + (RD == 0 && MREQ == 0 && WR == 1 && IORQ == 1) ? "MRD" : " ", + (RD == 0 && IORQ == 0 && WR == 1 && MREQ == 1) ? "IRD" : " ", + (WR == 0 && MREQ == 0 && RD == 1 && IORQ == 1) ? "MWR" : " ", + (WR == 0 && IORQ == 0 && RD == 1 && MREQ == 1) ? "IWR" : " ", + (NMI == 0) ? "NMI" : " ", + (INT == 0) ? "INT" : " ", + (M1 == 0) ? "M1" : " ", + (RFSH == 0) ? "RFSH" : " ", + (WAIT == 0) ? "WAIT" : " ", + (BUSRQ == 0) ? "BR" : " ", + (BUSACK == 0) ? "BA" : " ", + (ZBUSACK == 0) ? "ZBA" : " ", + (MBCLK == 1) ? "CLK" : " ", + (HALT == 0) ? "HALT" : " ", + (CLKSLCT == 0) ? "CLKS" : " " + ); + } + + return; +} +#endif // __TZPU_DEBUG__ + +#ifdef __cplusplus +} + #endif diff --git a/include/tools.h b/include/tools.h index ff46836..8856d0c 100644 --- a/include/tools.h +++ b/include/tools.h @@ -103,6 +103,11 @@ extern "C" { #define CMD_APP_MBASIC 141 // Mini Basic #define CMD_APP_KILO 142 // Kilo Editor #define CMD_APP_ED 143 // Ed Editor +#define CMD_TZ_ZPU 150 // tranZPUter interface/test. +#define CMD_TZ_LOAD 151 // tranZPUter memory load/save tool. +#define CMD_TZ_DUMP 152 // tranZPUter memory dump tool. +#define CMD_TZ_CLEAR 153 // tranZPUter memory clear tool. +#define CMD_TZ_RESET 154 // tranZPUter memory reset tool. #define CMD_BADKEY -1 #define CMD_NOKEY 0 #define CMD_GROUP_DISK 1 @@ -114,6 +119,7 @@ extern "C" { #define CMD_GROUP_EXEC 7 #define CMD_GROUP_MISC 8 #define CMD_GROUP_APP 9 +#define CMD_GROUP_TZ 10 #define CMD_GROUP_DISK_NAME "DISK IO CONTROLS" #define CMD_GROUP_BUFFER_NAME "DISK BUFFER CONTROLS" #define CMD_GROUP_FS_NAME "FILESYSTEM CONTROLS" @@ -123,6 +129,7 @@ extern "C" { #define CMD_GROUP_EXEC_NAME "EXECUTION" #define CMD_GROUP_MISC_NAME "MISC COMMANDS" #define CMD_GROUP_APP_NAME "APPLICATIONS" +#define CMD_GROUP_TZ_NAME "TRANZPUTER" // File Execution modes. // @@ -377,6 +384,13 @@ static t_cmdstruct cmdTable[] = { { "mbasic", BUILTIN_DEFAULT, CMD_APP_MBASIC, CMD_GROUP_APP }, { "kilo", BUILTIN_DEFAULT, CMD_APP_KILO, CMD_GROUP_APP }, { "ed", BUILTIN_DEFAULT, CMD_APP_ED, CMD_GROUP_APP }, + #if defined __TRANZPUTER__ + { "tzpu", BUILTIN_DEFAULT, CMD_TZ_TZPU, CMD_GROUP_TZ }, + { "tzload", BUILTIN_DEFAULT, CMD_TZ_LOAD, CMD_GROUP_TZ }, + { "tzdump", BUILTIN_DEFAULT, CMD_TZ_DUMP, CMD_GROUP_TZ }, + { "tzclear", BUILTIN_DEFAULT, CMD_TZ_CLEAR, CMD_GROUP_TZ }, + { "tzreset", BUILTIN_DEFAULT, CMD_TZ_RESET, CMD_GROUP_TZ }, + #endif }; #endif @@ -477,6 +491,14 @@ static t_helpstruct helpTable[] = { { CMD_APP_MBASIC, "[", "Mini Basic" }, { CMD_APP_KILO, "", "VT100 editor" }, { CMD_APP_ED, "", "Minimal VT100 editor" }, + #if defined __TRANZPUTER__ + // TranZPUter commands. + { CMD_TZ_TZPU, "--help", "Testing tool" }, + { CMD_TZ_LOAD, "--help", "Memory load/save tool" }, + { CMD_TZ_DUMP, "--help", "Memory dump tool" }, + { CMD_TZ_CLEAR, "--help", "Memory clearing tool" }, + { CMD_TZ_RESET, "--help", "Remote reset tool" }, + #endif }; #endif #define NGRPKEYS (sizeof(groupTable)/sizeof(t_groupstruct)) diff --git a/include/tranzputer.h b/include/tranzputer.h new file mode 100755 index 0000000..8facdd7 --- /dev/null +++ b/include/tranzputer.h @@ -0,0 +1,571 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Name: tranzputer.h +// Created: May 2020 +// Author(s): Philip Smart +// Description: The TranZPUter library. +// This file contains methods which allow applications to access and control the traZPUter board and the underlying Sharp MZ80A host. +// Credits: +// Copyright: (c) 2019-2020 Philip Smart +// +// History: May 2020 - Initial write of the TranZPUter 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 . +///////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifndef TRANZPUTER_H +#define TRANZPUTER_H + +#ifdef __cplusplus + extern "C" { +#endif + +// Configurable constants. +// +#define REFRESH_BYTE_COUNT 8 // This constant controls the number of bytes read/written to the z80 bus before a refresh cycle is needed. +#define RFSH_BYTE_CNT 256 // Number of bytes we can write before needing a full refresh for the DRAM. + +// tranZPUter Memory Modes - select one of the 32 possible memory models using these constants. +// +#define TZMM_ORIG 0x00 // Original Sharp MZ80A mode, no tranZPUter features are selected except the I/O control registers (default: 0x60-063). +#define TZMM_BOOT 0x01 // Original mode but E800-EFFF is mapped to tranZPUter RAM so TZFS can be booted. +#define TZMM_TZFS 0x02 // TZFS main memory configuration. all memory is in tranZPUter RAM, E800-FFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected. +#define TZMM_TZFS2 0x03 // TZFS main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected, F000-FFFF is in 64K Block 1. +#define TZMM_TZFS3 0x04 // TZFS main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected, F000-FFFF is in 64K Block 2. +#define TZMM_TZFS4 0x05 // TZFS main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used by TZFS, SA1510 is at 0000-1000 and RAM is 1000-CFFF, 64K Block 0 selected, F000-FFFF is in 64K Block 3. +#define TZMM_CPM 0x14 // CPM main memory configuration. all memory is in tranZPUter RAM, E800-EFFF is used as the static CBIOS and F000-FFFF is the paged CBIOS, TPA is from 0000-D000(BDOS+CCP = 1800), all is in 64K Block 4, F000-FFFF is in 64K Block 4. +#define TZMM_CPM2 0x15 // CPM main memory configuration. E800-EFFF and TPA 0000-D000 are in tranZPUter RAM 64K block 4, CBIOS2 F000-FFFF is in 64K block 5 and video and memory control D000-E7FF are on the mainboard. +#define TZMM_CPM3 0x16 // CPM main memory configuration. E800-EFFF and TPA 0000-D000 are in tranZPUter RAM 64K block 4, CBIOS2 F000-FFFF is in 64K block 6 and video and memory control D000-E7FF are on the mainboard. +#define TZMM_CPM4 0x17 // CPM main memory configuration. E800-EFFF and TPA 0000-D000 are in tranZPUter RAM 64K block 4, CBIOS2 F000-FFFF is in 64K block 7 and video and memory control D000-E7FF are on the mainboard. +#define TZMM_TZPU0 0x18 // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 0 is selected. +#define TZMM_TZPU1 0x19 // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 1 is selected. +#define TZMM_TZPU2 0x1A // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 2 is selected. +#define TZMM_TZPU3 0x1B // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 3 is selected. +#define TZMM_TZPU4 0x1C // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 4 is selected. +#define TZMM_TZPU5 0x1D // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 5 is selected. +#define TZMM_TZPU6 0x1E // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 6 is selected. +#define TZMM_TZPU7 0x1F // Everything is in tranZPUter domain, no access to underlying Sharp mainboard unless memory management mode is switched. tranZPUter RAM 64K block 7 is selected. + +// IO addresses on the tranZPUter or mainboard. +// +#define IO_TZ_CTRLLATCH 0x60 // Control latch which specifies the Memory Model/mode. +#define IO_TZ_SETXMHZ 0x62 // Switch to alternate CPU frequency provided by K64F. +#define IO_TZ_SET2MHZ 0x64 // Switch to system CPU frequency. +#define IO_TZ_CLKSELRD 0x66 // Read the status of the clock select, ie. which clock is connected to the CPU. +#define IO_TZ_SVCREQ 0x68 // Service request from the Z80 to be provided by the K64F. + +// Sharp MZ80A constants. +// +#define MZ_MROM_ADDR 0x0000 // Monitor ROM start address. +#define MZ_MROM_STACK_ADDR 0x1000 // Monitor ROM start stack address. +#define MZ_MROM_STACK_SIZE 0x0200 // Monitor ROM stack size. +#define MZ_UROM_ADDR 0xE800 // User ROM start address. +#define MZ_BANKRAM_ADDR 0xF000 // Floppy API address which is used in TZFS as the paged RAM for additional functionality. +#define MZ_CMT_ADDR 0x10F0 // Address of the CMT (tape) header record. +#define MZ_CMT_DEFAULT_LOAD_ADDR 0x1200 // The default load address for a CMT, anything below this is normally illegal. +#define MZ_VID_RAM_ADDR 0xD000 // Start of Video RAM +#define MZ_VID_RAM_SIZE 2048 // Size of Video RAM. +#define MZ_VID_DFLT_BYTE 0x00 // Default character (SPACE) for video RAM. +#define MZ_ATTR_RAM_ADDR 0xD800 // On machines with the upgrade, the start of the Attribute RAM. +#define MZ_ATTR_RAM_SIZE 2048 // Size of the attribute RAM. +#define MZ_ATTR_DFLT_BYTE 0x07 // Default colour (White on Black) for the attribute. +#define MZ_SCROL_BASE 0xE200 // Base address of the hardware scroll registers. +#define MZ_SCROL_END 0xE2FF // End address of the hardware scroll registers. +#define MZ_MEMORY_SWAP 0xE00C // Address when read swaps the memory from 0000-0FFF -> C000-CFFF +#define MZ_MEMORY_RESET 0xE010 // Address when read resets the memory to the default location 0000-0FFF. +#define MZ_CRT_NORMAL 0xE014 // Address when read sets the CRT to normal display mode. +#define MZ_CRT_INVERSE 0xE018 // Address when read sets the CRT to inverted display mode. +#define MZ_ROM_SA1510_40C "SA1510.ROM" // Original 40 character Monitor ROM. +#define MZ_ROM_SA1510_80C "SA1510-8.ROM" // Original Monitor ROM patched for 80 character screen mode. +#define MZ_ROM_TZFS "TZFS.ROM" // tranZPUter Filing System ROM. + +// Service request constants. +// +#define TZSVC_CMD_STRUCT_ADDR 0xEC80 // Address of the command structure. +#define TZSVC_CMD_STRUCT_SIZE 0x280 // Size of the inter z80/K64 service command memory. +#define TZSVC_CMD_SIZE 0x04+TZSVC_DIRNAME_SIZE+\ + +TZSVC_FILENAME_SIZE+\ + TZSVC_WILDCARD_SIZE // Size of the command/result portion of the control structure. +#define TZVC_MAX_CMPCT_DIRENT_BLOCK TZSVC_SECTOR_SIZE/TZSVC_CMPHDR_SIZE // Maximum number of directory entries per sector. +#define TZSVC_MAX_DIR_ENTRIES 255 // Maximum number of files in one directory, any more than this will be ignored. +#define TZSVC_CMPHDR_SIZE 32 // Compacted header size, contains everything except the comment field, padded out to 32bytes. +#define MZF_FILLER_LEN 8 // Filler to pad a compacted header entry to a power of 2 length. +#define TZVC_MAX_DIRENT_BLOCK TZSVC_SECTOR_SIZE/MZF_HEADER_SIZE // Maximum number of directory entries per sector. +#define TZSVC_CMD_READDIR 0x01 // Service command to open a directory and return the first block of entries. +#define TZSVC_CMD_NEXTDIR 0x02 // Service command to return the next block of an open directory. +#define TZSVC_CMD_READFILE 0x03 // Service command to open a file and return the first block. +#define TZSVC_CMD_MEXTREADFILE 0x04 // Service command to return the next block of an open file. +#define TZSVC_CMD_WRITEFILE 0x05 // Service command to create a file and write the first block into it. +#define TZSVC_CMD_NEXTWRITEFILE 0x06 // Service command to write the next block into the open file. +#define TZSVC_CMD_CLOSE 0x07 // Service command to close any open file or directory. +#define TZSVC_CMD_LOADFILE 0x08 // Service command to load a file directly into tranZPUter memory. +#define TZSVC_CMD_SAVEFILE 0x09 // Service command to save a file directly from tranZPUter memory. +#define TZSVC_CMD_ERASEFILE 0x0A // Service command to erase a file on the SD card. +#define TZSVC_CMD_CHANGEDIR 0x0B // Service command to change active directory on the SD card. +#define TZSVC_DEFAULT_DIR "MZF" // Default directory where MZF files are stored. +#define TZSVC_DEFAULT_EXT "MZF" // Default file extension for MZF files. +#define TZSVC_DEFAULT_WILDCARD "*" // Default wildcard file matching. +#define TZSVC_RESULT_OFFSET 0x01 // Offset into structure of the result byte. +#define TZSVC_DIRNAME_SIZE 8 // Limit is size of FAT32 directory name. +#define TZSVC_WILDCARD_SIZE 8 // Very basic pattern matching so small size. +#define TZSVC_FILENAME_SIZE MZF_FILENAME_LEN // Length of a Sharp MZF filename. +#define TZSVC_SECTOR_SIZE 512 // SD Card sector buffer size. +#define TZSVC_STATUS_OK 0x00 // Flag to indicate the K64F processing completed successfully. +#define TZSVC_STATUS_FILE_ERROR 0x01 // Flag to indicate a file or directory error. +#define TZSVC_STATUS_REQUEST 0xFE // Flag to indicate Z80 has posted a request. +#define TZSVC_STATUS_PROCESSING 0xFF // Flag to indicate the K64F is processing a command. +#define TZSVC_OPEN 0x00 // Service request to open a directory or file. +#define TZSVC_NEXT 0x01 // Service request to return the next directory block or file block or write the next file block. +#define TZSVC_CLOSE 0x02 // Service request to close open dir/file. + + +// 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. + +// Pin Constants - Pins assigned at the hardware level to specific tasks/signals. +// +#define MAX_TRANZPUTER_PINS 50 +#define Z80_MEM0_PIN 46 +#define Z80_MEM1_PIN 47 +#define Z80_MEM2_PIN 48 +#define Z80_MEM3_PIN 49 +#define Z80_MEM4_PIN 50 +#define Z80_WR_PIN 10 +#define Z80_RD_PIN 12 +#define Z80_IORQ_PIN 8 +#define Z80_MREQ_PIN 9 +#define Z80_A0_PIN 39 +#define Z80_A1_PIN 38 +#define Z80_A2_PIN 37 +#define Z80_A3_PIN 36 +#define Z80_A4_PIN 35 +#define Z80_A5_PIN 34 +#define Z80_A6_PIN 33 +#define Z80_A7_PIN 32 +#define Z80_A8_PIN 31 +#define Z80_A9_PIN 30 +#define Z80_A10_PIN 29 +#define Z80_A11_PIN 28 +#define Z80_A12_PIN 27 +#define Z80_A13_PIN 26 +#define Z80_A14_PIN 25 +#define Z80_A15_PIN 24 +#define Z80_A16_PIN 23 +#define Z80_A17_PIN 22 +#define Z80_A18_PIN 21 +#define Z80_D0_PIN 0 +#define Z80_D1_PIN 1 +#define Z80_D2_PIN 2 +#define Z80_D3_PIN 3 +#define Z80_D4_PIN 4 +#define Z80_D5_PIN 5 +#define Z80_D6_PIN 6 +#define Z80_D7_PIN 7 +#define Z80_WAIT_PIN 13 +#define Z80_BUSACK_PIN 17 +#define Z80_NMI_PIN 43 +#define Z80_INT_PIN 44 +#define Z80_RESET_PIN 54 +#define SYSCLK_PIN 11 +#define CTL_RFSH_PIN 45 +#define CTL_HALT_PIN 18 +#define CTL_M1_PIN 20 +#define CTL_BUSRQ_PIN 15 +#define CTL_BUSACK_PIN 16 +#define CTL_CLK_PIN 14 +#define CTL_CLKSLCT_PIN 19 +#define TZ_BUSACK_PIN 55 + +// IRQ mask values for the different types of IRQ trigger. +// +#define IRQ_MASK_CHANGE 0x0B0040 +#define IRQ_MASK_RISING 0x040040 +#define IRQ_MASK_FALLING 0x0A0040 +#define IRQ_MASK_LOW 0x080040 +#define IRQ_MASK_HIGH 0x0C0040 + +// Customised pin manipulation methods implemented as stripped down macros. The original had too much additional overhead with procedure call and validation tests, +// speed is of the essence for this project as pins change mode and value constantly. +// +// Studying the Teensyduino code these macros could be stripped down further and go direct to the BITBAND registers if more speed is needed. +// +#define STR(x) #x +#define XSTR(s) STR(s) +#define pinLow(a) *portClearRegister(pinMap[a]) = 1 +#define pinHigh(a) *portSetRegister(pinMap[a]) = 1 +#define pinSet(a, b) if(b) { *portSetRegister(pinMap[a]) = 1; } else { *portClearRegister(pinMap[a]) = 1; } +#define pinGet(a) *portInputRegister(pinMap[a]) +#define pinInput(a) { *portModeRegister(pinMap[a]) = 0; *ioPin[a] = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; } +#define pinOutput(a) { *portModeRegister(pinMap[a]) = 1;\ + *ioPin[a] = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);\ + *ioPin[a] &= ~PORT_PCR_ODE; } +#define pinOutputSet(a,b) { *portModeRegister(pinMap[a]) = 1;\ + *ioPin[a] = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);\ + *ioPin[a] &= ~PORT_PCR_ODE;\ + if(b) { *portSetRegister(pinMap[a]) = 1; } else { *portClearRegister(pinMap[a]) = 1; } } +#define installIRQ(a, mask) { uint32_t cfg;\ + __disable_irq();\ + cfg = *ioPin[a];\ + cfg &= ~0x000F0000;\ + *ioPin[a] = cfg;\ + cfg |= mask;\ + *ioPin[a] = cfg;\ + __enable_irq();\ + } +#define pinIndex(a) getPinIndex(pinMap[a]) + +#define setZ80Data(a) { pinSet(Z80_D7, ((a >> 7) & 0x1)); pinSet(Z80_D6, ((a >> 6) & 0x1));\ + pinSet(Z80_D5, ((a >> 5) & 0x1)); pinSet(Z80_D4, ((a >> 4) & 0x1));\ + pinSet(Z80_D3, ((a >> 3) & 0x1)); pinSet(Z80_D2, ((a >> 2) & 0x1));\ + pinSet(Z80_D1, ((a >> 1) & 0x1)); pinSet(Z80_D0, ((a ) & 0x1)); } +#define setZ80Addr(a) { pinSet(Z80_A15, ((a >> 15) & 0x1)); pinSet(Z80_A14, ((a >> 14) & 0x1));\ + pinSet(Z80_A13, ((a >> 13) & 0x1)); pinSet(Z80_A12, ((a >> 12) & 0x1));\ + pinSet(Z80_A11, ((a >> 11) & 0x1)); pinSet(Z80_A10, ((a >> 10) & 0x1));\ + pinSet(Z80_A9, ((a >> 9) & 0x1)); pinSet(Z80_A8, ((a >> 8) & 0x1));\ + pinSet(Z80_A7, ((a >> 7) & 0x1)); pinSet(Z80_A6, ((a >> 6) & 0x1));\ + pinSet(Z80_A5, ((a >> 5) & 0x1)); pinSet(Z80_A4, ((a >> 4) & 0x1));\ + pinSet(Z80_A3, ((a >> 3) & 0x1)); pinSet(Z80_A2, ((a >> 2) & 0x1));\ + pinSet(Z80_A1, ((a >> 1) & 0x1)); pinSet(Z80_A0, ((a ) & 0x1)); } +#define setZ80RefreshAddr(a) { pinSet(Z80_A6, ((a >> 6) & 0x1)); pinSet(Z80_A5, ((a >> 5) & 0x1));\ + pinSet(Z80_A4, ((a >> 4) & 0x1)); pinSet(Z80_A3, ((a >> 3) & 0x1));\ + pinSet(Z80_A2, ((a >> 2) & 0x1)); pinSet(Z80_A1, ((a >> 1) & 0x1));\ + pinSet(Z80_A0, ((a ) & 0x1)); } +#define readZ80AddrLower() ( pinGet(Z80_A7) << 7 | pinGet(Z80_A6) << 6 | pinGet(Z80_A5) << 5 | pinGet(Z80_A4) << 4 |\ + pinGet(Z80_A3) << 3 | pinGet(Z80_A2) << 2 | pinGet(Z80_A1) << 1 | pinGet(Z80_A0) ) +#define readZ80Addr(a) ( pinGet(Z80_A15) << 15 | pinGet(Z80_A14) << 14 | pinGet(Z80_A13) << 13 | pinGet(Z80_A12) << 12 |\ + pinGet(Z80_A11) << 11 | pinGet(Z80_A10) << 10 | pinGet(Z80_A9) << 9 | pinGet(Z80_A8) << 8 |\ + pinGet(Z80_A7) << 7 | pinGet(Z80_A6) << 6 | pinGet(Z80_A5) << 5 | pinGet(Z80_A4) << 4 |\ + pinGet(Z80_A3) << 3 | pinGet(Z80_A2) << 2 | pinGet(Z80_A1) << 1 | pinGet(Z80_A0) ) +#define readDataBus() ( pinGet(Z80_D7) << 7 | pinGet(Z80_D6) << 6 | pinGet(Z80_D5) << 5 | pinGet(Z80_D4) << 4 |\ + pinGet(Z80_D3) << 3 | pinGet(Z80_D2) << 2 | pinGet(Z80_D1) << 1 | pinGet(Z80_D0) ) +#define readCtrlLatch() ( (pinGet(Z80_MEM4) << 4 | pinGet(Z80_MEM3) << 3 | pinGet(Z80_MEM2) << 2 | pinGet(Z80_MEM1) << 1 | pinGet(Z80_MEM0)) & 0x1F ) +#define writeCtrlLatch(a) { writeZ80IO(IO_TZ_CTRLLATCH, a); } +#define setZ80Direction(a) { for(uint8_t idx=Z80_D0; idx <= Z80_D7; idx++) { if(a == WRITE) { pinOutput(idx); } else { pinInput(idx); } }; z80Control.busDir = a; } +#define reqZ80BusChange(a) { if(a == MAINBOARD_ACCESS && z80Control.ctrlMode == TRANZPUTER_ACCESS) \ + {\ + pinHigh(CTL_BUSACK);\ + z80Control.ctrlMode = MAINBOARD_ACCESS;\ + z80Control.curCtrlLatch = 0b00000000;\ + writeCtrlLatch(z80Control.curCtrlLatch);\ + } else if(a == TRANZPUTER_ACCESS && z80Control.ctrlMode == MAINBOARD_ACCESS)\ + {\ + pinLow(CTL_BUSACK);\ + z80Control.ctrlMode = TRANZPUTER_ACCESS;\ + z80Control.curCtrlLatch = 0b00011111;\ + writeCtrlLatch(z80Control.curCtrlLatch);\ + } } +// Lower level macro without pin mapping as this is called in the ResetHandler to halt the Z80 whilst the K64F starts up and is able to load up tranZPUter software. +#define holdZ80() { \ + *portModeRegister(CTL_BUSRQ_PIN) = 1; \ + *portConfigRegister(CTL_BUSRQ_PIN) = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1); \ + *portConfigRegister(CTL_BUSRQ_PIN) &= ~PORT_PCR_ODE; \ + *portClearRegister(CTL_BUSRQ_PIN) = 1; \ + } + + + +// Enumeration of the various pins on the project. These enums make it easy to refer to a signal and they are mapped +// to the actual hardware pin via the pinMap array. +// One of the big advantages is that a swath of pins, such as the address lines, can be switched in a tight loop rather than +// individual pin assignments or clunky lists. +// +enum pinIdxToPinNumMap { + Z80_A0 = 0, + Z80_A1 = 1, + Z80_A2 = 2, + Z80_A3 = 3, + Z80_A4 = 4, + Z80_A5 = 5, + Z80_A6 = 6, + Z80_A7 = 7, + Z80_A8 = 8, + Z80_A9 = 9, + Z80_A10 = 10, + Z80_A11 = 11, + Z80_A12 = 12, + Z80_A13 = 13, + Z80_A14 = 14, + Z80_A15 = 15, + Z80_A16 = 16, + Z80_A17 = 17, + Z80_A18 = 18, + + Z80_D0 = 19, + Z80_D1 = 20, + Z80_D2 = 21, + Z80_D3 = 22, + Z80_D4 = 23, + Z80_D5 = 24, + Z80_D6 = 25, + Z80_D7 = 26, + + Z80_MEM0 = 27, + Z80_MEM1 = 28, + Z80_MEM2 = 29, + Z80_MEM3 = 30, + Z80_MEM4 = 31, + + Z80_IORQ = 32, + Z80_MREQ = 33, + Z80_RD = 34, + Z80_WR = 35, + Z80_WAIT = 36, + Z80_BUSACK = 37, + + Z80_NMI = 38, + Z80_INT = 39, + Z80_RESET = 40, + MB_SYSCLK = 41, + TZ_BUSACK = 42, + + CTL_BUSACK = 43, + CTL_BUSRQ = 44, + CTL_RFSH = 45, + CTL_HALT = 46, + CTL_M1 = 47, + CTL_CLK = 48, + CTL_CLKSLCT = 49 +}; + +// Possible control modes that the K64F can be in, do nothing where the Z80 runs normally, control the Z80 and mainboard, or control the Z80 and tranZPUter. +enum CTRL_MODE { + Z80_RUN = 0, + TRANZPUTER_ACCESS = 1, + MAINBOARD_ACCESS = 2 +}; + +// Possible bus directions that the K64F can setup for controlling the Z80. +enum BUS_DIRECTION { + READ = 0, + WRITE = 1, + TRISTATE = 2 +}; + +// Possible video frames stored internally. +// +enum VIDEO_FRAMES { + SAVED = 0, + WORKING = 1 +}; + +// 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; + +// Structure to define a compacted Sharp MZ80A MZF directory structure (no comment) for use in directory listings. +// 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 filler[MZF_FILLER_LEN]; // Filler to pad to a power of 2 length. +} t_svcCmpDirEnt; + +// Structure to hold the map betwen an SD filename and the Sharp file it contains. The file is an MZF format file with a 128 byte header +// and this header contains the name understood on the Sharp MZ80A. +// +typedef struct __attribute__((__packed__)) { + uint8_t *sdFileName; // Name of file on the SD card. + t_svcCmpDirEnt mzfHeader; // Compact Sharp header data of this file. +} t_sharpToSDMap; + +// Structure to hold a map of an entire directory of files on the SD card and their associated Sharp MZ0A filename. +typedef struct __attribute__((__packed__)) { + uint8_t valid; // Is this mapping valid? + uint8_t entries; // Number of entries in cache. + char directory[TZSVC_DIRNAME_SIZE]; // Directory this mapping is associated with. + t_sharpToSDMap *file[TZSVC_MAX_DIR_ENTRIES]; // File mapping of SD file to its Sharp MZ80A name. +} t_dirMap; + +// Structure to maintain all the control and management variables of the Z80 and underlying hardware so that the state of run is well known by any called method. +// +typedef struct { + #ifndef __APP__ + uint8_t refreshAddr; // Refresh address for times when the K64F must issue refresh cycles on the Z80 bus. + uint8_t disableRefresh; // Disable refresh if the mainboard DRAM isnt being used. + uint8_t runCtrlLatch; // Latch value the Z80 is running with. + uint8_t curCtrlLatch; // Latch value set during tranZPUter access of the Z80 bus. + uint8_t videoRAM[2][2048]; // Two video memory buffer frames, allows for storage of original frame in [0] and working frame in [1]. + uint8_t attributeRAM[2][2048]; // Two attribute memory buffer frames, allows for storage of original frame in [0] and working frame in [1]. + + enum CTRL_MODE ctrlMode; // Mode of control, ie normal Z80 Running, controlling mainboard, controlling tranZPUter. + enum BUS_DIRECTION busDir; // Direction the bus has been configured for. + + uint8_t resetEvent; // A Z80_RESET event occurred, probably user pressing RESET button. + uint8_t ioAddr; // Address of a Z80 IO instruction. + uint8_t ioData; // Data of a Z80 IO instruction. + uint8_t ioEvent; // Event flag to indicate that an IO instruction was captured. + uint8_t memorySwap; // A memory Swap event has occurred, 0000-0FFF -> C000-CFFF (1), or C000-CFFF -> 0000-0FFF (0) + uint8_t crtMode; // A CRT event has occurred, Normal mode (0) or Reverse Mode (1) + uint8_t scroll; // Hardware scroll offset. + volatile uint32_t portA; + volatile uint32_t portB; + volatile uint32_t portC; + volatile uint32_t portD; + volatile uint32_t portE; + #endif +} t_z80Control; + +// Structure to maintain higher level OS control and management variables typically used for TZFS and CPM. +// +typedef struct { + uint8_t tzAutoBoot; // Autoboot the tranZPUter into TZFS mode. + t_dirMap dirMap; // Directory map of SD filenames to Sharp MZ80A filenames. +} t_osControl; + +// Structure to contain inter CPU communications memory for command service processing and results. +// Typically the z80 places a command into the structure in it's memory space and asserts an I/O request, +// the K64F detects the request and reads the lower portion of the struct from z80 memory space, +// determines the command and then either reads the remainder or writes to the remainder. This struct +// exists in both the z80 and K64F domains and data is sync'd between them as needed. +// +typedef struct __attribute__((__packed__)) { + uint8_t cmd; // Command request. + uint8_t result; // Result code. 0xFE - set by Z80, command available, 0xFE - set by K64F, command ack and processing. 0x00-0xF0 = cmd complete and result of processing. + union { + uint8_t dirSector; // Virtual directory sector number. + uint8_t fileSector; // Sector within open file to read/write. + }; + // uint16_t sectorNo; // Sector number of file to retrieve. + uint8_t fileNo; // File number of a file within the last directory listing to open/update. + uint8_t directory[TZSVC_DIRNAME_SIZE]; // Directory in which to look for a file. If no directory is given default to MZF. + uint8_t filename[TZSVC_FILENAME_SIZE]; // File to open or create. + uint8_t wildcard[TZSVC_WILDCARD_SIZE]; // A basic wildcard pattern match filter to be applied to a directory search. + uint8_t sector[TZSVC_SECTOR_SIZE]; // Sector buffer generally for disk read/write. +} t_svcControl; + +// Structure to define all the directory entries which are packed into a single SD sector which is used between the Z80<->K64F. +// +typedef struct __attribute__((__packed__)) { + t_svcDirEnt dirEnt[TZVC_MAX_DIRENT_BLOCK]; // Fixed number of directory entries per sector/block. +} t_svcDirBlock; + +// Structure to hold compacted directory entries which are packed into a single SD sector which is used between the Z80<->K64F. +// +typedef struct __attribute__((__packed__)) { + t_svcCmpDirEnt dirEnt[TZVC_MAX_CMPCT_DIRENT_BLOCK];// Fixed number of compacted directory entries per sector/block. +} t_svcCmpDirBlock; + +// Mapping table from Sharp MZ80A Ascii to real Ascii. +// +typedef struct { + uint8_t asciiCode; +} t_asciiMap; + +// Application execution constants. +// + +// For the ARM Cortex-M compiler, the standard filestreams in an app are set by the CRT0 startup code, +// the original reentrant definition is undefined as it is not needed in the app. +#if defined __APP__ && defined __K64F__ + #undef stdout + #undef stdin + #undef stderr + FILE *stdout; + FILE *stdin; + FILE *stderr; +#endif + +// References to variables within the main library code. +extern volatile uint32_t *ioPin[MAX_TRANZPUTER_PINS]; +extern uint8_t pinMap[MAX_TRANZPUTER_PINS]; + +// Prototypes. +// +#if defined __APP__ +void yield(void); +#endif +void setupZ80Pins(uint8_t, volatile uint32_t *); +void resetZ80(void); +uint8_t reqZ80Bus(uint32_t); +uint8_t reqMainboardBus(uint32_t); +uint8_t reqTranZPUterBus(uint32_t); +void setupSignalsForZ80Access(enum BUS_DIRECTION); +void releaseZ80(void); +void refreshZ80(void); +void setCtrlLatch(uint8_t); +uint8_t copyFromZ80(uint8_t *, uint32_t, uint32_t, uint8_t); +uint8_t copyToZ80(uint32_t, uint8_t *, uint32_t, uint8_t); +uint8_t writeZ80Memory(uint16_t, uint8_t); +uint8_t readZ80Memory(uint16_t); +uint8_t writeZ80IO(uint16_t, uint8_t); +uint8_t readZ80IO(uint16_t); +void fillZ80Memory(uint32_t, uint32_t, uint8_t, uint8_t); +void captureVideoFrame(enum VIDEO_FRAMES, uint8_t); +void refreshVideoFrame(enum VIDEO_FRAMES, uint8_t, uint8_t); +FRESULT loadVideoFrameBuffer(char *, enum VIDEO_FRAMES); +FRESULT saveVideoFrameBuffer(char *, enum VIDEO_FRAMES); +char *getVideoFrame(enum VIDEO_FRAMES); +char *getAttributeFrame(enum VIDEO_FRAMES); +FRESULT loadZ80Memory(const char *, uint32_t, uint32_t, uint32_t, uint8_t, uint8_t); +FRESULT saveZ80Memory(const char *, uint32_t, uint32_t, t_svcDirEnt *, uint8_t); +FRESULT loadMZFZ80Memory(const char *, uint32_t, uint8_t, uint8_t); + +// Getter/Setter methods! +uint8_t isZ80Reset(void); +uint8_t isZ80MemorySwapped(void); +uint8_t getZ80IO(uint8_t *, uint8_t *); +void clearZ80Reset(void); +void convertSharpFilenameToAscii(char *, char *, uint8_t); + +// tranZPUter OS i/f methods. +uint8_t setZ80SvcStatus(uint8_t); +void svcSetDefaults(void); +uint8_t svcReadDir(uint8_t); +uint8_t svcFindFile(char *, char *, uint32_t); +uint8_t svcReadDirCache(uint8_t); +uint8_t svcFindFileCache(char *, char *, uint32_t); +uint8_t svcCacheDir(const char *, uint8_t); +uint8_t svcReadFile(uint8_t); +uint8_t svcLoadFile(void); +uint8_t svcEraseFile(void); +void processServiceRequest(void); +void loadTranZPUterDefaultROMS(void); +void tranZPUterControl(void); +uint8_t testTZFSAutoBoot(void); +void setupTranZPUter(void); + +#if defined __APP__ +int memoryDumpZ80(uint32_t, uint32_t, uint32_t, uint8_t, uint8_t); +#endif + +// Debug methods. +#if defined __APP__ && defined __TZPU_DEBUG__ +void displaySignals(void); +#endif + +#ifdef __cplusplus +} +#endif +#endif // TRANZPUTER_H diff --git a/libraries/lib/libimath2-k64f.a b/libraries/lib/libimath2-k64f.a index a052a26..8fb4532 100644 Binary files a/libraries/lib/libimath2-k64f.a and b/libraries/lib/libimath2-k64f.a differ diff --git a/libraries/lib/libumansi-k64f.a b/libraries/lib/libumansi-k64f.a index 52effe9..8ec43aa 100644 Binary files a/libraries/lib/libumansi-k64f.a and b/libraries/lib/libumansi-k64f.a differ diff --git a/libraries/lib/libummath-k64f.a b/libraries/lib/libummath-k64f.a index a62b851..6420cd7 100644 Binary files a/libraries/lib/libummath-k64f.a and b/libraries/lib/libummath-k64f.a differ diff --git a/libraries/lib/libummathf-k64f.a b/libraries/lib/libummathf-k64f.a index 33c873d..292edf2 100644 Binary files a/libraries/lib/libummathf-k64f.a and b/libraries/lib/libummathf-k64f.a differ diff --git a/libraries/lib/libummisc-k64f.a b/libraries/lib/libummisc-k64f.a index d63cdfa..c64e663 100644 Binary files a/libraries/lib/libummisc-k64f.a and b/libraries/lib/libummisc-k64f.a differ diff --git a/libraries/lib/libumstdio-k64f.a b/libraries/lib/libumstdio-k64f.a index 84c8ee1..a28319e 100644 Binary files a/libraries/lib/libumstdio-k64f.a and b/libraries/lib/libumstdio-k64f.a differ diff --git a/startup/app_k64f_crt0.s b/startup/app_k64f_crt0.s index 9751df9..b82107b 100644 --- a/startup/app_k64f_crt0.s +++ b/startup/app_k64f_crt0.s @@ -273,18 +273,18 @@ BSS_END: .word __bss_section_end__ #defapifunc f_printf funcAddr .equ funcAddr, funcAddr+funcNext; defapifunc f_gets funcAddr - # - # Low level disk calls. - # + # + # Low level disk calls. + # .equ funcAddr, funcAddr+funcNext; defapifunc disk_read funcAddr .equ funcAddr, funcAddr+funcNext; defapifunc disk_write funcAddr .equ funcAddr, funcAddr+funcNext; defapifunc disk_ioctl funcAddr - # - # Miscellaneous calls. - # + # + # Miscellaneous calls. + # .equ funcAddr, funcAddr+funcNext; defapifunc getStrParam funcAddr .equ funcAddr, funcAddr+funcNext; @@ -306,4 +306,69 @@ BSS_END: .word __bss_section_end__ .equ funcAddr, funcAddr+funcNext; defapifunc sys_free funcAddr + # tranZPUter kernel methods. + .equ funcAddr, funcAddr+funcNext; + defapifunc setupZ80Pins funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc resetZ80 funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc reqZ80Bus funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc reqMainboardBus funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc reqTranZPUterBus funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc setupSignalsForZ80Access funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc releaseZ80 funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc writeZ80Memory funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc readZ80Memory funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc writeZ80IO funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc readZ80IO funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc refreshZ80 funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc refreshZ80AllRows funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc setCtrlLatch funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc copyFromZ80 funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc copyToZ80 funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc fillZ80Memory funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc captureVideoFrame funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc refreshVideoFrame funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc loadVideoFrameBuffer funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc saveVideoFrameBuffer funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc getVideoFrame funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc getAttributeFrame funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc loadZ80Memory funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc loadMZFZ80Memory funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc saveZ80Memory funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc memoryDumpZ80 funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc isZ80Reset funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc isZ80MemorySwapped funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc getZ80IO funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc clearZ80Reset funcAddr + .equ funcAddr, funcAddr+funcNext; + defapifunc loadTranZPUterDefaultROMS funcAddr .end diff --git a/startup/mk20dx128.c b/startup/mk20dx128.c index 35bb290..ecdb0f2 100644 --- a/startup/mk20dx128.c +++ b/startup/mk20dx128.c @@ -36,6 +36,11 @@ #include "usb_ser_print.h" // testing only #include +#if defined __TRANZPUTER__ + #define FRESULT uint8_t + #include +#endif + // Flash Security Setting. On Teensy 3.2, you can lock the MK20 chip to prevent // anyone from reading your code. You CAN still reprogram your Teensy while @@ -979,6 +984,44 @@ void _ZPUTA_Vectors(void) __asm__ volatile ("b realloc"); __asm__ volatile ("b calloc"); __asm__ volatile ("b free"); + + #if defined __TRANZPUTER__ + // + // tranZPUter methods which need to be called via the kernel not accessed by the application directly. + // + __asm__ volatile ("b setupZ80Pins"); + __asm__ volatile ("b resetZ80"); + __asm__ volatile ("b reqZ80Bus"); + __asm__ volatile ("b reqMainboardBus"); + __asm__ volatile ("b reqTranZPUterBus"); + __asm__ volatile ("b setupSignalsForZ80Access"); + __asm__ volatile ("b releaseZ80"); + __asm__ volatile ("b writeZ80Memory"); + __asm__ volatile ("b readZ80Memory"); + __asm__ volatile ("b writeZ80IO"); + __asm__ volatile ("b readZ80IO"); + __asm__ volatile ("b refreshZ80"); + __asm__ volatile ("b refreshZ80AllRows"); + __asm__ volatile ("b setCtrlLatch"); + __asm__ volatile ("b copyFromZ80"); + __asm__ volatile ("b copyToZ80"); + __asm__ volatile ("b fillZ80Memory"); + __asm__ volatile ("b captureVideoFrame"); + __asm__ volatile ("b refreshVideoFrame"); + __asm__ volatile ("b loadVideoFrameBuffer"); + __asm__ volatile ("b saveVideoFrameBuffer"); + __asm__ volatile ("b getVideoFrame"); + __asm__ volatile ("b getAttributeFrame"); + __asm__ volatile ("b loadZ80Memory"); + __asm__ volatile ("b loadMZFZ80Memory"); + __asm__ volatile ("b saveZ80Memory"); + __asm__ volatile ("b memoryDumpZ80"); + __asm__ volatile ("b isZ80Reset"); + __asm__ volatile ("b isZ80MemorySwapped"); + __asm__ volatile ("b getZ80IO"); + __asm__ volatile ("b clearZ80Reset"); + __asm__ volatile ("b loadTranZPUterDefaultROMS"); + #endif } // Automatically initialize the RTC. When the build defines the compile @@ -999,6 +1042,7 @@ static void startup_default_early_hook(void) { #endif } static void startup_default_late_hook(void) {} + void startup_early_hook(void) __attribute__ ((weak, alias("startup_default_early_hook"))); void startup_late_hook(void) __attribute__ ((weak, alias("startup_default_late_hook"))); @@ -1068,6 +1112,7 @@ void ResetHandler(void) UART0_C2 = UART_C2_TE; PORTB_PCR17 = PORT_PCR_MUX(3); #endif + #if defined(KINETISK) && !defined(__MK66FX1M0__) // If the RTC oscillator isn't enabled, get it started early. // But don't do this early on Teensy 3.6 - RTC_CR depends on 3.3V+VBAT @@ -1087,12 +1132,12 @@ void ResetHandler(void) #else SMC_PMPROT = SMC_PMPROT_AVLP | SMC_PMPROT_ALLS | SMC_PMPROT_AVLLS; #endif - + // TODO: do this while the PLL is waiting to lock.... while (dest < &_edata) *dest++ = *src++; dest = &_sbss; while (dest < &_ebss) *dest++ = 0; - + // default all interrupts to medium priority level for (i=0; i < NVIC_NUM_INTERRUPTS + 16; i++) _VectorsRam[i] = _VectorsFlash[i]; for (i=0; i < NVIC_NUM_INTERRUPTS; i++) NVIC_SET_PRIORITY(i, 128); @@ -1439,6 +1484,16 @@ void ResetHandler(void) SYST_CVR = 0; SYST_CSR = SYST_CSR_CLKSOURCE | SYST_CSR_TICKINT | SYST_CSR_ENABLE; SCB_SHPR3 = 0x20200000; // Systick = priority 32 + +// Earliest point when the K64F can change the GPIO pins. +// This macro sets the CTL_BUSRQ signal as an output and low to halt the Z80 from going through its startup +// procedure. This then gives the needed time for the K64F to startup and bring the SD card online so +// that the tranZPUter board can be configured before releasing the Z80. +// +//#if defined __TRANZPUTER__ +// holdZ80(); +//#endif + //init_pins(); __enable_irq(); @@ -1477,7 +1532,6 @@ void ResetHandler(void) #endif __libc_init_array(); - startup_late_hook(); main(); diff --git a/startup/zos_k64f.ld b/startup/zos_k64f.ld index ca317c3..ad6af08 100644 --- a/startup/zos_k64f.ld +++ b/startup/zos_k64f.ld @@ -39,7 +39,7 @@ ENTRY(_VectorsFlash) MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00080000 - RAM (rwx) : ORIGIN = 0x2001A000, LENGTH = 0x00016000 + RAM (rwx) : ORIGIN = 0x20019000, LENGTH = 0x00017000 } /* FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 512K diff --git a/teensy3/TeensyThreads-asm.s b/teensy3/TeensyThreads-asm.s new file mode 100644 index 0000000..7a031be --- /dev/null +++ b/teensy3/TeensyThreads-asm.s @@ -0,0 +1,187 @@ +/* + * Threads-asm.S - Library for threading on the Teensy. + * + ******************* + * + * Copyright 2017 by Fernando Trias. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ******************* + * + * context_switch() changes the context to a new thread. It follows this strategy: + * + * 1. Abort if called from within an interrupt (unless using PIT) + * 2. Save registers r4-r11 to the current thread state (s0-s31 for FPU) + * 3. If not running on MSP, save PSP to the current thread state + * 4. Get the next running thread state + * 5. Restore r4-r11 from thread state (s0-s31 for FPU) + * 6. Set MSP or PSP depending on state + * 7. Switch MSP/PSP on return + * + * Notes: + * - Cortex-M has two stack pointers, MSP and PSP, which we alternate. See the + * reference manual under the Exception Model section. + * - I tried coding this in asm embedded in Threads.cpp but the compiler + * optimizations kept changing my code and removing lines so I have to use + * a separate assembly file. But if you try it, make sure to declare the + * function "naked" so the stack pointer SP is not modified when called. + * This means you can't use local variables, which are stored in stack. + * Try to turn optimizations off using optimize("O0") (which doesn't really + * turn off all optimizations). + * - Function can be called from systick_isr() or from the PIT timer (implemented + * by IntervalTimer) + * - If using systick, we override the default systick_isr() in order + * to preserve the stack and LR. If using PIT, we override the pitX_isr() for + * the same reason. + * - Since Systick can be called from within another interrupt, for simplicity, we + * check for this and abort. + * - Teensy uses MSP for it's main thread; we preserve that. Alternatively, we + * could have used PSP for all threads, including main, and reserve MSP for + * interrupts only. This would simplify the code slightly, but could introduce + * incompatabilities. + * - If this interrupt is nested within another interrupt, all kinds of bad + * things can happen. This is especially true if usb_isr() is active. In theory + * we should be able to do a switch even within an interrupt, but in my + * tests, it would not work reliably. + * - If using the PIT interrupt, it's priority is set to 255 (the lowest) so it + * cannot interrupt an interrupt. + */ + + .syntax unified + .align 2 + .thumb + + .global context_switch_direct + .thumb_func +context_switch_direct: + CPSID I + // Call here to force a context switch, so we skip checking the tick counter. + B call_direct + + .global context_switch_direct_active + .thumb_func +context_switch_direct_active: + CPSID I + // Call here to force a context switch, so we skip checking the tick counter. + B call_direct_active + + .global context_switch_pit_isr + .thumb_func +context_switch_pit_isr: + CPSID I + LDR r0, =context_timer_flag // acknowledge the interrupt by + LDR r0, [r0] // getting the pointer to the pointer + MOVS r1, #1 // + STR r1, [r0] // and setting to 1 + B context_switch_check // now go do the context switch + + .global context_switch + .thumb_func +context_switch: + + // Disable all interrupts; if we get interrupted during a context switch this + // could corrupt the system. + CPSID I + + // Did we interrupt another interrupt? If so, don't switch. Switching would + // wreck the system. In theory, we could reschedule the switch until the + // other interrupt is done. Or we could do a more sophisticated switch, but the + // easiest thing is to just ignore this condition. + CMP lr, #0xFFFFFFF1 // this means we interrupted an interrupt + BEQ to_exit // so don't do anything until next time + CMP lr, #0xFFFFFFE1 // this means we interrupted an interrupt with FPU + BEQ to_exit // so don't do anything until next time + +context_switch_check: + + // Count down number of ticks we should stay in thread + LDR r0, =currentCount // get the tick count (address to variable) + LDR r1, [r0] // get the value from the address + CMP r1, #0 // is it 0? + BEQ call_direct // if so, thread is done, so switch + SUB r1, #1 // otherwise, subtract 1 tick + STR r1, [r0] // and put it back + B to_exit // and quit until next context_switch + +call_direct: + + // Just do the context-switch (even if it's not time) + LDR r0, =currentActive // If the thread isn't active, skip it + LDR r0, [r0] + CMP r0, #1 + BNE to_exit + +call_direct_active: + + // Save the r4-r11 registers; (r0-r3,r12 are saved by the interrupt handler). + // Most thread libraries save this to the thread stack. I don't for simplicity + // and to make debugging easier. Since the Teensy doesn't have a debugging port, + // it's hard to examine the stack so this is easier. + LDR r0, =currentSave // get the address of the pointer + LDR r0, [r0] // get the pointer itself + STMIA r0!, {r4-r11,lr} // save r4-r11 to buffer + +#ifdef __ARM_PCS_VFP // compile if using FPU + VSTMIA r0!, {s0-s31} // save all FPU registers + VMRS r1, FPSCR // and FPU app status register + STMIA r0!, {r1} +#endif + + // Are we running on thread 0, which is MSP? + // It so, there is no need to save the stack pointer because MSP is never changed. + // If not, save the stack pointer. + LDR r0, =currentMSP // get the address of the variable + LDR r0, [r0] // get value from address + CMP r0, #0 // it is 0? This means it's PSP + BNE current_is_msp // not 0, so MSP, we can skip saving SP + MRS r0, psp // get the PSP value + LDR r1, =currentSP // get the address of our save variable + STR r0, [r1] // and store the PSP value there + current_is_msp: + + BL loadNextThread; // set the state to next running thread + + // Restore the r4-r11 registers from the saved thread + LDR r0, =currentSave // get address of pointer save buffer + LDR r0, [r0] // get the actual pointer + LDMIA r0!, {r4-r11,lr} // and restore r4-r11 & lr from save buffer + +#ifdef __ARM_PCS_VFP // compile if using FPU + VLDMIA r0!, {s0-s31} // restore all FPU registers + LDMIA r0!, {r1} // and the FP app status register + VMSR FPSCR, r1 +#endif + + // Setting LR causes the handler to switch MSP/PSP when returning. + // Switching to MSP? no need to restore MSP. + AND lr, lr, #0x10 // return stack with FP bit? + ORR lr, lr, #0xFFFFFFE9 // add basic LR bits + LDR r0, =currentMSP // get address of the variable + LDR r0, [r0] // get the actual value + CMP r0, #0 // is it 0? Then it's PSP + BNE to_exit // it's not 0, so it's MSP, all done + // if it's PSP, we need to switch PSP + LDR r0, =currentSP // get address of stack pointer + LDR r0, [r0] // get the actual value + MSR psp, r0 // save it to PSP + ORR lr, lr, #0b100 // set the PSP context switch + +to_exit: + // Re-enable interrupts + CPSIE I + // Return. The CPU will change MSP/PSP as needed based on LR + BX lr diff --git a/teensy3/TeensyThreads.cpp b/teensy3/TeensyThreads.cpp new file mode 100644 index 0000000..95815b2 --- /dev/null +++ b/teensy3/TeensyThreads.cpp @@ -0,0 +1,679 @@ +/* + * Threads.cpp - Library for threading on the Teensy. + * + ******************* + * + * Copyright 2017 by Fernando Trias. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ******************* + */ +#include "TeensyThreads.h" +#include + +#ifndef __IMXRT1062__ + +#include +IntervalTimer context_timer; + +#endif + +Threads threads; + +unsigned int time_start; +unsigned int time_end; + +#define __flush_cpu() __asm__ volatile("DMB"); + +// These variables are used by the assembly context_switch() function. +// They are copies or pointers to data in Threads and ThreadInfo +// and put here seperately in order to simplify the code. +extern "C" { + int currentUseSystick; // using Systick vs PIT/GPT + int currentActive; // state of the system (first, start, stop) + int currentCount; + ThreadInfo *currentThread; // the thread currently running + void *currentSave; + int currentMSP; // Stack pointers to save + void *currentSP; + void loadNextThread() { + threads.getNextThread(); + } +} + +extern "C" void stack_overflow_default_isr() { + currentThread->flags = Threads::ENDED; +} +extern "C" void stack_overflow_isr(void) __attribute__ ((weak, alias("stack_overflow_default_isr"))); + +extern unsigned long _estack; // the main thread 0 stack + +// static void threads_svcall_isr(void); +// static void threads_systick_isr(void); + +IsrFunction Threads::save_systick_isr; +IsrFunction Threads::save_svcall_isr; + +/* + * Teensy 3: + * Replace the SysTick interrupt for our context switching. Note that + * this function is "naked" meaning it does not save it's registers + * on the stack. This is so we can preserve the stack of the caller. + * + * Interrupts will save r0-r4 in the stack and since this function + * is short and simple, it should only use those registers. In the + * future, this should be coded in assembly to make sure. + */ +extern volatile uint32_t systick_millis_count; +extern "C" void systick_isr(); +void __attribute((naked, noinline)) threads_systick_isr(void) +{ + if (Threads::save_systick_isr) { + asm volatile("push {r0-r4,lr}"); + (*Threads::save_systick_isr)(); + asm volatile("pop {r0-r4,lr}"); + } + + // TODO: Teensyduino 1.38 calls MillisTimer::runFromTimer() from SysTick + if (currentUseSystick) { + // we branch in order to preserve LR and the stack + __asm volatile("b context_switch"); + } + __asm volatile("bx lr"); +} + +void __attribute((naked, noinline)) threads_svcall_isr(void) +{ + if (Threads::save_svcall_isr) { + asm volatile("push {r0-r4,lr}"); + (*Threads::save_svcall_isr)(); + asm volatile("pop {r0-r4,lr}"); + } + + // Get the right stack so we can extract the PC (next instruction) + // and then see the SVC calling instruction number + __asm volatile("TST lr, #4 \n" + "ITE EQ \n" + "MRSEQ r0, msp \n" + "MRSNE r0, psp \n"); + register unsigned int *rsp __asm("r0"); + unsigned int svc = ((uint8_t*)rsp[6])[-2]; + if (svc == Threads::SVC_NUMBER) { + __asm volatile("b context_switch_direct"); + } + else if (svc == Threads::SVC_NUMBER_ACTIVE) { + currentActive = Threads::STARTED; + __asm volatile("b context_switch_direct_active"); + } + __asm volatile("bx lr"); +} + +#ifdef __IMXRT1062__ + +/* + * + * Teensy 4: + * Use unused GPT timers for context switching + */ + +extern "C" void unused_interrupt_vector(void); + +static void __attribute((naked, noinline)) gpt1_isr() { + GPT1_SR |= GPT_SR_OF1; // clear set bit + __asm volatile ("dsb"); // see github bug #20 by manitou48 + __asm volatile("b context_switch"); +} + +static void __attribute((naked, noinline)) gpt2_isr() { + GPT2_SR |= GPT_SR_OF1; // clear set bit + __asm volatile ("dsb"); // see github bug #20 by manitou48 + __asm volatile("b context_switch"); +} + +bool gtp1_init(unsigned int microseconds) +{ + // Initialization code derived from @manitou48. + // See https://github.com/manitou48/teensy4/blob/master/gpt_isr.ino + // See https://forum.pjrc.com/threads/54265-Teensy-4-testing-mbed-NXP-MXRT1050-EVKB-(600-Mhz-M7)?p=193217&viewfull=1#post193217 + + // keep track of which GPT timer we are using + static int gpt_number = 0; + + // not configured yet, so find an inactive GPT timer + if (gpt_number == 0) { + if (! NVIC_IS_ENABLED(IRQ_GPT1)) { + attachInterruptVector(IRQ_GPT1, &gpt1_isr); + NVIC_SET_PRIORITY(IRQ_GPT1, 255); + NVIC_ENABLE_IRQ(IRQ_GPT1); + gpt_number = 1; + } + else if (! NVIC_IS_ENABLED(IRQ_GPT2)) { + attachInterruptVector(IRQ_GPT2, &gpt2_isr); + NVIC_SET_PRIORITY(IRQ_GPT2, 255); + NVIC_ENABLE_IRQ(IRQ_GPT2); + gpt_number = 2; + } + else { + // if neither timer is free, we fail + return false; + } + } + + switch (gpt_number) { + case 1: + CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON) ; // enable GPT1 module + GPT1_CR = 0; // disable timer + GPT1_PR = 23; // prescale: divide by 24 so 1 tick = 1 microsecond at 24MHz + GPT1_OCR1 = microseconds - 1; // compare value + GPT1_SR = 0x3F; // clear all prior status + GPT1_IR = GPT_IR_OF1IE; // use first timer + GPT1_CR = GPT_CR_EN | GPT_CR_CLKSRC(1) ; // set to peripheral clock (24MHz) + break; + case 2: + CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON) ; // enable GPT1 module + GPT2_CR = 0; // disable timer + GPT2_PR = 23; // prescale: divide by 24 so 1 tick = 1 microsecond at 24MHz + GPT2_OCR1 = microseconds - 1; // compare value + GPT2_SR = 0x3F; // clear all prior status + GPT2_IR = GPT_IR_OF1IE; // use first timer + GPT2_CR = GPT_CR_EN | GPT_CR_CLKSRC(1) ; // set to peripheral clock (24MHz) + break; + default: + return false; + } + + return true; +} + +#endif + +Threads::Threads() : current_thread(0), thread_count(0), thread_error(0) { + // initilize thread slots to empty + for(int i=0; isave; + currentMSP = 1; + currentSP = 0; + currentCount = Threads::DEFAULT_TICKS; + currentActive = FIRST_RUN; + threadp[0]->flags = RUNNING; + threadp[0]->ticks = DEFAULT_TICKS; + threadp[0]->stack = (uint8_t*)&_estack - DEFAULT_STACK0_SIZE; + threadp[0]->stack_size = DEFAULT_STACK0_SIZE; + +#ifdef __IMXRT1062__ + + // commandeer SVCall & use GTP1 Interrupt + save_svcall_isr = _VectorsRam[11]; + if (save_svcall_isr == unused_interrupt_vector) save_svcall_isr = 0; + _VectorsRam[11] = threads_svcall_isr; + + currentUseSystick = 0; // disable Systick calls + gtp1_init(1000); // tick every millisecond + +#else + + currentUseSystick = 1; + + // commandeer the SVCall & SysTick Exceptions + save_svcall_isr = _VectorsRam[11]; + if (save_svcall_isr == unused_isr) save_svcall_isr = 0; + _VectorsRam[11] = threads_svcall_isr; + + save_systick_isr = _VectorsRam[15]; + if (save_systick_isr == unused_isr) save_systick_isr = 0; + _VectorsRam[15] = threads_systick_isr; + +#ifdef DEBUG +#if defined(__MK20DX256__) || defined(__MK20DX128__) + ARM_DEMCR |= ARM_DEMCR_TRCENA; // Make ssure Cycle Counter active + ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; +#endif +#endif + +#endif +} + +/* + * start() - Begin threading + */ +int Threads::start(int prev_state) { + __disable_irq(); + int old_state = currentActive; + if (prev_state == -1) prev_state = STARTED; + currentActive = prev_state; + __enable_irq(); + return old_state; +} + +/* + * stop() - Stop threading, even if active. + * + * If threads have already started, this should be called sparingly + * because it could destabalize the system if thread 0 is stopped. + */ +int Threads::stop() { + __disable_irq(); + int old_state = currentActive; + currentActive = STOPPED; + __enable_irq(); + return old_state; +} + +/* + * getNextThread() - Find next running thread + * + * This will also set the context_switcher() state variables + */ +void Threads::getNextThread() { + +#ifdef DEBUG + // Keep track of the number of cycles expended by each thread. + // See @dfragster: https://forum.pjrc.com/threads/41504-Teensy-3-x-multithreading-library-first-release?p=213086#post213086 + currentThread->cyclesAccum += ARM_DWT_CYCCNT - currentThread->cyclesStart; +#endif + + // First, save the currentSP set by context_switch + currentThread->sp = currentSP; + + // did we overflow the stack (don't check thread 0)? + // allow an extra 8 bytes for a call to the ISR and one additional call or variable + if (current_thread && ((uint8_t*)currentThread->sp - currentThread->stack <= 8)) { + stack_overflow_isr(); + } + + // Find the next running thread + while(1) { + current_thread++; + if (current_thread >= MAX_THREADS) { + current_thread = 0; // thread 0 is MSP; always active so return + break; + } + if (threadp[current_thread] && threadp[current_thread]->flags == RUNNING) break; + } + currentCount = threadp[current_thread]->ticks; + + currentThread = threadp[current_thread]; + currentSave = &threadp[current_thread]->save; + currentMSP = (current_thread==0?1:0); + currentSP = threadp[current_thread]->sp; + +#ifdef DEBUG + currentThread->cyclesStart = ARM_DWT_CYCCNT; +#endif +} + +/* + * Empty placeholder for IntervalTimer class + */ +static void context_pit_empty() {} + +/* + * Store the PIT timer flag register for use in assembly + */ +volatile uint32_t *context_timer_flag; + +/* + * Defined in assembly code + */ +extern "C" void context_switch_pit_isr(); + +/* + * Stop using the SysTick interrupt and start using + * the IntervalTimer timer. The parameter is the number of microseconds + * for each tick. + */ +int Threads::setMicroTimer(int tick_microseconds) +{ +#ifdef __IMXRT1062__ + + gtp1_init(tick_microseconds); + +#else + +/* + * Implementation strategy suggested by @tni in Teensy Forums; see + * https://forum.pjrc.com/threads/41504-Teensy-3-x-multithreading-library-first-release + */ + + // lowest priority so we don't interrupt other interrupts + context_timer.priority(255); + // start timer with dummy fuction + if (context_timer.begin(context_pit_empty, tick_microseconds) == 0) { + // failed to set the timer! + return 0; + } + currentUseSystick = 0; // disable Systick calls + + // get the PIT number [0-3] (IntervalTimer overrides IRQ_NUMBER_t op) + int number = (IRQ_NUMBER_t)context_timer - IRQ_PIT_CH0; + // calculate number of uint32_t per PIT; should be 4. + // Not hard-coded in case this changes in future CPUs. + const int width = (PIT_TFLG1 - PIT_TFLG0) / sizeof(uint32_t); + // get the right flag to ackowledge PIT interrupt + context_timer_flag = &PIT_TFLG0 + (width * number); + attachInterruptVector(context_timer, context_switch_pit_isr); + +#endif + + return 1; +} + +/* + * Set each time slice to be 'microseconds' long + */ +int Threads::setSliceMicros(int microseconds) +{ + setMicroTimer(microseconds); + setDefaultTimeSlice(1); + return 1; +} + +/* + * Set each time slice to be 'milliseconds' long + */ +int Threads::setSliceMillis(int milliseconds) +{ + if (currentUseSystick) { + setDefaultTimeSlice(milliseconds); + } + else { + // if we're using the PIT, we should probably really disable it and + // re-establish the systick timer; but this is easier for now + setSliceMicros(milliseconds * 1000); + } + return 1; +} + +/* + * del_process() - This is called when the task returns + * + * Turns thread off. Thread continues running until next call to + * context_switch() at which point it all stops. The while(1) statement + * just stalls until such time. + */ +void Threads::del_process(void) +{ + int old_state = threads.stop(); + ThreadInfo *me = threads.threadp[threads.current_thread]; + // Would love to delete stack here but the thread doesn't + // end now. It continues until the next tick. + // if (me->my_stack) { + // delete[] me->stack; + // me->stack = 0; + // } + threads.thread_count--; + me->flags = ENDED; //clear the flags so thread can stop and be reused + threads.start(old_state); + while(1); // just in case, keep working until context change when execution will not return to this thread +} + +/* + * Initializes a thread's stack. Called when thread is created + */ +void *Threads::loadstack(ThreadFunction p, void * arg, void *stackaddr, int stack_size) +{ + interrupt_stack_t * process_frame = (interrupt_stack_t *)((uint8_t*)stackaddr + stack_size - sizeof(interrupt_stack_t) - 8); + process_frame->r0 = (uint32_t)arg; + process_frame->r1 = 0; + process_frame->r2 = 0; + process_frame->r3 = 0; + process_frame->r12 = 0; + process_frame->lr = (uint32_t)Threads::del_process; + process_frame->pc = ((uint32_t)p); + process_frame->xpsr = 0x1000000; + uint8_t *ret = (uint8_t*)process_frame; + // ret -= sizeof(software_stack_t); // uncomment this if we are saving R4-R11 to the stack + return (void*)ret; +} + +/* + * Add a new thread to the queue. + * add_thread(fund, arg) + * + * fund : is a function pointer. The function prototype is: + * void *func(void *param) + * arg : is a void pointer that is passed as the first parameter + * of the function. In the example above, arg is passed + * as param. + * stack_size : the size of the buffer pointed to by stack. If + * it is 0, then "stack" must also be 0. If so, the function + * will allocate the default stack size of the heap using new(). + * stack : pointer to new data stack of size stack_size. If this is 0, + * then it will allocate a stack on the heap using new() of size + * stack_size. If stack_size is 0, a default size will be used. + * return: an integer ID to be used for other calls + */ +int Threads::addThread(ThreadFunction p, void * arg, int stack_size, void *stack) +{ + int old_state = stop(); + if (stack_size == -1) stack_size = DEFAULT_STACK_SIZE; + for (int i=1; i < MAX_THREADS; i++) { + if (threadp[i] == NULL) { // empty thread, so fill it + threadp[i] = new ThreadInfo(); + } + if (threadp[i]->flags == ENDED || threadp[i]->flags == EMPTY) { // free thread + ThreadInfo *tp = threadp[i]; // working on this thread + if (tp->stack && tp->my_stack) { + delete[] tp->stack; + } + if (stack==0) { + stack = new uint8_t[stack_size]; + tp->my_stack = 1; + } + else { + tp->my_stack = 0; + } + tp->stack = (uint8_t*)stack; + tp->stack_size = stack_size; + void *psp = loadstack(p, arg, tp->stack, tp->stack_size); + tp->sp = psp; + tp->ticks = DEFAULT_TICKS; + tp->flags = RUNNING; + tp->save.lr = 0xFFFFFFF9; + +#ifdef DEBUG + tp->cyclesStart = ARM_DWT_CYCCNT; + tp->cyclesAccum = 0; +#endif + + currentActive = old_state; + thread_count++; + if (old_state == STARTED || old_state == FIRST_RUN) start(); + return i; + } + } + if (old_state == STARTED) start(); + return -1; +} + +int Threads::getState(int id) +{ + return threadp[id]->flags; +} + +int Threads::setState(int id, int state) +{ + threadp[id]->flags = state; + return state; +} + +int Threads::wait(int id, unsigned int timeout_ms) +{ + unsigned int start = millis(); + // need to store state in temp volatile memory for optimizer. + // "while (thread[id].flags != RUNNING)" will be optimized away + volatile int state; + while (1) { + if (timeout_ms != 0 && millis() - start > timeout_ms) return -1; + state = threadp[id]->flags; + if (state != RUNNING) break; + yield(); + } + return id; +} + +int Threads::kill(int id) +{ + threadp[id]->flags = ENDED; + return id; +} + +int Threads::suspend(int id) +{ + threadp[id]->flags = SUSPENDED; + return id; +} + +int Threads::restart(int id) +{ + threadp[id]->flags = RUNNING; + return id; +} + +void Threads::setTimeSlice(int id, unsigned int ticks) +{ + threadp[id]->ticks = ticks - 1; +} + +void Threads::setDefaultTimeSlice(unsigned int ticks) +{ + DEFAULT_TICKS = ticks - 1; +} + +void Threads::setDefaultStackSize(unsigned int bytes_size) +{ + DEFAULT_STACK_SIZE = bytes_size; +} + +void Threads::yield() { + __asm volatile("svc %0" : : "i"(Threads::SVC_NUMBER)); +} + +void Threads::yield_and_start() { + __asm volatile("svc %0" : : "i"(Threads::SVC_NUMBER_ACTIVE)); +} + +void Threads::delay(int millisecond) { + int mx = millis(); + while((int)millis() - mx < millisecond) yield(); +} + +int Threads::id() { + volatile int ret; + __disable_irq(); + ret = current_thread; + __enable_irq(); + return ret; +} + +int Threads::getStackUsed(int id) { + return threadp[id]->stack + threadp[id]->stack_size - (uint8_t*)threadp[id]->sp; +} + +int Threads::getStackRemaining(int id) { + return (uint8_t*)threadp[id]->sp - threadp[id]->stack; +} + +#ifdef DEBUG +unsigned long Threads::getCyclesUsed(int id) { + stop(); + unsigned long ret = threadp[id]->cyclesAccum; + start(); + return ret; +} +#endif + +/* + * On creation, stop threading and save state + */ +Threads::Suspend::Suspend() { + __disable_irq(); + save_state = currentActive; + currentActive = 0; + __enable_irq(); +} + +/* + * On destruction, restore threading state + */ +Threads::Suspend::~Suspend() { + __disable_irq(); + currentActive = save_state; + __enable_irq(); +} + +int Threads::Mutex::getState() { + int p = threads.stop(); + int ret = state; + threads.start(p); + return ret; +} + +int __attribute__ ((noinline)) Threads::Mutex::lock(unsigned int timeout_ms) { + if (try_lock()) return 1; // we're good, so avoid more checks + + uint32_t start = systick_millis_count; + while (1) { + if (try_lock()) return 1; + if (timeout_ms && (systick_millis_count - start > timeout_ms)) return 0; + if (waitthread==-1) { // can hold 1 thread suspend until unlock + int p = threads.stop(); + waitthread = threads.current_thread; + waitcount = currentCount; + threads.suspend(waitthread); + threads.start(p); + } + threads.yield(); + } + __flush_cpu(); + return 0; +} + +int Threads::Mutex::try_lock() { + int p = threads.stop(); + if (state == 0) { + state = 1; + threads.start(p); + return 1; + } + threads.start(p); + return 0; +} + +int __attribute__ ((noinline)) Threads::Mutex::unlock() { + int p = threads.stop(); + if (state==1) { + state = 0; + if (waitthread >= 0) { // reanimate a suspended thread waiting for unlock + threads.restart(waitthread); + waitthread = -1; + __flush_cpu(); + threads.yield_and_start(); + return 1; + } + } + __flush_cpu(); + threads.start(p); + return 1; +} diff --git a/teensy3/TeensyThreads.h b/teensy3/TeensyThreads.h new file mode 100644 index 0000000..8005e28 --- /dev/null +++ b/teensy3/TeensyThreads.h @@ -0,0 +1,424 @@ +/* + * Threads.h - Library for threading on the Teensy. + * + ******************* + * + * Copyright 2017 by Fernando Trias. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ******************* + * + * Multithreading library for Teensy board. + * See Threads.cpp for explanation of internal functions. + * + * A global variable "threads" of type Threads will be created + * to provide all threading functions. See example below: + * + * #include + * + * volatile int count = 0; + * + * void thread_func(int data){ + * while(1) count++; + * } + * + * void setup() { + * threads.addThread(thread_func, 0); + * } + * + * void loop() { + * Serial.print(count); + * } + * + * Alternatively, you can use the std::threads class defined + * by C++11 + * + * #include + * + * volatile int count = 0; + * + * void thread_func(){ + * while(1) count++; + * } + * + * void setup() { + * std::thead th1(thread_func); + * th1.detach(); + * } + * + * void loop() { + * Serial.print(count); + * } + * + */ + +#ifndef _THREADS_H +#define _THREADS_H + +#include + +/* Enabling debugging information allows access to: + * getCyclesUsed() + */ +// #define DEBUG + +extern "C" { + void context_switch(void); + void context_switch_direct(void); + void context_switch_pit_isr(void); + void loadNextThread(); + void stack_overflow_isr(void); + void threads_svcall_isr(void); + void threads_systick_isr(void); +} + +// The stack frame saved by the interrupt +typedef struct { + uint32_t r0; + uint32_t r1; + uint32_t r2; + uint32_t r3; + uint32_t r12; + uint32_t lr; + uint32_t pc; + uint32_t xpsr; +} interrupt_stack_t; + +// The stack frame saved by the context switch +typedef struct { + uint32_t r4; + uint32_t r5; + uint32_t r6; + uint32_t r7; + uint32_t r8; + uint32_t r9; + uint32_t r10; + uint32_t r11; + uint32_t lr; +#ifdef __ARM_PCS_VFP + uint32_t s0; + uint32_t s1; + uint32_t s2; + uint32_t s3; + uint32_t s4; + uint32_t s5; + uint32_t s6; + uint32_t s7; + uint32_t s8; + uint32_t s9; + uint32_t s10; + uint32_t s11; + uint32_t s12; + uint32_t s13; + uint32_t s14; + uint32_t s15; + uint32_t s16; + uint32_t s17; + uint32_t s18; + uint32_t s19; + uint32_t s20; + uint32_t s21; + uint32_t s22; + uint32_t s23; + uint32_t s24; + uint32_t s25; + uint32_t s26; + uint32_t s27; + uint32_t s28; + uint32_t s29; + uint32_t s30; + uint32_t s31; + uint32_t fpscr; +#endif +} software_stack_t; + +// The state of each thread (including thread 0) +class ThreadInfo { + public: + int stack_size; + uint8_t *stack=0; + int my_stack = 0; + software_stack_t save; + volatile int flags = 0; + void *sp; + int ticks; +#ifdef DEBUG + unsigned long cyclesStart; // On T_4 the CycCnt is always active - on T_3.x it currently is not - unless Audio starts it AFAIK + unsigned long cyclesAccum; +#endif +}; + +extern "C" void unused_isr(void); + +typedef void (*ThreadFunction)(void*); +typedef void (*ThreadFunctionInt)(int); +typedef void (*ThreadFunctionNone)(); + +typedef void (*IsrFunction)(); + +/* + * Threads handles all the threading interaction with users. It gets + * instantiated in a global variable "threads". + */ +class Threads { +public: + // The maximum number of threads is hard-coded to simplify + // the implementation. See notes of ThreadInfo. + static const int MAX_THREADS = 8; + int DEFAULT_STACK_SIZE = 1024; + const int DEFAULT_STACK0_SIZE = 10240; // estimate for thread 0? + int DEFAULT_TICKS = 10; + static const int DEFAULT_TICK_MICROSECONDS = 100; + + // State of threading system + static const int STARTED = 1; + static const int STOPPED = 2; + static const int FIRST_RUN = 3; + + // State of individual threads + static const int EMPTY = 0; + static const int RUNNING = 1; + static const int ENDED = 2; + static const int ENDING = 3; + static const int SUSPENDED = 4; + + static const int SVC_NUMBER = 0x21; + static const int SVC_NUMBER_ACTIVE = 0x22; + +protected: + int current_thread; + int thread_count; + int thread_error; + + /* + * The maximum number of threads is hard-coded. Alternatively, we could implement + * a linked list which would mean using up less memory for a small number of + * threads while allowing an unlimited number of possible threads. This would + * probably not slow down thread switching too much, but it would introduce + * complexity and possibly bugs. So to simplifiy for now, we use an array. + * But in the future, a linked list might be more appropriate. + */ + ThreadInfo *threadp[MAX_THREADS]; + // This used to be allocated statically, as below. Kept for reference in case of bugs. + // ThreadInfo thread[MAX_THREADS]; + +public: // public for debugging + static IsrFunction save_systick_isr; + static IsrFunction save_svcall_isr; + +public: + Threads(); + + // Create a new thread for function "p", passing argument "arg". If stack is 0, + // stack allocated on heap. Function "p" has form "void p(void *)". + int addThread(ThreadFunction p, void * arg=0, int stack_size=-1, void *stack=0); + // For: void f(int) + int addThread(ThreadFunctionInt p, int arg=0, int stack_size=-1, void *stack=0) { + return addThread((ThreadFunction)p, (void*)arg, stack_size, stack); + } + // For: void f() + int addThread(ThreadFunctionNone p, int arg=0, int stack_size=-1, void *stack=0) { + return addThread((ThreadFunction)p, (void*)arg, stack_size, stack); + } + + // Get the state; see class constants. Can be EMPTY, RUNNING, etc. + int getState(int id); + // Explicityly set a state. See getState(). Call with care. + int setState(int id, int state); + // Wait until thread returns up to timeout_ms milliseconds. If ms is 0, wait + // indefinitely. + int wait(int id, unsigned int timeout_ms = 0); + // Permanently stop a running thread. Thread will end on the next thread slice tick. + int kill(int id); + // Suspend a thread (on the next slice tick). Can be restarted with restart(). + int suspend(int id); + // Restart a suspended thread. + int restart(int id); + // Set the slice length time in ticks for a thread (1 tick = 1 millisecond, unless using MicroTimer) + void setTimeSlice(int id, unsigned int ticks); + // Set the slice length time in ticks for all new threads (1 tick = 1 millisecond, unless using MicroTimer) + void setDefaultTimeSlice(unsigned int ticks); + // Set the stack size for new threads in bytes + void setDefaultStackSize(unsigned int bytes_size); + // Use the microsecond timer provided by IntervalTimer & PIT; instead of 1 tick = 1 millisecond, + // 1 tick will be the number of microseconds provided (default is 100 microseconds) + int setMicroTimer(int tick_microseconds = DEFAULT_TICK_MICROSECONDS); + // Simple function to set each time slice to be 'milliseconds' long + int setSliceMillis(int milliseconds); + // Set each time slice to be 'microseconds' long + int setSliceMicros(int microseconds); + + // Get the id of the currently running thread + int id(); + int getStackUsed(int id); + int getStackRemaining(int id); +#ifdef DEBUG + unsigned long getCyclesUsed(int id); +#endif + + // Yield current thread's remaining time slice to the next thread, causing immediate + // context switch + void yield(); + // Wait for milliseconds using yield(), giving other slices your wait time + void delay(int millisecond); + + // Start/restart threading system; returns previous state: STARTED, STOPPED, FIRST_RUN + // can pass the previous state to restore + int start(int old_state = -1); + // Stop threading system; returns previous state: STARTED, STOPPED, FIRST_RUN + int stop(); + + // Allow these static functions and classes to access our members + friend void context_switch(void); + friend void context_switch_direct(void); + friend void context_pit_isr(void); + friend void threads_systick_isr(void); + friend void threads_svcall_isr(void); + friend void loadNextThread(); + friend class ThreadLock; + +protected: + void getNextThread(); + void *loadstack(ThreadFunction p, void * arg, void *stackaddr, int stack_size); + static void force_switch_isr(); + +private: + static void del_process(void); + void yield_and_start(); + +public: + class Mutex { + private: + volatile int state = 0; + volatile int waitthread = -1; + volatile int waitcount = 0; + public: + int getState(); // get the lock state; 1=locked; 0=unlocked + int lock(unsigned int timeout_ms = 0); // lock, optionally waiting up to timeout_ms milliseconds + int try_lock(); // if lock available, get it and return 1; otherwise return 0 + int unlock(); // unlock if locked + }; + + class Scope { + private: + Mutex *r; + public: + Scope(Mutex& m) { r = &m; r->lock(); } + ~Scope() { r->unlock(); } + }; + + class Suspend { + private: + int save_state; + public: + Suspend(); // Stop threads and save thread state + ~Suspend(); // Restore saved state + }; + + template class GrabTemp { + private: + Mutex *lkp; + public: + C *me; + GrabTemp(C *obj, Mutex *lk) { me = obj; lkp=lk; lkp->lock(); } + ~GrabTemp() { lkp->unlock(); } + C &get() { return *me; } + }; + + template class Grab { + private: + Mutex lk; + T *me; + public: + Grab(T &t) { me = &t; } + GrabTemp grab() { return GrabTemp(me, &lk); } + operator T&() { return grab().get(); } + T *operator->() { return grab().me; } + Mutex &getLock() { return lk; } + }; + +#define ThreadWrap(OLDOBJ, NEWOBJ) Threads::Grab NEWOBJ(OLDOBJ); +#define ThreadClone(NEWOBJ) (NEWOBJ.grab().get()) + +}; + + +extern Threads threads; + +/* + * Rudimentary compliance to C++11 class + * + * See http://www.cplusplus.com/reference/thread/thread/ + * + * Example: + * int x; + * void thread_func() { x++; } + * int main() { + * std::thread(thread_func); + * } + * + */ +namespace std { + class thread { + private: + int id; // internal thread id + int destroy; // flag to kill thread on instance destruction + public: + // By casting all (args...) to (void*), if there are more than one args, the compiler + // will fail to find a matching function. This fancy template just allows any kind of + // function to match. + template explicit thread(F&& f, Args&&... args) { + id = threads.addThread((ThreadFunction)f, (void*)args...); + destroy = 1; + } + // If thread has not been detached when destructor called, then thread must end + ~thread() { + if (destroy) threads.kill(id); + } + // Threads are joinable until detached per definition, but in this implementation + // that's not so. We emulate expected behavior anyway. + bool joinable() { return destroy==1; } + // Once detach() is called, thread runs until it terminates; otherwise it terminates + // when destructor called. + void detach() { destroy = 0; } + // In theory, the thread merges with the running thread; if we just wait until + // termination, it's basically the same thing except it's slower because + // there are two threads running instead of one. Close enough. + void join() { threads.wait(id); } + // Get the unique thread id. + int get_id() { return id; } + }; + + class mutex { + private: + Threads::Mutex mx; + public: + void lock() { mx.lock(); } + bool try_lock() { return mx.try_lock(); } + void unlock() { mx.unlock(); } + }; + + template class lock_guard { + private: + cMutex *r; + public: + explicit lock_guard(cMutex& m) { r = &m; r->lock(); } + ~lock_guard() { r->unlock(); } + }; +} + +#endif diff --git a/zOS/Makefile.k64f b/zOS/Makefile.k64f index 09849ab..3bcb66d 100644 --- a/zOS/Makefile.k64f +++ b/zOS/Makefile.k64f @@ -63,10 +63,10 @@ BUILD_DIR = $(abspath $(CURDIR)/build) # Addresses where the zOS/ZPUTA base program loads and where apps load and execute. # With IOCP. These are defaults as they should be overriden by caller. ifeq ($(OS_BASEADDR),) -OS_BASEADDR = 0x00000000 + OS_BASEADDR = 0x00000000 endif ifeq ($(OS_APPADDR),) -OS_APPADDR = 0x1FFF8000 + OS_APPADDR = 0x1FFF8000 endif #************************************************************************ @@ -89,7 +89,7 @@ endif # path location for the arm-none-eabi compiler -COMPILERPATH = $(TOOLSPATH)/arm/bin +COMPILERPATH = $(TOOLSPATH)/arm/bin # path location for Teensy 3 core TEENSY3_DIR = $(CURDIR)/../teensy3 @@ -127,6 +127,9 @@ CPPFLAGS = -Wall -g -Os -mthumb -fno-builtin -ffunction-sections -fdata-se CPPFLAGS += -Isrc -Iteensy3 -I$(COMMON_DIR) -I$(FATFS_DIR) -I$(TEENSY3_DIR) -I$(INCLUDE_DIR) CPPFLAGS += -D__K64F__ -D__ZOS__ -DDEBUG -D__SD_CARD__ -DOS_BASEADDR=$(OS_BASEADDR) -DOS_APPADDR=$(OS_APPADDR) #CPPFLAGS += -fno-use-cxa-atexit +ifeq ($(__TRANZPUTER__),1) + CPPFLAGS += -D__TRANZPUTER__ +endif # compiler options for C++ only #CXXFLAGS = -std=gnu++0x -felide-constructors -fno-exceptions -fno-rtti @@ -165,6 +168,7 @@ else ifeq ($(TEENSY), LC) LIBS += -larm_cortexM0l_math else ifeq ($(TEENSY), 35) CPPFLAGS += -D__MK64FX512__ -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 + ASFLAGS += -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 LDSCRIPT = $(STARTUP_DIR)/zos_k64f.ld #LDFLAGS += -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -T$(STARTUP_DIR)/zos_k64f.ld -ffreestanding -mthumb #-fno-use-cxa-atexit LDFLAGS += -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -T$(LDSCRIPT) @@ -188,18 +192,25 @@ endif # automatically create lists of the sources and objects TEENSY_C_FILES := $(wildcard $(TEENSY3_DIR)/*.c) TEENSY_CPP_FILES:= $(wildcard $(TEENSY3_DIR)/*.cpp) +TEENSY_ASM_FILES:= $(wildcard $(TEENSY3_DIR)/*.s) SRC_C_FILES := $(wildcard src/*.c) SRC_CPP_FILES := $(wildcard src/*.cpp) INO_FILES := $(wildcard src/*.ino) CRT0_ASM_FILES := #$(STARTUP_DIR)/zos_k64f_crt0.s CRT0_C_FILES := $(STARTUP_DIR)/mk20dx128.c COMMON_FILES := $(COMMON_DIR)/utils.c $(COMMON_DIR)/k64f_soc.c $(COMMON_DIR)/interrupts.c $(COMMON_DIR)/ps2.c $(COMMON_DIR)/readline.c +ifeq ($(__TRANZPUTER__),1) + COMMON_FILES += $(COMMON_DIR)/tranzputer.c +endif FATFS_C_FILES := $(FATFS_DIR)/ff.c +ifeq ($(__TRANZPUTER__),1) +FATFS_C_FILES += $(FATFS_DIR)/ffunicode.c +endif FATFS_CPP_FILES:= $(FATFS_DIR)/sdmmc_k64f.cpp PFS_FILES := $(PFS_DIR)/sdmmc_teensy.c $(PFS_DIR)/pff.c # Define the sources and what they compile from->to. -SOURCES := $(CRT0_ASM_FILES:.s=.o) $(CRT0_C_FILES:.c=.o) $(COMMON_FILES:.c=.o) $(FATFS_C_FILES:.c=.o) $(FATFS_CPP_FILES:.cpp=.o) $(SRC_C_FILES:.c=.o) $(SRC_CPP_FILES:.cpp=.o) $(INO_FILES:.ino=.o) $(TEENSY_C_FILES:.c=.o) $(TEENSY_CPP_FILES:.cpp=.o) +SOURCES := $(CRT0_ASM_FILES:.s=.o) $(CRT0_C_FILES:.c=.o) $(COMMON_FILES:.c=.o) $(FATFS_C_FILES:.c=.o) $(FATFS_CPP_FILES:.cpp=.o) $(SRC_C_FILES:.c=.o) $(SRC_CPP_FILES:.cpp=.o) $(INO_FILES:.ino=.o) $(TEENSY_C_FILES:.c=.o) $(TEENSY_CPP_FILES:.cpp=.o) $(TEENSY_ASM_FILES:.s=.o) OBJS := $(foreach src,$(SOURCES), $(BUILD_DIR)/$(src)) all: version hex bin srec rpt lss dmp @@ -282,7 +293,7 @@ $(BUILD_DIR)/%.o: %.cpp $(BUILD_DIR)/%.o: %.s @echo "[AS]\t$<" @mkdir -p "$(dir $@)" - @$(AS) $(ASFLAGS) -o "$@" -c "$<" + $(AS) $(ASFLAGS) -o "$@" -c "$<" $(BUILD_DIR)/%.o: %.ino @echo "[CXX]\t$<" diff --git a/zOS/src/zOS.cpp b/zOS/src/zOS.cpp index 5e8b6ea..711ea11 100644 --- a/zOS/src/zOS.cpp +++ b/zOS/src/zOS.cpp @@ -56,7 +56,9 @@ #include "WProgram.h" #include "k64f_soc.h" #include <../libraries/include/stdmisc.h> -#else + #include + +#else // __ZPU__ #include #include #include @@ -75,6 +77,10 @@ #include "zOS_app.h" /* Header for definitions specific to apps run from zOS */ #include "zOS.h" +#if defined __TRANZPUTER__ + #include +#endif + #if defined(BUILTIN_TST_DHRYSTONE) && BUILTIN_TST_DHRYSTONE == 1 #include #endif @@ -226,6 +232,64 @@ uint8_t getCommandLine(char *buf, uint8_t bufSize) return(result); } +#if defined __TRANZPUTER__ +// Thread to monitor and control the tranZPUter board offering needed services and monitoring. +// +void tranZPUterControl(void) +{ + // Locals. + uint8_t ioAddr; + uint8_t ioData; + + // Loop waiting on events and processing. + // + while(1) + { + // Indicate the thread is busy so we are not interrupted whilst servicing the tranZPUter. + // + G.ctrlThreadBusy = 1; + + // If a user reset event occurred, reload the default ROM set. + // + if(isZ80Reset()) + { +printf("Doing a reset load\n"); + // Reload the memory on the tranZPUter to boot default. + loadTranZPUterDefaultROMS(); + + // Clear reset event which caused this reload. + clearZ80Reset(); + } + + // Has there been an IO instruction for a service request? + // + if(getZ80IO(&ioAddr, &ioData) == 1) + { +printf("Got an IO request, addr:%02x, Data:%02x\n", ioAddr, ioData); + switch(ioAddr) + { + // Service request. Actual data about the request is stored in the Z80 memory, so read the request and process. + // + case IO_TZ_SVCREQ: + // Handle the service request. + // + processServiceRequest(); + break; + + default: + break; + } + } + + // Indicate the thread is free. + // + G.ctrlThreadBusy = 0; + threads.delay(100); + threads.yield(); + } +} +#endif + // Interactive command processor. Allow user to input a command and execute accordingly. // int cmdProcessor(void) @@ -273,7 +337,22 @@ int cmdProcessor(void) } else { diskInitialised = 1; - fsInitialised = 1; + fsInitialised = 1; + + #if defined __TRANZPUTER__ + // Setup the tranZPUter ready for action! + setupTranZPUter(); + + // Setup memory on Z80 to default. + loadTranZPUterDefaultROMS(); + + // Cache initial directory. + svcCacheDir(TZSVC_DEFAULT_DIR, 1); + + // For the tranZPUter, once we know that an SD card is available, launch seperate thread to handle hardware and service functionality. + // No SD card = no tranZPUter functionality. + G.ctrlThreadId = threads.addThread(tranZPUterControl, 0, 16384); + #endif } #endif @@ -628,6 +707,18 @@ int cmdProcessor(void) if(diskInitialised && fsInitialised && strlen(src1FileName) < 16) { + #if defined __TRANZPUTER__ + // If running on the tranZPUter, suspend the control thread if we are to run a tranZPUter applet to prevent clashes with hardware. + // + if(src1FileName[0] == 't' && src1FileName[1] == 'z') + { + // Wait until the control thread has completed a loop then suspend thread for the duration of the app run to avoid clash for resources. + // + while(G.ctrlThreadBusy == 1); + threads.suspend(G.ctrlThreadId); + } + #endif + // The user normally just types the command, but it is possible to type the drive and or path and or extension, so cater // for these possibilities by trial. An alternate way is to disect the entered command but I think this would take more code space. trying = 1; @@ -660,6 +751,7 @@ int cmdProcessor(void) #if defined __ZPU__ retCode = fileExec(&line[40], APP_CMD_LOAD_ADDR, APP_CMD_EXEC_ADDR, EXEC_MODE_CALL, (uint32_t)ptr, (uint32_t)cmdline, (uint32_t)&G, (uint32_t)&cfgSoC); #else + //printf("%s,%08lx,%08lx,%d,%s,%s\n", &line[40], APP_CMD_LOAD_ADDR, APP_CMD_EXEC_ADDR, EXEC_MODE_CALL, ptr, cmdline); retCode = fileExec(&line[40], APP_CMD_LOAD_ADDR, APP_CMD_EXEC_ADDR, EXEC_MODE_CALL, (uint32_t)ptr, (uint32_t)cmdline, (uint32_t)&G, (uint32_t)&cfgSoC); #endif @@ -671,6 +763,11 @@ int cmdProcessor(void) trying = 0; } } + + #if defined __TRANZPUTER__ + // Restart the suspended control thread, the application should have left the + threads.restart(G.ctrlThreadId); + #endif } if(!diskInitialised || !fsInitialised || retCode == 0xffffffff) { diff --git a/zOS/src/zOS_app.h b/zOS/src/zOS_app.h index 72dc472..75f2ca6 100644 --- a/zOS/src/zOS_app.h +++ b/zOS/src/zOS_app.h @@ -39,13 +39,17 @@ // Global parameters accessible in applications. typedef struct { - uint8_t fileInUse; /* Flag to indicate if file[0] is in use. */ - FIL File[MAX_FILE_HANDLE]; /* Maximum open file objects */ - FATFS FatFs[FF_VOLUMES]; /* Filesystem object for each logical drive */ - BYTE Buff[512]; /* Working buffer */ - DWORD Sector; /* Sector to read */ + uint8_t fileInUse; // Flag to indicate if file[0] is in use. + FIL File[MAX_FILE_HANDLE]; // Maximum open file objects + FATFS FatFs[FF_VOLUMES]; // Filesystem object for each logical drive + BYTE Buff[512]; // Working buffer + DWORD Sector; // Sector to read #if defined __K64F__ - uint32_t volatile *millis; /* Pointer to the K64F millisecond tick */ + uint32_t volatile *millis; // Pointer to the K64F millisecond tick + #endif + #if defined __TRANZPUTER__ + int ctrlThreadId; // Id of tranZPUter control thread. + uint8_t ctrlThreadBusy; // Flag to indicate when the control thread cannot be disturbed. #endif } GLOBALS;