323 lines
7.1 KiB
C
323 lines
7.1 KiB
C
/*
|
|
|
|
This file is part of libgtemu, a library for Gigatron emulation.
|
|
Copyright (C) 2019 David Heiko Kolf
|
|
|
|
Published under the BSD-2-Clause license.
|
|
https://opensource.org/licenses/BSD-2-Clause
|
|
|
|
*/
|
|
|
|
#include "gtemu.h"
|
|
|
|
const char loaderframe = 'L';
|
|
const unsigned char initchecksum = (unsigned char) 'g';
|
|
const char maxframesize = 60;
|
|
|
|
enum LoaderState {
|
|
L_IDLE = 0,
|
|
L1_INIT_FV,
|
|
L2_INIT_RH,
|
|
L3_HEADER_RH,
|
|
L4_PAYLOAD_RH,
|
|
L5_FILLER_RH,
|
|
L6_CHECKSUM_RH,
|
|
L7_FINISH_RH,
|
|
LT1_KEYUP_FV,
|
|
LT2_KEYDOWN_FV,
|
|
LT3_KEYIDLE_FV,
|
|
};
|
|
|
|
static void sendbit (struct GTState *gt, struct GTPeriph *ph,
|
|
unsigned char value, int n)
|
|
{
|
|
gt->in = gt->in << 1 | (1 & (value >> (7 - n)));
|
|
ph->loader.bitcount++;
|
|
}
|
|
|
|
static void loadnextsegment (struct GTPeriph *ph)
|
|
{
|
|
ph->loader.addr = (ph->loader.data[0] << 8) | ph->loader.data[1];
|
|
if (ph->loader.data[2] != 0) {
|
|
ph->loader.remainingsegment = ph->loader.data[2];
|
|
} else {
|
|
ph->loader.remainingsegment = 256;
|
|
}
|
|
ph->loader.remainingdata -= 3;
|
|
ph->loader.data += 3;
|
|
if (ph->loader.remainingsegment > ph->loader.remainingdata) {
|
|
ph->loader.remainingsegment = ph->loader.remainingdata;
|
|
}
|
|
if (ph->loader.remainingsegment > maxframesize) {
|
|
ph->loader.remainingframe = maxframesize;
|
|
} else {
|
|
ph->loader.remainingframe = ph->loader.remainingsegment;
|
|
}
|
|
ph->loader.state = L1_INIT_FV;
|
|
}
|
|
|
|
static void sendheaderbit (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
int bc = ph->loader.bitcount;
|
|
if (bc < 8) {
|
|
sendbit(gt, ph, loaderframe, bc);
|
|
} else if (bc < 14) {
|
|
sendbit(gt, ph, ph->loader.remainingframe, bc - 6);
|
|
} else if (bc < 22) {
|
|
sendbit(gt, ph, (unsigned char) ph->loader.addr, bc - 14);
|
|
} else {
|
|
sendbit(gt, ph, (unsigned char) (ph->loader.addr >> 8), bc - 22);
|
|
if (bc + 1 >= 30) {
|
|
ph->loader.checksum += loaderframe +
|
|
(loaderframe << 6) +
|
|
ph->loader.remainingframe +
|
|
ph->loader.addr +
|
|
(ph->loader.addr >> 8);
|
|
ph->loader.bitcount = 0;
|
|
ph->loader.remainingfiller = maxframesize - ph->loader.remainingframe;
|
|
if (ph->loader.remainingframe > 0) {
|
|
ph->loader.addr += ph->loader.remainingframe;
|
|
ph->loader.state = L4_PAYLOAD_RH;
|
|
} else {
|
|
ph->loader.state = L5_FILLER_RH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sendpayloadbit (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
int bc = ph->loader.bitcount;
|
|
sendbit(gt, ph, ph->loader.data[0], bc);
|
|
if (bc + 1 >= 8) {
|
|
ph->loader.checksum += ph->loader.data[0];
|
|
ph->loader.bitcount = 0;
|
|
ph->loader.remainingframe--;
|
|
ph->loader.remainingsegment--;
|
|
ph->loader.remainingdata--;
|
|
ph->loader.data++;
|
|
if (ph->loader.remainingframe == 0) {
|
|
if (ph->loader.remainingfiller > 0) {
|
|
ph->loader.state = L5_FILLER_RH;
|
|
} else {
|
|
ph->loader.checksum = -ph->loader.checksum;
|
|
ph->loader.state = L6_CHECKSUM_RH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sendfillerbit (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
int bc = ph->loader.bitcount;
|
|
sendbit(gt, ph, 0, bc);
|
|
if (bc + 1 >= 8) {
|
|
ph->loader.bitcount = 0;
|
|
ph->loader.remainingfiller--;
|
|
if (ph->loader.remainingfiller == 0) {
|
|
ph->loader.checksum = -ph->loader.checksum;
|
|
ph->loader.state = L6_CHECKSUM_RH;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sendchecksumbit (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
int bc = ph->loader.bitcount;
|
|
sendbit(gt, ph, ph->loader.checksum, bc);
|
|
if (bc + 1 >= 8) {
|
|
if (ph->loader.remainingsegment > 0) {
|
|
if (ph->loader.remainingsegment > maxframesize) {
|
|
ph->loader.remainingframe = maxframesize;
|
|
} else {
|
|
ph->loader.remainingframe = (unsigned char) ph->loader.remainingsegment;
|
|
}
|
|
ph->loader.state = L1_INIT_FV;
|
|
} else if (ph->loader.remainingdata > 2) {
|
|
if (ph->loader.data[0] == 0) {
|
|
ph->loader.addr = (ph->loader.data[1] << 8) | ph->loader.data[2];
|
|
ph->loader.remainingdata = 0;
|
|
ph->loader.state = L1_INIT_FV;
|
|
} else {
|
|
loadnextsegment(ph);
|
|
}
|
|
} else {
|
|
ph->loader.state = L7_FINISH_RH;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sendtextrelease (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
ph->loader.remainingframe--;
|
|
if (ph->loader.remainingframe == 0) {
|
|
ph->loader.remainingframe = 3;
|
|
gt->in = 0xff;
|
|
if (ph->loader.remainingdata > 0) {
|
|
ph->loader.state = LT2_KEYDOWN_FV;
|
|
} else {
|
|
ph->loader.state = LT3_KEYIDLE_FV;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sendtextbyte (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
ph->loader.remainingframe--;
|
|
if (ph->loader.remainingframe == 0) {
|
|
gt->in = ph->loader.data[0];
|
|
if (gt->in == '\n') {
|
|
ph->loader.remainingframe = 9;
|
|
} else {
|
|
ph->loader.remainingframe = 3;
|
|
}
|
|
ph->loader.data++;
|
|
ph->loader.remainingdata--;
|
|
ph->loader.state = LT1_KEYUP_FV;
|
|
}
|
|
}
|
|
|
|
void gtloader_onfallingvsync (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
switch (ph->loader.state) {
|
|
case L1_INIT_FV:
|
|
ph->loader.state = L2_INIT_RH;
|
|
return;
|
|
case LT1_KEYUP_FV:
|
|
sendtextrelease(gt, ph);
|
|
return;
|
|
case LT2_KEYDOWN_FV:
|
|
sendtextbyte(gt, ph);
|
|
return;
|
|
case LT3_KEYIDLE_FV:
|
|
ph->loader.remainingframe--;
|
|
if (ph->loader.remainingframe == 0) {
|
|
ph->loader.state = L_IDLE;
|
|
}
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void gtloader_onrisinghsync (struct GTState *gt, struct GTPeriph *ph)
|
|
{
|
|
switch (ph->loader.state) {
|
|
case L2_INIT_RH:
|
|
ph->loader.bitcount = 0;
|
|
ph->loader.state = L3_HEADER_RH;
|
|
return;
|
|
case L3_HEADER_RH:
|
|
sendheaderbit(gt, ph);
|
|
return;
|
|
case L4_PAYLOAD_RH:
|
|
sendpayloadbit(gt, ph);
|
|
return;
|
|
case L5_FILLER_RH:
|
|
sendfillerbit(gt, ph);
|
|
return;
|
|
case L6_CHECKSUM_RH:
|
|
sendchecksumbit(gt, ph);
|
|
return;
|
|
case L7_FINISH_RH:
|
|
gt->in = 0xff;
|
|
ph->loader.state = L_IDLE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
int gtloader_sendgt1 (struct GTPeriph *ph,
|
|
const char *data, size_t datasize)
|
|
{
|
|
if (ph->loader.state != L_IDLE) {
|
|
return 0;
|
|
}
|
|
if (datasize < 3) {
|
|
return -1;
|
|
}
|
|
|
|
ph->loader.data = (const unsigned char *) data;
|
|
ph->loader.remainingdata = datasize;
|
|
ph->loader.checksum = initchecksum;
|
|
|
|
loadnextsegment(ph);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int gtloader_isactive (struct GTPeriph *ph)
|
|
{
|
|
return ph->loader.state != L_IDLE;
|
|
}
|
|
|
|
int gtloader_validategt1 (const char *data, size_t datasize)
|
|
{
|
|
const unsigned char *ud = (const unsigned char *) data;
|
|
unsigned char adhi;
|
|
if (datasize < 3) {
|
|
return 0;
|
|
}
|
|
adhi = ud[0];
|
|
do {
|
|
unsigned char adlo = ud[1];
|
|
int size = ud[2];
|
|
if (size == 0) {
|
|
size = 256;
|
|
}
|
|
ud += 3;
|
|
datasize -= 3;
|
|
if (size + 3 > datasize) {
|
|
return 0;
|
|
}
|
|
if (size + adlo > 256) {
|
|
return 0;
|
|
}
|
|
ud += size;
|
|
datasize -= size;
|
|
adhi = ud[0];
|
|
} while (adhi != 0 && datasize > 3);
|
|
if (datasize < 3) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int gtloader_sendtext (struct GTPeriph *ph,
|
|
const char *data, size_t datasize)
|
|
{
|
|
if (ph->loader.state != L_IDLE) {
|
|
return 0;
|
|
}
|
|
|
|
ph->loader.data = (const unsigned char *) data;
|
|
ph->loader.remainingdata = datasize;
|
|
ph->loader.remainingframe = 3;
|
|
|
|
ph->loader.state = LT1_KEYUP_FV;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int gtloader_sendkey (struct GTState *gt, struct GTPeriph *ph, char key)
|
|
{
|
|
if (ph->loader.state != L_IDLE &&
|
|
(ph->loader.state != LT1_KEYUP_FV ||
|
|
ph->loader.remainingdata > 0) &&
|
|
ph->loader.state != LT3_KEYIDLE_FV) {
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (ph->loader.state == L_IDLE || ph->loader.prevkey != key) {
|
|
ph->loader.prevkey = key;
|
|
gt->in = (unsigned char) key;
|
|
ph->loader.remainingdata = 0;
|
|
ph->loader.remainingframe = 3;
|
|
ph->loader.state = LT1_KEYUP_FV;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|