mirror of
https://github.com/phabrics/Run-Sun3-SunOS-4.1.1.git
synced 2026-04-29 11:02:59 -04:00
945 lines
29 KiB
C
945 lines
29 KiB
C
/* $Id: scsi-device.c,v 1.8 2009/11/08 17:17:42 fredette Exp $ */
|
|
|
|
/* scsi/scsi-device.c - implementation of generic SCSI device support: */
|
|
|
|
/*
|
|
* Copyright (c) 2003 Matt Fredette
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Matt Fredette.
|
|
* 4. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <tme/common.h>
|
|
_TME_RCSID("$Id: scsi-device.c,v 1.8 2009/11/08 17:17:42 fredette Exp $");
|
|
|
|
/* includes: */
|
|
#include <tme/scsi/scsi-device.h>
|
|
#include <tme/scsi/scsi-msg.h>
|
|
#include <tme/scsi/scsi-cdb.h>
|
|
|
|
/* macros: */
|
|
|
|
/* the callout flags: */
|
|
#define TME_SCSI_DEVICE_CALLOUT_CHECK (0)
|
|
#define TME_SCSI_DEVICE_CALLOUT_RUNNING TME_BIT(0)
|
|
#define TME_SCSI_DEVICE_CALLOUTS_MASK (-2)
|
|
#define TME_SCSI_DEVICE_CALLOUT_CYCLE TME_BIT(1)
|
|
|
|
/* the SCSI device callout function. it must be called with the mutex locked: */
|
|
static void
|
|
_tme_scsi_device_callout(struct tme_scsi_device *scsi_device,
|
|
int new_callouts)
|
|
{
|
|
struct tme_scsi_connection *conn_scsi;
|
|
int callouts, later_callouts;
|
|
int rc;
|
|
tme_uint32_t events;
|
|
tme_uint32_t actions;
|
|
const struct tme_scsi_dma *dma;
|
|
struct tme_scsi_dma dma_buffer;
|
|
|
|
/* add in any new callouts: */
|
|
scsi_device->tme_scsi_device_callout_flags |= new_callouts;
|
|
|
|
/* if this function is already running in another thread, simply
|
|
return now. the other thread will do our work: */
|
|
if (scsi_device->tme_scsi_device_callout_flags
|
|
& TME_SCSI_DEVICE_CALLOUT_RUNNING) {
|
|
return;
|
|
}
|
|
|
|
/* callouts are now running: */
|
|
scsi_device->tme_scsi_device_callout_flags
|
|
|= TME_SCSI_DEVICE_CALLOUT_RUNNING;
|
|
|
|
/* assume that we won't need any later callouts: */
|
|
later_callouts = 0;
|
|
|
|
/* loop while callouts are needed: */
|
|
for (; ((callouts
|
|
= scsi_device->tme_scsi_device_callout_flags)
|
|
& TME_SCSI_DEVICE_CALLOUTS_MASK); ) {
|
|
|
|
/* clear the needed callouts: */
|
|
scsi_device->tme_scsi_device_callout_flags
|
|
= (callouts
|
|
& ~TME_SCSI_DEVICE_CALLOUTS_MASK);
|
|
callouts
|
|
&= TME_SCSI_DEVICE_CALLOUTS_MASK;
|
|
|
|
/* get this card's SCSI connection: */
|
|
conn_scsi = scsi_device->tme_scsi_device_connection;
|
|
|
|
/* if we need to call out a SCSI bus cycle: */
|
|
if (callouts & TME_SCSI_DEVICE_CALLOUT_CYCLE) {
|
|
|
|
/* if the bus is busy: */
|
|
if (scsi_device->tme_scsi_device_control
|
|
& TME_SCSI_SIGNAL_BSY) {
|
|
|
|
/* run a target information transfer phase DMA sequence: */
|
|
events = TME_SCSI_EVENT_NONE;
|
|
actions = TME_SCSI_ACTION_DMA_TARGET;
|
|
dma_buffer = scsi_device->tme_scsi_device_dma;
|
|
dma = &dma_buffer;
|
|
}
|
|
|
|
/* otherwise, the bus is not busy: */
|
|
else {
|
|
|
|
/* wait to be selected: */
|
|
events = TME_SCSI_EVENT_SELECTED | TME_SCSI_EVENT_IDS_SELF(TME_BIT(scsi_device->tme_scsi_device_id));
|
|
actions = TME_SCSI_ACTION_RESPOND_SELECTED;
|
|
dma = NULL;
|
|
}
|
|
|
|
/* unlock the mutex: */
|
|
tme_mutex_unlock(&scsi_device->tme_scsi_device_mutex);
|
|
|
|
/* do the callout: */
|
|
rc = (conn_scsi != NULL
|
|
? ((*conn_scsi->tme_scsi_connection_cycle)
|
|
(conn_scsi,
|
|
scsi_device->tme_scsi_device_control,
|
|
0,
|
|
events,
|
|
actions,
|
|
dma))
|
|
: TME_OK);
|
|
|
|
/* lock the mutex: */
|
|
tme_mutex_lock(&scsi_device->tme_scsi_device_mutex);
|
|
|
|
/* if the callout was unsuccessful, remember that at some later
|
|
time this callout should be attempted again: */
|
|
if (rc != TME_OK) {
|
|
later_callouts |= TME_SCSI_DEVICE_CALLOUT_CYCLE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* put in any later callouts, and clear that callouts are running: */
|
|
scsi_device->tme_scsi_device_callout_flags = later_callouts;
|
|
}
|
|
|
|
/* when a device is the target, this enters a new bus phase: */
|
|
void
|
|
tme_scsi_device_target_phase(struct tme_scsi_device *scsi_device,
|
|
tme_scsi_control_t control)
|
|
{
|
|
const char *info_type;
|
|
|
|
/* set the phase: */
|
|
scsi_device->tme_scsi_device_control = control;
|
|
|
|
/* if we are not freeing the bus: */
|
|
if (control & TME_SCSI_SIGNAL_BSY) {
|
|
|
|
/* assume that we will not dump the information transferred: */
|
|
info_type = NULL;
|
|
|
|
/* XXX parity? */
|
|
|
|
/* dispatch on the phase: */
|
|
switch (TME_SCSI_PHASE(control)) {
|
|
|
|
/* for the DATA_OUT and DATA_IN phases, we assume that
|
|
the caller has already set up the DMA structure: */
|
|
case TME_SCSI_PHASE_DATA_IN:
|
|
info_type = "DATA_IN";
|
|
/* FALLTHROUGH */
|
|
case TME_SCSI_PHASE_DATA_OUT:
|
|
break;
|
|
|
|
/* for the COMMAND phase, begin by transferring the
|
|
first byte of the CDB: */
|
|
case TME_SCSI_PHASE_COMMAND:
|
|
assert ((scsi_device->tme_scsi_device_dma.tme_scsi_dma_flags
|
|
& TME_SCSI_DMA_WIDTH)
|
|
== TME_SCSI_DMA_8BIT);
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_in
|
|
= &scsi_device->tme_scsi_device_cdb[0];
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 1;
|
|
break;
|
|
|
|
/* for the STATUS phase, transfer the status byte: */
|
|
case TME_SCSI_PHASE_STATUS:
|
|
assert ((scsi_device->tme_scsi_device_dma.tme_scsi_dma_flags
|
|
& TME_SCSI_DMA_WIDTH)
|
|
== TME_SCSI_DMA_8BIT);
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_out
|
|
= &scsi_device->tme_scsi_device_status;
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 1;
|
|
info_type = "STATUS";
|
|
break;
|
|
|
|
/* for the MESSAGE_OUT phase, begin by transferring the
|
|
first byte of the message: */
|
|
case TME_SCSI_PHASE_MESSAGE_OUT:
|
|
assert ((scsi_device->tme_scsi_device_dma.tme_scsi_dma_flags
|
|
& TME_SCSI_DMA_WIDTH)
|
|
== TME_SCSI_DMA_8BIT);
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_in
|
|
= &scsi_device->tme_scsi_device_msg[0];
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 1;
|
|
break;
|
|
|
|
/* for the MESSAGE_IN phase, transfer the entire message: */
|
|
case TME_SCSI_PHASE_MESSAGE_IN:
|
|
assert ((scsi_device->tme_scsi_device_dma.tme_scsi_dma_flags
|
|
& TME_SCSI_DMA_WIDTH)
|
|
== TME_SCSI_DMA_8BIT);
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_out
|
|
= &scsi_device->tme_scsi_device_msg[0];
|
|
|
|
/* the total message length depends on the message type: */
|
|
|
|
/* "The extended message length specifies the length in bytes of
|
|
the extended message code plus the extended message arguments
|
|
to follow. Therefore, the total length of the message is
|
|
equal to the extended message length plus two. A value of
|
|
zero for the extended message length indicates 256 bytes
|
|
follow." */
|
|
if (scsi_device->tme_scsi_device_msg[0]
|
|
== TME_SCSI_MSG_EXTENDED) {
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= ((scsi_device->tme_scsi_device_msg[1]
|
|
== 0)
|
|
? (2 + 256)
|
|
: (2 + scsi_device->tme_scsi_device_msg[1]));
|
|
}
|
|
|
|
else if (TME_SCSI_MSG_IS_2(scsi_device->tme_scsi_device_msg[0])) {
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 2;
|
|
}
|
|
|
|
else {
|
|
assert (TME_SCSI_MSG_IS_1(scsi_device->tme_scsi_device_msg[0]));
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 1;
|
|
}
|
|
info_type = "MESSAGE_IN";
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
/* if we can, dump up to 128 bytes of information: */
|
|
if (info_type != NULL) {
|
|
tme_log_start(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
2000, TME_OK) {
|
|
unsigned int byte_i, count;
|
|
count = TME_MIN(scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid,
|
|
128);
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
"%s:", info_type);
|
|
for (byte_i = 0; byte_i < count ; byte_i++) {
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
" 0x%02x",
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_out[byte_i]);
|
|
}
|
|
if (count < scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid) {
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
" ...");
|
|
}
|
|
} tme_log_finish(&scsi_device->tme_scsi_device_element->tme_element_log_handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* this handles a SCSI bus cycle: */
|
|
static int
|
|
_tme_scsi_device_cycle(struct tme_scsi_connection *conn_scsi,
|
|
tme_scsi_control_t control_new,
|
|
tme_scsi_data_t data,
|
|
tme_uint32_t events,
|
|
tme_uint32_t actions,
|
|
const struct tme_scsi_dma *dma)
|
|
{
|
|
struct tme_scsi_device *scsi_device;
|
|
int new_callouts;
|
|
tme_scsi_control_t control_old;
|
|
unsigned long count;
|
|
void (*do_cdb) _TME_P((struct tme_scsi_device *,
|
|
tme_scsi_control_t,
|
|
tme_scsi_control_t));
|
|
|
|
/* recover our device: */
|
|
scsi_device = conn_scsi->tme_scsi_connection.tme_connection_element->tme_element_private;
|
|
|
|
/* assume that we won't need any new callouts: */
|
|
new_callouts = 0;
|
|
|
|
/* lock the mutex: */
|
|
tme_mutex_lock(&scsi_device->tme_scsi_device_mutex);
|
|
|
|
/* get the last bus state: */
|
|
control_old = scsi_device->tme_scsi_device_control;
|
|
|
|
/* if we last knew the bus to be free, we must now be selected: */
|
|
if (!(control_old
|
|
& TME_SCSI_SIGNAL_BSY)) {
|
|
|
|
/* "SCSI devices indicate their ability to accommodate more than
|
|
the COMMAND COMPLETE message by asserting or responding to the
|
|
ATN signal. The initiator indicates this in the SELECTION
|
|
phase by asserting ATN prior to the SCSI bus condition of SEL
|
|
true, and BSY false. The target indicates its ability to
|
|
accommodate more messages by responding to the ATTENTION
|
|
condition with the MESSAGE OUT phase after going through the
|
|
SELECTION phase." */
|
|
|
|
/* XXX parity? */
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_flags
|
|
= TME_SCSI_DMA_8BIT;
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
TME_SCSI_SIGNAL_BSY
|
|
| ((control_new
|
|
& TME_SCSI_SIGNAL_ATN)
|
|
? TME_SCSI_PHASE_MESSAGE_OUT
|
|
: TME_SCSI_PHASE_COMMAND));
|
|
|
|
/* no LUN has been addressed yet: */
|
|
scsi_device->tme_scsi_device_addressed_lun
|
|
= -1;
|
|
}
|
|
|
|
/* otherwise, we had the bus in some phase: */
|
|
else {
|
|
|
|
/* if we didn't transfer all of the bytes we wanted, something
|
|
went wrong with the initiator - probably the bus was reset: */
|
|
assert (dma != NULL);
|
|
scsi_device->tme_scsi_device_dma = *dma;
|
|
if (scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid > 0) {
|
|
|
|
/* return to the bus-free phase: */
|
|
scsi_device->tme_scsi_device_control
|
|
= 0;
|
|
|
|
/* log the forced bus free: */
|
|
tme_log(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
1000, TME_OK,
|
|
(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
_("short transfer, control now 0x%04x, forced bus free"),
|
|
control_new));
|
|
|
|
/* XXX callback? */
|
|
}
|
|
|
|
/* otherwise, we did transfer all of the bytes we wanted: */
|
|
else {
|
|
|
|
/* dispatch on the previous bus phase: */
|
|
switch (TME_SCSI_PHASE(control_old)) {
|
|
case TME_SCSI_PHASE_MESSAGE_OUT:
|
|
|
|
/* calculate how many message bytes we have transferred: */
|
|
count = (scsi_device->tme_scsi_device_dma.tme_scsi_dma_in
|
|
- &scsi_device->tme_scsi_device_msg[0]);
|
|
|
|
/* dispatch on the first byte of the message: */
|
|
|
|
/* if this is an extended message: */
|
|
if (scsi_device->tme_scsi_device_msg[0]
|
|
== TME_SCSI_MSG_EXTENDED) {
|
|
|
|
/* if we have only received the first message byte, receive
|
|
the next byte, which will give us the total length of the
|
|
message: */
|
|
if (count == 1) {
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 1;
|
|
}
|
|
|
|
/* if we have received the second message byte, we now know
|
|
the total length of the message: */
|
|
else if (count == 2) {
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= ((scsi_device->tme_scsi_device_msg[1]
|
|
== 0)
|
|
? 256
|
|
: scsi_device->tme_scsi_device_msg[1]);
|
|
}
|
|
|
|
/* otherwise, we must have received all of the message: */
|
|
}
|
|
|
|
/* if this is a two-byte message: */
|
|
else if (TME_SCSI_MSG_IS_2(scsi_device->tme_scsi_device_msg[0])) {
|
|
|
|
/* if we have only received the first message byte,
|
|
receive the second message byte: */
|
|
if (count == 1) {
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= 1;
|
|
}
|
|
|
|
/* otherwise, we must have received all of the message: */
|
|
}
|
|
|
|
/* otherwise, this must be a one-byte message, and we have
|
|
received all of it: */
|
|
else {
|
|
assert (TME_SCSI_MSG_IS_1(scsi_device->tme_scsi_device_msg[0]));
|
|
}
|
|
|
|
/* if we have received all of the message: */
|
|
if (scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid == 0) {
|
|
|
|
/* log the message: */
|
|
tme_log_start(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
1000, TME_OK) {
|
|
unsigned int byte_i;
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
_("MESSAGE_OUT:"));
|
|
for (byte_i = 0; byte_i < count ; byte_i++) {
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
" 0x%02x",
|
|
scsi_device->tme_scsi_device_msg[byte_i]);
|
|
}
|
|
} tme_log_finish(&scsi_device->tme_scsi_device_element->tme_element_log_handle);
|
|
|
|
/* "Normally, the initiator negates ATN while REQ is true
|
|
and ACK is false during the last REQ/ACK handshake of the
|
|
MESSAGE OUT phase." */
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
TME_SCSI_SIGNAL_BSY
|
|
| ((control_new
|
|
& TME_SCSI_SIGNAL_ATN)
|
|
? TME_SCSI_PHASE_MESSAGE_OUT
|
|
: TME_SCSI_PHASE_COMMAND));
|
|
|
|
/* call out for the message: */
|
|
(*((scsi_device->tme_scsi_device_msg[0]
|
|
== TME_SCSI_MSG_EXTENDED)
|
|
? (scsi_device->tme_scsi_device_do_msg_ext
|
|
[scsi_device->tme_scsi_device_msg[2]])
|
|
: (scsi_device->tme_scsi_device_do_msg
|
|
[TME_MIN(scsi_device->tme_scsi_device_msg[0],
|
|
TME_SCSI_MSG_IDENTIFY)])))
|
|
(scsi_device,
|
|
control_old,
|
|
control_new);
|
|
}
|
|
|
|
break;
|
|
|
|
case TME_SCSI_PHASE_COMMAND:
|
|
|
|
/* calculate how many message bytes we have transferred: */
|
|
count = (scsi_device->tme_scsi_device_dma.tme_scsi_dma_in
|
|
- &scsi_device->tme_scsi_device_cdb[0]);
|
|
|
|
/* if we have only transferred the first byte: */
|
|
if (count == 1) {
|
|
|
|
/* dispatch on the CDB group: */
|
|
switch (scsi_device->tme_scsi_device_cdb[0]
|
|
& TME_SCSI_CDB_GROUP_MASK) {
|
|
|
|
case TME_SCSI_CDB_GROUP_0:
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= TME_SCSI_CDB_GROUP_0_LEN;
|
|
break;
|
|
case TME_SCSI_CDB_GROUP_1:
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= TME_SCSI_CDB_GROUP_1_LEN;
|
|
break;
|
|
case TME_SCSI_CDB_GROUP_2:
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= TME_SCSI_CDB_GROUP_2_LEN;
|
|
break;
|
|
case TME_SCSI_CDB_GROUP_4:
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= TME_SCSI_CDB_GROUP_4_LEN;
|
|
break;
|
|
case TME_SCSI_CDB_GROUP_5:
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid
|
|
= TME_SCSI_CDB_GROUP_5_LEN;
|
|
break;
|
|
|
|
case TME_SCSI_CDB_GROUP_3:
|
|
case TME_SCSI_CDB_GROUP_6:
|
|
case TME_SCSI_CDB_GROUP_7:
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
/* we have already transferred the first byte of the CDB: */
|
|
scsi_device->tme_scsi_device_dma.tme_scsi_dma_resid--;
|
|
break;
|
|
}
|
|
|
|
/* otherwise, we must have transferred all of the command: */
|
|
else {
|
|
|
|
/* log the CDB: */
|
|
tme_log_start(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
1000, TME_OK) {
|
|
unsigned int byte_i;
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
_("CDB:"));
|
|
for (byte_i = 0; byte_i < count ; byte_i++) {
|
|
tme_log_part(&scsi_device->tme_scsi_device_element->tme_element_log_handle,
|
|
" 0x%02x",
|
|
scsi_device->tme_scsi_device_cdb[byte_i]);
|
|
}
|
|
} tme_log_finish(&scsi_device->tme_scsi_device_element->tme_element_log_handle);
|
|
|
|
/* if the addressed LUN is valid: */
|
|
if (((*scsi_device->tme_scsi_device_address_lun)
|
|
(scsi_device))
|
|
== TME_OK) {
|
|
|
|
/* if this command is illegal: */
|
|
do_cdb
|
|
= (scsi_device->tme_scsi_device_do_cdb
|
|
[scsi_device->tme_scsi_device_cdb[0]]);
|
|
if (__tme_predict_false(do_cdb == NULL)) {
|
|
|
|
/* use the illegal command handler: */
|
|
do_cdb = tme_scsi_device_cdb_illegal;
|
|
}
|
|
|
|
/* call out for the CDB: */
|
|
(*do_cdb)
|
|
(scsi_device,
|
|
control_old,
|
|
control_new);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
/* all of these phases just cause phase callbacks: */
|
|
case TME_SCSI_PHASE_STATUS:
|
|
case TME_SCSI_PHASE_MESSAGE_IN:
|
|
case TME_SCSI_PHASE_DATA_OUT:
|
|
case TME_SCSI_PHASE_DATA_IN:
|
|
(*scsi_device->tme_scsi_device_phase)
|
|
(scsi_device,
|
|
control_old,
|
|
control_new);
|
|
break;
|
|
|
|
default: abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* call out a new SCSI cycle: */
|
|
new_callouts |= TME_SCSI_DEVICE_CALLOUT_CYCLE;
|
|
|
|
/* make any needed callouts: */
|
|
_tme_scsi_device_callout(scsi_device, new_callouts);
|
|
|
|
/* unlock the mutex: */
|
|
tme_mutex_unlock(&scsi_device->tme_scsi_device_mutex);
|
|
|
|
return (TME_OK);
|
|
}
|
|
|
|
/* when a device is the target, this frees the bus: */
|
|
_TME_SCSI_DEVICE_PHASE_DECL(tme_scsi_device_target_f)
|
|
{
|
|
/* enter the BUS FREE phase: */
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
0);
|
|
|
|
/* there is no next phase we will enter: */
|
|
scsi_device->tme_scsi_device_phase
|
|
= NULL;
|
|
}
|
|
|
|
/* when a device is the target, this runs the MESSAGE IN phase,
|
|
followed by the BUS FREE phase. the transmitted message must be
|
|
preset: */
|
|
_TME_SCSI_DEVICE_PHASE_DECL(tme_scsi_device_target_mf)
|
|
{
|
|
/* enter the MESSAGE IN phase: */
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
TME_SCSI_SIGNAL_BSY
|
|
| TME_SCSI_PHASE_MESSAGE_IN);
|
|
|
|
/* the next phase we enter will be the BUS FREE phase: */
|
|
scsi_device->tme_scsi_device_phase
|
|
= tme_scsi_device_target_f;
|
|
}
|
|
|
|
/* when a device is the target, this runs the STATUS phase, followed
|
|
by the MESSAGE IN phase, followed by the BUS FREE phase. the
|
|
transmitted status and message must be preset: */
|
|
_TME_SCSI_DEVICE_PHASE_DECL(tme_scsi_device_target_smf)
|
|
{
|
|
/* enter the STATUS phase: */
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
TME_SCSI_SIGNAL_BSY
|
|
| TME_SCSI_PHASE_STATUS);
|
|
|
|
/* the next phase we enter will be the MESSAGE IN phase: */
|
|
scsi_device->tme_scsi_device_phase
|
|
= tme_scsi_device_target_mf;
|
|
}
|
|
|
|
/* when a device is the target, this runs the DATA IN or DATA OUT
|
|
phase, followed by the STATUS phase, followed by the MESSAGE IN
|
|
phase, followed by the BUS FREE phase. the transmitted data,
|
|
status and message must be preset: */
|
|
_TME_SCSI_DEVICE_PHASE_DECL(tme_scsi_device_target_dsmf)
|
|
{
|
|
/* enter the DATA IN or DATA OUT phase, depending on how
|
|
DMA was set up: */
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
TME_SCSI_SIGNAL_BSY
|
|
| ((scsi_device->tme_scsi_device_dma.tme_scsi_dma_in
|
|
!= NULL)
|
|
? TME_SCSI_PHASE_DATA_OUT
|
|
: TME_SCSI_PHASE_DATA_IN));
|
|
|
|
/* the next phase we enter will be the STATUS phase: */
|
|
scsi_device->tme_scsi_device_phase
|
|
= tme_scsi_device_target_smf;
|
|
}
|
|
|
|
/* when a device is the target, this runs a MESSAGE IN phase, followed
|
|
usually by a COMMAND phase (but possibly a MESSAGE OUT phase): */
|
|
_TME_SCSI_DEVICE_PHASE_DECL(tme_scsi_device_target_mc)
|
|
{
|
|
|
|
/* enter either the MESSAGE OUT phase or the COMMAND phase: */
|
|
tme_scsi_device_target_phase(scsi_device,
|
|
TME_SCSI_SIGNAL_BSY
|
|
| ((control_new
|
|
& TME_SCSI_SIGNAL_ATN)
|
|
? TME_SCSI_PHASE_MESSAGE_OUT
|
|
: TME_SCSI_PHASE_COMMAND));
|
|
|
|
#ifndef NDEBUG
|
|
/* both the MESSAGE OUT and COMMAND phases have specific
|
|
dispatchers: */
|
|
scsi_device->tme_scsi_device_phase = NULL;
|
|
#endif /* !NDEBUG */
|
|
}
|
|
|
|
/* when a device is the target, this builds a simple extended sense
|
|
and returns an immediate CHECK CONDITION status to the initiator: */
|
|
void
|
|
tme_scsi_device_check_condition(struct tme_scsi_device *scsi_device,
|
|
tme_uint8_t sense_key,
|
|
tme_uint16_t sense_asc_ascq)
|
|
{
|
|
struct tme_scsi_device_sense *sense;
|
|
int lun;
|
|
|
|
/* get the addressed LUN: */
|
|
lun = scsi_device->tme_scsi_device_addressed_lun;
|
|
|
|
/* this target must support extended sense: */
|
|
assert (!scsi_device->tme_scsi_device_sense_no_extended);
|
|
|
|
/* form the sense: */
|
|
sense = &scsi_device->tme_scsi_device_sense[lun];
|
|
|
|
/* the error class and error code: */
|
|
sense->tme_scsi_device_sense_data[0]
|
|
= 0x70;
|
|
|
|
/* the sense key: */
|
|
sense->tme_scsi_device_sense_data[2]
|
|
= sense_key;
|
|
|
|
/* if there is no additional sense: */
|
|
if (sense_asc_ascq == TME_SCSI_SENSE_EXT_ASC_ASCQ_NONE) {
|
|
|
|
/* there is no additional sense length: */
|
|
sense->tme_scsi_device_sense_data[7] = 0x00;
|
|
}
|
|
|
|
/* otherwise, there is additional sense: */
|
|
else {
|
|
|
|
/* the additional sense length: */
|
|
sense->tme_scsi_device_sense_data[7]
|
|
= 0x06;
|
|
|
|
/* the additional sense code and additional sense code qualifier: */
|
|
sense->tme_scsi_device_sense_data[12] = (sense_asc_ascq >> 8);
|
|
sense->tme_scsi_device_sense_data[13] = sense_asc_ascq;
|
|
}
|
|
|
|
/* this sense is valid: */
|
|
sense->tme_scsi_device_sense_valid
|
|
= TRUE;
|
|
|
|
/* return the CHECK CONDITION status: */
|
|
tme_scsi_device_target_do_smf(scsi_device,
|
|
TME_SCSI_STATUS_CHECK_CONDITION,
|
|
TME_SCSI_MSG_CMD_COMPLETE);
|
|
}
|
|
|
|
/* this is the LUN addresser for LUN-aware devices: */
|
|
int
|
|
tme_scsi_device_address_lun_aware(struct tme_scsi_device *scsi_device)
|
|
{
|
|
struct tme_scsi_device_sense *sense;
|
|
int lun;
|
|
|
|
/* if an IDENTIFY message was sent, use that LUN: */
|
|
lun = scsi_device->tme_scsi_device_addressed_lun;
|
|
|
|
/* otherwise, get the LUN from bits 5-7 of the second
|
|
CDB byte: */
|
|
if (lun < 0) {
|
|
lun = (scsi_device->tme_scsi_device_cdb[1] >> 5);
|
|
scsi_device->tme_scsi_device_addressed_lun = lun;
|
|
}
|
|
|
|
/* if this LUN is not defined, and this isn't a REQUEST SENSE
|
|
or INQUIRY command: */
|
|
if (!(scsi_device->tme_scsi_device_luns
|
|
& TME_BIT(lun))
|
|
&& (scsi_device->tme_scsi_device_cdb[0]
|
|
!= TME_SCSI_CDB_REQUEST_SENSE)
|
|
&& (scsi_device->tme_scsi_device_cdb[0]
|
|
!= TME_SCSI_CDB_INQUIRY)) {
|
|
|
|
/* this target must support extended sense: */
|
|
assert (!scsi_device->tme_scsi_device_sense_no_extended);
|
|
|
|
/* form the ILLEGAL REQUEST sense: */
|
|
sense = &scsi_device->tme_scsi_device_sense[lun];
|
|
|
|
/* the error class and error code: */
|
|
sense->tme_scsi_device_sense_data[0]
|
|
= 0x70;
|
|
|
|
/* the ILLEGAL REQUEST sense key: */
|
|
sense->tme_scsi_device_sense_data[2]
|
|
= 0x05;
|
|
|
|
/* the additional sense length: */
|
|
sense->tme_scsi_device_sense_data[7]
|
|
= 0x00;
|
|
|
|
sense->tme_scsi_device_sense_valid
|
|
= TRUE;
|
|
|
|
/* return the CHECK CONDITION status: */
|
|
tme_scsi_device_target_do_smf(scsi_device,
|
|
TME_SCSI_STATUS_CHECK_CONDITION,
|
|
TME_SCSI_MSG_CMD_COMPLETE);
|
|
return (EINVAL);
|
|
}
|
|
|
|
return (TME_OK);
|
|
}
|
|
|
|
/* this is the LUN addresser for LUN-unaware devices: */
|
|
int
|
|
tme_scsi_device_address_lun_unaware(struct tme_scsi_device *scsi_device)
|
|
{
|
|
|
|
/* we always force a LUN of zero: */
|
|
scsi_device->tme_scsi_device_addressed_lun = 0;
|
|
|
|
return (tme_scsi_device_address_lun_aware(scsi_device));
|
|
}
|
|
|
|
/* this makes a new connection: */
|
|
int
|
|
tme_scsi_device_connection_make(struct tme_connection *conn,
|
|
unsigned int state)
|
|
{
|
|
struct tme_scsi_device *scsi_device;
|
|
struct tme_scsi_connection *conn_scsi;
|
|
|
|
/* recover our device: */
|
|
scsi_device = conn->tme_connection_element->tme_element_private;
|
|
|
|
/* both sides must be SCSI connections: */
|
|
assert (conn->tme_connection_type
|
|
== TME_CONNECTION_SCSI);
|
|
assert (conn->tme_connection_other->tme_connection_type
|
|
== TME_CONNECTION_SCSI);
|
|
|
|
/* simple SCSI devices are always set up to answer calls across the
|
|
connection, so we only have to do work when the connection has
|
|
gone full, namely taking the other side of the connection: */
|
|
if (state == TME_CONNECTION_FULL) {
|
|
|
|
/* lock the mutex: */
|
|
tme_mutex_lock(&scsi_device->tme_scsi_device_mutex);
|
|
|
|
/* save our connection: */
|
|
conn_scsi
|
|
= ((struct tme_scsi_connection *)
|
|
conn->tme_connection_other);
|
|
scsi_device->tme_scsi_device_connection
|
|
= conn_scsi;
|
|
|
|
/* call out a SCSI bus cycle: */
|
|
scsi_device->tme_scsi_device_control = 0;
|
|
_tme_scsi_device_callout(scsi_device,
|
|
TME_SCSI_DEVICE_CALLOUT_CYCLE);
|
|
|
|
/* unlock the mutex: */
|
|
tme_mutex_unlock(&scsi_device->tme_scsi_device_mutex);
|
|
}
|
|
|
|
return (TME_OK);
|
|
}
|
|
|
|
/* this breaks a connection: */
|
|
int
|
|
tme_scsi_device_connection_break(struct tme_connection *conn, unsigned int state)
|
|
{
|
|
abort();
|
|
}
|
|
|
|
/* this makes a new connection side for a SCSI device: */
|
|
int
|
|
tme_scsi_device_connections_new(struct tme_element *element,
|
|
const char * const *args,
|
|
struct tme_connection **_conns,
|
|
char **_output)
|
|
{
|
|
struct tme_scsi_device *scsi_device;
|
|
struct tme_scsi_connection *conn_scsi;
|
|
struct tme_connection *conn;
|
|
|
|
/* recover our device: */
|
|
scsi_device = (struct tme_scsi_device *) element->tme_element_private;
|
|
|
|
/* if this device isn't connected to a SCSI bus, make the new
|
|
connection side: */
|
|
if (scsi_device->tme_scsi_device_connection == NULL) {
|
|
|
|
/* create our side of a SCSI connection: */
|
|
conn_scsi = tme_new0(struct tme_scsi_connection, 1);
|
|
conn = &conn_scsi->tme_scsi_connection;
|
|
|
|
/* fill in the generic connection: */
|
|
conn->tme_connection_next = *_conns;
|
|
conn->tme_connection_type = TME_CONNECTION_SCSI;
|
|
conn->tme_connection_score = tme_scsi_connection_score;
|
|
conn->tme_connection_make = tme_scsi_device_connection_make;
|
|
conn->tme_connection_break = tme_scsi_device_connection_break;
|
|
|
|
/* fill in the SCSI connection: */
|
|
conn_scsi->tme_scsi_connection_cycle
|
|
= _tme_scsi_device_cycle;
|
|
|
|
/* return the connection side possibility: */
|
|
*_conns = conn;
|
|
}
|
|
|
|
return (TME_OK);
|
|
}
|
|
|
|
/* this initializes a new SCSI device: */
|
|
int
|
|
tme_scsi_device_new(struct tme_scsi_device *scsi_device,
|
|
int id)
|
|
{
|
|
int i;
|
|
|
|
/* initialize the mutex: */
|
|
tme_mutex_init(&scsi_device->tme_scsi_device_mutex);
|
|
|
|
/* set the SCSI ID: */
|
|
scsi_device->tme_scsi_device_id = id;
|
|
|
|
/* initialize all of the message and CDB handlers to NULL: */
|
|
for (i = TME_ARRAY_ELS(scsi_device->tme_scsi_device_do_msg);
|
|
i-- > 0; ) {
|
|
scsi_device->tme_scsi_device_do_msg[i] = NULL;
|
|
}
|
|
for (i = TME_ARRAY_ELS(scsi_device->tme_scsi_device_do_cdb);
|
|
i-- > 0; ) {
|
|
scsi_device->tme_scsi_device_do_cdb[i] = NULL;
|
|
}
|
|
|
|
/* set the message handlers: */
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_CMD_COMPLETE,
|
|
tme_scsi_device_msg_cmd_complete);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_SAVE_DATA_POINTER,
|
|
tme_scsi_device_msg_save_data_pointer);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_RESTORE_POINTERS,
|
|
tme_scsi_device_msg_restore_pointers);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_DISCONNECT,
|
|
tme_scsi_device_msg_disconnect);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_INITIATOR_ERROR,
|
|
tme_scsi_device_msg_initiator_error);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_ABORT,
|
|
tme_scsi_device_msg_abort);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_MESSAGE_REJECT,
|
|
tme_scsi_device_msg_message_reject);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_NOP,
|
|
tme_scsi_device_msg_nop);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_PARITY_ERROR,
|
|
tme_scsi_device_msg_parity_error);
|
|
TME_SCSI_DEVICE_DO_MSG(scsi_device,
|
|
TME_SCSI_MSG_IDENTIFY,
|
|
tme_scsi_device_msg_identify);
|
|
TME_SCSI_DEVICE_DO_MSG_EXT(scsi_device,
|
|
TME_SCSI_MSG_EXT_SDTR,
|
|
tme_scsi_device_msg_target_reject);
|
|
|
|
/* set the "Group 0 Common Commands for All Device Types": */
|
|
TME_SCSI_DEVICE_DO_CDB(scsi_device,
|
|
TME_SCSI_CDB_TEST_UNIT_READY,
|
|
tme_scsi_device_cdb_tur);
|
|
TME_SCSI_DEVICE_DO_CDB(scsi_device,
|
|
TME_SCSI_CDB_REQUEST_SENSE,
|
|
tme_scsi_device_cdb_request_sense);
|
|
TME_SCSI_DEVICE_DO_CDB(scsi_device,
|
|
TME_SCSI_CDB_INQUIRY,
|
|
NULL);
|
|
|
|
/* assume that this device is LUN-aware: */
|
|
scsi_device->tme_scsi_device_address_lun
|
|
= tme_scsi_device_address_lun_aware;
|
|
|
|
return (TME_OK);
|
|
}
|