Files
scst/qla_isp/linux/isp_scst.c
Vladislav Bolkhovitin 451443a6c8 Patch from Stanislaw Gruszka <stanislawg1@open-e.com>
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
2007-06-25 16:32:46 +00:00

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(&current->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
*/