diff options
author | 2016-08-24 15:15:09 +0200 | |
---|---|---|
committer | 2016-08-24 15:16:43 +0200 | |
commit | cb30b9d3070281a0098023feedc9d3529602fe6d (patch) | |
tree | dedbca7b33e70ff7d73ad644a8eb8cac6efc27db /docs/source/techspecs | |
parent | a86a53fb64dd4b9e6dfa3d6a1ebd48e30f9d3efa (diff) |
Added documentation to main tree (nw)
Diffstat (limited to 'docs/source/techspecs')
-rw-r--r-- | docs/source/techspecs/floppy.rst | 414 | ||||
-rw-r--r-- | docs/source/techspecs/index.rst | 12 | ||||
-rw-r--r-- | docs/source/techspecs/luaengine.rst | 151 | ||||
-rw-r--r-- | docs/source/techspecs/m6502.rst | 379 | ||||
-rw-r--r-- | docs/source/techspecs/nscsi.rst | 170 |
5 files changed, 1126 insertions, 0 deletions
diff --git a/docs/source/techspecs/floppy.rst b/docs/source/techspecs/floppy.rst new file mode 100644 index 00000000000..af4b13dc367 --- /dev/null +++ b/docs/source/techspecs/floppy.rst @@ -0,0 +1,414 @@ +The new floppy subsystem +======================== + +1. Introduction +--------------- + +The new floppy subsystem aims at emulating the behaviour of floppies and floppy controllers at a level low enough that protections work as a matter of course. It reaches its goal by following the real hardware configuration: + +- a floppy image class keeps in memory the magnetic state of the floppy surface and its physical characteristics + +- an image handler class talks with the floppy image class to simulate the floppy drive, providing all the signals you have on a floppy drive connector + +- floppy controller devices talk with the image handler and provide the register interfaces to the host we all know and love + +- format handling classes are given the task of statelessly converting to and from an on-disk image format to the in-memory magnetic state format the floppy image class manages + + +2. Floppy storage 101 +--------------------- + +2.1. Floppy disk +~~~~~~~~~~~~~~~~ + +A floppy disk is a disc that stores magnetic orientations on their surface disposed in a series on concentric circles called tracks or cylinders [1]_. Its main characteristics are its size (goes from a diameter of around 2.8" to 8") , its number of writable sides (1 or 2) and its magnetic resistivity. The magnetic resistivity indicates how close magnetic orientation changes can happen and the information kept. That's one third of what defines the term "density" that is so often used for floppies (the other two are floppy drive head size and bit-level encoding). + +The magnetic orientations are always binary, e.g. they're one way or the opposite, there's no intermediate state. Their direction can either be tengentially to the track, e.g in the direction or opposite to the rotation, or in the case of perpendicular recording the direction is perpendicular to the disc surface (hence the name). Perpendicular recording allows for closer orientation changes by writing the magnetic information more deeply, but arrived late in the technology lifetime. 2.88Mb disks and the floppy children (Zip drives, etc) used perpendicular recording. For simulation purposes the direction is not important, only the fact that only two orientations are possible is. Two more states are possible though: a portion of a track can be demagnetized (no orientation) or damaged (no orientation and can't be written to). + +A specific position in the disk rotation triggers an index pulse. That position can be detected through a hole in the surface (very visible in 5.25" and 3" floppies for instance) or through a specific position of the rotating center (3.5" floppies, perhaps others). This index pulse is used to designate the beginning of the track, but is not used by every system. Older 8" floppies have multiple index holes used to mark the beginning of sectors (called hard sectoring) but one of them is positioned differently to be recognized as the track start, and the others are at fixed positions relative to the origin one. + + +2.2. Floppy drive +~~~~~~~~~~~~~~~~~ + +A floppy drive is what reads and writes a floppy disk. It includes an assembly capable of rotating the disk at a fixed speed and one or two magnetic heads tied to a positioning motor to access the tracks. + +The head width and positioning motor step size decides how many tracks are written on the floppy. Total number of tracks goes from 32 to 84 depending on the floppy and drive, with the track 0 being the most exterior (longer) one of the concentric circles, and the highest numbered the smallest interior circle. As a result the tracks with the lowest numbers have the lowest physical magnetic orientation density, hence the best reliability. Which is why important and/or often changed structures like the boot block or the fat allocation table are at track 0. That is also where the terminology "stepping in" to increase the track number and "stepping out" to decrease it comes from. The number of tracks available is the second part of what is usually behind the term "density". + +A sensor detects when the head is on track 0 and the controller is not supposed to try to go past it. In addition physical blocks prevent the head from going out of the correct track range. Some systems (Apple II, some C64) do not take the track 0 sensor into account and just wham the head against the track 0 physical block, giving a well-known crash noise and eventually damaging the head alignment. + +Also, some systems (Apple II and C64 again) have direct access to the phases of the head positioning motor, allowing to trick the head into going between tracks, in middle or even quarter positions. That was not usable to write more tracks, since the head width did not change, but since reliable reading was only possible with the correct position it was used for some copy protection systems. + +The disk rotates at a fixed speed for a given track. The most usual speed is 300 rpm for every track, with 360 rpm found for HD 5.25" floppies and most 8" ones, and a number of different values like 90 rpm for the earlier floppies or 150 rpm for an HD floppy in an Amiga. Having a fixed rotational speed for the whole disk is called Constant Angular Velocity (CAV, almost everybody) or Zoned Constant Angular Velocity (ZCAV, C64) depending on whether the read/write bitrate is constant or track-dependant. Some systems (Apple II, Mac) vary the rotational speed depending on the track (something like 394 rpm up to 590 rpm) to end up with a Constant Linear Velocity (CLV). The idea behind ZCAV/CLV is to get more bits out of the media by keeping the minimal spacing between magnetic orientation transitions close to the best the support can do. It seems that the complexity was not deemed worth it since almost no system does it. + +Finally, after the disc rotates and the head is over the proper track reading happens. The reading is done through an inductive head, which gives it the interesting characteristic of not reading the magnetic orientation directly but instead of being sensitive to orientation inversions, called flux transitions. This detection is weak and somewhat uncalibrated, so an amplifier with Automatic Gain Calibration (AGC) and a peak detector are put behind the head to deliver clean pulses. The AGC slowly increases the amplification level until a signal goes over the threshold, then modulates its gain so that said signal is at a fixed position over the threshold. Afterwards the increase happens again. This makes the amplifier calibrate itself to the signals read from the floppy as long as flux transitions happen often enough. Too long and the amplification level will reach a point where the random noise the head picks from the environment is amplified over the threshold, creating a pulse where none should be. Too long in our case happens to be around 16-20us with no transitions. That means a long enough zone with a fixed magnetic orientation or no orientation at all (demagnetized or damaged) is going to be read as a series of random pulses after a brief delay. This is used by protections and is known as "weak bits", which read differently each time they're accessed. + +A second level of filtering happens after the peak detector. When two transitions are a little close (but still over the media threshold) a bouncing effect happens between them giving two very close pulses in the middle in addition to the two normal pulses. The floppy drive detects when pulses are too close and filter them out, leaving the normal ones. As a result, if one writes a train of high-frequency pulses to the floppy they will be read back as a train of too close pulses (weak because they're over the media tolerance, but picked up by the AGC anyway, only somewhat unreliably) they will be all filtered out, giving a large amount of time without any pulse in the output signal. This is used by some protections since it's not writable with a normally clocked controller. + +Writing is symmetrical, with a series of pulses sent which make the write head invert the magnetic field orientation each time a pulse is received. + +So, in conclusion, the floppy drive provides inputs to control disk rotation and head position (and choice when double-sided), and the data goes both way as a train of pulses representing magnetic orientation inversions. The absolute value of the orientation itself is never known. + + +2.3. Floppy controller +~~~~~~~~~~~~~~~~~~~~~~ + +The task of the floppy controller is to turn the signals to/from the floppy drive into something the main CPU can digest. The level of support actually done by the controller is extremely variable from one device to the other, from pretty much nothing (Apple II, C64) through minimal (Amiga) to complete (Western Digital chips, uPD765 family). Usual functions include drive selection, motor control, track seeking and of course reading and writing data. Of these only the last two need to be described, the rest is obvious. + +The data is structured at two levels: how individual bits (or nibbles, or bytes) are encoded on the surface, and how these are grouped in individually-addressable sectors. Two standards exist for these, called FM and MFM, and in addition a number of systems use their home-grown variants. Moreover, some systems such as the Amiga use a standard bit-level encoding (MFM) but a homegrown sector-level organisation. + + +2.3.1. Bit-level encodings +'''''''''''''''''''''''''' + +2.3.1.1. Cell organization +`````````````````````````` + +All floppy controllers, even the wonkiest like the Apple II one, start by dividing the track in equally-sized cells. They're angular sections in the middle of which a magnetic orientation inversion may be present. From a hardware point of view the cells are seen as durations, which combined with the floppy rotation give the section. For instance the standard MFM cell size for a 3" double-density floppy is 2us, which combined with the also standard 300 rpm rotational speed gives an angular size of 1/100000th of a turn. Another way of saying it is that there are 100K cells in a 3" DD track. + +In every cell there may or may not be a magnetic orientation transition, e.g. a pulse coming from (reading) or going to (writing) the floppy drive. A cell with a pulse is traditionally noted '1', and one without '0'. Two constraints apply to the cell contents though. First, pulses must not be too close together or they'll blur each-other and/or be filtered out. The limit is slightly better than 1/50000th of a turn for single and double density floppies, half that for HD floppys, and half that again for ED floppies with perpendicular recording. Second, they must not be too away from each other or either the AGC is going to get wonky and introduce phantom pulses or the controller is going to lose sync and get a wrong timing on the cells on reading. Conservative rule of thumb is not to have more than three consecutive '0' cells. + +Of course protections play with that to make formats not reproducible by the system controller, either breaking the three-zeroes rule or playing with the cells durations/sizes. + +Bit endocing is then the art of transforming raw data into a cell 0/1 configuration that respects the two constraints. + +2.3.1.2. FM encoding +```````````````````` + +The very first encoding method developed for floppies is called Frequency Modulation, or FM. The cell size is set at slighly over the physical limit, e.g. 4us. That means it is possible to reliably have consecutive '1' cells. Each bit is encoded on two cells: + +- the first cell, called the clock bit, is '1' + +- the second cell, called data bit, is the bit + +Since every other cell at least is '1' there is no risk of going over three zeroes. + +The name Frequency Modulation simply derives from the fact that a 0 is encoded with one period of a 125Khz pulse train while a 1 is two periods of a 250Khz pulse train. + +2.3.1.3. MFM encoding +````````````````````` +The FM encoding has been superseded by the Modified Frequency Modulation encoding, which can cram exactly twice as much data on the same surface, hence its other name of "double density". The cell size is set at slightly over half the physical limit, e.g. 2us usually. The constraint means that two '1' cells must be separated by at least one '0' cell. Each bit is once again encoded on two cells: + +- the first cell, called the clock bit, is '1' if both the previous and current data bits are 0, '0' otherwise + +- the second cell, called data bit, is the bit + +The minimum space rule is respected since a '1' clock bit is by definition surrounded by two '0' data bits, and a '1' data bit is surrounded by two '0' clock bits. The longest '0'-cell string possible is when encoding 101 which gives x10001, respecting the maximum of three zeroes. + +2.3.1.4. GCR encodings +`````````````````````` + +Group Coded Recording, or GCR, encodings are a class of encodings where strings of bits at least nibble-size are encoded into a given cell stream given by a table. It has been used in particular by the Apple II, the Mac and the C64, and each system has its own table, or tables. + +2.3.1.5. Other encodings +```````````````````````` + +Other encodings exist, like M2FM, but they're very rare and system-specific. + +2.3.1.6. Reading back encoded data +`````````````````````````````````` + +Writing encoded data is easy, you only need a clock at the appropriate frequency and send or not a pulse on the clock edges. Reading back the data is where the fun is. Cells are a logical construct and not a physical measurable entity. Rotational speeds very around the defined one (+/- 2% is not rare) and local perturbations (air turbulence, surface distance...) make the instant speed very variable in general. So to extract the cell values stream the controller must dynamically synchronize with the pulse train that the floppy head picks up. The principle is simple: a cell-sized duration window is build within which the presence of at least one pulse indicates the cell is a '1', and the absence of any a '0'. After reaching the end of the window the starting time is moved appropriately to try to keep the observed pulse at the exact middle of the window. This allows to correct the phase on every '1' cell, making the synchronization work if the rotational speed is not too off. Subsequent generations of controllers used a Phase-Locked Loop (PLL) which vary both phase and window duration to adapt better to wrong rotational speeds, with usually a tolerance of +/- 15%. + +Once the cell data stream is extracted decoding depends on the encoding. In the FM and MFM case the only question is to recognize data bits from clock bits, while in GCR the start position of the first group should be found. That second level of synchronization is handled at a higher level using patterns not found in a normal stream. + + +2.3.2. Sector-level organization +'''''''''''''''''''''''''''''''' + +Floppies have been designed for read/write random access to reasonably sized blocks of data. Track selection allows for a first level of random access and sizing, but the ~6K of a double density track would be too big a block to handle. 256/512 bytes are considered a more appropriate value. To that end data on a track is organized as a series of (sector header, sector data) pairs where the sector header indicates important information like the sector number and size, and the sector data contains the data. Sectors have to be broken in two parts because while reading is easy, read the header then read the data if you want it, writing requires reading the header to find the correct place then once that is done switching on the writing head for the data. Starting writing is not instantaneous and will not be perfectly phase-aligned with the read header, so space for synchronization is required between header and data. + +In addition somewhere in the sector header and in the sector data are pretty much always added some kind of checksum allowing to know whether the data was damaged or not. + +FM and MFM have (not always used) standard sector layout methods. + +2.3.2.1. FM sector layout +````````````````````````` + +The standard "PC" track/sector layout for FM is as such: + +- A number of FM-encoded 0xff (usually 40) + +- 6 FM-encoded 0x00 (giving a steady 125KHz pulse train) + +- The 16-cell stream 1111011101111010 (f77a, clock 0xd7, data 0xfc) + +- A number of FM-encoded 0xff (usually 26, very variable) + +Then for each sector: +- 6 FM-encoded 0x00 (giving a steady 125KHz pulse train) + +- The 16-cell stream 1111010101111110 (f57e, clock 0xc7, data 0xfe) + +- Sector header, e.g. FM encoded track, head, sector, size code and two bytes of crc + +- 11 FM-encoded 0xff + +- 6 FM-encoded 0x00 (giving a steady 125KHz pulse train) + +- The 16-cell stream 1111010101101111 (f56f, clock 0xc7, data 0xfb) + +- FM-encoded sector data followed by two bytes of crc + +- A number of FM-encoded 0xff (usually 48, very variable) + +The track is finished with a stream of '1' cells. + +The 125KHz pulse trains are used to lock the PLL to the signal correctly. The specific 16-cells streams allow to distinguish between clock and data bits by providing a pattern that is not supposed to happen in normal FM-encoded data. In the sector header track numbers start at 0, heads are 0/1 depending on the size, sector numbers usually start at 1 and size code is 0 for 128 bytes, 1 for 256, 2 for 512, etc. + +The CRC is a cyclic redundancy check of the data bits starting with the mark just after the pulse train using polynom 0x11021. + +The Western Digital-based controllers usually get rid of everything but some 0xff before the first sector and allow a better use of space as a result. + +2.3.2.2. MFM sector layout +`````````````````````````` + +The standard "PC" track/sector layout for MFM is as such: + +- A number of MFM-encoded 0x4e (usually 80) + +- 12 FM-encoded 0x00 (giving a steady 250KHz pulse train) + +- 3 times the 16-cell stream 0101001000100100 (5224, clock 0x14, data 0xc2) + +- The MFM-encoded value 0xfc + +- A number of MFM-encoded 0x4e (usually 50, very variable) + +Then for each sector: + +- 12 FM-encoded 0x00 (giving a steady 250KHz pulse train) + +- 3 times the 16-cell stream 0100010010001001 (4489, clock 0x0a, data 0xa1) + +- Sector header, e.g. MFM-encoded 0xfe, track, head, sector, size code and two bytes of crc + +- 22 MFM-encoded 0x4e + +- 12 MFM-encoded 0x00 (giving a steady 250KHz pulse train) + +- 3 times the 16-cell stream 0100010010001001 (4489, clock 0x0a, data 0xa1) + +- MFM-encoded 0xfb, sector data followed by two bytes of crc + +- A number of MFM-encoded 0x4e (usually 84, very variable) + +The track is finished with a stream of MFM-encoded 0x4e. + +The 250KHz pulse trains are used to lock the PLL to the signal correctly. The cell pattern 4489 does not appear in normal MFM-encoded data and is used for clock/data separation. + +As for FM, the Western Digital-based controllers usually get rid of everything but some 0x4e before the first sector and allow a better use of space as a result. + +2.3.2.3. Formatting and write splices +````````````````````````````````````` + +To be usable, a floppy must have the sector headers and default sector data written on every track before using it. The controller starts writing at a given place, often the index pulse but on some systems whenever the command is sent, and writes until a complete turn is done. That's called formatting the floppy. At the point where the writing stops there is a synchronization loss since there is no chance the cell stream clock warps around perfectly. This brutal phase change is called a write splice, specifically the track write splice. It is the point where writing should start if one wants to raw copy the track to a new floppy. + +Similarly two write splices are created when a sector is written at the start and end of the data block part. They're not supposed to happen on a mastered disk though, even if there are some rare exceptions. + + +3. The new implementation +------------------------- + +3.1. Floppy disk representation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +The floppy disk contents are represented by the class floppy_image. It contains information of the media type and a representation of the magnetic state of the surface. + +The media type is divided in two parts. The first half indicates the physical form factor, i.e. all medias with that form factor can be physically inserted in a reader that handles it. The second half indicates the variants which are usually detectable by the reader, such as density and number of sides. + +Track data consists of a series of 32-bits lsb-first values representing magnetic cells. Bits 0-27 indicate the absolute position of the start of the cell (not the size), and bits 28-31 the type. Type can be: + +- 0, MG_A -> Magnetic orientation A + +- 1, MG_B -> Magnetic orientation B + +- 2, MG_N -> Non-magnetized zone (neutral) + +- 3, MG_D -> Damaged zone, reads as neutral but cannot be changed by writing + +The position is in angular units of 1/200,000,000th of a turn. It corresponds to one nanosecond when the drive rotates at 300 rpm. + +The last cell implicit end position is of course 200,000,000. + +Unformatted tracks are encoded as zero-size. + +The "track splice" information indicates where to start writing if you try to rewrite a physical disk with the data. Some preservation formats encode that information, it is guessed for others. The write track function of fdcs should set it. The representation is the angular position relative to the index. + +3.2. Converting to and from the internal representation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +3.2.1. Class and interface +'''''''''''''''''''''''''' + +We need to be able to convert on-disk formats of the floppy data to and from the internal representation. This is done through classes derived from floppy_image_format_t. The interface to be implemented includes: +- **name()** gives the short name of the on-disk format + +- **description()** gives a short description of the format + +- **extensions()** gives a comma-separated list of file name extensions found for that format + +- **supports_save()** returns true is converting to that external format is supported + +- **identify(file, form factor)** gives a 0-100 score for the file to be of that format: + + - **0** = not that format + + - **100** = certainly that format + + - **50** = format identified from file size only + +- **load(file, form factor, floppy_image)** loads an image and converts it into the internal representation + +- **save(file, floppy_image)** (if implemented) converts from the internal representation and saves an image + +All of these methods are supposed to be stateless. + +3.2.2. Conversion helper methods +'''''''''''''''''''''''''''''''' + + +A number of methods are provided to simplify writing the converter classes. + + +3.2.2.1. Load-oriented conversion methods +````````````````````````````````````````` + + +| **generate_track_from_bitstream(track number,** +| **head number,** +| **UINT8 \*cell stream,** +| **int cell count,** +| **floppy image)** +| + + Takes a stream of cell types (0/1), MSB-first, converts it to the internal format and stores it at the given track and head in the given image. + +| **generate_track_from_levels(track number,** +| **head number,** +| **UINT32 \*cell levels,** +| **int cell count,** +| **splice position,** +| **floppy image)** + + Takes a variant of the internal format where each value represents a cell, the position part of the values is the size of the cell and the level part is MG_0, MG_1 for normal cell types, MG_N, MG_D for unformatted/damaged cells, and MG_W for Dungeon-Master style weak bits. Converts it into the internal format. The sizes are normalized so that they total to a full turn. + +| **normalize_times(UINT32 \*levels,** +| **int level_count)** + + Takes an internal-format buffer where the position part represents angle until the next change and turns it into a normal positional stream, first ensuring that the total size is normalized to a full turn. + + +3.2.2.2. Save-oriented conversion methods +````````````````````````````````````````` + +| **generate_bitstream_from_track(track number,** +| **head number,** +| **base cell size**, +| **UINT8 \*cell stream,** +| **int &cell_stream_size,** +| **floppy image)** + + Extract a cell 0/1 stream from the internal format using a PLL setup with an initial cell size set to 'base cell size' and a +/- 25% tolerance. + + +| **struct desc_xs { int track, head, size; const UINT8 \*data }** +| **extract_sectors_from_bitstream_mfm_pc(...)** +| **extract_sectors_from_bitstream_fm_pc(const UINT8 \*cell stream,** +| **int cell_stream_size,** +| **desc_xs \*sectors,** +| **UINT8 \*sectdata,** +| **int sectdata_size)** + + Extract standard mfm or fm sectors from a regenerated cell stream. Sectors must point to an array of 256 desc_xs. + + An existing sector is recognizable by having ->data non-null. Sector data is written in sectdata up to sectdata_size bytes. + + +| **get_geometry_mfm_pc(...)** +| **get_geometry_fm_pc(floppy image,** +| **base cell size,** +| **int &track_count,** +| **int &head_count,** +| **int §or_count)** + + Extract the geometry (heads, tracks, sectors) from a pc-ish floppy image by checking track 20. + + +| **get_track_data_mfm_pc(...)** +| **get_track_data_fm_pc(track number,** +| **head number,** +| **floppy image,** +| **base cell size,** +| **sector size,** +| **sector count,** +| **UINT8 \*sector data)** +| + + Extract what you'd get by reading in order 'sector size'-sized sectors from number 1 to sector count and put the result in sector data. + + +3.3. Floppy drive +~~~~~~~~~~~~~~~~~ + + The class floppy_image_interface simulates the floppy drive. That includes a number of control signals, reading, and writing. Control signal changes must be synchronized, e.g. fired off a timer to ensure the current time is the same for all devices. + +3.3.1. Control signals +'''''''''''''''''''''' + + Due to the way they're usually connected to CPUs (e.g. directly on an I/O port), the control signals work with physical instead of logical values. Which means than in general 0 means active, 1 means inactive. Some signals also have a callback associated called when they change. + +**mon_w(state) / mon_r()** + + Motor on signal, rotates on 0. + + +**idx_r() / setup_index_pulse_cb(cb)** + + Index signal, goes 0 at start of track for about 2ms. Callback is synchronized. Only happens when a disk is in and the motor is running. + + +**ready_r() / setup_ready_cb(cb)** + + Ready signal, goes to 1 when the disk is removed or the motor stopped. Goes to 0 after two index pulses. + + +**wpt_r() / setup_wpt_cb(cb)** + + Write protect signal (1 = readonly). Callback is unsynchronized. + + +**dskchg_r()** + + Disk change signal, goes to 1 when a disk is change, goes to 0 on track change. + + +**dir_w(dir)** + + Selects track stepping direction (1 = out = decrease track number). + + +**stp_w(state)** + + Step signal, moves by one track on 1->0 transistion. + + +**trk00_r()** + + Track 0 sensor, returns 0 when on track 0. + + +**ss_w(ss) / ss_r()** + + Side select + + +3.3.2. Read/write interface +''''''''''''''''''''''''''' + +The read/write interface is designed to work asynchronously, e.g. somewhat independently of the current time. + + + +.. [1] Cylinder is a hard-drive term somewhat improperly used for floppies. It comes from the fact that hard-drives are similar to floppies but include a series of stacked disks with a read/write head on each. The heads are physically linked and all point to the same circle on every disk at a given time, making the accessed area look like a cylinder. Hence the name. diff --git a/docs/source/techspecs/index.rst b/docs/source/techspecs/index.rst new file mode 100644 index 00000000000..ed836ace341 --- /dev/null +++ b/docs/source/techspecs/index.rst @@ -0,0 +1,12 @@ +Technical Specifications +------------------------ + +This section covers technical specifications useful to programmers working on MAME's source or working on LUA scripts that run within the MAME framework. + +.. toctree:: + :titlesonly: + + floppy + nscsi + luaengine + m6502
\ No newline at end of file diff --git a/docs/source/techspecs/luaengine.rst b/docs/source/techspecs/luaengine.rst new file mode 100644 index 00000000000..988c6bb7838 --- /dev/null +++ b/docs/source/techspecs/luaengine.rst @@ -0,0 +1,151 @@ +Scripting MAME via LUA +====================== + +Introduction +------------ + +It is now possible to externally drive MAME via LUA scripts. This feature initially appeared in version 0.148, when a minimal +``luaengine`` was implemented. Nowadays, the LUA interface is rich enough to let you inspect and manipulate devices state, access CPU +registers, read and write memory, and draw a custom HUD on screen. + +Internally, MAME makes extensive use of ``luabridge`` to implement this feature: the idea is to transparently expose as many of the useful internals as possible. + +Finally, a warning: The LUA API is not yet declared stable and may suddenly change without prior notice. However, we expose methods to let you know at runtime which API version you are running against, and you can introspect most of the objects at runtime. + +Features +-------- + +The API is not yet complete, but this is a partial list of capabilities currently available to LUA scripts: + +- machine metadata (app version, current rom, rom details) +- machine control (starting, pausing, resetting, stopping) +- machine hooks (on frame painting and on user events) +- devices introspection (device tree listing, memory and register + enumeration) +- screens introspection (screens listing, screen details, frames + counting) +- screen HUD drawing (text, lines, boxes on multiple screens) +- memory read/write (8/16/32/64 bits, signed and unsigned) +- registers and states control (states enumeration, get and set) + +Usage +----- + +MAME supports external scripting via LUA (>= 5.3) scripts, either written on the interactive console or loaded as a file. To reach the +console, just run MAME with **-console** and you will be greeted by a naked ``>`` prompt where you can input your script. + +To load a whole script at once, store it in a plain text file and pass it via **-autoboot_script**. Please note that script loading may be delayed (few seconds by default), but you can override the default with the **-autoboot_delay** argument. + +To control the execution of your code, you can use a loop-based or an event-based approach. The former is not encouraged as it is +resource-intensive and makes control flow unnecessarily complex. Instead, we suggest to register custom hooks to be invoked on specific +events (eg. at each frame rendering). + +Walkthrough +----------- + +Let's first run MAME in a terminal to reach the LUA console: + +:: + + $ mame -console YOUR_ROM + M.A.M.E. v0.158 (Feb 5 2015) - Multiple Arcade Machine Emulator + Copyright Nicola Salmoria and the MAME team + Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio + + > + +At this point, your game is probably running in demo mode, let's pause it: + +:: + + > emu.pause() + > + +Even without textual feedback on the console, you'll notice the game is +now paused. In general, commands are quiet and only print back error +messages. + +You can check at runtime which version of MAME you are running, with: + +:: + + > print(emu.app_name() .. " " .. emu.app_version()) + mame 0.158 + +We now start exploring screen related methods. First, let's enumerate available screens: + +:: + + > for i,v in pairs(manager:machine().screens) do print(i) end + :screen + +**manager:machine()** is the root object of your currently running machine: we will be using this often. **screens** is a table with all +available screens; most machines only have one main screen. In our case, the main and only screen is tagged as **:screen**, and we can further inspect it: + +:: + + > -- let's define a shorthand for the main screen + > s = manager:machine().screens[":screen"] + > print(s:width() .. "x" .. s:height()) + 320x224 + +We have several methods to draw on the screen a HUD composed of lines, boxes and text: + +:: + + > -- we define a HUD-drawing function, and then call it + > function draw_hud() + >> s:draw_text(40, 40, "foo"); -- (x0, y0, msg) + >> s:draw_box(20, 20, 80, 80, 0, 0xff00ffff); -- (x0, y0, x1, y1, fill-color, line-color) + >> s:draw_line(20, 20, 80, 80, 0xff00ffff); -- (x0, y0, x1, y1, line-color) + >> end + > draw_hud(); + +This will draw some useless art on the screen. However, when unpausing the game, your HUD needs to be refreshed otherwise it will just disappear. In order to do this, you have to register your hook to be called on every frame repaint: + +:: + + > emu.sethook(draw_hud, "frame") + +All colors are expected in ARGB format (32b unsigned), while screen origin (0,0) normally corresponds to the top-left corner. + +Similarly to screens, you can inspect all the devices attached to a machine: + +:: + + > for k,v in pairs(manager:machine().devices) do print(k) end + :audiocpu + :maincpu + :saveram + :screen + :palette + [...] + +On some of them, you can also inspect and manipulate memory and state: + +:: + + > cpu = manager:machine().devices[":maincpu"] + > -- enumerate, read and write state registers + > for k,v in pairs(cpu.state) do print(k) end + D5 + SP + A4 + A3 + D0 + PC + [...] + > print(cpu.state["D0"].value) + 303 + > cpu.state["D0"].value = 255 + > print(cpu.state["D0"].value) + 255 + +:: + + > -- inspect memory + > for k,v in pairs(cpu.spaces) do print(k) end + program + > mem = cpu.spaces["program"] + > print(mem:read_i8(0xC000)) + 41
\ No newline at end of file diff --git a/docs/source/techspecs/m6502.rst b/docs/source/techspecs/m6502.rst new file mode 100644 index 00000000000..dbc7a0a02a5 --- /dev/null +++ b/docs/source/techspecs/m6502.rst @@ -0,0 +1,379 @@ +The new 6502 family implementation +================================== + +Introduction +------------ + +The new 6502 family implementation has been created to reach sub-instruction accuracy in observable behaviour. It is designed with 3 goals in mind: + +- every bus cycle must happen at the exact time it would happen in a real CPU, and every access the real CPU does is done + +- instructions can be interrupted at any time in the middle then restarted at that point transparently + +- instructions can be interrupted even from within a memory handler for bus contention/wait states emulation purposes + +Point 1 has been ensured through bisimulation with the gate-level simulation perfect6502. Point 2 has been ensured structurally through a code generator which will be explained in section 8. Point 3 is not done yet due to lack of support on the memory subsystem side, but section 9 shows how it will be handled. + + +The 6502 family +--------------- + +The MOS 6502 family has been large and productive. A large number of variants exist, varying on bus sizes, I/O, and even opcodes. Some offshots (g65c816, hu6280) even exist that live elsewhere in the mame tree. The final class hierarchy is this: + +:: + + 6502 + | + +------+--------+--+--+-------+-------+ + | | | | | | + 6510 deco16 6504 6509 n2a03 65c02 + | | + +-----+-----+ r65c02 + | | | | + 6510t 7501 8502 +---+---+ + | | + 65ce02 65sc02 + | + 4510 + + + +The 6510 adds an up to 8 bits I/O port, with the 6510t, 7501 and 8502 being software-identical variants with different pin count (hence I/O count), die process (NMOS, HNMOS, etc) and clock support. + +The deco16 is a Deco variant with a small number of not really understood additional instructions and some I/O. + +The 6504 is a pin and address-bus reduced version. + +The 6509 adds internal support for paging. + +The n2a03 is the NES variant with the D flag disabled and sound functionality integrated. + +The 65c02 is the very first cmos variant with some additional instructions, some fixes, and most of the undocumented instructions turned into nops. The R (Rockwell, but eventually produced by WDC too among others) variant adds a number of bitwise instructions and also stp and wai. The SC variant, used by the Lynx portable console, looks identical to the R variant. The 'S' probably indicates a static-ram-cell process allowing full DC-to-max clock control. + +The 65ce02 is the final evolution of the ISA in this hierarchy, with additional instructions, registers, and removals of a lot of dummy accesses that slowed the original 6502 down by at least 25%. The 4510 is a 65ce02 with integrated MMU and GPIO support. + + +Usage of the classes +-------------------- + +All the CPUs are standard modern CPU devices, with all the normal interaction with the device infrastructure. To include one of these CPUs in your driver you need to include "**CPU/m6502/<CPU>.h**" and then do a **MCFG_CPU_ADD("tag", <CPU>, clock)**. + +6510 variants port I/O callbacks are setup through: + **MCFG_<CPU>_PORT_CALLBACKS(READ8(type, read_method), WRITE8(type, write_method))** + +And the pullup and floating lines mask is given through: + **MCFG_<CPU>_PORT_PULLS(pullups, floating)** + +In order to see all bus accesses on the memory handlers it is possible to disable accesses through the direct map (at a CPU cost, of course) with: + **MCFG_M6502_DISABLE_DIRECT()** + +In that case, transparent decryption support is also disabled, everything goes through normal memory-map read/write calls. The state of the sync line is given by the CPU method **get_sync()**, making implementing the decryption in the handler possible. + +Also, as for every executable device, the CPU method **total_cycles()** gives the current time in cycles since the start of the machine from the point of view of the CPU. Or, in other words, what is usually called the cycle number for the CPU when somebody talks about bus contention or wait states. The call is designed to be fast (no system-wide sync, no call to **machine.time()**) and is precise. Cycle number for every access is exact at the sub-instruction level. + +The 4510 special nomap line is accessible through **get_nomap()**. + +Other than these specifics, these are perfectly normal CPU classes. + + +General structure of the emulations +----------------------------------- + +Each variant is emulated through up to 4 files: + +- <CPU>.h = header for the CPU class +- <CPU>.c = implementation of most of the CPU class +- d<CPU>.lst = dispatch table for the CPU +- o<CPU>.lst = opcode implementation code for the CPU + +The last two are optional. They're used to generate a **<CPU>.inc** file in the object directory which is included by the .c file. + +At a minimum, the class must include a constructor and an enum picking up the correct input line ids. See m65sc02 for a minimalist example. The header can also include specific configuration macros (see m8502) and also the class can include specific memory accessors (more on these later, simple example in m6504). + +If the CPU has its own dispatch table, the class must also include the declaration (but not definition) of **disasm_entries**, **do_exec_full** and **do_exec_partial**, the declaration and definition of **disasm_disassemble** (identical for all classes but refers to the class-specific **disasm_entries** array) and include the .inc file (which provides the missing definitions). Support for the generation must also be added to CPU.mak. + +If the CPU has in addition its own opcodes, their declaration must be done through a macro, see f.i. m65c02. The .inc file will provide the definitions. + + +Dispatch tables +--------------- + +Each d<CPU>.lst is the dispatch table for the CPU. Lines starting with '#' are comments. The file must include 257 entries, the first 256 being opcodes and the 257th what the CPU should do on reset. In the 6502 irq and nmi actually magically call the "brk" opcode, hence the lack of specific description for them. + +Entries 0 to 255, i.e. the opcodes, must have one of these two structures: + +- opcode_addressing-mode +- opcode_middle_addressing-mode + +Opcode is traditionally a three-character value. Addressing mode must be a 3-letter value corresponding to one of the DASM_* macros in m6502.h. Opcode and addressing mode are used to generate the disassembly table. The full entry text is used in the opcode description file and the dispatching methods, allowing for per-CPU variants for identical-looking opcodes. + +An entry of "." was usable for unimplemented/unknown opcodes, generating "???" in the disassembly, but is not a good idea at this point since it will infloop in execute() if encountered. + + +Opcode descriptions +------------------- + +Each **o<CPU>.lst** file includes the CPU-specific opcodes descriptions. An opcode description is a series of lines starting by an opcode entry by itself and followed by a series of indented lines with code executing the opcode. + +For instance the asl <absolute address> opcode looks like this: + +| asl_aba +| TMP = read_pc(); +| TMP = set_h(TMP, read_pc()); +| TMP2 = read(TMP); +| write(TMP, TMP2); +| TMP2 = do_asl(TMP2); +| write(TMP, TMP2); +| prefetch(); + +First the low part of the address is read, then the high part (**read_pc** is auto-incrementing). Then, now that the address is available the value to shift is read, then re-written (yes, the 6502 does that), shifted then the final result is written (do_asl takes care of the flags). The instruction finishes with a prefetch of the next instruction, as all non-CPU-crashing instructions do. + +Available bus-accessing functions are: + +================= ============================================ +read(adr) standard read +read_direct(adr) read from program space +read_pc() read at the PC address and increment it +read_pc_noinc() read at the PC address +read_9() 6509 indexed-y banked read +write(adr, val) standard write +prefetch() instruction prefetch +prefetch_noirq() instruction prefetch without irq check +================= ============================================ + +Cycle counting is done by the code generator which detects (through string matching) the accesses and generates the appropriate code. In addition to the bus-accessing functions a special line can be used to wait for the next event (irq or whatever). "**eat-all-cycles;**" on a line will do that wait then continue. It is used by wai_imp and stp_imp for the m65c02. + +Due to the constraints of the code generation, some rules have to be followed: + +- in general, stay with one instruction/expression per line + +- there must be no side effects in the parameters of a bus-accessing function + +- local variables lifetime must not go past a bus access. In general, it's better to leave them to helper methods (like **do_asl**) which do not do bus accesses. Note that "TMP" and "TMP2" are not local variables, they're variables of the class. + +- single-line then or else constructs must have braces around them if they're calling a bus-accessing function + +The per-opcode generated code are methods of the CPU class. As such they have complete access to other methods of the class, variables of the class, everything. + + +Memory interface +---------------- + +For better opcode reuse with the MMU/banking variants, a memory access subclass has been created. It's called **memory_interface**, declared in m6502_device, and provides the following accessors: + +==================================== ==================================================== +UINT8 read(UINT16 adr) normal read +UINT8 read_sync(UINT16 adr) opcode read with sync active (first byte of opcode) +UINT8 read_arg(UINT16 adr) opcode read with sync inactive (rest of opcode) +void write(UINT16 adr, UINT8 val) normal write +==================================== ==================================================== + +==================================== ==================================================== +UINT8 read_9(UINT16 adr) special y-indexed 6509 read, defaults to read() +void write_9(UINT16 adr, UINT8 val); special y-indexed 6509 write, defaults to write() +==================================== ==================================================== + +Two implementations are given by default, one usual, **mi_default_normal**, one disabling direct access, **mi_default_nd**. A CPU that wants its own interface (see 6504 or 6509 for instance) must override device_start, intialize mintf there then call **init()**. + + +The generated code +------------------ + +A code generator is used to support interrupting and restarting an instruction in the middle. This is done through a two-level state machine with updates only at the boundaries. More precisely, inst_state tells you which main state you're in. It's equal to the opcode byte when 0-255, and 0xff00 means reset. It's always valid and used by instructions like rmb. inst_substate indicates at which step we are in an instruction, but it set only when an instruction has been interrupted. Let's go back to the asl <abs> code: + +| +| asl_aba +| TMP = read_pc(); +| TMP = set_h(TMP, read_pc()); +| TMP2 = read(TMP); +| write(TMP, TMP2); +| TMP2 = do_asl(TMP2); +| write(TMP, TMP2); +| prefetch(); +| + +The complete generated code is: + +| void m6502_device::asl_aba_partial() +| { +| switch(inst_substate) { +| case 0: +| if(icount == 0) { inst_substate = 1; return; } +| case 1: +| TMP = read_pc(); +| icount--; +| if(icount == 0) { inst_substate = 2; return; } +| case 2: +| TMP = set_h(TMP, read_pc()); +| icount--; +| if(icount == 0) { inst_substate = 3; return; } +| case 3: +| TMP2 = read(TMP); +| icount--; +| if(icount == 0) { inst_substate = 4; return; } +| case 4: +| write(TMP, TMP2); +| icount--; +| TMP2 = do_asl(TMP2); +| if(icount == 0) { inst_substate = 5; return; } +| case 5: +| write(TMP, TMP2); +| icount--; +| if(icount == 0) { inst_substate = 6; return; } +| case 6: +| prefetch(); +| icount--; +| } +| inst_substate = 0; +| } +| + +One can see that the initial switch() restarts the instruction at the appropriate substate, that icount is updated after each access, and upon reaching 0 the instruction is interrupted and the substate updated. Since most instructions are started from the beginning a specific variant is generated for when inst_substate is known to be 0: + +| +| void m6502_device::asl_aba_full() +| { +| if(icount == 0) { inst_substate = 1; return; } +| TMP = read_pc(); +| icount--; +| if(icount == 0) { inst_substate = 2; return; } +| TMP = set_h(TMP, read_pc()); +| icount--; +| if(icount == 0) { inst_substate = 3; return; } +| TMP2 = read(TMP); +| icount--; +| if(icount == 0) { inst_substate = 4; return; } +| write(TMP, TMP2); +| icount--; +| TMP2 = do_asl(TMP2); +| if(icount == 0) { inst_substate = 5; return; } +| write(TMP, TMP2); +| icount--; +| if(icount == 0) { inst_substate = 6; return; } +| prefetch(); +| icount--; +| } +| + +That variant removes the switch, avoiding a costly computed branch and also an inst_substate write. There is in addition a fair chance that the decrement-test with zero pair is compiled into something efficient. + +All these opcode functions are called through two virtual methods, **do_exec_full** and **do_exec_partial**, which are generated into a 257-entry switch statement. Pointers-to-methods being expensive to call, a virtual function implementing a switch has a fair chance of being better. + +The execute main call ends up very simple: + +| void m6502_device::execute_run() +| { +| if(inst_substate) +| do_exec_partial(); +| +| while(icount > 0) { +| if(inst_state < 0x100) { +| PPC = NPC; +| inst_state = IR; +| if(machine().debug_flags & DEBUG_FLAG_ENABLED) +| debugger_instruction_hook(this, NPC); +| } +| do_exec_full(); +| } +| } + +If an instruction was partially executed finish it (icount will then be zero if it still doesn't finish). Then try to run complete instructions. The NPC/IR dance is due to the fact that the 6502 does instruction prefetching, so the instruction PC and opcode come from the prefetch results. + + +Future bus contention/delay slot support +---------------------------------------- + +Supporting bus contention and delay slots in the context of the code generator only requires being able to abort a bus access when not enough cycles are available into icount, and restart it when cycles have become available again. The implementation plan is to: + +- Have a delay() method on the CPU that removes cycles from icount. If icount becomes zero or less, having it throw a **suspend()** exception. + +- Change the code generator to generate this: + +| void m6502_device::asl_aba_partial() +| { +| switch(inst_substate) { +| case 0: +| if(icount == 0) { inst_substate = 1; return; } +| case 1: +| try { +| TMP = read_pc(); +| } catch(suspend) { inst_substate = 1; return; } +| icount--; +| if(icount == 0) { inst_substate = 2; return; } +| case 2: +| try { +| TMP = set_h(TMP, read_pc()); +| } catch(suspend) { inst_substate = 2; return; } +| icount--; +| if(icount == 0) { inst_substate = 3; return; } +| case 3: +| try { +| TMP2 = read(TMP); +| } catch(suspend) { inst_substate = 3; return; } +| icount--; +| if(icount == 0) { inst_substate = 4; return; } +| case 4: +| try { +| write(TMP, TMP2); +| } catch(suspend) { inst_substate = 4; return; } +| icount--; +| TMP2 = do_asl(TMP2); +| if(icount == 0) { inst_substate = 5; return; } +| case 5: +| try { +| write(TMP, TMP2); +| } catch(suspend) { inst_substate = 5; return; } +| icount--; +| if(icount == 0) { inst_substate = 6; return; } +| case 6: +| try { +| prefetch(); +| } catch(suspend) { inst_substate = 6; return; } +| icount--; +| } +| inst_substate = 0; +| } + +A modern try/catch costs nothing if an exception is not thrown. Using this the control will go back to the main loop, which will then look like this: + +| void m6502_device::execute_run() +| { +| if(waiting_cycles) { +| icount -= waiting_cycles; +| waiting_cycles = 0; +| } +| +| if(icount > 0 && inst_substate) +| do_exec_partial(); +| +| while(icount > 0) { +| if(inst_state < 0x100) { +| PPC = NPC; +| inst_state = IR; +| if(machine().debug_flags & DEBUG_FLAG_ENABLED) +| debugger_instruction_hook(this, NPC); +| } +| do_exec_full(); +| } +| +| waiting_cycles = -icount; +| icount = 0; +| } + +A negative icount means that the CPU won't be able to do anything for some time in the future, because it's either waiting for the bus to be free or for a peripheral to answer. These cycles will be counted until elapsed and then normal processing will go on. It's important to note that the exception path only happens when the contention/wait state goes further than the scheduling slice of the CPU. That should not usually be the case, so the cost should be minimal. + +Multi-dispatch variants +----------------------- + +Some variants currently in the process of being supported change instruction set depending on an internal flag, either switching to a 16-bits mode or changing some register accesses to memory accesses. This is handled by having multiple dispatch tables for the CPU, the d<CPU>.lst not being 257 entries anymore but 256*n+1. The variable **inst_state_base** must select which instruction table to use at a given time. It must be a multiple of 256, and is in fact simply OR-ed to the first instruction byte to get the dispatch table index (aka inst_state). + +Current TO-DO: +-------------- + +- Implement the bus contention/wait states stuff, but that requires support on the memory map side first. + +- Integrate the I/O subsystems in the 4510 + +- Possibly integrate the sound subsytem in the n2a03 + +- Add decent hookups for the Apple 3 madness diff --git a/docs/source/techspecs/nscsi.rst b/docs/source/techspecs/nscsi.rst new file mode 100644 index 00000000000..bc907d881c3 --- /dev/null +++ b/docs/source/techspecs/nscsi.rst @@ -0,0 +1,170 @@ +The new SCSI subsystem +====================== + +1. Introduction + +The **nscsi** subsystem was created to allow an implementation to be closer to the physical reality, making it easier (hopefully) to implement new controller chips from the documentations. + + +2. Global structure + +Parallel SCSI is built around a symmetric bus to which a number of devices are connected. The bus is composed of 9 control lines (for +now, later SCSI versions may have more) and up to 32 data lines (but currently implemented chips only handle 8). All the lines are open +collector, which means that either one or multiple chip connect the line to ground and the line, of course, goes to ground, or no chip +drives anything and the line stays at Vcc. Also, the bus uses inverted logic, where ground means 1. SCSI chips traditionally work +in logical and not physical levels, so the nscsi subsystem also works in logical levels and does a logical-or of all the outputs of the +devices. + +Structurally, the implementation is done around two main classes: **nscsi_bus_devices** represents the bus, and **nscsi_device** represents an individual device. A device only communicate with the bus, and the bus takes care of transparently handling the device discovery and communication. In addition the **nscsi_full_device** class proposes a SCSI device with the SCSI protocol implemented making building generic SCSI devices like hard drives or CD-ROM readers easier. + + +3. Plugging in a SCSI bus in a driver + +The nscsi subsystem leverages the slot interfaces and the device naming to allow for a configurable yet simple bus implementation. + +First you need to create a list of acceptable devices to plug on the bus. This usually comprises of **cdrom**, **harddisk** and the controller chip. For instance: + +| +| static SLOT_INTERFACE_START( next_scsi_devices ) +| SLOT_INTERFACE("cdrom", NSCSI_CDROM) +| SLOT_INTERFACE("harddisk", NSCSI_HARDDISK) +| SLOT_INTERFACE_INTERNAL("ncr5390", NCR5390) +| SLOT_INTERFACE_END +| + +The **_INTERNAL** interface indicates a device that is not user-selectable, which is useful for the controller. + +Then in the machine config (or in a fragment config) you need to first add the bus, and then the (potential) devices as sub-devices of the bus with the SCSI ID as the name. For instance you can use: + +| +| MCFG_NSCSI_BUS_ADD("scsibus") +| MCFG_NSCSI_ADD("scsibus:0", next_scsi_devices, "cdrom", 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:1", next_scsi_devices, "harddisk", 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:2", next_scsi_devices, 0, 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:3", next_scsi_devices, 0, 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:4", next_scsi_devices, 0, 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:5", next_scsi_devices, 0, 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:6", next_scsi_devices, 0, 0, 0, 0, false) +| MCFG_NSCSI_ADD("scsibus:7", next_scsi_devices, "ncr5390", 0, &next_ncr5390_interface, 10000000, true) +| + +That configuration puts as default a CD-ROM reader on SCSI ID 0 and a hard drive on SCSI ID 1, and forces the controller on ID 7. The +parameters of add are: + +- device tag, comprised of bus-tag:scsi-id +- the list of acceptable devices +- the device name as per the list, if one is to be there by default +- the device input config, if any (and there usually isn't one) +- the device configuration structure, usually for the controller only +- the frequency, usually for the controller only +- "**false**" for a user-modifiable slot, "**true**" for a fixed slot + +The full device name, for mapping purposes, will be **bus-tag:scsi-id:device-type**, i.e. "*scsibus:7:ncr5390*" for our +controller here. + + +4. Creating a new SCSI device using nscsi_device + +The base class "**nscsi_device**" is to be used for down-to-the-metal devices, i.e. SCSI controller chips. The class provides three +variables and one method. The first variable, **scsi_bus**, is a pointer to the **nscsi_bus_device**. The second, **scsi_refid**, is an opaque reference to pass to the bus on some operations. Finally, **scsi_id** gives the SCSI ID as per the device tag. It's written once at startup and never written or read afterwards, the device can do whatever it wants with the value or the variable. + +The virtual method **scsi_ctrl_changed** is called when watched-for control lines change. Which lines are watched is defined through the bus. + +The bus proposes five methods to access the lines. The read methods are **ctrl_r()** and **data_r()**. The meaning of the control bits are defined in the **S_\*** enum of **nscsi_device**. The bottom three bits (**INP**, **CTL** and **MSG**) are setup so that masking with 7 (**S_PHASE_MASK**) gives the traditional numbers for the phases, which are also available with the **S_PHASE_\*** enum. + +Writing the data lines is done with **data_w(scsi_refid, value)**. + +Writing the control lines is done with **ctrl_w(scsi_refid, value, mask-of-lines-to-change)**. To change all control lines in one call use **S_ALL** as the mask. + +Of course, what is read is the logical-or of all of what is driven by all devices. + +Finally, the method **ctrl_wait_w(scsi_id, value, mask-of-wait-lines-to-change)** allows to select which control lines are +watched. The watch mask is per-device, and the device method **scsi_ctrl_changed** is called whenever a control line in the mask changes due to an action of another device (not itself, to avoid an annoying and somewhat useless recursion). + +Implementing the controller is then just a matter of following the state machines descriptions, at least if they're available. The only +part often not described is the arbitration/selection, which is documented in the SCSI standard though. For an initiator (which is what the controller essentially always is), it goes like this: + +* wait for the bus to be idle +* assert the data line which number is your scsi_id (1 << scsi_id) +* assert the busy line +* wait the arbitration time +* check that the of the active data lines the one with the highest number is yours + + * if no, the arbitration was lost, stop driving anything and restart at the beginning + +* assert the select line (at that point, the bus is yours) +* wait a short while +* keep your data line asserted, assert the data line which number is the SCSI ID of the target +* wait a short while +* assert the atn line if needed, de-assert busy +* wait for busy to be asserted or timeout + + * timeout means nobody is answering at that id, de-assert everything and stop + +* wait a short while for de-skewing +* de-assert the data bus and the select line +* wait a short while + +and then you're done, you're connected with the target until the target de-asserts the busy line, either because you asked it to or just +to annoy you. The de-assert is called a disconnect. + +The **ncr5390** is an example of how to use a two-level state machine to handle all the events. + + +5. Creating a new SCSI device using **nscsi_full_device** + +The base class "**nscsi_full_device**" is used to create HLE-d SCSI devices intended for generic uses, like hard drives, CD-ROMs, perhaps scanners, etc. The class provides the SCSI protocol handling, leaving only the command handling and (optionally) the message handling to the implementation. + +The class currently only support target devices. + +The first method to implement is **scsi_command()**. That method is called when a command has fully arrived. The command is available in **scsi_cmdbuf[]**, and its length is in **scsi_cmdsize** (but the length is generally useless, the command first byte giving it). The 4096-bytes **scsi_cmdbuf** array is then freely modifiable. + +In **scsi_command()**, the device can either handle the command or pass it up with **nscsi_full_device::scsi_command()**. + +To handle the command, a number of methods are available: + +- **get_lun(lua-set-in-command)** will give you the LUN to work on (the in-command one can be overriden by a message-level one). + +- **bad_lun()** replies to the host that the specific LUN is unsupported. + +- **scsi_data_in(buffer-id, size)** sends size bytes from buffer *buffer-id* + +- **scsi_data_out(buffer-id, size)** receives size bytes into buffer *buffer-id* + +- **scsi_status_complete(status)** ends the command with a given status. + +- **sense(deferred, key)** prepares the sense buffer for a subsequent request-sense command, which is useful when returning a check-condition status. + +The **scsi_data_\*** and **scsi_status_complete** commands are queued, the command handler should call them all without waiting. + +*buffer-id* identifies a buffer. 0, aka **SBUF_MAIN**, targets the **scsi_cmdbuf** buffer. Other acceptable values are 2 or more. 2+ ids are handled through the **scsi_get_data** method for read and **scsi_put_data** for write. + +**UINT8 device::scsi_get_data(int id, int pos)** must return byte pos of buffer id, upcalling in nscsi_full_device for id < 2. + +**void device::scsi_put_data(int id, int pos, UINT8 data)** must write byte pos in buffer id, upcalling in nscsi_full_device for id < 2. + +**scsi_get_data** and **scsi_put_data** should do the external reads/writes when needed. + +The device can also override **scsi_message** to handle SCSI messages other than the ones generically handled, and it can also override some of the timings (but a lot of them aren't used, beware). + +A number of enums are defined to make things easier. The **SS_\*** enum gives status returns (with **SS_GOOD** for all's well). The **SC_\*** enum gives the scsi commands. The **SM_\*** enum gives the SCSI messages, with the exception of identify (which is **80-ff**, doesn't really fit in an enum). + + +6. What's missing +6.1. What's missing in **scsi_full_device** + +Initiator support - we have no initiator device to HLE at that point. + +Delays - a scsi_delay command would help giving more realistic timings to the CD-ROM reader in particular. + +Disconnected operation - would first require delays and in addition an emulated OS that can handle it. + +16-bits wide operation - needs an OS and an initiator that can handle it. + + +6.2. What's missing in the ncr5390 (and probably future other controllers) + +Bus free detection. Right now the bus is considered free if the controllers isn't using it, which is true. This may change once +disconnected operation is in. + +Target commands, we don't emulate (vs. HLE) any target yet. |