Kermit for CP/M-68K
I needed a way to transfer files to a CP/M-68K system with only a serial console and a RAM disk. For this I once again implemented the "Baby Kermit" reference program in C specifically to be compiled for CP/M.
Since CP/M-68K is from the early 80's there are some quirks regarding the C dialect that I discovered:
* K&R C syntax is of course expected, which affects function argument definitions.
* The "void" datatype must be declared using "VOID" in uppercase.
* NULL did not work as expected, so empty "" 0-length strings are used instead.
* To open a file in binary mode, the fopenb() call must be used since the regular fopen() will always open a file in ASCII mode.
The binary can be built using this emulator which happens to have the C toolchain available, using this command:
CC KERMIT.C -O KERMIT.REL
For convenience, here is a built KERMIT.REL ready for use.
I used the C-Kermit program on Linux to transfer files, which is very picky about having the correct settings. Here are the full instructions that has worked for me:
set modem type none
set line <tty-device>
set carrier-watch off
set flow none
set parity none
set stop-bits 1
set prefixing all
set streaming off
send <file>
Here is the CP/M-68K compatible C source code, which can also be compiled and run on Linux by the way:
#include <stdio.h>
#ifdef __unix__
#include <string.h>
#ifndef VOID
#define VOID void /* Non-CP/M compatibility */
#endif
#endif
#define SIZE 100
char send_buffer[SIZE];
char recv_buffer[SIZE];
char packet_data[SIZE];
int seq;
int ctl;
int eol;
VOID send_packet(type, data)
char type;
char *data;
{
int i;
int len;
char cs_send;
len = strlen(data);
if (len + 6 > SIZE) {
return; /* Overflow! */
}
send_buffer[0] = 1; /* 0x01 */
send_buffer[1] = len + 35;
send_buffer[2] = seq + 32;
send_buffer[3] = type;
for (i = 0; i < len; i++) {
send_buffer[i + 4] = data[i];
}
cs_send = 0;
for (i = 1; i < len + 4; i++) {
cs_send += send_buffer[i];
}
cs_send = (cs_send + ((cs_send & 192) / 64)) & 63;
send_buffer[len + 4] = cs_send + 32;
send_buffer[len + 5] = eol;
send_buffer[len + 6] = '\0';
i = 0;
while (send_buffer[i] != '\0') {
fputc(send_buffer[i], stdout);
i++;
}
}
VOID send_ack(data)
char *data;
{
send_packet('Y', data);
seq = (seq + 1) & 63;
}
VOID decode_packet(recv_seq, recv_type, dec_len)
char *recv_seq;
char *recv_type;
char *dec_len;
{
int i;
int t;
int t7;
int p;
int flag;
int c;
int mark;
char len;
unsigned cs_calc;
unsigned cs_recv;
/* Receive until a CR/LF/EOF appears. */
i = 0;
mark = -1;
while (1) {
c = fgetc(stdin);
if (c == '\r' || c == '\n' || c == EOF) {
break;
}
if (c == 1) { /* 0x01 */
mark = i; /* Store position of MARK in packet. */
}
recv_buffer[i] = c;
i++;
if (i >= SIZE) {
break; /* Overflow! */
}
}
if (mark == -1) {
*recv_type = 'Q'; /* No MARK found! */
return;
}
len = recv_buffer[mark + 1] - 32;
*recv_seq = recv_buffer[mark + 2] - 32;
*recv_type = recv_buffer[mark + 3];
if ((mark + len + 1) >= SIZE) {
*recv_type = 'Q'; /* Length exceeds buffer. */
return;
}
cs_recv = recv_buffer[mark + len + 1] - 32;
cs_calc = 0;
for (i = mark + 1; i < (mark + len + 1); i++) {
cs_calc += recv_buffer[i];
}
cs_calc = (cs_calc + ((cs_calc & 192) / 64)) & 63;
for (i = 0; i < SIZE; i++) {
packet_data[i] = '\0';
}
p = 0;
flag = 0;
for (i = mark + 4; i < mark + len + 1; i++) {
t = recv_buffer[i];
if (*recv_type != 'S') {
if (flag == 0 && t == ctl) {
flag = 1;
continue;
}
t7 = t & 127;
if (flag == 1) {
flag = 0;
if (t7 > 62 && t7 < 96) {
t ^= 64;
}
}
packet_data[p] = t;
p++;
}
}
*dec_len = p;
if (cs_recv != cs_calc) {
*recv_type = 'Q'; /* Checksum mismatch! */
}
}
char get_packet(len)
char *len;
{
int try;
char recv_seq;
char recv_type;
char recv_len;
/* Try to get a valid packet with the desired sequence number. */
decode_packet(&recv_seq, &recv_type, &recv_len);
for (try = 0; try < 5; try++) {
if (recv_seq == seq && recv_type != 'Q') {
*len = recv_len;
return recv_type;
}
send_packet('N', "");
decode_packet(&recv_seq, &recv_type, &recv_len);
}
*len = 0;
return 'T';
}
int main(void)
{
FILE *fh;
char type;
char len;
/* Default values */
ctl = '#';
eol = '\r';
seq = 0;
fh = NULL;
/* Get Send Initialization packet, exchange parameters. */
type = get_packet(&len);
if (type != 'S') {
send_packet('E', "Bad packet in S state");
return 1;
}
if (len > 4) {
eol = packet_data[5];
}
if (len > 5) {
ctl = packet_data[6];
}
send_ack("H* @-#N1");
/* Get a File Header packet. If a B packet comes, we're all done. */
while (1) {
type = get_packet(&len);
if (type == 'B') {
send_ack("");
break; /* Outer loop */
}
if (type != 'F') {
send_packet('E', "Bad packet in F state");
return 1;
}
#ifdef __unix__
fh = fopen(packet_data, "wb");
#else /* CP/M */
fh = fopenb(packet_data, "w");
#endif
if (fh == NULL) {
send_packet('E', "Error opening file");
return 1;
}
send_ack("");
/* Get Data packets. If a Z packet comes, the file is complete. */
while (1) {
type = get_packet(&len);
if (type == 'Z') {
fclose(fh);
send_ack("");
break; /* Inner loop */
}
if (type != 'D') {
send_packet('E', "Bad packet in D state");
fclose(fh);
return 1;
}
fwrite(packet_data, sizeof(char), len, fh);
send_ack("");
}
}
return 0;
}