// license:BSD-3-Clause // copyright-holders:Nathan Woods, Miodrag Milanovic /* This code handles the floppy drives. All FDD actions should be performed using these functions. The functions are emulated and a disk image is used. Real disk operation: - set unit id TODO: - Override write protect if disk image has been opened in read mode */ #include "emu.h" #include "flopdrv.h" #include "softlist_dev.h" #include "formats/imageutl.h" #include "util/ioprocs.h" #include "util/ioprocsfilter.h" //#define VERBOSE 1 #include "logmacro.h" /*************************************************************************** CONSTANTS ***************************************************************************/ #define LOG_FLOPPY 0 /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ struct floppy_error_map { floperr_t ferr; std::error_condition ierr; }; /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ static const floppy_error_map errmap[] = { { FLOPPY_ERROR_SUCCESS, { } }, { FLOPPY_ERROR_INTERNAL, { image_error::INTERNAL } }, { FLOPPY_ERROR_UNSUPPORTED, { image_error::UNSUPPORTED } }, { FLOPPY_ERROR_OUTOFMEMORY, { std::errc::not_enough_memory } }, { FLOPPY_ERROR_INVALIDIMAGE, { image_error::INVALIDIMAGE } } }; /*************************************************************************** IMPLEMENTATION ***************************************************************************/ floppy_image_legacy *legacy_floppy_image_device::flopimg_get_image() { return m_floppy; } int legacy_floppy_image_device::flopimg_get_sectors_per_track(int side) { floperr_t err; int sector_count; if (!m_floppy) return 0; err = floppy_get_sector_count(m_floppy, side, m_track, §or_count); if (err) return 0; return sector_count; } void legacy_floppy_image_device::flopimg_get_id_callback(chrn_id *id, int id_index, int side) { int cylinder, sector, N; unsigned long flags; uint32_t sector_length; if (!m_floppy) return; floppy_get_indexed_sector_info(m_floppy, side, m_track, id_index, &cylinder, &side, §or, §or_length, &flags); N = compute_log2(sector_length); id->C = cylinder; id->H = side; id->R = sector; id->data_id = id_index; id->flags = flags; id->N = ((N >= 7) && (N <= 10)) ? N - 7 : 0; } void legacy_floppy_image_device::log_readwrite(const char *name, int head, int track, int sector, const char *buf, int length) { char membuf[1024]; int i; for (i = 0; i < length; i++) snprintf(membuf + i*2, 1024 - (i*2), "%02x", (int) (uint8_t) buf[i]); logerror("%s: head=%i track=%i sector=%i buffer='%s'\n", name, head, track, sector, membuf); } void legacy_floppy_image_device::floppy_drive_set_geometry_absolute(int tracks, int sides) { m_max_track = tracks; m_num_sides = sides; } void legacy_floppy_image_device::floppy_drive_set_geometry(floppy_type_t type) { floppy_drive_set_geometry_absolute(type.max_track_number, type.head_number); } /* this is called on device init */ void legacy_floppy_image_device::floppy_drive_init() { /* initialise flags */ m_flags = 0; m_index_pulse_callback = nullptr; m_index_timer = timer_alloc(FUNC(legacy_floppy_image_device::floppy_drive_index_callback), this); m_idx = 0; floppy_drive_set_geometry(m_config->floppy_type); /* initialise id index - not so important */ m_id_index = 0; /* initialise track */ m_current_track = 0; /* default RPM */ m_rpm = 300; m_controller = nullptr; m_floppy_drive_type = FLOPPY_TYPE_REGULAR; } /* index pulses at rpm/60 Hz, and stays high 1/20th of time */ void legacy_floppy_image_device::floppy_drive_index_func() { double ms = 1000.0 / ((double) m_rpm / 60.0); if (m_idx) { m_idx = 0; m_index_timer->adjust(attotime::from_double(ms*19/20/1000.0)); } else { m_idx = 1; m_index_timer->adjust(attotime::from_double(ms/20/1000.0)); } m_out_idx_func(m_idx); if (m_index_pulse_callback) m_index_pulse_callback(m_controller, this, m_idx); } TIMER_CALLBACK_MEMBER(legacy_floppy_image_device::floppy_drive_index_callback) { floppy_drive_index_func(); } /*************************************************************************/ /* IO_FLOPPY device functions */ /* set flag state */ void legacy_floppy_image_device::floppy_drive_set_flag_state(int flag, int state) { int prev_state; int new_state; /* get old state */ prev_state = m_flags & flag; /* set new state */ m_flags &= ~flag; if (state) m_flags |= flag; /* get new state */ new_state = m_flags & flag; /* changed state? */ if (prev_state ^ new_state) { if (flag & FLOPPY_DRIVE_READY) { /* trigger state change callback */ //m_out_rdy_func(new_state ? ASSERT_LINE : CLEAR_LINE); } } } /* for pc, drive is always ready, for amstrad,pcw,spectrum it is only ready under a fixed set of circumstances */ /* use this to set ready state of drive */ void legacy_floppy_image_device::floppy_drive_set_ready_state(int state, int flag) { if (flag) { /* set ready only if drive is present, disk is in the drive, and disk motor is on - for Amstrad, Spectrum and PCW*/ /* drive present? */ /* disk inserted? */ if (exists()) { if (m_mon == CLEAR_LINE) { /* set state */ floppy_drive_set_flag_state(FLOPPY_DRIVE_READY, state); return; } } floppy_drive_set_flag_state(FLOPPY_DRIVE_READY, 0); } else { /* force ready state - for PC driver */ floppy_drive_set_flag_state(FLOPPY_DRIVE_READY, state); } } /* get flag state */ int legacy_floppy_image_device::floppy_drive_get_flag_state(int flag) { int drive_flags; int flags; flags = 0; drive_flags = m_flags; /* these flags are independent of a real drive/disk image */ flags |= drive_flags & (FLOPPY_DRIVE_READY | FLOPPY_DRIVE_INDEX); flags &= flag; return flags; } void legacy_floppy_image_device::floppy_drive_seek(signed int signed_tracks) { LOG("seek from: %d delta: %d\n", m_current_track, signed_tracks); /* update position */ m_current_track+=signed_tracks; if (m_current_track<0) { m_current_track = 0; } else if (m_current_track>=m_max_track) { m_current_track = m_max_track-1; } /* set track 0 flag */ m_tk00 = (m_current_track == 0) ? 0 : 1; //m_out_tk00_func(m_tk00); /* clear disk changed flag */ m_dskchg = 1; //m_out_dskchg_func(m_dskchg); /* inform disk image of step operation so it can cache information */ if (exists()) m_track = m_current_track; m_id_index = 0; } /* this is not accurate. But it will do for now */ int legacy_floppy_image_device::floppy_drive_get_next_id(int side, chrn_id *id) { int spt; /* get sectors per track */ spt = flopimg_get_sectors_per_track(side); /* set index */ if ((m_id_index==(spt-1)) || (spt==0)) { floppy_drive_set_flag_state(FLOPPY_DRIVE_INDEX, 1); } else { floppy_drive_set_flag_state(FLOPPY_DRIVE_INDEX, 0); } /* get id */ if (spt!=0) { flopimg_get_id_callback(id, m_id_index, side); } m_id_index++; if (spt!=0) m_id_index %= spt; else m_id_index = 0; return (spt == 0) ? 0 : 1; } void legacy_floppy_image_device::floppy_drive_read_track_data_info_buffer(int side, void *ptr, int *length ) { if (exists()) { if (!m_floppy) return; floppy_read_track_data(m_floppy, side, m_track, ptr, *length); } } void legacy_floppy_image_device::floppy_drive_write_track_data_info_buffer(int side, const void *ptr, int *length ) { if (exists()) { if (!m_floppy) return; floppy_write_track_data(m_floppy, side, m_track, ptr, *length); } } void legacy_floppy_image_device::floppy_drive_format_sector(int side, int sector_index,int c,int h, int r, int n, int filler) { if (exists()) { /* if (m_interface_.format_sector) m_interface_.format_sector(img, side, sector_index,c, h, r, n, filler);*/ } } void legacy_floppy_image_device::floppy_drive_read_sector_data(int side, int index1, void *ptr, int length) { if (exists()) { if (!m_floppy) return; floppy_read_indexed_sector(m_floppy, side, m_track, index1, 0, ptr, length); if (LOG_FLOPPY) log_readwrite("sector_read", side, m_track, index1, (const char *)ptr, length); } } void legacy_floppy_image_device::floppy_drive_write_sector_data(int side, int index1, const void *ptr,int length, int ddam) { if (exists()) { if (!m_floppy) return; if (LOG_FLOPPY) log_readwrite("sector_write", side, m_track, index1, (const char *)ptr, length); floppy_write_indexed_sector(m_floppy, side, m_track, index1, 0, ptr, length, ddam); } } void legacy_floppy_image_device::floppy_install_load_proc(void (*proc)(device_image_interface &image, bool is_created)) { m_load_proc = proc; } void legacy_floppy_image_device::floppy_install_unload_proc(void (*proc)(device_image_interface &image)) { m_unload_proc = proc; } /* set the callback for the index pulse */ void legacy_floppy_image_device::floppy_drive_set_index_pulse_callback(void (*callback)(device_t *controller, device_t *img, int state)) { m_index_pulse_callback = callback; } int legacy_floppy_image_device::floppy_drive_get_current_track() { return m_current_track; } uint64_t legacy_floppy_image_device::floppy_drive_get_current_track_size(int head) { int size = 0; if (exists()) { size = floppy_get_track_size(m_floppy, head, m_current_track); } return size; } void legacy_floppy_image_device::floppy_drive_set_rpm(float rpm) { m_rpm = rpm; } void legacy_floppy_image_device::floppy_drive_set_controller(device_t *controller) { m_controller = controller; } std::error_condition legacy_floppy_image_device::internal_floppy_device_load(bool is_create, int create_format, util::option_resolution *create_args) { const struct FloppyFormat *floppy_options = m_config->formats; floperr_t err; check_for_file(); auto io = util::random_read_write_fill(image_core_file(), 0xff); if (!io) { err = FLOPPY_ERROR_OUTOFMEMORY; } else if (is_create) { /* creating an image */ assert(create_format >= 0); err = floppy_create(std::move(io), &floppy_options[create_format], create_args, &m_floppy); } else { /* opening an image */ int const floppy_flags = !is_readonly() ? FLOPPY_FLAGS_READWRITE : FLOPPY_FLAGS_READONLY; err = floppy_open_choices(std::move(io), filetype(), floppy_options, floppy_flags, &m_floppy); } if (!err) { if (floppy_callbacks(m_floppy)->get_heads_per_disk && floppy_callbacks(m_floppy)->get_tracks_per_disk) { floppy_drive_set_geometry_absolute(floppy_get_tracks_per_disk(m_floppy),floppy_get_heads_per_disk(m_floppy)); } /* disk changed */ m_dskchg = CLEAR_LINE; // If we have one of our hacky load procs, call it if (m_load_proc) m_load_proc(*this, is_create); return std::error_condition(); } else { for (int i = 0; i < std::size(errmap); i++) { if (err == errmap[i].ferr) return errmap[i].ierr; } return image_error::UNSPECIFIED; } } TIMER_CALLBACK_MEMBER( legacy_floppy_image_device::set_wpt ) { m_wpt = param; //m_out_wpt_func(param); } int legacy_floppy_image_device::floppy_get_drive_type() { return m_floppy_drive_type; } void legacy_floppy_image_device::floppy_set_type(int ftype) { m_floppy_drive_type = ftype; } /* drive select */ void legacy_floppy_image_device::floppy_ds_w(int state) { m_active = (state == 0); } /* motor on, active low */ void legacy_floppy_image_device::floppy_mon_w(int state) { /* force off if there is no attached image */ if (!exists()) state = 1; /* off -> on */ if (m_mon && state == 0) { m_idx = 0; floppy_drive_index_func(); } /* on -> off */ else if (m_mon == CLEAR_LINE && state) m_index_timer->adjust(attotime::zero); m_mon = state; } /* direction */ void legacy_floppy_image_device::floppy_drtn_w(int state) { m_drtn = state; } /* write data */ void legacy_floppy_image_device::floppy_wtd_w(int state) { } /* step */ void legacy_floppy_image_device::floppy_stp_w(int state) { /* move head one track when going from high to low and write gate is high */ if (m_active && m_stp && state == CLEAR_LINE && m_wtg) { /* move head according to the direction line */ if (m_drtn) { /* move head outward */ if (m_current_track > 0) m_current_track--; /* are we at track 0 now? */ m_tk00 = (m_current_track == 0) ? 0 : 1; } else { /* move head inward */ if (m_current_track < m_max_track) m_current_track++; /* we can't be at track 0 here, so reset the line */ m_tk00 = 1; } /* update track 0 line with new status */ //m_out_tk00_func(m_tk00); /* inform disk image of step operation so it can cache information */ if (exists()) m_track = m_current_track; } m_stp = state; } /* write gate */ void legacy_floppy_image_device::floppy_wtg_w(int state) { m_wtg = state; } /* write protect signal, active low */ int legacy_floppy_image_device::floppy_wpt_r() { return m_wpt; } /* track 0 detect */ int legacy_floppy_image_device::floppy_tk00_r() { return m_tk00; } /* disk changed */ int legacy_floppy_image_device::floppy_dskchg_r() { return m_dskchg; } /* 2-sided disk */ int legacy_floppy_image_device::floppy_twosid_r() { if (m_floppy == nullptr) return 1; else return !floppy_get_heads_per_disk(m_floppy); } int legacy_floppy_image_device::floppy_index_r() { return m_idx; } int legacy_floppy_image_device::floppy_ready_r() { return !(floppy_drive_get_flag_state(FLOPPY_DRIVE_READY) == FLOPPY_DRIVE_READY); } // device type definition DEFINE_DEVICE_TYPE(LEGACY_FLOPPY, legacy_floppy_image_device, "legacy_floppy_image", "Floppy Disk") //------------------------------------------------- // legacy_floppy_image_device - constructor //------------------------------------------------- legacy_floppy_image_device::legacy_floppy_image_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : legacy_floppy_image_device(mconfig, LEGACY_FLOPPY, tag, owner, clock) { } legacy_floppy_image_device::legacy_floppy_image_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock), device_image_interface(mconfig, *this), m_out_idx_func(*this), m_drtn(0), m_stp(0), m_wtg(0), m_mon(0), m_idx(0), m_tk00(0), m_wpt(0), m_rdy(0), m_dskchg(0), m_active(0), m_config(nullptr), m_flags(0), m_max_track(0), m_num_sides(0), m_current_track(0), m_index_timer(nullptr), m_index_pulse_callback(nullptr), m_rpm(0.0f), m_id_index(0), m_controller(nullptr), m_floppy(nullptr), m_track(0), m_load_proc(nullptr), m_unload_proc(nullptr), m_floppy_drive_type(0) { memset(&m_extension_list,0,sizeof(m_extension_list)); } //------------------------------------------------- // legacy_floppy_image_device - destructor //------------------------------------------------- legacy_floppy_image_device::~legacy_floppy_image_device() { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void legacy_floppy_image_device::device_start() { floppy_drive_init(); m_active = false; /* resolve callbacks */ //m_in_mon_func.resolve(m_config->in_mon_func, *this); //m_out_tk00_func.resolve(m_config->out_tk00_func, *this); //m_out_wpt_func.resolve(m_config->out_wpt_func, *this); //m_out_rdy_func.resolve(m_config->out_rdy_func, *this); // m_out_dskchg_func.resolve(m_config->out_dskchg_func, *this); /* by default we are not write-protected */ m_wpt = 1; //m_out_wpt_func(m_wpt); /* not at track 0 */ m_tk00 = 1; //m_out_tk00_func(m_tk00); /* motor off */ m_mon = 1; /* disk changed */ m_dskchg = CLEAR_LINE; // m_out_dskchg_func(m_dskchg); /* write-protect callback */ m_wpt_timer = timer_alloc(FUNC(legacy_floppy_image_device::set_wpt), this); } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void legacy_floppy_image_device::device_config_complete() { m_extension_list[0] = '\0'; if (m_config) { const struct FloppyFormat *floppy_options = m_config->formats; for (int i = 0; floppy_options && floppy_options[i].construct; i++) { // only add if creatable if (floppy_options[i].param_guidelines) { // allocate a new format and append it to the list add_format(floppy_options[i].name, floppy_options[i].description, floppy_options[i].extensions, floppy_options[i].param_guidelines); } image_specify_extension(m_extension_list, 256, floppy_options[i].extensions); } } } const software_list_loader &legacy_floppy_image_device::get_software_list_loader() const { return image_software_list_loader::instance(); } std::pair legacy_floppy_image_device::call_create(int format_type, util::option_resolution *format_options) { return std::make_pair(internal_floppy_device_load(true, format_type, format_options), std::string()); } std::pair legacy_floppy_image_device::call_load() { std::error_condition retVal = internal_floppy_device_load(false, -1, nullptr); /* push disk halfway into drive */ m_wpt = CLEAR_LINE; //m_out_wpt_func(m_wpt); /* set timer for disk load */ int next_wpt; if (!is_readonly()) next_wpt = 1; else next_wpt = 0; m_wpt_timer->adjust(attotime::from_msec(250), next_wpt); return std::make_pair(retVal, std::string()); } void legacy_floppy_image_device::call_unload() { if (m_unload_proc) m_unload_proc(*this); floppy_close(m_floppy); m_floppy = nullptr; /* disk changed */ m_dskchg = 0; //m_out_dskchg_func(m_dskchg); /* pull disk halfway out of drive */ m_wpt = 0; //m_out_wpt_func(m_wpt); /* set timer for disk eject */ m_wpt_timer->adjust(attotime::from_msec(250), 1); } bool legacy_floppy_image_device::is_creatable() const noexcept { if (m_config) { const struct FloppyFormat *floppy_options = m_config->formats; for (int i = 0; floppy_options[i].construct; i++) { if (floppy_options[i].param_guidelines) return true; } } return false; } const char *legacy_floppy_image_device::image_interface() const noexcept { return m_config->interface; }