Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc040f7f49 | ||
|
|
4196e58420 |
2
projects/tzpuPico/esp32/filepack_version.txt
vendored
2
projects/tzpuPico/esp32/filepack_version.txt
vendored
@@ -1 +1 @@
|
||||
2.43
|
||||
2.49
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
#include "CommandProcessor.h"
|
||||
#include "ipc_protocol.h"
|
||||
#include "esp_attr.h" // RTC_NOINIT_ATTR
|
||||
#include "esp_timer.h"
|
||||
#include "esp_rom_crc.h" // esp_rom_crc32_le for network payload CRC
|
||||
|
||||
// Declared in IO.cpp — set before esp_restart() in the OOB handler so we know
|
||||
// this SW reset was clean (no mid-transaction state) and can use the fast path.
|
||||
@@ -414,6 +416,404 @@ void CommandProcessor::cmdNop(const t_IpcFrameHdr &frame)
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Network command handlers (Celestite W5100 emulation).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void CommandProcessor::cmdNetCfg(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
// Return ESP32's WiFi STA network configuration.
|
||||
t_IpcFrameHdr hdr = {};
|
||||
hdr.frameType = IPCF_TYPE_RESPONSE;
|
||||
hdr.command = frame.command;
|
||||
|
||||
// Get the default STA interface.
|
||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (!netif)
|
||||
{
|
||||
hdr.status = IPCF_STATUS_ERR;
|
||||
hdr.payloadLen = 0;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, IPCF_HEADER_SIZE + IPCF_CRC_SIZE, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t ipInfo;
|
||||
esp_netif_get_ip_info(netif, &ipInfo);
|
||||
uint8_t mac[6];
|
||||
esp_netif_get_mac(netif, mac);
|
||||
|
||||
// Pack: IP[4] + GW[4] + Subnet[4] + MAC[6] = 18 bytes.
|
||||
uint8_t payload[18];
|
||||
memcpy(payload, &ipInfo.ip.addr, 4);
|
||||
memcpy(payload + 4, &ipInfo.gw.addr, 4);
|
||||
memcpy(payload + 8, &ipInfo.netmask.addr, 4);
|
||||
memcpy(payload + 12, mac, 6);
|
||||
|
||||
hdr.status = IPCF_STATUS_OK;
|
||||
hdr.payloadLen = 18;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + 18 + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, payload, 18, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void CommandProcessor::cmdNetSocket(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
t_IpcFrameHdr hdr = {};
|
||||
hdr.frameType = IPCF_TYPE_RESPONSE;
|
||||
hdr.command = frame.command;
|
||||
|
||||
uint8_t sockNum = frame.diskNo;
|
||||
uint8_t op = (uint8_t)(frame.sectorCount & 0xFF);
|
||||
uint8_t protocol = (uint8_t)frame.filename[2];
|
||||
uint16_t port = ((uint8_t)frame.filename[0] << 8) | (uint8_t)frame.filename[1];
|
||||
uint32_t ipAddr = frame.fileOffset;
|
||||
|
||||
if (sockNum >= 4)
|
||||
{
|
||||
hdr.status = IPCF_STATUS_ERR;
|
||||
hdr.payloadLen = 0;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, IPCF_HEADER_SIZE + IPCF_CRC_SIZE, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t resultStatus = 0x00; // SOCK_CLOSED by default.
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case 0x01: // OPEN
|
||||
{
|
||||
int type = (protocol == 1) ? SOCK_STREAM : SOCK_DGRAM;
|
||||
int fd = socket(AF_INET, type, 0);
|
||||
if (fd >= 0)
|
||||
{
|
||||
netSockFd[sockNum] = fd;
|
||||
resultStatus = (protocol == 1) ? 0x13 : 0x22; // SOCK_INIT or SOCK_UDP.
|
||||
ESP_LOGI("NET", "Socket %d opened: fd=%d proto=%d", sockNum, fd, protocol);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE("NET", "Socket %d open failed: errno=%d", sockNum, errno);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x04: // CONNECT
|
||||
{
|
||||
int fd = netSockFd[sockNum];
|
||||
if (fd < 0)
|
||||
break;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = ipAddr; // Already in network byte order.
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
ESP_LOGI("NET", "Socket %d connecting to %d.%d.%d.%d:%d",
|
||||
(int)sockNum, (int)(ipAddr & 0xFF), (int)((ipAddr >> 8) & 0xFF), (int)((ipAddr >> 16) & 0xFF), (int)((ipAddr >> 24) & 0xFF), (int)port);
|
||||
|
||||
// Non-blocking connect with select() timeout to avoid blocking the SPI command loop.
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (ret == 0)
|
||||
{
|
||||
// Immediate connect (localhost or cached).
|
||||
resultStatus = 0x17; // SOCK_ESTABLISHED.
|
||||
ESP_LOGI("NET", "Socket %d connected (immediate)", sockNum);
|
||||
}
|
||||
else if (errno == EINPROGRESS)
|
||||
{
|
||||
// Wait for connection with 5-second timeout via select().
|
||||
fd_set wfds;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(fd, &wfds);
|
||||
struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };
|
||||
int sel = select(fd + 1, NULL, &wfds, NULL, &tv);
|
||||
if (sel > 0)
|
||||
{
|
||||
// Check if connect succeeded.
|
||||
int err = 0;
|
||||
socklen_t errLen = sizeof(err);
|
||||
getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errLen);
|
||||
if (err == 0)
|
||||
{
|
||||
resultStatus = 0x17; // SOCK_ESTABLISHED.
|
||||
ESP_LOGI("NET", "Socket %d connected", sockNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStatus = 0x00;
|
||||
ESP_LOGE("NET", "Socket %d connect error: %d", sockNum, err);
|
||||
close(fd);
|
||||
netSockFd[sockNum] = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStatus = 0x00; // Timeout or error.
|
||||
ESP_LOGE("NET", "Socket %d connect timeout", sockNum);
|
||||
close(fd);
|
||||
netSockFd[sockNum] = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStatus = 0x00; // SOCK_CLOSED on failure.
|
||||
ESP_LOGE("NET", "Socket %d connect failed: errno=%d", sockNum, errno);
|
||||
close(fd);
|
||||
netSockFd[sockNum] = -1;
|
||||
}
|
||||
|
||||
// Restore blocking mode if still open.
|
||||
if (netSockFd[sockNum] >= 0)
|
||||
fcntl(fd, F_SETFL, flags);
|
||||
break;
|
||||
}
|
||||
case 0x02: // LISTEN
|
||||
{
|
||||
int fd = netSockFd[sockNum];
|
||||
if (fd < 0)
|
||||
break;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0 &&
|
||||
listen(fd, 1) == 0)
|
||||
{
|
||||
resultStatus = 0x14; // SOCK_LISTEN.
|
||||
ESP_LOGI("NET", "Socket %d listening on port %d", sockNum, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE("NET", "Socket %d listen failed: errno=%d", sockNum, errno);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x10: // CLOSE
|
||||
case 0x08: // DISCON
|
||||
{
|
||||
int fd = netSockFd[sockNum];
|
||||
if (fd >= 0)
|
||||
{
|
||||
if (op == 0x08)
|
||||
shutdown(fd, SHUT_WR);
|
||||
close(fd);
|
||||
netSockFd[sockNum] = -1;
|
||||
ESP_LOGI("NET", "Socket %d closed", sockNum);
|
||||
}
|
||||
resultStatus = 0x00; // SOCK_CLOSED.
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW("NET", "Unknown socket op: 0x%02X", op);
|
||||
break;
|
||||
}
|
||||
|
||||
// Send 1-byte response with new socket status.
|
||||
hdr.status = IPCF_STATUS_OK;
|
||||
hdr.payloadLen = 1;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + 1 + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, &resultStatus, 1, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void CommandProcessor::cmdNetSend(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
t_IpcFrameHdr hdr = {};
|
||||
hdr.frameType = IPCF_TYPE_RESPONSE;
|
||||
hdr.command = frame.command;
|
||||
|
||||
uint8_t sockNum = frame.diskNo;
|
||||
if (sockNum >= 4 || netSockFd[sockNum] < 0)
|
||||
{
|
||||
hdr.status = IPCF_STATUS_ERR;
|
||||
hdr.payloadLen = 0;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, IPCF_HEADER_SIZE + IPCF_CRC_SIZE, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Receive the T2 write payload (data to send over network).
|
||||
// Inline T2 receive: same logic as SDCard::receiveWritePayload but without private access.
|
||||
uint32_t txLen = frame.payloadLen;
|
||||
bool gotPayload = false;
|
||||
if (txLen > 0 && txLen <= IPCF_MAX_PAYLOAD)
|
||||
{
|
||||
uint32_t frameLen = (txLen + IPCF_CRC_SIZE + 3u) & ~3u;
|
||||
spi_slave_transaction_t t2 = {};
|
||||
t2.length = frameLen * 8;
|
||||
t2.rx_buffer = fspi.ipcCmdBuf;
|
||||
t2.tx_buffer = fspi.ipcRespBuf;
|
||||
if (spi_slave_transmit(FSPI_HOST, &t2, portMAX_DELAY) == ESP_OK)
|
||||
{
|
||||
// Verify CRC32.
|
||||
uint32_t recvCrc, calcCrc;
|
||||
memcpy(&recvCrc, fspi.ipcCmdBuf + txLen, IPCF_CRC_SIZE);
|
||||
calcCrc = esp_rom_crc32_le(0, fspi.ipcCmdBuf, txLen);
|
||||
if (calcCrc == recvCrc)
|
||||
gotPayload = true;
|
||||
else
|
||||
ESP_LOGE("NET", "Send payload CRC mismatch: calc=%08lx recv=%08lx", (unsigned long)calcCrc, (unsigned long)recvCrc);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t sent = 0;
|
||||
if (gotPayload && txLen > 0)
|
||||
{
|
||||
sent = send(netSockFd[sockNum], fspi.ipcCmdBuf, txLen, 0);
|
||||
if (sent < 0)
|
||||
{
|
||||
ESP_LOGE("NET", "Socket %d send failed: errno=%d", sockNum, errno);
|
||||
sent = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI("NET", "Socket %d sent %d bytes", sockNum, (int)sent);
|
||||
}
|
||||
}
|
||||
|
||||
// Response: header + CRC only (no payload).
|
||||
// FSPI_sendBinaryCmd expects write-command responses to have no payload
|
||||
// (it calculates T3 size as IPCF_HEADER_SIZE + CRC_SIZE for write ops).
|
||||
// Including a payload causes CRC offset mismatch.
|
||||
hdr.status = (sent > 0) ? IPCF_STATUS_OK : IPCF_STATUS_ERR;
|
||||
hdr.payloadLen = 0;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void CommandProcessor::cmdNetRecv(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
t_IpcFrameHdr hdr = {};
|
||||
hdr.frameType = IPCF_TYPE_RESPONSE;
|
||||
hdr.command = frame.command;
|
||||
|
||||
uint8_t sockNum = frame.diskNo;
|
||||
if (sockNum >= 4 || netSockFd[sockNum] < 0)
|
||||
{
|
||||
hdr.status = IPCF_STATUS_OK;
|
||||
hdr.payloadLen = 0;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, IPCF_HEADER_SIZE + IPCF_CRC_SIZE, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set non-blocking for poll.
|
||||
int flags = fcntl(netSockFd[sockNum], F_GETFL, 0);
|
||||
fcntl(netSockFd[sockNum], F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
static uint8_t recvBuf[IPCF_MAX_PAYLOAD];
|
||||
uint16_t reqSize = frame.sectorCount;
|
||||
if (reqSize == 0 || reqSize > IPCF_MAX_PAYLOAD)
|
||||
reqSize = IPCF_MAX_PAYLOAD;
|
||||
|
||||
int recvd = recv(netSockFd[sockNum], recvBuf, reqSize, 0);
|
||||
|
||||
// Restore blocking mode.
|
||||
fcntl(netSockFd[sockNum], F_SETFL, flags);
|
||||
|
||||
if (recvd <= 0)
|
||||
{
|
||||
// No data or error — return empty response.
|
||||
// Check if connection was closed by peer.
|
||||
if (recvd == 0)
|
||||
{
|
||||
ESP_LOGI("NET", "Socket %d: peer closed connection", sockNum);
|
||||
}
|
||||
hdr.status = IPCF_STATUS_OK;
|
||||
hdr.payloadLen = 0;
|
||||
// Use flags to signal peer close: flags bit 0 = peer disconnected.
|
||||
if (recvd == 0)
|
||||
hdr.flags = 0x04; // Signal peer close.
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, IPCF_HEADER_SIZE + IPCF_CRC_SIZE, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI("NET", "Socket %d recv %d bytes", sockNum, recvd);
|
||||
hdr.status = IPCF_STATUS_OK;
|
||||
hdr.payloadLen = (uint16_t)recvd;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + recvd + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, recvBuf, recvd, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void CommandProcessor::cmdNetPing(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
t_IpcFrameHdr hdr = {};
|
||||
hdr.frameType = IPCF_TYPE_RESPONSE;
|
||||
hdr.command = frame.command;
|
||||
|
||||
uint32_t targetIp = frame.fileOffset;
|
||||
|
||||
// Use lwIP raw socket for ICMP echo.
|
||||
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
||||
uint32_t rtt = 0xFFFFFFFF; // Timeout sentinel.
|
||||
|
||||
if (sock >= 0)
|
||||
{
|
||||
struct timeval tv = { .tv_sec = 3, .tv_usec = 0 };
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = targetIp;
|
||||
|
||||
// Build ICMP echo request.
|
||||
uint8_t icmpPkt[64];
|
||||
memset(icmpPkt, 0, sizeof(icmpPkt));
|
||||
icmpPkt[0] = 8; // Type: Echo Request.
|
||||
icmpPkt[1] = 0; // Code: 0.
|
||||
icmpPkt[4] = 0x12; // ID high.
|
||||
icmpPkt[5] = 0x34; // ID low.
|
||||
icmpPkt[6] = 0; // Seq high.
|
||||
icmpPkt[7] = 1; // Seq low.
|
||||
// Checksum.
|
||||
uint32_t sum = 0;
|
||||
for (int i = 0; i < 64; i += 2)
|
||||
sum += (icmpPkt[i] << 8) | icmpPkt[i + 1];
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
uint16_t cksum = ~sum;
|
||||
icmpPkt[2] = (uint8_t)(cksum >> 8);
|
||||
icmpPkt[3] = (uint8_t)(cksum & 0xFF);
|
||||
|
||||
int64_t t0 = esp_timer_get_time();
|
||||
int sent = sendto(sock, icmpPkt, 64, 0, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (sent > 0)
|
||||
{
|
||||
uint8_t pingRecvBuf[128];
|
||||
struct sockaddr_in from;
|
||||
socklen_t fromLen = sizeof(from);
|
||||
int pingRecvd = recvfrom(sock, pingRecvBuf, sizeof(pingRecvBuf), 0, (struct sockaddr *)&from, &fromLen);
|
||||
if (pingRecvd > 0)
|
||||
{
|
||||
int64_t t1 = esp_timer_get_time();
|
||||
rtt = (uint32_t)((t1 - t0) / 1000); // Convert us to ms.
|
||||
ESP_LOGI("NET", "Ping %d.%d.%d.%d: RTT=%d ms",
|
||||
(int)(targetIp & 0xFF), (int)((targetIp >> 8) & 0xFF), (int)((targetIp >> 16) & 0xFF), (int)((targetIp >> 24) & 0xFF), (int)rtt);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW("NET", "Ping timeout");
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE("NET", "Cannot create ICMP socket: errno=%d", errno);
|
||||
}
|
||||
|
||||
// Response: no payload (FSPI expects write-style response for non-sector commands).
|
||||
// RTT encoded in sectorCount field (16-bit, ms). 0xFFFF = timeout.
|
||||
hdr.status = (rtt < 0xFFFFFFFF) ? IPCF_STATUS_OK : IPCF_STATUS_ERR;
|
||||
hdr.payloadLen = 0;
|
||||
hdr.sectorCount = (uint16_t)(rtt < 0xFFFF ? rtt : 0xFFFF);
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// start — spawn the waitForCommand task.
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -471,6 +871,24 @@ void CommandProcessor::processCommand(const t_IpcFrameHdr &frame)
|
||||
case IPCF_CMD_INF:
|
||||
cmdReadInfo(frame);
|
||||
break;
|
||||
case IPCF_CMD_DIR:
|
||||
cmdReadDir(frame);
|
||||
break;
|
||||
case IPCF_CMD_NET_CFG:
|
||||
cmdNetCfg(frame);
|
||||
break;
|
||||
case IPCF_CMD_NET_SOCK:
|
||||
cmdNetSocket(frame);
|
||||
break;
|
||||
case IPCF_CMD_NET_SEND:
|
||||
cmdNetSend(frame);
|
||||
break;
|
||||
case IPCF_CMD_NET_RECV:
|
||||
cmdNetRecv(frame);
|
||||
break;
|
||||
case IPCF_CMD_NET_PING:
|
||||
cmdNetPing(frame);
|
||||
break;
|
||||
default:
|
||||
cmdUnknown(frame);
|
||||
break;
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
@@ -56,6 +58,13 @@
|
||||
#include "esp_vfs_fat.h"
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include "lwip/sockets.h"
|
||||
#include "esp_netif.h"
|
||||
#include "FSPI.h"
|
||||
#include "WiFi.h"
|
||||
#include "cJSON.h"
|
||||
@@ -85,6 +94,7 @@ class CommandProcessor : public WiFi, FSPI, SDCard
|
||||
CommandProcessor(WiFi &wifi, FSPI &fspi, SDCard &sdcard, cJSON *config) : wifi(wifi), fspi(fspi), sdcard(sdcard)
|
||||
{
|
||||
(void) config;
|
||||
for (int i = 0; i < 4; i++) netSockFd[i] = -1;
|
||||
}
|
||||
|
||||
const char *getClassName(const std::string &prettyFunction)
|
||||
@@ -176,6 +186,65 @@ class CommandProcessor : public WiFi, FSPI, SDCard
|
||||
}
|
||||
}
|
||||
|
||||
void cmdReadDir(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
// Build a directory listing as text and send back as payload.
|
||||
// The path is in frame.filename (relative to SD card mount point).
|
||||
char fullPath[256];
|
||||
if (frame.filename[0] == '\0')
|
||||
snprintf(fullPath, sizeof(fullPath), "%s", SD_CARD_MOUNT_POINT);
|
||||
else
|
||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", SD_CARD_MOUNT_POINT, frame.filename);
|
||||
|
||||
// Remove trailing slash if present (except for mount root).
|
||||
size_t plen = strlen(fullPath);
|
||||
if (plen > strlen(SD_CARD_MOUNT_POINT) && fullPath[plen - 1] == '/')
|
||||
fullPath[plen - 1] = '\0';
|
||||
|
||||
ESP_LOGI("CMDPROC", "DIR: '%s' → '%s'", frame.filename, fullPath);
|
||||
DIR *dir = opendir(fullPath);
|
||||
t_IpcFrameHdr hdr = {};
|
||||
hdr.frameType = IPCF_TYPE_RESPONSE;
|
||||
hdr.command = frame.command;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
hdr.status = IPCF_STATUS_ERR;
|
||||
hdr.payloadLen = 0;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build listing into a buffer. Use the DMA TX buffer scratch area.
|
||||
static char dirBuf[IPCF_MAX_PAYLOAD];
|
||||
int pos = 0;
|
||||
struct dirent *entry;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL && pos < (int) sizeof(dirBuf) - 80)
|
||||
{
|
||||
// Stat the entry for size and type.
|
||||
char entryPath[512];
|
||||
struct stat st;
|
||||
snprintf(entryPath, sizeof(entryPath), "%.255s/%.255s", fullPath, entry->d_name);
|
||||
bool isDir = (entry->d_type == DT_DIR);
|
||||
long sz = 0;
|
||||
if (!isDir && stat(entryPath, &st) == 0)
|
||||
sz = (long) st.st_size;
|
||||
|
||||
if (isDir)
|
||||
pos += snprintf(dirBuf + pos, sizeof(dirBuf) - pos, " <DIR> %s\n", entry->d_name);
|
||||
else
|
||||
pos += snprintf(dirBuf + pos, sizeof(dirBuf) - pos, " %7ld %s\n", sz, entry->d_name);
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
hdr.status = IPCF_STATUS_OK;
|
||||
hdr.payloadLen = (uint16_t) pos;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + pos + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, (uint8_t *) dirBuf, pos, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void cmdUnknown(const t_IpcFrameHdr &frame)
|
||||
{
|
||||
ESP_LOGE("CMDPROC", "Unknown binary cmd opcode: %02X", frame.command);
|
||||
@@ -188,5 +257,14 @@ class CommandProcessor : public WiFi, FSPI, SDCard
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + IPCF_CRC_SIZE;
|
||||
fspi.sendBinaryResp(&hdr, NULL, 0, respSize, portMAX_DELAY);
|
||||
}
|
||||
|
||||
// Network command handlers (Celestite W5100 emulation).
|
||||
void cmdNetCfg(const t_IpcFrameHdr &frame);
|
||||
void cmdNetSocket(const t_IpcFrameHdr &frame);
|
||||
void cmdNetSend(const t_IpcFrameHdr &frame);
|
||||
void cmdNetRecv(const t_IpcFrameHdr &frame);
|
||||
void cmdNetPing(const t_IpcFrameHdr &frame);
|
||||
|
||||
int netSockFd[4]; // BSD socket file descriptors, one per W5100 socket.
|
||||
};
|
||||
#endif // CMDPROC_H
|
||||
|
||||
@@ -48,6 +48,14 @@
|
||||
#define IPCF_CMD_RFD 0x08 // Read floppy disk image file
|
||||
#define IPCF_CMD_RQD 0x09 // Read quick-disk image file
|
||||
#define IPCF_CMD_RRF 0x0A // Read RAM-file backup image
|
||||
#define IPCF_CMD_DIR 0x0B // Read directory listing (returns text in payload)
|
||||
|
||||
// Network commands (Celestite W5100 emulation)
|
||||
#define IPCF_CMD_NET_CFG 0x10 // Get ESP32 network config (IP, gateway, subnet, MAC)
|
||||
#define IPCF_CMD_NET_SOCK 0x11 // Socket operation (open/connect/listen/close/discon)
|
||||
#define IPCF_CMD_NET_SEND 0x12 // Send data to socket (payload = data to send)
|
||||
#define IPCF_CMD_NET_RECV 0x13 // Receive data from socket (response payload = data)
|
||||
#define IPCF_CMD_NET_PING 0x14 // ICMP echo request (ping)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Status codes (t_IpcFrameHdr::status — response frames only)
|
||||
|
||||
@@ -362,6 +362,19 @@ static esp_err_t usb_ncm_recv_callback(void *buffer, uint16_t len, void *ctx)
|
||||
// CommandProcessor (SPI slave) without waiting for USB delays. The 2-second
|
||||
// disconnect/reconnect cycle that macOS needs would otherwise block SPI
|
||||
// slave init, causing the RP2350's first sector reads to fail.
|
||||
// Maximum number of disconnect/connect cycles to attempt before giving up.
|
||||
static constexpr int USB_NCM_MAX_RETRIES = 3;
|
||||
// Time to hold the bus disconnected so the host tears down its NCM driver.
|
||||
static constexpr int USB_NCM_DISCONNECT_MS = 2500;
|
||||
// Time to wait after tud_connect() for the host to begin enumeration.
|
||||
static constexpr int USB_NCM_CONNECT_SETTLE_MS = 500;
|
||||
// Polling interval while waiting for tud_mounted().
|
||||
static constexpr int USB_NCM_POLL_MS = 100;
|
||||
// Maximum time to wait for tud_mounted() after a connect before retrying.
|
||||
static constexpr int USB_NCM_MOUNT_TIMEOUT_MS = 5000;
|
||||
// Time to wait after mount for the host DHCP client to obtain its IP.
|
||||
static constexpr int USB_NCM_DHCP_WAIT_MS = 3000;
|
||||
|
||||
void setupUSBTask(void *pvParameters)
|
||||
{
|
||||
bool cdcOk = false;
|
||||
@@ -382,16 +395,50 @@ void setupUSBTask(void *pvParameters)
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Force a clean USB disconnect/reconnect cycle. These delays run in this
|
||||
// background task so the main task is free to start the CommandProcessor.
|
||||
// The disconnect duration must be long enough for the host OS to fully
|
||||
// tear down its NCM driver; the post-connect wait allows the host to
|
||||
// enumerate the device and start its DHCP client. Shorter timings cause
|
||||
// intermittent failures where the host never brings the link up.
|
||||
tud_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
tud_connect();
|
||||
vTaskDelay(pdMS_TO_TICKS(1500));
|
||||
// 2. Disconnect/reconnect with retry loop. macOS in particular can fail to
|
||||
// enumerate an NCM device on the first attempt after a long power-off period
|
||||
// because stale USB driver state persists in the kernel. Rather than using
|
||||
// fixed blind delays, we poll tud_mounted() to confirm the host has actually
|
||||
// completed enumeration, and retry the full cycle if it hasn't.
|
||||
bool mounted = false;
|
||||
for (int attempt = 1; attempt <= USB_NCM_MAX_RETRIES && !mounted; attempt++)
|
||||
{
|
||||
ESP_LOGI(MAINTAG, "USB: disconnect/connect attempt %d/%d", attempt, USB_NCM_MAX_RETRIES);
|
||||
|
||||
// Disconnect — hold long enough for the host to fully tear down.
|
||||
tud_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(USB_NCM_DISCONNECT_MS));
|
||||
|
||||
// Reconnect and let the bus settle.
|
||||
tud_connect();
|
||||
vTaskDelay(pdMS_TO_TICKS(USB_NCM_CONNECT_SETTLE_MS));
|
||||
|
||||
// Poll tud_mounted() — the host has completed enumeration when this returns true.
|
||||
int waited = 0;
|
||||
while (waited < USB_NCM_MOUNT_TIMEOUT_MS)
|
||||
{
|
||||
if (tud_mounted())
|
||||
{
|
||||
mounted = true;
|
||||
ESP_LOGI(MAINTAG, "USB: host enumerated device after %d ms (attempt %d)", waited, attempt);
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(USB_NCM_POLL_MS));
|
||||
waited += USB_NCM_POLL_MS;
|
||||
}
|
||||
|
||||
if (!mounted)
|
||||
{
|
||||
ESP_LOGW(MAINTAG, "USB: mount timeout after %d ms (attempt %d), %s",
|
||||
USB_NCM_MOUNT_TIMEOUT_MS, attempt,
|
||||
attempt < USB_NCM_MAX_RETRIES ? "retrying..." : "giving up");
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted)
|
||||
{
|
||||
ESP_LOGE(MAINTAG, "USB: host did not enumerate device after %d attempts", USB_NCM_MAX_RETRIES);
|
||||
}
|
||||
|
||||
// 3. Initialise CDC-ACM for serial logging.
|
||||
tinyusb_config_cdcacm_t acm_cfg = {};
|
||||
@@ -444,6 +491,15 @@ void setupUSBTask(void *pvParameters)
|
||||
esp_netif_dhcps_option(s_usb_ncm_netif, ESP_NETIF_OP_SET,
|
||||
ESP_NETIF_IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
|
||||
esp_netif_action_start(s_usb_ncm_netif, 0, 0, 0);
|
||||
|
||||
// Notify the USB host that the NCM network link is up. Without this
|
||||
// the host never receives a ConnectionSpeedChange notification and will
|
||||
// not start its DHCP client, leaving the interface in "no IP" state.
|
||||
if (mounted) {
|
||||
vTaskDelay(pdMS_TO_TICKS(200)); // Let netif settle before signalling host.
|
||||
tud_network_link_state(0, true);
|
||||
ESP_LOGI(MAINTAG, "USB NCM: link state set to UP");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,15 +508,27 @@ void setupUSBTask(void *pvParameters)
|
||||
esp_tusb_init_console(TINYUSB_CDC_ACM_0);
|
||||
}
|
||||
|
||||
// 7. Allow time for the host to complete DHCP and bring the link up before
|
||||
// signalling ready. Without this the webserver may start before the host
|
||||
// has an IP address, causing the first connection attempt to fail.
|
||||
if (ncmOk) {
|
||||
ESP_LOGI(MAINTAG, "USB NCM netif up — waiting for host DHCP...");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
// 7. Wait for the host to complete DHCP and bring the network link up.
|
||||
// Poll esp_netif_is_netif_up() rather than using a blind delay so we
|
||||
// proceed as soon as the link is ready (or time out gracefully).
|
||||
if (ncmOk && mounted && s_usb_ncm_netif != NULL) {
|
||||
ESP_LOGI(MAINTAG, "USB NCM: waiting for host DHCP...");
|
||||
int dhcpWait = 0;
|
||||
while (dhcpWait < USB_NCM_DHCP_WAIT_MS) {
|
||||
if (esp_netif_is_netif_up(s_usb_ncm_netif)) {
|
||||
ESP_LOGI(MAINTAG, "USB NCM: netif is up after %d ms", dhcpWait);
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(USB_NCM_POLL_MS));
|
||||
dhcpWait += USB_NCM_POLL_MS;
|
||||
}
|
||||
// Allow a little extra time for the host DHCP client to finish even
|
||||
// after the interface reports up.
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
|
||||
ESP_LOGI(MAINTAG, "USB setup complete (CDC:%s NCM:%s)", cdcOk ? "OK" : "FAIL", ncmOk ? "OK" : "FAIL");
|
||||
ESP_LOGI(MAINTAG, "USB setup complete (CDC:%s NCM:%s mounted:%s)",
|
||||
cdcOk ? "OK" : "FAIL", ncmOk ? "OK" : "FAIL", mounted ? "YES" : "NO");
|
||||
g_usbReady = true;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
2
projects/tzpuPico/esp32/version.txt
vendored
2
projects/tzpuPico/esp32/version.txt
vendored
@@ -1 +1 @@
|
||||
2.54
|
||||
2.66
|
||||
|
||||
@@ -316,30 +316,33 @@ var ConfigGUI = (function($) {
|
||||
// Driver and Interface Definitions
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
var knownDrivers = ['MZ700', 'MZ80A', 'MZ80B', 'MZ2000', 'MZ1500', 'MZ2200', 'MZ2500'];
|
||||
// Known drivers — must match virtualFuncMap[] in Z80CPU.c.
|
||||
var knownDrivers = ['MZ700', 'MZ1500', 'MZ80A', 'MZ2000', 'MZ2200'];
|
||||
|
||||
// ROM constraints per interface: minRom = initial entries, maxRom = hard limit.
|
||||
var interfaceRomLimits = {
|
||||
'RFS': { minRom: 2, maxRom: 4 },
|
||||
'TZFS': { minRom: 1, maxRom: 1 },
|
||||
'MZ-1E05': { minRom: 1, maxRom: 1 },
|
||||
'MZ-1E14': { minRom: 1, maxRom: 1 },
|
||||
'MZ-1E19': { minRom: 0, maxRom: 1 },
|
||||
'MZ-1R12': { minRom: 1, maxRom: 1 },
|
||||
'MZ-1R18': { minRom: 0, maxRom: 0 },
|
||||
'MZ80AFI': { minRom: 1, maxRom: 1 },
|
||||
'MZ-8BFI': { minRom: 0, maxRom: 0 }
|
||||
'MZ-8BFI': { minRom: 0, maxRom: 0 },
|
||||
'MZ-1R23': { minRom: 0, maxRom: 0 },
|
||||
'MZ-1R37': { minRom: 0, maxRom: 0 },
|
||||
'PIO-3034': { minRom: 0, maxRom: 0 },
|
||||
'Celestite': { minRom: 0, maxRom: 0 }
|
||||
};
|
||||
|
||||
// Valid interfaces per driver, derived from firmware source.
|
||||
// Valid interfaces per driver — derived from interfaceFuncMap[] in each
|
||||
// driver source file (src/drivers/Sharp/MZ*.c).
|
||||
var driverInterfaces = {
|
||||
'MZ700': ['RFS', 'MZ-1E05', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18'],
|
||||
'MZ80A': ['RFS', 'MZ80AFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18'],
|
||||
'MZ80B': ['RFS', 'MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18'],
|
||||
'MZ2000': ['RFS', 'MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18'],
|
||||
'MZ1500': ['RFS', 'MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18'],
|
||||
'MZ2200': ['RFS', 'MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18'],
|
||||
'MZ2500': ['RFS', 'MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18']
|
||||
'MZ700': ['RFS', 'MZ-1E05', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18', 'MZ-1R23', 'MZ-1R37', 'PIO-3034', 'Celestite'],
|
||||
'MZ1500': ['RFS', 'MZ-1E05', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18', 'MZ-1R23', 'MZ-1R37', 'PIO-3034', 'Celestite'],
|
||||
'MZ80A': ['RFS', 'MZ80AFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18', 'MZ-1R37', 'PIO-3034'],
|
||||
'MZ2000': ['MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18', 'MZ-1R23', 'MZ-1R37', 'PIO-3034', 'Celestite'],
|
||||
'MZ2200': ['MZ-8BFI', 'MZ-1E14', 'MZ-1E19', 'MZ-1R12', 'MZ-1R18', 'MZ-1R23', 'MZ-1R37', 'PIO-3034', 'Celestite']
|
||||
};
|
||||
|
||||
// Get the ROM limit for a given interface name.
|
||||
@@ -958,15 +961,27 @@ var ConfigGUI = (function($) {
|
||||
// Param (disk/file) Renderer (for interface param)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Per-interface parameter descriptions: paramHints[interfaceName][paramIndex] = hint text.
|
||||
// Shown as placeholder/title on the param value input to guide the user.
|
||||
var paramHints = {
|
||||
'Celestite': {
|
||||
0: 'MZ-1R12 RAM backing file (e.g. ram/CELESTITE_R12.ram)',
|
||||
1: 'MZ-1R37 EMM backing file (e.g. ram/CELESTITE_R37.ram)'
|
||||
},
|
||||
'MZ-1R12': { 0: 'RAM backing file (e.g. ram/RAMBOARD1.ram)' },
|
||||
'MZ-1R23': { 0: 'Kanji ROM file', 1: 'Dictionary ROM file' }
|
||||
};
|
||||
|
||||
// Known parameter types and how to render their value editor.
|
||||
// Each entry: { label, renderValue(val), collectValue($td) }
|
||||
// New parameter types can be added here as the firmware evolves.
|
||||
var paramTypes = {
|
||||
file: {
|
||||
label: 'File',
|
||||
renderValue: function(val) {
|
||||
renderValue: function(val, hint) {
|
||||
var ph = hint ? ' placeholder="' + hint + '" title="' + hint + '"' : '';
|
||||
return '<div class="input-group" style="width:260px;">'
|
||||
+ '<input type="text" data-field="param-value" value="' + (val || '') + '" style="font-size:11px;">'
|
||||
+ '<input type="text" data-field="param-value" value="' + (val || '') + '"' + ph + ' style="font-size:11px;">'
|
||||
+ '<span class="input-group-btn"><button type="button" class="btn btn-default btn-xs" data-action="browse-file-param" title="Browse" style="height:22px;padding:1px 6px;">'
|
||||
+ '<i class="fa fa-folder-open"></i></button></span></div>';
|
||||
},
|
||||
@@ -991,23 +1006,23 @@ var ConfigGUI = (function($) {
|
||||
return paramTypeOrder[0]; // default
|
||||
}
|
||||
|
||||
function renderParamTable(params) {
|
||||
function renderParamTable(params, ifName) {
|
||||
if (!params || params.length === 0) return '';
|
||||
var html = '<table class="table table-condensed table-bordered cfg-param-table" data-section="param">';
|
||||
html += '<thead><tr><th title="Enable this parameter">En</th><th title="Parameter type">Type</th><th title="Parameter value">Value</th><th></th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
html += renderParamRow(params[i]);
|
||||
html += renderParamRow(params[i], ifName, i);
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Always-visible param section with Add button (even when no params exist).
|
||||
function renderParamSection(params) {
|
||||
function renderParamSection(params, ifName) {
|
||||
var html = '<div data-section="params-wrapper" style="margin-top:6px;"><b style="font-size:11px;">Parameters</b>';
|
||||
if (params && params.length > 0) {
|
||||
html += renderParamTable(params);
|
||||
html += renderParamTable(params, ifName);
|
||||
} else {
|
||||
// Empty table ready to receive rows
|
||||
html += '<table class="table table-condensed table-bordered cfg-param-table" data-section="param">';
|
||||
@@ -1019,10 +1034,12 @@ var ConfigGUI = (function($) {
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderParamRow(paramObj) {
|
||||
function renderParamRow(paramObj, ifName, paramIdx) {
|
||||
var pType = detectParamType(paramObj);
|
||||
var pValue = paramObj[pType] || '';
|
||||
var chk = paramObj.enable ? ' checked' : '';
|
||||
// Look up per-interface hint for this param index.
|
||||
var hint = (ifName && paramHints[ifName] && paramHints[ifName][paramIdx]) ? paramHints[ifName][paramIdx] : '';
|
||||
var html = '<tr data-param-type="' + pType + '">';
|
||||
html += '<td style="width:30px;"><input type="checkbox" data-field="enable"' + chk + ' title="Enable this parameter"></td>';
|
||||
html += '<td style="width:80px;"><select data-field="param-type" style="font-size:11px;width:75px;" title="Parameter type">';
|
||||
@@ -1032,7 +1049,7 @@ var ConfigGUI = (function($) {
|
||||
html += '<option value="' + key + '"' + (key === pType ? ' selected' : '') + '>' + pt.label + '</option>';
|
||||
}
|
||||
html += '</select></td>';
|
||||
html += '<td class="cfg-param-value-cell">' + paramTypes[pType].renderValue(pValue) + '</td>';
|
||||
html += '<td class="cfg-param-value-cell">' + paramTypes[pType].renderValue(pValue, hint) + '</td>';
|
||||
html += '<td style="width:30px;"><button type="button" class="btn btn-danger btn-xs cfg-btn-remove" data-action="remove-row"><i class="fa fa-times"></i></button></td>';
|
||||
html += '</tr>';
|
||||
return html;
|
||||
@@ -1099,8 +1116,8 @@ var ConfigGUI = (function($) {
|
||||
html += renderIOMapTable(iface.iomap);
|
||||
}
|
||||
|
||||
// Params - always show section with Add button
|
||||
html += renderParamSection(iface.param);
|
||||
// Params - always show section with Add button (pass interface name for hints).
|
||||
html += renderParamSection(iface.param, iface.name);
|
||||
|
||||
html += '</div></div></div>';
|
||||
return html;
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.43
|
||||
2.49
|
||||
|
||||
6
projects/tzpuPico/src/CMakeLists.txt
vendored
6
projects/tzpuPico/src/CMakeLists.txt
vendored
@@ -15,8 +15,10 @@ set(pZ80_common_src
|
||||
|
||||
set(pZ80_drivers_sharp_src
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ700.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ1500.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ80A.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ2000.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ2200.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/RFS.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/WD1773.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/QDDrive.c
|
||||
@@ -27,6 +29,10 @@ set(pZ80_drivers_sharp_src
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E19.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1R12.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1R18.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1R23.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1R37.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/PIO-3034.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/Celestite.c
|
||||
)
|
||||
|
||||
set(pM6502_common_src
|
||||
|
||||
@@ -462,6 +462,42 @@ int ESP_readBurstSectors(const char *fileName, int filePos, uint8_t *memLocation
|
||||
return (int) payloadLen;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ESP_readDir — read a directory listing from the ESP32 SD card.
|
||||
// path: directory path relative to /sdcard/ (e.g., "roms" or "dsk/MZ2000")
|
||||
// buf: output buffer for null-terminated text listing
|
||||
// bufLen: size of buf
|
||||
// Returns: bytes written to buf (excluding null), or -1 on error.
|
||||
// ---------------------------------------------------------------------------
|
||||
int ESP_readDir(const char *path, char *buf, int bufLen)
|
||||
{
|
||||
uint32_t payloadLen = 0;
|
||||
// Pass path as filename — empty string sends "/" for root.
|
||||
const char *sendPath = (path && path[0]) ? path : "/";
|
||||
|
||||
// Retry up to 3 times — first SPI command after idle can fail.
|
||||
uint8_t *payload = NULL;
|
||||
for (int retry = 0; retry < 3 && !payload; retry++)
|
||||
{
|
||||
payloadLen = 0;
|
||||
payload = FSPI_sendBinaryCmd(
|
||||
IPCF_CMD_DIR, sendPath, 0, 0, 0, (uint8_t) retry, NULL, 0, &payloadLen);
|
||||
}
|
||||
|
||||
if (!payload || payloadLen == 0)
|
||||
return -1;
|
||||
|
||||
t_IpcFrameHdr *resp = (t_IpcFrameHdr *)(payload - IPCF_HEADER_SIZE);
|
||||
if (resp->status != IPCF_STATUS_OK)
|
||||
return -1;
|
||||
|
||||
int copyLen = ((int) payloadLen < (bufLen - 1))
|
||||
? (int) payloadLen : (bufLen - 1);
|
||||
memcpy(buf, payload, copyLen);
|
||||
buf[copyLen] = '\0';
|
||||
return copyLen;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ESP_writeFile — write an entire buffer to a file on the ESP32 SD card.
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -613,3 +649,101 @@ float ESP_version(void)
|
||||
{
|
||||
return ESP_VERSION;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Network functions for Celestite W5100 emulation (Phase 2).
|
||||
// These are called from Core 0 (main loop) to send IPC commands to ESP32.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool ESP_netCfg(uint8_t *ipOut, uint8_t *gwOut, uint8_t *subnetOut, uint8_t *macOut)
|
||||
{
|
||||
uint32_t payloadLen = 0;
|
||||
uint8_t *payload = FSPI_sendBinaryCmd(IPCF_CMD_NET_CFG, "", 0, 0, 0, gIpcSeq++, NULL, 0, &payloadLen);
|
||||
if (payload == NULL || payloadLen < 18)
|
||||
return false;
|
||||
// Response payload: IP[4] + GW[4] + Subnet[4] + MAC[6] = 18 bytes.
|
||||
if (ipOut) memcpy(ipOut, payload, 4);
|
||||
if (gwOut) memcpy(gwOut, payload + 4, 4);
|
||||
if (subnetOut) memcpy(subnetOut, payload + 8, 4);
|
||||
if (macOut) memcpy(macOut, payload + 12, 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP_netSocket(uint8_t sockNum, uint8_t operation, uint8_t protocol, uint32_t ipAddr, uint16_t port, uint8_t *statusOut)
|
||||
{
|
||||
// Pack params into IPC frame fields:
|
||||
// diskNo=sockNum, sectorCount=operation, fileOffset=ipAddr
|
||||
// filename[0..1]=port (big-endian), filename[2]=protocol
|
||||
char params[IPCF_FILENAME_LEN];
|
||||
memset(params, 0, sizeof(params));
|
||||
params[0] = (char)(port >> 8);
|
||||
params[1] = (char)(port & 0xFF);
|
||||
params[2] = (char)protocol;
|
||||
|
||||
uint32_t payloadLen = 0;
|
||||
uint8_t *payload = FSPI_sendBinaryCmd(IPCF_CMD_NET_SOCK, params, ipAddr, operation, sockNum, gIpcSeq++, NULL, 0, &payloadLen);
|
||||
if (payload == NULL)
|
||||
return false;
|
||||
// Response payload: 1 byte = new socket status.
|
||||
if (statusOut && payloadLen >= 1)
|
||||
*statusOut = payload[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP_netSend(uint8_t sockNum, uint8_t *data, uint32_t len, uint32_t *sentOut)
|
||||
{
|
||||
if (data == NULL || len == 0)
|
||||
return false;
|
||||
// Cap at max payload.
|
||||
if (len > IPCF_MAX_PAYLOAD)
|
||||
len = IPCF_MAX_PAYLOAD;
|
||||
|
||||
uint32_t payloadLen = 0;
|
||||
uint8_t *payload = FSPI_sendBinaryCmd(IPCF_CMD_NET_SEND, "", 0, 0, sockNum, gIpcSeq++, data, len, &payloadLen);
|
||||
if (payload == NULL)
|
||||
return false;
|
||||
// Response payload: 4 bytes = bytes sent (little-endian).
|
||||
if (sentOut && payloadLen >= 4)
|
||||
{
|
||||
*sentOut = payload[0] | (payload[1] << 8) | (payload[2] << 16) | (payload[3] << 24);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP_netRecv(uint8_t sockNum, uint8_t *buf, uint32_t bufSize, uint32_t *recvdOut)
|
||||
{
|
||||
if (buf == NULL || bufSize == 0)
|
||||
return false;
|
||||
|
||||
// Request recv: sectorCount = requested size (capped at max payload).
|
||||
uint16_t reqSize = (uint16_t)(bufSize > IPCF_MAX_PAYLOAD ? IPCF_MAX_PAYLOAD : bufSize);
|
||||
|
||||
uint32_t payloadLen = 0;
|
||||
uint8_t *payload = FSPI_sendBinaryCmd(IPCF_CMD_NET_RECV, "", 0, reqSize, sockNum, gIpcSeq++, NULL, 0, &payloadLen);
|
||||
if (payload == NULL)
|
||||
return false;
|
||||
// Response payload = received data.
|
||||
if (payloadLen > 0 && payloadLen <= bufSize)
|
||||
{
|
||||
memcpy(buf, payload, payloadLen);
|
||||
}
|
||||
if (recvdOut)
|
||||
*recvdOut = payloadLen;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP_netPing(uint32_t ipAddr, uint32_t *rttOut)
|
||||
{
|
||||
uint32_t payloadLen = 0;
|
||||
// fileOffset carries the target IP.
|
||||
uint8_t *payload = FSPI_sendBinaryCmd(IPCF_CMD_NET_PING, "", ipAddr, 0, 0, gIpcSeq++, NULL, 0, &payloadLen);
|
||||
if (payload == NULL)
|
||||
return false;
|
||||
// RTT encoded in response header sectorCount field (no payload).
|
||||
t_IpcFrameHdr *resp = (t_IpcFrameHdr *)(payload - IPCF_HEADER_SIZE);
|
||||
if (rttOut)
|
||||
{
|
||||
*rttOut = resp->sectorCount; // RTT in ms, 0xFFFF = timeout.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -423,8 +423,8 @@ uint8_t *FSPI_sendBinaryCmd(uint8_t opcode,
|
||||
cmd->fileOffset = fileOffset;
|
||||
cmd->diskNo = diskNo;
|
||||
cmd->payloadLen = (uint16_t) (writeData ? writeLen : 0);
|
||||
if (filename && filename[0])
|
||||
strncpy(cmd->filename, filename, IPCF_FILENAME_LEN - 1);
|
||||
if (filename)
|
||||
memcpy(cmd->filename, filename, IPCF_FILENAME_LEN);
|
||||
|
||||
// === Transaction 1: Send 64-byte command header ===
|
||||
// Flush any stale RX FIFO bytes left by a previous failed transaction.
|
||||
@@ -490,7 +490,12 @@ uint8_t *FSPI_sendBinaryCmd(uint8_t opcode,
|
||||
// read commands: header + sectorCount × 512 + CRC32
|
||||
// write commands: header + CRC32 (just status, no payload returned)
|
||||
// INF command: header + CRC32
|
||||
uint32_t respPayload = (writeData == NULL && sectorCount > 0) ? (uint32_t) sectorCount * IPCF_SECTOR_SIZE : 0;
|
||||
// DIR command: header + variable payload + CRC32 (use max frame size)
|
||||
uint32_t respPayload;
|
||||
if (opcode == IPCF_CMD_DIR)
|
||||
respPayload = IPCF_MAX_PAYLOAD; // variable-length — allocate max
|
||||
else
|
||||
respPayload = (writeData == NULL && sectorCount > 0) ? (uint32_t) sectorCount * IPCF_SECTOR_SIZE : 0;
|
||||
uint32_t respSize = IPCF_HEADER_SIZE + respPayload + IPCF_CRC_SIZE;
|
||||
if (respSize > IPCF_MAX_FRAME_SIZE)
|
||||
respSize = IPCF_MAX_FRAME_SIZE;
|
||||
|
||||
@@ -50,8 +50,10 @@
|
||||
// Include Device drivers.
|
||||
#ifdef INCLUDE_SHARP_DRIVERS
|
||||
#include "drivers/Sharp/MZ700.h" // Sharp MZ700 Persona driver.
|
||||
#include "drivers/Sharp/MZ1500.h" // Sharp MZ-1500 Persona driver.
|
||||
#include "drivers/Sharp/MZ80A.h" // Sharp MZ80A Persona driver.
|
||||
#include "drivers/Sharp/MZ2000.h" // Sharp MZ-2000 Persona driver.
|
||||
#include "drivers/Sharp/MZ2200.h" // Sharp MZ-2200 Persona driver.
|
||||
#endif
|
||||
|
||||
|
||||
@@ -206,10 +208,14 @@ static const t_VirtualFuncMap virtualFuncMap[] = {
|
||||
#ifdef INCLUDE_SHARP_DRIVERS
|
||||
{"MZ700", (t_VirtualFunc) MZ700_Init}, // This virtual function creates a Sharp MZ700 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ700 logic.
|
||||
{"MZ1500", (t_VirtualFunc) MZ1500_Init}, // This virtual function creates a Sharp MZ-1500 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ-1500 logic.
|
||||
{"MZ80A", (t_VirtualFunc) MZ80A_Init}, // This virtual function creates a Sharp MZ80A 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ80A logic.
|
||||
{"MZ2000", (t_VirtualFunc) MZ2000_Init}, // This virtual function creates a Sharp MZ-2000 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ-2000 logic.
|
||||
{"MZ2200", (t_VirtualFunc) MZ2200_Init}, // This virtual function creates a Sharp MZ-2200 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ-2200 logic.
|
||||
#endif
|
||||
};
|
||||
static const size_t virtualFuncMapSize = sizeof(virtualFuncMap) / sizeof(virtualFuncMap[0]);
|
||||
@@ -2560,7 +2566,7 @@ void __func_in_RAM(Z80CPU_task)(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char
|
||||
|
||||
// Method to reset the Z80 CPU.
|
||||
// This method is directly invoked to reset the Z80 Emulation or invoked via the hardware reset line going active low.
|
||||
void __func_in_RAM(Z80CPU_reset)(t_Z80CPU *cpu)
|
||||
void __func_in_RAM(Z80CPU_reset)(t_Z80CPU *cpu, bool physicalReset)
|
||||
{
|
||||
#ifdef DEBUG_PIO
|
||||
debugf("Resetting Z80\n");
|
||||
@@ -2569,21 +2575,24 @@ void __func_in_RAM(Z80CPU_reset)(t_Z80CPU *cpu)
|
||||
// Reset the CPU emulation.
|
||||
z80_instant_reset(&cpu->_Z80);
|
||||
|
||||
// Call driver reset handlers first — they return 1 to allow hardware reset
|
||||
// (default), or 0 to suppress it. Results are AND'd: if ANY driver returns 0,
|
||||
// the hardware reset is suppressed. Drivers running in physical mode on hosts
|
||||
// that generate their own /RESET (e.g., MZ-2000 NST) return 0 to prevent the
|
||||
// picoZ80 from driving /RESET and interfering with the host's reset sequence.
|
||||
uint8_t doHwReset = 1;
|
||||
// Call driver reset handlers to reset driver state. For physical resets,
|
||||
// drivers return 1 to allow hardware reset or 0 to suppress it (e.g., MZ-2000
|
||||
// in physical mode returns 0 because the gate array manages /RESET for
|
||||
// NST/BST transitions). For software resets (web page reboot, dbgsh reset
|
||||
// command), always drive /RESET regardless of driver opinion.
|
||||
uint8_t doHwReset = physicalReset ? 0 : 1;
|
||||
for (int idx = 0; idx < cpu->_drivers.drvCount; idx++)
|
||||
{
|
||||
if (cpu->_drivers.driver[idx].resetPtr != NULL)
|
||||
{
|
||||
doHwReset &= cpu->_drivers.driver[idx].resetPtr(cpu);
|
||||
uint8_t drvResult = cpu->_drivers.driver[idx].resetPtr(cpu);
|
||||
if (physicalReset)
|
||||
doHwReset |= drvResult; // OR: any driver requesting HW reset enables it
|
||||
}
|
||||
}
|
||||
|
||||
// Only drive /RESET on the bus if all drivers agree.
|
||||
// Drive /RESET on the bus for software resets, or if a driver requests it
|
||||
// for physical resets.
|
||||
if (doHwReset)
|
||||
Z80CPU_ForceHostResetActive(cpu, true);
|
||||
|
||||
@@ -2626,7 +2635,10 @@ void __func_in_RAM(Z80CPU_cpu)(t_Z80CPU *cpu)
|
||||
// Notify core 0 than we are configured and running.
|
||||
multicore_fifo_push_blocking(1);
|
||||
|
||||
sleep_ms(2000);
|
||||
// This is a setup delay, basically to allow the ESP32 to come online and serve any floppy/qd images. If
|
||||
// immediate Z80 start is required and your not worried about the Floppy/QD being available immediately,
|
||||
// remote this delay.
|
||||
sleep_ms(250);
|
||||
|
||||
// Loop forever, running Z80 instructions and processing external signals.
|
||||
while (1)
|
||||
@@ -2776,7 +2788,7 @@ void __func_in_RAM(Z80CPU_cpu)(t_Z80CPU *cpu)
|
||||
cpu->_Z80.sp.uint16_value);
|
||||
#endif
|
||||
|
||||
Z80CPU_reset(cpu);
|
||||
Z80CPU_reset(cpu, physicalReset);
|
||||
cpu->forceReset = false;
|
||||
|
||||
if (physicalReset)
|
||||
|
||||
@@ -1327,26 +1327,28 @@ static void cmdSaveHist(t_Z80CPU *cpu, int argc, char **argv)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Command: load — load a file from SD card into memory
|
||||
// load <p|v> <filename> <addr> <len> [fileofs]
|
||||
// load <p|v> <filename> <addr> [len] [fileofs]
|
||||
// p = physical bus write, v = virtual PSRAM (bank 0)
|
||||
// filename is relative to /sdcard/ on the ESP32
|
||||
// len is optional — if omitted, loads the entire file (up to 64KB)
|
||||
// ---------------------------------------------------------------------------
|
||||
static void cmdLoad(t_Z80CPU *cpu, int argc, char **argv)
|
||||
{
|
||||
if (!cpu) { shPuts("CPU not initialised.\r\n"); return; }
|
||||
|
||||
if (argc < 5)
|
||||
if (argc < 4)
|
||||
{
|
||||
shPuts("Usage: load <p|v> <filename> <addr> <len> [fileofs]\r\n"
|
||||
shPuts("Usage: load <p|v> <filename> <addr> [len] [fileofs]\r\n"
|
||||
" p = physical bus write, v = virtual PSRAM bank 0\r\n"
|
||||
" filename relative to /sdcard/ on ESP32\r\n");
|
||||
" filename relative to /sdcard/ on ESP32\r\n"
|
||||
" len optional — omit to load entire file (max 64KB)\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bool physical = (argv[1][0] == 'p' || argv[1][0] == 'P');
|
||||
const char *fileName = argv[2];
|
||||
uint32_t addr = (uint32_t) strtoul(argv[3], NULL, 0);
|
||||
uint32_t len = (uint32_t) strtoul(argv[4], NULL, 0);
|
||||
uint32_t len = (argc >= 5) ? (uint32_t) strtoul(argv[4], NULL, 0) : 0x10000;
|
||||
int fileOfs = (argc >= 6) ? (int) strtoul(argv[5], NULL, 0) : 0;
|
||||
|
||||
if (len == 0 || len > 0x100000)
|
||||
@@ -1511,6 +1513,51 @@ static void cmdSaveFile(t_Z80CPU *cpu, int argc, char **argv)
|
||||
// Phase 2 — In-Circuit Emulation commands
|
||||
// ===========================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Command: dir — list SD card directory
|
||||
// dir — list root directory
|
||||
// dir <path> — list given path (relative to /sdcard/)
|
||||
// ---------------------------------------------------------------------------
|
||||
static void cmdDir(t_Z80CPU *cpu, int argc, char **argv)
|
||||
{
|
||||
(void) cpu;
|
||||
const char *path = (argc >= 2) ? argv[1] : "";
|
||||
|
||||
// Strip leading '/' — the ESP32 prepends /sdcard/.
|
||||
while (*path == '/')
|
||||
path++;
|
||||
|
||||
static char dirBuf[4096];
|
||||
int len = ESP_readDir(path, dirBuf, sizeof(dirBuf));
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
shPrintf("Failed to read directory '%s'.\r\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
shPrintf("Directory '%s' is empty.\r\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
// Output line by line with \r\n conversion.
|
||||
shPrintf("Directory: /%s\r\n", path);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (dirBuf[i] == '\n')
|
||||
shPuts("\r\n");
|
||||
else
|
||||
shWrite(&dirBuf[i], 1);
|
||||
|
||||
if ((i & 0xFF) == 0xFF)
|
||||
shFlush();
|
||||
}
|
||||
if (len > 0 && dirBuf[len - 1] != '\n')
|
||||
shPuts("\r\n");
|
||||
}
|
||||
|
||||
// Helper: ensure CPU is held before debug operations that inspect state.
|
||||
static bool ensureHeld(t_Z80CPU *cpu)
|
||||
{
|
||||
@@ -3979,8 +4026,9 @@ static const t_DbgShCmd cmdTable[] =
|
||||
{"fdctrace","FDC trace: fdctrace <on|off|dump>", cmdFdcTrace},
|
||||
{"qdtrace", "QD trace: qdtrace <on|off|dump>", cmdQdTrace},
|
||||
{"piodbg", "RP2350 PIO diagnostics: piodbg [clear]", cmdPiodbg},
|
||||
{"load", "Load file: load <p|v> <file> <addr> <len> [ofs]", cmdLoad},
|
||||
{"load", "Load file: load <p|v> <file> <addr> [len] [ofs]", cmdLoad},
|
||||
{"save", "Save mem: save <p|pf|v> <file> <addr> <len>", cmdSaveFile},
|
||||
{"dir", "List SD card directory: dir [path]", cmdDir},
|
||||
{"echo", "Toggle character echo [on|off]", cmdEcho},
|
||||
{"hist", "Show command history: hist [n]", cmdHist},
|
||||
{"savehst", "Force-save history to ESP32 now", cmdSaveHist},
|
||||
|
||||
1133
projects/tzpuPico/src/drivers/Sharp/Celestite.c
Normal file
1133
projects/tzpuPico/src/drivers/Sharp/Celestite.c
Normal file
File diff suppressed because it is too large
Load Diff
379
projects/tzpuPico/src/drivers/Sharp/MZ-1R23.c
Normal file
379
projects/tzpuPico/src/drivers/Sharp/MZ-1R23.c
Normal file
@@ -0,0 +1,379 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ-1R23.c
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - MZ-1R23 Kanji ROM / MZ-1R24 Dictionary ROM Board
|
||||
// This file contains setup and driver to emulate the MZ-1R23 Kanji ROM board and
|
||||
// the MZ-1R24 Dictionary ROM board. The two boards share the same I/O interface
|
||||
// (B8h control, B9h address/data) and can coexist — the KANJI bit in the control
|
||||
// register selects which ROM is active.
|
||||
//
|
||||
// ROM files are loaded from the ESP32 SD card at init time. The JSON config
|
||||
// specifies filenames via ifParam entries:
|
||||
// params[0].file = kanji ROM filename (e.g. "MZ-1R23.ROM")
|
||||
// params[1].file = dictionary ROM filename (optional, e.g. "MZ-1R24.ROM")
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Z80CPU.h"
|
||||
#define Z80_STATIC
|
||||
#include "Z80.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pico/printf.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include "pico/util/queue.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "intercore.h"
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/MZ-1R23.h"
|
||||
#include "debug.h"
|
||||
|
||||
t_MZ1R23 *mz1r23Ctrl; // Control structure for the MZ-1R23/MZ-1R24 board.
|
||||
|
||||
// Reverse bits in a byte (for ENDIAN mode).
|
||||
static inline uint8_t reverseBits(uint8_t b)
|
||||
{
|
||||
b = (uint8_t)(((b & 0xF0) >> 4) | ((b & 0x0F) << 4));
|
||||
b = (uint8_t)(((b & 0xCC) >> 2) | ((b & 0x33) << 2));
|
||||
b = (uint8_t)(((b & 0xAA) >> 1) | ((b & 0x55) << 1));
|
||||
return b;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Interface: Sharp MZ-1R23 Kanji ROM Board / MZ-1R24 Dictionary ROM Board
|
||||
// Description: Read-only ROM boards accessed via I/O ports B8h (control) and B9h (address/data).
|
||||
// Kanji ROM provides 16x16 JIS kanji bitmaps (32 bytes per character).
|
||||
// Dictionary ROM provides up to 4 × 64KB banks of data.
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
// Method to initialise the CPU state to support an MZ-1R23/MZ-1R24 interface.
|
||||
uint8_t MZ1R23_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Interface logic only for virtual device. Physical device doesnt need configuration.
|
||||
if (config->isPhysical)
|
||||
return (0);
|
||||
|
||||
// Only one instance allowed.
|
||||
if (mz1r23Ctrl != NULL)
|
||||
return (0);
|
||||
|
||||
// Allocate control structure.
|
||||
mz1r23Ctrl = (t_MZ1R23 *) calloc(1, sizeof(t_MZ1R23));
|
||||
if (!mz1r23Ctrl)
|
||||
{
|
||||
debugf("MZ-1R23: Failed to allocate control structure\r\n");
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Initialise state.
|
||||
mz1r23Ctrl->ctrlReg = MZ1R23_CTRL_KANJI; // Default to Kanji mode.
|
||||
mz1r23Ctrl->readAddr = 0;
|
||||
|
||||
// Load ROM files from SD card if specified in the configuration.
|
||||
// params[0] = kanji ROM file, params[1] = dictionary ROM file (optional).
|
||||
for (int paramIdx = 0; paramIdx < config->ifParamCount && paramIdx < 2; paramIdx++)
|
||||
{
|
||||
if (config->ifParam[paramIdx].file == NULL || config->ifParam[paramIdx].file[0] == '\0')
|
||||
continue;
|
||||
|
||||
if (paramIdx == 0)
|
||||
{
|
||||
// Kanji ROM.
|
||||
mz1r23Ctrl->kanjiRom = (uint8_t *) calloc(1, MZ1R23_KANJI_ROM_MAX);
|
||||
if (mz1r23Ctrl->kanjiRom == NULL)
|
||||
{
|
||||
debugf("MZ-1R23: Failed to allocate kanji ROM buffer (%d bytes)\r\n", MZ1R23_KANJI_ROM_MAX);
|
||||
continue;
|
||||
}
|
||||
mz1r23Ctrl->kanjiRomSize = MZ1R23_KANJI_ROM_MAX;
|
||||
mz1r23Ctrl->kanjiFileName = strdup(config->ifParam[paramIdx].file);
|
||||
mz1r23Ctrl->kanjiLoadCtl = MZ1R23_NOT_LOADED;
|
||||
mz1r23Ctrl->kanjiLoadPending = true;
|
||||
|
||||
// Queue inter-core load request.
|
||||
t_CoreMsg msg = {.type = MSG_LOAD_RAMFILE, .context = mz1r23Ctrl, .requestId = 0};
|
||||
strncpy(msg.fileOp.filename, mz1r23Ctrl->kanjiFileName, MAX_IC_FILENAME_LEN - 1);
|
||||
msg.fileOp.buffer = mz1r23Ctrl->kanjiRom;
|
||||
msg.fileOp.size = MZ1R23_KANJI_ROM_MAX;
|
||||
queue_try_add(&cpu->requestQueue, &msg);
|
||||
|
||||
debugf("MZ-1R23: Queued kanji ROM load: %s\r\n", mz1r23Ctrl->kanjiFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dictionary ROM.
|
||||
mz1r23Ctrl->dictRom = (uint8_t *) calloc(1, MZ1R23_DICT_ROM_MAX);
|
||||
if (mz1r23Ctrl->dictRom == NULL)
|
||||
{
|
||||
debugf("MZ-1R24: Failed to allocate dictionary ROM buffer (%d bytes)\r\n", MZ1R23_DICT_ROM_MAX);
|
||||
continue;
|
||||
}
|
||||
mz1r23Ctrl->dictRomSize = MZ1R23_DICT_ROM_MAX;
|
||||
mz1r23Ctrl->dictFileName = strdup(config->ifParam[paramIdx].file);
|
||||
mz1r23Ctrl->dictLoadCtl = MZ1R23_NOT_LOADED;
|
||||
mz1r23Ctrl->dictLoadPending = true;
|
||||
|
||||
// Queue inter-core load request.
|
||||
t_CoreMsg msg = {.type = MSG_LOAD_RAMFILE, .context = mz1r23Ctrl, .requestId = 1};
|
||||
strncpy(msg.fileOp.filename, mz1r23Ctrl->dictFileName, MAX_IC_FILENAME_LEN - 1);
|
||||
msg.fileOp.buffer = mz1r23Ctrl->dictRom;
|
||||
msg.fileOp.size = MZ1R23_DICT_ROM_MAX;
|
||||
queue_try_add(&cpu->requestQueue, &msg);
|
||||
|
||||
debugf("MZ-1R24: Queued dictionary ROM load: %s\r\n", mz1r23Ctrl->dictFileName);
|
||||
}
|
||||
}
|
||||
|
||||
// Install I/O handlers.
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
// B8h — Control register (write only).
|
||||
if ((idx & 0x00ff) == 0xB8)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ1R23_IO_Ctrl;
|
||||
// B9h — Address write / Data read.
|
||||
if ((idx & 0x00ff) == 0xB9)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ1R23_IO_Data;
|
||||
}
|
||||
|
||||
debugf("MZ-1R23: Initialised (kanji=%s, dict=%s)\r\n",
|
||||
mz1r23Ctrl->kanjiFileName ? mz1r23Ctrl->kanjiFileName : "none",
|
||||
mz1r23Ctrl->dictFileName ? mz1r23Ctrl->dictFileName : "none");
|
||||
|
||||
result = 1;
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Reset handler.
|
||||
uint8_t MZ1R23_Reset(t_Z80CPU *cpu)
|
||||
{
|
||||
(void)cpu;
|
||||
|
||||
if (mz1r23Ctrl)
|
||||
{
|
||||
mz1r23Ctrl->ctrlReg = MZ1R23_CTRL_KANJI;
|
||||
mz1r23Ctrl->readAddr = 0;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Poll handler — drain any pending inter-core load responses.
|
||||
uint8_t __func_in_RAM(MZ1R23_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
if (mz1r23Ctrl && (mz1r23Ctrl->kanjiLoadPending || mz1r23Ctrl->dictLoadPending))
|
||||
{
|
||||
MZ1R23_ProcessQueueResponses(cpu);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Process inter-core queue responses for pending ROM loads.
|
||||
int MZ1R23_ProcessQueueResponses(t_Z80CPU *cpu)
|
||||
{
|
||||
int processedCount = 0;
|
||||
|
||||
if (cpu == NULL || mz1r23Ctrl == NULL)
|
||||
return (-1);
|
||||
|
||||
while (true)
|
||||
{
|
||||
t_CoreMsg response;
|
||||
if (!queue_try_remove(&cpu->responseQueue, &response))
|
||||
break;
|
||||
|
||||
// If the response is not for us, push it back.
|
||||
if (response.context != (void *) mz1r23Ctrl)
|
||||
{
|
||||
queue_add_blocking(&cpu->responseQueue, &response);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (response.type)
|
||||
{
|
||||
case MSG_LOAD_COMPLETE:
|
||||
if (response.requestId == 0)
|
||||
{
|
||||
// Kanji ROM load complete.
|
||||
mz1r23Ctrl->kanjiLoadPending = false;
|
||||
if (response.response.success)
|
||||
{
|
||||
mz1r23Ctrl->kanjiLoadCtl = MZ1R23_LOADED;
|
||||
debugf("MZ-1R23: Kanji ROM loaded (%d bytes)\r\n", (int)response.response.size);
|
||||
if (response.response.size < mz1r23Ctrl->kanjiRomSize)
|
||||
mz1r23Ctrl->kanjiRomSize = response.response.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
debugf("MZ-1R23: Kanji ROM load FAILED\r\n");
|
||||
}
|
||||
}
|
||||
else if (response.requestId == 1)
|
||||
{
|
||||
// Dictionary ROM load complete.
|
||||
mz1r23Ctrl->dictLoadPending = false;
|
||||
if (response.response.success)
|
||||
{
|
||||
mz1r23Ctrl->dictLoadCtl = MZ1R23_LOADED;
|
||||
debugf("MZ-1R24: Dictionary ROM loaded (%d bytes)\r\n", (int)response.response.size);
|
||||
if (response.response.size < mz1r23Ctrl->dictRomSize)
|
||||
mz1r23Ctrl->dictRomSize = response.response.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
debugf("MZ-1R24: Dictionary ROM load FAILED\r\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
processedCount++;
|
||||
}
|
||||
return processedCount;
|
||||
}
|
||||
|
||||
// Task processor — no external tasks currently handled.
|
||||
uint8_t MZ1R23_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)param;
|
||||
uint8_t result = 0;
|
||||
|
||||
switch (task)
|
||||
{
|
||||
default:
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for B8h — Control register (write only).
|
||||
// Bit 7: KANJI (1=Kanji ROM, 0=Dictionary ROM)
|
||||
// Bit 6: ENDIAN (1=bit-reversed byte order)
|
||||
// Bits 0-1: BANK (dictionary ROM bank, 00-11)
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(MZ1R23_IO_Ctrl)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)addr;
|
||||
|
||||
if (read)
|
||||
return 0xFF; // Write-only register.
|
||||
|
||||
if (mz1r23Ctrl)
|
||||
{
|
||||
mz1r23Ctrl->ctrlReg = data;
|
||||
mz1r23Ctrl->readAddr = 0; // Changing mode resets the read address.
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for B9h — Address write / Data read.
|
||||
//
|
||||
// Write (via OUT (C),A):
|
||||
// The Z80 B register provides A15-A8 (upper address byte) in the port
|
||||
// address MSB, and the A register provides D7-D0 (lower byte) as data.
|
||||
// - Kanji mode: the 16-bit value is the kanji pattern number.
|
||||
// Internal read address is set to patternNumber × 32.
|
||||
// - Dictionary mode: the 16-bit value is a raw byte address within
|
||||
// the selected bank.
|
||||
//
|
||||
// Read:
|
||||
// Returns the byte at the current read address, then auto-increments.
|
||||
// In Kanji mode, the 32 bytes of a character are read in order:
|
||||
// upper-left (8), lower-left (8), upper-right (8), lower-right (8).
|
||||
// If ENDIAN bit is set, each byte is bit-reversed before returning.
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(MZ1R23_IO_Data)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
|
||||
if (mz1r23Ctrl == NULL)
|
||||
return 0xFF;
|
||||
|
||||
if (read)
|
||||
{
|
||||
// Data read with auto-increment.
|
||||
uint8_t result = 0xFF;
|
||||
|
||||
if (mz1r23Ctrl->ctrlReg & MZ1R23_CTRL_KANJI)
|
||||
{
|
||||
// Kanji ROM read.
|
||||
if (mz1r23Ctrl->kanjiRom && mz1r23Ctrl->readAddr < mz1r23Ctrl->kanjiRomSize)
|
||||
{
|
||||
result = mz1r23Ctrl->kanjiRom[mz1r23Ctrl->readAddr];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dictionary ROM read.
|
||||
uint8_t bank = mz1r23Ctrl->ctrlReg & MZ1R23_CTRL_BANK_MASK;
|
||||
uint32_t dictAddr = (uint32_t)bank * MZ1R23_DICT_BANK_SIZE + mz1r23Ctrl->readAddr;
|
||||
if (mz1r23Ctrl->dictRom && dictAddr < mz1r23Ctrl->dictRomSize)
|
||||
{
|
||||
result = mz1r23Ctrl->dictRom[dictAddr];
|
||||
}
|
||||
}
|
||||
|
||||
// Apply ENDIAN bit-reversal if set.
|
||||
if (mz1r23Ctrl->ctrlReg & MZ1R23_CTRL_ENDIAN)
|
||||
{
|
||||
result = reverseBits(result);
|
||||
}
|
||||
|
||||
mz1r23Ctrl->readAddr++;
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Address write via OUT (C),A.
|
||||
// Upper address byte comes from B register (port address MSB).
|
||||
// Lower address byte comes from A register (data).
|
||||
uint16_t addrVal = (uint16_t)((addr >> 8) & 0xFF) << 8 | data;
|
||||
|
||||
if (mz1r23Ctrl->ctrlReg & MZ1R23_CTRL_KANJI)
|
||||
{
|
||||
// Kanji mode: addrVal is the pattern number.
|
||||
// Internal byte address = pattern number × 32.
|
||||
mz1r23Ctrl->readAddr = (uint32_t)addrVal * MZ1R23_PATTERN_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dictionary mode: addrVal is a raw byte address within the bank.
|
||||
mz1r23Ctrl->readAddr = addrVal;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
227
projects/tzpuPico/src/drivers/Sharp/MZ-1R37.c
Normal file
227
projects/tzpuPico/src/drivers/Sharp/MZ-1R37.c
Normal file
@@ -0,0 +1,227 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ-1R37.c
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - MZ-1R37 640K EMM
|
||||
// This file contains setup and driver to emulate the MZ-1R37 640KB Expanded Memory
|
||||
// Manager board.
|
||||
//
|
||||
// Unlike the PIO-3034, this board has NO auto-increment. All addressing is done
|
||||
// through the Z80 B register (port address MSB) combined with data byte:
|
||||
//
|
||||
// To write byte X to EMM address 0xABCDE:
|
||||
// LD BC, 0x0AAC ; B=0x0A (addr[19:16]), C=0xAC (port)
|
||||
// LD A, 0xBC ; A=0xBC (addr[15:8])
|
||||
// OUT (C), A ; Latch addr[19:8] = 0x0ABC
|
||||
// LD BC, 0xDEAD ; B=0xDE (addr[7:0]), C=0xAD (port)
|
||||
// LD A, X ; A=data byte
|
||||
// OUT (C), A ; Write X to address 0xABCDE
|
||||
//
|
||||
// The I/O base address is configurable via ioMap in the JSON config:
|
||||
// "ioMap": [{"srcAddr": 172, "dstAddr": <base>, "size": 2}]
|
||||
// Default base is 0xAC.
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Z80CPU.h"
|
||||
#define Z80_STATIC
|
||||
#include "Z80.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pico/printf.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include "pico/util/queue.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "intercore.h"
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/MZ-1R37.h"
|
||||
#include "debug.h"
|
||||
|
||||
t_MZ1R37 *mz1r37Ctrl; // Control structure for the MZ-1R37 EMM board.
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Interface: MZ-1R37 640K EMM
|
||||
// Description: 640KB expanded memory board with 20-bit address space. No auto-increment.
|
||||
// Address is formed from a latched upper portion (port ACh) and the B register
|
||||
// value during data access (port ADh).
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
// Method to initialise the CPU state to support an MZ-1R37 EMM interface.
|
||||
uint8_t MZ1R37_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Interface logic only for virtual device. Physical device doesnt need configuration.
|
||||
if (config->isPhysical)
|
||||
return (0);
|
||||
|
||||
// Only one instance allowed.
|
||||
if (mz1r37Ctrl != NULL)
|
||||
return (0);
|
||||
|
||||
// Allocate control structure.
|
||||
mz1r37Ctrl = (t_MZ1R37 *) calloc(1, sizeof(t_MZ1R37));
|
||||
if (!mz1r37Ctrl)
|
||||
{
|
||||
debugf("MZ-1R37: Failed to allocate control structure\r\n");
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Allocate 640KB RAM.
|
||||
mz1r37Ctrl->ram = (uint8_t *) calloc(1, MZ1R37_RAM_SIZE);
|
||||
if (mz1r37Ctrl->ram == NULL)
|
||||
{
|
||||
debugf("MZ-1R37: Failed to allocate %d bytes RAM\r\n", MZ1R37_RAM_SIZE);
|
||||
free(mz1r37Ctrl);
|
||||
mz1r37Ctrl = NULL;
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Determine I/O base address from ioMap config (default 0xAC).
|
||||
mz1r37Ctrl->ioBase = MZ1R37_DEFAULT_BASE;
|
||||
for (int remapIdx = 0; remapIdx < config->ioMapCount; remapIdx++)
|
||||
{
|
||||
mz1r37Ctrl->ioBase = (uint8_t)config->ioMap[remapIdx].dstAddr;
|
||||
}
|
||||
mz1r37Ctrl->addrLatch = 0;
|
||||
|
||||
// Install I/O handlers at the configured base address.
|
||||
uint8_t base = mz1r37Ctrl->ioBase;
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
uint8_t port = idx & 0xFF;
|
||||
if (port == base)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ1R37_IO_AddrLatch;
|
||||
else if (port == (uint8_t)(base + 1))
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ1R37_IO_Data;
|
||||
}
|
||||
|
||||
debugf("MZ-1R37: Initialised, base=0x%02X, RAM=%dKB\r\n", base, MZ1R37_RAM_SIZE / 1024);
|
||||
|
||||
result = 1;
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Reset handler.
|
||||
uint8_t MZ1R37_Reset(t_Z80CPU *cpu)
|
||||
{
|
||||
(void)cpu;
|
||||
|
||||
if (mz1r37Ctrl)
|
||||
{
|
||||
mz1r37Ctrl->addrLatch = 0;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Poll handler — nothing to do for a static RAM board.
|
||||
uint8_t __func_in_RAM(MZ1R37_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
(void)cpu;
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Task processor — no external tasks currently handled.
|
||||
uint8_t MZ1R37_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)param;
|
||||
uint8_t result = 0;
|
||||
|
||||
switch (task)
|
||||
{
|
||||
default:
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for Base+0 (default ACh) — Address latch (write only).
|
||||
//
|
||||
// Via OUT (C),A:
|
||||
// B register (port address MSB) provides address[19:16] (bits 0-3 used).
|
||||
// A register (data) provides address[15:8].
|
||||
//
|
||||
// The latched value is combined with the B register during data access
|
||||
// to form the full 20-bit address.
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(MZ1R37_IO_AddrLatch)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
|
||||
if (read)
|
||||
return 0xFF; // Write-only port.
|
||||
|
||||
if (mz1r37Ctrl)
|
||||
{
|
||||
// addr MSB (B register) = address[19:16], data (A register) = address[15:8].
|
||||
uint8_t addrHi = (uint8_t)((addr >> 8) & 0x0F); // Bits 19:16 (only 4 bits used).
|
||||
mz1r37Ctrl->addrLatch = ((uint32_t)addrHi << 16) | ((uint32_t)data << 8);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for Base+1 (default ADh) — Data read/write.
|
||||
//
|
||||
// Via OUT (C),A or IN A,(C):
|
||||
// B register (port address MSB) provides address[7:0].
|
||||
// Combined with the latched address[19:8] to form the full 20-bit address.
|
||||
//
|
||||
// No auto-increment — each access must set the B register explicitly.
|
||||
// Reads from addresses >= 640KB (0xA0000) return 0xFF (unpopulated).
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(MZ1R37_IO_Data)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
|
||||
if (mz1r37Ctrl == NULL)
|
||||
return 0xFF;
|
||||
|
||||
// Form the full 20-bit address: latch[19:8] | B register[7:0].
|
||||
uint32_t fullAddr = (mz1r37Ctrl->addrLatch & 0xFFF00) | (uint32_t)((addr >> 8) & 0xFF);
|
||||
|
||||
if (read)
|
||||
{
|
||||
if (fullAddr < MZ1R37_RAM_SIZE)
|
||||
{
|
||||
return mz1r37Ctrl->ram[fullAddr];
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fullAddr < MZ1R37_RAM_SIZE)
|
||||
{
|
||||
mz1r37Ctrl->ram[fullAddr] = data;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
1214
projects/tzpuPico/src/drivers/Sharp/MZ1500.c
Normal file
1214
projects/tzpuPico/src/drivers/Sharp/MZ1500.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -51,7 +51,6 @@
|
||||
#include "intercore.h"
|
||||
#include "debug.h"
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/RFS.h"
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
#include "drivers/Sharp/QDDrive.h"
|
||||
#include "drivers/Sharp/MZ8BFI.h"
|
||||
@@ -59,17 +58,24 @@
|
||||
#include "drivers/Sharp/MZ-1E19.h"
|
||||
#include "drivers/Sharp/MZ-1R12.h"
|
||||
#include "drivers/Sharp/MZ-1R18.h"
|
||||
#include "drivers/Sharp/MZ-1R23.h"
|
||||
#include "drivers/Sharp/MZ-1R37.h"
|
||||
#include "drivers/Sharp/PIO-3034.h"
|
||||
#include "drivers/Sharp/Celestite.h"
|
||||
#include "drivers/Sharp/MZ2000.h"
|
||||
|
||||
// Map to contain interface name against method to initialise it.
|
||||
static t_InterfaceFuncMap interfaceFuncMap[] = {
|
||||
{"RFS", false, RFS_Init, RFS_Reset, RFS_PollCB, RFS_TaskProcessor},
|
||||
{"MZ-8BFI", false, MZ8BFI_Init, MZ8BFI_Reset, MZ8BFI_PollCB, MZ8BFI_TaskProcessor},
|
||||
{"E0054PA", false, MZ8BFI_Init, MZ8BFI_Reset, MZ8BFI_PollCB, MZ8BFI_TaskProcessor},
|
||||
{"MZ-1E14", false, MZ1E14_Init, MZ1E14_Reset, MZ1E14_PollCB, MZ1E14_TaskProcessor},
|
||||
{"MZ-1E19", false, MZ1E19_Init, MZ1E19_Reset, MZ1E19_PollCB, MZ1E19_TaskProcessor},
|
||||
{"MZ-1R12", false, MZ1R12_Init, MZ1R12_Reset, MZ1R12_PollCB, MZ1R12_TaskProcessor},
|
||||
{"MZ-1R18", false, MZ1R18_Init, MZ1R18_Reset, MZ1R18_PollCB, MZ1R18_TaskProcessor},
|
||||
{"MZ-1R23", false, MZ1R23_Init, MZ1R23_Reset, MZ1R23_PollCB, MZ1R23_TaskProcessor},
|
||||
{"MZ-1R37", false, MZ1R37_Init, MZ1R37_Reset, MZ1R37_PollCB, MZ1R37_TaskProcessor},
|
||||
{"PIO-3034", false, PIO3034_Init, PIO3034_Reset, PIO3034_PollCB, PIO3034_TaskProcessor},
|
||||
{"Celestite", false, Celestite_Init, Celestite_Reset, Celestite_PollCB, Celestite_TaskProcessor},
|
||||
};
|
||||
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[0]);
|
||||
|
||||
@@ -83,9 +89,6 @@ static bool mz2000VramEnabled = false; // PIO A bit 7: VRAM paged into add
|
||||
static bool mz2000CharMode = false; // PIO A bit 6: H=Character VRAM, L=Graphics VRAM.
|
||||
static uint32_t mz2000RamBlocks[32]; // Saved RAM membankPtr for blocks 96-127 (0xC000-0xFFFF).
|
||||
|
||||
// RFS control structure — kept for compatibility with RFS interface driver.
|
||||
extern t_RFSCtrl *rfsCtrl;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Comprehensive Intel 8253 PIT (Programmable Interval Timer) emulation.
|
||||
// The MZ-2000 uses an 8253 PIT at I/O ports 0xE4-0xE7 clocked at ~4 MHz.
|
||||
@@ -876,7 +879,7 @@ uint8_t MZ2000_Reset(t_Z80CPU *cpu)
|
||||
// Call interface reset handlers if board installed.
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
//debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
if (interfaceFuncMap[i].active)
|
||||
{
|
||||
result |= interfaceFuncMap[i].resetFuncPtr(cpu);
|
||||
@@ -1128,9 +1131,6 @@ uint8_t MZ2000_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfi
|
||||
}
|
||||
}
|
||||
|
||||
// When any interface uses physical hardware, RFS bank switching will change
|
||||
// blocks from PHYSICAL to RAM type. The Z80 then fetches from virtual RAM
|
||||
// without generating physical M1/RFSH cycles, causing host DRAM to decay.
|
||||
// Force refreshEnable so Z80CPU_refreshDRAM is called during virtual fetches.
|
||||
if (config->isPhysical)
|
||||
{
|
||||
|
||||
1160
projects/tzpuPico/src/drivers/Sharp/MZ2200.c
Normal file
1160
projects/tzpuPico/src/drivers/Sharp/MZ2200.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,10 @@
|
||||
#include "drivers/Sharp/MZ-1E19.h"
|
||||
#include "drivers/Sharp/MZ-1R12.h"
|
||||
#include "drivers/Sharp/MZ-1R18.h"
|
||||
#include "drivers/Sharp/MZ-1R23.h"
|
||||
#include "drivers/Sharp/MZ-1R37.h"
|
||||
#include "drivers/Sharp/PIO-3034.h"
|
||||
#include "drivers/Sharp/Celestite.h"
|
||||
#include "drivers/Sharp/MZ700.h"
|
||||
|
||||
// Map to contain interface name against method to initialise it.
|
||||
@@ -58,6 +62,10 @@ static t_InterfaceFuncMap interfaceFuncMap[] = {
|
||||
{"MZ-1E19", false, MZ1E19_Init, MZ1E19_Reset, MZ1E19_PollCB, MZ1E19_TaskProcessor},
|
||||
{"MZ-1R12", false, MZ1R12_Init, MZ1R12_Reset, MZ1R12_PollCB, MZ1R12_TaskProcessor},
|
||||
{"MZ-1R18", false, MZ1R18_Init, MZ1R18_Reset, MZ1R18_PollCB, MZ1R18_TaskProcessor},
|
||||
{"MZ-1R23", false, MZ1R23_Init, MZ1R23_Reset, MZ1R23_PollCB, MZ1R23_TaskProcessor},
|
||||
{"MZ-1R37", false, MZ1R37_Init, MZ1R37_Reset, MZ1R37_PollCB, MZ1R37_TaskProcessor},
|
||||
{"PIO-3034", false, PIO3034_Init, PIO3034_Reset, PIO3034_PollCB, PIO3034_TaskProcessor},
|
||||
{"Celestite", false, Celestite_Init, Celestite_Reset, Celestite_PollCB, Celestite_TaskProcessor},
|
||||
};
|
||||
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[MZ700_MEMBANK_0]);
|
||||
|
||||
@@ -783,7 +791,7 @@ uint8_t MZ700_Reset(t_Z80CPU *cpu)
|
||||
// Call interface reset handlers if board installed.
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
//debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
if (interfaceFuncMap[i].active)
|
||||
{
|
||||
result |= interfaceFuncMap[i].resetFuncPtr(cpu);
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
#include "drivers/Sharp/MZ-1E19.h"
|
||||
#include "drivers/Sharp/MZ-1R12.h"
|
||||
#include "drivers/Sharp/MZ-1R18.h"
|
||||
#include "drivers/Sharp/MZ-1R37.h"
|
||||
#include "drivers/Sharp/PIO-3034.h"
|
||||
#include "drivers/Sharp/MZ80A.h"
|
||||
|
||||
// Map to contain interface name against method to initialise it.
|
||||
@@ -59,6 +61,8 @@ static t_InterfaceFuncMap interfaceFuncMap[] = {
|
||||
{"MZ-1E19", false, MZ1E19_Init, MZ1E19_Reset, MZ1E19_PollCB, MZ1E19_TaskProcessor},
|
||||
{"MZ-1R12", false, MZ1R12_Init, MZ1R12_Reset, MZ1R12_PollCB, MZ1R12_TaskProcessor},
|
||||
{"MZ-1R18", false, MZ1R18_Init, MZ1R18_Reset, MZ1R18_PollCB, MZ1R18_TaskProcessor},
|
||||
{"MZ-1R37", false, MZ1R37_Init, MZ1R37_Reset, MZ1R37_PollCB, MZ1R37_TaskProcessor},
|
||||
{"PIO-3034", false, PIO3034_Init, PIO3034_Reset, PIO3034_PollCB, PIO3034_TaskProcessor},
|
||||
};
|
||||
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[0]);
|
||||
|
||||
@@ -688,7 +692,7 @@ uint8_t MZ80A_Reset(t_Z80CPU *cpu)
|
||||
// RFS_Reset will swap-back _membankPtr and clear rfsCtrl->memSwitch if MEMSW was active.
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
//debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
if (interfaceFuncMap[i].active)
|
||||
{
|
||||
result |= interfaceFuncMap[i].resetFuncPtr(cpu);
|
||||
|
||||
250
projects/tzpuPico/src/drivers/Sharp/PIO-3034.c
Normal file
250
projects/tzpuPico/src/drivers/Sharp/PIO-3034.c
Normal file
@@ -0,0 +1,250 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: PIO-3034.c
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - PIO-3034 320K EMM (IO DATA)
|
||||
// This file contains setup and driver to emulate the PIO-3034 320KB Expanded Memory
|
||||
// Manager board by IO DATA.
|
||||
//
|
||||
// The board provides 320KB of RAM accessed via a 19-bit address counter and a data
|
||||
// port with auto-increment. The I/O base address is selectable via DIP switch on
|
||||
// real hardware; here it is configured via ioMap in the JSON config:
|
||||
// "ioMap": [{"srcAddr": 0, "dstAddr": <base>, "size": 4}]
|
||||
// Default base is 0x00 (as used by HuBASIC).
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Z80CPU.h"
|
||||
#define Z80_STATIC
|
||||
#include "Z80.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pico/printf.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include "pico/util/queue.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "intercore.h"
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/PIO-3034.h"
|
||||
#include "debug.h"
|
||||
|
||||
t_PIO3034 *pio3034Ctrl; // Control structure for the PIO-3034 EMM board.
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Interface: IO DATA PIO-3034 320K EMM
|
||||
// Description: 320KB expanded memory board with 19-bit address counter and auto-increment data
|
||||
// port. Base I/O address is configurable (default 0x00).
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
// Method to initialise the CPU state to support a PIO-3034 EMM interface.
|
||||
uint8_t PIO3034_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Interface logic only for virtual device. Physical device doesnt need configuration.
|
||||
if (config->isPhysical)
|
||||
return (0);
|
||||
|
||||
// Only one instance allowed.
|
||||
if (pio3034Ctrl != NULL)
|
||||
return (0);
|
||||
|
||||
// Allocate control structure.
|
||||
pio3034Ctrl = (t_PIO3034 *) calloc(1, sizeof(t_PIO3034));
|
||||
if (!pio3034Ctrl)
|
||||
{
|
||||
debugf("PIO-3034: Failed to allocate control structure\r\n");
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Allocate 320KB RAM.
|
||||
pio3034Ctrl->ram = (uint8_t *) calloc(1, PIO3034_RAM_SIZE);
|
||||
if (pio3034Ctrl->ram == NULL)
|
||||
{
|
||||
debugf("PIO-3034: Failed to allocate %d bytes RAM\r\n", PIO3034_RAM_SIZE);
|
||||
free(pio3034Ctrl);
|
||||
pio3034Ctrl = NULL;
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Determine I/O base address from ioMap config (default 0x00).
|
||||
pio3034Ctrl->ioBase = PIO3034_DEFAULT_BASE;
|
||||
for (int remapIdx = 0; remapIdx < config->ioMapCount; remapIdx++)
|
||||
{
|
||||
pio3034Ctrl->ioBase = (uint8_t)config->ioMap[remapIdx].dstAddr;
|
||||
}
|
||||
pio3034Ctrl->addr = 0;
|
||||
|
||||
// Install I/O handlers at the configured base address.
|
||||
uint8_t base = pio3034Ctrl->ioBase;
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
uint8_t port = idx & 0xFF;
|
||||
if (port == base)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) PIO3034_IO_AddrLo;
|
||||
else if (port == (uint8_t)(base + 1))
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) PIO3034_IO_AddrMid;
|
||||
else if (port == (uint8_t)(base + 2))
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) PIO3034_IO_AddrHi;
|
||||
else if (port == (uint8_t)(base + 3))
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) PIO3034_IO_Data;
|
||||
}
|
||||
|
||||
debugf("PIO-3034: Initialised, base=0x%02X, RAM=%dKB\r\n", base, PIO3034_RAM_SIZE / 1024);
|
||||
|
||||
result = 1;
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Reset handler.
|
||||
uint8_t PIO3034_Reset(t_Z80CPU *cpu)
|
||||
{
|
||||
(void)cpu;
|
||||
|
||||
if (pio3034Ctrl)
|
||||
{
|
||||
pio3034Ctrl->addr = 0;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Poll handler — nothing to do for a static RAM board.
|
||||
uint8_t __func_in_RAM(PIO3034_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
(void)cpu;
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Task processor — no external tasks currently handled.
|
||||
uint8_t PIO3034_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)param;
|
||||
uint8_t result = 0;
|
||||
|
||||
switch (task)
|
||||
{
|
||||
default:
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for Base+0 — Address counter bits [7:0] (write only).
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(PIO3034_IO_AddrLo)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)addr;
|
||||
|
||||
if (read)
|
||||
return 0xFF;
|
||||
|
||||
if (pio3034Ctrl)
|
||||
{
|
||||
pio3034Ctrl->addr = (pio3034Ctrl->addr & 0x7FF00) | (uint32_t)data;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for Base+1 — Address counter bits [15:8] (write only).
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(PIO3034_IO_AddrMid)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)addr;
|
||||
|
||||
if (read)
|
||||
return 0xFF;
|
||||
|
||||
if (pio3034Ctrl)
|
||||
{
|
||||
pio3034Ctrl->addr = (pio3034Ctrl->addr & 0x700FF) | ((uint32_t)data << 8);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for Base+2 — Address counter bits [18:16] (write only).
|
||||
// Only bits 0-2 are significant (19-bit address space).
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(PIO3034_IO_AddrHi)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)addr;
|
||||
|
||||
if (read)
|
||||
return 0xFF;
|
||||
|
||||
if (pio3034Ctrl)
|
||||
{
|
||||
pio3034Ctrl->addr = (pio3034Ctrl->addr & 0x0FFFF) | (((uint32_t)data & 0x07) << 16);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// I/O handler for Base+3 — Data read/write with address auto-increment.
|
||||
// Reads from addresses >= 320KB (0x50000) return 0xFF (unpopulated).
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t __func_in_RAM(PIO3034_IO_Data)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
(void)cpu;
|
||||
(void)addr;
|
||||
|
||||
if (pio3034Ctrl == NULL)
|
||||
return 0xFF;
|
||||
|
||||
if (read)
|
||||
{
|
||||
uint8_t result = 0xFF;
|
||||
|
||||
if (pio3034Ctrl->addr < PIO3034_RAM_SIZE)
|
||||
{
|
||||
result = pio3034Ctrl->ram[pio3034Ctrl->addr];
|
||||
}
|
||||
|
||||
pio3034Ctrl->addr = (pio3034Ctrl->addr + 1) & PIO3034_ADDR_MASK;
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pio3034Ctrl->addr < PIO3034_RAM_SIZE)
|
||||
{
|
||||
pio3034Ctrl->ram[pio3034Ctrl->addr] = data;
|
||||
}
|
||||
|
||||
pio3034Ctrl->addr = (pio3034Ctrl->addr + 1) & PIO3034_ADDR_MASK;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -217,21 +217,26 @@ void __not_in_flash_func(qdSioWriteControl)(t_QdDrive *qd, t_sioChannel *ch, uin
|
||||
uint32_t pos = qd->currentPosition;
|
||||
bool found = false;
|
||||
|
||||
if (qd->sync16bit)
|
||||
// Hunt for sync pattern: look for the mark byte (0x00) followed by sync char (0x16).
|
||||
// The 0x00 mark always precedes a new block's sync preamble in both compact and
|
||||
// QDF formats. This ensures we skip past any trailing sync bytes from the previous
|
||||
// block (which are preceded by CRC bytes, not 0x00).
|
||||
{
|
||||
while (pos + 1 < qd->diskSize)
|
||||
{
|
||||
if (qd->diskImage[pos] == qd->syncChar1 && qd->diskImage[pos + 1] == qd->syncChar2)
|
||||
if (qd->diskImage[pos] == QD_MARK_FLAG && qd->diskImage[pos + 1] == qd->syncChar1)
|
||||
{
|
||||
qd->currentPosition = pos + 2;
|
||||
qd->currentPosition = pos + 2; // Past the 00 16
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!found)
|
||||
{
|
||||
// Fallback: scan for just sync char (for edge cases at track boundaries).
|
||||
pos = qd->currentPosition;
|
||||
while (pos < qd->diskSize)
|
||||
{
|
||||
if (qd->diskImage[pos] == qd->syncChar1)
|
||||
@@ -252,8 +257,9 @@ void __not_in_flash_func(qdSioWriteControl)(t_QdDrive *qd, t_sioChannel *ch, uin
|
||||
qd->inBlock = true;
|
||||
qd->bytesReadInBlock = 0;
|
||||
qd->chA.rr[1] &= ~0x40;
|
||||
// debugf("QD: SYNC FOUND pos=%d byte=%02X\r\n", qd->currentPosition,
|
||||
// qd->currentPosition < qd->diskSize ? qd->diskImage[qd->currentPosition] : 0xFF);
|
||||
// debugf("QD: SYNC@%d nxt=%02X s16=%d ds=%d\r\n", qd->currentPosition,
|
||||
// qd->currentPosition < qd->diskSize ? qd->diskImage[qd->currentPosition] : 0xFF,
|
||||
// qd->sync16bit, qd->diskSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -330,10 +336,13 @@ uint32_t qdGetNextRequestId(t_QdDrive *qd)
|
||||
return id;
|
||||
}
|
||||
|
||||
// Get size of the QD Image.
|
||||
// Get size of the QD Image buffer. Must be large enough for the Japanese
|
||||
// QDF format (81936 bytes = 16-byte header + 81920 bytes track data).
|
||||
int qdGetDiskSize(t_QdDrive *qd, int machineType)
|
||||
{
|
||||
return MAX_QD_SIZE;
|
||||
(void)qd;
|
||||
(void)machineType;
|
||||
return QDF_FILE_SIZE; // 81936 — large enough for both compact QD and QDF formats.
|
||||
}
|
||||
|
||||
// Process intercore message response.
|
||||
@@ -366,8 +375,36 @@ int __not_in_flash_func(qdProcessDeviceResponses)(t_QdDrive *qd, queue_t *respon
|
||||
case MSG_LOAD_COMPLETE:
|
||||
qd->opState.loadPending = false;
|
||||
qd->diskLoaded = response.response.success;
|
||||
if (!response.response.success)
|
||||
if (response.response.success)
|
||||
{
|
||||
// Detect QDF format: 16-byte header "-QD format-" + 81920 bytes track data.
|
||||
// If found, adjust diskImage pointer past the header (no memmove needed).
|
||||
// Data is always loaded to diskBuf; diskImage points to track start.
|
||||
qd->diskImage = qd->diskBuf; // Reset to base.
|
||||
if (response.response.size >= QDF_FILE_SIZE &&
|
||||
memcmp(qd->diskBuf, QDF_SIGNATURE, 11) == 0)
|
||||
{
|
||||
qd->diskImage = qd->diskBuf + QDF_HEADER_SIZE;
|
||||
qd->diskSize = MAX_QDF_SIZE;
|
||||
debugf("QD: QDF format detected, diskSize=%lu\r\n", (unsigned long)qd->diskSize);
|
||||
}
|
||||
else if (response.response.size <= MAX_QD_FORMAT_SIZE)
|
||||
{
|
||||
// Standard compact QD format.
|
||||
qd->diskSize = (response.response.size > MAX_QD_SIZE) ? response.response.size : MAX_QD_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown size — use what we got, cap at buffer.
|
||||
qd->diskSize = (response.response.size > MAX_QDF_SIZE) ? MAX_QDF_SIZE : response.response.size;
|
||||
debugf("QD: Non-standard size=%lu\r\n", (unsigned long)qd->diskSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
|
||||
}
|
||||
qd->currentPosition = 0;
|
||||
qd->intrq = true;
|
||||
break;
|
||||
|
||||
@@ -458,31 +495,23 @@ void __not_in_flash_func(qdUpdateStatus)(t_QdDrive *qd)
|
||||
qd->chA.rr[0] |= 0x40; // EOM
|
||||
}
|
||||
|
||||
// Sync stripping — only strip leading sync bytes before the first data byte of a block.
|
||||
// Real Z80 SIO only strips sync chars at the start of a message, not mid-data.
|
||||
if (qd->rxEnable && !qd->huntActive && qd->inBlock && qd->currentOperation == QD_OP_READ && qd->currentPosition < qd->diskSize && qd->inhibit
|
||||
&& qd->bytesReadInBlock == 0)
|
||||
// Sync stripping — strip ALL leading sync bytes after hunt finds sync, before first data byte.
|
||||
// Real Z80 SIO automatically strips sync characters after entering sync mode.
|
||||
// QDF format tracks have long sync preambles (9+ bytes of 0x16 before the A5 flag).
|
||||
// Always strip individual sync bytes regardless of 16-bit mode — handles any count correctly.
|
||||
if (qd->rxEnable && !qd->huntActive && qd->inBlock && qd->currentOperation == QD_OP_READ
|
||||
&& qd->currentPosition < qd->diskSize && qd->bytesReadInBlock == 0)
|
||||
{
|
||||
uint32_t startPos = qd->currentPosition;
|
||||
if (qd->sync16bit)
|
||||
uint32_t stripStart = qd->currentPosition;
|
||||
while (qd->currentPosition < qd->diskSize &&
|
||||
(qd->diskImage[qd->currentPosition] == qd->syncChar1 ||
|
||||
qd->diskImage[qd->currentPosition] == qd->syncChar2))
|
||||
{
|
||||
while (qd->currentPosition + 1 < qd->diskSize && qd->diskImage[qd->currentPosition] == qd->syncChar1 &&
|
||||
qd->diskImage[qd->currentPosition + 1] == qd->syncChar2)
|
||||
{
|
||||
qd->currentPosition += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (qd->currentPosition < qd->diskSize && qd->diskImage[qd->currentPosition] == qd->syncChar1)
|
||||
{
|
||||
qd->currentPosition++;
|
||||
}
|
||||
}
|
||||
if (qd->currentPosition != startPos)
|
||||
{
|
||||
// debugf("QD: SYNC_STRIP %d→%d\r\n", startPos, qd->currentPosition);
|
||||
qd->currentPosition++;
|
||||
}
|
||||
// if (qd->currentPosition != stripStart)
|
||||
// debugf("QD: STRIP %d->%d nxt=%02X\r\n", stripStart, qd->currentPosition,
|
||||
// qd->currentPosition < qd->diskSize ? qd->diskImage[qd->currentPosition] : 0xFF);
|
||||
}
|
||||
|
||||
qd->drq = false;
|
||||
@@ -589,6 +618,8 @@ bool qdInit(t_QdDrive *qd, queue_t *requestQueue, queue_t *responseQueue, const
|
||||
qd->requestQueue = requestQueue;
|
||||
qd->responseQueue = responseQueue;
|
||||
qd->diskImage = diskImage;
|
||||
qd->diskBuf = diskImage; // Save original base for reloads.
|
||||
qd->diskBufSize = bufferSize;
|
||||
qd->currentPosition = 0;
|
||||
qd->motorOn = false;
|
||||
qd->diskLoaded = false;
|
||||
@@ -598,7 +629,7 @@ bool qdInit(t_QdDrive *qd, queue_t *requestQueue, queue_t *responseQueue, const
|
||||
qd->opState.loadPending = false;
|
||||
qd->opState.writePending = false;
|
||||
qd->filename = (filename != NULL) ? strdup(filename) : NULL;
|
||||
qd->diskSize = MAX_QD_SIZE;
|
||||
qd->diskSize = MAX_QD_SIZE; // Default; updated to MAX_QDF_SIZE on QDF format detection.
|
||||
qd->machineType = machineType;
|
||||
qd->chA.pointer = 0;
|
||||
qd->chB.pointer = 0;
|
||||
@@ -628,8 +659,8 @@ bool qdInit(t_QdDrive *qd, queue_t *requestQueue, queue_t *responseQueue, const
|
||||
qd->opState.loadPending = true;
|
||||
t_CoreMsg msg = {.type = MSG_LOAD_QUICKDISK, .context = qd};
|
||||
strncpy(msg.fileOp.filename, filename, MAX_IC_FILENAME_LEN - 1);
|
||||
msg.fileOp.buffer = diskImage;
|
||||
msg.fileOp.size = qd->diskSize;
|
||||
msg.fileOp.buffer = qd->diskBuf;
|
||||
msg.fileOp.size = qd->diskBufSize;
|
||||
msg.fileOp.diskNo = 0;
|
||||
queue_try_add(qd->requestQueue, &msg);
|
||||
}
|
||||
@@ -651,11 +682,15 @@ void qdChangeDisk(t_QdDrive *qd, const char *newFilename, int diskNo)
|
||||
qd->currentPosition = 0;
|
||||
qd->formatFill = false;
|
||||
|
||||
// Reset diskImage to buffer base (in case it was offset for QDF header).
|
||||
qd->diskImage = qd->diskBuf;
|
||||
qd->diskSize = MAX_QD_SIZE;
|
||||
|
||||
// Only 1 QD drive can be configured at a time, so set diskNo to 0 for all change requests.
|
||||
t_CoreMsg msg = {.type = MSG_LOAD_QUICKDISK, .context = qd};
|
||||
strncpy(msg.fileOp.filename, newFilename, MAX_IC_FILENAME_LEN - 1);
|
||||
msg.fileOp.buffer = qd->diskImage;
|
||||
msg.fileOp.size = qd->diskSize;
|
||||
msg.fileOp.buffer = qd->diskBuf; // Always load to base buffer.
|
||||
msg.fileOp.size = qd->diskBufSize;
|
||||
msg.fileOp.diskNo = 0;
|
||||
queue_try_add(qd->requestQueue, &msg);
|
||||
}
|
||||
|
||||
8
projects/tzpuPico/src/include/ESP.h
vendored
8
projects/tzpuPico/src/include/ESP.h
vendored
@@ -119,6 +119,7 @@ int ESP_readFile_(const char *fileName,
|
||||
int ESP_readSector(const char *fileName, int filePos, uint8_t *memLocation, int secLen);
|
||||
bool ESP_writeFile(const char *fileName, uint8_t *memLocation, int maxLen);
|
||||
bool ESP_writeSector(const char *fileName, int filePos, uint8_t *memLocation, int secLen);
|
||||
int ESP_readDir(const char *path, char *buf, int bufLen);
|
||||
|
||||
// Burst sector I/O — transfers up to IPCF_MAX_SECTORS sectors in a single SPI transaction.
|
||||
// Returns bytes copied into memLocation (numSectors × IPCF_SECTOR_SIZE on success, 0 on error).
|
||||
@@ -126,6 +127,13 @@ int ESP_readBurstSectors(const char *fileName, int filePos, uint8_t *memLocation
|
||||
// Returns true on success.
|
||||
bool ESP_writeBurstSectors(const char *fileName, int filePos, uint8_t *memLocation, int numSectors);
|
||||
|
||||
// Network functions (Celestite W5100 emulation — Phase 2).
|
||||
bool ESP_netCfg(uint8_t *ipOut, uint8_t *gwOut, uint8_t *subnetOut, uint8_t *macOut);
|
||||
bool ESP_netSocket(uint8_t sockNum, uint8_t operation, uint8_t protocol, uint32_t ipAddr, uint16_t port, uint8_t *statusOut);
|
||||
bool ESP_netSend(uint8_t sockNum, uint8_t *data, uint32_t len, uint32_t *sentOut);
|
||||
bool ESP_netRecv(uint8_t sockNum, uint8_t *buf, uint32_t bufSize, uint32_t *recvdOut);
|
||||
bool ESP_netPing(uint32_t ipAddr, uint32_t *rttOut);
|
||||
|
||||
cJSON *ESP_readConfig(const char *fileName);
|
||||
const char *ESP_getClassName(void);
|
||||
void ESP_replaceExt(char *fileName, size_t fileNameLen, const char *newExt);
|
||||
|
||||
2
projects/tzpuPico/src/include/Z80CPU.h
vendored
2
projects/tzpuPico/src/include/Z80CPU.h
vendored
@@ -487,7 +487,7 @@ void Z80CPU_deinit(t_Z80CPU *cpu);
|
||||
bool Z80CPU_configFromJSON(t_Z80CPU *cpu, int cfgAppNo);
|
||||
bool Z80CPU_parseJSONStore(t_Z80CPU *cpu, cJSON *configRoot, uint8_t cfgApp, char *json);
|
||||
void Z80CPU_ForceHostResetActive(t_Z80CPU *cpu, bool resetHardware);
|
||||
void Z80CPU_reset(t_Z80CPU *cpu);
|
||||
void Z80CPU_reset(t_Z80CPU *cpu, bool physicalReset);
|
||||
void Z80CPU_cpu_init(t_Z80CPU *cpu);
|
||||
void Z80CPU_cpu(t_Z80CPU *cpu);
|
||||
|
||||
|
||||
260
projects/tzpuPico/src/include/drivers/Sharp/Celestite.h
vendored
Normal file
260
projects/tzpuPico/src/include/drivers/Sharp/Celestite.h
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: Celestite.h
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - Celestite LAN / Memory Composite Board
|
||||
// Emulates the Celestite board (designed by Oh!Ishi) which provides:
|
||||
// - Wiznet W5100 hardwired TCP/IP Ethernet controller (IDM access)
|
||||
// - Interrupt controller
|
||||
// - UFM (User Flash Memory) for MAC address / config persistence
|
||||
// - Board unlock features (MZ-1R12 doubling, ROM write-protect, EMM)
|
||||
//
|
||||
// I/O ports (active at 0x60-0x6F):
|
||||
// 60h (W) : W5100 MR (Mode Register)
|
||||
// 61h (W) : W5100 IDM_AR0 (internal address high byte)
|
||||
// 62h (W) : W5100 IDM_AR1 (internal address low byte)
|
||||
// 63h (R/W) : W5100 IDM_DR (data read/write, auto-increment)
|
||||
// 64h (R/W) : Interrupt vector
|
||||
// 65h (W) : Interrupt control (bit 0: ENABLE)
|
||||
// 65h (R) : Interrupt status (bit 7: input, bit 6: ack, bit 0: enable)
|
||||
// 66h (R/W) : Memo register
|
||||
// 68h (W) : UFM address register
|
||||
// 68h (R) : UFM status (bit 7: WP, bit 1: READY, bit 0: BUSY)
|
||||
// 69h (R/W) : UFM data register
|
||||
// 6Fh (W) : Unlock (write D1h then keyword)
|
||||
//
|
||||
// Credits: Board designed by Oh!Ishi (Oh!石)
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Phase 1: Register file + IDM access (no networking).
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef CELESTITE_H
|
||||
#define CELESTITE_H
|
||||
|
||||
// Need IPCF_MAX_PAYLOAD for receive buffer sizing.
|
||||
#include "ipc_protocol.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// W5100 Internal Address Map
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Common registers (0x0000-0x002F).
|
||||
#define W5100_MR 0x0000 // Mode Register (RST, WOL, PB, PPPoE, AI, IND).
|
||||
#define W5100_GAR0 0x0001 // Gateway Address [0-3].
|
||||
#define W5100_SUBR0 0x0005 // Subnet Mask [0-3].
|
||||
#define W5100_SHAR0 0x0009 // Source Hardware (MAC) Address [0-5].
|
||||
#define W5100_SIPR0 0x000F // Source IP Address [0-3].
|
||||
#define W5100_IR 0x0015 // Interrupt Register.
|
||||
#define W5100_IMR 0x0016 // Interrupt Mask Register.
|
||||
#define W5100_RTR0 0x0017 // Retry Time [0-1] (100us units).
|
||||
#define W5100_RCR 0x0019 // Retry Count.
|
||||
#define W5100_RMSR 0x001A // RX Memory Size (per socket).
|
||||
#define W5100_TMSR 0x001B // TX Memory Size (per socket).
|
||||
|
||||
// MR bit definitions.
|
||||
#define W5100_MR_RST 0x80 // Software reset.
|
||||
#define W5100_MR_PB 0x10 // Ping block.
|
||||
#define W5100_MR_AI 0x02 // Auto-increment IDM address.
|
||||
#define W5100_MR_IND 0x01 // Indirect bus mode (always set for IDM).
|
||||
|
||||
// Socket register base addresses (4 sockets × 256 bytes each).
|
||||
#define W5100_SOCK_BASE 0x0400
|
||||
#define W5100_SOCK_SIZE 0x0100
|
||||
#define W5100_NUM_SOCKETS 4
|
||||
|
||||
// Socket register offsets (relative to socket base).
|
||||
#define Sn_MR 0x00 // Socket Mode.
|
||||
#define Sn_CR 0x01 // Socket Command.
|
||||
#define Sn_IR 0x02 // Socket Interrupt.
|
||||
#define Sn_SR 0x03 // Socket Status.
|
||||
#define Sn_PORT0 0x04 // Source Port [0-1].
|
||||
#define Sn_DHAR0 0x06 // Dest Hardware Addr [0-5].
|
||||
#define Sn_DIPR0 0x0C // Dest IP [0-3].
|
||||
#define Sn_DPORT0 0x10 // Dest Port [0-1].
|
||||
#define Sn_MSS0 0x12 // Max Segment Size [0-1].
|
||||
#define Sn_PROTO 0x14 // IP Protocol (IPRAW mode).
|
||||
#define Sn_TOS 0x15 // Type of Service.
|
||||
#define Sn_TTL 0x16 // Time to Live.
|
||||
#define Sn_TX_FSR0 0x20 // TX Free Size [0-1].
|
||||
#define Sn_TX_RD0 0x22 // TX Read Pointer [0-1].
|
||||
#define Sn_TX_WR0 0x24 // TX Write Pointer [0-1].
|
||||
#define Sn_RX_RSR0 0x26 // RX Received Size [0-1].
|
||||
#define Sn_RX_RD0 0x28 // RX Read Pointer [0-1].
|
||||
|
||||
// Socket Mode (Sn_MR) protocol field (bits 3-0).
|
||||
#define Sn_MR_TCP 0x01
|
||||
#define Sn_MR_UDP 0x02
|
||||
#define Sn_MR_IPRAW 0x03
|
||||
#define Sn_MR_MACRAW 0x04
|
||||
#define Sn_MR_PROTO_MASK 0x0F
|
||||
|
||||
// Socket Command (Sn_CR) values.
|
||||
#define Sn_CR_OPEN 0x01
|
||||
#define Sn_CR_LISTEN 0x02
|
||||
#define Sn_CR_CONNECT 0x04
|
||||
#define Sn_CR_DISCON 0x08
|
||||
#define Sn_CR_CLOSE 0x10
|
||||
#define Sn_CR_SEND 0x20
|
||||
#define Sn_CR_SEND_MAC 0x21
|
||||
#define Sn_CR_SEND_KEEP 0x22
|
||||
#define Sn_CR_RECV 0x40
|
||||
|
||||
// Socket Status (Sn_SR) values.
|
||||
#define SOCK_CLOSED 0x00
|
||||
#define SOCK_INIT 0x13
|
||||
#define SOCK_LISTEN 0x14
|
||||
#define SOCK_SYNSENT 0x15
|
||||
#define SOCK_SYNRECV 0x16
|
||||
#define SOCK_ESTABLISHED 0x17
|
||||
#define SOCK_FIN_WAIT 0x18
|
||||
#define SOCK_CLOSING 0x1A
|
||||
#define SOCK_TIME_WAIT 0x1B
|
||||
#define SOCK_CLOSE_WAIT 0x1C
|
||||
#define SOCK_LAST_ACK 0x1D
|
||||
#define SOCK_UDP 0x22
|
||||
#define SOCK_IPRAW 0x32
|
||||
#define SOCK_MACRAW 0x42
|
||||
|
||||
// Socket Interrupt (Sn_IR) bits.
|
||||
#define Sn_IR_CON 0x01
|
||||
#define Sn_IR_DISCON 0x02
|
||||
#define Sn_IR_RECV 0x04
|
||||
#define Sn_IR_TIMEOUT 0x08
|
||||
#define Sn_IR_SEND_OK 0x10
|
||||
|
||||
// TX/RX buffer memory.
|
||||
#define W5100_TX_BASE 0x4000 // TX buffer start.
|
||||
#define W5100_TX_SIZE 0x2000 // 8KB total TX.
|
||||
#define W5100_RX_BASE 0x6000 // RX buffer start.
|
||||
#define W5100_RX_SIZE 0x2000 // 8KB total RX.
|
||||
|
||||
// Total W5100 address space we emulate.
|
||||
#define W5100_MEM_SIZE 0x8000 // 32KB covers all registers + buffers.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Celestite board-level registers (outside W5100 address space)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define CELESTITE_UFM_SIZE 256 // UFM storage size in bytes.
|
||||
#define CELESTITE_R12_SIZE 32768 // MZ-1R12 base RAM size (32KB).
|
||||
#define CELESTITE_R12_DBL 65536 // MZ-1R12 doubled RAM size (64KB).
|
||||
#define CELESTITE_R37_SIZE 655360 // MZ-1R37 EMM size (640KB).
|
||||
#define CELESTITE_R37_MASK 0xFFFFF // 20-bit address mask.
|
||||
|
||||
// Unlock register state machine.
|
||||
typedef enum
|
||||
{
|
||||
UNLOCK_IDLE = 0, // Waiting for D1h prefix.
|
||||
UNLOCK_WAIT_KEYWORD // D1h received, waiting for keyword byte.
|
||||
} t_CelestiteUnlockState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main control structure
|
||||
// ---------------------------------------------------------------------------
|
||||
typedef struct
|
||||
{
|
||||
// W5100 emulated address space (registers + TX/RX buffers).
|
||||
uint8_t *w5100Mem; // 32KB W5100 register/buffer memory.
|
||||
uint16_t idmAddr; // Current IDM address (set by AR0/AR1).
|
||||
|
||||
// Celestite board-level registers.
|
||||
uint8_t intVector; // Interrupt vector (port 64h).
|
||||
uint8_t intControl; // Interrupt control (port 65h write).
|
||||
uint8_t intStatus; // Interrupt status (port 65h read).
|
||||
uint8_t memoReg; // Memo register (port 66h).
|
||||
|
||||
// UFM (User Flash Memory).
|
||||
uint8_t ufm[CELESTITE_UFM_SIZE]; // 256-byte UFM storage.
|
||||
uint8_t ufmAddr; // UFM address register.
|
||||
bool ufmWriteProtect; // UFM write-protection state (true=protected).
|
||||
bool ufmBusy; // UFM busy flag.
|
||||
|
||||
// Unlock register state machine.
|
||||
t_CelestiteUnlockState unlockState;
|
||||
|
||||
// Board feature flags (set by unlock keywords).
|
||||
bool romWriteUnprotected; // Expansion/FD ROM write-protect removed (keyword 05h).
|
||||
|
||||
// Integrated MZ-1R12 CMOS Battery Backed RAM (32KB, doubler to 64KB).
|
||||
// Ports: F8h (W=set high addr, R=reset to 0), F9h (W=set low addr, R=read+autoinc), FAh (W=write+autoinc).
|
||||
// Enabled by params[0].file in JSON config. SD-card backed for persistence.
|
||||
uint8_t *mz1r12Ram; // 64KB buffer (32KB used initially, 64KB when doubled).
|
||||
uint32_t mz1r12Size; // Current active RAM size (32768 or 65536).
|
||||
uint16_t mz1r12Addr; // 16-bit address pointer (auto-increments on read/write).
|
||||
bool mz1r12Doubled; // True after unlock keyword 12h doubles from 32K to 64K.
|
||||
char *mz1r12FileName; // SD card backing file name (NULL = no persistence).
|
||||
bool mz1r12WritePending; // True while a scheduled write is in progress.
|
||||
uint32_t mz1r12NextReqId; // Monotonic request ID for write coalescing.
|
||||
|
||||
// Integrated MZ-1R37 640K EMM.
|
||||
// Ports: ACh (W=address latch via OUT(C),A), ADh (R/W=data via OUT(C),A / IN A,(C)).
|
||||
// No auto-increment. 20-bit address: latch[19:8] | B_register[7:0].
|
||||
// Enabled by params[1].file in JSON config. SD-card backed for persistence.
|
||||
uint8_t *mz1r37Ram; // 640KB RAM buffer.
|
||||
uint32_t mz1r37AddrLatch; // Latched address bits [19:8].
|
||||
char *mz1r37FileName; // SD card backing file name (NULL = no persistence).
|
||||
|
||||
// Network state (Phase 2).
|
||||
queue_t *requestQueue; // Pointer to inter-core request queue.
|
||||
queue_t *responseQueue; // Pointer to inter-core response queue.
|
||||
volatile bool netActive; // Fast-path guard: true when ANY network op is pending.
|
||||
bool netCfgPending; // True while NET_CFG request is in flight.
|
||||
bool netCfgDone; // True once ESP32 network config has been loaded.
|
||||
bool netSockPending[W5100_NUM_SOCKETS]; // True while a socket op is in flight per socket.
|
||||
bool netRecvPending[W5100_NUM_SOCKETS]; // True while a RECV poll is in flight per socket.
|
||||
uint8_t netRecvBuf[IPCF_MAX_PAYLOAD]; // Shared receive buffer for incoming data.
|
||||
uint32_t pollCounter; // Throttle periodic recv polling.
|
||||
|
||||
// Direct shared results from Core 0 (bypasses responseQueue for reliability).
|
||||
// Socket operation result:
|
||||
volatile bool netResultReady; // Core 0 sets true when a socket op result is available.
|
||||
volatile uint8_t netResultSock; // Socket number for the result.
|
||||
volatile uint8_t netResultStatus; // Socket status from ESP32.
|
||||
volatile bool netResultSuccess; // Operation success flag.
|
||||
volatile uint8_t netResultOp; // Operation that completed (Sn_CR_CONNECT etc).
|
||||
// Recv data result:
|
||||
volatile bool netRecvReady; // Core 0 sets true when recv data is available.
|
||||
volatile uint8_t netRecvSock; // Socket number for recv data.
|
||||
volatile uint32_t netRecvLen; // Length of received data in netRecvBuf.
|
||||
// NET_CFG result:
|
||||
volatile bool netCfgReady; // Core 0 sets true when NET_CFG result is available.
|
||||
volatile bool netCfgSuccess; // True if config was retrieved successfully.
|
||||
uint8_t netCfgData[18]; // IP[4]+GW[4]+Subnet[4]+MAC[6] from ESP32.
|
||||
} t_Celestite;
|
||||
|
||||
// Public prototypes.
|
||||
uint8_t Celestite_Init(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
uint8_t Celestite_Reset(t_Z80CPU *cpu);
|
||||
uint8_t Celestite_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t Celestite_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t Celestite_IO_MR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_AR0(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_AR1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_DR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_Int(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_Memo(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_Ping(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_UFM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_Unlock(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_R12(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_R37Latch(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Celestite_IO_R37Data(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
|
||||
#endif // CELESTITE_H
|
||||
103
projects/tzpuPico/src/include/drivers/Sharp/MZ-1R23.h
vendored
Normal file
103
projects/tzpuPico/src/include/drivers/Sharp/MZ-1R23.h
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ-1R23.h
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - MZ-1R23 Kanji ROM / MZ-1R24 Dictionary ROM Board
|
||||
// This file contains setup and driver to emulate the MZ-1R23 Kanji ROM board and
|
||||
// the MZ-1R24 Dictionary ROM board.
|
||||
//
|
||||
// I/O ports:
|
||||
// B8h (W): Control register
|
||||
// Bit 7: KANJI — 1=Kanji ROM, 0=Dictionary ROM
|
||||
// Bit 6: ENDIAN — 0=Bit7 left/Bit0 right, 1=Bit0 left/Bit7 right
|
||||
// Bits 0-1: BANK — Dictionary ROM bank select (00-11)
|
||||
//
|
||||
// B9h (W): Address set via OUT (C),A — upper byte from B register (A15-A8),
|
||||
// lower byte from A register (D7-D0).
|
||||
// Kanji mode: sets kanji pattern number; internal byte address = pattern * 32.
|
||||
// Dict mode: sets raw byte address within selected bank.
|
||||
//
|
||||
// B9h (R): Data read with auto-increment.
|
||||
// Kanji: pattern bytes in order upper-left, lower-left, upper-right, lower-right
|
||||
// (four 8x8 quadrants of a 16x16 character, 8 bytes per quadrant = 32 bytes total).
|
||||
//
|
||||
// Kanji pattern number = (JisH - 0x21 - ((JisH > 0x30) ? 0x08 : 0x00)) * 94
|
||||
// + (JisL - 0x21)
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MZ1R23_H
|
||||
#define MZ1R23_H
|
||||
|
||||
// Constants.
|
||||
#define MZ1R23_KANJI_ROM_MAX 131072 // 128KB kanji ROM (4 × HN61256 32KB, ~4096 JIS patterns).
|
||||
#define MZ1R23_DICT_ROM_MAX 262144 // 256KB dictionary ROM (8 × HN61256 32KB = 4 × 64KB banks).
|
||||
#define MZ1R23_DICT_BANK_SIZE 65536 // 64KB per dictionary bank.
|
||||
#define MZ1R23_PATTERN_SIZE 32 // 32 bytes per 16x16 kanji pattern (4 quadrants × 8 bytes).
|
||||
|
||||
// Control register (B8h) bit definitions.
|
||||
#define MZ1R23_CTRL_KANJI 0x80 // Bit 7: 1=Kanji ROM, 0=Dictionary ROM.
|
||||
#define MZ1R23_CTRL_ENDIAN 0x40 // Bit 6: 1=Bit-reversed byte order.
|
||||
#define MZ1R23_CTRL_BANK_MASK 0x03 // Bits 0-1: Dictionary ROM bank select.
|
||||
|
||||
// Load state.
|
||||
enum t_MZ1R23LoadCtrl
|
||||
{
|
||||
MZ1R23_NO_FILE = 0, // No file specified.
|
||||
MZ1R23_NOT_LOADED = 1, // File specified but not yet loaded.
|
||||
MZ1R23_LOADED = 2 // ROM loaded and ready.
|
||||
};
|
||||
|
||||
// Struct for the MZ-1R23/MZ-1R24 board state.
|
||||
typedef struct
|
||||
{
|
||||
// Control registers.
|
||||
uint8_t ctrlReg; // B8h control register value.
|
||||
uint32_t readAddr; // Current byte-level read address (auto-increments on read).
|
||||
|
||||
// Kanji ROM.
|
||||
uint8_t *kanjiRom; // Kanji ROM data buffer.
|
||||
uint32_t kanjiRomSize; // Actual size of loaded kanji ROM.
|
||||
char *kanjiFileName; // Filename on SD card.
|
||||
enum t_MZ1R23LoadCtrl kanjiLoadCtl; // Load state.
|
||||
bool kanjiLoadPending; // True while inter-core load is in progress.
|
||||
|
||||
// Dictionary ROM (optional).
|
||||
uint8_t *dictRom; // Dictionary ROM data buffer (NULL if not present).
|
||||
uint32_t dictRomSize; // Actual size of loaded dictionary ROM.
|
||||
char *dictFileName; // Filename on SD card.
|
||||
enum t_MZ1R23LoadCtrl dictLoadCtl; // Load state.
|
||||
bool dictLoadPending; // True while inter-core load is in progress.
|
||||
} t_MZ1R23;
|
||||
|
||||
// Public prototypes.
|
||||
uint8_t MZ1R23_Init(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
uint8_t MZ1R23_Reset(t_Z80CPU *cpu);
|
||||
uint8_t MZ1R23_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ1R23_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ1R23_IO_Ctrl(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1R23_IO_Data(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
int MZ1R23_ProcessQueueResponses(t_Z80CPU *cpu);
|
||||
|
||||
#endif // MZ1R23_H
|
||||
69
projects/tzpuPico/src/include/drivers/Sharp/MZ-1R37.h
vendored
Normal file
69
projects/tzpuPico/src/include/drivers/Sharp/MZ-1R37.h
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ-1R37.h
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - MZ-1R37 640K EMM
|
||||
// This file contains setup and driver to emulate the MZ-1R37 640KB Expanded Memory
|
||||
// Manager board.
|
||||
//
|
||||
// I/O ports (no auto-increment — all addressing via OUT (C),A / IN A,(C)):
|
||||
// ACh (W): Address latch — sets address[19:8].
|
||||
// Bits from OUT (C),A: B register = address[19:16] (upper nibble)
|
||||
// A register = address[15:8] (mid byte)
|
||||
// ADh (W): Data write — address[7:0] from B register, data from A register.
|
||||
// ADh (R): Data read — address[7:0] from B register, data returned in A register.
|
||||
//
|
||||
// 20-bit address space (1MB), 640KB physically populated.
|
||||
// Addresses above 640KB (0xA0000) return 0xFF on read.
|
||||
//
|
||||
// Note from I/O map: "There is no automatic address increment."
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MZ1R37_H
|
||||
#define MZ1R37_H
|
||||
|
||||
// Constants.
|
||||
#define MZ1R37_RAM_SIZE 655360 // 640KB = 640 × 1024 bytes.
|
||||
#define MZ1R37_ADDR_MASK 0xFFFFF // 20-bit address space (1MB).
|
||||
#define MZ1R37_DEFAULT_BASE 0xAC // Default I/O base address.
|
||||
#define MZ1R37_PORT_COUNT 2 // Number of consecutive I/O ports used.
|
||||
|
||||
// Struct for the MZ-1R37 EMM board state.
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *ram; // 640KB RAM buffer.
|
||||
uint32_t addrLatch; // Latched address bits [19:8] (from port ACh write).
|
||||
uint8_t ioBase; // I/O base address.
|
||||
} t_MZ1R37;
|
||||
|
||||
// Public prototypes.
|
||||
uint8_t MZ1R37_Init(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
uint8_t MZ1R37_Reset(t_Z80CPU *cpu);
|
||||
uint8_t MZ1R37_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ1R37_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ1R37_IO_AddrLatch(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1R37_IO_Data(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
|
||||
#endif // MZ1R37_H
|
||||
134
projects/tzpuPico/src/include/drivers/Sharp/MZ1500.h
vendored
Normal file
134
projects/tzpuPico/src/include/drivers/Sharp/MZ1500.h
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ1500.h
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - Sharp MZ-1500
|
||||
// This file contains setup and drivers to mimic a Sharp MZ-1500 machine internally to
|
||||
// increase speed through use of internal RAM and provide virtual expansion drivers.
|
||||
// The MZ-1500 is a superset of the MZ-700 with an inbuilt QD drive instead of CMT,
|
||||
// a PCG (Programmable Character Generator), stereo PSG sound (SN76489AN), and a
|
||||
// Z80PIO-based printer interface replacing the MZ-700's simple latch.
|
||||
// Dip switches select MZ-700 mode or MZ-1500 mode at boot.
|
||||
//
|
||||
// MZ-1500 memory map:
|
||||
// 0000-0FFF : Monitor ROM (or DRAM via bank switching)
|
||||
// 1000-CFFF : DRAM
|
||||
// D000-D3FF : Text VRAM
|
||||
// D400-D7FF : PCG VRAM 1 (MZ-1500 only, PCGN[7:0])
|
||||
// D800-DBFF : Attribute VRAM (FC, BC, ATB)
|
||||
// DC00-DFFF : PCG VRAM 2 (MZ-1500 only, PCGN[9:8] + PCGE)
|
||||
// E000-E003 : 8255 PPI
|
||||
// E004-E007 : 8253 PIT
|
||||
// E008 : LS367 (joystick, HBLK, TEMPO) / GATE
|
||||
// E009 : PSG (simultaneous L+R write, MZ-1500 only)
|
||||
// E010-E012 : PCG-RAM control (MZ-1500 only)
|
||||
// E800-EFFF : User ROM / expansion ROM
|
||||
// F000-FFFF : PCG-RAM/CG-ROM bank (MZ-1500) or FDD ROM (MZ-700)
|
||||
//
|
||||
// MZ-1500 I/O ports (beyond MZ-700):
|
||||
// E5h (W) : PCG bank open + select (bits 0-1: 00=CGROM, 01=blue, 10=red, 11=green)
|
||||
// E6h (W) : PCG bank close
|
||||
// E9h (W) : PSG SN76489AN simultaneous (both L+R channels)
|
||||
// F0h (W) : Text/PCG priority + PCG display enable
|
||||
// F1h (W) : Palette
|
||||
// F2h (W) : PSG SN76489AN left channel
|
||||
// F3h (W) : PSG SN76489AN right channel
|
||||
// F4h-F7h : QD I/F via Z80A-SIO (inbuilt, same as MZ-1E19)
|
||||
// FCh-FFh : Printer via Z80PIO (replaces MZ-700 simple printer latch)
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write based on MZ-700 driver.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MZ1500_H
|
||||
#define MZ1500_H
|
||||
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
#include "drivers/Sharp/QDDrive.h"
|
||||
|
||||
// Constants.
|
||||
#define MZ1500_MEMBANK_0 0 // Primary RAM bank.
|
||||
#define MZ1500_MEMBANK_1 1 // RAM bank to use for paging in RAM.
|
||||
#define MZ1500_UPPERMEM_BLOCKS 64 // Number of blocks in the upper paged 12K RAM.
|
||||
|
||||
// PCG constants.
|
||||
#define MZ1500_PCG_BANK_CGROM 0 // CG-ROM (character generator ROM).
|
||||
#define MZ1500_PCG_BANK_BLUE 1 // PCG blue colour plane.
|
||||
#define MZ1500_PCG_BANK_RED 2 // PCG red colour plane.
|
||||
#define MZ1500_PCG_BANK_GREEN 3 // PCG green colour plane.
|
||||
#define MZ1500_PCG_BANK_BLOCKS 16 // Blocks for F000-FFFF (16 × 512B = 8KB).
|
||||
|
||||
// Machine sub-mode — selectable by dip switch on real hardware.
|
||||
typedef enum
|
||||
{
|
||||
MZ1500_MODE_MZ700 = 0, // Running in MZ-700 compatibility mode.
|
||||
MZ1500_MODE_MZ1500 = 1 // Running in native MZ-1500 mode.
|
||||
} t_MZ1500Mode;
|
||||
|
||||
// Saved memioPtr entry for DRAM/MMIO switching.
|
||||
// On real hardware, virtual devices (RFS etc.) at 0xE800-0xEFFF are not on the bus when DRAM is
|
||||
// selected. We save and NULL their memioPtr entries on E1, restore on E3/E4.
|
||||
#define MZ1500_MAX_SAVED_MEMIO 32
|
||||
#ifndef SAVED_MEMIO_ENTRY_DEFINED
|
||||
#define SAVED_MEMIO_ENTRY_DEFINED
|
||||
typedef struct
|
||||
{
|
||||
uint16_t addr;
|
||||
t_MemoryFunc func;
|
||||
} t_savedMemioEntry;
|
||||
#endif
|
||||
|
||||
// Struct for controlling the virtual MZ-1500 state.
|
||||
typedef struct
|
||||
{
|
||||
t_MZ1500Mode mode; // MZ-700 or MZ-1500 sub-mode.
|
||||
uint8_t regCtrl; // Control register.
|
||||
bool loDRAMen; // Lower bank 0000:0FFF DRAM enabled, else monitor.
|
||||
bool hiDRAMen; // Higher bank D000:FFFF DRAM enabled, else memory mapped I/O.
|
||||
bool inhibit; // Inhibit access to upper 12K address range.
|
||||
uint32_t upmembankPtr[MZ1500_UPPERMEM_BLOCKS]; // 12K mirror pointers for bank switching.
|
||||
t_savedMemioEntry upMemio[MZ1500_MAX_SAVED_MEMIO]; // Saved memioPtr entries for DRAM switching.
|
||||
uint8_t upMemioCount; // Number of saved memioPtr entries.
|
||||
|
||||
// MZ-1500-specific PCG state.
|
||||
bool pcgBankOpen; // True when PCG bank is mapped at F000-FFFF.
|
||||
uint8_t pcgBankSelect; // Active PCG bank (0=CGROM, 1=blue, 2=red, 3=green).
|
||||
uint8_t pcgPriority; // Text/PCG priority register (F0h).
|
||||
uint8_t pcgPalette; // Palette register (F1h).
|
||||
uint32_t pcgSavedBankPtr[MZ1500_PCG_BANK_BLOCKS]; // Saved F000-FFFF bank ptrs when PCG bank is open.
|
||||
} t_MZ1500Ctrl;
|
||||
|
||||
// Private prototypes.
|
||||
void MZ1500_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len);
|
||||
void MZ1500_readROMData(void *ctx, void *cfg, char *buf, int len);
|
||||
uint8_t MZ1500_IO_MemoryBankPorts(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1500_IO_SIO_NotPresent(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1500_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1500_IO_PSG(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1500_IO_PCG(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1500_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ1500_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ1500_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ1500_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName);
|
||||
|
||||
#endif // MZ1500_H
|
||||
67
projects/tzpuPico/src/include/drivers/Sharp/MZ2200.h
vendored
Normal file
67
projects/tzpuPico/src/include/drivers/Sharp/MZ2200.h
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ2200.h
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - Sharp MZ-2200
|
||||
// This file contains setup and drivers to mimic a Sharp MZ-2200 machine internally to
|
||||
// increase speed through use of internal RAM and provide virtual expansion drivers.
|
||||
// Based on the MZ80A driver but adapted for the MZ-2200 memory map and peripherals.
|
||||
//
|
||||
// MZ-2200 I/O map (I/O port space, not memory-mapped):
|
||||
// 0xD8-0xDE : MB8866 FDC
|
||||
// 0xE0-0xE3 : 8255 PPI (cassette, BST/NST memory mode)
|
||||
// 0xE4-0xE7 : 8253 PIT (timers)
|
||||
// 0xE8-0xEB : Z80 PIO (keyboard, VRAM paging)
|
||||
// 0xF4-0xF7 : Colour CRT / Graphics VRAM bank select
|
||||
//
|
||||
// MZ-2200 memory map:
|
||||
// Boot (BST): 0x0000-0x7FFF = IPL ROM, 0x8000-0xFFFF = RAM
|
||||
// Normal (NST): 0x0000-0xFFFF = RAM (0x8000 content swapped to 0x0000)
|
||||
// VRAM (PIO A7=1): 0xD000-0xD7FF = Character VRAM (A6=1)
|
||||
// 0xC000-0xFFFF = Graphics VRAM (A6=0, bank via port 0xF7)
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write based on MZ80A driver.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MZ2200_H
|
||||
#define MZ2200_H
|
||||
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
|
||||
// Constants.
|
||||
#define MZ2200_MEMBANK_0 0 // Primary RAM bank (IPL ROM + RAM in boot, swapped RAM in normal).
|
||||
#define MZ2200_MEMBANK_1 1 // Secondary RAM bank (0x8000-0xFFFF in normal mode).
|
||||
|
||||
// Private prototypes.
|
||||
void MZ2200_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len);
|
||||
void MZ2200_readROMData(void *ctx, void *cfg, char *buf, int len);
|
||||
uint8_t MZ2200_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ2200_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ2200_IO_PPI(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ2200_IO_PIO(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ2200_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ2200_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ2200_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName);
|
||||
|
||||
#endif // MZ2200_H
|
||||
@@ -41,11 +41,14 @@
|
||||
// On real hardware, virtual devices (RFS etc.) at 0xE800-0xEFFF are not on the bus when DRAM is
|
||||
// selected. We save and NULL their memioPtr entries on E1, restore on E3/E4.
|
||||
#define MZ700_MAX_SAVED_MEMIO 32
|
||||
#ifndef SAVED_MEMIO_ENTRY_DEFINED
|
||||
#define SAVED_MEMIO_ENTRY_DEFINED
|
||||
typedef struct
|
||||
{
|
||||
uint16_t addr;
|
||||
t_MemoryFunc func;
|
||||
} t_savedMemioEntry;
|
||||
#endif
|
||||
|
||||
// Struct for controlling the virtual MZ700 state.
|
||||
typedef struct
|
||||
|
||||
69
projects/tzpuPico/src/include/drivers/Sharp/PIO-3034.h
vendored
Normal file
69
projects/tzpuPico/src/include/drivers/Sharp/PIO-3034.h
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: PIO-3034.h
|
||||
// Created: May 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - PIO-3034 320K EMM (IO DATA)
|
||||
// This file contains setup and driver to emulate the PIO-3034 320KB Expanded Memory
|
||||
// Manager board by IO DATA.
|
||||
//
|
||||
// I/O ports (base address selectable via DIP switch, default 00h):
|
||||
// Base+0 (W): Address counter bits [7:0]
|
||||
// Base+1 (W): Address counter bits [15:8]
|
||||
// Base+2 (W): Address counter bits [18:16]
|
||||
// Base+3 (R): Data read, address counter auto-increment
|
||||
// Base+3 (W): Data write, address counter auto-increment
|
||||
//
|
||||
// 19-bit address space (512KB), 320KB physically populated.
|
||||
// Addresses above 320KB (0x50000) return 0xFF on read.
|
||||
//
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: May 2026 v1.0 - Initial write.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PIO3034_H
|
||||
#define PIO3034_H
|
||||
|
||||
// Constants.
|
||||
#define PIO3034_RAM_SIZE 327680 // 320KB = 320 × 1024 bytes.
|
||||
#define PIO3034_ADDR_MASK 0x7FFFF // 19-bit address space (512KB).
|
||||
#define PIO3034_DEFAULT_BASE 0x00 // Default I/O base address.
|
||||
#define PIO3034_PORT_COUNT 4 // Number of consecutive I/O ports used.
|
||||
|
||||
// Struct for the PIO-3034 EMM board state.
|
||||
typedef struct
|
||||
{
|
||||
uint8_t *ram; // 320KB RAM buffer.
|
||||
uint32_t addr; // 19-bit address counter.
|
||||
uint8_t ioBase; // I/O base address (DIP-switch selectable).
|
||||
} t_PIO3034;
|
||||
|
||||
// Public prototypes.
|
||||
uint8_t PIO3034_Init(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
uint8_t PIO3034_Reset(t_Z80CPU *cpu);
|
||||
uint8_t PIO3034_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t PIO3034_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t PIO3034_IO_AddrLo(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t PIO3034_IO_AddrMid(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t PIO3034_IO_AddrHi(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t PIO3034_IO_Data(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
|
||||
#endif // PIO3034_H
|
||||
@@ -52,8 +52,12 @@
|
||||
// Constants
|
||||
|
||||
// Sharp QD values
|
||||
#define MAX_QD_SIZE 61455U // Standard QD disk image size (~60KB)
|
||||
#define MAX_QD_FORMAT_SIZE 64139U // Formatted size of a QD.
|
||||
#define MAX_QD_SIZE 61455U // Standard QD disk image size (~60KB, compact format)
|
||||
#define MAX_QD_FORMAT_SIZE 64139U // Formatted size of a QD (compact).
|
||||
#define MAX_QDF_SIZE 81920U // Japanese standard QDF format (full formatted track, 80KB)
|
||||
#define QDF_HEADER_SIZE 16U // QDF file header size ("-QD format-" + padding)
|
||||
#define QDF_FILE_SIZE (QDF_HEADER_SIZE + MAX_QDF_SIZE) // Total QDF file size (81936 bytes)
|
||||
#define QDF_SIGNATURE "-QD format-" // QDF file header signature (11 bytes)
|
||||
#ifndef ROTATION_US
|
||||
#define ROTATION_US 200000ULL // 300 RPM spiral track rotation period
|
||||
#endif
|
||||
@@ -137,8 +141,10 @@ typedef struct
|
||||
uint32_t bytesWritten; // Bytes written in current block
|
||||
|
||||
// Disk & file system
|
||||
uint8_t *diskImage; // Pointer to 61455-byte disk buffer
|
||||
uint32_t diskSize; // Size of disk image (always 61455)
|
||||
uint8_t *diskImage; // Pointer to active disk data (may be offset from diskBuf for QDF).
|
||||
uint8_t *diskBuf; // Original allocated buffer base (used for loads).
|
||||
uint32_t diskBufSize; // Allocated buffer size.
|
||||
uint32_t diskSize; // Active disk image size.
|
||||
char *filename; // Current disk filename (for SD save/load)
|
||||
|
||||
// Inter-core communication
|
||||
|
||||
22
projects/tzpuPico/src/include/intercore.h
vendored
22
projects/tzpuPico/src/include/intercore.h
vendored
@@ -50,7 +50,15 @@ typedef enum
|
||||
MSG_LOAD_COMPLETE, // Previous Load request completed.
|
||||
MSG_WRITE_COMPLETE, // Previous Write request completed.
|
||||
MSG_WRITE_OP_SCHEDULED, // Previous Scheduled Write request completed.
|
||||
MSG_READ_COMPLETE // Previous Read request completed.
|
||||
MSG_READ_COMPLETE, // Previous Read request completed.
|
||||
|
||||
// Network operations (Celestite W5100 emulation)
|
||||
MSG_NET_SOCKET, // Socket operation request (open/connect/listen/close)
|
||||
MSG_NET_SEND, // Send data to socket
|
||||
MSG_NET_RECV, // Receive data from socket (poll)
|
||||
MSG_NET_CFG, // Get network config from ESP32
|
||||
MSG_NET_PING, // ICMP ping request
|
||||
MSG_NET_COMPLETE // Network operation complete (response)
|
||||
} t_MsgType;
|
||||
|
||||
typedef struct
|
||||
@@ -86,6 +94,18 @@ typedef struct
|
||||
bool success;
|
||||
uint32_t size;
|
||||
} response; // For completions
|
||||
struct
|
||||
{
|
||||
uint8_t sockNum; // Socket number (0-3)
|
||||
uint8_t operation; // Socket operation (W5100 Sn_CR value)
|
||||
uint8_t protocol; // Protocol (1=TCP, 2=UDP)
|
||||
uint8_t status; // Result status / new socket state
|
||||
uint32_t ipAddr; // IP address (network byte order)
|
||||
uint16_t port; // Port number (host byte order)
|
||||
void *buffer; // Data buffer for send/recv
|
||||
uint32_t size; // Data size
|
||||
uint32_t rtt; // Ping RTT in ms
|
||||
} netOp; // For MSG_NET_* operations
|
||||
};
|
||||
} t_CoreMsg;
|
||||
|
||||
|
||||
8
projects/tzpuPico/src/include/ipc_protocol.h
vendored
8
projects/tzpuPico/src/include/ipc_protocol.h
vendored
@@ -48,6 +48,14 @@
|
||||
#define IPCF_CMD_RFD 0x08 // Read floppy disk image file
|
||||
#define IPCF_CMD_RQD 0x09 // Read quick-disk image file
|
||||
#define IPCF_CMD_RRF 0x0A // Read RAM-file backup image
|
||||
#define IPCF_CMD_DIR 0x0B // Read directory listing (returns text in payload)
|
||||
|
||||
// Network commands (Celestite W5100 emulation)
|
||||
#define IPCF_CMD_NET_CFG 0x10 // Get ESP32 network config (IP, gateway, subnet, MAC)
|
||||
#define IPCF_CMD_NET_SOCK 0x11 // Socket operation (open/connect/listen/close/discon)
|
||||
#define IPCF_CMD_NET_SEND 0x12 // Send data to socket (payload = data to send)
|
||||
#define IPCF_CMD_NET_RECV 0x13 // Receive data from socket (response payload = data)
|
||||
#define IPCF_CMD_NET_PING 0x14 // ICMP echo request (ping)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Status codes (t_IpcFrameHdr::status — response frames only)
|
||||
|
||||
@@ -60,6 +60,8 @@
|
||||
#include "Z80CPU.h"
|
||||
#include "FSPI.h"
|
||||
#include "ESP.h"
|
||||
#include "drivers/Sharp/Celestite.h"
|
||||
extern t_Celestite *celestiteCtrl; // Defined in Celestite.c, used for direct shared results.
|
||||
#include "rp2350.h"
|
||||
#include "flash_ram.h"
|
||||
#include "usb_bridge.h"
|
||||
@@ -986,6 +988,7 @@ void processInterCoreCommands(void)
|
||||
plogf("IC:%d ", (int) qmsg.type);
|
||||
debugf("IC_REQ: type=%d ctx=%p\r\n", (int) qmsg.type, qmsg.context);
|
||||
t_CoreMsg response = {.context = qmsg.context, .requestId = qmsg.requestId, .response.success = false, .response.size = 0};
|
||||
bool skipResponse = false; // Set true for fire-and-forget operations.
|
||||
|
||||
response.requestId = qmsg.requestId;
|
||||
response.response.success = false;
|
||||
@@ -1232,6 +1235,104 @@ void processInterCoreCommands(void)
|
||||
responseType = MSG_LOAD_COMPLETE;
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Network operations (Celestite W5100 emulation).
|
||||
if (qmsg.type == MSG_NET_CFG)
|
||||
{
|
||||
// Write NET_CFG result to shared struct (bypasses responseQueue).
|
||||
uint8_t ipBuf[4], gwBuf[4], subBuf[4], macBuf[6];
|
||||
if (celestiteCtrl && ESP_netCfg(ipBuf, gwBuf, subBuf, macBuf))
|
||||
{
|
||||
memcpy(celestiteCtrl->netCfgData, ipBuf, 4);
|
||||
memcpy(celestiteCtrl->netCfgData + 4, gwBuf, 4);
|
||||
memcpy(celestiteCtrl->netCfgData + 8, subBuf, 4);
|
||||
memcpy(celestiteCtrl->netCfgData + 12, macBuf, 6);
|
||||
celestiteCtrl->netCfgSuccess = true;
|
||||
__dmb();
|
||||
celestiteCtrl->netCfgReady = true;
|
||||
debugf("NET_CFG: IP=%d.%d.%d.%d GW=%d.%d.%d.%d\r\n",
|
||||
ipBuf[0], ipBuf[1], ipBuf[2], ipBuf[3],
|
||||
gwBuf[0], gwBuf[1], gwBuf[2], gwBuf[3]);
|
||||
}
|
||||
else if (celestiteCtrl)
|
||||
{
|
||||
celestiteCtrl->netCfgSuccess = false;
|
||||
celestiteCtrl->netCfgReady = true;
|
||||
}
|
||||
skipResponse = true;
|
||||
}
|
||||
else if (qmsg.type == MSG_NET_SOCKET)
|
||||
{
|
||||
uint8_t sockStatus = 0;
|
||||
uint8_t op = qmsg.netOp.operation;
|
||||
if (ESP_netSocket(qmsg.netOp.sockNum, op, qmsg.netOp.protocol,
|
||||
qmsg.netOp.ipAddr, qmsg.netOp.port, &sockStatus))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
debugf("NET_SOCK: s=%d op=%d status=0x%02x\r\n",
|
||||
(int)qmsg.netOp.sockNum, (int)op, (int)sockStatus);
|
||||
// Write result directly to shared struct (bypasses responseQueue).
|
||||
// Include drivers/Sharp/Celestite.h provides t_Celestite*.
|
||||
{
|
||||
if (celestiteCtrl)
|
||||
{
|
||||
celestiteCtrl->netResultSock = qmsg.netOp.sockNum;
|
||||
celestiteCtrl->netResultStatus = sockStatus;
|
||||
celestiteCtrl->netResultSuccess = success;
|
||||
celestiteCtrl->netResultOp = op;
|
||||
__dmb(); // Ensure all fields written before setting flag.
|
||||
celestiteCtrl->netResultReady = true;
|
||||
}
|
||||
}
|
||||
skipResponse = true; // Don't also push to queue.
|
||||
}
|
||||
else if (qmsg.type == MSG_NET_SEND)
|
||||
{
|
||||
uint32_t sent = 0;
|
||||
ESP_netSend(qmsg.netOp.sockNum, (uint8_t *)qmsg.netOp.buffer,
|
||||
qmsg.netOp.size, &sent);
|
||||
debugf("NET_SEND: s=%d len=%lu sent=%lu\r\n",
|
||||
(int)qmsg.netOp.sockNum, (unsigned long)qmsg.netOp.size, (unsigned long)sent);
|
||||
skipResponse = true; // Fire-and-forget.
|
||||
}
|
||||
else if (qmsg.type == MSG_NET_RECV)
|
||||
{
|
||||
uint32_t recvd = 0;
|
||||
ESP_netRecv(qmsg.netOp.sockNum, (uint8_t *)qmsg.netOp.buffer,
|
||||
qmsg.netOp.size, &recvd);
|
||||
if (celestiteCtrl)
|
||||
{
|
||||
if (recvd > 0)
|
||||
{
|
||||
// Data available — signal to IDM handler.
|
||||
celestiteCtrl->netRecvLen = recvd;
|
||||
celestiteCtrl->netRecvSock = qmsg.netOp.sockNum;
|
||||
__dmb();
|
||||
celestiteCtrl->netRecvReady = true;
|
||||
debugf("NET_RECV: s=%d len=%lu\r\n", (int)qmsg.netOp.sockNum, (unsigned long)recvd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No data yet — clear pending so next RX_RSR poll retries.
|
||||
celestiteCtrl->netRecvPending[qmsg.netOp.sockNum] = false;
|
||||
}
|
||||
}
|
||||
skipResponse = true;
|
||||
}
|
||||
else if (qmsg.type == MSG_NET_PING)
|
||||
{
|
||||
uint32_t rtt = 0xFFFFFFFF;
|
||||
if (celestiteCtrl && ESP_netPing(qmsg.netOp.ipAddr, &rtt))
|
||||
{
|
||||
// Write RTT directly to memoReg (read by Z80 via port 67h).
|
||||
celestiteCtrl->memoReg = (rtt < 255) ? (uint8_t)rtt : 0xFE;
|
||||
debugf("PING: RTT=%d ms\r\n", (int)rtt);
|
||||
}
|
||||
skipResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1243,7 +1344,7 @@ void processInterCoreCommands(void)
|
||||
// No response for scheduled events as they are rapid and many. Scheduled events are for storing
|
||||
// data where the host writes adhoc so it cuts down on physical writes, only when the host has stopped
|
||||
// writing is the data flushed.
|
||||
if (qmsg.type != MSG_WRITE_FILE_SCHEDULED)
|
||||
if (qmsg.type != MSG_WRITE_FILE_SCHEDULED && !skipResponse)
|
||||
{
|
||||
bootStage(BOOTP_IC_RESPONSE);
|
||||
response.type = responseType;
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.491
|
||||
2.534
|
||||
|
||||
541
projects/tzpuPico/src/test/Celestite/celestite_stress.asm
Normal file
541
projects/tzpuPico/src/test/Celestite/celestite_stress.asm
Normal file
@@ -0,0 +1,541 @@
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- Celestite LAN Board — Network Stress Test
|
||||
;- All output on MZ-1500 screen (VRAM D000-D3FF). Loops continuously.
|
||||
;- Also mirrors summary to debug port (50h).
|
||||
;- D800-DFFF colour attributes are NOT touched.
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
|
||||
PORT_DBG EQU 050h
|
||||
PORT_MR EQU 060h
|
||||
PORT_AR0 EQU 061h
|
||||
PORT_AR1 EQU 062h
|
||||
PORT_DR EQU 063h
|
||||
PORT_PING EQU 067h
|
||||
|
||||
CMD_OPEN EQU 001h
|
||||
CMD_CLOSE EQU 010h
|
||||
ST_ESTAB EQU 017h
|
||||
ST_CLOSED EQU 000h
|
||||
|
||||
TPSTART EQU 010F0h
|
||||
MEMSTART EQU 01200h
|
||||
VRAM EQU 0D000h
|
||||
VL EQU 40
|
||||
|
||||
ORG TPSTART
|
||||
ATRB: DB 001h
|
||||
NAME: DB "CELESTITE STRESS", 0Dh
|
||||
SIZE: DW MEND - MEMSTART
|
||||
DTADR: DW MEMSTART
|
||||
EXADR: DW MEMSTART
|
||||
COMNT: DS 104
|
||||
|
||||
ORG MEMSTART
|
||||
|
||||
START: LD SP, 01180h
|
||||
;- Init W5100.
|
||||
LD A, 080h
|
||||
OUT (PORT_MR), A
|
||||
LD A, 003h
|
||||
OUT (PORT_MR), A
|
||||
;- Write gateway IP to GAR.
|
||||
XOR A
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, 192
|
||||
OUT (PORT_DR), A
|
||||
LD A, 168
|
||||
OUT (PORT_DR), A
|
||||
LD A, 1
|
||||
OUT (PORT_DR), A
|
||||
LD A, 1
|
||||
OUT (PORT_DR), A
|
||||
|
||||
LD HL, 0
|
||||
LD (LOOPCNT), HL
|
||||
|
||||
;- ============================================================
|
||||
MAINLP: ;- Clear text VRAM only (D000-D3E7, 1000 bytes). Leave D800 colours alone.
|
||||
LD HL, VRAM
|
||||
LD BC, 1000
|
||||
CLR: LD (HL), 000h
|
||||
INC HL
|
||||
DEC BC
|
||||
LD A, B
|
||||
OR C
|
||||
JR NZ, CLR
|
||||
|
||||
LD HL, (LOOPCNT)
|
||||
INC HL
|
||||
LD (LOOPCNT), HL
|
||||
|
||||
;- Line 0: Title.
|
||||
LD HL, VRAM
|
||||
LD DE, S_TITLE
|
||||
CALL PVRAM
|
||||
|
||||
;- Line 1: Loop count.
|
||||
LD HL, VRAM + VL
|
||||
LD DE, S_LOOP
|
||||
CALL PVRAM
|
||||
LD A, (LOOPCNT+1)
|
||||
CALL PHEXV
|
||||
LD A, (LOOPCNT)
|
||||
CALL PHEXV
|
||||
|
||||
;- ---- PING (line 3) ----
|
||||
LD HL, VRAM + VL*3
|
||||
LD DE, S_PING
|
||||
CALL PVRAM
|
||||
XOR A
|
||||
OUT (PORT_PING), A
|
||||
LD B, 250
|
||||
PNGW: PUSH BC
|
||||
IN A, (PORT_PING)
|
||||
POP BC
|
||||
CP 0FFh
|
||||
JR NZ, PNGGOT
|
||||
LD DE, 12000
|
||||
PNGD: DEC DE
|
||||
LD A, D
|
||||
OR E
|
||||
JR NZ, PNGD
|
||||
DJNZ PNGW
|
||||
LD HL, VRAM + VL*3 + 28
|
||||
LD DE, S_TOUT
|
||||
CALL PVRAM
|
||||
JR PNGDN
|
||||
PNGGOT: LD HL, VRAM + VL*3 + 28
|
||||
CALL PDECV
|
||||
LD DE, S_MS
|
||||
CALL PVRAM
|
||||
PNGDN:
|
||||
|
||||
;- ---- TCP CONNECT (line 4) ----
|
||||
LD HL, VRAM + VL*4
|
||||
LD DE, S_TCP
|
||||
CALL PVRAM
|
||||
;- Close.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, CMD_CLOSE
|
||||
OUT (PORT_DR), A
|
||||
;- Mode = TCP.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
XOR A
|
||||
OUT (PORT_AR1), A
|
||||
LD A, 001h
|
||||
OUT (PORT_DR), A
|
||||
;- Dest IP.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 00Ch
|
||||
OUT (PORT_AR1), A
|
||||
LD A, 192
|
||||
OUT (PORT_DR), A
|
||||
LD A, 168
|
||||
OUT (PORT_DR), A
|
||||
LD A, 1
|
||||
OUT (PORT_DR), A
|
||||
LD A, 1
|
||||
OUT (PORT_DR), A
|
||||
;- Port 80.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 010h
|
||||
OUT (PORT_AR1), A
|
||||
XOR A
|
||||
OUT (PORT_DR), A
|
||||
LD A, 80
|
||||
OUT (PORT_DR), A
|
||||
;- OPEN + CONNECT.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, CMD_OPEN
|
||||
OUT (PORT_DR), A
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, 004h
|
||||
OUT (PORT_DR), A
|
||||
;- Wait.
|
||||
LD B, 250
|
||||
TCPW: PUSH BC
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 003h
|
||||
OUT (PORT_AR1), A
|
||||
IN A, (PORT_DR)
|
||||
POP BC
|
||||
CP ST_ESTAB
|
||||
JR Z, TCPOK
|
||||
CP ST_CLOSED
|
||||
JP Z, TCPFL
|
||||
LD DE, 20000
|
||||
TCPD: DEC DE
|
||||
LD A, D
|
||||
OR E
|
||||
JR NZ, TCPD
|
||||
DJNZ TCPW
|
||||
TCPFL: LD HL, VRAM + VL*4 + 28
|
||||
LD DE, S_FAIL
|
||||
CALL PVRAM
|
||||
JP HTTPDN
|
||||
TCPOK: LD HL, VRAM + VL*4 + 28
|
||||
LD DE, S_PASS
|
||||
CALL PVRAM
|
||||
|
||||
;- ---- HTTP GET (line 5) ----
|
||||
LD HL, VRAM + VL*5
|
||||
LD DE, S_HTTP
|
||||
CALL PVRAM
|
||||
;- Reset TX pointers (TX_RD=0, TX_WR=0 at 0x0422-0x0425).
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 022h
|
||||
OUT (PORT_AR1), A
|
||||
XOR A
|
||||
OUT (PORT_DR), A ; TX_RD high = 0.
|
||||
OUT (PORT_DR), A ; TX_RD low = 0.
|
||||
OUT (PORT_DR), A ; TX_WR high = 0.
|
||||
OUT (PORT_DR), A ; TX_WR low = 0.
|
||||
;- Write request to TX buffer.
|
||||
LD A, 040h
|
||||
OUT (PORT_AR0), A
|
||||
XOR A
|
||||
OUT (PORT_AR1), A
|
||||
LD HL, HTTPREQ
|
||||
HTTPWR: LD A, (HL)
|
||||
OR A
|
||||
JR Z, HTTPWD
|
||||
OUT (PORT_DR), A
|
||||
INC HL
|
||||
JR HTTPWR
|
||||
HTTPWD: LD DE, HTTPREQ
|
||||
OR A
|
||||
SBC HL, DE
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 024h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, H
|
||||
OUT (PORT_DR), A
|
||||
LD A, L
|
||||
OUT (PORT_DR), A
|
||||
;- SEND.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, 020h
|
||||
OUT (PORT_DR), A
|
||||
;- Poll RX_RSR.
|
||||
LD B, 250
|
||||
HTTPRW: PUSH BC
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 026h
|
||||
OUT (PORT_AR1), A
|
||||
IN A, (PORT_DR)
|
||||
LD H, A
|
||||
IN A, (PORT_DR)
|
||||
LD L, A
|
||||
POP BC
|
||||
LD A, H
|
||||
OR L
|
||||
JR NZ, HTTPGOT
|
||||
LD DE, 20000
|
||||
HTTPRD: DEC DE
|
||||
LD A, D
|
||||
OR E
|
||||
JR NZ, HTTPRD
|
||||
DJNZ HTTPRW
|
||||
LD HL, VRAM + VL*5 + 28
|
||||
LD DE, S_TOUT
|
||||
CALL PVRAM
|
||||
JP HTTPDN
|
||||
|
||||
HTTPGOT: ;- Show hex count on line 5.
|
||||
PUSH HL
|
||||
LD HL, VRAM + VL*5 + 28
|
||||
POP DE
|
||||
LD A, D
|
||||
CALL PHEXV
|
||||
LD A, E
|
||||
CALL PHEXV
|
||||
LD DE, S_BYT
|
||||
CALL PVRAM
|
||||
|
||||
;- Read HTTP response and display on screen lines 7-24 (18 lines).
|
||||
LD A, 060h
|
||||
OUT (PORT_AR0), A
|
||||
XOR A
|
||||
OUT (PORT_AR1), A
|
||||
LD HL, VRAM + VL*7
|
||||
LD C, 200 ; Max bytes to display.
|
||||
HTDISP: IN A, (PORT_DR)
|
||||
CP 00Dh
|
||||
JR Z, HTNXT ; Skip CR.
|
||||
CP 00Ah
|
||||
JR Z, HTNL ; LF = new line.
|
||||
;- Convert and write to VRAM.
|
||||
PUSH HL
|
||||
PUSH DE
|
||||
CALL A2DSP
|
||||
POP DE
|
||||
POP HL
|
||||
LD (HL), A
|
||||
INC HL
|
||||
JR HTCHK
|
||||
HTNL: ;- Advance to next 40-char line. Round HL up to next VL boundary.
|
||||
;- New HL = ((HL - VRAM + VL) / VL) * VL + VRAM.
|
||||
PUSH DE
|
||||
LD DE, VRAM
|
||||
OR A
|
||||
SBC HL, DE ; HL = offset from VRAM.
|
||||
LD A, L
|
||||
ADD A, VL
|
||||
JR NC, HTNL2
|
||||
INC H
|
||||
HTNL2: LD L, A
|
||||
;- Round down to VL boundary: offset = (offset / 40) * 40.
|
||||
;- Approximate: clear low bits. 40 = 8*5, not power of 2. Use division.
|
||||
;- Simple: subtract (offset mod 40). Use repeated subtraction.
|
||||
LD A, L
|
||||
LD E, A
|
||||
LD A, H
|
||||
LD D, A
|
||||
HTNL3: LD A, D
|
||||
OR A
|
||||
JR NZ, HTNL4
|
||||
LD A, E
|
||||
CP VL
|
||||
JR C, HTNL5
|
||||
HTNL4: LD A, E
|
||||
SUB VL
|
||||
LD E, A
|
||||
JR NC, HTNL3
|
||||
DEC D
|
||||
JR HTNL3
|
||||
HTNL5: ;- E = offset mod 40. Subtract from HL.
|
||||
LD A, L
|
||||
SUB E
|
||||
LD L, A
|
||||
JR NC, HTNL6
|
||||
DEC H
|
||||
HTNL6: LD DE, VRAM
|
||||
ADD HL, DE
|
||||
POP DE
|
||||
JR HTCHK
|
||||
HTNXT: ;- Just consumed CR — skip, don't advance.
|
||||
HTCHK: ;- Bounds check.
|
||||
LD A, H
|
||||
CP 0D4h
|
||||
JR NC, HTEND
|
||||
DEC C
|
||||
JR NZ, HTDISP
|
||||
HTEND:
|
||||
;- RECV confirm.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, 040h
|
||||
OUT (PORT_DR), A
|
||||
|
||||
HTTPDN: ;- Close socket.
|
||||
LD A, 004h
|
||||
OUT (PORT_AR0), A
|
||||
LD A, 001h
|
||||
OUT (PORT_AR1), A
|
||||
LD A, CMD_CLOSE
|
||||
OUT (PORT_DR), A
|
||||
|
||||
;- Debug port summary.
|
||||
LD HL, DBGLP
|
||||
CALL PSTR
|
||||
LD A, (LOOPCNT+1)
|
||||
CALL PHEX
|
||||
LD A, (LOOPCNT)
|
||||
CALL PHEX
|
||||
LD HL, DBGCR
|
||||
CALL PSTR
|
||||
|
||||
;- Delay ~1 second at 3.54MHz (2 × 65535 × 26T ≈ 0.96s).
|
||||
LD B, 2
|
||||
DLY1: LD DE, 0FFFFh
|
||||
DLY2: DEC DE
|
||||
LD A, D
|
||||
OR E
|
||||
JR NZ, DLY2
|
||||
DJNZ DLY1
|
||||
|
||||
JP MAINLP
|
||||
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- PVRAM: Write null-terminated ASCII string at DE to VRAM at HL. Returns HL advanced.
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
PVRAM: LD A, (DE)
|
||||
OR A
|
||||
RET Z
|
||||
PUSH HL
|
||||
PUSH DE
|
||||
CALL A2DSP
|
||||
POP DE
|
||||
POP HL
|
||||
LD (HL), A
|
||||
INC HL
|
||||
INC DE
|
||||
JR PVRAM
|
||||
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- PDECV: Print A as decimal to VRAM at HL. Advances HL.
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
PDECV: PUSH BC
|
||||
LD B, 0
|
||||
PVD100: CP 100
|
||||
JR C, PVD10
|
||||
SUB 100
|
||||
INC B
|
||||
JR PVD100
|
||||
PVD10: PUSH AF
|
||||
LD A, B
|
||||
OR A
|
||||
JR Z, PVD10N
|
||||
ADD A, 020h
|
||||
LD (HL), A
|
||||
INC HL
|
||||
PVD10N: POP AF
|
||||
LD B, 0
|
||||
PVD10L: CP 10
|
||||
JR C, PVD1
|
||||
SUB 10
|
||||
INC B
|
||||
JR PVD10L
|
||||
PVD1: PUSH AF
|
||||
LD A, B
|
||||
OR A
|
||||
JR Z, PVD1N
|
||||
ADD A, 020h
|
||||
LD (HL), A
|
||||
INC HL
|
||||
PVD1N: POP AF
|
||||
ADD A, 020h
|
||||
LD (HL), A
|
||||
INC HL
|
||||
POP BC
|
||||
RET
|
||||
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- PHEXV: Print A as 2-digit hex to VRAM at HL.
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
PHEXV: PUSH AF
|
||||
RRCA
|
||||
RRCA
|
||||
RRCA
|
||||
RRCA
|
||||
AND 00Fh
|
||||
CALL PHXN
|
||||
POP AF
|
||||
AND 00Fh
|
||||
PHXN: CP 00Ah
|
||||
JR C, PHXN1
|
||||
ADD A, 001h - 00Ah
|
||||
LD (HL), A
|
||||
INC HL
|
||||
RET
|
||||
PHXN1: ADD A, 020h
|
||||
LD (HL), A
|
||||
INC HL
|
||||
RET
|
||||
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- A2DSP: Convert ASCII in A to MZ display code via table.
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
A2DSP: PUSH HL
|
||||
AND 07Fh
|
||||
LD L, A
|
||||
LD H, 0
|
||||
LD DE, ATBL
|
||||
ADD HL, DE
|
||||
LD A, (HL)
|
||||
POP HL
|
||||
RET
|
||||
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- Debug port helpers.
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
PSTR: LD A, (HL)
|
||||
OR A
|
||||
RET Z
|
||||
OUT (PORT_DBG), A
|
||||
INC HL
|
||||
JR PSTR
|
||||
|
||||
PHEX: PUSH AF
|
||||
RRCA
|
||||
RRCA
|
||||
RRCA
|
||||
RRCA
|
||||
AND 00Fh
|
||||
ADD A, 030h
|
||||
CP 03Ah
|
||||
JR C, PH1
|
||||
ADD A, 007h
|
||||
PH1: OUT (PORT_DBG), A
|
||||
POP AF
|
||||
AND 00Fh
|
||||
ADD A, 030h
|
||||
CP 03Ah
|
||||
JR C, PH2
|
||||
ADD A, 007h
|
||||
PH2: OUT (PORT_DBG), A
|
||||
RET
|
||||
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
;- Strings (ASCII, null-terminated).
|
||||
;--------------------------------------------------------------------------------------------------------
|
||||
S_TITLE: DB "CELESTITE NET STRESS TEST", 0
|
||||
S_LOOP: DB "LOOP ", 0
|
||||
S_PING: DB "PING 192.168.1.1", 0
|
||||
S_TCP: DB "TCP CONNECT GATEWAY 80", 0
|
||||
S_HTTP: DB "HTTP GET GATEWAY", 0
|
||||
S_PASS: DB "PASS", 0
|
||||
S_FAIL: DB "FAIL", 0
|
||||
S_TOUT: DB "TOUT", 0
|
||||
S_MS: DB "MS", 0
|
||||
S_BYT: DB "H", 0
|
||||
|
||||
HTTPREQ: DB "GET / HTTP/1.0", 0Dh, 0Ah, 0Dh, 0Ah, 00h
|
||||
|
||||
DBGLP: DB 0Dh, 0Ah, "Loop:", 0
|
||||
DBGCR: DB 0Dh, 0Ah, 0
|
||||
|
||||
LOOPCNT: DW 0
|
||||
|
||||
;- ASCII to MZ display code table (128 bytes).
|
||||
ATBL: DB 0CCh,0E0h,0F2h,0F3h,0CEh,0CFh,0F6h,0F7h
|
||||
DB 0F8h,0F9h,0CDh,0FBh,0FCh,0CDh,0FEh,0FFh
|
||||
DB 0E1h,0C1h,0C2h,0C3h,0C4h,0C5h,0C6h,0E2h
|
||||
DB 0E3h,0E4h,0E5h,0E6h,0EBh,0EEh,0EFh,0F4h
|
||||
DB 000h,061h,062h,063h,064h,065h,066h,067h
|
||||
DB 068h,069h,06Bh,06Ah,02Fh,02Ah,02Eh,02Dh
|
||||
DB 020h,021h,022h,023h,024h,025h,026h,027h
|
||||
DB 028h,029h,04Fh,02Ch,051h,02Bh,057h,049h
|
||||
DB 055h,001h,002h,003h,004h,005h,006h,007h
|
||||
DB 008h,009h,00Ah,00Bh,00Ch,00Dh,00Eh,00Fh
|
||||
DB 010h,011h,012h,013h,014h,015h,016h,017h
|
||||
DB 018h,019h,01Ah,052h,059h,054h,050h,045h
|
||||
DB 000h,001h,002h,003h,004h,005h,006h,007h
|
||||
DB 008h,009h,00Ah,00Bh,00Ch,00Dh,00Eh,00Fh
|
||||
DB 010h,011h,012h,013h,014h,015h,016h,017h
|
||||
DB 018h,019h,01Ah,053h,000h,058h,000h,000h
|
||||
|
||||
MEND:
|
||||
1137
projects/tzpuPico/src/test/Celestite/celestite_test.asm
Normal file
1137
projects/tzpuPico/src/test/Celestite/celestite_test.asm
Normal file
File diff suppressed because it is too large
Load Diff
BIN
projects/tzpuPico/tools/MZQD/MZQDTool
vendored
BIN
projects/tzpuPico/tools/MZQD/MZQDTool
vendored
Binary file not shown.
@@ -40,6 +40,10 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define QDSIZE 61455
|
||||
#define QDFSIZE 81920 /* Japanese QDF format track size (80KB) */
|
||||
#define QDFHDRSIZE 16 /* QDF file header size */
|
||||
#define QDFTOTALSIZE (QDFHDRSIZE + QDFSIZE) /* 81936 bytes total */
|
||||
#define QDFSIGNATURE "-QD format-"
|
||||
#define MAXQDFILETYPES 12
|
||||
#define DEFAULTQDFILE "MZ700.qd"
|
||||
#define CMTHDRSIZE 128 /* MZF/CMT file header size */
|
||||
@@ -71,8 +75,9 @@ static const char *QDFileTypes[MAXQDFILETYPES] = {
|
||||
"???", "LIB", "???", "???", "SYS", "GR "
|
||||
};
|
||||
|
||||
static uint8_t QDArray[QDSIZE];
|
||||
static uint8_t QDArray[QDFSIZE]; /* Large enough for both formats */
|
||||
static char qdFileName[256] = DEFAULTQDFILE;
|
||||
static int qdfFormat = 0; /* 1 = output in Japanese QDF format */
|
||||
|
||||
/* ------- Low-level QD block I/O helpers ------- */
|
||||
|
||||
@@ -98,7 +103,17 @@ static int save_qd(void)
|
||||
fprintf(stderr, "ERROR: Cannot write '%s'\n", qdFileName);
|
||||
return 0;
|
||||
}
|
||||
fwrite(QDArray, QDSIZE, 1, f);
|
||||
if (qdfFormat) {
|
||||
/* Write QDF header + full 81920 byte track */
|
||||
uint8_t hdr[QDFHDRSIZE];
|
||||
memset(hdr, 0xFF, QDFHDRSIZE);
|
||||
memcpy(hdr, QDFSIGNATURE, strlen(QDFSIGNATURE));
|
||||
fwrite(hdr, QDFHDRSIZE, 1, f);
|
||||
fwrite(QDArray, QDFSIZE, 1, f);
|
||||
printf(" Output: QDF format (%d bytes)\n", QDFTOTALSIZE);
|
||||
} else {
|
||||
fwrite(QDArray, QDSIZE, 1, f);
|
||||
}
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
@@ -110,6 +125,27 @@ static int load_qd(void)
|
||||
fprintf(stderr, "ERROR: Cannot open '%s'\n", qdFileName);
|
||||
return 0;
|
||||
}
|
||||
/* Check file size to detect format */
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (sz == QDFTOTALSIZE) {
|
||||
/* Likely QDF format — check header signature */
|
||||
uint8_t hdr[QDFHDRSIZE];
|
||||
fread(hdr, QDFHDRSIZE, 1, f);
|
||||
if (memcmp(hdr, QDFSIGNATURE, strlen(QDFSIGNATURE)) == 0) {
|
||||
printf(" Input: QDF format detected (%ld bytes)\n", sz);
|
||||
memset(QDArray, 0, sizeof(QDArray));
|
||||
fread(QDArray, QDFSIZE, 1, f);
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
/* Not QDF — rewind and read as raw */
|
||||
fseek(f, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
memset(QDArray, 0, sizeof(QDArray));
|
||||
fread(QDArray, QDSIZE, 1, f);
|
||||
fclose(f);
|
||||
return 1;
|
||||
@@ -315,12 +351,14 @@ static void AddFileToQD(const char *mzfFileName)
|
||||
static void usage(void)
|
||||
{
|
||||
printf("Usage:\n");
|
||||
printf(" MZQDTool format [-o disk.qd] Format empty QD image\n");
|
||||
printf(" MZQDTool dir [-o disk.qd] List QD directory\n");
|
||||
printf(" MZQDTool add <file.mzf> [-o disk.qd] Add MZF file to QD\n");
|
||||
printf(" MZQDTool format [-o disk.qd] [-j] Format empty QD image\n");
|
||||
printf(" MZQDTool dir [-o disk.qd] List QD directory\n");
|
||||
printf(" MZQDTool add <file.mzf> [-o disk.qd] [-j] Add MZF file to QD\n");
|
||||
printf(" MZQDTool convert [-o out.qdf] [-j] <input> Convert between formats\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -o <filename> QD image file (default: %s)\n", DEFAULTQDFILE);
|
||||
printf(" -j Output in Japanese QDF format (81936 bytes with header)\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
@@ -333,12 +371,21 @@ int main(int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Parse -o option from any position */
|
||||
/* Parse options from any position */
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp("-j", argv[i]) == 0) {
|
||||
qdfFormat = 1;
|
||||
/* Remove from argv */
|
||||
for (int j = i; j < argc - 1; j++)
|
||||
argv[j] = argv[j + 1];
|
||||
argc--;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < argc - 1; i++) {
|
||||
if (strcmp("-o", argv[i]) == 0) {
|
||||
strncpy(qdFileName, argv[i + 1], sizeof(qdFileName) - 1);
|
||||
qdFileName[sizeof(qdFileName) - 1] = '\0';
|
||||
/* Remove -o and its argument from argv for command parsing */
|
||||
for (int j = i; j < argc - 2; j++)
|
||||
argv[j] = argv[j + 2];
|
||||
argc -= 2;
|
||||
@@ -356,6 +403,39 @@ int main(int argc, char *argv[])
|
||||
return 2;
|
||||
}
|
||||
AddFileToQD(argv[2]);
|
||||
} else if (strcmp("convert", argv[1]) == 0) {
|
||||
/* Convert between formats. Only compact→QDF is supported (adds header + pads to 80KB).
|
||||
* QDF→compact is NOT supported because the track layouts are structurally different:
|
||||
* compact has data at offset 0, QDF has ~4KB of gap before data starts. */
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "ERROR: No input file specified\n");
|
||||
return 2;
|
||||
}
|
||||
char outFile[256];
|
||||
strncpy(outFile, qdFileName, sizeof(outFile) - 1);
|
||||
strncpy(qdFileName, argv[2], sizeof(qdFileName) - 1);
|
||||
|
||||
/* Check if input is QDF */
|
||||
FILE *chk = fopen(qdFileName, "rb");
|
||||
if (chk) {
|
||||
uint8_t sig[16];
|
||||
fread(sig, 1, 16, chk);
|
||||
fclose(chk);
|
||||
if (memcmp(sig, QDFSIGNATURE, strlen(QDFSIGNATURE)) == 0 && !qdfFormat) {
|
||||
fprintf(stderr, "ERROR: Cannot convert QDF to compact — track layouts are incompatible.\n");
|
||||
fprintf(stderr, " The picoZ80 QD driver loads QDF files directly. No conversion needed.\n");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!load_qd()) return 1;
|
||||
strncpy(qdFileName, outFile, sizeof(qdFileName) - 1);
|
||||
if (!qdfFormat) {
|
||||
fprintf(stderr, "ERROR: Only compact→QDF conversion is supported (use -j flag).\n");
|
||||
return 2;
|
||||
}
|
||||
if (!save_qd()) return 1;
|
||||
printf(" Converted: %s -> %s (QDF)\n", argv[2], qdFileName);
|
||||
} else {
|
||||
fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
|
||||
usage();
|
||||
|
||||
2
projects/tzpuPico/version.txt
vendored
2
projects/tzpuPico/version.txt
vendored
@@ -1 +1 @@
|
||||
2.472
|
||||
2.518
|
||||
|
||||
Reference in New Issue
Block a user