Kjetil's Information Center: A Blog About My Projects

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


Topic: Scripts and Code, by Kjetil @ 03/03-2023, Article Link