mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-25 07:51:28 +00:00
This is SCST driver for ISP Qlogic chipsets commonly used in many SCSI and FC host bus adapters. Supported chipset are listed in README file, incomplete list of supported HBA's is in doc/Hardware.txt . It is based on Matthew Jacob's multiplatform driver for ISP chipsets, which can be download from ftp://ftp.feral.com/pub/isp/isp_dist.tgz git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@135 d57e44dd-8a1f-0410-8b47-8ef2f437770f
1053 lines
28 KiB
C
1053 lines
28 KiB
C
/*
|
|
* Copyright (c) 1997-2007 by Matthew Jacob
|
|
* 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.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
|
|
*
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of the
|
|
* the GNU Public License ("GPL") with platforms where the prevalant license
|
|
* is the GNU Public License:
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*
|
|
* Matthew Jacob
|
|
* Feral Software
|
|
* 421 Laurel Avenue
|
|
* Menlo Park, CA 94025
|
|
* USA
|
|
*
|
|
* gplbsd at feral com
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Qlogic ISP target driver for SCST.
|
|
* Copyright 2007 by Stanislaw Gruszka <stanislawg1@open-e.com>
|
|
*/
|
|
|
|
#ifdef MODULE
|
|
#define EXPORT_SYMTAB
|
|
#else
|
|
#error "this can only be built as a module"
|
|
#endif
|
|
|
|
#include <linux/version.h>
|
|
#ifndef KERNEL_VERSION
|
|
#define KERNEL_VERSION(v,p,s) (((v)<<16)+(p<<8)+s)
|
|
#endif
|
|
#include <linux/autoconf.h>
|
|
#ifdef CONFIG_SMP
|
|
#define __SMP__ 1
|
|
#endif
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/autoconf.h>
|
|
#include <linux/init.h>
|
|
#include <linux/types.h>
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
|
|
#include <linux/blk.h>
|
|
#endif
|
|
#include <linux/blkdev.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/slab.h>
|
|
#include <scsi/scsi.h>
|
|
#include <asm/dma.h>
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/spinlock.h>
|
|
#include <asm/scatterlist.h>
|
|
#include <asm/system.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
#include <scsi/scsi_host.h>
|
|
#include <scsi_tgt.h>
|
|
|
|
#ifdef min
|
|
#undef min
|
|
#endif
|
|
#define min(a,b) (((a)<(b))?(a):(b))
|
|
|
|
#include "isp_tpublic.h"
|
|
#include "linux/smp_lock.h"
|
|
|
|
#define DEFAULT_DEVICE_TYPE 0 /* DISK */
|
|
#define MAX_BUS 8
|
|
#define MAX_LUN 64
|
|
|
|
/* FIXME: comment me */
|
|
#define cd_scst_cmd cd_hreserved[0].ptrs[0]
|
|
#define cd_bus cd_hreserved[1].ptrs[0]
|
|
|
|
#ifndef SCSI_GOOD
|
|
#define SCSI_GOOD 0x0
|
|
#endif
|
|
#ifndef SCSI_BUSY
|
|
#define SCSI_BUSY 0x8
|
|
#endif
|
|
#ifndef SCSI_CHECK
|
|
#define SCSI_CHECK 0x2
|
|
#endif
|
|
#ifndef SCSI_QFULL
|
|
#define SCSI_QFULL 0x28
|
|
#endif
|
|
|
|
#ifndef SERVICE_ACTION_IN
|
|
#define SERVICE_ACTION_IN 0x9e
|
|
#endif
|
|
#ifndef SAI_READ_CAPACITY_16
|
|
#define SAI_READ_CAPACITY_16 0x10
|
|
#endif
|
|
|
|
#include "scsi_target.h"
|
|
|
|
#ifndef SERNO
|
|
#define SERNO "000000"
|
|
#endif
|
|
|
|
typedef struct bus bus_t;
|
|
typedef struct initiator ini_t;
|
|
|
|
struct initiator {
|
|
ini_t * ini_next;
|
|
bus_t * ini_bus; /* backpointer to containing bus */
|
|
uint64_t ini_iid; /* initiator identifier */
|
|
struct scst_session * ini_scst_sess; /* FIXME: comment me */
|
|
};
|
|
|
|
#define HASH_WIDTH 16
|
|
#define INI_HASH_LISTP(busp, ini_id) busp->list[ini_id & (HASH_WIDTH - 1)]
|
|
|
|
struct bus {
|
|
hba_register_t h; /* must be first */
|
|
ini_t * list[HASH_WIDTH]; /* hash list of known initiators */
|
|
struct scst_tgt * scst_tgt;
|
|
int enable;
|
|
};
|
|
|
|
#define SDprintk if (scsi_tdebug) printk
|
|
#define SDprintk2 if (scsi_tdebug > 1) printk
|
|
#define SDprintk3 if (scsi_tdebug > 2) printk
|
|
|
|
static int scsi_tdebug = 0;
|
|
|
|
static void scsi_target_handler(qact_e, void *);
|
|
|
|
static __inline bus_t *bus_from_tmd(tmd_cmd_t *);
|
|
static __inline bus_t *bus_from_name(const char *);
|
|
static __inline ini_t *ini_from_tmd(bus_t *, tmd_cmd_t *);
|
|
static __inline ini_t *ini_from_notify(bus_t *, tmd_notify_t *);
|
|
|
|
static void scsi_target_start_cmd(tmd_cmd_t *, int);
|
|
static void scsi_target_done_cmd(tmd_cmd_t *, int);
|
|
static int scsi_target_thread(void *);
|
|
static int scsi_target_enadis(bus_t *, int);
|
|
|
|
static bus_t busses[MAX_BUS];
|
|
|
|
DECLARE_MUTEX_LOCKED(scsi_thread_sleep_semaphore);
|
|
DECLARE_MUTEX_LOCKED(scsi_thread_entry_exit_semaphore);
|
|
static tmd_cmd_t *p_front = NULL, *p_last = NULL;
|
|
|
|
static spinlock_t scsi_target_lock = SPIN_LOCK_UNLOCKED;
|
|
static int scsi_target_thread_exit = 0;
|
|
static int register_scst_flg = 0;
|
|
static int unregister_scst_flg = 0;
|
|
|
|
static __inline int
|
|
validate_bus_pointer(bus_t *bp, void *identity)
|
|
{
|
|
if (bp >= busses && bp < &busses[MAX_BUS]) {
|
|
if (bp->h.r_action) {
|
|
if (bp->h.r_identity == identity) {
|
|
return (1);
|
|
}
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static __inline bus_t *
|
|
bus_from_tmd(tmd_cmd_t *tmd)
|
|
{
|
|
bus_t *bp;
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
if (validate_bus_pointer(bp, tmd->cd_hba)) {
|
|
return (bp);
|
|
}
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static __inline bus_t *
|
|
bus_from_name(const char *name)
|
|
{
|
|
bus_t *bp;
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
char localbuf[32];
|
|
if (bp->h.r_action == NULL) {
|
|
continue;
|
|
}
|
|
snprintf(localbuf, sizeof (localbuf), "%s%d", bp->h.r_name, bp->h.r_inst);
|
|
if (strncmp(name, localbuf, sizeof (localbuf) - 1) == 0) {
|
|
return (bp);
|
|
}
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static __inline ini_t *
|
|
ini_from_tmd(bus_t *bp, tmd_cmd_t *tmd)
|
|
{
|
|
ini_t *ptr = INI_HASH_LISTP(bp, tmd->cd_iid);
|
|
if (ptr) {
|
|
do {
|
|
if (ptr->ini_iid == tmd->cd_iid) {
|
|
return (ptr);
|
|
}
|
|
} while ((ptr = ptr->ini_next) != NULL);
|
|
}
|
|
return (ptr);
|
|
}
|
|
|
|
static __inline ini_t *
|
|
ini_from_notify(bus_t *bp, tmd_notify_t *np)
|
|
{
|
|
ini_t *ptr = INI_HASH_LISTP(bp, np->nt_iid);
|
|
if (ptr) {
|
|
do {
|
|
if (ptr->ini_iid == np->nt_iid) {
|
|
return (ptr);
|
|
}
|
|
} while ((ptr = ptr->ini_next) != NULL);
|
|
}
|
|
return (ptr);
|
|
}
|
|
|
|
static __inline bus_t *
|
|
bus_from_notify(tmd_notify_t *np)
|
|
{
|
|
bus_t *bp;
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
if (bp->h.r_action == NULL) {
|
|
continue;
|
|
}
|
|
if (bp->h.r_identity == np->nt_hba) {
|
|
return (bp);
|
|
}
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Make an initiator structure
|
|
*/
|
|
|
|
static ini_t *
|
|
alloc_ini(bus_t *bp)
|
|
{
|
|
ini_t *nptr;
|
|
|
|
// FIXME: info to user - scst not register ours buses
|
|
if (!bp || !bp->scst_tgt)
|
|
return (NULL);
|
|
|
|
nptr = kmalloc(sizeof(ini_t), GFP_KERNEL);
|
|
if (!nptr)
|
|
return (NULL);
|
|
|
|
nptr->ini_scst_sess = scst_register_session(bp->scst_tgt, 0, bp->h.r_name, NULL, NULL);
|
|
if (!nptr->ini_scst_sess) {
|
|
kfree(nptr);
|
|
return (NULL);
|
|
}
|
|
|
|
return (nptr);
|
|
}
|
|
|
|
static void
|
|
add_ini(bus_t *bp, uint64_t iid, ini_t *nptr)
|
|
{
|
|
ini_t **ptrlptr = &INI_HASH_LISTP(bp, iid);
|
|
|
|
nptr->ini_iid = iid;
|
|
nptr->ini_bus = (struct bus *) bp;
|
|
nptr->ini_next = *ptrlptr;
|
|
|
|
*ptrlptr = nptr;
|
|
}
|
|
|
|
static void
|
|
free_ini(ini_t *ini)
|
|
{
|
|
scst_unregister_session(ini->ini_scst_sess, 0, NULL);
|
|
kfree(ini);
|
|
}
|
|
|
|
static __inline void
|
|
scsi_cmd_sched_restart_locked(tmd_cmd_t *tmd, int donotify, const char *msg)
|
|
{
|
|
SDprintk("scsi_cmd_sched_restart[%llx]: %s\n", tmd->cd_tagval, msg);
|
|
tmd->cd_private = NULL;
|
|
if (p_front) {
|
|
p_last->cd_private = tmd;
|
|
} else {
|
|
p_front = tmd;
|
|
}
|
|
p_last = tmd;
|
|
if (donotify) {
|
|
up(&scsi_thread_sleep_semaphore);
|
|
}
|
|
}
|
|
|
|
static __inline void
|
|
scsi_cmd_sched_restart(tmd_cmd_t *tmd, const char *msg)
|
|
{
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
scsi_cmd_sched_restart_locked(tmd, 1, msg);
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
}
|
|
|
|
static __inline void
|
|
schedule_register_scst(void)
|
|
{
|
|
register_scst_flg = 1;
|
|
up(&scsi_thread_sleep_semaphore);
|
|
}
|
|
|
|
static __inline void
|
|
schedule_unregister_scst(void)
|
|
{
|
|
unregister_scst_flg = 1;
|
|
up(&scsi_thread_sleep_semaphore);
|
|
}
|
|
|
|
static int
|
|
scsi_target_rx_cmd(ini_t *ini, tmd_cmd_t *tmd, int from_intr)
|
|
{
|
|
struct scst_cmd *scst_cmd;
|
|
|
|
scst_cmd = scst_rx_cmd(ini->ini_scst_sess, tmd->cd_lun, sizeof(tmd->cd_lun), tmd->cd_cdb, sizeof(tmd->cd_cdb), from_intr);
|
|
if (!scst_cmd)
|
|
return (-ENOMEM);
|
|
|
|
scst_cmd_set_tgt_priv(scst_cmd, tmd);
|
|
scst_cmd_set_tag(scst_cmd, tmd->cd_tagval);
|
|
tmd->cd_scst_cmd = scst_cmd;
|
|
|
|
switch (tmd->cd_tagtype) {
|
|
case CD_UNTAGGED:
|
|
scst_cmd->queue_type = SCST_CMD_QUEUE_UNTAGGED;
|
|
break;
|
|
case CD_SIMPLE_TAG:
|
|
scst_cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
|
|
break;
|
|
case CD_ORDERED_TAG:
|
|
scst_cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
|
|
break;
|
|
case CD_HEAD_TAG:
|
|
scst_cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
|
|
break;
|
|
case CD_ACA_TAG:
|
|
scst_cmd->queue_type = SCST_CMD_QUEUE_ACA;
|
|
break;
|
|
default:
|
|
scst_cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
|
|
break;
|
|
}
|
|
// FIXME: expected length end direction
|
|
|
|
scst_cmd_init_done(scst_cmd, SCST_CONTEXT_TASKLET);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
scsi_target_start_cmd(tmd_cmd_t *tmd, int from_intr)
|
|
{
|
|
unsigned long flags;
|
|
bus_t *bp;
|
|
ini_t *ini;
|
|
int ret;
|
|
|
|
tmd->cd_hflags = 0;
|
|
tmd->cd_scsi_status = SCSI_GOOD;
|
|
tmd->cd_data = NULL;
|
|
tmd->cd_xfrlen = 0;
|
|
tmd->cd_resid = tmd->cd_totlen;
|
|
|
|
/*
|
|
* First, find the bus.
|
|
*/
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
bp = bus_from_tmd(tmd);
|
|
if (bp == NULL) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
printk(KERN_WARNING "cannot find bus for incoming command\n");
|
|
return;
|
|
}
|
|
tmd->cd_bus = bp;
|
|
|
|
/*
|
|
* Next check if we have commands pending on the front
|
|
* queue and we're coming in at interrupt level. In order
|
|
* to preserve ordering, we force commands to come in at thread
|
|
* level if there are commands already at thread level
|
|
*/
|
|
if (from_intr && p_front) {
|
|
scsi_cmd_sched_restart_locked(tmd, 1, "from_intr && p_front");
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
return;
|
|
}
|
|
|
|
ini = ini_from_tmd(bp, tmd);
|
|
if (ini == NULL) {
|
|
ini_t *nptr;
|
|
|
|
if (from_intr) {
|
|
scsi_cmd_sched_restart_locked(tmd, 1, "had to make ini");
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
return;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
nptr = alloc_ini(bp);
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
|
|
/*
|
|
* Check again to see if it showed while we were allocating...
|
|
*/
|
|
ini = ini_from_tmd(bp, tmd);
|
|
if (ini) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
if (nptr) {
|
|
free_ini(nptr);
|
|
}
|
|
} else {
|
|
if (nptr == NULL) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
goto err;
|
|
}
|
|
add_ini(bp, tmd->cd_iid, nptr);
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
ini = nptr;
|
|
}
|
|
} else {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
}
|
|
|
|
ret = scsi_target_rx_cmd(ini, tmd, from_intr);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
return;
|
|
|
|
err:
|
|
tmd->cd_scsi_status = SCSI_BUSY;
|
|
tmd->cd_hflags |= CDFH_STSVALID;
|
|
tmd->cd_hflags &= ~CDFH_DATA_MASK;
|
|
tmd->cd_xfrlen = 0;
|
|
(*bp->h.r_action)(QIN_TMD_CONT, tmd);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
scsi_target_done_cmd(tmd_cmd_t *tmd, int from_intr)
|
|
{
|
|
// bus_t *bp;
|
|
struct scst_cmd *scst_cmd;
|
|
|
|
SDprintk2("scsi_target: TMD_DONE[%llx] %p hf %x lf %x xfrlen %d resid %d\n",
|
|
tmd->cd_tagval, tmd, tmd->cd_hflags, tmd->cd_lflags, tmd->cd_xfrlen, tmd->cd_resid);
|
|
/*
|
|
bp = bus_from_tmd(tmd);
|
|
if (bp == NULL) {
|
|
printk(KERN_WARNING "%s: TMD_DONE cannot find bus again\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
*/
|
|
scst_cmd = tmd->cd_scst_cmd;
|
|
if (!scst_cmd) {
|
|
printk(KERN_WARNING "%s TMD_DONE cannot find scst command\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
if (tmd->cd_hflags & CDFH_STSVALID) {
|
|
if (tmd->cd_hflags & CDFH_DATA_IN) {
|
|
tmd->cd_hflags &= ~CDFH_DATA_MASK;
|
|
tmd->cd_xfrlen = 0;
|
|
}
|
|
scst_tgt_cmd_done(scst_cmd);
|
|
return;
|
|
}
|
|
|
|
if (tmd->cd_hflags & CDFH_DATA_OUT) {
|
|
if (tmd->cd_resid == 0) {
|
|
if (tmd->cd_xfrlen) {
|
|
// FIXME: error handle
|
|
scst_rx_data(scst_cmd, SCST_RX_STATUS_SUCCESS, SCST_CONTEXT_TASKLET);
|
|
} else {
|
|
scst_tgt_cmd_done(scst_cmd);
|
|
}
|
|
} else {
|
|
; /* we don't have all data, do nothing */
|
|
}
|
|
} else if (tmd->cd_hflags & CDFH_DATA_IN) {
|
|
tmd->cd_hflags &= ~CDFH_DATA_MASK;
|
|
tmd->cd_xfrlen = 0;
|
|
scst_tgt_cmd_done(scst_cmd);
|
|
}
|
|
}
|
|
|
|
void
|
|
scsi_target_handler(qact_e action, void *arg)
|
|
{
|
|
unsigned long flags;
|
|
bus_t *bp;
|
|
|
|
switch (action) {
|
|
case QOUT_HBA_REG:
|
|
{
|
|
hba_register_t *hp;
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
if (bp->h.r_action == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
if (bp == &busses[MAX_BUS]) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
printk("scsi_target: cannot register any more SCSI busses\n");
|
|
break;
|
|
}
|
|
hp = arg;
|
|
if (hp->r_version != QR_VERSION) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
printk("scsi_target: version mismatch - compiled with %d, got %d\n", QR_VERSION, hp->r_version);
|
|
break;
|
|
}
|
|
bp->h = *hp;
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
schedule_register_scst();
|
|
printk("scsi_target: registering %s%d\n", hp->r_name, hp->r_inst);
|
|
(hp->r_action)(QIN_HBA_REG, arg);
|
|
break;
|
|
}
|
|
case QOUT_ENABLE:
|
|
{
|
|
enadis_t *ep = arg;
|
|
if (ep->en_private) {
|
|
up(ep->en_private);
|
|
}
|
|
break;
|
|
}
|
|
case QOUT_DISABLE:
|
|
{
|
|
enadis_t *ep = arg;
|
|
if (ep->en_private) {
|
|
up(ep->en_private);
|
|
}
|
|
break;
|
|
}
|
|
case QOUT_TMD_START:
|
|
{
|
|
tmd_cmd_t *tmd = arg;
|
|
SDprintk2("scsi_target: TMD_START[%llx] %p cdb0=%x\n", tmd->cd_tagval, tmd, tmd->cd_cdb[0] & 0xff);
|
|
scsi_target_start_cmd(arg, 1);
|
|
break;
|
|
}
|
|
case QOUT_TMD_DONE:
|
|
{
|
|
tmd_cmd_t *tmd = arg;
|
|
SDprintk2("scsi_target: TMD_DONE[%llx] %p cdb0=%x\n", tmd->cd_tagval, tmd, tmd->cd_cdb[0] & 0xff);
|
|
scsi_target_done_cmd(arg, 1);
|
|
break;
|
|
}
|
|
case QOUT_NOTIFY:
|
|
{
|
|
ini_t *ini;
|
|
tmd_notify_t *np = arg;
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
|
|
bp = bus_from_notify(arg);
|
|
if (bp == NULL) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
printk(KERN_WARNING "%s: TMD_NOTIFY cannot find bus\n", __FUNCTION__);
|
|
break;
|
|
}
|
|
|
|
ini = ini_from_notify(bp, np);
|
|
if (ini == NULL) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
printk(KERN_WARNING "%s: TMD_NOTIFY cannot find initiator\n", __FUNCTION__);
|
|
(*bp->h.r_action) (QIN_NOTIFY_ACK, arg);
|
|
break;
|
|
}
|
|
|
|
if (np->nt_ncode == NT_ABORT_TASK) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
scst_rx_mgmt_fn_tag(ini->ini_scst_sess, SCST_ABORT_TASK, np->nt_tagval, 1, arg);
|
|
} else {
|
|
uint8_t lunbuf[8];
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
SDprintk("scsi_target: MGT code %x from %s%d\n", np->nt_ncode, bp->h.r_name, bp->h.r_inst);
|
|
FLATLUN_TO_L0LUN(lunbuf, np->nt_lun);
|
|
scst_rx_mgmt_fn_lun(ini->ini_scst_sess, np->nt_ncode, lunbuf, sizeof(lunbuf), 1, arg);
|
|
}
|
|
break;
|
|
}
|
|
case QOUT_HBA_UNREG:
|
|
{
|
|
hba_register_t *hp = arg;
|
|
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
if (bp->h.r_action == NULL) {
|
|
continue;
|
|
}
|
|
if (bp->h.r_identity == hp->r_identity) {
|
|
break;
|
|
}
|
|
}
|
|
if (bp == &busses[MAX_BUS]) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
printk(KERN_WARNING "%s: HBA_UNREG cannot find busp)\n", __FUNCTION__);
|
|
break;
|
|
}
|
|
memset(&bp->h, 0, sizeof (hba_register_t));
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
schedule_unregister_scst();
|
|
printk("scsi_target: unregistering %s%d\n", hp->r_name, hp->r_inst);
|
|
(hp->r_action)(QIN_HBA_UNREG, arg);
|
|
break;
|
|
}
|
|
default:
|
|
printk("scsi_target: action code %d (0x%x)?\n", action, action);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int register_scst(void);
|
|
static void unregister_scst(void);
|
|
|
|
static int
|
|
scsi_target_thread(void *arg)
|
|
{
|
|
unsigned long flags;
|
|
|
|
siginitsetinv(¤t->blocked, 0);
|
|
lock_kernel();
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
|
|
daemonize();
|
|
snprintf(current->comm, sizeof (current->comm), "scsi_target_thread");
|
|
#else
|
|
daemonize("scsi_target_thread");
|
|
#endif
|
|
unlock_kernel();
|
|
up(&scsi_thread_entry_exit_semaphore);
|
|
SDprintk("scsi_target_thread starting\n");
|
|
|
|
while (scsi_target_thread_exit == 0) {
|
|
tmd_cmd_t *tp;
|
|
|
|
SDprintk3("scsi_task_thread sleeping\n");
|
|
down(&scsi_thread_sleep_semaphore);
|
|
SDprintk3("scsi_task_thread running\n");
|
|
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
if ((tp = p_front) != NULL) {
|
|
p_last = p_front = NULL;
|
|
}
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
while (tp) {
|
|
tmd_cmd_t *nxt = tp->cd_private;
|
|
tp->cd_private = NULL;
|
|
scsi_target_start_cmd(tp, 0);
|
|
tp = nxt;
|
|
}
|
|
|
|
if (register_scst_flg) {
|
|
register_scst_flg = 0;
|
|
register_scst();
|
|
}
|
|
|
|
if (unregister_scst_flg) {
|
|
unregister_scst_flg = 0;
|
|
unregister_scst();
|
|
}
|
|
}
|
|
SDprintk("scsi_target_thread exiting\n");
|
|
up(&scsi_thread_entry_exit_semaphore);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
scsi_target_enadis(bus_t *bp, int en)
|
|
{
|
|
DECLARE_MUTEX_LOCKED(rsem);
|
|
enadis_t ec;
|
|
|
|
/*
|
|
* XXX: yes, there is a race condition here where the bus can
|
|
* XXX: go away. But in order to solve it, we have to make the
|
|
* XXX: bus structure stay around while we call into the HBA
|
|
* XXX: anyway, so fooey,.
|
|
*/
|
|
if (bp == NULL) {
|
|
SDprintk("%s: cannot find bus\n", __FUNCTION__);
|
|
return (-ENXIO);
|
|
}
|
|
|
|
if (bp->enable == en)
|
|
return (0);
|
|
|
|
memset(&ec, 0, sizeof (ec));
|
|
ec.en_hba = bp->h.r_identity;
|
|
ec.en_tgt = TGT_ANY;
|
|
if (bp->h.r_type == R_FC) {
|
|
SDprintk("%s: ANY LUN acceptable\n", __FUNCTION__);
|
|
ec.en_lun = LUN_ANY;
|
|
} else {
|
|
ec.en_lun = 0;
|
|
}
|
|
ec.en_private = &rsem;
|
|
|
|
(*bp->h.r_action)(en ? QIN_ENABLE : QIN_DISABLE, &ec);
|
|
down(&rsem);
|
|
|
|
if (ec.en_error) {
|
|
SDprintk("%s: HBA returned %d for %s action\n", __FUNCTION__, ec.en_error, en? "enable" : "disable");
|
|
return (ec.en_error);
|
|
}
|
|
|
|
bp->enable = en;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
isp_detect(struct scst_tgt_template *tgt_template)
|
|
{
|
|
schedule_register_scst();
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
isp_release(struct scst_tgt *tgt)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
isp_rdy_to_xfer(struct scst_cmd *scst_cmd)
|
|
{
|
|
tmd_cmd_t *tmd;
|
|
bus_t *bp;
|
|
|
|
tmd = (tmd_cmd_t *) scst_cmd_get_tgt_priv(scst_cmd);
|
|
|
|
// FIXME: this function is called for reading ?
|
|
if (scst_cmd_get_data_direction(scst_cmd) == SCST_DATA_READ) {
|
|
tmd->cd_hflags |= CDFH_DATA_IN;
|
|
SDprintk("%s: read nbytes %u\n", __FUNCTION__, scst_cmd_get_bufflen(scst_cmd));
|
|
}
|
|
|
|
if (scst_cmd_get_data_direction(scst_cmd) == SCST_DATA_WRITE) {
|
|
tmd->cd_hflags |= CDFH_DATA_OUT;
|
|
tmd->cd_data = scst_cmd_get_sg(scst_cmd);
|
|
tmd->cd_xfrlen = scst_cmd_get_bufflen(scst_cmd);
|
|
SDprintk("%s: write nbytes %u\n", __FUNCTION__, scst_cmd_get_bufflen(scst_cmd));
|
|
|
|
bp = bus_from_tmd(tmd);
|
|
(*bp->h.r_action)(QIN_TMD_CONT, tmd);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
isp_xmit_response(struct scst_cmd *scst_cmd)
|
|
{
|
|
tmd_cmd_t *tmd;
|
|
bus_t *bp;
|
|
|
|
tmd = (tmd_cmd_t *) scst_cmd_get_tgt_priv(scst_cmd);
|
|
|
|
//FIXME: maximum length
|
|
//unsigned int len = min(scst_cmd_get_resp_data_len(scst_cmd), tmd->cd_totlen);
|
|
if (scst_cmd_get_data_direction(scst_cmd) == SCST_DATA_READ) {
|
|
tmd->cd_hflags |= CDFH_DATA_IN;
|
|
tmd->cd_xfrlen = scst_cmd_get_resp_data_len(scst_cmd);
|
|
tmd->cd_data = scst_cmd_get_sg(scst_cmd);
|
|
} else {
|
|
/* finished write to target or command with no data */
|
|
tmd->cd_xfrlen = 0;
|
|
tmd->cd_hflags &= ~CDFH_DATA_MASK;
|
|
}
|
|
|
|
if (scst_cmd_get_tgt_resp_flags(scst_cmd) & SCST_TSC_FLAG_STATUS) {
|
|
tmd->cd_hflags |= CDFH_STSVALID;
|
|
tmd->cd_scsi_status = scst_cmd_get_status(scst_cmd);
|
|
|
|
if (tmd->cd_scsi_status == SCSI_CHECK) {
|
|
unsigned int slen = scst_cmd_get_sense_buffer_len(scst_cmd);
|
|
if (slen > TMD_SENSELEN) {
|
|
// FIMXE: info to user
|
|
slen = TMD_SENSELEN;
|
|
}
|
|
memcpy(tmd->cd_sense, scst_cmd_get_sense_buffer(scst_cmd), slen);
|
|
tmd->cd_hflags |= CDFH_SNSVALID;
|
|
tmd->cd_xfrlen = 0;
|
|
tmd->cd_hflags &= ~CDFH_DATA_MASK;
|
|
}
|
|
|
|
SDprintk2("%s: status %d\n", __FUNCTION__, scst_cmd_get_status(scst_cmd));
|
|
}
|
|
|
|
bp = bus_from_tmd(tmd);
|
|
(*bp->h.r_action)(QIN_TMD_CONT, tmd);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
isp_on_free_cmd(struct scst_cmd *scst_cmd)
|
|
{
|
|
tmd_cmd_t *tmd;
|
|
bus_t *bp;
|
|
|
|
tmd = (tmd_cmd_t *) scst_cmd_get_tgt_priv(scst_cmd);
|
|
tmd->cd_data = NULL;
|
|
|
|
SDprintk("%s: TMD_FIN[%llx]\n", __FUNCTION__, tmd->cd_tagval);
|
|
bp = bus_from_tmd(tmd);
|
|
(*bp->h.r_action)(QIN_TMD_FIN, tmd);
|
|
}
|
|
|
|
static void
|
|
isp_task_mgmt_fn_done(struct scst_mgmt_cmd *mgmt_cmd)
|
|
{
|
|
tmd_notify_t *np = mgmt_cmd->tgt_priv;
|
|
bus_t *bp;
|
|
|
|
// FIXME bus can not dissapear
|
|
SDprintk("%s: NOTIFY_ACK[%llx]\n", __FUNCTION__, np->nt_tagval);
|
|
bp = bus_from_notify(np);
|
|
(*bp->h.r_action) (QIN_NOTIFY_ACK, np);
|
|
}
|
|
|
|
static int
|
|
isp_read_proc(struct seq_file *seq, struct scst_tgt *tgt)
|
|
{
|
|
bus_t *bp;
|
|
|
|
SDprintk("%s\n", __FUNCTION__);
|
|
|
|
bp = tgt->tgt_priv;
|
|
if (!bp)
|
|
return -ENODEV;
|
|
|
|
seq_printf(seq, "%d\n", bp->enable);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isp_write_proc(char *buf, char **start, off_t offset, int len, int *eof, struct scst_tgt *tgt)
|
|
{
|
|
bus_t *bp;
|
|
int ret, en;
|
|
|
|
SDprintk("%s\n", __FUNCTION__);
|
|
|
|
bp = tgt->tgt_priv;
|
|
if (!bp)
|
|
return (-ENODEV);
|
|
|
|
if (len != 2 && len != 3)
|
|
return (-EINVAL);
|
|
|
|
en = buf[0] - '0';
|
|
if (en < 0 || en > 1)
|
|
return (-EINVAL);
|
|
|
|
ret = scsi_target_enadis(bp, en);
|
|
if (ret < 0)
|
|
return (ret);
|
|
|
|
return (len);
|
|
}
|
|
|
|
static struct scst_tgt_template isp_tgt_template =
|
|
{
|
|
.sg_tablesize = SG_ALL, // FIXME do this depending of hardware ?
|
|
.name = "qla_isp",
|
|
.unchecked_isa_dma = 0,
|
|
.use_clustering = 1,
|
|
.xmit_response_atomic = 1,
|
|
.rdy_to_xfer_atomic = 1,
|
|
//.report_aen_atomic = 0,
|
|
|
|
.detect = isp_detect,
|
|
.release = isp_release,
|
|
|
|
.xmit_response = isp_xmit_response,
|
|
.rdy_to_xfer = isp_rdy_to_xfer,
|
|
.on_free_cmd = isp_on_free_cmd,
|
|
.task_mgmt_fn_done = isp_task_mgmt_fn_done,
|
|
|
|
//.report_aen = isp_report_aen,
|
|
.read_proc = isp_read_proc,
|
|
.write_proc = isp_write_proc,
|
|
};
|
|
|
|
static int
|
|
register_scst(void)
|
|
{
|
|
bus_t *bp;
|
|
int ntgts = 0;
|
|
|
|
// FIXME: race conditions
|
|
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
char name[32];
|
|
|
|
if (bp->h.r_action == NULL || bp->scst_tgt) {
|
|
continue;
|
|
}
|
|
|
|
// FIXME: give scst WWN or something like that
|
|
snprintf(name, sizeof(name), "%s%d", bp->h.r_name, bp->h.r_inst);
|
|
|
|
//bp->scst_tgt = scst_register(&isp_tgt_template);
|
|
bp->scst_tgt = scst_register(&isp_tgt_template, name);
|
|
if (bp->scst_tgt) {
|
|
SDprintk("%s: device %s\n", __FUNCTION__ ,name);
|
|
bp->scst_tgt->tgt_priv = bp;
|
|
ntgts++;
|
|
} else {
|
|
printk(KERN_ERR "cannot register scst device %s\n", name);
|
|
}
|
|
}
|
|
|
|
return (ntgts);
|
|
}
|
|
|
|
static void
|
|
unregister_scst(void)
|
|
{
|
|
bus_t *bp;
|
|
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
if (bp->h.r_action == NULL && bp->scst_tgt) {
|
|
int i;
|
|
|
|
/* remove existing initiators */
|
|
for (i = 0; i < HASH_WIDTH; i++) {
|
|
ini_t *ini_next;
|
|
ini_t *ptr = bp->list[i];
|
|
if (ptr) {
|
|
do {
|
|
ini_next = ptr->ini_next;
|
|
free_ini(ptr);
|
|
} while ((ptr = ini_next) != NULL);
|
|
}
|
|
bp->list[i] = NULL;
|
|
}
|
|
|
|
scst_unregister(bp->scst_tgt);
|
|
bp->scst_tgt = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
|
|
EXPORT_SYMBOL_NOVERS(scsi_target_handler);
|
|
MODULE_PARM(scsi_tdebug, "i");
|
|
#else
|
|
EXPORT_SYMBOL(scsi_target_handler);
|
|
module_param(scsi_tdebug, int, 0);
|
|
#endif
|
|
#ifdef MODULE_LICENSE
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
#endif
|
|
|
|
static void
|
|
start_scsi_target_thread(void)
|
|
{
|
|
kernel_thread(scsi_target_thread, NULL, 0);
|
|
down(&scsi_thread_entry_exit_semaphore);
|
|
}
|
|
|
|
static void
|
|
stop_scsi_target_thread(void)
|
|
{
|
|
scsi_target_thread_exit = 1;
|
|
up(&scsi_thread_sleep_semaphore);
|
|
down(&scsi_thread_entry_exit_semaphore);
|
|
}
|
|
|
|
int init_module(void)
|
|
{
|
|
int ret;
|
|
|
|
spin_lock_init(&scsi_target_lock);
|
|
start_scsi_target_thread();
|
|
|
|
ret = scst_register_target_template(&isp_tgt_template);
|
|
if (ret < 0) {
|
|
printk(KERN_ERR "cannot register scst target template\n");
|
|
stop_scsi_target_thread();
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* We can't get here until all hbas have deregistered
|
|
*/
|
|
void cleanup_module(void)
|
|
{
|
|
stop_scsi_target_thread();
|
|
scst_unregister_target_template(&isp_tgt_template);
|
|
}
|
|
/*
|
|
* vim:ts=4:sw=4:expandtab
|
|
*/
|