mirror of
https://github.com/pevans/erc-c.git
synced 2024-11-27 20:51:17 +00:00
Add drive phases, switches for control and i/o
This commit is contained in:
parent
e901bcf54d
commit
9c0e01ecd2
@ -1,10 +1,18 @@
|
||||
#ifndef _APPLE2_DISK_DRIVE_H
|
||||
#define _APPLE2_DISK_DRIVE_H
|
||||
|
||||
/*
|
||||
* Forward declaration of apple2dd for some files (e.g. apple2.h) which
|
||||
* want to know about us before we have actually defined the struct.
|
||||
*/
|
||||
struct apple2dd;
|
||||
typedef struct apple2dd apple2dd;
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "apple2.h"
|
||||
#include "vm_bits.h"
|
||||
#include "vm_segment.h"
|
||||
|
||||
@ -38,7 +46,44 @@ enum apple2_dd_mode {
|
||||
*/
|
||||
#define MAX_SECTOR_POS 4095
|
||||
|
||||
typedef struct {
|
||||
struct apple2dd {
|
||||
/*
|
||||
* Inside the disk drive there is a stepper motor, and it's
|
||||
* controlled through four "phases", which are a bit hard to
|
||||
* describe. Imagine four points on a wheel; suppose in order to
|
||||
* to roll the wheel forward on the ground, you could only do so by
|
||||
* controlling which point is facing the ground. A smooth rotation
|
||||
* would always require you choose the point which is
|
||||
* counter-clockwise from the ground at a 90º angle; and the point
|
||||
* which was on the ground, is now clockwise from the ground, at
|
||||
* 90º. Going backwards is similar, except you choose the point
|
||||
* clockwise from the ground; and the point on the ground now goes
|
||||
* counter-clockwise.
|
||||
*
|
||||
* To advance the motor, you need to turn on an adjacent phase; if
|
||||
* phase 1 is on, turn on phase 2 and turn off phase 1; this allows
|
||||
* you to go "forward"; and then turn on phase 3, and turn off phase
|
||||
* 2; and you wrap around, so you turn on phase 0 and turn off phase
|
||||
* 3. And vice versa for going "backward". In this field, then, we
|
||||
* really care about only four bits; 0x1, 0x2, 0x4, and 0x8; and, in
|
||||
* particular, we care about the adjacent relations of those high
|
||||
* and low bits.
|
||||
*
|
||||
* It's really like if you unrolled the surface of the wheel and
|
||||
* laid it out as a flat line, but still with those points defined.
|
||||
* Does that make sense?
|
||||
*/
|
||||
vm_8bit phase_state;
|
||||
vm_8bit last_phase;
|
||||
|
||||
/*
|
||||
* Data is written via a "latch", and happens in two steps; one, you
|
||||
* set the latch; two, you commit the write. By steps, I mean two
|
||||
* separate instructions--not necessarily adjacent to each other,
|
||||
* but in some sequence and in that order.
|
||||
*/
|
||||
vm_8bit latch;
|
||||
|
||||
/*
|
||||
* Disk II drives allow the stepper to move in half-tracks, so we
|
||||
* track (pun intended) the position of the head in those
|
||||
@ -99,19 +144,26 @@ typedef struct {
|
||||
* that you can enable or disable on the drive.
|
||||
*/
|
||||
bool write_protect;
|
||||
} apple2dd;
|
||||
};
|
||||
|
||||
extern SEGMENT_READER(apple2_dd_switch_read);
|
||||
extern SEGMENT_WRITER(apple2_dd_switch_write);
|
||||
extern apple2dd *apple2_dd_create();
|
||||
extern int apple2_dd_insert(apple2dd *, FILE *);
|
||||
extern int apple2_dd_position(apple2dd *);
|
||||
extern vm_8bit apple2_dd_read(apple2dd *);
|
||||
extern void apple2_dd_eject(apple2dd *);
|
||||
extern void apple2_dd_free(apple2dd *);
|
||||
extern void apple2_dd_map(vm_segment *);
|
||||
extern void apple2_dd_set_mode(apple2dd *, int);
|
||||
extern void apple2_dd_shift(apple2dd *, int);
|
||||
extern void apple2_dd_step(apple2dd *, int);
|
||||
extern void apple2_dd_switch_drive(apple2 *, size_t);
|
||||
extern void apple2_dd_switch_latch(apple2dd *, vm_8bit);
|
||||
extern void apple2_dd_switch_phase(apple2dd *, size_t);
|
||||
extern vm_8bit apple2_dd_switch_rw(apple2dd *);
|
||||
extern void apple2_dd_turn_on(apple2dd *, bool);
|
||||
extern void apple2_dd_write(apple2dd *, vm_8bit);
|
||||
extern void apple2_dd_write(apple2dd *);
|
||||
extern void apple2_dd_write_protect(apple2dd *, bool);
|
||||
|
||||
#endif
|
||||
|
@ -1,6 +1,13 @@
|
||||
#ifndef _APPLE2_H_
|
||||
#define _APPLE2_H_
|
||||
|
||||
/*
|
||||
* A forward declaration is needed to avoid some errors in dd.h where we
|
||||
* need to define a function that accepts an apple2 pointer.
|
||||
*/
|
||||
struct apple2;
|
||||
typedef struct apple2 apple2;
|
||||
|
||||
#include "apple2.dd.h"
|
||||
#include "mos6502.h"
|
||||
#include "vm_bitfont.h"
|
||||
@ -236,7 +243,7 @@ enum bank_switch {
|
||||
BANK_ALTZP = 0x8,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct apple2 {
|
||||
/*
|
||||
* The apple 2 hardware used an MOS-6502 processor.
|
||||
*/
|
||||
@ -320,7 +327,13 @@ typedef struct {
|
||||
*/
|
||||
apple2dd *drive1;
|
||||
apple2dd *drive2;
|
||||
} apple2;
|
||||
|
||||
/*
|
||||
* The Apple II machine allows you to "select" a drive, and the
|
||||
* operations you perform are (mostly) targeting that drive.
|
||||
*/
|
||||
apple2dd *selected_drive;
|
||||
};
|
||||
|
||||
extern apple2 *apple2_create(int, int);
|
||||
extern bool apple2_is_double_video(apple2 *);
|
||||
|
255
src/apple2.dd.c
255
src/apple2.dd.c
@ -2,6 +2,7 @@
|
||||
* apple2.disk_drive.c
|
||||
*/
|
||||
|
||||
#include "apple2.h"
|
||||
#include "apple2.dd.h"
|
||||
|
||||
/*
|
||||
@ -30,6 +31,8 @@ apple2_dd_create()
|
||||
drive->online = false;
|
||||
drive->write_protect = true;
|
||||
drive->mode = DD_READ;
|
||||
drive->phase_state = 0;
|
||||
drive->last_phase = 0;
|
||||
|
||||
return drive;
|
||||
}
|
||||
@ -78,6 +81,51 @@ apple2_dd_insert(apple2dd *drive, FILE *stream)
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate the value of the phase_state against the last known phase,
|
||||
* and decide from there whether we should step forward or backward by a
|
||||
* half-track. This function, as a side-effect, will update the last
|
||||
* phase to be the current phase state if the step was successful.
|
||||
*/
|
||||
void
|
||||
apple2_dd_phaser(apple2dd *drive)
|
||||
{
|
||||
int phase = drive->phase_state;
|
||||
int last = drive->last_phase;
|
||||
|
||||
// This is a bit of trickery; there is no phase state for 0x10 or
|
||||
// 0x0, but we want to pretend like the phase is "next" to the bit
|
||||
// we're operating with for the purpose of establishing a direction.
|
||||
if (phase == 0x1 && last == 0x8) {
|
||||
phase = 0x10;
|
||||
} else if (phase == 0x8 && last == 0x1) {
|
||||
phase = 0x0;
|
||||
}
|
||||
|
||||
// We only want to respond to adjacent phases, so if the last phase
|
||||
// shifted in _any_ direction is not equal to the phase state, then
|
||||
// we should do nothing.
|
||||
if (phase != (last << 1) ||
|
||||
phase != (last >> 1)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If phase > last, then we must move the head forward by a half
|
||||
// track. If it's < last, then we move the head backward, again by a
|
||||
// half track.
|
||||
if (phase > last) {
|
||||
apple2_dd_step(drive, 1);
|
||||
} else if (phase < last) {
|
||||
apple2_dd_step(drive, -1);
|
||||
}
|
||||
|
||||
// Recall our trickery above with the phase variable? Because of it,
|
||||
// we have to save the phase_state field into last_phase, and not
|
||||
// the pseudo-value we assigned to phase.
|
||||
drive->last_phase = drive->phase_state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the segment position that the drive is currently at, based
|
||||
* upon track and sector position.
|
||||
@ -214,9 +262,9 @@ apple2_dd_turn_on(apple2dd *drive, bool online)
|
||||
* shift the drive position forward by one byte.
|
||||
*/
|
||||
void
|
||||
apple2_dd_write(apple2dd *drive, vm_8bit byte)
|
||||
apple2_dd_write(apple2dd *drive)
|
||||
{
|
||||
vm_segment_set(drive->data, apple2_dd_position(drive), byte);
|
||||
vm_segment_set(drive->data, apple2_dd_position(drive), drive->latch);
|
||||
apple2_dd_shift(drive, 1);
|
||||
}
|
||||
|
||||
@ -231,3 +279,206 @@ apple2_dd_write_protect(apple2dd *drive, bool protect)
|
||||
{
|
||||
drive->write_protect = protect;
|
||||
}
|
||||
|
||||
/*
|
||||
* Half of all the disk drive switches deal with turning on and off the
|
||||
* different phases of the stepper. We handle all of them here.
|
||||
*/
|
||||
void
|
||||
apple2_dd_switch_phase(apple2dd *drive, size_t addr)
|
||||
{
|
||||
switch (addr & 0xF) {
|
||||
case 0x0: drive->phase_state &= ~0x1; break;
|
||||
case 0x1: drive->phase_state |= 0x1; break;
|
||||
case 0x2: drive->phase_state &= ~0x2; break;
|
||||
case 0x3: drive->phase_state |= 0x2; break;
|
||||
case 0x4: drive->phase_state &= ~0x4; break;
|
||||
case 0x5: drive->phase_state |= 0x4; break;
|
||||
case 0x6: drive->phase_state &= ~0x8; break;
|
||||
case 0x7: drive->phase_state |= 0x8; break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles all of the switch behavior that handles drive
|
||||
* metadata, like what drive is turned on, or which one is selected.
|
||||
*/
|
||||
void
|
||||
apple2_dd_switch_drive(apple2 *mach, size_t addr)
|
||||
{
|
||||
switch (addr) {
|
||||
case 0x8:
|
||||
apple2_dd_turn_on(mach->drive1, false);
|
||||
apple2_dd_turn_on(mach->drive2, false);
|
||||
break;
|
||||
|
||||
case 0x9:
|
||||
apple2_dd_turn_on(mach->selected_drive, true);
|
||||
break;
|
||||
|
||||
case 0xA:
|
||||
mach->selected_drive = mach->drive1;
|
||||
break;
|
||||
|
||||
case 0xB:
|
||||
mach->selected_drive = mach->drive2;
|
||||
break;
|
||||
|
||||
case 0xE:
|
||||
mach->selected_drive->mode = DD_READ;
|
||||
break;
|
||||
|
||||
case 0xF:
|
||||
mach->selected_drive->mode = DD_WRITE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the disk drive is in write mode, we are allowed (!) to set the
|
||||
* latch value to whatever is passed in here.
|
||||
*
|
||||
* What's a latch value? Good question! It's basically a placeholder for
|
||||
* data to be committed (written) to disk. Writing via the Disk II drive
|
||||
* is a two-legged process; one, set the latch value; two, actually
|
||||
* write the latch value to the disk. As to why things are done this
|
||||
* way, I can only imagine that there were technical reasons for
|
||||
* essentially requiring the data to be written to be onboard the disk
|
||||
* drive itself.
|
||||
*/
|
||||
void
|
||||
apple2_dd_switch_latch(apple2dd *drive, vm_8bit value)
|
||||
{
|
||||
if (drive->mode == DD_WRITE) {
|
||||
drive->latch = value;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles the logic for reading and/or writing to a Disk
|
||||
* II drive. What exactly happens here depends very much on the drive
|
||||
* mode, as well as whether or not we consider the disk to be
|
||||
* write-protected.
|
||||
*/
|
||||
vm_8bit
|
||||
apple2_dd_switch_rw(apple2dd *drive)
|
||||
{
|
||||
// If we are in read mode OR if we are working with a
|
||||
// write-protected disk, then all operations are interpreted as
|
||||
// read operations. If we are specifically in write mode, and in the
|
||||
// else condition we can say that the drive is not write-protected,
|
||||
// then we will write the latch data to the drive.
|
||||
if (drive->mode == DD_READ || drive->write_protect) {
|
||||
return apple2_dd_read(drive);
|
||||
} else if (drive->mode == DD_WRITE) {
|
||||
apple2_dd_write(drive);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles reads to any of the disk II controller
|
||||
* addresses. Note that it's possible to write to a disk with a call to
|
||||
* this "read" function! Pay less attention to where these functions are
|
||||
* mapped, and pay more attention to the specific behavior of the
|
||||
* address switches being used.
|
||||
*/
|
||||
SEGMENT_READER(apple2_dd_switch_read)
|
||||
{
|
||||
apple2 *mach = (apple2 *)_mach;
|
||||
apple2dd *drive = mach->selected_drive;
|
||||
|
||||
// A nibble is a half-byte... not to be confused with the .NIB file
|
||||
// format
|
||||
size_t nib = addr & 0xF;
|
||||
|
||||
// This might not be _right_... a better solution might be to bail
|
||||
// out unless the address indicates that the operation is not on a
|
||||
// specific drive, like turning drives off, or selecting a new
|
||||
// drive.
|
||||
if (drive == NULL) {
|
||||
drive = mach->drive1;
|
||||
}
|
||||
|
||||
// In the first if block, we will handle 0x0..0x8; in the second if,
|
||||
// we'll do 0x9..0xB, 0xE, and 0xF.
|
||||
if (nib < 0x9) {
|
||||
apple2_dd_switch_phase(drive, nib);
|
||||
} else if (nib < 0xC || nib > 0xD) {
|
||||
apple2_dd_switch_drive(mach, nib);
|
||||
}
|
||||
|
||||
// This is the read/write address... various states of the disk
|
||||
// drive will dictate what we do here.
|
||||
if (nib == 0xC) {
|
||||
return apple2_dd_switch_rw(drive);
|
||||
} else if (nib == 0xD) {
|
||||
// In a read context, accessing the latch switch will pass a
|
||||
// zero value into the latch. (The latch value will only be
|
||||
// committed if the drive itself is in write mode.)
|
||||
apple2_dd_switch_latch(drive, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* A decent portion of the logic in this function is similar to the
|
||||
* switch_read function, the defining difference being that here we have
|
||||
* a potentially-nonzero value to pass into the switch_latch function.
|
||||
* As such I have not commented much on the code here outside of what
|
||||
* can happen specifically in switch_write(); in the future it wouldn't
|
||||
* be a bad idea to refactor this common code into its own function.
|
||||
*/
|
||||
SEGMENT_WRITER(apple2_dd_switch_write)
|
||||
{
|
||||
apple2 *mach = (apple2 *)_mach;
|
||||
apple2dd *drive = mach->selected_drive;
|
||||
size_t nib = addr & 0xF;
|
||||
|
||||
if (drive == NULL) {
|
||||
drive = mach->drive1;
|
||||
}
|
||||
|
||||
if (nib < 0x9) {
|
||||
apple2_dd_switch_phase(drive, nib);
|
||||
} else if (nib < 0xC || nib > 0xD) {
|
||||
apple2_dd_switch_drive(mach, nib);
|
||||
}
|
||||
|
||||
// It's possible to attempt a "read" from a disk drive while doing a
|
||||
// write to the $C0nC address; all this does in effect is to shift
|
||||
// the disk forward. The more likely thing is that, if we are in
|
||||
// write mode, we will commit the latch value to disk; but that can
|
||||
// happen from either this particular function or from the
|
||||
// switch_read function.
|
||||
if (nib == 0xC) {
|
||||
apple2_dd_switch_rw(drive);
|
||||
} else if (nib == 0xD) {
|
||||
// The only way to get a latch value that is non-zero is to
|
||||
// write to the $C0nD address, where n is the address of one of
|
||||
// the disk controller ROMs. And even then, the drive needs to
|
||||
// be in write mode.
|
||||
apple2_dd_switch_latch(drive, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Map the Disk II drive switch addresses.
|
||||
*/
|
||||
void
|
||||
apple2_dd_map(vm_segment *seg)
|
||||
{
|
||||
size_t addr;
|
||||
|
||||
for (addr = 0xC0E0; addr < 0xC0F0; addr++) {
|
||||
vm_segment_read_map(seg, addr, apple2_dd_switch_read);
|
||||
vm_segment_write_map(seg, addr, apple2_dd_switch_write);
|
||||
}
|
||||
|
||||
for (addr = 0xC0F0; addr < 0xC100; addr++) {
|
||||
vm_segment_read_map(seg, addr, apple2_dd_switch_read);
|
||||
vm_segment_write_map(seg, addr, apple2_dd_switch_write);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "apple2.bank.h"
|
||||
#include "apple2.dbuf.h"
|
||||
#include "apple2.dd.h"
|
||||
#include "apple2.h"
|
||||
#include "apple2.kb.h"
|
||||
#include "apple2.mem.h"
|
||||
@ -62,6 +63,9 @@ apple2_mem_map(apple2 *mach, vm_segment *segment)
|
||||
// And this handles our keyboard soft switches
|
||||
apple2_kb_map(segment);
|
||||
|
||||
// Map our disk drive switches
|
||||
apple2_dd_map(segment);
|
||||
|
||||
// We will do the mapping for the zero page and stack addresses.
|
||||
// Accessing those addresses can be affected by bank-switching, but
|
||||
// those addresses do not actually exist in the capital
|
||||
|
Loading…
Reference in New Issue
Block a user