VOGONS


IBM PC AT emulation crashing?

Topic actions

Reply 120 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm currently trying to fix the floppy disk controller used by the AT BIOS. Currently, it's emulating a 82072A floppy disk controller.

I see it emitting various commands before giving the error:
1. Sense interrupt, resulting bytes C0h 00h.
2. Sense interrupt, resulting bytes D1h, 00h.
3. Recalibrate, ST0 becoming 0x70(No floppy disk inserted). Disk changed bit is cleared, ST1 becomes 0x00.

No floppy disk is inserted into the drive.

Anyone can see what might be going wrong here?
My documentation I'm using: http://www.isdaman.com/alsos/hardware/fdc/floppy.htm (also http://bochs.sourceforge.net/cgi-bin/lxr/sour … iodev/floppy.cc )
My floppy disk emulation: https://bitbucket.org/superfury/unipcemu/src/ … ppy.c?at=master

Why is the BIOS throwing an error after the recalibrate command (which fails because of there being no floppy disk inserted)? It's a 601-Floppy disk error.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 122 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

I don't mean no drive is connected. Just that no disk is inserted in the drive(existing drive without disk inserted).
This is the current result(based on Bochs code):

case RECALIBRATE: //Calibrate drive
//Execute interrupt!
FLOPPY.commandstep = 0; //Reset controller command status!
FLOPPY.currentcylinder[FLOPPY.DOR.DriveNumber] = 0; //Goto cylinder #0!
FLOPPY.ST0.data = 0x20|FLOPPY.DOR.DriveNumber; //Completed command!
if (!has_drive(FLOPPY.DOR.DriveNumber ? FLOPPY1 : FLOPPY0)) //Drive present?
{
FLOPPY.ST0.data |= 0x50; //Completed command! 0x40: Abnormal termination, 0x10: Unit Check, cannot find track 0 after 79 pulses.
}
updateFloppyWriteProtected(0); //Try to read with(out) protection!
clearDiskChanged(); //Clear the disk changed flag for the new command!
FLOPPY_raiseIRQ(); //We're finished!
break;

Is that correct?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 124 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

Not exactly. has_drive returns 1 when a storage medium(disk image file) is present and mounted in the specified slot. Otherwise 0.

In this case it returns 0, since I don't have a floppy disk image mounted in the emulator Settings. Compare it to Dosbox state of imgmount used successfully vs after "mount -u" image(or never mounted) states.

Maybe a more correct name would be something like "is_mounted". The drives (FLOPPY0, FLOPPY1, HDD0, HDD1, CDROM0, CDROM1) themselves are always valid entries. They just can have two states(mounted with backend or not mounted(and thus no backend)). has_drive simply returns the mounted/not mounted state(1=mounted to backend, 0=not mounted to any backend). Compare it to having a floppy drive with or without a disk inserted.

Btw, results are instantaneous(not timed according to accurate timing) atm. The only thing that's currently timed is the DMA transfer itself(which is timed by the DMA controller at 10 14.31818MHz cycles per byte).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 125 of 151, by crazyc

User metadata
Rank Member
Rank
Member

