kegs/src/INTERNALS.iwm

240 lines
11 KiB
Plaintext

KEGS's Apple //gs IWM emulation routines.
The IWM code does 5.25" and 3.5" reads & writes, and updates the Unix disk
image on writes. It is also nearly cycle-accurate--Let me know if you
have a program which can detect it's not a real Apple II. There are
a few 5.25" features missing (No 1/4 or 1/2 tracks, no support for Unix nibble
images, limited disk switching), but what's there is pretty accurate.
The low-level code support 1/4 and 1/2 tracks--it's the arm movement
and image-handling routines which don't. And lack of Unix nibble images
are also due to lack of higher-level routines to make those features work.
How my disk emulation works: The routines have a nibblized image of each
track of each drive (two 5.25" and two 3.5" drives are supported) in memory.
The nibble images are declared as arrays, but it could be made to use
more dynamic memory allocation.
Each track's data format is a series of two-byte pairs. The first byte
of the pair is the number of bits in this disk byte, and the second byte
is the value. So a size of 8 is normal. A size of 10 means that there
are 2 sync bits written before this byte on the disk. So for 5.25" disk
accesses, 40 cycles need to pass in the simulator before providing a
valid nibble. Partial nibbles are correctly formed if a read happens
too early (this actually makes things slower, but is required if you
want to make nibble copiers work). Similarly, writing to the disk
watches timing carefully to write out the correct number of bits per
disk byte. These routines will definitely test out your emulator's cycle
counting ability.
If a long delay occurs between a read (or a write) the routines skips
the correct number of bits to return the correctly formed disk byte.
After a long delay, for efficiency, I always return a full disk byte,
instead of a partial one, even if the timing would put it in the middle
of a disk byte.
The arm stepping is really lame. I will clean it up soon.
Smartport support is sufficient to claim that there are no smartport
devices. This is necessary since the ROM tries to see if there are
smartport devices at power-on.
I tested my 5.25" drive routines on EDD, which could correctly measure
drive speed and other disk factors. I also nibble-copied some disks,
which also worked fine. I tested the 3.5" routines using Copy II+,
which successfully nibble-copied several disks.
Code description:
Most code is in iwm.c, with some defines in iwm.h, and some stuff in
iwm_35_525.h.
Code only supports DOS3.3 ordered 5.25" images now, and ProDOS-ordered 3.5"
images. Well, the code supports ProDOS-order 5.25" also, but has no
mechanism to tell it an image is prodos-order yet. :-)
Iwm state is encoded in the Iwm structure.
drive525[2]: Disk structure for each 5.25" disk
drive35[2]: Disk structure for each 3.5" disk
smarport[32]: Disk structure for each "smartport" device emulated
via slot 7 (this code not included)
motor_on: True if IWM motor_on signal (c0e9) is asserted. Some
drive is on.
motor_off: True if motor has been turned off in software, but the
1 second timeout has not expired yet.
motor_on35: True if 3.5" motor is on (controlled differently than
5.25" c0e9).
motor_off_vbl_count: VBL count to turn motor off.
head35, step_direction35: 3.5" controls, useless.
iwm_phase[4]: Has '1' for each 5.25" phase that is on.
iwm_mode: IWM mode register.
drive_select: 0 = drive 1, 1 = drive 2.
q6, q7: IWM q6, q7 registers.
enable2: Smartport /ENABLE2 asserted.
reset: Smartport /RESET asserted.
previous_write_val: Partial write value.
previous_write_bits: How many bits are valid in previous_write_val.
Each disk (3.5" and 5.25") is encoded in the Disk struct:
fd: Unix file descriptor. If < 0, no disk.
name_ptr: Unix file name for this disk.
image_start: offset from beginning of file for this partition.
image_size: size of this partition.
smartport: 1 if this is a smartport image, 0 if it is 5.25" or 3.5"
disk_525: 1 if this is a 5.25" image, 0 if it is 3.5"
drive: 0 = drive 1, 1 = drive 2.
cur_qtr_track: Current qtr track. So track 1 == qtr_track 4.
For 3.5", cur_qtr_track encodes the side also, so track 3
side 1 would be qtr_track 7.
prodos_order: True if Unix image is ProDOS order.
vol_num: DOS3.3 volume number to use. Always 254.
write_prot: True if disk is write protected.
write_through_to_unix: True if writes should be passed through to
the unix image. If this is false, you can write
to the image in memory, but it won't get reflected
into the Unix file. If you create a non-DOS3.3
or ProDOS format image, it automatically sets this
false.
disk_dirty: Some track has dirty data that need to be flushed.
just_ejected: Ejection flag.
dcycs_last_read: Cycle count of last disk data register access.
last_phase: Phase number last accessed.
nib_pos: Nibble offset ptr--points to a byte.
num_tracks: Number of tracks: 140 for 5.25" and 160 for 3.5"
track[MAX_TRACKS]: nibble image of all possible tracks.
Each track is represented by the Track structure:
track_dirty: Contains data that needs to be written back to
the Unix image file.
overflow_size: Count of overflow bits, used in writing.
track_len: Number of nibbles on this track.
dsk: Handy pointer to parent Disk structure.
nib_area[]: ptr to memory containing pairs of [size,data],
encoding disk data bytes.
pad1: If the structure is 32 bytes long, some array
indexing is done better by my compiler.
Externally callable routines:
iwm_init(): Init various data structures at simulation start.
iwm_reset(): Called at Apple //gs reset time.
iwm_vbl_update(): Called every VBL (60 Hz) period. Used to turn motor
off, and flush out dirty data.
g_vbl_count is the count of VBL ticks (so it counts
at 60 times a second).
iwm_read_c0ec(double dcycs): Optimized routine to handle reading $C0EC
faster. Exactly the same as read_iwm(0xc, dcycs);
read_iwm(loc, dcycs):
Read from 0xc0e0 + loc. Loc is between 0x0 and 0xf.
Dcycs is an artifact from my simulator. Dcycs is a
double holding the number of Apple //gs cycles since the
emulator started. Dcycs always counts at 1.024MHz. If
you are running at 2.5MHz, it increments by 0.4 every
"cycle". This is a very convenient timing strategy. It
also allows emulating the delay caused by synchronizing
the fast part of a real Apple //gs with slow memory,
which means my emulator knows that reading softswitches
takes longer than reading fast memory.
write_iwm(int loc, int val, double dcycs):
Write to 0xc0e0 + loc. Just like read_iwm, but write "val" into
loc.
Tricky routines:
IWM_READ_ROUT(): called by read_iwm() if q6,q7 = 0,0.
This is actually in the file iwm_35_525.h. This is so I
write the basic code once for 5.25" and 3.5" disk reads,
but then include the file with some macros set to create
the correct function optimized for 5.25" or 3.5"
accesses. The function for 5.25" is called
iwm_read_data_525, and iwm_read_data_35 for 3.5".
Returns next disk byte.
Takes three arguments: ptr to the Disk structure for
the active drive, fast_disk_emul, and dcycs. dcycs is
so that it can see how many cycles have passed since
the last read (stored in dsk->dcycs_last_read).
16.0 dcycs need to pass for an 8 bit nibble for 3.5"
accesses, and 32.0 dcycs for an 8 bit nibble for 5.25".
Fast_disk_emul == 1 says don't mess around with accuracy,
and always return the next fully-formed nibble.
There is a lot of complexity in this routine. All IWM
routines must skip over nibbles (stored as byte pairs in
dsk->nib_area[]) which have a size of 0 (special padding
trick, described later). It then determines how much
time has passed, and so how many bits are valid.
If too many bits have gone by (11 cycs is almost 3 5.25"
bit times, which is about the nibble valid time in
the Apple //gs IWM hardware latch), it tries to skip
to the correct position.
Handles IWM latch mode for 3.5" or 5.25" accesses. If a
partial read is indicated, it ensures that the high bit
is clear by shifting the nibble to the right
appropriately. Again, nib_area[] is an array of bytes,
which are treated as pairs. Byte 0 = size, byte 1 =
disk nibble.
IWM_WRITE_ROUT(): called by write_iwm() if q6,q7 = 1,1.
Similar to above. Handles async and sync mode writes.
Handles partial writes. Handles the ROM writing
0xff, 0x3f, 0xcf, 0xf3, 0xfc to be four 10-bit nibbles.
Routine disk_nib_out(dsk, val, bits_read) does the
actual work of merging the bits into the track image.
disk_nib_out(): called by IWM_WRITE_ROUTE() and iwm_nibblize_track_*().
Writes byte into nib_area[]. If size > 10, makes it 10.
If high order bit not set, it sets it (makes certain routines
in EDD happy).
overflow_size:
Writing to the disk creates some problems. I need to
maintain 2 things at all times on the track:
1) Constant number of bits for the entire track.
2) know where each synchronized byte starts on
the track.
If the track was just stored as raw bits, then correctly
simulating a delay of 300*4 cycles is tough, since it has to
be done by reading through all 300 bits on the track,
so that we keep in sync with where bytes really start.
But if you just store the bytes themselves, then sync
bytes look like every other byte. And if you now add
the size field, you have a situation where a track could
gain or lose bits when rewritten. Here's the case:
Assume the track contains: 10,ff 10,ff 10,ff 10,ff.
(That is 4 self-sync disk bytes of 10 bits each).
If we rewrite that area of the track with 'D5 AA 96 FF',
where each byte is 8 bits, we would have:
8,D5 8,AA, 8,96, 8,FF.
Looks OK, but we just lost 8 bits! The original 4 nibbles
were using 40 bits of space on the disk. Our new 4 nibbles
are using 32 bits. 8 bits are lost.
Solution: log these missing bits via overflow_size.
When writing, if overflow_size gets > 8, force out a 0,0
nibble. So sync bytes get written as:
10,FF 10,FF 10,FF 10,FF 0,0 10,FF 10,FF 10,FF 10,FF, 0,0.
So when they get re-written with 8,xx, we don't lose any
bytes on the disk.
Unfortunately, it doesn't quite work that easily, and bits
can still be lost when sync fields are partially overwritten.
This happens when all the 0,0's end up in a place on the
track where few overwrites occur, but other sync bytes
are being turned into 8,xx. So overflow_size goes negative,
saying we've got too much on the track.
The code prints an error when it gains more than 64 bits.
If someone can come up with a better scheme, I'd love to
hear it. A partial solution would be to have a routine
re-space the track to spread the needed 0,0's around
a little better when overflow_size gets too negative.
In iwm_nibblize_track_35(), the comments with hex numbers correspond
to the ROM 01 addresses which I disassembled to determine the checksum
algorithm. The code is not well written--it's basically hand-translated
65816 assembly code. I'll clean it up someday.
Much of the code is not well-optimized. I'll get to that someday, but
the speed has been adequate for me so far.