Logo Search packages:      
Sourcecode: cbmlink version File versions  Download package

disk.c

Go to the documentation of this file.
/**
 * @file disk.c
 * Extension for accessing a disk drive on the remote host
 * @author Marko Mäkelä (msmakela@nic.funet.fi)
 */

/*
 * Copyright © 1994-1996 Marko Mäkelä and Olaf Seibert
 * Copyright © 2001,2002 Marko Mäkelä
 * Original Linux and Commodore 64/128/Vic-20 version by Marko Mäkelä
 * Ported to the PET and the Amiga series by Olaf Seibert
 * Restructured by Marko Mäkelä
 * 
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef __BCC__
long lseek ();
#endif /* __BCC__ */

#include "comm.h"
#include "info.h"
#include "ext.h"
#include <stdio.h>
#include <string.h>
#include "disk.h"
#include "disk-o.h"

/** install the disk extension
 * @param comm          the communication primitives
 * @param hostinfo      information on the remote host
 * @param device  the device number of the disk drive
 * @return        zero on success, nonzero on error
 */
int
00048 disk_install (const struct comm* comm,
            const struct hostinfo* hostinfo,
            unsigned device)
{
  int status = 1;
  switch (hostinfo->host) {
  case PET:
    break;
  case PET3:
    status = ext (comm, hostinfo, disk_pet3000, sizeof disk_pet3000,
              device, 15) ? 2 : 0;
    break;
  case PET4:
    status = ext (comm, hostinfo, disk_pet4000, sizeof disk_pet4000,
              device, 15) ? 2 : 0;
    break;
  case P500: case B128: case B256:
    status = ext (comm, hostinfo, disk_cbm2, sizeof disk_cbm2,
              device, 15) ? 2 : 0;
    break;
  case Vic: case C64: case C128: case C264:
    status = ext (comm, hostinfo, disk_cbm, sizeof disk_cbm,
              device, 15) ? 2 : 0;
    break;
  }

  if (!status) {
    unsigned char ch = 0;
    if ((*comm->comm_write) (&ch, 1))
      status = 3;
    else {
      (*comm->comm_sr) ();
      if ((*comm->comm_read) (&ch, 1) || ch) {
      fprintf (stderr, "disk: remote error %#x\n", ch);
      status = 3;
      disk_remove (comm);
      }
    }
  }
  else if (status == 1)
    fprintf (stderr, "disk: unsupported server %u\n", hostinfo->host);

  return status;
}

/** remove the disk extension
 * @param comm          the communication primitives
 * @return        zero on success, nonzero on error
 */
int
00098 disk_remove (const struct comm* comm)
{
  unsigned char ch = 0;
  return (*comm->comm_write) (&ch, 1);
}

/** compute a simple 8-bit data checksum
 * @param buf           the data
 * @param len           number of data bytes
 * @return        the 8-bit checksum (len+buf[0]+buf[1]+...+buf[len-1])
 */
static unsigned
00110 checksum (const char* buf, unsigned len)
{
  register unsigned check = len;
  register const char* b = buf + len;
  while (b-- > buf)
    check += (unsigned char) *b;
  return (unsigned char) check;
}

/** write data to a remote file
 * @param comm          the communication primitives
 * @param file          the file number (2 for data, 15 for command)
 * @param buf           the data (with buf[-3..-1] and buf[len] writable)
 * @param len           the length of the data (1..256 bytes)
 * @return        remote status code, or -1 on transfer failure
 */
static int
00127 write_remote (const struct comm* comm, unsigned file, char* buf, unsigned len)
{
  buf[-3] = file | 0x40;
  buf[-2] = len;
  buf[-1] = checksum (buf, len);
  buf[len] = 0;

  for (;;) {
    char c;
    if ((*comm->comm_write) (buf - 3, len + 4))
      return -1;
    (*comm->comm_sr) ();
    if ((*comm->comm_read) (&c, 1))
      return -1;
    (*comm->comm_rs) ();
    if (!c)
      return 0;
    else if (c == (char) 0xff)
      fputs ("\ndisk: checksum error on write, retrying\n", stderr);
    else
      return c;
  }
}

/** read data from a remote file
 * @param comm          the communication primitives
 * @param file          the file number (2 for data, 15 for status)
 * @param buf           (output) space for the data (up to 256 bytes)
 * @param len           (output) the length of the data (1..256 bytes)
 * @return        remote status code, or -1 on transfer failure
 */
