Kjetil's Information Center: A Blog About My Projects

DOS Serial File Transfer

I was doing research on how to "bootstrap" an old DOS based computer with more functional software, like a proper terminal emulator. Because the floppy drive was broken, I had to get this transferred over the existing RS-232 serial port.

The initial results were a couple of programs: A "decode" program meant to be run on DOS, written in x86 assembly, to receive the data. An "encode" program meant to be run on Linux, written in C, to send the data. Very simple encoding is used; each byte is split into two nibbles and added with 0x30 to produce a valid ASCII character.

Unfortunately I was not able to run the programs with larger amounts of data without loosing bits and pieces. Even at very low baud rates like 1200 data still got lost and enabling/disabling flow control made no difference. However, I found that other people have been suffering from these kinds of issues in the past as well. So I found some other alternatives, and the best one so far is Kermit. The MS-DOS Kermit package also provides a BASIC bootstrap program that can be run in QBASIC on the DOS computer.

Anyway, for future reference, here is the "decode" program:
(Assemble it under Linux with NASM like so: nasm decode.asm -fbin -o decode.com)

org 0x100 ; DOS COM file start offset.

section .text
start:
  ; Call BIOS to initialize serial port.
  mov ah, 0x00
  mov al, 0xe3 ; 9600,8,n,1
  mov dx, 0x00 ; COM1
  int 0x14

  ; Call DOS to create new file and handle.
  mov ah, 0x3c
  mov cx, 0 ; Standard attributes.
  mov dx, filename
  int 0x21
  jc end_file_create_error
  mov [filehandle], ax

read_loop:
  ; Call BIOS to read byte from serial port.
  mov ah, 0x02
  mov dx, 0x00 ; COM1
  int 0x14
  and ah, 0x80
  jnz end_read_serial_error

  ; Check if DOS-style EOF.
  cmp al, 0x1A
  je end_success

  ; Determine high or low nibble next.
  mov bl, [have_seen_high_nibble]
  cmp bl, 0
  jne convert_low_nibble

convert_high_nibble:
  sub al, 0x30
  mov cl, 4
  shl al, cl
  mov [high_nibble], al
  mov byte [have_seen_high_nibble], 1
  jmp read_loop

convert_low_nibble:
  sub al, 0x30
  or al, [high_nibble]
  mov byte [have_seen_high_nibble], 0

  ; Call DOS to write to file.
  mov [write_buffer], al
  mov ah, 0x40
  mov bx, [filehandle]
  mov cx, 1 ; One byte at a time.
  mov dx, write_buffer
  int 0x21
  jc end_file_write_error

  ; Call DOS to display '.' for progress.
  mov ah, 0x2
  mov dl, '.'
  int 0x21

  jmp read_loop

end_success:
  ; Call DOS to close file handle.
  mov ah, 0x3e
  mov bx, [filehandle]
  int 0x21

  ; Call DOS to terminate program.
  mov ah, 0x4c
  mov al, 0 ; 0 = OK
  int 0x21

end_file_create_error:
  mov [error_code], ax
  ; Call DOS to display 'C' signifying "Create Error".
  mov ah, 0x2
  mov dl, 'C'
  int 0x21

  ; Call DOS to display error code.
  ; mov ah, 0x2
  mov dl, [error_code]
  add dl, 0x30
  int 0x21
  jmp end_error

end_read_serial_error:
  ; Call DOS to display 'R' signifying "Read Error".
  mov ah, 0x2
  mov dl, 'R'
  int 0x21

  ; Call BIOS to get serial port status
  mov ah, 03
  mov dx, 0x00 ; COM1
  int 0x14
  mov [error_code], ax

  ; Call DOS to display error code(s).
  mov ah, 0x2
  mov dl, [error_code] ; Modem Status
  mov cl, 4
  shr dl, cl ; High Nibble
  add dl, 0x30
  int 0x21

  ; mov ah, 0x2
  mov dl, [error_code] ; Modem Status
  and dl, 0x0f ; Low Nibble
  add dl, 0x30
  int 0x21

  ; mov ah, 0x2
  mov dl, [error_code+1] ; Port Status
  mov cl, 4
  shr dl, cl ; High Nibble
  add dl, 0x30
  int 0x21

  ; mov ah, 0x2
  mov dl, [error_code+1] ; Port Status
  and dl, 0x0f ; Low Nibble
  add dl, 0x30
  int 0x21
  jmp end_error

end_file_write_error:
  mov [error_code], ax
  ; Call DOS to display 'W' signifying "Write Error".
  mov ah, 0x2
  mov dl, 'W'
  int 0x21

  ; Call DOS to display error code.
  ; mov ah, 0x2
  mov dl, [error_code]
  add dl, 0x30
  int 0x21

end_error:
  ; Call DOS to terminate program.
  mov ah, 0x4c
  mov al, 1 ; 1 = Error
  int 0x21
 
section .data:

high_nibble:
  db 0

have_seen_high_nibble:
  db 0

error_code:
filehandle:
  dw 0

write_buffer:
  db 0

filename:
  db "DECODE.BIN",0
          


And the "encode" program:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
  int c, fd, result;
  unsigned char byte;
  struct termios attr;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <TTY>\n", argv[0]);
    return 1;
  }

  fd = open(argv[1], O_RDWR | O_NOCTTY);
  if (fd == -1) {
    fprintf(stderr, "open() failed with errno: %d\n", errno);
    return 1;
  }

  result = tcgetattr(fd, &attr);
  if (result == -1) {
    fprintf(stderr, "tcgetattr() failed with errno: %d\n", errno);
    close(fd);
    return 1;
  }

  attr.c_cflag = B9600 | CS8 | CRTSCTS | CLOCAL;
  attr.c_iflag = 0;
  attr.c_oflag = 0;
  attr.c_lflag = 0;

  result = tcsetattr(fd, TCSANOW, &attr);
  if (result == -1) {
    fprintf(stderr, "tcgetattr() failed with errno: %d\n", errno);
    close(fd);
    return 1;
  }

  while ((c = fgetc(stdin)) != EOF) {
    byte = ((c & 0xf0) >> 4) + 0x30;
    write(fd, &byte, 1);
    byte = (c & 0x0f) + 0x30;
    write(fd, &byte, 1);
  }

  byte = 0x1a; /* DOS EOF */
  write(fd, &byte, 1);

  close(fd);
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 19/08-2018, Article Link