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; }