static int
00159 read_remote (const struct comm* comm, unsigned file, char* buf, unsigned* len)
{
  buf[0] = file, buf[1] = 0;
  if ((*comm->comm_write) (buf, 2))
    return -1;
  for (;;) {
    unsigned check, length;
    (*comm->comm_sr) ();
    /* get the remote status */
    if ((*comm->comm_read) (buf, 1))
      return -1;
    if (*buf) { /* non-zero remote status */
      (*comm->comm_rs) ();
      return (unsigned char) *buf;
    }
    /* get the length and the checksum */
    if ((*comm->comm_read) (buf, 2))
      return -1;
    if (!(length = (unsigned char) *buf)) length = 256;
    check = (unsigned char) buf[1];
    /* get the data */
    if ((*comm->comm_read) (buf, length))
      return -1;
    (*comm->comm_rs) ();

    if (checksum (buf, length) == check) {
      *len = length;
      return 0;
    }

    fputs ("\ndisk: checksum error on read, retrying\n", stderr);
    *buf = (char) 0xff;
    if ((*comm->comm_write) (buf, 1))
      return -1;
  }
}

/** issue a U1 or U2 command to read or write a disk block
 * @param comm          the communication primitives
 * @param buf           a 18-byte buffer for the command
 * @param command the command: '1'=read, '2'=write
 * @param unit          the drive unit: '0' or '1'
 * @param track         the track number: 1 to 999
 * @param sector  the sector number: 0 to 999
 * @return        remote status code, or -1 on transfer failure
 */
static int
00206 command_remote (const struct comm* comm,
            char* buf,
            char command, char unit,
            unsigned track, unsigned sector)
{
  unsigned len = sprintf (buf, "U%c:2 %c %u %u", command, unit, track, sector);
  return write_remote (comm, 15, buf, len);
}

/** a string of backspaces for erasing the "track %u" text */
00216 static const char backspaces[] = "\b\b\b\b\b\b\b\b\b";

/** copy a disk sector from the remote host
 * @param comm          the communication primitives
 * @param drive         the disk drive unit ('0' or '1')
 * @param track         track to be read
 * @param sector  sector to be read
 * @param buf           a buffer of 256 bytes
 * @return        0=success, 1=track ends, 2=disk ends, nonzero on error
 */
static int
00227 read_sector (const struct comm* comm,
           char drive,
           unsigned track,
           unsigned sector,
           char* buf)
{
  unsigned len;
  int status;

  fputs (backspaces + (sizeof backspaces - 1) -
       fprintf (stderr, "s%u  ", sector), stderr);
  fflush (stderr);

  if ((status = command_remote (comm, buf + 3, '1',
                        drive, track, sector)) ||
      (status = read_remote (comm, 15, buf, &len)) || len < 3) {
  abort:
    if (status == -1)
      fputs ("\ndisk_read: communication failure\n", stderr);
    else
      fprintf (stderr, "\ndisk_read: remote status %d\n", status);
    return status;
  }
  if (!memcmp (buf, "66,", 3)) {
    /* illegal track or sector */
    return 1 + !sector;
  }
  if (memcmp (buf, "00,", 3)) {
    fputs ("\ndisk_read: ", stderr);
    fwrite (buf, 1, len, stderr);
    fputc ('\n', stderr);
    return -2;
  }

  if ((status = read_remote (comm, 2, buf, &len)))
    goto abort;
  if (len != 256) {
    fprintf (stderr, "\ndisk_read: got %u bytes; expected 256\n", len);
    return -2;
  }

  return 0;
}

/** copy a disk from the remote host
 * @param comm          the communication primitives
 * @param unit          the disk unit (drive 0 or 1)
 * @param interleave    the interleave factor (number of sectors to skip)
 * @param track         number of the track to start reading from
 * @param track_end     number of the last track, plus 1
 * @param file          output file
 * @param buf           a buffer of at least 1256 bytes
 * @return        zero on success, nonzero on error
 */