Oh, that's your code, I thought that was from bochs. If you look at bochs it doesn't return track 0 not found if the drive is missing or the motor is off (which was a mistake in my comment above as drives won't seek if not spinning).

Reply 126 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

So if there's no floppy disk inserted into the drive, it will give a success code(my if clause not used case), the only difference between an existing and non-existing floppy disk being inserted or not being the TRK0 bit? So it will be 1 when a disk is present, but 0 when no disk is inserted?

So the code would need to become(based on below documentation):

 case RECALIBRATE: //Calibrate drive
//Execute interrupt!
FLOPPY.commandstep = 0; //Reset controller command status!
FLOPPY.currentcylinder[FLOPPY.DOR.DriveNumber] = 0; //Goto cylinder #0!
FLOPPY.ST0.data = 0x20|FLOPPY.DOR.DriveNumber; //Completed command!
if (!has_drive(FLOPPY.DOR.DriveNumber ? FLOPPY1 : FLOPPY0)) //Drive present?
{
FLOPPY.ST0.data |= 0x10; //Completed command! 0x10: Unit Check, cannot find track 0 after 79 pulses.
}
updateFloppyWriteProtected(0); //Try to read with(out) protection!
clearDiskChanged(); //Clear the disk changed flag for the new command!
FLOPPY_raiseIRQ(); //We're finished!
break;

From my documentation:

When the controller sees this command, it sets the DIR signal to 0, and passes the drive up to 79 step pulses. After each of these pulses, the controller checks the TRK0 signal. If it is active (htat is, the head is on track 0), the controller sets the SE bit in Status Register 0, and aborts the command. If TRK0 is not active after 79 step pulses, the controller sets bits SE and EC in Status Register 0, and terminates the command.

Thus it needs to set SE to 1, and set EC to 1(floppy inserted) or 0(floppy not inserted)?

Edit: This is my current version:

		case RECALIBRATE: //Calibrate drive
//Execute interrupt!
FLOPPY.commandstep = 0; //Reset controller command status!
FLOPPY.currentcylinder[FLOPPY.commandbuffer[1]] = 0; //Goto cylinder #0!
FLOPPY.ST0.data = 0x20|FLOPPY.commandbuffer[1]; //Completed command!
if (!is_mounted(FLOPPY.commandbuffer[1] ? FLOPPY1 : FLOPPY0)) //Media inserted?
{
FLOPPY.ST0.data |= 0x10; //Completed command! 0x10: Unit Check, cannot find track 0 after 79 pulses.
}
updateFloppyWriteProtected(0,FLOPPY.commandbuffer[1]); //Try to read with(out) protection!
clearDiskChanged(); //Clear the disk changed flag for the new command!
FLOPPY_raiseIRQ(); //We're finished!
break;

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 127 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

Even after those latest improvements, it still errors out immediately after the RECALIBRATE command for some reason. It doesn't even get to interpreting the ST0 value. It simply reports the 601 error immediately after issuing the recalibrate command? The command resets the controller back to state 0, which is the state used for issuing new commands:

//Execution after command and data phrases!
byte oldMSR = 0; //Old MSR!

OPTINLINE void updateFloppyMSR() //Update the floppy MSR!
{
switch (FLOPPY.commandstep) //What command step?
{
case 0: //Command?
FLOPPY.sectorstransferred = 0; //There's nothing transferred yet!
FLOPPY.MSR.CommandBusy = 0; //Not busy: we're waiting for a command!
FLOPPY.MSR.RQM = !FLOPPY.floppy_resetted; //Ready for data transfer when not being reset!
FLOPPY.MSR.HaveDataForCPU = 0; //We don't have data for the CPU!
break;
case 1: //Parameters?
FLOPPY.MSR.CommandBusy = 1; //Default: busy!
FLOPPY.MSR.RQM = 1; //Ready for data transfer!
FLOPPY.MSR.HaveDataForCPU = 0; //We don't have data for the CPU!
break;
case 2: //Data?
FLOPPY.MSR.CommandBusy = 1; //Default: busy!
//Check DMA, RQM and Busy flag!
switch (FLOPPY.commandbuffer[0]) //What command are we processing?
{
case WRITE_DATA: //Write sector?
case WRITE_DELETED_DATA: //Write deleted sector?
case FORMAT_TRACK: //Format sector?
case READ_DATA: //Read sector?
case READ_DELETED_DATA: //Read deleted sector?
FLOPPY.MSR.RQM = FLOPPY.MSR.NonDMA = !FLOPPY_useDMA(); //Use no DMA? Then transfer data and set NonDMA! Else, clear non DMA and don't transfer!
break;
default: //Unknown command?
FLOPPY.MSR.RQM = FLOPPY.MSR.NonDMA = 1; //Use no DMA by default, for safety!
break; //Don't process!
}

//Check data direction!
switch (FLOPPY.commandbuffer[0]) //Process input/output to/from controller!
{
case WRITE_DATA: //Write sector?
case WRITE_DELETED_DATA: //Write deleted sector?
case FORMAT_TRACK: //Format sector?
FLOPPY.MSR.HaveDataForCPU = 0; //We request data from the CPU!
break;
case READ_DATA: //Read sector?
case READ_DELETED_DATA: //Read deleted sector?
FLOPPY.MSR.HaveDataForCPU = 1; //We have data for the CPU!
break;
default: //Unknown direction?
FLOPPY.MSR.HaveDataForCPU = 0; //Nothing, say output by default!
break;
}
break;
case 3: //Result?
FLOPPY.MSR.CommandBusy = 1; //Default: busy!
FLOPPY.MSR.RQM = 1; //Data transfer!
FLOPPY.MSR.HaveDataForCPU = 1; //We have data for the CPU!
break;
case 0xFF: //Error?
FLOPPY.MSR.CommandBusy = 1; //Default: busy!
FLOPPY.MSR.RQM = 1; //Data transfer!
Show last 12 lines
		FLOPPY.MSR.HaveDataForCPU = 1; //We have data for the CPU!
break;
default: //Unknown status?
break; //Unknown?
}
if (FLOPPY.MSR.data != oldMSR) //MSR changed?
{
oldMSR = FLOPPY.MSR.data; //Update old!
FLOPPY_LOGD("FLOPPY: MSR changed: %02x", FLOPPY.MSR.data) //The updated MSR!
}
}

Can you see why it seems to error out immediately?
https://bitbucket.org/superfury/unipcemu/src/ … ppy.c?at=master

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 128 of 151, by crazyc

User metadata
Rank Member
Rank
Member
crazyc wrote:

Oh, that's your code, I thought that was from bochs. If you look at bochs it only returns track 0 not found if the drive is missing or the motor is off (which was a mistake in my comment above as drives won't seek if not spinning).

