/*
* Copyright (C) 2002-2003 Ardis Technolgies <roman@ardistech.com>
*
* Released under the terms of the GNU GPL v2.0.
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include <linux/init.h>
#include <linux/compiler.h>
#ifdef CONFIG_TRACE
#include <linux/trace.h>
#else
#define trace_std_formatted_event(event, ...)
#define trace_create_event(name, ...) 0
#define trace_destroy_event(event)
#endif
#include <asm/uaccess.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <scsi/scsi.h>
#include "iscsi.h"
#include "target.h"
#include "target_dbg.h"
#include "target_device.h"
#define D_GENERIC 0
#define D_THREAD 0
#define D_DUMP_PDU 0
#define D_TASK_MGT 1
#define D_SETUP 0
#define dprintk(debug, fmt...) ({ \
if (debug) \
printk(fmt); \
})
#define STATUS_SHIFT 1
#ifndef REPORT_LUNS
#define REPORT_LUNS 0xa0
#endif
static int iscsi_target_read_thread(void *arg);
static int iscsi_target_write_thread(void *arg);
static int iscsi_device_thread(void *arg);
static void iscsi_session_defaults(struct iscsi_param *param);
static inline void iscsi_conn_init_read(struct iscsi_conn *conn, void *data, size_t len);
static inline struct iscsi_cmnd *iscsi_cmnd_get_req_cmnd(struct iscsi_cmnd *rsp_cmnd);
static inline struct iscsi_cmnd *iscsi_cmnd_get_rsp_cmnd(struct iscsi_cmnd *req_cmnd);
static inline void iscsi_cmnd_get_length(struct iscsi_pdu *pdu);
static inline void iscsi_cmnd_set_length(struct iscsi_pdu *pdu);
static inline int iscsi_session_pushstate(struct iscsi_cmnd *cmnd);
static inline int iscsi_session_check_cmd_sn(struct iscsi_cmnd *cmnd);
static inline void iscsi_conn_closefd_nolock(struct iscsi_conn *conn);
struct semaphore iscsi_sem;
static struct list_head target_list;
static struct list_head target_device_list;
static kmem_cache_t *iscsi_cmnd_cache;
static wait_queue_head_t iscsi_wq;
static char dummy_data[1024];
static int trace_read_cmnd, trace_execute_cmnd, trace_write_cmnd, trace_execute_scsi;
static long iscsi_jiffies;
/*****************************************************************************/
/* TARGET */
/*****************************************************************************/
/**
* Create a new iscsi target.
* Caller must hold iscsi_sem.
*
* iscsi_target_create -
* @id: id of target
* @name: default iscsi name
*
* @return -errno
*/
int iscsi_target_create(u32 id, const char *name)
{
struct iscsi_target *target;
int err, tid;
dprintk(D_SETUP, "iscsi_target_create: %u %s\n", id, name);
target = kmalloc(sizeof(*target), GFP_KERNEL);
if (!target)
return -ENOMEM;
memset(target, 0, sizeof(*target));
target->target.id = id;
iscsi_session_defaults(&target->default_param);
target->name = kmalloc(strlen(name) + 1, GFP_KERNEL);
strcpy(target->name, name);
INIT_LIST_HEAD(&target->session_list);
INIT_LIST_HEAD(&target->lun_list);
INIT_LIST_HEAD(&target->poll.list);
init_rwsem(&target->poll.sem);
poll_initwait(&target->poll.read_poll);
poll_initwait(&target->poll.write_poll);
init_waitqueue_head(&target->poll.wq);
list_add(&target->target.list, &target_list);
tid = kernel_thread(iscsi_target_read_thread, target, CLONE_FS | CLONE_FILES);
if (tid < 0) {
err = tid;
goto fail1;
}
wait_event(target->poll.wq, target->poll.read_flags & POLL_INITIALIZED);
tid = kernel_thread(iscsi_target_write_thread, target, CLONE_FS | CLONE_FILES);
if (tid < 0) {
err = tid;
goto fail2;
}
wait_event(target->poll.wq, target->poll.write_flags & POLL_INITIALIZED);
iscsi_target_proc_init(target);
MOD_INC_USE_COUNT;
return 0;
fail2:
//stop thread
fail1:
list_del(&target->target.list);
kfree(target);
return err;
}
/**
* Remove a iscsi target.
* Caller must hold iscsi_sem.
*
* iscsi_target_remove -
* @target: ptr to target
*
* @return -errno
*/
int iscsi_target_remove(struct iscsi_target *target)
{
dprintk(D_SETUP, "iscsi_target_remove: %u\n", target->target.id);
if (!list_empty(&target->session_list) || !list_empty(&target->lun_list))
return -EBUSY;
iscsi_target_proc_exit(target);
target->poll.read_flags |= POLL_EXIT;
wake_up_process(target->poll.read_thread);
wait_event(target->poll.wq, !(target->poll.read_flags & POLL_EXIT));
target->poll.write_flags |= POLL_EXIT;
wake_up_process(target->poll.write_thread);
target->poll.last_write_wake = iscsi_jiffies++;
wait_event(target->poll.wq, !(target->poll.write_flags & POLL_EXIT));
list_del(&target->target.list);
kfree(target->name);
kfree(target->alias);
kfree(target);
MOD_DEC_USE_COUNT;
return 0;
}
/**
* Lookup a iscsi target.
* Caller must hold iscsi_sem.
*
* iscsi_target_lookup -
* @id: id of target
*
* @return ptr to target or NULL
*/
struct iscsi_target *iscsi_target_lookup(u32 id)
{
struct list_head *entry;
struct iscsi_target *target;
for (entry = target_list.next; entry != &target_list; entry = entry->next) {
target = list_entry(entry, struct iscsi_target, target.list);
if (target->target.id == id)
return target;
}
return NULL;
}
/**
* Find a session of a target.
* TODO: protect me!!!
*
* iscsi_target_lookup_session -
* @target: ptr to target
* @id: id of session
*
* @return ptr to session or NULL
*/
struct iscsi_session *iscsi_target_lookup_session(struct iscsi_target *target, u64 sid)
{
struct list_head *entry;
struct iscsi_session *session;
list_for_each(entry, &target->session_list) {
session = list_entry(entry, struct iscsi_session, list);
if (session->sid == sid)
return session;
}
return NULL;
}
/**
* Find a lun of a target.
* TODO: protect me!!!
*
* iscsi_target_lookup_lun
* @target: ptr to target
* @id: id of lun
*
* @return ptr to lun or NULL
*/
struct iscsi_lun *iscsi_target_lookup_lun(struct iscsi_target *target, u32 id)
{
struct list_head *entry;
struct iscsi_lun *lun;
// TODO: FIXME!!! PROTECTME!!!
list_for_each(entry, &target->lun_list) {
lun = list_entry(entry, struct iscsi_lun, list);
if (lun->lun == id)
return lun;
}
return NULL;
}
/**
* read thread of a target.
*
* iscsi_target_read_thread -
* @arg: ptr to target
*
* @return ignored
*/
static int iscsi_target_read_thread(void *arg)
{
struct iscsi_target *target = arg;
struct iscsi_conn *conn;
struct iscsi_conn *closed_conn = NULL;
struct iscsi_cmnd *cmnd;
struct list_head *entry;
poll_table *wp;
struct msghdr msg;
struct iovec iov[ISCSI_CONN_IOV_MAX];
unsigned int mask;
int res, len, i;
daemonize();
reparent_to_init();
/* block signals */
siginitsetinv(¤t->blocked, 0);
/* Set the name of this process. */
sprintf(current->comm, "itarget%dr", target->target.id);
target->poll.read_thread = current;
wp = NULL;
set_fs(KERNEL_DS);
dprintk(D_THREAD, "iscsi_target_read_thread(%u): initialized\n", target->target.id);
memset(&msg, 0, sizeof(msg));
target->poll.read_flags = POLL_INITIALIZED;
wake_up(&target->poll.wq);
while (!(target->poll.read_flags & POLL_EXIT)) {
dprintk(D_THREAD, "iscsi_target_read_thread(%u): wakeup\n", target->target.id);
down_read(&target->poll.sem);
set_current_state(TASK_INTERRUPTIBLE);
list_for_each(entry, &target->poll.list) {
conn = list_entry(entry, struct iscsi_conn, poll_list);
if (conn->state != ISCSI_CONN_ACTIVE) {
switch (conn->state) {
case ISCSI_CONN_CLOSING:
if (!closed_conn)
closed_conn = conn;
continue;
default:
continue;
}
}
if (!conn->read_cmnd) {
dprintk(D_THREAD, "new command at %#Lx:%u\n", conn->session->sid, conn->cid);
iscsi_conn_create_read_cmnd(conn);
mask = conn->file->f_op->poll(conn->file, &target->poll.read_poll);
} else
mask = conn->file->f_op->poll(conn->file, wp);
if (mask & POLLIN) while (1) {
dprintk(D_THREAD, "recv %#Lx:%u: %d\n", conn->session->sid, conn->cid, conn->read_si