2012-08-05 11:09:22 +02:00
|
|
|
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.
|