int
00282 disk_read (const struct comm* comm,
         unsigned unit,
         unsigned interleave,
         unsigned track,
         unsigned track_end,
         FILE* file, char* buf)
{
  /** current sector and number of blocks */
  unsigned sector, blocks;
  /** communication status */
  int status;
  /** drive unit */
  char drive = unit ? '1' : '0';
  /** position of the "track" display */
  unsigned pos = 0;
  /** interleave table */
  char* iltab = buf + 256;

  for (blocks = 0; track < track_end; track++) {
    if (pos)
      fputs (backspaces + (sizeof backspaces - 1) - pos, stderr);
    pos = fprintf (stderr, "track %u", track);
    fflush (stderr);

    if (interleave) {
      unsigned highsect = 1000, sectdone = 0;
      memset (iltab, 0, 1000);
      for (sector = 0; sectdone < highsect;
         sector += interleave + 1, sector %= highsect) {
      while (iltab[sector]) {
        if (++sector == highsect) {
        findnext:
          for (sector = 0; iltab[++sector]; );
          break;
        }
      }
      switch ((status = read_sector (comm, drive, track, sector, buf))) {
      case 0: /* ok, mark the sector read */
        iltab[sector] = 1;
        break;
      case 1: /* no such sector */
        iltab[highsect = sector] = 2;
        if (sectdone < highsect)
          goto findnext;
        continue;
      case 2: /* end of disk */
        blocks += sectdone;
        goto done;
      default:
        return status;
      }
      sectdone++;
#ifdef __BCC__
      {
        long ofs = 256L * (blocks + sector);
        if (lseek (file->fd, ofs, SEEK_SET) != ofs) {
          perror ("\ndisk_read: lseek");
          return 2;
        }
      }
#else /* __BCC__ */
      if (fseek (file, 256L * (blocks + sector), SEEK_SET)) {
        perror ("\ndisk_read: fseek");
        return -2;
      }
#endif /* __BCC__ */
      if (256 != fwrite (buf, 1, 256, file)) {
        perror ("\ndisk_read: fwrite");
        return -2;
      }
      }
      blocks += sectdone;
    }
    else {
      for (sector = 0; sector < 1000; sector++, blocks++) {
      switch ((status = read_sector (comm, drive, track, sector, buf))) {
      case 0:
        break;
      case 1: /* end of track */
        goto next_track;
      case 2: /* end of disk */
        goto done;
      default:
        return status;
      }
      if (256 != fwrite (buf, 1, 256, file)) {
        perror ("\ndisk_read: fwrite");
        return -2;
      }
      }
    }
  next_track:
    continue;
  }

 done:
  fprintf (stderr, "\ndisk_read: %u blocks\n", blocks);
  return 0;
}

/** Read a sector from the specified file
 * @param f       the input file
 * @param sector  number of the current sector (for diagnostics)
 * @param blocks  number of blocks processed (for diagnostics)
 * @param buf           (output) the sector (256 bytes)
 * @return        0 on success, nonzero on failure
 */
static int
00390 read_sector_file (FILE* f, unsigned sector, unsigned blocks, char* buf)
{
  unsigned len;
  fputs (backspaces + (sizeof backspaces - 1) -
       fprintf (stderr, "s%u  ", sector), stderr);
  fflush (stderr);

  len = fread (buf, 1, 256, f);
  switch (len) {
  case 256:
    return 0;
  case 0:
    fprintf (stderr, "\ndisk_write: %u blocks\n", blocks);
    break;
  default:
    fprintf (stderr, "\ndisk_write: short block %u (%u bytes)\n",
           blocks, len);
  }
  return 1;
}

/** copy a disk to the remote host
 * @param comm          the communication primitives
 * @param unit          the disk unit (drive 0 or 1)
 * @param interleave    the interleave factor (number of sectors to skip)
 * @param track         number of the track to start reading from
 * @param track_end     number of the last track, plus 1
 * @param file          output file
 * @param buf           a buffer of at least 1260 bytes
 * @return        zero on success, nonzero on error
 */