Made a error which reversed what I meant.

So if there's no floppy disk inserted into the drive, it will give a success code(my if clause not used case), the only difference between an existing and non-existing floppy disk being inserted or not being the TRK0 bit? So it will be 1 when a disk is present, but 0 when no disk is inserted?

There is no difference between an disk inserted or not with a seek/recal. The controller has no way of telling if a disk is inserted or not.

Reply 129 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

Then what DO seek/recal commands return? Are they always reporting success on both seek and recalibrate even without enough tracks(seek) or no floppy disk inserted?

This is what my seek command currently does when executed:

		case SEEK: //Seek/park head
FLOPPY.commandstep = 0; //Reset controller command status!
updateFloppyWriteProtected(0,FLOPPY.DOR.DriveNumber); //Try to read with(out) protection!
if ((FLOPPY.DOR.DriveNumber >= 2) || (FLOPPY.DOR.DriveNumber!=(FLOPPY.commandbuffer[1]&3))) //Invalid drive specified?
{
FLOPPY.ST0.data = (FLOPPY.ST0.data & 0x32) | 0x14 | FLOPPY.DOR.DriveNumber; //Error: drive not ready!
FLOPPY.commandstep = 0; //Reset command!
clearDiskChanged(); //Clear the disk changed flag for the new command!
return; //Abort!
}
if (!is_mounted(FLOPPY.DOR.DriveNumber ? FLOPPY1 : FLOPPY0)) //Floppy not inserted?
{
FLOPPY.ST0.data = (FLOPPY.ST0.data & 0x30) | 0x18 | FLOPPY.DOR.DriveNumber; //Error: drive not ready!
FLOPPY.commandstep = 0; //Reset command!
clearDiskChanged(); //Clear the disk changed flag for the new command!
return; //Abort!
}
if (FLOPPY.commandbuffer[2] < floppy_tracks(disksize(FLOPPY.DOR.DriveNumber ? FLOPPY1 : FLOPPY0))) //Valid track?
{
FLOPPY.currentcylinder[FLOPPY.DOR.DriveNumber] = FLOPPY.commandbuffer[2]; //Set the current cylinder!
FLOPPY.ST0.data = (FLOPPY.ST0.data & 0x30) | 0x20 | FLOPPY.DOR.DriveNumber; //Valid command!
FLOPPY_raiseIRQ(); //Finished executing phase!
clearDiskChanged(); //Clear the disk changed flag for the new command!
return; //Give an error!
}

//Invalid track?
FLOPPY.ST2.data |= 0x4; //Invalid seek!
FLOPPY.commandstep = (byte)(FLOPPY.commandposition = 0); //Reset command!
break;

Edit: It seems that after sending the recalibrate(1 command byte, 1 parameter) it simply doesn't touch the floppy disk controller anymore(ports 1FXh)? Nothing is sent or received to the FDC between sending that one parameter byte and throwing the 601 error?

Last edited by superfury on 2016-10-27, 18:57. Edited 1 time in total.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 131 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've changed the setting of ST2 to 0x00 instead during seek to an invalid track.

My latest source code:
https://bitbucket.org/superfury/unipcemu/src/ … ppy.c?at=master

- Recalibrate always returns success, no matter what the floppy disk number of tracks and no matter if a disk or no disk is inserted.
- Track0 now updates the whole ST3 register in all used cases.
- Not Ready is never set, not even during error conditions(in ST0).
- Seeking to an invalid track updates ST0 the same way as a successfull seek, but doesn't set bit 5(Seek Ended). Bits 4&5 are kept, all other bits are cleared and bits 0-1 are set to the drive number.

Edit: After the last recalibrate instruction, the MSR contains 0x90. When the recalibrate command is sent (which triggers an IRQ6), the MSR (port 3F4) isn't ready anymore until the 601 error is displayed?

Only the Sense Interrupt command lowers the IRQ(enables further IRQs to be received). Interrupts after another interrupt without Sense Interrupt having been received don't trigger new IRQs(IRQ goes from high to high instead of low to high, thus triggering no new interrupt handler). Is that correct?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 132 of 151, by crazyc

