mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 15:31:09 +00:00
Merge pull request #242 from TomHarte/8272ReadyInterruption
Improves CPC disk emulation
This commit is contained in:
commit
98adb01721
@ -34,6 +34,7 @@ using namespace Intel::i8272;
|
||||
#define SetSeekEnd() (status_[0] |= 0x20)
|
||||
#define SetEquipmentCheck() (status_[0] |= 0x10)
|
||||
#define SetNotReady() (status_[0] |= 0x08)
|
||||
#define SetSide2() (status_[0] |= 0x04)
|
||||
|
||||
#define SetEndOfCylinder() (status_[1] |= 0x80)
|
||||
#define SetDataError() (status_[1] |= 0x20)
|
||||
@ -43,6 +44,7 @@ using namespace Intel::i8272;
|
||||
#define SetMissingAddressMark() (status_[1] |= 0x01)
|
||||
|
||||
#define SetControlMark() (status_[2] |= 0x40)
|
||||
#define ClearControlMark() (status_[2] &= ~0x40)
|
||||
#define ControlMark() (status_[2] & 0x40)
|
||||
|
||||
#define SetDataFieldDataError() (status_[2] |= 0x20)
|
||||
@ -158,6 +160,11 @@ void i8272::run_for(Cycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
// check for busy plus ready disabled
|
||||
if(is_executing_ && !get_drive().get_is_ready()) {
|
||||
posit_event((int)Event8272::NoLongerReady);
|
||||
}
|
||||
|
||||
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
|
||||
if(is_sleeping_) update_sleep_observer();
|
||||
}
|
||||
@ -230,6 +237,7 @@ uint8_t i8272::get_register(int address) {
|
||||
#define SET_DRIVE_HEAD_MFM() \
|
||||
active_drive_ = command_[1]&3; \
|
||||
active_head_ = (command_[1] >> 2)&1; \
|
||||
status_[0] = (command_[1]&7); \
|
||||
select_drive(active_drive_); \
|
||||
get_drive().set_head((unsigned int)active_head_); \
|
||||
set_is_double_density(command_[0] & 0x40);
|
||||
@ -263,6 +271,10 @@ uint8_t i8272::get_register(int address) {
|
||||
|
||||
void i8272::posit_event(int event_type) {
|
||||
if(event_type == (int)Event::IndexHole) index_hole_count_++;
|
||||
if(event_type == (int)Event8272::NoLongerReady) {
|
||||
SetNotReady();
|
||||
goto abort;
|
||||
}
|
||||
if(!(interesting_event_mask_ & event_type)) return;
|
||||
interesting_event_mask_ &= ~event_type;
|
||||
|
||||
@ -332,6 +344,7 @@ void i8272::posit_event(int event_type) {
|
||||
}
|
||||
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
|
||||
// cylinder, head, sector and size registers from the command stream.
|
||||
is_executing_ = true;
|
||||
if(!dma_mode_) SetNonDMAExecution();
|
||||
SET_DRIVE_HEAD_MFM();
|
||||
LOAD_HEAD();
|
||||
@ -425,6 +438,7 @@ void i8272::posit_event(int event_type) {
|
||||
// flag doesn't match the sort the command was looking for.
|
||||
read_data_found_header:
|
||||
FIND_DATA();
|
||||
ClearControlMark();
|
||||
if(event_type == (int)Event::Token) {
|
||||
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
|
||||
// Something other than a data mark came next — impliedly an ID or index mark.
|
||||
@ -525,7 +539,6 @@ void i8272::posit_event(int event_type) {
|
||||
WAIT_FOR_EVENT(Event::DataWritten);
|
||||
if(!has_input_) {
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
}
|
||||
write_byte(input_);
|
||||
@ -646,7 +659,6 @@ void i8272::posit_event(int event_type) {
|
||||
switch(event_type) {
|
||||
case (int)Event::IndexHole:
|
||||
SetOverrun();
|
||||
end_writing();
|
||||
goto abort;
|
||||
break;
|
||||
case (int)Event::DataWritten:
|
||||
@ -767,10 +779,9 @@ void i8272::posit_event(int event_type) {
|
||||
main_status_ &= ~(1 << found_drive);
|
||||
SetSeekEnd();
|
||||
|
||||
result_stack_.push_back(drives_[found_drive].head_position);
|
||||
result_stack_.push_back(status_[0]);
|
||||
result_stack_ = { drives_[found_drive].head_position, status_[0]};
|
||||
} else {
|
||||
result_stack_.push_back(0x80);
|
||||
result_stack_ = { 0x80 };
|
||||
}
|
||||
}
|
||||
goto post_result;
|
||||
@ -793,24 +804,27 @@ void i8272::posit_event(int event_type) {
|
||||
{
|
||||
int drive = command_[1] & 3;
|
||||
select_drive(drive);
|
||||
result_stack_.push_back(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
||||
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
||||
);
|
||||
result_stack_= {
|
||||
static_cast<uint8_t>(
|
||||
(command_[1] & 7) | // drive and head number
|
||||
0x08 | // single sided
|
||||
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(get_drive().get_is_ready() ? 0x20 : 0x00) |
|
||||
(get_drive().get_is_read_only() ? 0x40 : 0x00)
|
||||
)
|
||||
};
|
||||
}
|
||||
goto post_result;
|
||||
|
||||
// Performs any invalid command.
|
||||
invalid:
|
||||
// A no-op, but posts ST0 (but which ST0?)
|
||||
result_stack_.push_back(0x80);
|
||||
result_stack_ = {0x80};
|
||||
goto post_result;
|
||||
|
||||
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
|
||||
abort:
|
||||
end_writing();
|
||||
SetAbnormalTermination();
|
||||
goto post_st012chrn;
|
||||
|
||||
@ -818,14 +832,7 @@ void i8272::posit_event(int event_type) {
|
||||
post_st012chrn:
|
||||
SCHEDULE_HEAD_UNLOAD();
|
||||
|
||||
result_stack_.push_back(size_);
|
||||
result_stack_.push_back(sector_);
|
||||
result_stack_.push_back(head_);
|
||||
result_stack_.push_back(cylinder_);
|
||||
|
||||
result_stack_.push_back(status_[2]);
|
||||
result_stack_.push_back(status_[1]);
|
||||
result_stack_.push_back(status_[0]);
|
||||
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
|
||||
|
||||
goto post_result;
|
||||
|
||||
@ -839,6 +846,7 @@ void i8272::posit_event(int event_type) {
|
||||
printf("\n");
|
||||
|
||||
// Set ready to send data to the processor, no longer in non-DMA execution phase.
|
||||
is_executing_ = false;
|
||||
ResetNonDMAExecution();
|
||||
SetDataRequest();
|
||||
SetDataDirectionToProcessor();
|
||||
|
@ -65,6 +65,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
CommandByte = (1 << 3),
|
||||
Timer = (1 << 4),
|
||||
ResultEmpty = (1 << 5),
|
||||
NoLongerReady = (1 << 6)
|
||||
};
|
||||
void posit_event(int type);
|
||||
int interesting_event_mask_;
|
||||
@ -107,10 +108,11 @@ class i8272: public Storage::Disk::MFMController {
|
||||
bool seek_is_satisfied(int drive);
|
||||
|
||||
// User-supplied parameters; as per the specify command.
|
||||
int step_rate_time_;
|
||||
int head_unload_time_;
|
||||
int head_load_time_;
|
||||
bool dma_mode_;
|
||||
int step_rate_time_ = 1;
|
||||
int head_unload_time_ = 1;
|
||||
int head_load_time_ = 1;
|
||||
bool dma_mode_ = false;
|
||||
bool is_executing_ = false;
|
||||
|
||||
// A count of head unload timers currently running.
|
||||
int head_timers_running_;
|
||||
|
@ -587,7 +587,7 @@ class FDC: public Intel::i8272::i8272 {
|
||||
public:
|
||||
FDC() :
|
||||
i8272(bus_handler_, Cycles(8000000)),
|
||||
drive_(new Storage::Disk::Drive(8000000, 300)) {
|
||||
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
|
||||
set_drive(drive_);
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ MachineBase::MachineBase() :
|
||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||
drive_VIA_(drive_VIA_port_handler_),
|
||||
serial_port_VIA_(*serial_port_VIA_port_handler_),
|
||||
drive_(new Storage::Disk::Drive(1000000, 300)) {
|
||||
drive_(new Storage::Disk::Drive(1000000, 300, 2)) {
|
||||
// attach the serial port to its VIA and vice versa
|
||||
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
|
||||
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
|
||||
|
@ -16,7 +16,7 @@ Plus3::Plus3() : WD1770(P1770) {
|
||||
|
||||
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300));
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
|
@ -30,7 +30,7 @@ Microdisc::Microdisc() :
|
||||
|
||||
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
|
||||
if(!drives_[drive]) {
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300));
|
||||
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
|
||||
if(drive == selected_drive_) set_drive(drives_[drive]);
|
||||
}
|
||||
drives_[drive]->set_disk(disk);
|
||||
|
@ -22,7 +22,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
|
||||
std::shared_ptr<Storage::Disk::Drive> drive;
|
||||
|
||||
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
|
||||
drive.reset(new Storage::Disk::Drive(4000000, 300));
|
||||
drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
|
||||
set_drive(drive);
|
||||
drive->set_motor_on(true);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ using namespace Storage::Disk;
|
||||
Controller::Controller(Cycles clock_rate) :
|
||||
clock_rate_multiplier_(128000000 / clock_rate.as_int()),
|
||||
clock_rate_(clock_rate.as_int() * clock_rate_multiplier_),
|
||||
empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1)) {
|
||||
empty_drive_(new Drive((unsigned int)clock_rate.as_int(), 1, 1)) {
|
||||
// seed this class with a PLL, any PLL, so that it's safe to assume non-nullptr later
|
||||
Time one(1);
|
||||
set_expected_bit_length(one);
|
||||
@ -104,8 +104,10 @@ void Controller::begin_writing(bool clamp_to_index_hole) {
|
||||
}
|
||||
|
||||
void Controller::end_writing() {
|
||||
is_reading_ = true;
|
||||
get_drive().end_writing();
|
||||
if(!is_reading_) {
|
||||
is_reading_ = true;
|
||||
get_drive().end_writing();
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::is_reading() {
|
||||
|
@ -15,9 +15,10 @@
|
||||
|
||||
using namespace Storage::Disk;
|
||||
|
||||
Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute):
|
||||
Drive::Drive(unsigned int input_clock_rate, int revolutions_per_minute, unsigned int number_of_heads):
|
||||
Storage::TimedEventLoop(input_clock_rate),
|
||||
rotational_multiplier_(60, revolutions_per_minute) {
|
||||
rotational_multiplier_(60, revolutions_per_minute),
|
||||
available_heads_(number_of_heads) {
|
||||
}
|
||||
|
||||
void Drive::set_disk(const std::shared_ptr<Disk> &disk) {
|
||||
@ -51,6 +52,7 @@ void Drive::step(int direction) {
|
||||
}
|
||||
|
||||
void Drive::set_head(unsigned int head) {
|
||||
head = std::min(head, available_heads_ - 1);
|
||||
if(head != head_) {
|
||||
head_ = head;
|
||||
track_ = nullptr;
|
||||
@ -73,6 +75,7 @@ bool Drive::get_is_read_only() {
|
||||
}
|
||||
|
||||
bool Drive::get_is_ready() {
|
||||
return true;
|
||||
return ready_index_count_ == 2;
|
||||
}
|
||||
|
||||
@ -234,16 +237,18 @@ void Drive::write_bit(bool value) {
|
||||
}
|
||||
|
||||
void Drive::end_writing() {
|
||||
is_reading_ = true;
|
||||
if(!is_reading_) {
|
||||
is_reading_ = true;
|
||||
|
||||
if(!patched_track_) {
|
||||
// Avoid creating a new patched track if this one is already patched
|
||||
patched_track_ = std::dynamic_pointer_cast<PCMPatchedTrack>(track_);
|
||||
if(!patched_track_) {
|
||||
patched_track_.reset(new PCMPatchedTrack(track_));
|
||||
// Avoid creating a new patched track if this one is already patched
|
||||
patched_track_ = std::dynamic_pointer_cast<PCMPatchedTrack>(track_);
|
||||
if(!patched_track_) {
|
||||
patched_track_.reset(new PCMPatchedTrack(track_));
|
||||
}
|
||||
}
|
||||
patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
|
||||
cycles_since_index_hole_ %= get_input_clock_rate();
|
||||
invalidate_track();
|
||||
}
|
||||
patched_track_->add_segment(write_start_time_, write_segment_, clamp_writing_to_index_hole_);
|
||||
cycles_since_index_hole_ %= get_input_clock_rate();
|
||||
invalidate_track();
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ namespace Disk {
|
||||
|
||||
class Drive: public Sleeper, public TimedEventLoop {
|
||||
public:
|
||||
Drive(unsigned int input_clock_rate, int revolutions_per_minute);
|
||||
Drive(unsigned int input_clock_rate, int revolutions_per_minute, unsigned int number_of_heads);
|
||||
|
||||
/*!
|
||||
Replaces whatever is in the drive with @c disk.
|
||||
@ -140,6 +140,7 @@ class Drive: public Sleeper, public TimedEventLoop {
|
||||
// A record of head position and active head.
|
||||
int head_position_ = 0;
|
||||
unsigned int head_ = 0;
|
||||
unsigned int available_heads_ = 0;
|
||||
|
||||
// Motor control state.
|
||||
bool motor_is_on_ = false;
|
||||
|
@ -244,7 +244,7 @@ Parser::Parser(bool is_mfm) :
|
||||
bit_length.clock_rate = is_mfm ? 500000 : 250000; // i.e. 250 kbps (including clocks)
|
||||
set_expected_bit_length(bit_length);
|
||||
|
||||
drive_.reset(new Storage::Disk::Drive(4000000, 300));
|
||||
drive_.reset(new Storage::Disk::Drive(4000000, 300, 2));
|
||||
set_drive(drive_);
|
||||
drive_->set_motor_on(true);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user