int
00422 disk_write (const struct comm* comm,
          unsigned unit,
          unsigned interleave,
          unsigned track,
          unsigned track_end,
          FILE* file, char* buf)
{
  /** current sector, number of blocks, and block length */
  unsigned sector = 0, blocks = 0, len;
  /** communication status */
  int status;
  /** drive unit */
  char drive = unit ? '1' : '0';
  /** buffer pointer reset command */
  static char bp[11];
  /** position of the "track" display */
  unsigned pos = 0;
  /** interleave table */
  char* iltab = buf + 260;
  /** length of the input file */
  unsigned maxblock = 0;

  if (interleave) {
#ifdef __BCC__
    maxblock = (unsigned long) (lseek (file->fd, 0L, SEEK_END) + 255) / 256;
#else /* __BCC__ */
    if (fseek (file, 0L, SEEK_END)) {
      perror ("disk_write: fseek");
      return -2;
    }
    maxblock = (unsigned) (ftell (file) + 255) / 256;
#endif /* __BCC__ */
  }

  buf += 3;
  memcpy (bp + 3, "B-P:2 0", 7);

  if ((status = command_remote (comm, buf, '1', drive, track, sector)) ||
      (status = read_remote (comm, 15, buf, &len)) || len < 3) {
  abort:
    if (status == -1)
      fputs ("\ndisk_write: communication failure\n", stderr);
    else
      fprintf (stderr, "\ndisk_write: remote status %d\n", status);
    return status;
  }
  if (memcmp (buf, "00,", 3)) {
  not00:
    fputs ("\ndisk_write: ", stderr);
    fwrite (buf, 1, len, stderr);
    fputc ('\n', stderr);
    return -2;
  }

  for (; track < track_end; track++) {
    if (pos)
      fputs (backspaces + (sizeof backspaces - 1) - pos, stderr);
    pos = fprintf (stderr, "track %u", track);
    fflush (stderr);

    if (interleave) {
      unsigned highsect = 1000, sectdone = 0;
      if (maxblock <= blocks)
      goto done;
      if (maxblock < blocks + highsect)
      highsect = maxblock - blocks;
      memset (iltab, 0, highsect);
      for (sector = 0; sectdone < highsect;
         sector += interleave + 1, sector %= highsect) {
      while (iltab[sector]) {
        if (++sector == highsect) {
        findnext:
          for (sector = 0; iltab[++sector]; );
          break;
        }
      }

#ifdef __BCC__
      {
        long ofs = 256L * (blocks + sector);
        if (lseek (file->fd, ofs, SEEK_SET) != ofs) {
          perror ("\ndisk_write: lseek");
          return 2;
        }
      }
#else /* __BCC__ */
      if (fseek (file, 256L * (blocks + sector), SEEK_SET)) {
        perror ("\ndisk_write: fseek");
        return -2;
      }
#endif /* __BCC__ */

      if (read_sector_file (file, sector, blocks + sectdone, buf))
        return 0;

      if ((status = write_remote (comm, 15, bp + 3, 7)) ||
          (status = write_remote (comm, 2, buf, 256)))
        goto abort;

      for (;;) {
        if ((status = command_remote (comm, buf, '2', drive,
                              track, sector)) ||
            (status = read_remote (comm, 15, buf, &len)) || len < 3)
          goto abort;
        if (!memcmp (buf, "66,", 3)) {
          /* illegal track or sector */
          if (!sector) {
            blocks += sectdone;
            goto done;
          }
          iltab[highsect = sector] = 2;
          if (sectdone < highsect)
            goto findnext;
          goto nexttrack;
        }
        else if (memcmp (buf, "00,", 3))
          goto not00;
        else
          break;
      }
      /* ok, mark the sector written */
      iltab[sector] = 1;
      sectdone++;
      }

    nexttrack:
      blocks += sectdone;
    }
    else {
      for (sector = 0; sector < 1000; sector++, blocks++) {
      if (read_sector_file (file, sector, blocks, buf))
        return 0;

      if ((status = write_remote (comm, 15, bp + 3, 7)) ||
          (status = write_remote (comm, 2, buf, 256)))
        goto abort;

      for (;;) {
        if ((status = command_remote (comm, buf, '2', drive,
                              track, sector)) ||
            (status = read_remote (comm, 15, buf, &len)) || len < 3)
          goto abort;
        if (!memcmp (buf, "66,", 3)) {
          /* illegal track or sector */
          if (!sector)
            goto done;
          goto trackdone;
        }
        else if (memcmp (buf, "00,", 3))
          goto not00;
        else
          break;
      }
      }
    trackdone:
      continue;
    }
  }

 done:
  fprintf (stderr, "\ndisk_write: %u blocks\n", blocks);
  return 0;
}

/** copy data from a disk drive's address space to a file
 * @param comm          the communication primitives
 * @param file          output file
 * @param start         start address (inclusive)
 * @param end           end address (exclusive)
 * @param buf           a buffer of at least 260 bytes
 * @return        zero on success, nonzero on error
 */
