mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-18 03:01:26 +00:00
git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@147 d57e44dd-8a1f-0410-8b47-8ef2f437770f
1097 lines
29 KiB
C
1097 lines
29 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
|
|
|
|
/* usefull pointers when data is processed */
|
|
#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;
|
|
|
|
#define Eprintk(fmt, args...) printk(KERN_ERR "isp_scst(%s): " fmt, __FUNCTION__, ##args)
|
|
#define Iprintk(fmt, args...) printk(KERN_INFO "isp_scst(%s): " fmt, __FUNCTION__, ##args)
|
|
|
|
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, uint64_t iid)
|
|
{
|
|
ini_t *nptr;
|
|
char ini_name[24];
|
|
|
|
if (!bp->scst_tgt) {
|
|
Eprintk("cannot find SCST target for incoming command\n");
|
|
return (NULL);
|
|
}
|
|
|
|
nptr = kmalloc(sizeof(ini_t), GFP_KERNEL);
|
|
if (!nptr) {
|
|
Eprintk("cannot allocate initiator data\n");
|
|
return (NULL);
|
|
}
|
|
|
|
#define GET(byte) (uint8_t) ((iid >> 8*byte) & 0xff)
|
|
snprintf(ini_name, sizeof(ini_name), "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
|
|
GET(7), GET(6), GET(5) , GET(4), GET(3), GET(2), GET(1), GET(0));
|
|
#undef GET
|
|
|
|
nptr->ini_scst_sess = scst_register_session(bp->scst_tgt, 0, ini_name, NULL, NULL);
|
|
if (!nptr->ini_scst_sess) {
|
|
Eprintk("cannot register SCST session\n");
|
|
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_data_direction dir;
|
|
|
|
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;
|
|
}
|
|
|
|
dir = SCST_DATA_UNKNOWN; // bidirectional or no transfer
|
|
if ((tmd->cd_lflags & CDFL_DATA_OUT) && !(tmd->cd_lflags & CDFL_DATA_IN)) {
|
|
dir = SCST_DATA_WRITE;
|
|
} else if (tmd->cd_lflags & CDFL_DATA_IN) {
|
|
dir = SCST_DATA_READ;
|
|
}
|
|
scst_cmd_set_expected(scst_cmd, dir, tmd->cd_totlen);
|
|
|
|
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);
|
|
Eprintk("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, tmd->cd_iid);
|
|
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)
|
|
{
|
|
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);
|
|
|
|
scst_cmd = tmd->cd_scst_cmd;
|
|
if (!scst_cmd) {
|
|
Eprintk("TMD_DONE cannot find scst command\n");
|
|
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) {
|
|
int rx_status = SCST_RX_STATUS_SUCCESS;
|
|
|
|
if (tmd->cd_lflags & CDFL_ERROR) {
|
|
rx_status = SCST_RX_STATUS_ERROR;
|
|
}
|
|
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);
|
|
Eprintk("cannot register any more SCSI busses\n");
|
|
break;
|
|
}
|
|
hp = arg;
|
|
if (hp->r_version != QR_VERSION) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
Eprintk("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();
|
|
Iprintk("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);
|
|
Eprintk("TMD_NOTIFY cannot find bus\n");
|
|
break;
|
|
}
|
|
|
|
ini = ini_from_notify(bp, np);
|
|
if (ini == NULL) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
Eprintk("TMD_NOTIFY cannot find initiator\n");
|
|
(*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);
|
|
Eprintk("HBA_UNREG cannot find bus\n");
|
|
break;
|
|
}
|
|
memset(&bp->h, 0, sizeof (hba_register_t));
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
schedule_unregister_scst();
|
|
Iprintk("unregistering %s%d\n", hp->r_name, hp->r_inst);
|
|
(hp->r_action)(QIN_HBA_UNREG, arg);
|
|
break;
|
|
}
|
|
default:
|
|
Eprintk("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);
|
|
|
|
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 void
|
|
fill_sense(tmd_cmd_t *tmd, const uint8_t *sbuf, uint8_t slen)
|
|
{
|
|
if (slen > TMD_SENSELEN) {
|
|
// FIXME: maybe increase TMD_SENSELEN ?
|
|
// Eprintk("sense data too big (totlen %u len %u)\n", TMD_SENSELEN, slen);
|
|
slen = TMD_SENSELEN;
|
|
}
|
|
|
|
memcpy(tmd->cd_sense, sbuf, slen);
|
|
tmd->cd_hflags |= CDFH_SNSVALID;
|
|
tmd->cd_xfrlen = 0;
|
|
tmd->cd_hflags &= ~CDFH_DATA_MASK;
|
|
|
|
if (scsi_tdebug) {
|
|
uint8_t key, asc, ascq;
|
|
key = (slen >= 2) ? sbuf[2] : 0;
|
|
asc = (slen >= 12) ? sbuf[12] : 0;
|
|
ascq = (slen >= 13) ? sbuf[13] : 0;
|
|
SDprintk("%s: key 0x%02x asc 0x%02x ascq 0x%02x\n", __FUNCTION__, key, asc, ascq);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (scst_cmd_get_data_direction(scst_cmd) == SCST_DATA_READ) {
|
|
unsigned int len = scst_cmd_get_resp_data_len(scst_cmd);
|
|
if (len > tmd->cd_totlen) {
|
|
/* this shouldn't happen */
|
|
const uint8_t ifailure[TMD_SENSELEN] = { 0xf0, 0, 0x4, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0x44 };
|
|
|
|
Eprintk("data size too big (totlen %u len %u)\n", tmd->cd_totlen, len);
|
|
WARN_ON(1);
|
|
|
|
fill_sense(tmd, ifailure, TMD_SENSELEN);
|
|
|
|
tmd->cd_hflags |= CDFH_STSVALID;
|
|
tmd->cd_scsi_status = SCSI_CHECK;
|
|
goto out;
|
|
|
|
} else {
|
|
tmd->cd_hflags |= CDFH_DATA_IN;
|
|
tmd->cd_xfrlen = len;
|
|
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) {
|
|
uint8_t *sbuf = scst_cmd_get_sense_buffer(scst_cmd);
|
|
unsigned int slen = scst_cmd_get_sense_buffer_len(scst_cmd);
|
|
|
|
fill_sense(tmd, sbuf, slen);
|
|
}
|
|
SDprintk2("%s: status %d\n", __FUNCTION__, scst_cmd_get_status(scst_cmd));
|
|
}
|
|
|
|
out:
|
|
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 {
|
|
Eprintk("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) {
|
|
Eprintk("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
|
|
*/
|