2017-12-14 02:30:32 +00:00
|
|
|
/*
|
|
|
|
* apple2.disk_drive.c
|
|
|
|
*/
|
|
|
|
|
2017-12-15 04:27:45 +00:00
|
|
|
#include "apple2.dd.h"
|
2017-12-14 02:30:32 +00:00
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Create a new disk drive. We do not create a memory segment for the
|
|
|
|
* drive right away, as the size of said data can be variable based on
|
|
|
|
* the disk format.
|
|
|
|
*/
|
2017-12-15 04:14:55 +00:00
|
|
|
apple2dd *
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_create()
|
2017-12-14 02:30:32 +00:00
|
|
|
{
|
2017-12-15 04:14:55 +00:00
|
|
|
apple2dd *drive;
|
2017-12-14 02:30:32 +00:00
|
|
|
|
2017-12-15 04:14:55 +00:00
|
|
|
drive = malloc(sizeof(apple2dd));
|
2017-12-14 02:30:32 +00:00
|
|
|
if (drive == NULL) {
|
|
|
|
log_critical("Could not malloc space for apple2 disk drive");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// To begin with, we have no segment for data; that's something that
|
|
|
|
// will depend on the disk you insert. For example, a DOS 3.3 or
|
|
|
|
// ProDOS disk will have 140k, but a NIB file would have more.
|
|
|
|
drive->data = NULL;
|
|
|
|
|
|
|
|
drive->track_pos = 0;
|
2017-12-15 04:14:55 +00:00
|
|
|
drive->sector_pos = 0;
|
2017-12-14 02:30:32 +00:00
|
|
|
drive->online = false;
|
|
|
|
drive->write_protect = true;
|
|
|
|
drive->mode = DD_READ;
|
|
|
|
|
|
|
|
return drive;
|
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Insert a "disk" into the drive, such that a disk is delivered to us
|
|
|
|
* through a FILE stream. Return an error code if the disk format is
|
|
|
|
* something we cannot accept.
|
|
|
|
*/
|
2017-12-15 22:45:20 +00:00
|
|
|
int
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_insert(apple2dd *drive, FILE *stream)
|
2017-12-15 22:45:20 +00:00
|
|
|
{
|
|
|
|
struct stat finfo;
|
2018-01-01 02:28:11 +00:00
|
|
|
int err;
|
2017-12-15 22:45:20 +00:00
|
|
|
|
|
|
|
if (stream == NULL) {
|
|
|
|
log_critical("File stream is null");
|
|
|
|
return ERR_BADFILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// How large is this data set? Let's get the stat info.
|
|
|
|
if (fstat(fileno(stream), &finfo)) {
|
|
|
|
log_critical("Couldn't inspect file stream: %s", strerror(errno));
|
|
|
|
return ERR_BADFILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (finfo.st_size != _140K_) {
|
|
|
|
log_critical("Unexpected file format (file size = %d)", finfo.st_size);
|
|
|
|
return ERR_BADFILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have any data, get rid of it. We'll start fresh here.
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_eject(drive);
|
2017-12-15 22:45:20 +00:00
|
|
|
|
|
|
|
drive->data = vm_segment_create(finfo.st_size);
|
|
|
|
drive->track_pos = 0;
|
|
|
|
drive->sector_pos = 0;
|
|
|
|
|
2018-01-01 02:28:11 +00:00
|
|
|
// Read the data from the stream and write into the memory segment
|
2018-01-03 21:21:00 +00:00
|
|
|
err = vm_segment_fread(drive->data, stream, 0, finfo.st_size);
|
2018-01-01 02:28:11 +00:00
|
|
|
if (err != OK) {
|
|
|
|
log_critical("Could not read data into disk drive");
|
|
|
|
return err;
|
|
|
|
}
|
2017-12-31 21:54:30 +00:00
|
|
|
|
2017-12-15 22:45:20 +00:00
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Return the segment position that the drive is currently at, based
|
|
|
|
* upon track and sector position.
|
|
|
|
*/
|
2017-12-26 22:42:04 +00:00
|
|
|
int
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_position(apple2dd *drive)
|
2017-12-15 22:45:20 +00:00
|
|
|
{
|
2017-12-26 22:42:04 +00:00
|
|
|
// Special case: they didn't load any image data into the "drive".
|
|
|
|
// Return zero.
|
|
|
|
if (drive->data == NULL) {
|
|
|
|
return 0;
|
2017-12-15 22:45:20 +00:00
|
|
|
}
|
|
|
|
|
2017-12-26 22:42:04 +00:00
|
|
|
// This is a normative DOS 3.3 / ProDOS disk. (Except ProDOS is
|
|
|
|
// separated into 512 byte blocks which _shouldn't_ matter for our
|
|
|
|
// purposes but let's not talk about that here do-de-doo.)
|
|
|
|
if (drive->data->size == _140K_) {
|
|
|
|
int track_offset;
|
2017-12-14 02:30:32 +00:00
|
|
|
|
2017-12-26 22:42:04 +00:00
|
|
|
track_offset = (drive->track_pos % 2) * 4096;
|
|
|
|
return track_offset + drive->sector_pos;
|
2017-12-14 02:30:32 +00:00
|
|
|
}
|
2017-12-26 22:42:04 +00:00
|
|
|
|
|
|
|
return 0;
|
2017-12-14 02:30:32 +00:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Read a single byte from the disk drive, at its current position, and
|
|
|
|
* then shift the head by 1 byte.
|
|
|
|
*/
|
2017-12-26 22:42:04 +00:00
|
|
|
vm_8bit
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_read(apple2dd *drive)
|
2017-12-14 02:30:32 +00:00
|
|
|
{
|
2018-01-07 19:44:07 +00:00
|
|
|
vm_8bit byte = vm_segment_get(drive->data, apple2_dd_position(drive));
|
|
|
|
apple2_dd_shift(drive, 1);
|
2017-12-14 02:30:32 +00:00
|
|
|
|
2017-12-26 22:42:04 +00:00
|
|
|
return byte;
|
2017-12-14 02:30:32 +00:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Here we mean to "empty" the drive, essentially freeing the segment
|
|
|
|
* memory and resetting the head position.
|
|
|
|
*/
|
2017-12-14 02:30:32 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_eject(apple2dd *drive)
|
2017-12-14 02:30:32 +00:00
|
|
|
{
|
2017-12-26 22:42:04 +00:00
|
|
|
if (drive->data) {
|
|
|
|
vm_segment_free(drive->data);
|
|
|
|
drive->data = NULL;
|
|
|
|
}
|
2018-01-01 23:26:29 +00:00
|
|
|
|
|
|
|
drive->track_pos = 0;
|
|
|
|
drive->sector_pos = 0;
|
2017-12-14 02:30:32 +00:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Free the memory taken up by the disk drive.
|
|
|
|
*/
|
2017-12-14 02:30:32 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_free(apple2dd *drive)
|
2017-12-15 04:14:55 +00:00
|
|
|
{
|
2017-12-26 22:42:04 +00:00
|
|
|
if (drive->data) {
|
|
|
|
vm_segment_free(drive->data);
|
2017-12-15 05:08:33 +00:00
|
|
|
}
|
|
|
|
|
2017-12-26 22:42:04 +00:00
|
|
|
free(drive);
|
|
|
|
}
|
2017-12-15 04:14:55 +00:00
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Set the disk drive mode, which is either read or write. (It can only
|
|
|
|
* be one or the other at a time.)
|
|
|
|
*/
|
2017-12-26 22:42:04 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_set_mode(apple2dd *drive, int mode)
|
2017-12-26 22:42:04 +00:00
|
|
|
{
|
|
|
|
if (mode != DD_READ && mode != DD_WRITE) {
|
|
|
|
return;
|
2017-12-15 04:14:55 +00:00
|
|
|
}
|
|
|
|
|
2017-12-26 22:42:04 +00:00
|
|
|
drive->mode = mode;
|
2017-12-15 04:14:55 +00:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Shift the head position in the drive by the given positions, which is
|
|
|
|
* in bytes. Pos may be a negative number; if so, the head essentially
|
|
|
|
* moves further away from the center of the magnetic wafer.
|
|
|
|
*/
|
2017-12-15 22:45:20 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_shift(apple2dd *drive, int pos)
|
2017-12-15 04:14:55 +00:00
|
|
|
{
|
2017-12-15 22:45:20 +00:00
|
|
|
drive->sector_pos += pos;
|
2017-12-15 04:14:55 +00:00
|
|
|
|
2017-12-15 22:52:47 +00:00
|
|
|
while (drive->sector_pos > MAX_SECTOR_POS) {
|
2017-12-15 22:45:20 +00:00
|
|
|
// We need to reset the sector pos to zero, because...
|
2017-12-15 22:52:47 +00:00
|
|
|
drive->sector_pos -= (MAX_SECTOR_POS + 1);
|
2017-12-15 22:45:20 +00:00
|
|
|
|
|
|
|
// We also need to move to the next track, so let's adjust by
|
|
|
|
// two half-tracks.
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_step(drive, 2);
|
2017-12-15 04:14:55 +00:00
|
|
|
}
|
2017-12-15 22:45:20 +00:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* When you step the drive, you are essentially moving the head in
|
|
|
|
* track positions. It's not really faster for _us_, but it's faster for
|
|
|
|
* a mechanical drive than a bunch of shifts if you know the data is far
|
|
|
|
* away track-wise. This function also safeguards (as the drive did!)
|
|
|
|
* against stepping too far out or too far in.
|
|
|
|
*/
|
2017-12-26 22:42:04 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_step(apple2dd *drive, int steps)
|
2017-12-15 22:45:20 +00:00
|
|
|
{
|
2017-12-26 22:42:04 +00:00
|
|
|
drive->track_pos += steps;
|
2017-12-15 04:14:55 +00:00
|
|
|
|
2017-12-26 22:42:04 +00:00
|
|
|
if (drive->track_pos > MAX_DRIVE_STEPS) {
|
|
|
|
drive->track_pos = MAX_DRIVE_STEPS;
|
|
|
|
} else if (drive->track_pos < 0) {
|
|
|
|
drive->track_pos = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* A really simple function to turn the drive "on".
|
|
|
|
*/
|
2017-12-26 22:42:04 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_turn_on(apple2dd *drive, bool online)
|
2017-12-26 22:42:04 +00:00
|
|
|
{
|
|
|
|
drive->online = online;
|
2017-12-15 04:14:55 +00:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Write a byte to the disk in the drive. This is pretty similar to the
|
|
|
|
* read function in that, once we do what we need with the segment, we
|
|
|
|
* shift the drive position forward by one byte.
|
|
|
|
*/
|
2017-12-15 04:14:55 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_write(apple2dd *drive, vm_8bit byte)
|
2017-12-15 04:14:55 +00:00
|
|
|
{
|
2018-01-07 19:44:07 +00:00
|
|
|
vm_segment_set(drive->data, apple2_dd_position(drive), byte);
|
|
|
|
apple2_dd_shift(drive, 1);
|
2017-12-15 04:14:55 +00:00
|
|
|
}
|
2017-12-26 22:42:04 +00:00
|
|
|
|
2018-01-01 23:26:29 +00:00
|
|
|
/*
|
|
|
|
* Set the write-protect status for the disk. Note that it was _disks_
|
|
|
|
* that were write-protected in the past, sometimes by taping over a
|
|
|
|
* chunk that was clipped out of the disk. So this function is somewhat
|
|
|
|
* similar to just taping over or removing that tape.
|
|
|
|
*/
|
2017-12-26 22:42:04 +00:00
|
|
|
void
|
2018-01-07 19:44:07 +00:00
|
|
|
apple2_dd_write_protect(apple2dd *drive, bool protect)
|
2017-12-26 22:42:04 +00:00
|
|
|
{
|
|
|
|
drive->write_protect = protect;
|
|
|
|
}
|