2017-08-06 02:26:15 +00:00
//
// MFMDiskController.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/08/2017.
2018-05-13 19:19:52 +00:00
// Copyright 2017 Thomas Harte. All rights reserved.
2017-08-06 02:26:15 +00:00
//
2024-01-17 04:34:46 +00:00
# pragma once
2017-08-06 02:26:15 +00:00
# include "DiskController.hpp"
2020-01-20 04:14:35 +00:00
# include "../../../Numeric/CRC.hpp"
2017-09-23 02:39:23 +00:00
# include "../../../ClockReceiver/ClockReceiver.hpp"
2017-09-25 00:07:56 +00:00
# include "../Encodings/MFM/Shifter.hpp"
2017-08-06 02:26:15 +00:00
2023-05-10 21:02:18 +00:00
namespace Storage : : Disk {
2017-08-06 02:26:15 +00:00
/*!
Extends Controller with a built - in shift register and FM / MFM decoding logic ,
being able to post event messages to subclasses .
*/
class MFMController : public Controller {
public :
2017-09-11 02:56:05 +00:00
MFMController ( Cycles clock_rate ) ;
2017-08-06 02:26:15 +00:00
protected :
2017-08-06 15:36:36 +00:00
/// Indicates whether the controller should try to decode double-density MFM content, or single-density FM content.
2017-08-06 02:26:15 +00:00
void set_is_double_density ( bool ) ;
2017-08-06 15:36:36 +00:00
/// @returns @c true if currently decoding MFM content; @c false otherwise.
2017-08-06 02:26:15 +00:00
bool get_is_double_density ( ) ;
enum DataMode {
2017-08-06 15:36:36 +00:00
/// When the controller is scanning it will obey all synchronisation marks found, even if in the middle of data.
2017-08-06 02:26:15 +00:00
Scanning ,
2017-08-06 15:36:36 +00:00
/// When the controller is reading it will ignore synchronisation marks and simply return a new token every sixteen PLL clocks.
2017-08-06 02:26:15 +00:00
Reading ,
2017-08-06 15:36:36 +00:00
/// When the controller is writing, it will replace the underlying data with that which has been enqueued, posting Event::DataWritten when the queue is empty.
2017-08-06 02:26:15 +00:00
Writing
} ;
2017-08-06 15:36:36 +00:00
/// Sets the current data mode.
2017-08-06 02:26:15 +00:00
void set_data_mode ( DataMode ) ;
2017-08-06 15:36:36 +00:00
/*!
Describes a token found in the incoming PLL bit stream . Tokens can be one of :
Index : the bit pattern usually encoded at the start of a track to denote the position of the index hole ;
ID : the pattern that begins an ID section , i . e . a sector header , announcing sector number , track number , etc .
Data : the pattern that begins a data section , i . e . sector contents .
DeletedData : the pattern that begins a deleted data section , i . e . deleted sector contents .
Sync : MFM only ; the same synchronisation mark is used in MFM to denote the bottom three of the four types
of token listed above ; this class combines notification of that mark and the distinct index sync mark .
Both are followed by a byte to indicate type . When scanning an MFM stream , subclasses will receive an
announcement of sync followed by an announcement of one of the above four types of token .
Byte : reports reading of an ordinary byte , with expected timing bits .
When the data mode is set to ' reading ' , only Byte tokens are returned ; detection of the other kinds of token
is suppressed . Controllers will likely want to switch data mode when receiving ID and sector contents , as
spurious sync signals can otherwise be found in ordinary data , causing framing errors .
*/
2017-08-06 02:26:15 +00:00
struct Token {
enum Type {
Index , ID , Data , DeletedData , Sync , Byte
} type ;
uint8_t byte_value ;
} ;
2017-08-06 15:36:36 +00:00
/// @returns The most-recently read token from the surface of the disk.
2017-08-06 02:26:15 +00:00
Token get_latest_token ( ) ;
2017-08-07 16:37:22 +00:00
/// @returns The controller's CRC generator. This is automatically fed during reading.
2018-05-24 02:21:57 +00:00
CRC : : CCITT & get_crc_generator ( ) ;
2017-08-07 16:37:22 +00:00
2017-08-06 02:26:15 +00:00
// Events
enum class Event : int {
2017-08-06 15:36:36 +00:00
Token = ( 1 < < 0 ) , // Indicates recognition of a new token in the flux stream. Use get_latest_token() for more details.
IndexHole = ( 1 < < 1 ) , // Indicates the passing of a physical index hole.
DataWritten = ( 1 < < 2 ) , // Indicates that all queued bits have been written
2017-08-06 02:26:15 +00:00
} ;
2017-08-06 15:36:36 +00:00
/*!
Subclasses should implement this . It is called every time a new @ c Event is discovered in the incoming data stream .
Therefore it is called to announce when :
( i ) a new token is discovered in the incoming stream : an index , ID , data or deleted data , a sync mark or a new byte of data .
( ii ) the index hole passes ; or
( iii ) the queue of data to be written has been exhausted .
*/
2017-08-06 16:35:20 +00:00
virtual void posit_event ( int type ) = 0 ;
2017-08-06 02:26:15 +00:00
2017-08-13 16:50:07 +00:00
/*!
Encodes @ c bit according to the current single / double density mode and adds it
to the controller ' s write buffer .
*/
void write_bit ( int bit ) ;
/*!
Encodes @ c byte according to the current single / double density mode and adds it
to the controller ' s write buffer .
*/
void write_byte ( uint8_t byte ) ;
/*!
Serialises @ c value into the controller ' s write buffer without adjustment .
*/
void write_raw_short ( uint16_t value ) ;
2017-08-13 22:27:00 +00:00
/*!
Gets the current value of the CRC generator and makes two calls to @ c write_byte , to
write first its higher - value byte and then its lower .
*/
2017-08-13 22:05:19 +00:00
void write_crc ( ) ;
2017-08-14 19:50:36 +00:00
/*!
Calls @ c write_byte with @ c value , @ c quantity times .
*/
void write_n_bytes ( int quantity , uint8_t value ) ;
2017-08-14 20:03:35 +00:00
/*!
2018-05-13 19:34:31 +00:00
Writes everything that should per the spec appear prior to the address contained
in an ID mark ( i . e . proper gaps and the ID mark ) and appropriate seeds the CRC generator .
2017-08-14 20:03:35 +00:00
*/
void write_id_joiner ( ) ;
/*!
2017-08-15 19:29:23 +00:00
Writes at most what should , per the spec , appear after the ID ' s CRC , up to and
2017-08-14 20:03:35 +00:00
including the mark that indicates the beginning of data , appropriately seeding
2017-08-15 19:29:23 +00:00
the CRC generator ; if @ c skip_first_gap is set then the initial gap after the
CRC isn ' t written .
2017-08-14 20:03:35 +00:00
*/
2017-08-15 19:29:23 +00:00
void write_id_data_joiner ( bool is_deleted , bool skip_first_gap ) ;
2017-08-14 20:03:35 +00:00
/*!
Writes the gap expected after a sector ' s data CRC and before the beginning of the
next ID joiner .
*/
void write_post_data_gap ( ) ;
/*!
Writes everything that should , per the spec , following the index hole and prior
to any sectors .
*/
void write_start_of_track ( ) ;
2017-08-06 02:26:15 +00:00
private :
// Storage::Disk::Controller
2017-09-10 23:23:23 +00:00
virtual void process_input_bit ( int value ) ;
2017-08-06 02:26:15 +00:00
virtual void process_index_hole ( ) ;
virtual void process_write_completed ( ) ;
2017-09-25 00:07:56 +00:00
// Reading state.
Token latest_token_ ;
Encodings : : MFM : : Shifter shifter_ ;
2017-08-06 02:26:15 +00:00
// input configuration
bool is_double_density_ ;
2017-11-11 03:47:10 +00:00
DataMode data_mode_ = DataMode : : Scanning ;
2017-08-06 02:26:15 +00:00
2017-08-13 16:50:07 +00:00
// writing
int last_bit_ ;
2017-08-06 02:26:15 +00:00
// CRC generator
2018-05-24 02:21:57 +00:00
CRC : : CCITT crc_generator_ ;
2017-08-06 02:26:15 +00:00
} ;
}