User metadata
Rank Member
Rank
Member
superfury wrote:
I've changed the setting of ST2 to 0x00 instead during seek to an invalid track. […]
Show full quote

I've changed the setting of ST2 to 0x00 instead during seek to an invalid track.

My latest source code:
https://bitbucket.org/superfury/unipcemu/src/ … ppy.c?at=master

- Recalibrate always returns success, no matter what the floppy disk number of tracks and no matter if a disk or no disk is inserted.

The 765 will actually only seek 77 tracks and report error if track 0 isn't reached so 80 tracks drives may not hit track 0 with one recalibrate but this likely isn't something you have to worry about.

superfury wrote:

- Track0 now updates the whole ST3 register in all used cases.
- Not Ready is never set, not even during error conditions(in ST0).

Correct.

superfury wrote:

- Seeking to an invalid track updates ST0 the same way as a successfull seek, but doesn't set bit 5(Seek Ended). Bits 4&5 are kept, all other bits are cleared and bits 0-1 are set to the drive number.

I think Seek ended is always set after a seek or recal command but I'm not certain.

superfury wrote:

Edit: After the last recalibrate instruction, the MSR contains 0x90. When the recalibrate command is sent (which triggers an IRQ6), the MSR (port 3F4) isn't ready anymore until the 601 error is displayed?

Only the Sense Interrupt command lowers the IRQ(enables further IRQs to be received). Interrupts after another interrupt without Sense Interrupt having been received don't trigger new IRQs(IRQ goes from high to high instead of low to high, thus triggering no new interrupt handler). Is that correct?

The Generic XT BIOS requires a seek command to also clear a seek irq (in reality any command has to clear a seek irq, the only machine I know of that requires this though is the x68000).

Reply 133 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

So essentially all interrupts raising by the FDC operate by lowering and raising the IRQ line each time an execution phase ends? So not only raised(or kept raised) when an execution phase ends and only lower when an Sense Interrupt is received?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 135 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've modified the FDC to save the current IR line status for Sense Interrupt and lower the IR line after saving it, before handling the written data itself. Sense Interrupt now uses that stored last IR status(before lowering) to determine invalid calls(which lock up the controller until reset).

Raising still works the same way(triggering an interrupt through raising the IR line when transitioning from low to high(PIC), but not from high to high(normally not the case, since new commands, and thus interrupts, send a command byte through the data port).

Unfortunately my documentation on the NEC µPD765 doesn't explain that behaviour:(
http://www.isdaman.com/alsos/hardware/fdc/floppy.htm

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 137 of 151, by superfury

User metadata
Rank l33t++
Rank
l33t++

It now issues one(or two) recalibrates, then executes a seek. which fails on the mounted check (the lines within the is_mounted if-clause gets executed), then throws a 601 error again.

Edit: Changing it a bit, based on Bochs' floppy.cc:
- Sense interrupt (reset 1/2)
- Sense interrupt (reset 2/2)
- Recalibrate(A)
- Sense interrupt
- Recalibrate(B)
- Sense interrupt
- Seek (A)
- Sense interrupt
- Seek (B)
- Sense interrupt
- Sense drive status
- 601 diskette error reported.

Edit: After closer inspection of the ST1, ST2 and ST3 registers, it seems that their bit values in thier respective structs were backwards. So they started with the highest bits(bit 7) and ended with the lowest bits(bit 0). They need to be reversed for their proper results:S

It seems it's not happy with the drive status after recalibrating and seeking on both floppy drives?

It returns 0x39 in ST3 with both floppy drives.

Latest source code: https://bitbucket.org/superfury/unipcemu/src/ … ppy.c?at=master

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 138 of 151, by crazyc

User metadata
Rank Member
Rank
Member
superfury wrote:

Unfortunately my documentation on the NEC µPD765 doesn't explain that behaviour:(
http://www.isdaman.com/alsos/hardware/fdc/floppy.htm

That's where you got the seek failed thing from, huh. You need to look at the datasheet for controller registers instead, http://www.classiccmp.org/dunfield/r/765.pdf.

Reply 139 of 151, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Edit: After closer inspection of the ST1, ST2 and ST3 registers, it seems that their bit values in thier respective structs were backwards. So they started with the highest bits(bit 7) and ended with the lowest bits(bit 0). They need to be reversed for their proper results:S

Note that bit field orders are not defined by C standard so it is implementation specific - it may be different on a different compiler and/or platform and it won't work if it is different. IIRC, microsoft compiler and gcc compiler have different bit field orders by default.

If code depend on first bit field entry within a byte being 0x80 or 0x01 by value, I don't know what would be the best way to handle it in a cross platform way. I'm sure other projects have hints how to handle it, like one solution is not to use bitfields 😀