int
00595 disk_mread (const struct comm* comm, FILE* file,
          unsigned start, unsigned end,
          char* buf)
{
  for (;;) {
    unsigned len;
    int status;
    {
      register char* b = buf + 3;
      *b++ = 'M', *b++ = '-', *b++ = 'R';
      *b++ = start, *b++ = start >> 8, *b++ = 0;
    }
    if ((status = write_remote (comm, 15, buf + 3, 6))) {
    fail:
      fputs ("disk_mread: communication failure\n", stderr);
      return status;
    }

    if ((status = read_remote (comm, 15, buf, &len)))
      goto fail;
    if (len >= ((end - start) & 0xffff)) {
      len = (end - start) & 0xffff;
      if (len != fwrite (buf, 1, len, file)) {
      failWrite:
      perror ("disk_mread: fwrite");
      return -1;
      }
      else {
      register char* b = buf + 3;
      *b++ = 'U', *b++ = 'I';
      return write_remote (comm, 15, buf + 3, 2);
      }
    }

    start += len;
    if (len != fwrite (buf, 1, len, file))
      goto failWrite;
  }
}

/** copy data from a disk drive's controller address space to a file
 * @param comm          the communication primitives
 * @param file          output file
 * @param start         start address (inclusive)
 * @param end           end address (exclusive)
 * @param buf           a buffer of at least 260 bytes
 * @return        zero on success, nonzero on error
 */
int
00644 disk_cread (const struct comm* comm, FILE* file,
          unsigned start, unsigned end,
          char* buf)
{
  /** code to copy a page of controller address space to buffer #2
   * (0x1300 in DOS CPU address space; 0x0700 in disk controller address space)
   * - the code is written to buffer #1
   * (0x1200 in DOS CPU address space; 0x0600 in disk controller address space)
   */
  static const unsigned char dumper[] = {
    'M', '-', 'W', 0x00, 0x12, 16, /* write the following to buffer #1 */
    0xa2, 0,            /* ldx #0 */
    0xbd, 0x00, 0x00,   /* lda $0000,x    ; source address */
    0x9d, 0x00, 0x07,   /* sta $0700,x    ; target address, 0x1300 in DOS CPU */
    0xca,         /* dex */
    0xd0, 0xf7,         /* bne .-7 */
    0xa9, 1,            /* lda #1 */
    0x6c, 0x02, 0xfc    /* jmp ($fc02)    ; terminate controller job */
  };

  /** code for initiating the firmware dump */
  static const unsigned char dumpstart[] = {
    'M', '-', 'W', 0x04, 0x10, 1, 0xd0 /* start job in buffer #1 */
  };

  static const unsigned char copydump[] = {
    'M', '-', 'R', 0x00, 0x13, 0 /* read 256 bytes from buffer #2 */
  };

  for (;;) {
    unsigned len;
    int status;
    memcpy (buf + 3, dumper, sizeof dumper);
    buf[3 + 6 + 3] = start, buf[3 + 6 + 4] = start >> 8;
    if ((status = write_remote (comm, 15, buf + 3, sizeof dumper))) {
    fail:
      fputs ("disk_cread: communication failure\n", stderr);
      return status;
    }
    memcpy (buf + 3, dumpstart, sizeof dumpstart);
    if ((status = write_remote (comm, 15, buf + 3, sizeof dumpstart)))
      goto fail;
    memcpy (buf + 3, copydump, sizeof copydump);
    if ((status = write_remote (comm, 15, buf + 3, sizeof copydump)))
      goto fail;
    if ((status = read_remote (comm, 15, buf, &len)))
      goto fail;
    if (len >= ((end - start) & 0xffff)) {
      len = (end - start) & 0xffff;
      if (len != fwrite (buf, 1, len, file)) {
      failWrite:
      perror ("disk_cread: fwrite");
      return -1;
      }
      else {
      register char* b = buf + 3;
      *b++ = 'U', *b++ = 'I';
      return write_remote (comm, 15, buf + 3, 2);
      }
    }

    start += len;
    if (len != fwrite (buf, 1, len, file))
      goto failWrite;
  }
}

/** copy data from a file to a disk drive's address space
 * @param comm          the communication primitives
 * @param file          input file
 * @param start         start address (inclusive)
 * @param buf           a buffer of at least 42 bytes
 * @return        zero on success, nonzero on error
 */
int
00719 disk_mwrite (const struct comm* comm, FILE* file,
           unsigned start,
           char* buf)
{
  unsigned len;
  register char* b = buf + 3;
  *b++ = 'M', *b++ = '-', *b++ = 'W';
  for (;;) {
    int status;
    *b++ = start, *b++ = start >> 8;
    if (!(*b++ = len = fread (b + 1, 1, 32, file)))
      return 0;
    if ((status = write_remote (comm, 15, buf + 3, len))) {
      fputs ("disk_mwrite: communication failure\n", stderr);
      return status;
    }
    start += len, b -= 3;
  }
}

Generated by  Doxygen 1.6.0   Back to index