mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 09:11:27 +00:00
git-svn-id: http://svn.code.sf.net/p/scst/svn/branches/3.3.x@7715 d57e44dd-8a1f-0410-8b47-8ef2f437770f
1871 lines
53 KiB
C
1871 lines
53 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 Version 2 GNU General Public License as published
|
|
* by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*
|
|
* Matthew Jacob
|
|
* Feral Software
|
|
* 421 Laurel Avenue
|
|
* Menlo Park, CA 94025
|
|
* USA
|
|
*
|
|
* gplbsd at feral com
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Qlogic ISP target driver for SCST.
|
|
* Copyright (c) 2007 Stanislaw Gruszka
|
|
* Copyright (c) 2007, 2008 Open-E Inc
|
|
*/
|
|
|
|
/*
|
|
* This file connects tpublic API from the low level ISP driver (see common/isp_tpublic.h)
|
|
* with the SCST target driver API. Such a design does have certain disadvantages as
|
|
* opposed to using SCST target API directly in the low level driver:
|
|
* - we need to maintain duplicate data structures which are already maintained in the low
|
|
* level driver (commands queue, initiator data),
|
|
* - processing takes additional cpu time for calling procedures and processing data.
|
|
* However, the performance/memory cost is not so big, and such a design is flexible, as we
|
|
* don't need to worry about low level details (e.g. if there is support for a new chipset
|
|
* added to the low level ISP driver this code will not need to be changed).
|
|
*/
|
|
|
|
#ifndef MODULE
|
|
#error "this can only be built as a module"
|
|
#endif
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/kthread.h>
|
|
|
|
#include <asm/byteorder.h>
|
|
|
|
#define LOG_PREFIX "qla_isp"
|
|
|
|
#include <scsi/scsi_host.h>
|
|
#include <scsi/scsi.h>
|
|
#include <scst.h>
|
|
#include <scst_debug.h>
|
|
|
|
#include "isp_tpublic.h"
|
|
#include "isp_linux.h"
|
|
#include "linux/smp_lock.h"
|
|
|
|
#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]
|
|
#define cd_hnext cd_hreserved[2].ptrs[0]
|
|
#define cd_ini cd_hreserved[3].ptrs[0]
|
|
#define nt_ini nt_hreserved
|
|
|
|
/* command private flags */
|
|
#define CDF_PRIVATE_ABORTED 0x1000
|
|
|
|
#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
|
|
|
|
typedef struct bus bus_t;
|
|
typedef struct bus_chan bus_chan_t;
|
|
typedef struct initiator ini_t;
|
|
|
|
struct initiator {
|
|
ini_t * ini_next;
|
|
uint64_t ini_iid; /* initiator identifier */
|
|
struct scst_session * ini_scst_sess; /* sesson established by this remote initiator */
|
|
int ini_refcnt; /* reference counter, protected by bus_chan_t::tmds_lock */
|
|
};
|
|
|
|
#define HASH_WIDTH 16
|
|
#define INI_HASH_LISTP(bc, ini_id) bc->list[ini_id & (HASH_WIDTH - 1)]
|
|
|
|
struct bus_chan {
|
|
ini_t * list[HASH_WIDTH]; /* hash list of known initiators */
|
|
spinlock_t tmds_lock;
|
|
tmd_cmd_t * tmds_front;
|
|
tmd_cmd_t * tmds_tail;
|
|
struct tasklet_struct tasklet;
|
|
struct scst_tgt * scst_tgt;
|
|
uint64_t enable; /* is target mode enabled in low level driver, one bit per lun */
|
|
bus_t * bus; /* back pointer */
|
|
wait_queue_head_t wait_queue;
|
|
atomic_t sess_count;
|
|
};
|
|
|
|
struct bus {
|
|
hba_register_t h; /* must be first */
|
|
int need_reg; /* helpers for registration / unregistration */
|
|
hba_register_t * unreg_hp;
|
|
bus_chan_t * bchan; /* channels */
|
|
struct scst_proc_data proc_data;
|
|
};
|
|
|
|
#define DEBUG 1
|
|
|
|
#ifdef DEBUG
|
|
#define BUS_DBG(bp, fmt, args...) if (debug > 0) printk("%s%d: %s " fmt, bp->h.r_name, bp->h.r_inst, __func__, ##args)
|
|
#define BUS_DBG2(bp, fmt, args...) if (debug > 1) printk("%s%d: %s " fmt, bp->h.r_name, bp->h.r_inst, __func__, ##args)
|
|
static int debug = 0;
|
|
module_param(debug, int, 0);
|
|
#else
|
|
#define BUS_DBG(bp, fmt, args...)
|
|
#define BUS_DBG2(bp, fmt, args...)
|
|
#endif
|
|
|
|
#define Eprintk(fmt, args...) printk(KERN_ERR "isp_scst(%s): " fmt, __func__, ##args)
|
|
#define Iprintk(fmt, args...) printk(KERN_INFO "isp_scst(%s): " fmt, __func__, ##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 void scsi_target_start_cmd(tmd_cmd_t *);
|
|
static void scsi_target_done_cmd(tmd_cmd_t *);
|
|
static int scsi_target_enadis(bus_t *, uint64_t, int, int);
|
|
static void bus_chan_unregister_sessions(bus_chan_t *bc, int wait);
|
|
|
|
static bus_t busses[MAX_BUS];
|
|
|
|
static DEFINE_SPINLOCK(scsi_target_lock);
|
|
|
|
DECLARE_WAIT_QUEUE_HEAD(qlaispd_waitq);
|
|
struct task_struct *qlaispd_task;
|
|
|
|
static unsigned long qlaispd_flags = 0;
|
|
#define SF_ADD_INITIATORS 0
|
|
#define SF_REGISTER_SCST 1
|
|
#define SF_UNREGISTER_SCST 2
|
|
|
|
static __inline void
|
|
schedule_qlaispd(int flag)
|
|
{
|
|
set_bit(flag, &qlaispd_flags);
|
|
wake_up_interruptible(&qlaispd_waitq);
|
|
}
|
|
|
|
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 bus_t *
|
|
bus_from_notify(isp_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);
|
|
}
|
|
|
|
static __inline ini_t *
|
|
ini_from_iid(bus_chan_t *bc, uint64_t iid)
|
|
{
|
|
ini_t *ptr = INI_HASH_LISTP(bc, iid);
|
|
if (ptr) {
|
|
do {
|
|
if (ptr->ini_iid == iid) {
|
|
return (ptr);
|
|
}
|
|
} while ((ptr = ptr->ini_next) != NULL);
|
|
}
|
|
return (ptr);
|
|
}
|
|
|
|
static ini_t *
|
|
alloc_ini(bus_chan_t *bc, uint64_t iid)
|
|
{
|
|
ini_t *nptr;
|
|
char ini_name[24];
|
|
|
|
nptr = kmalloc(sizeof(ini_t), GFP_KERNEL);
|
|
if (!nptr) {
|
|
Eprintk("cannot allocate initiator data\n");
|
|
return (NULL);
|
|
}
|
|
memset(nptr, 0, sizeof(ini_t));
|
|
|
|
#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
|
|
|
|
/* Set the iid here so the callback to get transport ID will be able to extract it
|
|
* to generate the transport ID
|
|
*/
|
|
nptr->ini_iid = iid;
|
|
nptr->ini_scst_sess = scst_register_session(bc->scst_tgt, 0, ini_name, nptr, NULL, NULL);
|
|
if (!nptr->ini_scst_sess) {
|
|
Eprintk("cannot register SCST session\n");
|
|
kfree(nptr);
|
|
return (NULL);
|
|
}
|
|
|
|
atomic_inc(&bc->sess_count);
|
|
BUS_DBG(bc->bus, "0x%016llx, ++sess_count %d\n", iid, atomic_read(&bc->sess_count));
|
|
return (nptr);
|
|
}
|
|
|
|
static void
|
|
free_ini(bus_chan_t *bc, ini_t *ini, int wait)
|
|
{
|
|
BUS_DBG(bc->bus, "0x%016llx, sess_count-- %d, wait %d\n", ini->ini_iid, atomic_read(&bc->sess_count), wait);
|
|
scst_unregister_session(ini->ini_scst_sess, wait, NULL);
|
|
/* no wait call is only when there are no pending commands, so we can free stuff here */
|
|
kfree(ini);
|
|
atomic_dec(&bc->sess_count);
|
|
wake_up(&bc->wait_queue);
|
|
}
|
|
|
|
static void
|
|
add_ini(bus_chan_t *bc, uint64_t iid, ini_t *nptr)
|
|
{
|
|
ini_t **ptrlptr = &INI_HASH_LISTP(bc, iid);
|
|
|
|
nptr->ini_iid = iid;
|
|
nptr->ini_next = *ptrlptr;
|
|
nptr->ini_refcnt = 0;
|
|
*ptrlptr = nptr;
|
|
}
|
|
|
|
static int
|
|
del_ini(bus_chan_t *bc, uint64_t iid)
|
|
{
|
|
ini_t *ptr, *prev;
|
|
ini_t **ptrlptr = &INI_HASH_LISTP(bc, iid);
|
|
|
|
ptr = *ptrlptr;
|
|
if (ptr == NULL) {
|
|
return (0);
|
|
}
|
|
if (ptr->ini_iid == iid) {
|
|
*ptrlptr = ptr->ini_next;
|
|
ptr->ini_next = NULL;
|
|
} else {
|
|
while (1) {
|
|
prev = ptr;
|
|
ptr = ptr->ini_next;
|
|
if (ptr == NULL) {
|
|
return (0);
|
|
}
|
|
if (ptr->ini_iid == iid) {
|
|
prev->ini_next = ptr->ini_next;
|
|
ptr->ini_next = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
static __inline void
|
|
__ini_get(bus_chan_t *bc, ini_t *ini)
|
|
{
|
|
if (ini != NULL) {
|
|
ini->ini_refcnt++;
|
|
BUS_DBG2(bc->bus, "0x%016llx ++refcnt (%d)\n", ini->ini_iid, ini->ini_refcnt);
|
|
}
|
|
}
|
|
|
|
static __inline void
|
|
ini_get(bus_chan_t *bc, ini_t *ini)
|
|
{
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
__ini_get(bc, ini);
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
}
|
|
|
|
static __inline void
|
|
__ini_put(bus_chan_t *bc, ini_t *ini)
|
|
{
|
|
if (ini != NULL) {
|
|
ini->ini_refcnt--;
|
|
BUS_DBG2(bc->bus, "0x%016llx --refcnt (%d)\n", ini->ini_iid, ini->ini_refcnt);
|
|
if (ini->ini_refcnt < 0) {
|
|
free_ini(bc, ini, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static __inline void
|
|
ini_put(bus_chan_t *bc, ini_t *ini)
|
|
{
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
__ini_put(bc, ini);
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
}
|
|
|
|
static void
|
|
tasklet_rx_cmds(unsigned long data)
|
|
{
|
|
bus_chan_t *bc = (bus_chan_t *) data;
|
|
bus_t *bp = bc->bus;
|
|
ini_t *ini;
|
|
tmd_cmd_t *tmd;
|
|
tmd_xact_t *xact;
|
|
struct scst_cmd *scst_cmd;
|
|
|
|
rx_loop:
|
|
spin_lock_irq(&bc->tmds_lock);
|
|
tmd = bc->tmds_front;
|
|
if (tmd == NULL || tmd->cd_ini == NULL) {
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
return;
|
|
}
|
|
|
|
/* remove from queue */
|
|
bc->tmds_front = tmd->cd_hnext;
|
|
if (bc->tmds_front == NULL) {
|
|
bc->tmds_tail = NULL;
|
|
}
|
|
|
|
/* free command if aborted */
|
|
if (tmd->cd_flags & CDF_PRIVATE_ABORTED) {
|
|
__ini_put(bc, tmd->cd_ini);
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
BUS_DBG(bp, "ABORTED TMD_FIN[%llx]\n", tmd->cd_tagval);
|
|
(*bp->h.r_action)(QIN_TMD_FIN, tmd);
|
|
goto rx_loop;
|
|
}
|
|
|
|
ini = tmd->cd_ini;
|
|
scst_cmd = scst_rx_cmd(ini->ini_scst_sess, tmd->cd_lun, sizeof(tmd->cd_lun), tmd->cd_cdb, sizeof(tmd->cd_cdb), 1);
|
|
if (scst_cmd == NULL) {
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
tmd->cd_scsi_status = SCSI_BUSY;
|
|
xact = &tmd->cd_xact;
|
|
xact->td_hflags = TDFH_STSVALID;
|
|
xact->td_lflags = 0;
|
|
xact->td_xfrlen = 0;
|
|
(*bp->h.r_action)(QIN_TMD_CONT, xact);
|
|
goto rx_loop;
|
|
}
|
|
|
|
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_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED);
|
|
break;
|
|
case CD_SIMPLE_TAG:
|
|
scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE);
|
|
break;
|
|
case CD_ORDERED_TAG:
|
|
scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
|
|
break;
|
|
case CD_HEAD_TAG:
|
|
scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE);
|
|
break;
|
|
case CD_ACA_TAG:
|
|
scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ACA);
|
|
break;
|
|
default:
|
|
scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED);
|
|
break;
|
|
}
|
|
|
|
if (bp->h.r_type == R_FC) {
|
|
scst_data_direction dir;
|
|
int len;
|
|
|
|
dir = SCST_DATA_NONE;
|
|
if ((tmd->cd_flags & CDF_BIDIR) == CDF_BIDIR) {
|
|
dir = SCST_DATA_UNKNOWN;
|
|
} else if (tmd->cd_flags & CDF_DATA_OUT) {
|
|
dir = SCST_DATA_WRITE;
|
|
} else if (tmd->cd_flags & CDF_DATA_IN) {
|
|
dir = SCST_DATA_READ;
|
|
}
|
|
len = tmd->cd_totlen;
|
|
if (tmd->cd_cdb[0] == INQUIRY) {
|
|
len = min(len, tmd->cd_cdb[4]);
|
|
}
|
|
scst_cmd_set_expected(scst_cmd, dir, len);
|
|
}
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
|
|
scst_cmd_init_done(scst_cmd, SCST_CONTEXT_DIRECT_ATOMIC);
|
|
|
|
goto rx_loop;
|
|
}
|
|
|
|
static void
|
|
scsi_target_start_cmd(tmd_cmd_t *tmd)
|
|
{
|
|
unsigned long flags;
|
|
bus_t *bp;
|
|
bus_chan_t *bc;
|
|
|
|
/* first, find the bus */
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
bp = bus_from_tmd(tmd);
|
|
if (unlikely(bp == NULL || bp->bchan == NULL)) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
Eprintk("cannot find %s for incoming command\n", (bp == NULL) ? "bus" : "channel");
|
|
return;
|
|
}
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
|
|
BUS_DBG2(bp, "TMD_START[%llx] %p cdb0=%x\n", tmd->cd_tagval, tmd, tmd->cd_cdb[0] & 0xff);
|
|
|
|
bc = &bp->bchan[tmd->cd_channel];
|
|
if (unlikely(bc->enable == 0)) {
|
|
BUS_DBG2(bp, "TMD_START[%llx] Chan %d not enabled - finishing command\n", tmd->cd_tagval, tmd->cd_channel);
|
|
(*bp->h.r_action)(QIN_TMD_FIN, tmd);
|
|
return;
|
|
}
|
|
|
|
tmd->cd_bus = bp;
|
|
tmd->cd_hnext = NULL;
|
|
|
|
/* then, add commands to queue */
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
tmd->cd_ini = ini_from_iid(bc, tmd->cd_iid);
|
|
__ini_get(bc, tmd->cd_ini);
|
|
if (bc->tmds_front == NULL) {
|
|
bc->tmds_front = tmd;
|
|
} else {
|
|
bc->tmds_tail->cd_hnext = tmd;
|
|
}
|
|
bc->tmds_tail = tmd;
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
|
|
/* finally, schedule proper action */
|
|
if (unlikely(tmd->cd_ini == NULL)) {
|
|
schedule_qlaispd(SF_ADD_INITIATORS);
|
|
} else {
|
|
tasklet_schedule(&bc->tasklet);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bus_chan_add_initiators(bus_t *bp, int chan)
|
|
{
|
|
bus_chan_t *bc = &bp->bchan[chan];
|
|
ini_t *ini;
|
|
tmd_cmd_t *tmd;
|
|
tmd_cmd_t *prev_tmd = NULL;
|
|
tmd_xact_t *xact;
|
|
|
|
BUS_DBG(bp, "Chan %d searching new initiators\n", chan);
|
|
|
|
/* iterate over queue and find any commands not assigned to initiator */
|
|
spin_lock_irq(&bc->tmds_lock);
|
|
tmd = bc->tmds_front;
|
|
while (tmd) {
|
|
BUG_ON(tmd->cd_channel != chan);
|
|
if (tmd->cd_ini != NULL) {
|
|
/* ini assigned, go to the next command */
|
|
prev_tmd = tmd;
|
|
tmd = tmd->cd_hnext;
|
|
} else {
|
|
/* check if proper initiator exist already */
|
|
ini = ini_from_iid(bc, tmd->cd_iid);
|
|
if (ini != NULL) {
|
|
tmd->cd_ini = ini;
|
|
__ini_get(bc, ini);
|
|
} else {
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
|
|
ini = alloc_ini(bc, tmd->cd_iid);
|
|
|
|
spin_lock_irq(&bc->tmds_lock);
|
|
if (ini != NULL) {
|
|
tmd->cd_ini = ini;
|
|
add_ini(bc, tmd->cd_iid, ini);
|
|
__ini_get(bc, ini);
|
|
} else {
|
|
/* fail to alloc initiator, remove from queue and send busy */
|
|
if (prev_tmd == NULL) {
|
|
bc->tmds_front = tmd->cd_hnext;
|
|
} else {
|
|
prev_tmd->cd_hnext = tmd->cd_hnext;
|
|
}
|
|
if (bc->tmds_tail == tmd) {
|
|
bc->tmds_tail = prev_tmd;
|
|
}
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
|
|
tmd->cd_scsi_status = SCSI_BUSY;
|
|
xact = &tmd->cd_xact;
|
|
xact->td_hflags = TDFH_STSVALID;
|
|
xact->td_lflags = 0;
|
|
xact->td_xfrlen = 0;
|
|
(*bp->h.r_action)(QIN_TMD_CONT, xact);
|
|
|
|
spin_lock_irq(&bc->tmds_lock);
|
|
/* iterate to the next command, previous is not changed */
|
|
tmd = tmd->cd_hnext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
/* now we can run queue and pass commands to scst */
|
|
tasklet_schedule(&bc->tasklet);
|
|
}
|
|
|
|
static void
|
|
bus_add_initiators(void)
|
|
{
|
|
bus_t *bp;
|
|
int chan;
|
|
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
spin_lock_irq(&scsi_target_lock);
|
|
if (bp->h.r_action == NULL) {
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
continue;
|
|
}
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
|
|
for (chan = 0; chan < bp->h.r_nchannels; chan++) {
|
|
bus_chan_add_initiators(bp, chan);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
scsi_target_done_cmd(tmd_cmd_t *tmd)
|
|
{
|
|
bus_t *bp;
|
|
struct scst_cmd *scst_cmd;
|
|
tmd_xact_t *xact = &tmd->cd_xact;
|
|
enum scst_exec_context context = scst_estimate_context();
|
|
|
|
bp = tmd->cd_bus;
|
|
|
|
BUS_DBG2(bp,"TMD_DONE[%llx] %p hf %x lf %x xfrlen %d totlen %d moved %d\n",
|
|
tmd->cd_tagval, tmd, xact->td_hflags, xact->td_lflags, xact->td_xfrlen, tmd->cd_totlen, tmd->cd_moved);
|
|
|
|
scst_cmd = tmd->cd_scst_cmd;
|
|
if (!scst_cmd) {
|
|
/* command returned by us with status BUSY */
|
|
BUS_DBG(bp, "BUSY TMD_FIN[%llx]\n", tmd->cd_tagval);
|
|
ini_put(&bp->bchan[tmd->cd_channel], tmd->cd_ini);
|
|
(*bp->h.r_action)(QIN_TMD_FIN, tmd);
|
|
return;
|
|
}
|
|
|
|
if (xact->td_hflags & TDFH_STSVALID) {
|
|
if (xact->td_hflags & TDFH_DATA_IN) {
|
|
xact->td_hflags &= ~TDFH_DATA_MASK;
|
|
xact->td_xfrlen = 0;
|
|
}
|
|
if (unlikely(xact->td_error)) {
|
|
scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_FAILED);
|
|
}
|
|
scst_tgt_cmd_done(scst_cmd, context);
|
|
return;
|
|
}
|
|
|
|
if (xact->td_hflags & TDFH_DATA_OUT) {
|
|
if (likely(tmd->cd_totlen == tmd->cd_moved) || unlikely(xact->td_error)) {
|
|
if (xact->td_xfrlen) {
|
|
int rx_status = SCST_RX_STATUS_SUCCESS;
|
|
if (unlikely(xact->td_error)) {
|
|
rx_status = SCST_RX_STATUS_ERROR;
|
|
}
|
|
scst_rx_data(scst_cmd, rx_status, context);
|
|
} else {
|
|
if (unlikely(xact->td_error)) {
|
|
scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_FAILED);
|
|
}
|
|
scst_tgt_cmd_done(scst_cmd, context);
|
|
}
|
|
} else {
|
|
; /* we don't have all data, do nothing */
|
|
}
|
|
} else if (xact->td_hflags & TDFH_DATA_IN) {
|
|
xact->td_hflags &= ~TDFH_DATA_MASK;
|
|
xact->td_xfrlen = 0;
|
|
if (unlikely(xact->td_error)) {
|
|
scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_FAILED);
|
|
}
|
|
scst_tgt_cmd_done(scst_cmd, context);
|
|
} else {
|
|
Eprintk("don't know what to do with TMD_DONE[%llx] cdb0 %x hf %x lf %x xfrlen %d totlen %d moved %d\n",
|
|
tmd->cd_tagval, tmd->cd_cdb[0], xact->td_hflags, xact->td_lflags, xact->td_xfrlen, tmd->cd_totlen, tmd->cd_moved);
|
|
}
|
|
}
|
|
|
|
static int
|
|
abort_task(bus_chan_t *bc, uint64_t iid, uint64_t tagval)
|
|
{
|
|
unsigned long flags;
|
|
tmd_cmd_t *tmd;
|
|
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
for (tmd = bc->tmds_front; tmd; tmd = tmd->cd_hnext) {
|
|
if (tmd->cd_tagval == tagval && tmd->cd_iid == iid) {
|
|
tmd->cd_flags |= CDF_PRIVATE_ABORTED;
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
tasklet_schedule(&bc->tasklet);
|
|
return (1);
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
abort_all_tasks(bus_chan_t *bc, uint64_t iid)
|
|
{
|
|
unsigned long flags;
|
|
tmd_cmd_t *tmd;
|
|
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
for (tmd = bc->tmds_front; tmd; tmd = tmd->cd_hnext) {
|
|
if (tmd->cd_iid == iid) {
|
|
tmd->cd_flags |= CDF_PRIVATE_ABORTED;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
tasklet_schedule(&bc->tasklet);
|
|
}
|
|
|
|
static void
|
|
scsi_target_notify(notify_t *ins)
|
|
{
|
|
bus_t *bp;
|
|
bus_chan_t *bc;
|
|
ini_t *ini;
|
|
int fn;
|
|
char *tmf = NULL;
|
|
uint16_t lun;
|
|
uint8_t lunbuf[8];
|
|
unsigned long flags;
|
|
isp_notify_t *np = &ins->notify;
|
|
|
|
spin_lock_irqsave(&scsi_target_lock, flags);
|
|
bp = bus_from_notify(np);
|
|
if (unlikely(bp == NULL || bp->bchan == NULL)) {
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
Eprintk("cannot find %s for incoming notify\n", bp == NULL ? "bus" : "channel");
|
|
return;
|
|
}
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
|
|
BUS_DBG(bp, "TMD_NOTIFY %p code %x iid 0x%016llx tag %llx\n", np, np->nt_ncode, np->nt_wwn, np->nt_tagval);
|
|
|
|
bc = &bp->bchan[np->nt_channel];
|
|
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
ini = ini_from_iid(bc, np->nt_wwn);
|
|
np->nt_ini = ini;
|
|
__ini_get(bc, np->nt_ini);
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
|
|
switch (np->nt_ncode) {
|
|
case NT_ABORT_TASK:
|
|
tmf = "ABORT TASK";
|
|
if (ini == NULL) {
|
|
goto err_no_ini;
|
|
}
|
|
if (abort_task(bc, np->nt_wwn, np->nt_tagval)) {
|
|
BUS_DBG(bp, "TMD_NOTIFY abort task [%llx]\n", np->nt_tagval);
|
|
goto notify_ack;
|
|
}
|
|
if (scst_rx_mgmt_fn_tag(ini->ini_scst_sess, SCST_ABORT_TASK, np->nt_tagval, 1, np) < 0) {
|
|
np->nt_failed = 1;
|
|
goto notify_ack;
|
|
}
|
|
/* wait for SCST now */
|
|
return;
|
|
case NT_ABORT_TASK_SET:
|
|
tmf = "ABORT TASK SET";
|
|
if (ini == NULL) {
|
|
goto err_no_ini;
|
|
}
|
|
abort_all_tasks(bc, np->nt_wwn);
|
|
fn = SCST_ABORT_TASK_SET;
|
|
break;
|
|
case NT_CLEAR_TASK_SET:
|
|
tmf = "CLEAR TASK SET";
|
|
if (ini == NULL) {
|
|
goto err_no_ini;
|
|
}
|
|
abort_all_tasks(bc, np->nt_wwn);
|
|
fn = SCST_CLEAR_TASK_SET;
|
|
break;
|
|
case NT_CLEAR_ACA:
|
|
tmf = "CLEAR ACA";
|
|
fn = SCST_CLEAR_ACA;
|
|
break;
|
|
case NT_LUN_RESET:
|
|
tmf = "LUN RESET";
|
|
if (np->nt_lun == LUN_ANY) {
|
|
np->nt_failed = 1;
|
|
goto notify_ack;
|
|
}
|
|
fn = SCST_LUN_RESET;
|
|
break;
|
|
case NT_TARGET_RESET:
|
|
tmf = "TARGET RESET";
|
|
fn = SCST_TARGET_RESET;
|
|
break;
|
|
case NT_BUS_RESET:
|
|
case NT_HBA_RESET:
|
|
ini_put(bc, ini);
|
|
//schedule_reset();
|
|
return;
|
|
case NT_LIP_RESET:
|
|
case NT_LINK_UP:
|
|
case NT_LINK_DOWN:
|
|
/* we don't care about lip resets and link up/down */
|
|
goto notify_ack;
|
|
case NT_LOGOUT:
|
|
spin_lock_irqsave(&bc->tmds_lock, flags);
|
|
/*
|
|
* If someone disables the target during this notify, reference to initiator
|
|
* is currently dropped, so we need to check if IID is still in initiators
|
|
* table to avoid double free
|
|
*/
|
|
if (del_ini(bc, np->nt_wwn)) {
|
|
BUS_DBG(bp, "droping reference to initiator 0x%016llx\n", np->nt_wwn);
|
|
__ini_put(bc, ini);
|
|
} else {
|
|
Eprintk("cannot logout initiator 0x%016llx\n", np->nt_wwn);
|
|
}
|
|
spin_unlock_irqrestore(&bc->tmds_lock, flags);
|
|
goto notify_ack;
|
|
default:
|
|
Eprintk("unknown notify 0x%x\n", np->nt_ncode);
|
|
ini_put(bc, ini);
|
|
return;
|
|
}
|
|
|
|
if (tmf) {
|
|
if (ini == NULL) {
|
|
goto err_no_ini;
|
|
}
|
|
if (np->nt_lun == LUN_ANY) {
|
|
lun = 0;
|
|
} else {
|
|
lun = np->nt_lun;
|
|
}
|
|
FLATLUN_TO_L0LUN(lunbuf, lun);
|
|
if (scst_rx_mgmt_fn_lun(ini->ini_scst_sess, fn, lunbuf,
|
|
sizeof(lunbuf), 1, ins) < 0) {
|
|
np->nt_failed = 1;
|
|
goto notify_ack;
|
|
}
|
|
}
|
|
return;
|
|
|
|
err_no_ini:
|
|
Eprintk("cannot find initiator 0x%016llx for %s\n", np->nt_wwn, tmf);
|
|
np->nt_failed = 1;
|
|
notify_ack:
|
|
ini_put(bc, ini);
|
|
(*bp->h.r_action) (QIN_NOTIFY_ACK, ins);
|
|
}
|
|
|
|
static 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;
|
|
bp->need_reg = 1;
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
schedule_qlaispd(SF_REGISTER_SCST);
|
|
break;
|
|
}
|
|
case QOUT_ENABLE:
|
|
{
|
|
enadis_t *ep = arg;
|
|
if (ep->en_private) {
|
|
up((struct semaphore *)ep->en_private);
|
|
}
|
|
break;
|
|
}
|
|
case QOUT_DISABLE:
|
|
{
|
|
enadis_t *ep = arg;
|
|
if (ep->en_private) {
|
|
up((struct semaphore *)ep->en_private);
|
|
}
|
|
break;
|
|
}
|
|
case QOUT_TMD_START:
|
|
{
|
|
tmd_cmd_t *tmd = arg;
|
|
tmd->cd_xact.td_cmd = tmd;
|
|
scsi_target_start_cmd(arg);
|
|
break;
|
|
}
|
|
case QOUT_TMD_DONE:
|
|
{
|
|
tmd_xact_t *xact = arg;
|
|
tmd_cmd_t *tmd = xact->td_cmd;
|
|
scsi_target_done_cmd(tmd);
|
|
break;
|
|
}
|
|
case QOUT_NOTIFY:
|
|
{
|
|
notify_t *ins = arg;
|
|
scsi_target_notify(ins);
|
|
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;
|
|
}
|
|
bp->unreg_hp = hp;
|
|
spin_unlock_irqrestore(&scsi_target_lock, flags);
|
|
schedule_qlaispd(SF_UNREGISTER_SCST);
|
|
break;
|
|
}
|
|
default:
|
|
Eprintk("action code %d (0x%x)?\n", action, action);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void register_scst(void);
|
|
static void unregister_scst(void);
|
|
|
|
static int
|
|
qlaispd_function(void *arg)
|
|
{
|
|
printk(KERN_DEBUG "qlaispd starting\n");
|
|
while (!kthread_should_stop()) {
|
|
printk(KERN_DEBUG "qlaispd sleeping\n");
|
|
wait_event_interruptible(qlaispd_waitq, qlaispd_flags || kthread_should_stop());
|
|
printk(KERN_DEBUG "qlaispd running\n");
|
|
|
|
if (test_and_clear_bit(SF_REGISTER_SCST, &qlaispd_flags)) {
|
|
register_scst();
|
|
}
|
|
if (test_and_clear_bit(SF_ADD_INITIATORS, &qlaispd_flags)) {
|
|
bus_add_initiators();
|
|
}
|
|
if (test_and_clear_bit(SF_UNREGISTER_SCST, &qlaispd_flags)) {
|
|
unregister_scst();
|
|
}
|
|
}
|
|
printk(KERN_DEBUG "qlaispd exiting\n");
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
scsi_target_enable(bus_t *bp, int chan, int lun)
|
|
{
|
|
struct semaphore rsem;
|
|
bus_chan_t *bc;
|
|
uint64_t mask;
|
|
enadis_t ec;
|
|
|
|
memset(&ec, 0, sizeof (ec));
|
|
ec.en_hba = bp->h.r_identity;
|
|
ec.en_chan = chan;
|
|
if (bp->h.r_type == R_FC) {
|
|
ec.en_lun = LUN_ANY;
|
|
} else {
|
|
ec.en_lun = lun;
|
|
}
|
|
sema_init(&rsem, 0);
|
|
ec.en_private = &rsem;
|
|
(*bp->h.r_action)(QIN_ENABLE, &ec);
|
|
down(&rsem);
|
|
if (ec.en_error) {
|
|
return (ec.en_error);
|
|
}
|
|
|
|
bc = &bp->bchan[chan];
|
|
if (bp->h.r_type == R_FC) {
|
|
bc->enable = 1;
|
|
} else {
|
|
mask = ~(1 << lun);
|
|
bc->enable &= mask;
|
|
bc->enable |= (1 << lun);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
scsi_target_disable(bus_t *bp, int chan, int lun)
|
|
{
|
|
uint64_t mask;
|
|
uint64_t old_enable;
|
|
struct semaphore rsem;
|
|
enadis_t ec;
|
|
bus_chan_t *bc;
|
|
|
|
bc = &bp->bchan[chan];
|
|
old_enable = bc->enable;
|
|
|
|
if (bp->h.r_type == R_FC) {
|
|
bc->enable = 0;
|
|
} else {
|
|
mask = ~(1 << lun);
|
|
bc->enable &= mask;
|
|
}
|
|
|
|
// FIXME I don't know what I'm doing .... but I will know ... some day
|
|
smp_wmb();
|
|
|
|
if (bc->enable == 0) {
|
|
BUS_DBG(bp, "Chan %d drop all initiators references\n", chan);
|
|
/*
|
|
* If no lun is active on channel we want to logoff from SCST. At this point we ignore all
|
|
* new commands and notifies comeing from low level driver, but we need to care on pending
|
|
* ones. We just drop reference to initiators. When last command/notify finish for initiator,
|
|
* we will unregister session from SCST and disable target mode in low lever driver here.
|
|
*/
|
|
bus_chan_unregister_sessions(bc, 0);
|
|
|
|
/*
|
|
* Now wait for all sessions associated with channel stop.
|
|
*/
|
|
BUS_DBG(bp, "Chan %d waiting for finishing %d sessions\n", chan, atomic_read(&bc->sess_count));
|
|
wait_event(bc->wait_queue, atomic_read(&bc->sess_count) == 0);
|
|
BUS_DBG(bp, "Chan %d all sessions finished\n", chan);
|
|
}
|
|
|
|
memset(&ec, 0, sizeof (ec));
|
|
ec.en_hba = bp->h.r_identity;
|
|
ec.en_chan = chan;
|
|
if (bp->h.r_type == R_FC) {
|
|
ec.en_lun = LUN_ANY;
|
|
} else {
|
|
ec.en_lun = lun;
|
|
}
|
|
sema_init(&rsem, 0);
|
|
ec.en_private = &rsem;
|
|
(*bp->h.r_action)(QIN_DISABLE, &ec);
|
|
down(&rsem);
|
|
if (ec.en_error) {
|
|
bc->enable = old_enable;
|
|
return (ec.en_error);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
scsi_target_enadis(bus_t *bp, uint64_t en, int chan, int lun)
|
|
{
|
|
bus_chan_t *bc;
|
|
info_t info;
|
|
uint64_t mask;
|
|
|
|
BUG_ON(chan < 0 || chan >= bp->h.r_nchannels);
|
|
BUG_ON(lun != LUN_ANY && (lun < 0 || lun >= MAX_LUN));
|
|
|
|
bc = &bp->bchan[chan];
|
|
|
|
if (bp->h.r_type == R_FC) {
|
|
if (en == bc->enable) {
|
|
return (0);
|
|
}
|
|
} else {
|
|
if (lun == LUN_ANY) {
|
|
return (-EINVAL);
|
|
} else {
|
|
mask = ~(1 << lun);
|
|
if ((en << lun) == (bc->enable & mask)) {
|
|
return (0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if requested HBA is there
|
|
*/
|
|
memset(&info, 0, sizeof (info));
|
|
info.i_identity = bp->h.r_identity;
|
|
info.i_channel = chan;
|
|
(*bp->h.r_action)(QIN_GETINFO, &info);
|
|
if (info.i_error) {
|
|
return (info.i_error);
|
|
}
|
|
|
|
if (en) {
|
|
return scsi_target_enable(bp, chan, lun);
|
|
} else {
|
|
return scsi_target_disable(bp, chan, lun);
|
|
}
|
|
}
|
|
|
|
static int
|
|
isp_detect(struct scst_tgt_template *tgt_template)
|
|
{
|
|
schedule_qlaispd(SF_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)
|
|
{
|
|
/* don't need to check against aborted, low level driver handle
|
|
* this and call us back with error */
|
|
|
|
if (scst_cmd_get_data_direction(scst_cmd) == SCST_DATA_WRITE) {
|
|
tmd_cmd_t *tmd = (tmd_cmd_t *) scst_cmd_get_tgt_priv(scst_cmd);
|
|
tmd_xact_t *xact = &tmd->cd_xact;
|
|
bus_t *bp = tmd->cd_bus;
|
|
int len = scst_cmd_get_bufflen(scst_cmd);
|
|
|
|
xact->td_hflags = TDFH_DATA_OUT;
|
|
xact->td_lflags = 0;
|
|
xact->td_data = scst_cmd_get_sg(scst_cmd);
|
|
xact->td_xfrlen = len;
|
|
if (bp->h.r_type == R_SPI) {
|
|
tmd->cd_totlen = len;
|
|
}
|
|
|
|
BUS_DBG2(bp, "TMD[%llx] write nbytes %u\n", tmd->cd_tagval, scst_cmd_get_bufflen(scst_cmd));
|
|
|
|
(*bp->h.r_action)(QIN_TMD_CONT, xact);
|
|
/*
|
|
* Did we have an error starting this particular transaction?
|
|
*/
|
|
if (unlikely((xact->td_lflags & (TDFL_ERROR|TDFL_SYNCERROR)) == (TDFL_ERROR|TDFL_SYNCERROR))) {
|
|
if (xact->td_error == -ENOMEM) {
|
|
return (SCST_TGT_RES_QUEUE_FULL);
|
|
} else {
|
|
return (SCST_TGT_RES_FATAL_ERROR);
|
|
}
|
|
}
|
|
}
|
|
return (SCST_TGT_RES_SUCCESS);
|
|
}
|
|
|
|
static int
|
|
isp_xmit_response(struct scst_cmd *scst_cmd)
|
|
{
|
|
tmd_cmd_t *tmd = (tmd_cmd_t *) scst_cmd_get_tgt_priv(scst_cmd);
|
|
bus_t *bp = tmd->cd_bus;
|
|
tmd_xact_t *xact = &tmd->cd_xact;
|
|
|
|
if (unlikely(scst_cmd_aborted_on_xmit(scst_cmd))) {
|
|
scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_ABORTED);
|
|
scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME);
|
|
return (SCST_TGT_RES_SUCCESS);
|
|
}
|
|
|
|
if (scst_cmd_get_data_direction(scst_cmd) == SCST_DATA_READ) {
|
|
unsigned int len = scst_cmd_get_resp_data_len(scst_cmd);
|
|
if (bp->h.r_type == R_SPI) {
|
|
tmd->cd_totlen = len;
|
|
}
|
|
if (unlikely(len > tmd->cd_totlen)) {
|
|
/* some broken FC initiators may send SCSI commands with data load
|
|
* larger than underlaying transport specified */
|
|
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);
|
|
|
|
memcpy(tmd->cd_sense, ifailure, TMD_SENSELEN);
|
|
xact->td_hflags = TDFH_STSVALID;
|
|
tmd->cd_scsi_status = SCSI_CHECK;
|
|
goto out;
|
|
} else {
|
|
xact->td_hflags = TDFH_DATA_IN;
|
|
xact->td_xfrlen = len;
|
|
xact->td_data = scst_cmd_get_sg(scst_cmd);
|
|
}
|
|
} else {
|
|
/* finished write to target or command with no data */
|
|
xact->td_xfrlen = 0;
|
|
xact->td_hflags &= ~TDFH_DATA_MASK;
|
|
}
|
|
|
|
xact->td_lflags = 0;
|
|
|
|
if (scst_cmd_get_is_send_status(scst_cmd)) {
|
|
xact->td_hflags |= TDFH_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);
|
|
if (likely(slen > TMD_SENSELEN)) {
|
|
/* 18 bytes sense code not cover vendor specific sense data,
|
|
* we can't send more than 18 bytes through low level driver,
|
|
* however SCST give us 96 bytes, so truncate */
|
|
slen = TMD_SENSELEN;
|
|
}
|
|
memcpy(tmd->cd_sense, sbuf, slen);
|
|
#ifdef DEBUG
|
|
if (unlikely(debug > 0)) {
|
|
uint8_t key, asc, ascq;
|
|
key = (slen >= 2) ? sbuf[2] : 0;
|
|
asc = (slen >= 12) ? sbuf[12] : 0;
|
|
ascq = (slen >= 13) ? sbuf[13] : 0;
|
|
BUS_DBG(bp, "sense code: key 0x%02x asc 0x%02x ascq 0x%02x\n", key, asc, ascq);
|
|
}
|
|
#endif
|
|
}
|
|
BUS_DBG2(bp, "TMD[%llx] status %d\n", tmd->cd_tagval, scst_cmd_get_status(scst_cmd));
|
|
}
|
|
|
|
out:
|
|
if ((xact->td_hflags & TDFH_STSVALID) && (tmd->cd_scsi_status == SCSI_CHECK)) {
|
|
xact->td_xfrlen = 0;
|
|
xact->td_hflags &= ~TDFH_DATA_MASK;
|
|
xact->td_hflags |= TDFH_SNSVALID;
|
|
}
|
|
|
|
(*bp->h.r_action)(QIN_TMD_CONT, xact);
|
|
/*
|
|
* Did we have an error starting this particular transaction?
|
|
*/
|
|
if (unlikely((xact->td_lflags & (TDFL_ERROR|TDFL_SYNCERROR)) == (TDFL_ERROR|TDFL_SYNCERROR))) {
|
|
if (xact->td_error == -ENOMEM) {
|
|
return (SCST_TGT_RES_QUEUE_FULL);
|
|
} else {
|
|
return (SCST_TGT_RES_FATAL_ERROR);
|
|
}
|
|
}
|
|
return (SCST_TGT_RES_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
isp_on_free_cmd(struct scst_cmd *scst_cmd)
|
|
{
|
|
tmd_cmd_t *tmd = (tmd_cmd_t *) scst_cmd_get_tgt_priv(scst_cmd);
|
|
bus_t *bp = tmd->cd_bus;
|
|
tmd_xact_t *xact = &tmd->cd_xact;
|
|
|
|
xact->td_data = NULL;
|
|
ini_put(&bp->bchan[tmd->cd_channel], tmd->cd_ini);
|
|
BUS_DBG2(bp, "TMD_FIN[%llx]\n", tmd->cd_tagval);
|
|
(*bp->h.r_action)(QIN_TMD_FIN, tmd);
|
|
}
|
|
|
|
static int
|
|
isp_task_mgmt_fn_get_resp(int scst_mgmt_status)
|
|
{
|
|
switch (scst_mgmt_status) {
|
|
case SCST_MGMT_STATUS_SUCCESS:
|
|
return FCP_RSPNS_TMF_SUCCEEDED;
|
|
|
|
case SCST_MGMT_STATUS_TASK_NOT_EXIST:
|
|
return FCP_RSPNS_BADCMND;
|
|
|
|
case SCST_MGMT_STATUS_LUN_NOT_EXIST:
|
|
return FCP_RSPNS_TMF_INCORRECT_LUN;
|
|
|
|
case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
|
|
case SCST_MGMT_STATUS_REJECTED:
|
|
return FCP_RSPNS_TMF_REJECT;
|
|
|
|
case SCST_MGMT_STATUS_FAILED:
|
|
default:
|
|
return FCP_RSPNS_TMF_FAILED;
|
|
}
|
|
}
|
|
|
|
static void
|
|
isp_task_mgmt_fn_done(struct scst_mgmt_cmd *mgmt_cmd)
|
|
{
|
|
notify_t *ins = scst_mgmt_cmd_get_tgt_priv(mgmt_cmd);
|
|
isp_notify_t *np = &ins->notify;
|
|
bus_t *bp = bus_from_notify(np);
|
|
ins->tmf_resp =
|
|
isp_task_mgmt_fn_get_resp(scst_mgmt_cmd_get_status(mgmt_cmd));
|
|
|
|
ini_put(&bp->bchan[np->nt_channel], np->nt_ini);
|
|
BUS_DBG(bp, "NOTIFY_ACK[%llx]\n", np->nt_tagval);
|
|
(*bp->h.r_action) (QIN_NOTIFY_ACK, ins);
|
|
}
|
|
|
|
int isp_get_initiator_port_transport_id(struct scst_tgt *tgt,
|
|
struct scst_session *scst_sess, uint8_t **transport_id)
|
|
{
|
|
ini_t *ini;
|
|
int res = 0;
|
|
int tr_id_size;
|
|
uint8_t *tr_id;
|
|
uint64_t iid;
|
|
uint64_t *n_port_name;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
if (scst_sess == NULL) {
|
|
res = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
|
|
goto out;
|
|
}
|
|
|
|
TRACE_DBG("Called to get transport ID (iid = %llu)", ini->ini_iid);
|
|
ini = (ini_t*)scst_sess_get_tgt_priv(scst_sess);
|
|
|
|
iid = ini->ini_iid;
|
|
|
|
tr_id_size = 24;
|
|
tr_id = kzalloc(tr_id_size, GFP_KERNEL);
|
|
if (tr_id == NULL) {
|
|
PRINT_ERROR("Allocation of TransportID (size %d) failed",
|
|
tr_id_size);
|
|
res = -ENOMEM;
|
|
goto out;
|
|
}
|
|
n_port_name = (uint64_t*)&tr_id[8];
|
|
*n_port_name = __cpu_to_be64(iid);
|
|
|
|
PRINT_BUFF_FLAG(TRACE_DEBUG, "PR transport ID: 0x%x", tr_id, tr_id_size);
|
|
|
|
*transport_id = tr_id;
|
|
|
|
out:
|
|
TRACE_EXIT_RES(res);
|
|
return res;
|
|
}
|
|
|
|
|
|
static DEFINE_MUTEX(proc_mutex);
|
|
|
|
/*
|
|
* Many procfs things is taken from scst/src/scst_proc.c
|
|
*/
|
|
|
|
#if !defined(CONFIG_PPC) && (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22))
|
|
|
|
int strncasecmp(const char *s1, const char *s2, size_t n)
|
|
{
|
|
int c1, c2;
|
|
do {
|
|
c1 = tolower(*s1++);
|
|
c2 = tolower(*s2++);
|
|
} while ((--n > 0) && c1 == c2 && c1 != 0);
|
|
return c1 - c2;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int
|
|
isp_read_proc(struct seq_file *seq, void *v)
|
|
{
|
|
bus_t *bp = seq->private;
|
|
bus_chan_t *bc;
|
|
int chan;
|
|
|
|
if (bp == NULL || bp->bchan == NULL) {
|
|
return (-ENODEV);
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&proc_mutex)) {
|
|
return (-ERESTARTSYS);
|
|
}
|
|
|
|
seq_printf(seq, "%s HBA %s%d DEVID %x\n", bp->h.r_type == R_FC ? "FC" : "SPI", bp->h.r_name, bp->h.r_inst, bp->h.r_locator);
|
|
for (chan = 0; chan < bp->h.r_nchannels; chan++) {
|
|
bc = &bp->bchan[chan];
|
|
if (bp->h.r_type == R_FC) {
|
|
seq_printf(seq, "%-2d: %d\n", chan, bc->enable ? 1 : 0);
|
|
} else {
|
|
seq_printf(seq, "%-2d: 0x%llx\n", chan, bc->enable);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&proc_mutex);
|
|
return (0);
|
|
}
|
|
|
|
static ssize_t
|
|
isp_write_proc(struct file *file, const char __user *buf, size_t len, loff_t *off)
|
|
{
|
|
char *ptr, *p, *old;
|
|
enum { DISABLE = 0, ENABLE = 1, TEST } action;
|
|
int en = -1, res = -EINVAL;
|
|
int all_channels = 0, all_luns = 0;
|
|
int lun = 0, chan = 0;
|
|
bus_t *bp = PDE(file->f_dentry->d_inode)->data;
|
|
|
|
if (bp == NULL || bp->bchan == NULL) {
|
|
return (-ENODEV);
|
|
}
|
|
if (!buf) {
|
|
goto out;
|
|
}
|
|
ptr = (char *)__get_free_page(GFP_KERNEL);
|
|
if (ptr == NULL) {
|
|
res = -ENOMEM;
|
|
goto out;
|
|
}
|
|
if (copy_from_user(ptr, buf, len)) {
|
|
res = -EFAULT;
|
|
goto out_free;
|
|
}
|
|
if (len < PAGE_SIZE) {
|
|
ptr[len] = '\0';
|
|
} else if (ptr[PAGE_SIZE-1]) {
|
|
goto out_free;
|
|
}
|
|
|
|
/*
|
|
* Usage: echo "enable|disable chan lun" > /proc/scsi_tgt/qla_isp/N
|
|
* or echo "test" > /proc/scsi_tgt/qla_isp/N
|
|
*/
|
|
p = ptr;
|
|
if (p[strlen(p) - 1] == '\n') {
|
|
p[strlen(p) - 1] = '\0';
|
|
}
|
|
if (!strncasecmp("enable", p, 6)) {
|
|
p += 6;
|
|
action = ENABLE;
|
|
} else if (!strncasecmp("disable", p, 7)) {
|
|
p += 7;
|
|
action = DISABLE;
|
|
} else if (!strncasecmp("test", p, 4)) {
|
|
action = TEST;
|
|
} else {
|
|
PRINT_ERROR("unknown action \"%s\"", p);
|
|
goto out_free;
|
|
}
|
|
|
|
switch (action) {
|
|
case ENABLE:
|
|
case DISABLE:
|
|
if (!isspace(*p)) {
|
|
PRINT_ERROR("cannot parse arguments for action \"%s\"", action == DISABLE ? "disable" : "enable");
|
|
goto out_free;
|
|
}
|
|
|
|
/* get channel */
|
|
while (isspace(*p) && *p != '\0') {
|
|
p++;
|
|
}
|
|
old = p;
|
|
chan = simple_strtoul(p, &p, 0);
|
|
if (old == p) {
|
|
if (!strncasecmp("all", p, 3)) {
|
|
all_channels = 1;
|
|
} else {
|
|
PRINT_ERROR("cannot parse channel for action \"%s\"", action == DISABLE ? "disable" : "enable");
|
|
goto out_free;
|
|
}
|
|
} else if (chan < 0 || chan >= bp->h.r_nchannels) {
|
|
PRINT_ERROR("bad channel number %d", chan);
|
|
goto out_free;
|
|
}
|
|
|
|
/* get lun */
|
|
if (bp->h.r_type == R_SPI) {
|
|
while (isspace(*p) && *p != '\0') {
|
|
p++;
|
|
}
|
|
old = p;
|
|
lun = simple_strtoul(p, &p, 0);
|
|
if (old == p) {
|
|
if (!strncasecmp("all", p, 3)) {
|
|
all_luns = 1;
|
|
} else {
|
|
PRINT_ERROR("cannot parse lun for action \"%s\"", action == DISABLE ? "disable" : "enable");
|
|
goto out_free;
|
|
}
|
|
} else if (lun < 0 && lun >= MAX_LUN) {
|
|
PRINT_ERROR("bad lun %d", lun);
|
|
goto out_free;
|
|
}
|
|
} else {
|
|
lun = LUN_ANY;
|
|
}
|
|
|
|
en = action;
|
|
break;
|
|
case TEST:
|
|
printk("%s test\n", __FUNCTION__);
|
|
res = len;
|
|
break;
|
|
}
|
|
|
|
if (en == 0 || en == 1) {
|
|
/*
|
|
* channel 0 must be enabled first and disabled last, so when enabling all
|
|
* channels do it in ascending order and when disabling all in descending order
|
|
*/
|
|
int chan_srt, chan_end, chan_inc;
|
|
int lun_srt, lun_end;
|
|
|
|
if (all_channels) {
|
|
if (en) {
|
|
chan_srt = 0;
|
|
chan_end = bp->h.r_nchannels;
|
|
chan_inc = 1;
|
|
} else {
|
|
chan_srt = bp->h.r_nchannels - 1;
|
|
chan_end = -1;
|
|
chan_inc = -1;
|
|
}
|
|
} else {
|
|
chan_srt = chan;
|
|
chan_end = chan + 1;
|
|
chan_inc = 1;
|
|
}
|
|
|
|
if (bp->h.r_type == R_FC) {
|
|
lun_srt = LUN_ANY;
|
|
lun_end = LUN_ANY + 1;
|
|
} else {
|
|
if (all_luns) {
|
|
lun_srt = 0;
|
|
lun_end = MAX_LUN;
|
|
} else {
|
|
lun_srt = lun;
|
|
lun_end = lun + 1;
|
|
}
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&proc_mutex)) {
|
|
res = -ERESTARTSYS;
|
|
goto out_free;
|
|
}
|
|
for (chan = chan_srt; chan != chan_end; chan += chan_inc) {
|
|
for (lun = lun_srt; lun != lun_end; lun++) {
|
|
res = scsi_target_enadis(bp, en, chan, lun);
|
|
if (res < 0) {
|
|
PRINT_ERROR("%s channel %d failed with error %d", en ? "enable" : "disable", chan, res);
|
|
/* processed anyway */
|
|
}
|
|
}
|
|
}
|
|
res = len;
|
|
mutex_unlock(&proc_mutex);
|
|
}
|
|
|
|
out_free:
|
|
free_page((unsigned long)ptr);
|
|
out:
|
|
return (res);
|
|
}
|
|
|
|
static uint16_t isp_get_scsi_transport_version(struct scst_tgt *scst_tgt)
|
|
{
|
|
return 0x0900; /* FCP-2 */
|
|
}
|
|
|
|
static struct scst_tgt_template isp_tgt_template = {
|
|
.sg_tablesize = SG_ALL, /* we set this value lately based on 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,
|
|
.get_scsi_transport_version = isp_get_scsi_transport_version,
|
|
|
|
//.report_aen = isp_report_aen,
|
|
.get_initiator_port_transport_id = isp_get_initiator_port_transport_id,
|
|
};
|
|
|
|
#ifdef ISP_DAC_SUPPORTED
|
|
#define ISP_A64 1
|
|
#else
|
|
#define ISP_A64 0
|
|
#endif
|
|
|
|
static int
|
|
get_sg_tablesize(ispsoftc_t *isp)
|
|
{
|
|
// FIXME: check if this is correct? What about multichannel ?
|
|
// FIXME: move to the low level driver and export via tpublic API
|
|
int rq_seglim, ct_seglim;
|
|
int nctios = (isp->isp_maxcmds < 4) ? 0 : isp->isp_maxcmds - 4;
|
|
|
|
if (IS_24XX(isp)) {
|
|
rq_seglim = 1;
|
|
ct_seglim = ISP_CDSEG64;
|
|
} else if (IS_2322(isp) || ISP_A64) {
|
|
rq_seglim = ISP_RQDSEG_T3;
|
|
ct_seglim = ISP_CDSEG64;
|
|
} else if (IS_FC(isp)) {
|
|
rq_seglim = ISP_RQDSEG_T2;
|
|
ct_seglim = ISP_CDSEG;
|
|
} else { // SPI
|
|
rq_seglim = ISP_RQDSEG;
|
|
ct_seglim = ISP_RQDSEG;
|
|
}
|
|
|
|
return rq_seglim + nctios * ct_seglim;
|
|
}
|
|
|
|
static void
|
|
bus_set_proc_data(bus_t *bp)
|
|
{
|
|
const struct scst_proc_data proc_data = {
|
|
SCST_DEF_RW_SEQ_OP(isp_write_proc)
|
|
.show = isp_read_proc,
|
|
};
|
|
memcpy(&bp->proc_data, &proc_data, sizeof(bp->proc_data));
|
|
bp->proc_data.data = bp;
|
|
}
|
|
|
|
static void
|
|
register_hba(bus_t *bp)
|
|
{
|
|
char name[32];
|
|
info_t info;
|
|
int chan;
|
|
bus_chan_t *bchan, *bc;
|
|
struct scst_tgt *scst_tgt;
|
|
struct proc_dir_entry *pde;
|
|
|
|
bchan = kzalloc(bp->h.r_nchannels * sizeof(bus_chan_t), GFP_KERNEL);
|
|
if (bchan == NULL) {
|
|
Eprintk("cannot allocate %d channels for %s%d\n", bp->h.r_nchannels, bp->h.r_name, bp->h.r_inst);
|
|
goto err_free_bus;
|
|
}
|
|
|
|
for (chan = 0; chan < bp->h.r_nchannels; chan++) {
|
|
memset(&info, 0, sizeof(info_t));
|
|
info.i_identity = bp->h.r_identity;
|
|
if (bp->h.r_type == R_FC) {
|
|
info.i_type = I_FC;
|
|
} else {
|
|
info.i_type = I_SPI;
|
|
}
|
|
info.i_channel = chan;
|
|
(*bp->h.r_action)(QIN_GETINFO, &info);
|
|
if (info.i_error) {
|
|
Eprintk("cannot get device name from %s%d!\n", bp->h.r_name, bp->h.r_inst);
|
|
goto err_free_chan;
|
|
}
|
|
|
|
if (info.i_type == I_FC) {
|
|
#define GET(byte) (uint8_t) ((info.i_id.fc.wwpn >> 8*byte) & 0xff)
|
|
snprintf(name, sizeof(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
|
|
} else { // SPI
|
|
#define GET(byte) (uint8_t) ((info.i_id.spi.iid >> 8*byte) & 0xff)
|
|
snprintf(name, sizeof(name), "%02x:%02x:%02x:%02x", GET(3), GET(2), GET(1), GET(0));
|
|
#undef GET
|
|
}
|
|
|
|
isp_tgt_template.sg_tablesize = get_sg_tablesize(bp->h.r_identity);
|
|
scst_tgt = scst_register_target(&isp_tgt_template, name);
|
|
if (scst_tgt == NULL) {
|
|
Eprintk("cannot register scst device %s for %s%d\n", name, bp->h.r_name, bp->h.r_inst);
|
|
goto err_free_chan;
|
|
}
|
|
|
|
bc = &bchan[chan];
|
|
spin_lock_init(&bc->tmds_lock);
|
|
tasklet_init(&bc->tasklet, tasklet_rx_cmds, (unsigned long) bc);
|
|
init_waitqueue_head(&bc->wait_queue);
|
|
atomic_set(&bc->sess_count, 0);
|
|
bc->bus = bp;
|
|
bc->scst_tgt = scst_tgt;
|
|
scst_tgt->tgt_priv = bc;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "%d", ((ispsoftc_t *)bp->h.r_identity)->isp_osinfo.host->host_no);
|
|
bus_set_proc_data(bp);
|
|
pde = scst_create_proc_entry(scst_proc_get_tgt_root(&isp_tgt_template), name, &bp->proc_data);
|
|
if (pde == NULL) {
|
|
Eprintk("cannot create entry %s in /proc\n", name);
|
|
goto err_free_chan;
|
|
}
|
|
|
|
spin_lock_irq(&scsi_target_lock);
|
|
bp->bchan = bchan;
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
|
|
Iprintk("registering %s%d\n", bp->h.r_name, bp->h.r_inst);
|
|
(bp->h.r_action)(QIN_HBA_REG, &bp->h);
|
|
return;
|
|
|
|
err_free_chan:
|
|
for (chan = bp->h.r_nchannels -1; chan >= 0; chan--) {
|
|
if (bchan[chan].scst_tgt) {
|
|
scst_unregister_target(bchan[chan].scst_tgt);
|
|
}
|
|
}
|
|
kfree(bchan);
|
|
|
|
err_free_bus:
|
|
spin_lock_irq(&scsi_target_lock);
|
|
memset(&bp->h, 0, sizeof (hba_register_t));
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
}
|
|
|
|
static void
|
|
bus_chan_unregister_sessions(bus_chan_t *bc, int wait)
|
|
{
|
|
int i;
|
|
ini_t *ini_next, *ptr;
|
|
|
|
for (i = 0; i < HASH_WIDTH; i++) {
|
|
spin_lock_irq(&bc->tmds_lock);
|
|
ptr = bc->list[i];
|
|
bc->list[i] = NULL;
|
|
spin_unlock_irq(&bc->tmds_lock);
|
|
|
|
if (ptr) {
|
|
do {
|
|
ini_next = ptr->ini_next;
|
|
if (wait) {
|
|
free_ini(bc, ptr, 1);
|
|
} else {
|
|
ini_put(bc, ptr);
|
|
}
|
|
} while ((ptr = ini_next) != NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
unregister_hba(bus_t *bp, hba_register_t *unreg_hp)
|
|
{
|
|
int chan;
|
|
char name[32];
|
|
bus_chan_t *bc;
|
|
|
|
snprintf(name, sizeof(name), "%d", ((ispsoftc_t *)bp->h.r_identity)->isp_osinfo.host->host_no);
|
|
remove_proc_entry(name, scst_proc_get_tgt_root(&isp_tgt_template));
|
|
|
|
/* it's safe now to unregister and reinit bp */
|
|
for (chan = 0; chan < bp->h.r_nchannels; chan++) {
|
|
bc = &bp->bchan[chan];
|
|
bus_chan_unregister_sessions(bc, 1);
|
|
if (bc->scst_tgt) {
|
|
BUS_DBG(bp, "Chan %d waiting for finishing %d sessions\n", chan, atomic_read(&bc->sess_count));
|
|
wait_event(bc->wait_queue, atomic_read(&bc->sess_count) == 0);
|
|
BUS_DBG(bp, "Chan %d all sessions finished\n", chan);
|
|
scst_unregister_target(bc->scst_tgt);
|
|
}
|
|
}
|
|
kfree(bp->bchan);
|
|
spin_lock_irq(&scsi_target_lock);
|
|
memset(bp, 0, sizeof(bus_t));
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
|
|
Iprintk("unregistering %s%d\n", unreg_hp->r_name, unreg_hp->r_inst);
|
|
(unreg_hp->r_action)(QIN_HBA_UNREG, unreg_hp);
|
|
}
|
|
|
|
/* Register SCST target, must be called in process context */
|
|
static void
|
|
register_scst(void)
|
|
{
|
|
bus_t *bp;
|
|
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
spin_lock_irq(&scsi_target_lock);
|
|
if (bp->need_reg == 0) {
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
continue;
|
|
}
|
|
bp->need_reg = 0;
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
|
|
register_hba(bp);
|
|
}
|
|
}
|
|
|
|
/* Unregister SCST target, must be called in process context */
|
|
static void
|
|
unregister_scst(void)
|
|
{
|
|
bus_t *bp;
|
|
hba_register_t *unreg_hp;
|
|
|
|
for (bp = busses; bp < &busses[MAX_BUS]; bp++) {
|
|
spin_lock_irq(&scsi_target_lock);
|
|
if (bp->unreg_hp == NULL) {
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
continue;
|
|
}
|
|
unreg_hp = bp->unreg_hp;
|
|
bp->unreg_hp = NULL;
|
|
spin_unlock_irq(&scsi_target_lock);
|
|
|
|
unregister_hba(bp, unreg_hp);
|
|
}
|
|
}
|
|
|
|
EXPORT_SYMBOL(scsi_target_handler);
|
|
|
|
#ifdef MODULE_LICENSE
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
#endif
|
|
|
|
int init_module(void)
|
|
{
|
|
int ret;
|
|
|
|
qlaispd_task = kthread_run(qlaispd_function, NULL, "qlaispd");
|
|
if (IS_ERR(qlaispd_task)) {
|
|
Eprintk("running qlaispd failed\n");
|
|
return PTR_ERR(qlaispd_task);
|
|
}
|
|
|
|
ret = scst_register_target_template(&isp_tgt_template);
|
|
if (ret < 0) {
|
|
Eprintk("cannot register scst target template\n");
|
|
kthread_stop(qlaispd_task);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* We can't get here until all hbas have deregistered
|
|
*/
|
|
void cleanup_module(void)
|
|
{
|
|
kthread_stop(qlaispd_task);
|
|
scst_unregister_target_template(&isp_tgt_template);
|
|
}
|
|
/*
|
|
* vim:ts=4:sw=4:expandtab
|
|
*/
|