mirror of
https://github.com/libretro/mame.git
synced 2024-12-13 21:31:24 +00:00
nscsi: documentation [O. Galibert]
This commit is contained in:
parent
dfd3712f0a
commit
ebf2aac078
234
docs/nscsi.txt
234
docs/nscsi.txt
@ -0,0 +1,234 @@
|
||||
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 harddrives or cdrom 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 subdevices 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 cdrom 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-modifyable 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, deassert busy
|
||||
- wait for busy to be asserted or timeout
|
||||
- timeout means nobody is answering at that id, deassert everything and stop
|
||||
- wait a short while for deskewing
|
||||
- deassert 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 deasserts the busy line, either because you asked it to or just
|
||||
to annoy you. The deassert 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, cdroms, 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) recieves 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 cdrom 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.
|
Loading…
Reference in New Issue
Block a user