8b6083e5fd
Though this is still incomplete and has some missing features such as exclusive login and event notification, it may be enough for someone who wants to play with it. This driver is supposed to work with firewire(4), targ(4) of CAM(4) and scsi_target(8) which can be found in /usr/share/example/scsi_target. This driver doesn't require sbp(4) which implements initiator mode. Sample configuration: Kernel: (you can use modules as well) device firewire device scbus device targ device sbp_targ After reboot: # mdconfig -a -t malloc -s 10m md0 # scsi_target 0:0:0 /dev/md0 (Assuming sbp_targ0 on scbus0) You should find the 10MB HDD on FreeBSD/MacOS X/WinXP or whatever connected to the target using FireWire. Manpage is not finished yet.
1551 lines
38 KiB
C
1551 lines
38 KiB
C
/*
|
|
* Copyright (C) 2003
|
|
* Hidetoshi Shimokawa. 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 Hidetoshi Shimokawa.
|
|
*
|
|
* 4. Neither the name of the author nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/malloc.h>
|
|
#if __FreeBSD_version < 500000
|
|
#include <sys/devicestat.h>
|
|
#endif
|
|
|
|
#include <sys/bus.h>
|
|
#include <machine/bus.h>
|
|
|
|
#include <dev/firewire/firewire.h>
|
|
#include <dev/firewire/firewirereg.h>
|
|
#include <dev/firewire/iec13213.h>
|
|
#include <dev/firewire/sbp.h>
|
|
#include <dev/firewire/fwmem.h>
|
|
|
|
#include <cam/cam.h>
|
|
#include <cam/cam_ccb.h>
|
|
#include <cam/cam_sim.h>
|
|
#include <cam/cam_xpt_sim.h>
|
|
#include <cam/cam_debug.h>
|
|
#include <cam/cam_periph.h>
|
|
#include <cam/scsi/scsi_all.h>
|
|
|
|
#define SBP_TARG_RECV_LEN (8)
|
|
#define MAX_LUN 63
|
|
/*
|
|
* management/command block agent registers
|
|
*
|
|
* BASE 0xffff f001 0000 management port
|
|
* BASE 0xffff f001 0020 command port for lun0
|
|
* BASE 0xffff f001 0040 command port for lun1
|
|
*
|
|
*/
|
|
#define SBP_TARG_MGM 0x10000 /* offset from 0xffff f000 000 */
|
|
#define SBP_TARG_BIND_HI 0xffff
|
|
#define SBP_TARG_BIND_LO(l) (0xf0000000 + SBP_TARG_MGM + 0x20 * ((l) + 1))
|
|
#define SBP_TARG_BIND_START (((u_int64_t)SBP_TARG_BIND_HI << 32) | \
|
|
SBP_TARG_BIND_LO(-1))
|
|
#define SBP_TARG_BIND_END (((u_int64_t)SBP_TARG_BIND_HI << 32) | \
|
|
SBP_TARG_BIND_LO(MAX_LUN))
|
|
#define SBP_TARG_LUN(lo) (((lo) - SBP_TARG_BIND_LO(0))/0x20)
|
|
|
|
#define FETCH_MGM 0
|
|
#define FETCH_CMD 1
|
|
#define FETCH_POINTER 2
|
|
|
|
MALLOC_DEFINE(M_SBP_TARG, "sbp_targ", "SBP-II/FireWire target mode");
|
|
|
|
static int debug = 0;
|
|
|
|
SYSCTL_INT(_debug, OID_AUTO, sbp_targ_debug, CTLFLAG_RW, &debug, 0,
|
|
"SBP target mode debug flag");
|
|
|
|
struct sbp_targ_softc {
|
|
struct firewire_dev_comm fd;
|
|
struct cam_sim *sim;
|
|
struct cam_path *path;
|
|
struct fw_bind fwb;
|
|
int ndevs;
|
|
struct crom_chunk unit;
|
|
struct sbp_targ_lstate *lstate[MAX_LUN];
|
|
struct sbp_targ_lstate *black_hole;
|
|
};
|
|
|
|
struct sbp_targ_lstate {
|
|
struct sbp_targ_softc *sc;
|
|
struct cam_path *path;
|
|
struct ccb_hdr_slist accept_tios;
|
|
struct ccb_hdr_slist immed_notifies;
|
|
struct crom_chunk model;
|
|
/* XXX per initiater data */
|
|
struct fw_device *fwdev;
|
|
struct sbp_login_res loginres;
|
|
u_int32_t flags;
|
|
#define LINK_ACTIVE 1
|
|
#define ATIO_STARVED 2
|
|
u_int16_t fifo_hi;
|
|
u_int16_t last_hi;
|
|
u_int32_t fifo_lo;
|
|
u_int32_t last_lo;
|
|
STAILQ_HEAD(, orb_info) orbs;
|
|
u_int16_t login_id;
|
|
u_int16_t lun;
|
|
};
|
|
|
|
struct corb4 {
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
u_int32_t n:1,
|
|
rq_fmt:2,
|
|
:1,
|
|
dir:1,
|
|
spd:3,
|
|
max_payload:4,
|
|
page_table_present:1,
|
|
page_size:3,
|
|
data_size:16;
|
|
#else
|
|
u_int32_t data_size:16,
|
|
page_size:3,
|
|
page_table_present:1,
|
|
max_payload:4,
|
|
spd:3,
|
|
dir:1,
|
|
:1,
|
|
rq_fmt:2,
|
|
n:1;
|
|
#endif
|
|
};
|
|
|
|
struct morb4 {
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
u_int32_t n:1,
|
|
rq_fmt:2,
|
|
:9,
|
|
fun:4,
|
|
id:16;
|
|
#else
|
|
u_int32_t id:16,
|
|
fun:4,
|
|
:9,
|
|
rq_fmt:2,
|
|
n:1;
|
|
#endif
|
|
};
|
|
|
|
struct orb_info {
|
|
struct sbp_targ_softc *sc;
|
|
struct fw_device *fwdev;
|
|
struct sbp_targ_lstate *lstate;
|
|
union ccb *ccb;
|
|
struct ccb_accept_tio *atio;
|
|
u_int8_t state;
|
|
#define ORBI_STATUS_NONE 0
|
|
#define ORBI_STATUS_FETCH 1
|
|
#define ORBI_STATUS_ATIO 2
|
|
#define ORBI_STATUS_CTIO 3
|
|
#define ORBI_STATUS_STATUS 4
|
|
#define ORBI_STATUS_POINTER 5
|
|
#define ORBI_STATUS_ABORTED 7
|
|
u_int8_t refcount;
|
|
u_int16_t orb_hi;
|
|
u_int32_t orb_lo;
|
|
u_int32_t data_hi;
|
|
u_int32_t data_lo;
|
|
struct corb4 orb4;
|
|
STAILQ_ENTRY(orb_info) link;
|
|
u_int32_t orb[8];
|
|
u_int32_t *page_table;
|
|
struct sbp_status status;
|
|
};
|
|
|
|
static char *orb_fun_name[] = {
|
|
ORB_FUN_NAMES
|
|
};
|
|
|
|
static void sbp_targ_recv(struct fw_xfer *);
|
|
static void sbp_targ_fetch_orb(struct sbp_targ_softc *, struct fw_device *,
|
|
u_int16_t, u_int32_t, struct sbp_targ_lstate *, int);
|
|
|
|
static void
|
|
sbp_targ_identify(driver_t *driver, device_t parent)
|
|
{
|
|
BUS_ADD_CHILD(parent, 0, "sbp_targ", device_get_unit(parent));
|
|
}
|
|
|
|
static int
|
|
sbp_targ_probe(device_t dev)
|
|
{
|
|
device_t pa;
|
|
|
|
pa = device_get_parent(dev);
|
|
if(device_get_unit(dev) != device_get_unit(pa)){
|
|
return(ENXIO);
|
|
}
|
|
|
|
device_set_desc(dev, "SBP-2/SCSI over FireWire target mode");
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_post_busreset(void *arg)
|
|
{
|
|
struct sbp_targ_softc *sc;
|
|
struct crom_src *src;
|
|
struct crom_chunk *root;
|
|
struct crom_chunk *unit;
|
|
struct sbp_targ_lstate *lstate;
|
|
int i;
|
|
|
|
sc = (struct sbp_targ_softc *) arg;
|
|
src = sc->fd.fc->crom_src;
|
|
root = sc->fd.fc->crom_root;
|
|
|
|
unit = &sc->unit;
|
|
|
|
bzero(unit, sizeof(struct crom_chunk));
|
|
|
|
crom_add_chunk(src, root, unit, CROM_UDIR);
|
|
crom_add_entry(unit, CSRKEY_SPEC, CSRVAL_ANSIT10);
|
|
crom_add_entry(unit, CSRKEY_VER, CSRVAL_T10SBP2);
|
|
crom_add_entry(unit, CSRKEY_COM_SPEC, CSRVAL_ANSIT10);
|
|
crom_add_entry(unit, CSRKEY_COM_SET, CSRVAL_SCSI);
|
|
|
|
crom_add_entry(unit, CROM_MGM, SBP_TARG_MGM >> 2);
|
|
crom_add_entry(unit, CSRKEY_UNIT_CH, (10<<8) | 8);
|
|
|
|
for (i = 0; i < MAX_LUN; i ++) {
|
|
lstate = sc->lstate[i];
|
|
if (lstate == NULL)
|
|
continue;
|
|
crom_add_entry(unit, CSRKEY_FIRM_VER, 1);
|
|
crom_add_entry(unit, CROM_LUN, i);
|
|
crom_add_entry(unit, CSRKEY_MODEL, 1);
|
|
crom_add_simple_text(src, unit, &lstate->model, "TargetMode");
|
|
}
|
|
}
|
|
|
|
static cam_status
|
|
sbp_targ_find_devs(struct sbp_targ_softc *sc, union ccb *ccb,
|
|
struct sbp_targ_lstate **lstate, int notfound_failure)
|
|
{
|
|
u_int lun;
|
|
|
|
/* XXX 0 is the only vaild target_id */
|
|
if (ccb->ccb_h.target_id == CAM_TARGET_WILDCARD &&
|
|
ccb->ccb_h.target_lun == CAM_LUN_WILDCARD) {
|
|
*lstate = sc->black_hole;
|
|
return (CAM_REQ_CMP);
|
|
}
|
|
|
|
if (ccb->ccb_h.target_id != 0)
|
|
return (CAM_TID_INVALID);
|
|
|
|
lun = ccb->ccb_h.target_lun;
|
|
if (lun >= MAX_LUN)
|
|
return (CAM_LUN_INVALID);
|
|
|
|
*lstate = sc->lstate[lun];
|
|
|
|
if (notfound_failure != 0 && *lstate == NULL)
|
|
return (CAM_PATH_INVALID);
|
|
|
|
return (CAM_REQ_CMP);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_en_lun(struct sbp_targ_softc *sc, union ccb *ccb)
|
|
{
|
|
struct ccb_en_lun *cel = &ccb->cel;
|
|
struct sbp_targ_lstate *lstate;
|
|
struct orb_info *orbi, *next;
|
|
cam_status status;
|
|
|
|
status = sbp_targ_find_devs(sc, ccb, &lstate, 0);
|
|
if (status != CAM_REQ_CMP) {
|
|
ccb->ccb_h.status = status;
|
|
return;
|
|
}
|
|
|
|
if (cel->enable != 0) {
|
|
if (lstate != NULL) {
|
|
xpt_print_path(ccb->ccb_h.path);
|
|
printf("Lun already enabled\n");
|
|
ccb->ccb_h.status = CAM_LUN_ALRDY_ENA;
|
|
return;
|
|
}
|
|
if (cel->grp6_len != 0 || cel->grp7_len != 0) {
|
|
ccb->ccb_h.status = CAM_REQ_INVALID;
|
|
printf("Non-zero Group Codes\n");
|
|
return;
|
|
}
|
|
lstate = (struct sbp_targ_lstate *)
|
|
malloc(sizeof(*lstate), M_SBP_TARG, M_NOWAIT | M_ZERO);
|
|
if (lstate == NULL) {
|
|
xpt_print_path(ccb->ccb_h.path);
|
|
printf("Couldn't allocate lstate\n");
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
|
|
return;
|
|
}
|
|
if (ccb->ccb_h.target_id == CAM_TARGET_WILDCARD)
|
|
sc->black_hole = lstate;
|
|
else
|
|
sc->lstate[ccb->ccb_h.target_lun] = lstate;
|
|
memset(lstate, 0, sizeof(*lstate));
|
|
lstate->sc = sc;
|
|
status = xpt_create_path(&lstate->path, /*periph*/NULL,
|
|
xpt_path_path_id(ccb->ccb_h.path),
|
|
xpt_path_target_id(ccb->ccb_h.path),
|
|
xpt_path_lun_id(ccb->ccb_h.path));
|
|
if (status != CAM_REQ_CMP) {
|
|
free(lstate, M_SBP_TARG);
|
|
xpt_print_path(ccb->ccb_h.path);
|
|
printf("Couldn't allocate path\n");
|
|
ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
|
|
return;
|
|
}
|
|
SLIST_INIT(&lstate->accept_tios);
|
|
SLIST_INIT(&lstate->immed_notifies);
|
|
STAILQ_INIT(&lstate->orbs);
|
|
lstate->last_hi = 0xffff;
|
|
lstate->last_lo = 0xffffffff;
|
|
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
xpt_print_path(ccb->ccb_h.path);
|
|
printf("Lun now enabled for target mode\n");
|
|
/* bus reset */
|
|
sc->fd.fc->ibr(sc->fd.fc);
|
|
} else {
|
|
if (lstate == NULL) {
|
|
ccb->ccb_h.status = CAM_LUN_INVALID;
|
|
return;
|
|
}
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
|
|
if (SLIST_FIRST(&lstate->accept_tios) != NULL) {
|
|
printf("ATIOs pending\n");
|
|
ccb->ccb_h.status = CAM_REQ_INVALID;
|
|
}
|
|
|
|
if (SLIST_FIRST(&lstate->immed_notifies) != NULL) {
|
|
printf("INOTs pending\n");
|
|
ccb->ccb_h.status = CAM_REQ_INVALID;
|
|
}
|
|
|
|
if (ccb->ccb_h.status != CAM_REQ_CMP) {
|
|
return;
|
|
}
|
|
|
|
xpt_print_path(ccb->ccb_h.path);
|
|
printf("Target mode disabled\n");
|
|
xpt_free_path(lstate->path);
|
|
|
|
for (orbi = STAILQ_FIRST(&lstate->orbs); orbi != NULL;
|
|
orbi = next) {
|
|
next = STAILQ_NEXT(orbi, link);
|
|
free(orbi, M_SBP_TARG);
|
|
}
|
|
|
|
if (ccb->ccb_h.target_id == CAM_TARGET_WILDCARD)
|
|
sc->black_hole = NULL;
|
|
else
|
|
sc->lstate[ccb->ccb_h.target_lun] = NULL;
|
|
free(lstate, M_SBP_TARG);
|
|
|
|
/* bus reset */
|
|
sc->fd.fc->ibr(sc->fd.fc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbp_targ_send_lstate_events(struct sbp_targ_softc *sc,
|
|
struct sbp_targ_lstate *lstate)
|
|
{
|
|
#if 0
|
|
struct ccb_hdr *ccbh;
|
|
struct ccb_immed_notify *inot;
|
|
|
|
printf("%s: not implemented yet\n", __FUNCTION__);
|
|
#endif
|
|
}
|
|
|
|
static __inline void
|
|
sbp_targ_remove_orb_info(struct sbp_targ_lstate *lstate, struct orb_info *orbi)
|
|
{
|
|
STAILQ_REMOVE(&lstate->orbs, orbi, orb_info, link);
|
|
}
|
|
|
|
/*
|
|
* tag_id/init_id encoding
|
|
*
|
|
* tag_id and init_id has only 32bit for each.
|
|
* scsi_target can handle very limited number(up to 15) of init_id.
|
|
* we have to encode 48bit orb and 64bit EUI64 into these
|
|
* variables.
|
|
*
|
|
* tag_id represents lower 32bit of ORB address.
|
|
* init_id represents node_id now.
|
|
*
|
|
*/
|
|
|
|
static struct orb_info *
|
|
sbp_targ_get_orb_info(struct sbp_targ_lstate *lstate,
|
|
u_int tag_id, u_int init_id)
|
|
{
|
|
struct orb_info *orbi;
|
|
|
|
STAILQ_FOREACH(orbi, &lstate->orbs, link)
|
|
if (orbi->orb_lo == tag_id &&
|
|
#if 0
|
|
orbi->orb_hi == (init_id & 0xffff) &&
|
|
orbi->fwdev->dst == (init_id >> 16))
|
|
#else
|
|
orbi->fwdev->dst == init_id)
|
|
#endif
|
|
goto found;
|
|
printf("%s: orb not found\n", __FUNCTION__);
|
|
return (NULL);
|
|
found:
|
|
return (orbi);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_abort(struct orb_info *orbi)
|
|
{
|
|
struct orb_info *norbi;
|
|
|
|
for (; orbi != NULL; orbi = norbi) {
|
|
printf("%s: status=%d\n", __FUNCTION__, orbi->state);
|
|
norbi = STAILQ_NEXT(orbi, link);
|
|
if (orbi->state != ORBI_STATUS_ABORTED) {
|
|
if (orbi->ccb != NULL) {
|
|
orbi->ccb->ccb_h.status = CAM_REQ_ABORTED;
|
|
xpt_done(orbi->ccb);
|
|
orbi->ccb = NULL;
|
|
}
|
|
if (orbi->state <= ORBI_STATUS_ATIO) {
|
|
sbp_targ_remove_orb_info(orbi->lstate, orbi);
|
|
free(orbi, M_SBP_TARG);
|
|
} else
|
|
orbi->state = ORBI_STATUS_ABORTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbp_targ_free_orbi(struct fw_xfer *xfer)
|
|
{
|
|
struct orb_info *orbi;
|
|
|
|
orbi = (struct orb_info *)xfer->sc;
|
|
if (xfer->resp != 0) {
|
|
/* XXX */
|
|
printf("%s: xfer->resp != 0\n", __FUNCTION__);
|
|
}
|
|
free(orbi, M_SBP_TARG);
|
|
fw_xfer_free(xfer);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_status_FIFO(struct orb_info *orbi,
|
|
u_int32_t fifo_hi, u_int32_t fifo_lo, int dequeue)
|
|
{
|
|
struct fw_xfer *xfer;
|
|
|
|
if (dequeue)
|
|
sbp_targ_remove_orb_info(orbi->lstate, orbi);
|
|
|
|
xfer = fwmem_write_block(orbi->fwdev, (void *)orbi,
|
|
/*spd*/2, fifo_hi, fifo_lo,
|
|
sizeof(u_int32_t) * (orbi->status.len + 1), (char *)&orbi->status,
|
|
sbp_targ_free_orbi);
|
|
|
|
if (xfer == NULL) {
|
|
/* XXX */
|
|
printf("%s: xfer == NULL\n", __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbp_targ_send_status(struct orb_info *orbi, union ccb *ccb)
|
|
{
|
|
struct sbp_status *sbp_status;
|
|
|
|
sbp_status = &orbi->status;
|
|
|
|
orbi->state = ORBI_STATUS_STATUS;
|
|
|
|
sbp_status->resp = 0; /* XXX */
|
|
sbp_status->status = 0; /* XXX */
|
|
sbp_status->dead = 0; /* XXX */
|
|
|
|
switch (ccb->csio.scsi_status) {
|
|
case SCSI_STATUS_OK:
|
|
if (debug)
|
|
printf("%s: STATUS_OK\n", __FUNCTION__);
|
|
sbp_status->len = 1;
|
|
break;
|
|
case SCSI_STATUS_CHECK_COND:
|
|
case SCSI_STATUS_BUSY:
|
|
case SCSI_STATUS_CMD_TERMINATED:
|
|
{
|
|
struct sbp_cmd_status *sbp_cmd_status;
|
|
struct scsi_sense_data *sense;
|
|
|
|
if (debug)
|
|
printf("%s: STATUS %d\n", __FUNCTION__,
|
|
ccb->csio.scsi_status);
|
|
sbp_cmd_status = (struct sbp_cmd_status *)&sbp_status->data[0];
|
|
sbp_cmd_status->status = ccb->csio.scsi_status;
|
|
sense = &ccb->csio.sense_data;
|
|
|
|
sbp_targ_abort(STAILQ_NEXT(orbi, link));
|
|
|
|
if ((sense->error_code & SSD_ERRCODE) == SSD_CURRENT_ERROR)
|
|
sbp_cmd_status->sfmt = SBP_SFMT_CURR;
|
|
else
|
|
sbp_cmd_status->sfmt = SBP_SFMT_DEFER;
|
|
|
|
sbp_cmd_status->valid = (sense->error_code & SSD_ERRCODE_VALID)
|
|
? 1 : 0;
|
|
sbp_cmd_status->s_key = sense->flags & SSD_KEY;
|
|
sbp_cmd_status->mark = (sense->flags & SSD_FILEMARK)? 1 : 0;
|
|
sbp_cmd_status->eom = (sense->flags & SSD_EOM) ? 1 : 0;
|
|
sbp_cmd_status->ill_len = (sense->flags & SSD_ILI) ? 1 : 0;
|
|
|
|
bcopy(&sense->info[0], &sbp_cmd_status->info, 4);
|
|
|
|
if (sense->extra_len <= 6)
|
|
/* add_sense_code(_qual), info, cmd_spec_info */
|
|
sbp_status->len = 4;
|
|
else
|
|
/* fru, sense_key_spec */
|
|
sbp_status->len = 5;
|
|
|
|
bcopy(&sense->cmd_spec_info[0], &sbp_cmd_status->cdb, 4);
|
|
|
|
sbp_cmd_status->s_code = sense->add_sense_code;
|
|
sbp_cmd_status->s_qlfr = sense->add_sense_code_qual;
|
|
sbp_cmd_status->fru = sense->fru;
|
|
|
|
bcopy(&sense->sense_key_spec[0],
|
|
&sbp_cmd_status->s_keydep[0], 3);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
printf("%s: unknown scsi status 0x%x\n", __FUNCTION__,
|
|
sbp_status->status);
|
|
}
|
|
|
|
sbp_targ_status_FIFO(orbi,
|
|
orbi->lstate->fifo_hi, orbi->lstate->fifo_lo, /*dequeue*/1);
|
|
|
|
if (orbi->page_table != NULL)
|
|
free(orbi->page_table, M_SBP_TARG);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_cam_done(struct fw_xfer *xfer)
|
|
{
|
|
struct orb_info *orbi;
|
|
union ccb *ccb;
|
|
|
|
orbi = (struct orb_info *)xfer->sc;
|
|
|
|
if (debug)
|
|
printf("%s: resp=%d refcount=%d\n", __FUNCTION__,
|
|
xfer->resp, orbi->refcount);
|
|
|
|
if (xfer->resp != 0) {
|
|
printf("%s: xfer->resp != 0\n", __FUNCTION__);
|
|
orbi->status.resp = SBP_TRANS_FAIL;
|
|
orbi->status.status = htonl(OBJ_DATA | SBE_TIMEOUT /*XXX*/);
|
|
orbi->status.dead = 1;
|
|
sbp_targ_abort(STAILQ_NEXT(orbi, link));
|
|
}
|
|
|
|
orbi->refcount --;
|
|
|
|
ccb = orbi->ccb;
|
|
if (orbi->refcount == 0) {
|
|
if (orbi->state == ORBI_STATUS_ABORTED) {
|
|
if (debug)
|
|
printf("%s: orbi aborted\n", __FUNCTION__);
|
|
sbp_targ_remove_orb_info(orbi->lstate, orbi);
|
|
if (orbi->page_table != NULL)
|
|
free(orbi->page_table, M_SBP_TARG);
|
|
free(orbi, M_SBP_TARG);
|
|
} else if (orbi->status.resp == 0) {
|
|
if ((ccb->ccb_h.flags & CAM_SEND_STATUS) != 0)
|
|
sbp_targ_send_status(orbi, ccb);
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
xpt_done(ccb);
|
|
} else {
|
|
orbi->status.len = 1;
|
|
sbp_targ_status_FIFO(orbi,
|
|
orbi->lstate->fifo_hi, orbi->lstate->fifo_lo,
|
|
/*dequeue*/1);
|
|
ccb->ccb_h.status = CAM_REQ_ABORTED;
|
|
xpt_done(ccb);
|
|
}
|
|
}
|
|
|
|
fw_xfer_free(xfer);
|
|
}
|
|
|
|
static cam_status
|
|
sbp_targ_abort_ccb(struct sbp_targ_softc *sc, union ccb *ccb)
|
|
{
|
|
union ccb *accb;
|
|
struct sbp_targ_lstate *lstate;
|
|
struct ccb_hdr_slist *list;
|
|
struct ccb_hdr *curelm;
|
|
int found;
|
|
cam_status status;
|
|
|
|
status = sbp_targ_find_devs(sc, ccb, &lstate, 0);
|
|
if (status != CAM_REQ_CMP)
|
|
return (status);
|
|
|
|
accb = ccb->cab.abort_ccb;
|
|
|
|
if (accb->ccb_h.func_code == XPT_ACCEPT_TARGET_IO)
|
|
list = &lstate->accept_tios;
|
|
else if (accb->ccb_h.func_code == XPT_IMMED_NOTIFY)
|
|
list = &lstate->immed_notifies;
|
|
else
|
|
return (CAM_UA_ABORT);
|
|
|
|
curelm = SLIST_FIRST(list);
|
|
found = 0;
|
|
if (curelm == &accb->ccb_h) {
|
|
found = 1;
|
|
SLIST_REMOVE_HEAD(list, sim_links.sle);
|
|
} else {
|
|
while(curelm != NULL) {
|
|
struct ccb_hdr *nextelm;
|
|
|
|
nextelm = SLIST_NEXT(curelm, sim_links.sle);
|
|
if (nextelm == &accb->ccb_h) {
|
|
found = 1;
|
|
SLIST_NEXT(curelm, sim_links.sle) =
|
|
SLIST_NEXT(nextelm, sim_links.sle);
|
|
break;
|
|
}
|
|
curelm = nextelm;
|
|
}
|
|
}
|
|
if (found) {
|
|
accb->ccb_h.status = CAM_REQ_ABORTED;
|
|
xpt_done(accb);
|
|
return (CAM_REQ_CMP);
|
|
}
|
|
printf("%s: not found\n", __FUNCTION__);
|
|
return (CAM_PATH_INVALID);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_xfer_buf(struct orb_info *orbi, u_int offset,
|
|
u_int16_t dst_hi, u_int32_t dst_lo, u_int size,
|
|
void (*hand)(struct fw_xfer *))
|
|
{
|
|
struct fw_xfer *xfer;
|
|
u_int len, ccb_dir, off = 0;
|
|
char *ptr;
|
|
|
|
if (debug)
|
|
printf("%s: offset=%d size=%d\n", __FUNCTION__, offset, size);
|
|
ccb_dir = orbi->ccb->ccb_h.flags & CAM_DIR_MASK;
|
|
ptr = (char *)orbi->ccb->csio.data_ptr + offset;
|
|
|
|
while (size > 0) {
|
|
/* XXX assume dst_lo + off doesn't overflow */
|
|
len = MIN(size, 2048 /* XXX */);
|
|
size -= len;
|
|
orbi->refcount ++;
|
|
if (ccb_dir == CAM_DIR_OUT)
|
|
xfer = fwmem_read_block(orbi->fwdev,
|
|
(void *)orbi, /*spd*/2,
|
|
dst_hi, dst_lo + off, len,
|
|
ptr + off, hand);
|
|
else
|
|
xfer = fwmem_write_block(orbi->fwdev,
|
|
(void *)orbi, /*spd*/2,
|
|
dst_hi, dst_lo + off, len,
|
|
ptr + off, hand);
|
|
if (xfer == NULL) {
|
|
printf("%s: xfer == NULL", __FUNCTION__);
|
|
/* XXX what should we do?? */
|
|
orbi->refcount --;
|
|
}
|
|
off += len;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbp_targ_pt_done(struct fw_xfer *xfer)
|
|
{
|
|
struct orb_info *orbi;
|
|
union ccb *ccb;
|
|
u_int i, offset, res, len;
|
|
u_int32_t t1, t2, *p;
|
|
|
|
orbi = (struct orb_info *)xfer->sc;
|
|
ccb = orbi->ccb;
|
|
if (orbi->state == ORBI_STATUS_ABORTED) {
|
|
if (debug)
|
|
printf("%s: orbi aborted\n", __FUNCTION__);
|
|
sbp_targ_remove_orb_info(orbi->lstate, orbi);
|
|
free(orbi->page_table, M_SBP_TARG);
|
|
free(orbi, M_SBP_TARG);
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
if (xfer->resp != 0) {
|
|
printf("%s: xfer->resp != 0\n", __FUNCTION__);
|
|
orbi->status.resp = SBP_TRANS_FAIL;
|
|
orbi->status.status = htonl(OBJ_PT | SBE_TIMEOUT /*XXX*/);
|
|
orbi->status.dead = 1;
|
|
orbi->status.len = 1;
|
|
sbp_targ_abort(STAILQ_NEXT(orbi, link));
|
|
|
|
sbp_targ_status_FIFO(orbi,
|
|
orbi->lstate->fifo_hi, orbi->lstate->fifo_lo, /*dequeue*/1);
|
|
free(orbi->page_table, M_SBP_TARG);
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
res = ccb->csio.dxfer_len;
|
|
offset = 0;
|
|
if (debug)
|
|
printf("%s: dxfer_len=%d\n", __FUNCTION__, res);
|
|
orbi->refcount ++;
|
|
for (p = orbi->page_table, i = orbi->orb4.data_size; i > 0; i --) {
|
|
t1 = ntohl(*p++);
|
|
t2 = ntohl(*p++);
|
|
if (debug)
|
|
printf("page_table: %04x:%08x %d\n",
|
|
t1 & 0xffff, t2, t1>>16);
|
|
len = MIN(t1 >> 16, res);
|
|
res -= len;
|
|
sbp_targ_xfer_buf(orbi, offset, t1 & 0xffff, t2, len,
|
|
sbp_targ_cam_done);
|
|
offset += len;
|
|
if (res == 0)
|
|
break;
|
|
}
|
|
orbi->refcount --;
|
|
if (orbi->refcount == 0)
|
|
printf("%s: refcount == 0\n", __FUNCTION__);
|
|
if (res !=0)
|
|
/* XXX handle res != 0 case */
|
|
printf("%s: page table is too small(%d)\n", __FUNCTION__, res);
|
|
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_fetch_pt(struct orb_info *orbi)
|
|
{
|
|
struct fw_xfer *xfer;
|
|
|
|
if (debug)
|
|
printf("%s: page_table_size=%d\n",
|
|
__FUNCTION__, orbi->orb4.data_size);
|
|
orbi->page_table = malloc(orbi->orb4.data_size*8, M_SBP_TARG, M_NOWAIT);
|
|
if (orbi->page_table == NULL)
|
|
goto error;
|
|
xfer = fwmem_read_block(orbi->fwdev, (void *)orbi, /*spd*/2,
|
|
orbi->data_hi, orbi->data_lo, orbi->orb4.data_size*8,
|
|
(void *)orbi->page_table, sbp_targ_pt_done);
|
|
if (xfer != NULL)
|
|
return;
|
|
error:
|
|
orbi->ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
|
|
xpt_done(orbi->ccb);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_action1(struct cam_sim *sim, union ccb *ccb)
|
|
{
|
|
struct sbp_targ_softc *sc;
|
|
struct sbp_targ_lstate *lstate;
|
|
cam_status status;
|
|
u_int ccb_dir;
|
|
|
|
sc = (struct sbp_targ_softc *)cam_sim_softc(sim);
|
|
|
|
status = sbp_targ_find_devs(sc, ccb, &lstate, TRUE);
|
|
|
|
switch (ccb->ccb_h.func_code) {
|
|
case XPT_CONT_TARGET_IO:
|
|
{
|
|
struct orb_info *orbi;
|
|
|
|
if (debug)
|
|
printf("%s: XPT_CONT_TARGET_IO\n", __FUNCTION__);
|
|
|
|
if (status != CAM_REQ_CMP) {
|
|
ccb->ccb_h.status = status;
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
/* XXX transfer from/to initiator */
|
|
orbi = sbp_targ_get_orb_info(lstate,
|
|
ccb->csio.tag_id, ccb->csio.init_id);
|
|
if (orbi == NULL) {
|
|
printf("%s: no such ORB found, aborted?\n",
|
|
__FUNCTION__);
|
|
ccb->ccb_h.status = CAM_REQ_ABORTED; /* XXX */
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
if (orbi->state == ORBI_STATUS_ABORTED) {
|
|
if (debug)
|
|
printf("%s: ctio aborted\n", __FUNCTION__);
|
|
sbp_targ_remove_orb_info(orbi->lstate, orbi);
|
|
free(orbi, M_SBP_TARG);
|
|
break;
|
|
}
|
|
orbi->state = ORBI_STATUS_CTIO;
|
|
|
|
orbi->ccb = ccb;
|
|
ccb_dir = ccb->ccb_h.flags & CAM_DIR_MASK;
|
|
|
|
/* XXX */
|
|
if (ccb->csio.dxfer_len == 0)
|
|
ccb_dir = CAM_DIR_NONE;
|
|
|
|
/* Sanity check */
|
|
if (ccb_dir == CAM_DIR_IN && orbi->orb4.dir == 0)
|
|
printf("%s: direction mismatch\n", __FUNCTION__);
|
|
|
|
/* check page table */
|
|
if (ccb_dir != CAM_DIR_NONE && orbi->orb4.page_table_present) {
|
|
if (debug)
|
|
printf("%s: page_table_present\n",
|
|
__FUNCTION__);
|
|
if (orbi->orb4.page_size != 0) {
|
|
printf("%s: unsupported pagesize %d != 0\n",
|
|
__FUNCTION__, orbi->orb4.page_size);
|
|
ccb->ccb_h.status = CAM_REQ_INVALID;
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
sbp_targ_fetch_pt(orbi);
|
|
break;
|
|
}
|
|
|
|
/* Sanity check */
|
|
if (ccb_dir != CAM_DIR_NONE &&
|
|
orbi->orb4.data_size != ccb->csio.dxfer_len)
|
|
printf("%s: data_size(%d) != dxfer_len(%d)\n",
|
|
__FUNCTION__, orbi->orb4.data_size,
|
|
ccb->csio.dxfer_len);
|
|
|
|
if (ccb_dir != CAM_DIR_NONE)
|
|
sbp_targ_xfer_buf(orbi, 0, orbi->data_hi,
|
|
orbi->data_lo,
|
|
MIN(orbi->orb4.data_size, ccb->csio.dxfer_len),
|
|
sbp_targ_cam_done);
|
|
|
|
if (ccb_dir == CAM_DIR_NONE) {
|
|
if ((ccb->ccb_h.flags & CAM_SEND_STATUS) != 0)
|
|
sbp_targ_send_status(orbi, ccb);
|
|
ccb->ccb_h.status = CAM_REQ_CMP;
|
|
xpt_done(ccb);
|
|
}
|
|
break;
|
|
}
|
|
case XPT_ACCEPT_TARGET_IO: /* Add Accept Target IO Resource */
|
|
if (status != CAM_REQ_CMP) {
|
|
ccb->ccb_h.status = status;
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
SLIST_INSERT_HEAD(&lstate->accept_tios, &ccb->ccb_h,
|
|
sim_links.sle);
|
|
ccb->ccb_h.status = CAM_REQ_INPROG;
|
|
if ((lstate->flags & ATIO_STARVED) != 0) {
|
|
if (debug)
|
|
printf("%s: new atio arrived\n", __FUNCTION__);
|
|
lstate->flags &= ~ATIO_STARVED;
|
|
sbp_targ_fetch_orb(lstate->sc, lstate->fwdev,
|
|
lstate->last_hi, lstate->last_lo,
|
|
lstate, FETCH_CMD);
|
|
}
|
|
break;
|
|
case XPT_NOTIFY_ACK: /* recycle notify ack */
|
|
case XPT_IMMED_NOTIFY: /* Add Immediate Notify Resource */
|
|
if (status != CAM_REQ_CMP) {
|
|
ccb->ccb_h.status = status;
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
SLIST_INSERT_HEAD(&lstate->immed_notifies, &ccb->ccb_h,
|
|
sim_links.sle);
|
|
ccb->ccb_h.status = CAM_REQ_INPROG;
|
|
sbp_targ_send_lstate_events(sc, lstate);
|
|
break;
|
|
case XPT_EN_LUN:
|
|
sbp_targ_en_lun(sc, ccb);
|
|
xpt_done(ccb);
|
|
break;
|
|
case XPT_PATH_INQ:
|
|
{
|
|
struct ccb_pathinq *cpi = &ccb->cpi;
|
|
|
|
cpi->version_num = 1; /* XXX??? */
|
|
cpi->hba_inquiry = PI_TAG_ABLE;
|
|
cpi->target_sprt = PIT_PROCESSOR
|
|
| PIT_DISCONNECT
|
|
| PIT_TERM_IO;
|
|
cpi->hba_misc = PIM_NOBUSRESET | PIM_NO_6_BYTE;
|
|
cpi->hba_eng_cnt = 0;
|
|
cpi->max_target = 7; /* XXX */
|
|
cpi->max_lun = MAX_LUN - 1;
|
|
cpi->initiator_id = 7; /* XXX */
|
|
cpi->bus_id = sim->bus_id;
|
|
cpi->base_transfer_speed = 400 * 1000 / 8;
|
|
strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
|
|
strncpy(cpi->hba_vid, "SBP_TARG", HBA_IDLEN);
|
|
strncpy(cpi->dev_name, sim->sim_name, DEV_IDLEN);
|
|
cpi->unit_number = sim->unit_number;
|
|
|
|
cpi->ccb_h.status = CAM_REQ_CMP;
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
case XPT_ABORT:
|
|
{
|
|
union ccb *accb = ccb->cab.abort_ccb;
|
|
|
|
switch (accb->ccb_h.func_code) {
|
|
case XPT_ACCEPT_TARGET_IO:
|
|
case XPT_IMMED_NOTIFY:
|
|
ccb->ccb_h.status = sbp_targ_abort_ccb(sc, ccb);
|
|
break;
|
|
case XPT_CONT_TARGET_IO:
|
|
/* XXX */
|
|
ccb->ccb_h.status = CAM_UA_ABORT;
|
|
break;
|
|
default:
|
|
printf("%s: aborting unknown function %d\n",
|
|
__FUNCTION__, accb->ccb_h.func_code);
|
|
ccb->ccb_h.status = CAM_REQ_INVALID;
|
|
break;
|
|
}
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
default:
|
|
printf("%s: unknown function %d\n",
|
|
__FUNCTION__, ccb->ccb_h.func_code);
|
|
ccb->ccb_h.status = CAM_REQ_INVALID;
|
|
xpt_done(ccb);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_action(struct cam_sim *sim, union ccb *ccb)
|
|
{
|
|
int s;
|
|
|
|
s = splfw();
|
|
sbp_targ_action1(sim, ccb);
|
|
splx(s);
|
|
}
|
|
|
|
static void
|
|
sbp_targ_poll(struct cam_sim *sim)
|
|
{
|
|
/* XXX */
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_cmd_handler(struct fw_xfer *xfer)
|
|
{
|
|
struct fw_pkt *fp;
|
|
u_int32_t *orb;
|
|
struct corb4 *orb4;
|
|
struct orb_info *orbi;
|
|
struct ccb_accept_tio *atio;
|
|
struct sbp_targ_lstate *lstate;
|
|
u_char *bytes;
|
|
int i;
|
|
|
|
orbi = (struct orb_info *)xfer->sc;
|
|
if (xfer->resp != 0) {
|
|
printf("%s: xfer->resp != 0\n", __FUNCTION__);
|
|
orbi->status.resp = SBP_TRANS_FAIL;
|
|
orbi->status.status = htonl(OBJ_ORB | SBE_TIMEOUT /*XXX*/);
|
|
orbi->status.dead = 1;
|
|
orbi->status.len = 1;
|
|
sbp_targ_abort(STAILQ_NEXT(orbi, link));
|
|
|
|
sbp_targ_status_FIFO(orbi,
|
|
orbi->lstate->fifo_hi, orbi->lstate->fifo_lo, /*dequeue*/1);
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
fp = &xfer->recv.hdr;
|
|
|
|
if (orbi->state == ORBI_STATUS_ABORTED) {
|
|
printf("%s: aborted\n", __FUNCTION__);
|
|
sbp_targ_remove_orb_info(orbi->lstate, orbi);
|
|
free(orbi, M_SBP_TARG);
|
|
goto done0;
|
|
}
|
|
orbi->state = ORBI_STATUS_ATIO;
|
|
|
|
lstate = orbi->lstate;
|
|
|
|
orb = orbi->orb;
|
|
/* swap payload except SCSI command */
|
|
for (i = 0; i < 5; i ++)
|
|
orb[i] = ntohl(orb[i]);
|
|
|
|
orb4 = (struct corb4 *)&orb[4];
|
|
if (orb4->rq_fmt != 0) {
|
|
/* XXX */
|
|
printf("%s: rq_fmt(%d) != 0\n", __FUNCTION__, orb4->rq_fmt);
|
|
}
|
|
|
|
atio = orbi->atio;
|
|
atio->ccb_h.target_id = 0; /* XXX */
|
|
atio->ccb_h.target_lun = lstate->lun;
|
|
atio->sense_len = 0;
|
|
atio->tag_action = 1; /* XXX */
|
|
atio->tag_id = orbi->orb_lo;
|
|
#if 0
|
|
atio->init_id = (orbi->fwdev->dst << 16) | (orbi->orb_hi & 0xffff);
|
|
#else
|
|
atio->init_id = orbi->fwdev->dst;
|
|
#endif
|
|
atio->ccb_h.flags = CAM_TAG_ACTION_VALID;
|
|
bytes = (char *)&orb[5];
|
|
if (debug)
|
|
printf("%s: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
|
__FUNCTION__,
|
|
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4],
|
|
bytes[5], bytes[6], bytes[7], bytes[8], bytes[9]);
|
|
switch (bytes[0] >> 5) {
|
|
case 0:
|
|
atio->cdb_len = 6;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
atio->cdb_len = 10;
|
|
break;
|
|
case 4:
|
|
atio->cdb_len = 16;
|
|
break;
|
|
case 5:
|
|
atio->cdb_len = 12;
|
|
break;
|
|
case 3:
|
|
default:
|
|
/* Only copy the opcode. */
|
|
atio->cdb_len = 1;
|
|
printf("Reserved or VU command code type encountered\n");
|
|
break;
|
|
}
|
|
|
|
memcpy(atio->cdb_io.cdb_bytes, bytes, atio->cdb_len);
|
|
|
|
atio->ccb_h.status |= CAM_CDB_RECVD;
|
|
|
|
/* next ORB */
|
|
if ((orb[0] & (1<<31)) == 0) {
|
|
if (debug)
|
|
printf("%s: fetch next orb\n", __FUNCTION__);
|
|
orbi->status.src = SRC_NEXT_EXISTS;
|
|
sbp_targ_fetch_orb(orbi->sc, orbi->fwdev,
|
|
orb[0], orb[1], orbi->lstate, FETCH_CMD);
|
|
} else {
|
|
orbi->status.src = SRC_NO_NEXT;
|
|
orbi->lstate->flags &= ~LINK_ACTIVE;
|
|
}
|
|
|
|
orbi->data_hi = orb[2];
|
|
orbi->data_lo = orb[3];
|
|
orbi->orb4 = *orb4;
|
|
|
|
xpt_done((union ccb*)atio);
|
|
done0:
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_mgm_handler(struct fw_xfer *xfer)
|
|
{
|
|
struct sbp_targ_lstate *lstate;
|
|
struct fw_pkt *fp;
|
|
u_int32_t *orb;
|
|
struct morb4 *orb4;
|
|
struct orb_info *orbi;
|
|
int i;
|
|
|
|
orbi = (struct orb_info *)xfer->sc;
|
|
if (xfer->resp != 0) {
|
|
printf("%s: xfer->resp != 0\n", __FUNCTION__);
|
|
orbi->status.resp = SBP_TRANS_FAIL;
|
|
orbi->status.status = htonl(OBJ_ORB | SBE_TIMEOUT /*XXX*/);
|
|
orbi->status.dead = 1;
|
|
orbi->status.len = 1;
|
|
sbp_targ_abort(STAILQ_NEXT(orbi, link));
|
|
|
|
sbp_targ_status_FIFO(orbi,
|
|
orbi->lstate->fifo_hi, orbi->lstate->fifo_lo, /*dequeue*/0);
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
fp = &xfer->recv.hdr;
|
|
|
|
orb = orbi->orb;
|
|
/* swap payload */
|
|
for (i = 0; i < 8; i ++) {
|
|
orb[i] = ntohl(orb[i]);
|
|
}
|
|
orb4 = (struct morb4 *)&orb[4];
|
|
if (debug)
|
|
printf("%s: %s\n", __FUNCTION__, orb_fun_name[orb4->fun]);
|
|
|
|
orbi->status.src = SRC_NO_NEXT;
|
|
|
|
switch (orb4->fun << 16) {
|
|
case ORB_FUN_LGI:
|
|
{
|
|
|
|
if (orb4->id >= MAX_LUN || orbi->sc->lstate[orb4->id] == NULL) {
|
|
/* error */
|
|
orbi->status.dead = 1;
|
|
orbi->status.status = STATUS_ACCESS_DENY;
|
|
orbi->status.len = 1;
|
|
sbp_targ_status_FIFO(orbi, orb[6], orb[7],
|
|
/*dequeue*/0);
|
|
break;
|
|
}
|
|
/* XXX check exclusive login */
|
|
lstate = orbi->sc->lstate[orb4->id];
|
|
lstate->fifo_hi = orb[6];
|
|
lstate->fifo_lo = orb[7];
|
|
lstate->login_id = 0; /* XXX random number? */
|
|
lstate->lun = orb4->id;
|
|
lstate->loginres.len = htons(sizeof(u_int32_t) * 4);
|
|
lstate->loginres.id = htons(lstate->login_id);
|
|
lstate->loginres.cmd_hi = htons(SBP_TARG_BIND_HI);
|
|
lstate->loginres.cmd_lo = htonl(SBP_TARG_BIND_LO(orb4->id));
|
|
lstate->loginres.recon_hold = htons(0); /* XXX */
|
|
fwmem_write_block(orbi->fwdev, NULL, /*spd*/2, orb[2], orb[3],
|
|
sizeof(struct sbp_login_res), (void *)&lstate->loginres,
|
|
fw_asy_callback_free);
|
|
break;
|
|
}
|
|
case ORB_FUN_RCN:
|
|
orbi->status.dead = 1;
|
|
orbi->status.status = STATUS_ACCESS_DENY;
|
|
break;
|
|
default:
|
|
printf("%s: %s not implemented yet\n",
|
|
__FUNCTION__, orb_fun_name[orb4->fun]);
|
|
break;
|
|
}
|
|
orbi->status.len = 1;
|
|
sbp_targ_status_FIFO(orbi, orb[6], orb[7], /*dequeue*/0);
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_pointer_handler(struct fw_xfer *xfer)
|
|
{
|
|
struct orb_info *orbi;
|
|
u_int32_t orb0, orb1;
|
|
|
|
orbi = (struct orb_info *)xfer->sc;
|
|
if (xfer->resp != 0) {
|
|
printf("%s: xfer->resp != 0\n", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
|
|
orb0 = ntohl(orbi->orb[0]);
|
|
orb1 = ntohl(orbi->orb[1]);
|
|
if ((orb0 & (1 << 31)) != 0) {
|
|
printf("%s: invalid pointer\n", __FUNCTION__);
|
|
goto done;
|
|
}
|
|
sbp_targ_fetch_orb(orbi->lstate->sc, orbi->fwdev,
|
|
(u_int16_t)orb0, orb1, orbi->lstate, FETCH_CMD);
|
|
done:
|
|
free(orbi, M_SBP_TARG);
|
|
fw_xfer_free(xfer);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
sbp_targ_fetch_orb(struct sbp_targ_softc *sc, struct fw_device *fwdev,
|
|
u_int16_t orb_hi, u_int32_t orb_lo, struct sbp_targ_lstate *lstate,
|
|
int mode)
|
|
{
|
|
struct orb_info *orbi;
|
|
|
|
if (debug)
|
|
printf("%s: fetch orb %04x:%08x\n", __FUNCTION__, orb_hi, orb_lo);
|
|
orbi = malloc(sizeof(struct orb_info), M_SBP_TARG, M_NOWAIT | M_ZERO);
|
|
if (orbi == NULL) {
|
|
printf("%s: malloc failed\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
orbi->sc = sc;
|
|
orbi->fwdev = fwdev;
|
|
orbi->lstate = lstate;
|
|
orbi->orb_hi = orb_hi;
|
|
orbi->orb_lo = orb_lo;
|
|
orbi->status.orb_hi = htons(orb_hi);
|
|
orbi->status.orb_lo = htonl(orb_lo);
|
|
|
|
switch (mode) {
|
|
case FETCH_MGM:
|
|
fwmem_read_block(fwdev, (void *)orbi, /*spd*/2, orb_hi, orb_lo,
|
|
sizeof(u_int32_t) * 8, &orbi->orb[0],
|
|
sbp_targ_mgm_handler);
|
|
break;
|
|
case FETCH_CMD:
|
|
orbi->state = ORBI_STATUS_FETCH;
|
|
lstate->last_hi = orb_hi;
|
|
lstate->last_lo = orb_lo;
|
|
lstate->flags |= LINK_ACTIVE;
|
|
/* dequeue */
|
|
orbi->atio = (struct ccb_accept_tio *)
|
|
SLIST_FIRST(&lstate->accept_tios);
|
|
if (orbi->atio == NULL) {
|
|
printf("%s: no free atio\n", __FUNCTION__);
|
|
lstate->flags |= ATIO_STARVED;
|
|
lstate->fwdev = fwdev;
|
|
break;
|
|
}
|
|
SLIST_REMOVE_HEAD(&lstate->accept_tios, sim_links.sle);
|
|
fwmem_read_block(fwdev, (void *)orbi, /*spd*/2, orb_hi, orb_lo,
|
|
sizeof(u_int32_t) * 8, &orbi->orb[0],
|
|
sbp_targ_cmd_handler);
|
|
STAILQ_INSERT_TAIL(&lstate->orbs, orbi, link);
|
|
break;
|
|
case FETCH_POINTER:
|
|
orbi->state = ORBI_STATUS_POINTER;
|
|
fwmem_read_block(fwdev, (void *)orbi, /*spd*/2, orb_hi, orb_lo,
|
|
sizeof(u_int32_t) * 2, &orbi->orb[0],
|
|
sbp_targ_pointer_handler);
|
|
break;
|
|
default:
|
|
printf("%s: invalid mode %d\n", __FUNCTION__, mode);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sbp_targ_resp_callback(struct fw_xfer *xfer)
|
|
{
|
|
struct sbp_targ_softc *sc;
|
|
int s;
|
|
|
|
if (debug)
|
|
printf("%s: xfer=%p\n", __FUNCTION__, xfer);
|
|
sc = (struct sbp_targ_softc *)xfer->sc;
|
|
fw_xfer_unload(xfer);
|
|
xfer->recv.pay_len = SBP_TARG_RECV_LEN;
|
|
xfer->act.hand = sbp_targ_recv;
|
|
s = splfw();
|
|
STAILQ_INSERT_TAIL(&sc->fwb.xferlist, xfer, link);
|
|
splx(s);
|
|
}
|
|
|
|
static int
|
|
sbp_targ_cmd(struct fw_xfer *xfer, struct fw_device *fwdev, int lun, int reg)
|
|
{
|
|
struct sbp_targ_lstate *lstate;
|
|
struct sbp_targ_softc *sc;
|
|
int rtcode = 0;
|
|
|
|
if (lun < 0 || lun >= MAX_LUN)
|
|
return(RESP_ADDRESS_ERROR);
|
|
|
|
sc = (struct sbp_targ_softc *)xfer->sc;
|
|
lstate = sc->lstate[lun];
|
|
if (lstate == NULL)
|
|
return(RESP_ADDRESS_ERROR);
|
|
|
|
/* XXX check logined? */
|
|
switch (reg) {
|
|
case 0x08: /* ORB_POINTER */
|
|
if (debug)
|
|
printf("%s: ORB_POINTER\n", __FUNCTION__);
|
|
sbp_targ_fetch_orb(lstate->sc, fwdev,
|
|
ntohl(xfer->recv.payload[0]),
|
|
ntohl(xfer->recv.payload[1]),
|
|
lstate, FETCH_CMD);
|
|
break;
|
|
case 0x04: /* AGENT_RESET */
|
|
if (debug)
|
|
printf("%s: AGENT RESET\n", __FUNCTION__);
|
|
lstate->last_hi = 0xffff;
|
|
lstate->last_lo = 0xffffffff;
|
|
sbp_targ_abort(STAILQ_FIRST(&lstate->orbs));
|
|
break;
|
|
case 0x10: /* DOORBELL */
|
|
if (debug)
|
|
printf("%s: DOORBELL\n", __FUNCTION__);
|
|
if (lstate->last_hi == 0xffff &&
|
|
lstate->last_lo == 0xffffffff) {
|
|
printf("%s: no previous pointer(DOORBELL)\n",
|
|
__FUNCTION__);
|
|
break;
|
|
}
|
|
if ((lstate->flags & LINK_ACTIVE) != 0) {
|
|
if (debug)
|
|
printf("link active (DOORBELL)\n");
|
|
break;
|
|
}
|
|
lstate->flags |= LINK_ACTIVE;
|
|
sbp_targ_fetch_orb(lstate->sc, fwdev,
|
|
lstate->last_hi, lstate->last_lo,
|
|
lstate, FETCH_POINTER);
|
|
break;
|
|
case 0x00: /* AGENT_STATE */
|
|
printf("%s: AGENT_STATE (ignore)\n", __FUNCTION__);
|
|
break;
|
|
case 0x14: /* UNSOLICITED_STATE_ENABLE */
|
|
printf("%s: UNSOLICITED_STATE_ENABLE (ignore)\n", __FUNCTION__);
|
|
break;
|
|
default:
|
|
printf("%s: invalid register %d\n", __FUNCTION__, reg);
|
|
rtcode = RESP_ADDRESS_ERROR;
|
|
}
|
|
|
|
return (rtcode);
|
|
}
|
|
|
|
static int
|
|
sbp_targ_mgm(struct fw_xfer *xfer, struct fw_device *fwdev)
|
|
{
|
|
struct sbp_targ_softc *sc;
|
|
struct fw_pkt *fp;
|
|
|
|
sc = (struct sbp_targ_softc *)xfer->sc;
|
|
|
|
fp = &xfer->recv.hdr;
|
|
if (fp->mode.wreqb.tcode != FWTCODE_WREQB){
|
|
printf("%s: tcode = %d\n", __FUNCTION__, fp->mode.wreqb.tcode);
|
|
return(RESP_TYPE_ERROR);
|
|
}
|
|
|
|
sbp_targ_fetch_orb(sc, fwdev,
|
|
ntohl(xfer->recv.payload[0]),
|
|
ntohl(xfer->recv.payload[1]),
|
|
NULL, FETCH_MGM);
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
static void
|
|
sbp_targ_recv(struct fw_xfer *xfer)
|
|
{
|
|
struct fw_pkt *fp, *sfp;
|
|
struct fw_device *fwdev;
|
|
u_int32_t lo;
|
|
int s, rtcode;
|
|
struct sbp_targ_softc *sc;
|
|
|
|
s = splfw();
|
|
sc = (struct sbp_targ_softc *)xfer->sc;
|
|
fp = &xfer->recv.hdr;
|
|
fwdev = fw_noderesolve_nodeid(sc->fd.fc, fp->mode.wreqb.src & 0x3f);
|
|
if (fwdev == NULL) {
|
|
printf("%s: cannot resolve nodeid=%d\n",
|
|
__FUNCTION__, fp->mode.wreqb.src & 0x3f);
|
|
rtcode = RESP_TYPE_ERROR; /* XXX */
|
|
goto done;
|
|
}
|
|
lo = fp->mode.wreqb.dest_lo;
|
|
if (lo == SBP_TARG_BIND_LO(-1))
|
|
rtcode = sbp_targ_mgm(xfer, fwdev);
|
|
else if (lo >= SBP_TARG_BIND_LO(0))
|
|
rtcode = sbp_targ_cmd(xfer, fwdev, SBP_TARG_LUN(lo), lo % 0x20);
|
|
else
|
|
rtcode = RESP_ADDRESS_ERROR;
|
|
|
|
done:
|
|
if (rtcode != 0)
|
|
printf("%s: rtcode = %d\n", __FUNCTION__, rtcode);
|
|
sfp = &xfer->send.hdr;
|
|
xfer->send.spd = 2; /* XXX */
|
|
xfer->act.hand = sbp_targ_resp_callback;
|
|
xfer->retry_req = fw_asybusy;
|
|
sfp->mode.wres.dst = fp->mode.wreqb.src;
|
|
sfp->mode.wres.tlrt = fp->mode.wreqb.tlrt;
|
|
sfp->mode.wres.tcode = FWTCODE_WRES;
|
|
sfp->mode.wres.rtcode = rtcode;
|
|
sfp->mode.wres.pri = 0;
|
|
|
|
fw_asyreq(xfer->fc, -1, xfer);
|
|
splx(s);
|
|
}
|
|
|
|
static int
|
|
sbp_targ_attach(device_t dev)
|
|
{
|
|
struct sbp_targ_softc *sc;
|
|
struct cam_devq *devq;
|
|
struct fw_xfer *xfer;
|
|
int i;
|
|
|
|
sc = (struct sbp_targ_softc *) device_get_softc(dev);
|
|
bzero((void *)sc, sizeof(struct sbp_targ_softc));
|
|
|
|
sc->fd.fc = device_get_ivars(dev);
|
|
sc->fd.dev = dev;
|
|
sc->fd.post_explore = NULL;
|
|
sc->fd.post_busreset = (void *) sbp_targ_post_busreset;
|
|
|
|
devq = cam_simq_alloc(/*maxopenings*/1);
|
|
if (devq == NULL)
|
|
return (ENXIO);
|
|
|
|
sc->sim = cam_sim_alloc(sbp_targ_action, sbp_targ_poll,
|
|
"sbp_targ", sc, device_get_unit(dev),
|
|
/*untagged*/ 1, /*tagged*/ 1, devq);
|
|
if (sc->sim == NULL) {
|
|
cam_simq_free(devq);
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (xpt_bus_register(sc->sim, /*bus*/0) != CAM_SUCCESS)
|
|
goto fail;
|
|
|
|
if (xpt_create_path(&sc->path, /*periph*/ NULL, cam_sim_path(sc->sim),
|
|
CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
|
|
xpt_bus_deregister(cam_sim_path(sc->sim));
|
|
goto fail;
|
|
}
|
|
|
|
sc->fwb.start = SBP_TARG_BIND_START;
|
|
sc->fwb.end = SBP_TARG_BIND_END;
|
|
sc->fwb.act_type = FWACT_XFER;
|
|
|
|
/* pre-allocate xfer */
|
|
STAILQ_INIT(&sc->fwb.xferlist);
|
|
for (i = 0; i < MAX_LUN /* XXX */; i ++) {
|
|
xfer = fw_xfer_alloc_buf(M_SBP_TARG,
|
|
/* send */ 0,
|
|
/* recv */ SBP_TARG_RECV_LEN);
|
|
xfer->act.hand = sbp_targ_recv;
|
|
xfer->fc = sc->fd.fc;
|
|
xfer->sc = (caddr_t)sc;
|
|
STAILQ_INSERT_TAIL(&sc->fwb.xferlist, xfer, link);
|
|
}
|
|
fw_bindadd(sc->fd.fc, &sc->fwb);
|
|
return 0;
|
|
|
|
fail:
|
|
cam_sim_free(sc->sim, /*free_devq*/TRUE);
|
|
return (ENXIO);
|
|
}
|
|
|
|
static int
|
|
sbp_targ_detach(device_t dev)
|
|
{
|
|
struct sbp_targ_softc *sc;
|
|
struct sbp_targ_lstate *lstate;
|
|
struct fw_xfer *xfer, *next;
|
|
int i;
|
|
|
|
sc = (struct sbp_targ_softc *)device_get_softc(dev);
|
|
sc->fd.post_busreset = NULL;
|
|
|
|
xpt_free_path(sc->path);
|
|
xpt_bus_deregister(cam_sim_path(sc->sim));
|
|
cam_sim_free(sc->sim, /*free_devq*/TRUE);
|
|
|
|
for (i = 0; i < MAX_LUN; i ++) {
|
|
lstate = sc->lstate[i];
|
|
if (lstate != NULL) {
|
|
xpt_free_path(lstate->path);
|
|
free(lstate, M_SBP_TARG);
|
|
}
|
|
}
|
|
if (sc->black_hole != NULL) {
|
|
xpt_free_path(sc->black_hole->path);
|
|
free(sc->black_hole, M_SBP_TARG);
|
|
}
|
|
|
|
for (xfer = STAILQ_FIRST(&sc->fwb.xferlist);
|
|
xfer != NULL; xfer = next) {
|
|
next = STAILQ_NEXT(xfer, link);
|
|
fw_xfer_free_buf(xfer);
|
|
}
|
|
STAILQ_INIT(&sc->fwb.xferlist);
|
|
fw_bindremove(sc->fd.fc, &sc->fwb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static devclass_t sbp_targ_devclass;
|
|
|
|
static device_method_t sbp_targ_methods[] = {
|
|
/* device interface */
|
|
DEVMETHOD(device_identify, sbp_targ_identify),
|
|
DEVMETHOD(device_probe, sbp_targ_probe),
|
|
DEVMETHOD(device_attach, sbp_targ_attach),
|
|
DEVMETHOD(device_detach, sbp_targ_detach),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t sbp_targ_driver = {
|
|
"sbp_targ",
|
|
sbp_targ_methods,
|
|
sizeof(struct sbp_targ_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(sbp_targ, firewire, sbp_targ_driver, sbp_targ_devclass, 0, 0);
|
|
MODULE_VERSION(sbp_targ, 1);
|
|
MODULE_DEPEND(sbp_targ, firewire, 1, 1, 1);
|
|
MODULE_DEPEND(sbp_targ, cam, 1, 1, 1);
|