From: Jeff Garzik <jgarzik@pobox.com>
To: linux-scsi@vger.kernel.org
Subject: [iscsi 2/2] iscsi-probe.c
Date: Tue, 23 Sep 2003 11:23:06 -0400 [thread overview]
Message-ID: <20030923152306.GA14522@gtf.org> (raw)
For review only.
/*
* iSCSI driver for Linux
* Copyright (C) 2001 Cisco Systems, Inc.
* maintained by linux-iscsi@cisco.com
*
* 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.
*
* See the file COPYING included with this distribution for more details.
*
* $Id: iscsi-probe.c,v 1.24.2.1 2003/09/09 11:52:33 smhatre Exp $
*
*/
#include <linux/config.h>
#include <linux/version.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/byteorder.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/file.h>
#include <linux/kernel.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/dirent.h>
#include <linux/proc_fs.h>
#include <linux/blk.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/config.h>
#include <linux/poll.h>
#include <linux/smp_lock.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/net.h>
#include <net/sock.h>
#include <linux/socket.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/timer.h>
# include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <scsi/sg.h>
#include <endian.h>
/* these are from $(TOPDIR)/drivers/scsi, not $(TOPDIR)/include */
#include <scsi.h>
#include <hosts.h>
#include "iscsi-common.h"
#include "iscsi-ioctl.h"
#include "iscsi.h"
/* LUN probing needs to be serialized across all HBA's, to keep a somewhat sane ordering */
DECLARE_MUTEX(iscsi_lun_probe_mutex);
spinlock_t iscsi_lun_probe_lock = SPIN_LOCK_UNLOCKED;
static iscsi_session_t *iscsi_lun_probe_head = NULL;
static iscsi_session_t *iscsi_lun_probe_tail = NULL;
static iscsi_session_t *iscsi_currently_probing = NULL;
static volatile int iscsi_next_probe = 0;
volatile unsigned long iscsi_lun_probe_start = 0;
/* we need to make some syscalls to create and destroy the device name tree. */
static int errno = 0;
static inline _syscall2(long, mkdir, const char *, dir, int, mode);
static inline _syscall1(long, unlink, const char *, path);
static inline _syscall2(long, symlink, const char *, oldname, const char *,
newname);
static inline
_syscall3(int, open, const char *, file, int, flag, int, mode)
static inline
_syscall1(int, close, int, fd)
static inline
_syscall1(long, rmdir, const char *, path);
static inline
_syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);
/* caller must hold iscsi_lun_probe_lock */
static int
enqueue_lun_probe(iscsi_session_t * session)
{
if (session->probe_next || session->probe_prev) {
DEBUG_INIT
("iSCSI: session for bus_id id %d target_id %d already queued for LUN probing\n",
session->iscsi_bus, session->target_id);
return 0;
}
if (iscsi_lun_probe_head) {
if (session->probe_order < iscsi_lun_probe_head->probe_order) {
/* insert before the current head */
session->probe_prev = NULL;
session->probe_next = iscsi_lun_probe_head;
iscsi_lun_probe_head->probe_prev = session;
iscsi_lun_probe_head = session;
} else if (session->probe_order >=
iscsi_lun_probe_tail->probe_order) {
/* insert after the tail */
session->probe_next = NULL;
session->probe_prev = iscsi_lun_probe_tail;
iscsi_lun_probe_tail->probe_next = session;
iscsi_lun_probe_tail = session;
} else {
/* insert somewhere in the middle */
iscsi_session_t *search = iscsi_lun_probe_head;
while (search && search->probe_next) {
if (session->probe_order <
search->probe_next->probe_order) {
session->probe_next =
search->probe_next;
session->probe_prev = search;
search->probe_next->probe_prev =
session;
search->probe_next = session;
break;
}
search = search->probe_next;
}
}
} else {
/* become the only session in the queue */
session->probe_next = session->probe_prev = NULL;
iscsi_lun_probe_head = iscsi_lun_probe_tail = session;
}
return 1;
}
/* caller must hold iscsi_lun_probe_lock */
static void
dequeue_lun_probe(iscsi_session_t * session)
{
if (iscsi_currently_probing == session) {
/* the timer may have tried to start us probing just before we gave up */
iscsi_currently_probing = NULL;
} else {
if (iscsi_lun_probe_head == session) {
if ((iscsi_lun_probe_head =
iscsi_lun_probe_head->probe_next))
iscsi_lun_probe_head->probe_prev = NULL;
else
iscsi_lun_probe_tail = NULL;
} else if (iscsi_lun_probe_tail == session) {
iscsi_lun_probe_tail = iscsi_lun_probe_tail->probe_prev;
iscsi_lun_probe_tail->probe_next = NULL;
} else {
/* in the middle */
if (session->probe_next && session->probe_prev) {
session->probe_prev->probe_next =
session->probe_next;
session->probe_next->probe_prev =
session->probe_prev;
} else {
printk
("iSCSI: bug - dequeue_lun_probe session for bus %d target %d , prev %p, next %p\n",
session->iscsi_bus, session->target_id,
session->probe_prev, session->probe_next);
}
}
}
}
static int
wait_for_probe_order(iscsi_session_t * session)
{
spin_lock(&iscsi_lun_probe_lock);
if ((iscsi_currently_probing == session) || session->probe_next
|| session->probe_prev) {
/* we're already probing or queued to be probed, ignore the 2nd probe request */
DEBUG_INIT
("iSCSI: session for bus %d target %d to %s ignoring duplicate probe request\n",
session->iscsi_bus, session->target_id, session->log_name);
spin_unlock(&iscsi_lun_probe_lock);
return 0;
} else if ((iscsi_currently_probing == NULL)
&& (session->probe_order <= iscsi_next_probe)) {
/* if there's no LUN being probed, and our probe_order can go now, start probing */
DEBUG_INIT
("iSCSI: session for bus %d target %d to %s, probe_order %d <= next %d, not waiting\n",
session->iscsi_bus, session->target_id, session->log_name,
session->probe_order, iscsi_next_probe);
iscsi_currently_probing = session;
/* let the timer know another session became ready for LUN probing. */
iscsi_lun_probe_start = (jiffies + (3 * HZ));
if (iscsi_lun_probe_start == 0)
iscsi_lun_probe_start = 1;
smp_mb();
spin_unlock(&iscsi_lun_probe_lock);
return 1;
} else if (enqueue_lun_probe(session)) {
/* otherwise queue up based on our probe order */
/* tell the timer when to start the LUN probing, to handle gaps in the probe_order */
iscsi_lun_probe_start =
(jiffies + (3 * HZ)) ? (jiffies + (3 * HZ)) : 1;
smp_mb();
DEBUG_INIT
("iSCSI: queued session for bus %d target %d for LUN probing, probe_order %d, probe_start at %lu\n",
session->iscsi_bus, session->target_id,
session->probe_order, iscsi_lun_probe_start);
spin_unlock(&iscsi_lun_probe_lock);
/* and wait for either the timer or the currently probing session to wake us up */
if (down_interruptible(&session->probe_sem)) {
logmsg(AS_INFO,
"iSCSI: session for bus %d target %d to %s interrupted while waiting to probe LUNs\n",
session->iscsi_bus, session->target_id,
session->log_name);
/* give up and take ourselves out of the lun probing data structures */
spin_lock(&iscsi_lun_probe_lock);
dequeue_lun_probe(session);
spin_unlock(&iscsi_lun_probe_lock);
return 0;
}
/* give up if the session is terminating */
if (test_bit(SESSION_TERMINATING, &session->control_bits)) {
logmsg(AS_INFO,
"iSCSI: session for bus %d target %d to %s terminated while waiting to probe LUNs\n",
session->iscsi_bus, session->target_id,
session->log_name);
/* give up and take ourselves out of the lun probing data structures */
spin_lock(&iscsi_lun_probe_lock);
dequeue_lun_probe(session);
spin_unlock(&iscsi_lun_probe_lock);
return 0;
}
#ifdef DEBUG
/* we should be out of the queue, and in iscsi_currently_probing */
spin_lock(&iscsi_lun_probe_lock);
if (iscsi_currently_probing != session)
printk
("iSCSI: bug - currently probing should be (bus %d target %d) , not (bus %d target %d\n",
session->iscsi_bus, session->target_id,
iscsi_currently_probing->iscsi_bus,
iscsi_currently_probing->target_id);
spin_unlock(&iscsi_lun_probe_lock);
#endif
DEBUG_INIT
("iSCSI: wait_for_probe_order (bus %d target %d) returning 1\n",
session->iscsi_bus, session->target_id);
return 1;
}
/* silently fail, since the enqueue attempt will have logged any detailed messages needed */
spin_unlock(&iscsi_lun_probe_lock);
return 0;
}
/* caller must hold iscsi_lun_probe_lock */
static void
start_next_lun_probe(void)
{
if (iscsi_currently_probing) {
printk
("iSCSI: bug - start_next_lun_probe called while currently probing on (bus %d target %d at %lu\n",
iscsi_currently_probing->iscsi_bus,
iscsi_currently_probing->target_id, jiffies);
} else if (iscsi_lun_probe_head) {
/* pop one off the queue, and tell it to start probing */
iscsi_currently_probing = iscsi_lun_probe_head;
if ((iscsi_lun_probe_head =
iscsi_currently_probing->probe_next))
iscsi_lun_probe_head->probe_prev = NULL;
else
iscsi_lun_probe_tail = NULL;
/* it's out of the queue now */
iscsi_currently_probing->probe_next = NULL;
iscsi_currently_probing->probe_prev = NULL;
/* skip over any gaps in the probe order */
if (iscsi_next_probe < iscsi_currently_probing->probe_order) {
DEBUG_INIT
("iSCSI: LUN probe_order skipping from %d to %d\n",
iscsi_next_probe,
iscsi_currently_probing->probe_order);
iscsi_next_probe = iscsi_currently_probing->probe_order;
smp_mb();
}
/* wake up the ioctl which is waiting to do a probe */
DEBUG_INIT
("iSCSI: starting LUN probe for session (bus %d target %d) to %s\n",
iscsi_currently_probing->iscsi_bus,
iscsi_currently_probing->
target_id iscsi_currently_probing->log_name);
up(&iscsi_currently_probing->probe_sem);
} else {
/* if there is nothing else queued, then we don't need the timer to keep checking,
* and we want to reset the probe order so that future LUN probes get queued,
* and maintain the proper relative order amonst themselves, even if the global
* order may have been lost.
*/
DEBUG_INIT
("iSCSI: start_next_lun_probe has nothing to start, resetting next LUN probe from %d to 0 at %lu\n",
iscsi_next_probe, jiffies);
iscsi_lun_probe_start = 0;
iscsi_next_probe = 0;
smp_mb();
}
}
void
iscsi_possibly_start_lun_probing(void)
{
spin_lock(&iscsi_lun_probe_lock);
if (iscsi_currently_probing == NULL) {
/* if we're not probing already, make sure we start */
DEBUG_INIT("iSCSI: timer starting LUN probing at %lu\n",
jiffies);
start_next_lun_probe();
}
spin_unlock(&iscsi_lun_probe_lock);
}
static void
iscsi_probe_finished(iscsi_session_t * session)
{
spin_lock(&iscsi_lun_probe_lock);
if (iscsi_currently_probing == session) {
iscsi_currently_probing = NULL;
DEBUG_INIT
("iSCSI: session (bus %d target %d) to %s finished probing LUNs at %lu\n",
session->iscsi_bus, session->target_id, session->log_name,
jiffies);
/* continue through the probe order */
if (iscsi_next_probe == session->probe_order)
iscsi_next_probe++;
/* and possibly start another session probing */
if (iscsi_lun_probe_head == NULL) {
/* nothing is queued, reset LUN probing */
DEBUG_INIT
("iSCSI: probe_finished has nothing to start, resetting next LUN probe from %d to 0 at %lu\n",
iscsi_next_probe, jiffies);
iscsi_next_probe = 0;
iscsi_lun_probe_start = 0;
smp_mb();
} else
if ((iscsi_lun_probe_head->probe_order <= iscsi_next_probe)
|| (iscsi_lun_probe_start
&& time_before_eq(iscsi_lun_probe_start,
jiffies))) {
/* next in order is up, or the timer has expired, start probing */
start_next_lun_probe();
} else {
DEBUG_INIT
("iSCSI: iscsi_probe_finished can't start_next_lun_probe at %lu, next %d, head %p (%d), tail %p (%d), current %p, start time %lu\n",
jiffies, iscsi_next_probe, iscsi_lun_probe_head,
iscsi_lun_probe_head ? iscsi_lun_probe_head->
probe_order : -1, iscsi_lun_probe_tail,
iscsi_lun_probe_tail ? iscsi_lun_probe_tail->
probe_order : -1, iscsi_currently_probing,
iscsi_lun_probe_start);
}
} else {
/* should be impossible */
printk
("iSCSI: bug - session (bus %d target %d) in iscsi_probe_finished, but currently probing (bus %d target %d)\n",
session->iscsi_bus, session->target_id,
iscsi_currently_probing->iscsi_bus,
iscsi_currently_probing->target_id);
}
spin_unlock(&iscsi_lun_probe_lock);
}
/* try to write to /proc/scsi/scsi */
static int
write_proc_scsi_scsi(iscsi_session_t * session, char *str)
{
struct file *filp = NULL;
loff_t offset = 0;
int rc = 0;
mm_segment_t oldfs = get_fs();
set_fs(get_ds());
filp = filp_open("/proc/scsi/scsi", O_WRONLY, 0);
if (IS_ERR(filp)) {
printk
("iSCSI: session (bus %d target %d)couldn't open /proc/scsi/scsi\n",
session->iscsi_bus, session->target_id);
set_fs(oldfs);
return -ENOENT;
}
rc = filp->f_op->write(filp, str, strlen(str), &offset);
filp_close(filp, 0);
set_fs(oldfs);
if (rc >= 0) {
/* assume it worked, since the non-negative return codes aren't set very reliably.
* wait for 20 ms to avoid deadlocks on SMP systems.
* FIXME: figure out why the SMP systems need this wait, and fix the kernel.
*/
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(MSECS_TO_JIFFIES(20));
return 1;
}
return rc;
}
/* caller must hold the iscsi_lun_probe_mutex */
static int
iscsi_probe_lun(iscsi_session_t * session, int lun)
{
char str[80];
int rc;
if (lun >= ISCSI_MAX_LUN)
return 0;
sprintf(str, "scsi add-single-device %d %d %d %d\n",
session->host_no, session->channel, session->target_id, lun);
str[sizeof (str) - 1] = '\0';
rc = write_proc_scsi_scsi(session, str);
if (rc < 0) {
/* clear the newline */
str[strlen(str) - 1] = '\0';
logmsg
(AS_ERROR,
"iSCSI: session (bus %d target %d) error %d writing '%s' to /proc/scsi/scsi\n",
session->iscsi_bus, session->target_id, rc, str);
return 0;
}
return rc;
}
static int
iscsi_remove_lun(iscsi_session_t * session, int lun)
{
char str[88];
int rc = 0;
sprintf(str, "scsi remove-single-device %d %d %d %d\n",
session->host_no, session->channel, session->target_id, lun);
str[sizeof (str) - 1] = '\0';
rc = write_proc_scsi_scsi(session, str);
if (rc < 0) {
/* clear the newline */
str[strlen(str) - 1] = '\0';
printk
("iSCSI: session (bus %d target %d) error %d writing '%s' to /proc/scsi/scsi\n",
session->iscsi_bus, session->target_id, rc, str);
return 0;
} else {
/* removed it */
clear_bit(lun, session->luns_activated);
clear_bit(lun, session->luns_detected);
return 1;
}
return rc;
}
static void
empty_directory(char *dir, char *data, int size)
{
int fd;
struct dirent dent;
int rc, processed;
char *name = dir + strlen(dir);
/* there should only be directories in the target dir */
if ((fd = open(dir, O_DIRECTORY | O_RDONLY, 0)) >= 0) {
/* loop doing getdents, and unlinking files */
do {
rc = getdents(fd, (struct dirent *) data, size);
DEBUG_FLOW("iSCSI: getdents %s, size %d, returned %d\n",
dir, size, rc);
processed = 0;
while (processed < rc) {
memcpy(&dent, &data[processed], sizeof (dent));
strcpy(name,
&data[processed] +
offsetof(struct dirent, d_name));
if (strcmp(name, ".") && strcmp(name, "..")) {
DEBUG_FLOW("iSCSI: unlink %s\n", dir);
unlink(dir);
}
processed += dent.d_reclen;
}
} while (rc > 0);
name[0] = '\0';
close(fd);
}
}
void
iscsi_remove_luns(iscsi_session_t * session)
{
int l;
mm_segment_t oldfs;
char *data = session->rx_buffer;
int size = sizeof (session->rx_buffer) - 1;
char *lun_dir =
session->target_link_dir + strlen(session->target_link_dir);
char *bus_dir = lun_dir - 2; /* before the slash */
char c;
/* try to release the kernel's SCSI device structures for every LUN */
down(&iscsi_lun_probe_mutex);
oldfs = get_fs();
set_fs(get_ds());
for (l = 0; l < ISCSI_MAX_LUN; l++) {
if (session->target_link_dir[0] == '/') {
sprintf(lun_dir, "lun%d/", l);
/* this assumes the session isn't using the rx_buffer right now */
empty_directory(session->target_link_dir, data, size);
rmdir(session->target_link_dir);
}
if (test_bit(l, session->luns_activated)) {
/* tell Linux to release the Scsi_Devices */
iscsi_remove_lun(session, l);
}
}
if (session->target_link_dir[0] == '/') {
/* and get rid of the target dir itself */
*lun_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n", session->target_link_dir);
rmdir(session->target_link_dir);
/* if the bus dir is empty now, get rid of it too, but don't corrupt the session's target dir */
while (*bus_dir != '/')
bus_dir--;
bus_dir++; /* leave the slash */
c = *bus_dir;
*bus_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n", session->target_link_dir);
rmdir(session->target_link_dir);
*bus_dir = c;
}
set_fs(oldfs);
up(&iscsi_lun_probe_mutex);
}
void
iscsi_remove_lun_complete(iscsi_session_t * session, int lun_id)
{
mm_segment_t oldfs;
char data[sizeof (struct dirent) + 1];
int size = sizeof (data) - 1;
char *lun_dir =
session->target_link_dir + strlen(session->target_link_dir);
char *bus_dir = lun_dir - 2; /* before the slash */
char c;
/* try to release the kernel's SCSI device structures for every LUN */
down(&iscsi_lun_probe_mutex);
oldfs = get_fs();
set_fs(get_ds());
if (session->target_link_dir[0] == '/') {
sprintf(lun_dir, "lun%d/", lun_id);
/* this assumes the session isn't using the rx_buffer right now */
empty_directory(session->target_link_dir, data, size);
rmdir(session->target_link_dir);
}
if (test_bit(lun_id, session->luns_activated)) {
/* tell Linux to release the Scsi_Devices */
iscsi_remove_lun(session, lun_id);
}
if (session->target_link_dir[0] == '/') {
/* and get rid of the target dir itself */
*lun_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n", session->target_link_dir);
rmdir(session->target_link_dir);
/* if the bus dir is empty now, get rid of it too, but don't corrupt the session's target dir */
while (*bus_dir != '/')
bus_dir--;
bus_dir++; /* leave the slash */
c = *bus_dir;
*bus_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n", session->target_link_dir);
rmdir(session->target_link_dir);
*bus_dir = c;
}
set_fs(oldfs);
up(&iscsi_lun_probe_mutex);
}
/* find all dir prefixes of pathname, and make them all if they don't exist */
static void
ensure_directories_exist(char *pathname, mode_t dir_mode)
{
char *end = pathname;
/* skip leading slashes */
while (end && *end && (*end == '/'))
end++;
while (end && (*end != '\0')) {
/* if there is another slash, make the dir.
* FIXME: we ought to ignore errors when the directory exists,
* but report errors where the directory doesn't exist and
* we failed to create it.
*/
while ((*end != '/') && (*end != '\0'))
end++;
if (*end == '/') {
*end = '\0';
mkdir(pathname, dir_mode);
*end = '/';
end++;
}
}
}
static int
get_device_scsi_quad(char *device_name, int *host, int *channel, int *target,
int *lun)
{
int ret = 0;
u_long info[2];
struct file *filp = NULL;
struct inode *inode = NULL;
filp = filp_open(device_name, O_RDONLY | O_NONBLOCK, 0);
if (IS_ERR(filp)) {
return 0;
}
memset(info, 0, sizeof (info));
inode = filp->f_dentry->d_inode;
if (filp->f_op->
ioctl(inode, filp, SCSI_IOCTL_GET_IDLUN,
(unsigned long) info) == 0) {
if (target)
*target = info[0] & 0xff;
if (lun)
*lun = (info[0] >> 8) & 0xff;
if (channel)
*channel = (info[0] >> 16) & 0xff;
/* 2.4 kernels give us all the info we need with that ioctl. */
if (host)
*host = ((info[0] >> 24) & 0xff);
ret = 1;
}
filp_close(filp, 0);
return ret;
}
static void
iscsi_update_disk_links(iscsi_session_t * session, int max_sd_devices,
int max_sd_partitions, mode_t dir_mode)
{
int i;
char devname[20];
/* we've reserved enough space in session->target_link_dir so that we can use it to build pathnames */
char *lun_dir =
session->target_link_dir + strlen(session->target_link_dir);
/* FIXME: can we get the number of devices supported from the running kernel? */
for (i = 0; i < max_sd_devices; i++) {
int host = -1, channel = -1, id = -1, lun = -1;
if (i < 26) {
sprintf(devname, "/dev/sd%c", 'a' + i);
} else {
/* double char names for disknum 26+ */
sprintf(devname, "/dev/sd%c%c", 'a' + (i / 26) - 1,
'a' + (i % 26));
}
if (get_device_scsi_quad(devname, &host, &channel, &id, &lun)) {
if ((host == session->host_no)
&& (channel == session->channel)
&& (id == session->target_id)) {
char *partition = devname + strlen(devname);
char *link;
int p;
DEBUG_INIT
("iSCSI: disk device node %s = bus %d target %d LUN %d\n",
devname, session->iscsi_bus, id, lun);
/* ensure the LUN dir exists */
sprintf(lun_dir, "lun%d/", lun);
ensure_directories_exist(session->
target_link_dir,
dir_mode);
link = lun_dir + strlen(lun_dir);
/* symlink the whole-disk device */
strcpy(link, "disk");
unlink(session->target_link_dir); /* remove any existing symlink */
symlink(devname, session->target_link_dir); /* make a new symlink */
/* and make links for each possible disk partition as well,
* since we don't want to have to track what partitions get added or removed.
* This works just like the normal partition device nodes, which
* are always present, but may or may not be openable.
*/
for (p = 1; p <= max_sd_partitions; p++) {
sprintf(partition, "%d", p);
sprintf(link, "part%d", p);
unlink(session->target_link_dir);
symlink(devname,
session->target_link_dir);
}
}
}
}
/* restore the session's target dir */
*lun_dir = '\0';
}
static void
iscsi_update_tape_links(iscsi_session_t * session, int max_st_devices,
mode_t dir_mode)
{
int i;
char devname[20];
/* we've reserved enough space in session->target_link_dir so that we can use it to build pathnames */
char *lun_dir =
session->target_link_dir + strlen(session->target_link_dir);
/* FIXME: can we get the number of devices supported from the running kernel? */
for (i = 0; i < max_st_devices; i++) {
int host = -1, channel = -1, id = -1, lun = -1;
/* we check the no-rewind device to avoid having side-effects */
sprintf(devname, "/dev/nst%d", i);
if (get_device_scsi_quad(devname, &host, &channel, &id, &lun)) {
if ((host == session->host_no)
&& (channel == session->channel)
&& (id == session->target_id)) {
char *link;
DEBUG_INIT
("iSCSI: tape device node %s = bus %d target %d LUN %d\n",
devname, session->iscsi_bus, id, lun);
/* ensure the LUN dir exists */
sprintf(lun_dir, "lun%d/", lun);
ensure_directories_exist(session->
target_link_dir,
dir_mode);
link = lun_dir + strlen(lun_dir);
/* auto-rewind nodes */
strcpy(link, "mt");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/st%d", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
strcpy(link, "mtl");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/st%dl", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
strcpy(link, "mtm");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/st%dm", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
strcpy(link, "mta");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/st%da", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
/* no rewind nodes */
strcpy(link, "mtn");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/nst%d", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
strcpy(link, "mtln");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/nst%dl", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
strcpy(link, "mtmn");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/nst%dm", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
strcpy(link, "mtan");
unlink(session->target_link_dir); /* remove any existing symlink */
sprintf(devname, "/dev/nst%da", i);
symlink(devname, session->target_link_dir); /* make a new symlink */
}
}
}
/* restore the session's target dir */
*lun_dir = '\0';
}
static void
iscsi_update_generic_links(iscsi_session_t * session, int max_sg_devices,
mode_t dir_mode)
{
int i;
char devname[20];
/* we've reserved enough space in session->target_link_dir so that we can use it to build pathnames */
char *lun_dir =
session->target_link_dir + strlen(session->target_link_dir);
char *link;
/* FIXME: can we get the number of devices supported from the running kernel? */
for (i = 0; i < max_sg_devices; i++) {
int host = -1, channel = -1, id = -1, lun = -1;
sprintf(devname, "/dev/sg%d", i);
if (get_device_scsi_quad(devname, &host, &channel, &id, &lun)) {
if ((host == session->host_no)
&& (channel == session->channel)
&& (id == session->target_id)) {
DEBUG_INIT
("iSCSI: generic device node %s = bus %d target %d LUN %d\n",
devname, session->iscsi_bus, id, lun);
/* ensure the LUN dir exists */
sprintf(lun_dir, "lun%d/", lun);
ensure_directories_exist(session->
target_link_dir,
dir_mode);
link = lun_dir + strlen(lun_dir);
strcpy(link, "generic");
unlink(session->target_link_dir); /* remove any existing symlink */
symlink(devname, session->target_link_dir); /* make a new symlink */
}
}
}
/* restore the session's target dir */
*lun_dir = '\0';
}
static void
iscsi_update_cd_links(iscsi_session_t * session, int max_sr_devices,
mode_t dir_mode)
{
int i;
char devname[20];
/* we've reserved enough space in session->target_link_dir so that we can use it to build pathnames */
char *lun_dir =
session->target_link_dir + strlen(session->target_link_dir);
char *link;
/* FIXME: can we get the number of devices supported from the running kernel? */
for (i = 0; i < max_sr_devices; i++) {
int host = -1, channel = -1, id = -1, lun = -1;
/* FIXME: the distribution may be using /dev/sr instead of /dev/scd */
sprintf(devname, "/dev/scd%d", i);
if (get_device_scsi_quad(devname, &host, &channel, &id, &lun)) {
if ((host == session->host_no)
&& (channel == session->channel)
&& (id == session->target_id)) {
DEBUG_INIT
("iSCSI: cdrom device node %s = bus %d target %d LUN %d\n",
devname, session->iscsi_bus, id, lun);
/* ensure the LUN dir exists */
sprintf(lun_dir, "lun%d/", lun);
ensure_directories_exist(session->
target_link_dir,
dir_mode);
link = lun_dir + strlen(lun_dir);
strcpy(link, "cd");
unlink(session->target_link_dir); /* remove any existing symlink */
symlink(devname, session->target_link_dir); /* make a new symlink */
}
}
}
/* restore the session's target dir */
*lun_dir = '\0';
}
/* compute the intersection of the LUNS detected and configured, and probe each LUN */
void
iscsi_probe_luns(iscsi_session_t * session, uint32_t * lun_bitmap,
scsi_device_info_t * device_info)
{
int l;
int detected = 0;
int probed = 0;
int activated = 0;
/* try wait for our turn to probe, to keep the device node ordering as repeatable as possible */
DEBUG_INIT
("iSCSI: session (bus %d target %d) to %s waiting to probe LUNs at %lu, probe order %d\n",
session->iscsi_bus, session->target_id, session->log_name, jiffies,
session->probe_order);
if (!wait_for_probe_order(session)) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) to %s couldn't probe LUNs, error waiting for probe order\n",
session->iscsi_bus, session->target_id, session->log_name);
return;
}
if (test_bit(SESSION_TERMINATING, &session->control_bits)) {
printk
("iSCSI: session (bus %d target %d)to %s terminated while waiting to probe LUNs\n",
session->iscsi_bus, session->target_id, session->log_name);
goto done;
}
if (signal_pending(current)) {
printk
("iSCSI: session (bus %d target %d) ioctl killed while waiting to probe LUNs\n",
session->iscsi_bus, session->target_id);
goto done;
}
/* make sure we're the only driver process trying to add or remove LUNs */
if (down_interruptible(&iscsi_lun_probe_mutex)) {
printk
("iSCSI: session (bus %d target %d) to %s interrupted while probing LUNs\n",
session->iscsi_bus, session->target_id, session->log_name);
goto done;
}
/* need to set the host's max_channel, max_id, max_lun, since we
* zero them in iscsi_detect in order to disable the scan that
* occurs during scsi_register_host.
*/
session->hba->host->max_id = ISCSI_MAX_TARGET_IDS_PER_BUS;
session->hba->host->max_lun = ISCSI_MAX_LUNS_PER_TARGET;
session->hba->host->max_channel = ISCSI_MAX_CHANNELS_PER_HBA - 1; /* convert from count to index */
smp_mb();
DEBUG_INIT
("iSCSI: probing LUNs for session (bus %d target %d) to %s at %lu, probe_order %d at %lu\n",
session->iscsi_bus, session->target_id, session->log_name, jiffies,
session->probe_order, jiffies);
for (l = 0; l < ISCSI_MAX_LUN; l++) {
if (test_bit(SESSION_TERMINATING, &session->control_bits))
goto give_up;
if (signal_pending(current))
goto give_up;
if (test_bit(l, session->luns_detected)) {
/* Check if lun has been removed */
if (!test_bit(l, session->luns_found)) {
if (iscsi_remove_lun(session, l) != 0) {
char buffer[sizeof (struct dirent) + 1],
c;
mm_segment_t oldfs;
int size = sizeof (buffer) - 1;
char *lun_dir =
session->target_link_dir +
strlen(session->target_link_dir);
char *bus_dir = lun_dir - 2; /* before the slash */
oldfs = get_fs();
set_fs(get_ds());
if (session->target_link_dir[0] == '/') {
sprintf(lun_dir, "lun%d/", l);
empty_directory(session->
target_link_dir,
buffer, size);
rmdir(session->target_link_dir);
}
/* If all luns on this target have been deleted.
* remove the target entry.
*
*/
if (session->target_link_dir[0] == '/') {
/* and get rid of the target dir itself */
*lun_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n",
session->
target_link_dir);
rmdir(session->target_link_dir);
/* if the bus dir is empty now, get rid of it too, but don't corrupt the session's target dir */
while (*bus_dir != '/')
bus_dir--;
bus_dir++;
c = *bus_dir;
*bus_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n",
session->
target_link_dir);
rmdir(session->target_link_dir);
*bus_dir = c;
}
set_fs(oldfs);
}
} else {
detected++;
/* if allowed and not already activated (successfully probed), probe it */
if ((lun_bitmap[l / 32] & (1 << (l % 32)))
&& !test_bit(l, session->luns_activated)) {
DEBUG_FLOW
("iSCSI: session (bus %d target %d) probing LUN %d at %lu\n",
session->iscsi_bus,
session->target_id, l, jiffies);
iscsi_probe_lun(session, l);
probed++;
if (test_bit
(l, session->luns_activated))
activated++;
}
}
} else {
if (test_bit(l, session->luns_activated)) {
if (iscsi_remove_lun(session, l) != 0) {
char buffer[sizeof (struct dirent) + 1],
c;
mm_segment_t oldfs;
int size = sizeof (buffer) - 1;
char *lun_dir =
session->target_link_dir +
strlen(session->target_link_dir);
char *bus_dir = lun_dir - 2; /* before the slash */
oldfs = get_fs();
set_fs(get_ds());
if (session->target_link_dir[0] == '/') {
sprintf(lun_dir, "lun%d/", l);
empty_directory(session->
target_link_dir,
buffer, size);
rmdir(session->target_link_dir);
}
/* If all luns on this target have been deleted.
* remove the target entry.
*
*/
if (session->target_link_dir[0] == '/') {
/* and get rid of the target dir itself */
*lun_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n",
session->
target_link_dir);
rmdir(session->target_link_dir);
/* if the bus dir is empty now, get rid of it too, but don't corrupt the session's target dir */
while (*bus_dir != '/')
bus_dir--;
bus_dir++;
c = *bus_dir;
*bus_dir = '\0';
DEBUG_FLOW("iSCSI: rmdir %s\n",
session->
target_link_dir);
rmdir(session->target_link_dir);
*bus_dir = c;
}
set_fs(oldfs);
}
}
}
}
if (detected == 0) {
printk
("iSCSI: no LUNs detected for session (bus %d target %d) to %s\n",
session->iscsi_bus, session->target_id, session->log_name);
} else if (LOG_ENABLED(ISCSI_LOG_INIT)) {
printk
("iSCSI: session (bus %d target %d) to %s probed %d of %d LUNs detected, %d new LUNs activated\n",
session->iscsi_bus, session->target_id, session->log_name,
probed, detected, activated);
}
/* optionally set up a symlink tree. We do this in the kernel so that we
* can guard it with the lun_probe_mutex. The high-level SCSI drivers in Linux tend
* to crash if a device node is opened while the Scsi_Device is still being
* initialized, so we want to make sure we're not doing any probes when we open
* lots of device nodes.
*/
if (session->target_link_dir[0] == '/') {
mm_segment_t oldfs = get_fs();
set_fs(get_ds());
/* make the target dir, so that the user can always see the target has a session, even if
* LUN probing fails to find anything or no target drivers have attached.
*/
ensure_directories_exist(session->target_link_dir,
session->dir_mode);
if (device_info->max_sd_devices > 0) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) updating disk links under %s\n",
session->iscsi_bus, session->target_id,
session->target_link_dir);
iscsi_update_disk_links(session,
device_info->max_sd_devices,
device_info->max_sd_partitions,
session->dir_mode);
}
if (device_info->max_sg_devices > 0) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) updating generic links under %s\n",
session->iscsi_bus, session->target_id,
session->target_link_dir);
iscsi_update_generic_links(session,
device_info->max_sg_devices,
session->dir_mode);
}
if (device_info->max_st_devices > 0) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) updating tape links under %s\n",
session->iscsi_bus, session->target_id,
session->target_link_dir);
iscsi_update_tape_links(session,
device_info->max_st_devices,
session->dir_mode);
}
if (device_info->max_sr_devices > 0) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) updating cdrom links under %s\n",
session->iscsi_bus, session->target_id,
session->target_link_dir);
iscsi_update_cd_links(session,
device_info->max_sr_devices,
session->dir_mode);
}
set_fs(oldfs);
}
give_up:
up(&iscsi_lun_probe_mutex);
done:
/* clean up after wait_for_probe_order, and possibly start the next session probing */
iscsi_probe_finished(session);
}
typedef struct iscsi_cmnd {
struct semaphore done_sem;
Scsi_Cmnd sc;
unsigned int bufflen;
uint8_t buffer[1];
} iscsi_cmnd_t;
/* callback function for Scsi_Cmnd's generated by the iSCSI driver itself */
void
iscsi_done(Scsi_Cmnd * sc)
{
iscsi_cmnd_t *c = (iscsi_cmnd_t *) sc->buffer;
up(&c->done_sem);
}
static int
iscsi_do_cmnd(iscsi_session_t * session, iscsi_cmnd_t * c,
unsigned int attempts_allowed)
{
Scsi_Cmnd *sc = NULL;
int queue_attempts = 0;
if (c->sc.host) {
DEBUG_FLOW
("iSCSI: iscsi_do_cmnd %p to (%u %u %u %u), Cmd 0x%02x, %u retries, buffer %p, bufflen %u\n",
c, c->sc.host->host_no, c->sc.channel,
c->sc.target, c->sc.lun, c->sc.cmnd[0], attempts_allowed,
c->sc.request_buffer, c->sc.request_bufflen);
} else {
printk
("iSCSI: session (bus %d target %d) iscsi_do_cmnd %p, buffer %p, bufflen %u, host %p\n",
session->iscsi_bus, session->target_id, c,
c->sc.request_buffer, c->sc.request_bufflen, c->sc.host);
return 0;
}
if (!c->sc.request_buffer)
return 0;
if (!c->sc.request_bufflen)
return 0;
sc = &(c->sc);
sc->retries = -1;
sc->allowed = attempts_allowed;
retry:
while (++sc->retries < sc->allowed) {
if (signal_pending(current))
return 0;
if (test_bit(SESSION_TERMINATING, &session->control_bits))
return 0;
sc->result = 0;
memset(sc->sense_buffer, 0, sizeof (sc->sense_buffer));
memset(c->buffer, 0, c->bufflen);
/* try to queue the command */
queue_attempts = 0;
for (;;) {
sema_init(&c->done_sem, 0);
smp_mb();
if (signal_pending(current))
return 0;
if (test_bit
(SESSION_TERMINATING, &session->control_bits))
return 0;
DEBUG_INIT
("iSCSI: detect_luns queueing %p to session (bus %d target %d) at %lu\n",
sc, session->iscsi_bus, session->target_id,
jiffies);
/* give up eventually, in case the replacement timeout is in effect.
* we don't want to loop forever trying to queue to a session
* that may never accept commands.
*/
if (iscsi_queue(session, sc, iscsi_done)) {
break;
} else if (queue_attempts++ >= 500) {
/* give up after 10 seconds */
return 0;
}
/* command not queued, wait a bit and try again */
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(MSECS_TO_JIFFIES(20));
}
DEBUG_QUEUE
("iSCSI: session (bus %d target %d) queued iscsi_cmnd %p, buffer %p, bufflen %u, scsi_done %p\n",
session->iscsi_bus, session->target_id, c,
c->sc.request_buffer, c->sc.request_bufflen,
c->sc.scsi_done);
/* wait til either the command completes, or we get signalled. */
if (down_interruptible(&c->done_sem)) {
/* if we got signalled, squash the command and give up */
iscsi_squash_cmnd(session, sc);
return 0;
}
DEBUG_QUEUE
("iSCSI: session (bus %d target %d) hba %p host %p woken up by iscsi_cmnd %p, buffer %p, bufflen %u\n",
session->iscsi_bus, session->target_id, session->hba,
session->hba->host, c, c->sc.request_buffer,
c->sc.request_bufflen);
/* the command completed, check the result and decide if it needs to be retried. */
DEBUG_FLOW
("iSCSI: session (bus %d target %d) iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x, "
"host byte 0x%x, SCSI status 0x%x, residual %u\n",
session->iscsi_bus, session->target_id c,
c->sc.host->host_no, c->sc.channel, c->sc.target,
c->sc.lun, c->sc.cmnd[0], (sc->result >> 24) & 0xFF,
sc->result & 0xFF, sc->resid);
/* check the host byte */
switch (host_byte(sc->result)) {
case DID_OK:
/* no problems so far */
break;
case DID_NO_CONNECT:
/* give up, we can't talk to the device */
printk
("iSCSI: failing iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x, "
"host byte 0x%x, SCSI status 0x%x, residual %u\n",
c, c->sc.host->host_no, c->sc.channel,
c->sc.target, c->sc.lun, c->sc.cmnd[0],
(sc->result >> 24) & 0xFF, sc->result & 0xFF,
sc->resid);
return 0;
case DID_ERROR:
case DID_SOFT_ERROR:
case DID_ABORT:
case DID_BUS_BUSY:
case DID_PARITY:
case DID_TIME_OUT:
case DID_RESET:
default:
if (LOG_ENABLED(ISCSI_LOG_INIT))
printk
("iSCSI: iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x, "
"host byte 0x%x, SCSI status 0x%x, residual %u\n",
c, c->sc.host->host_no,
c->sc.channel, c->sc.target, c->sc.lun,
c->sc.cmnd[0], (sc->result >> 24) & 0xFF,
sc->result & 0xFF, sc->resid);
/* some sort of problem, possibly retry */
goto retry;
}
/* check the SCSI status byte. Note, Linux values are right-shifted once compared to the SCSI spec */
switch (status_byte(sc->result)) {
case GOOD:
case COMMAND_TERMINATED:
/* make sure we got enough of a response */
if (sc->resid
&& ((iscsi_expected_data_length(sc) - sc->resid) <
sc->underflow)) {
/* try again */
if (LOG_ENABLED(ISCSI_LOG_INIT))
printk
("iSCSI: iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x, "
"residual %u, retrying to get %u bytes desired\n",
c, c->sc.host->host_no,
c->sc.channel, c->sc.target,
c->sc.lun, c->sc.cmnd[0],
sc->resid, sc->underflow);
goto retry;
}
/* all done */
return 1;
case BUSY: /* device is busy, try again later */
case QUEUE_FULL: /* tagged queuing device has a full queue, wait a bit and try again. */
sc->allowed++;
if (sc->allowed > 100) {
printk
("iSCSI: iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x, SCSI status 0x%x, out of retries\n",
c, c->sc.host->host_no,
c->sc.channel, c->sc.target, c->sc.lun,
c->sc.cmnd[0], sc->result & 0xFF);
return 0;
}
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(MSECS_TO_JIFFIES(20));
goto retry;
case CONDITION_GOOD:
case INTERMEDIATE_GOOD:
case INTERMEDIATE_C_GOOD:
/* we should never get the linked command return codes */
case RESERVATION_CONFLICT:
/* this is probably never going to happen for INQUIRY or REPORT_LUNS, but retry if it does */
printk
("iSCSI: session (bus %d target %d) iscsi_do_cmnd %p SCSI status 0x%x at %lu, retrying\n",
session->iscsi_bus, session->target_id, c,
sc->result & 0xFF, jiffies);
goto retry;
case CHECK_CONDITION:
/* look at the sense. If it's illegal request, don't bother retrying the command */
if ((sc->sense_buffer[0] & 0x70) == 0x70) {
switch (SENSE_KEY(sc->sense_buffer)) {
case ILLEGAL_REQUEST:
printk
("iSCSI: iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x, illegal request\n",
c, c->sc.host->host_no,
c->sc.channel, c->sc.target,
c->sc.lun, c->sc.cmnd[0]);
return 0;
default:
/* possibly retry */
if (LOG_ENABLED(ISCSI_LOG_INIT))
printk
("iSCSI: iscsi cmnd %p to (%u %u %u %u), Cmd 0x%02x with sense, retrying\n",
c, c->sc.host->host_no,
c->sc.channel,
c->sc.target, c->sc.lun,
c->sc.cmnd[0]);
goto retry;
}
}
goto retry;
default:
printk
("iSCSI: session (bus %d target %d) iscsi_do_cmnd %p unexpected SCSI status 0x%x at %lu\n",
session->iscsi_bus, session->target_id, c,
sc->result & 0xFF, jiffies);
return 0;
}
}
if (LOG_ENABLED(ISCSI_LOG_INIT))
printk
("iSCSI: session (bus %d target %d) iscsi_do_cmnd %p SCSI status 0x%x, out of retries at %lu\n",
session->iscsi_bus, session->target_id, c,
sc->result & 0xFF, jiffies);
return 0;
}
void
send_tur(iscsi_session_t * session)
{
iscsi_cmnd_t *c = NULL;
Scsi_Cmnd *sc = NULL;
size_t cmd_size = sizeof (iscsi_cmnd_t);
unsigned int bufflen = 255;
cmd_size += bufflen;
c = kmalloc(cmd_size, GFP_KERNEL);
if (!c) {
printk
("iSCSI: session (bus %d target %d) send_tur couldn't allocate a Scsi_Cmnd\n",
session->iscsi_bus, session->target_id);
return;
}
/* initialize */
memset(c, 0, cmd_size);
sema_init(&c->done_sem, 0);
c->bufflen = bufflen;
DEBUG_ALLOC
("iSCSI: session (bus %d target %d) hba %p host %p allocated iscsi cmnd %p, size %d, buffer %p, bufflen %u, end %p\n",
session->iscsi_bus, session->target_id, session->hba,
session->hba->host, c, cmd_size, c->buffer, c->bufflen,
c->buffer + c->bufflen);
/* fill in the basic required info in the Scsi_Cmnd */
sc = &(c->sc);
sc->host = session->hba->host;
sc->channel = session->channel;
sc->target = session->target_id;
sc->lun = 0;
sc->use_sg = 0;
sc->request_buffer = c->buffer;
sc->request_bufflen = c->bufflen;
sc->scsi_done = iscsi_done;
sc->timeout_per_command = 30 * HZ;
sc->resid = 0;
sc->underflow = 8;
init_timer(&sc->eh_timeout);
/* save a pointer to the iscsi_cmnd in the Scsi_Cmnd, so that iscsi_done can
use it */
sc->buffer = (void *) c;
{
if (signal_pending(current)) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) send_tur aborted by signal\n",
session->iscsi_bus, session->target_id);
goto done;
}
if (test_bit(SESSION_TERMINATING, &session->control_bits))
goto done;
sc->cmd_len = 6;
memset(sc->cmnd, 0, sizeof (sc->cmnd));
sc->cmnd[0] = TEST_UNIT_READY;
sc->cmnd[1] = 0;
sc->cmnd[2] = 0;
sc->cmnd[3] = 0;
sc->cmnd[4] = 0;
sc->cmnd[5] = 0;
smp_mb();
if (iscsi_do_cmnd(session, c, 6)) {
} else {
printk
("iSCSI: session (bus %d target %d) Received a sense for a TEST UNIT READY\n",
session->iscsi_bus, session->target_id);
}
}
done:
kfree(c);
}
void
reinitialize_disk(iscsi_session_t * session)
{
iscsi_cmnd_t *c = NULL;
Scsi_Cmnd *sc = NULL;
size_t cmd_size = sizeof (iscsi_cmnd_t);
unsigned int bufflen = 255;
cmd_size += bufflen;
c = kmalloc(cmd_size, GFP_KERNEL);
if (!c) {
printk
("iSCSI: session (bus %d target %d) reinitialize_disk couldn't allocate a Scsi_Cmnd\n",
session->iscsi_bus, session->target_id);
return;
}
/* initialize */
memset(c, 0, cmd_size);
sema_init(&c->done_sem, 0);
c->bufflen = bufflen;
DEBUG_ALLOC
("iSCSI: session (bus %d target %d) hba %p host %p allocated iscsi cmnd %p, size %d, buffer %p, bufflen %u, end %p\n",
session->iscsi_bus, session->target_id, session->hba,
session->hba->host, c, cmd_size, c->buffer, c->bufflen,
c->buffer + c->bufflen);
/* fill in the basic required info in the Scsi_Cmnd */
sc = &(c->sc);
sc->host = session->hba->host;
sc->channel = session->channel;
sc->target = session->target_id;
sc->lun = 0;
sc->use_sg = 0;
sc->request_buffer = c->buffer;
sc->request_bufflen = c->bufflen;
sc->scsi_done = iscsi_done;
sc->timeout_per_command = 30 * HZ;
sc->resid = 0;
sc->underflow = 8;
init_timer(&sc->eh_timeout);
/* save a pointer to the iscsi_cmnd in the Scsi_Cmnd, so that iscsi_done can
use it */
sc->buffer = (void *) c;
{
if (signal_pending(current)) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) reinitialize_disk aborted by signal\n",
session->iscsi_bus, session->target_id);
goto done;
}
if (test_bit(SESSION_TERMINATING, &session->control_bits))
goto done;
sc->cmd_len = 6;
memset(sc->cmnd, 0, sizeof (sc->cmnd));
sc->cmnd[0] = START_STOP;
sc->cmnd[1] = 0;
sc->cmnd[1] |= 1;
sc->cmnd[2] = 0;
sc->cmnd[3] = 0;
sc->cmnd[4] = 1;
sc->cmnd[5] = 0;
smp_mb();
if (iscsi_do_cmnd(session, c, 6)) {
} else {
printk
("\niSCSI:session (bus %d target %d) Received a sense for a START STOP\n",
session->iscsi_bus, session->target_id);
}
}
done:
kfree(c);
}
static void
make_report_luns(Scsi_Cmnd * sc, uint32_t max_entries)
{
uint32_t length = 8 + (max_entries * 8); /* 8 byte header plus 8 bytes per LUN */
sc->cmd_len = 10;
sc->request_bufflen = length;
sc->underflow = 8; /* need at least the length */
sc->resid = 0;
/* CDB */
memset(sc->cmnd, 0, sizeof (sc->cmnd));
sc->cmnd[0] = REPORT_LUNS;
sc->cmnd[1] = 0;
sc->cmnd[2] = 0; /* either reserved or select report in various versions of SCSI-3 */
sc->cmnd[3] = 0;
sc->cmnd[4] = 0;
sc->cmnd[5] = 0;
sc->cmnd[6] = (length >> 24) & 0xFF;
sc->cmnd[7] = (length >> 16) & 0xFF;
sc->cmnd[8] = (length >> 8) & 0xFF;
sc->cmnd[9] = (length) & 0xFF;
}
static void
make_inquiry(Scsi_Cmnd * sc, int lun0_scsi_level)
{
sc->cmd_len = 6;
sc->request_bufflen = 255;
if (sc->lun == 0)
sc->underflow = 3; /* we need at least the peripheral code and SCSI version */
else
sc->underflow = 1; /* we need at least the peripheral code */
sc->resid = 0;
memset(sc->cmnd, 0, sizeof (sc->cmnd));
sc->cmnd[0] = INQUIRY;
if (lun0_scsi_level >= 0x3)
sc->cmnd[1] = 0; /* reserved in SCSI-3 and higher */
else
sc->cmnd[1] = (sc->lun << 5) & 0xe0;
sc->cmnd[2] = 0;
sc->cmnd[3] = 0;
sc->cmnd[4] = 255; /* length */
sc->cmnd[5] = 0;
}
/* scan for LUNs */
void
iscsi_detect_luns(iscsi_session_t * session)
{
int l;
iscsi_cmnd_t *c = NULL;
Scsi_Cmnd *sc = NULL;
int lun0_scsi_level = 0;
size_t cmd_size = sizeof (iscsi_cmnd_t);
unsigned int bufflen = 0;
uint32_t last_luns = 0;
uint32_t luns = 32; /* start small to avoid bugs in REPORT_LUNS handling */
int report_luns_failed = 0;
memset(session->luns_found, 0, sizeof (session->luns_found));
/* need enough buffer space for replies to INQUIRY and REPORT_LUNS */
if ((8 + (ISCSI_MAX_LUN * 8)) < 255)
bufflen = 255;
else
bufflen = (ISCSI_MAX_LUN * 8) + 8;
cmd_size += bufflen;
c = kmalloc(cmd_size, GFP_KERNEL);
if (!c) {
printk
("iSCSI: session (bus %d target %d) iscsi_detect_luns couldn't allocate a Scsi_Cmnd\n",
session->iscsi_bus, session->target_id);
return;
}
/* initialize */
memset(c, 0, cmd_size);
sema_init(&c->done_sem, 0);
c->bufflen = bufflen;
DEBUG_ALLOC
("iSCSI: session (bus %d target %d) hba %p host %p allocated iscsi cmnd %p, size %d, buffer %p, bufflen %u, end %p\n",
session->iscsi_bus, session->target_id, session->hba,
session->hba->host, c, cmd_size, c->buffer, c->bufflen,
c->buffer + c->bufflen);
/* fill in the basic required info in the Scsi_Cmnd */
sc = &(c->sc);
sc->host = session->hba->host;
sc->channel = session->channel;
sc->target = session->target_id;
sc->lun = 0;
sc->use_sg = 0;
sc->request_buffer = c->buffer;
sc->request_bufflen = c->bufflen;
sc->scsi_done = iscsi_done;
sc->timeout_per_command = 30 * HZ;
init_timer(&sc->eh_timeout);
/* save a pointer to the iscsi_cmnd in the Scsi_Cmnd, so that iscsi_done can use it */
sc->buffer = (void *) c;
do {
if (signal_pending(current)) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) detect LUNs aborted by signal\n",
session->iscsi_bus, session->target_id);
goto done;
}
if (test_bit(SESSION_TERMINATING, &session->control_bits))
goto done;
/* send a REPORT_LUNS to LUN 0. If it works, we know the LUNs. */
last_luns = luns;
make_report_luns(sc, luns);
smp_mb();
if (iscsi_do_cmnd(session, c, 6)) {
uint8_t *lun_list = c->buffer + 8;
int luns_listed;
uint32_t length = 0;
/* get the list length the target has */
length = c->buffer[0] << 24;
length |= c->buffer[1] << 16;
length |= c->buffer[2] << 8;
length |= c->buffer[3];
if (length < 8) {
/* odd, assume REPORT_LUNS is broken, fall back to doing INQUIRY */
DEBUG_INIT
("iSCSI: session (bus %d target %d) REPORT_LUNS length 0, falling back to INQUIRY\n",
session->iscsi_bus, session->target_id);
report_luns_failed = 1;
break;
}
/* figure out how many luns we were told about this time */
if ((length / 8U) < luns)
luns_listed = length / 8U;
else
luns_listed = luns;
/* loop until we run out of data, or out of buffer */
for (l = 0; l < luns_listed; l++) {
int address_method = (lun_list[0] & 0xc0) >> 6;
int lun;
if (LOG_ENABLED(ISCSI_LOG_LOGIN)
|| LOG_ENABLED(ISCSI_LOG_INIT))
printk
("iSCSI: session (%u %u %u *) REPORT_LUNS[%d] = %02x %02x %02x %02x %02x %02x %02x %02x\n",
session->host_no,
session->channel,
session->target_id, l, lun_list[0],
lun_list[1], lun_list[2],
lun_list[3], lun_list[4],
lun_list[5], lun_list[6],
lun_list[7]);
switch (address_method) {
case 0x0:{
/* single-level LUN if bus id is 0, else peripheral device addressing */
lun = lun_list[1];
set_bit(lun,
session->luns_detected);
/* This is useful while checking for deleted luns */
set_bit(lun,
session->luns_found);
break;
}
case 0x1:{
/* flat-space addressing */
lun = lun_list[1];
set_bit(lun,
session->luns_detected);
/* This is useful while checking for deleted luns */
set_bit(lun,
session->luns_found);
break;
}
case 0x2:{
/* logical unit addressing method */
lun = lun_list[1] & 0x1F;
set_bit(lun,
session->luns_detected);
/* This is useful while checking for deleted luns */
set_bit(lun,
session->luns_found);
break;
}
case 0x3:{
/* extended logical unit addressing method is too complicated for us to want to deal with */
printk
("iSCSI: session (%u %u %u *) REPORT_LUNS[%d] with extended LU address method 0x%x ignored\n",
session->host_no,
session->channel,
session->target_id, l,
address_method);
break;
}
default:
printk
("iSCSI: session (%u %u %u *) REPORT_LUNS[%d] with unknown address method 0x%x ignored\n",
session->host_no,
session->channel,
session->target_id, l,
address_method);
break;
}
/* next LUN in the list */
lun_list += 8;
}
/* decide how many luns to ask for on the next iteration, if there is one */
luns = length / 8U;
if (luns > ISCSI_MAX_LUN) {
/* we only have buffer space for so many LUNs */
luns = ISCSI_MAX_LUN;
printk
("iSCSI: session (bus %d target %d) REPORT_LUNS length %u (%u entries) truncated to %u (%u entries)\n",
session->iscsi_bus, session->target_id,
length, (length / 8) - 1, (luns + 1) * 8U,
luns);
}
} else {
/* REPORT_LUNS failed, fall back to doing INQUIRY */
DEBUG_INIT
("iSCSI: session (bus %d target %d) REPORT_LUNS failed, falling back to INQUIRY\n",
session->iscsi_bus, session->target_id);
report_luns_failed = 1;
break;
}
} while (luns > last_luns);
if (signal_pending(current)) {
DEBUG_INIT
("iSCSI: session (bus %d target %d) detect LUNs aborted by signal\n",
session->iscsi_bus, session->target_id);
goto done;
}
if (report_luns_failed) {
/* if REPORT_LUNS failed, then either it's a SCSI-2 device
* that doesn't understand the command, or it's a SCSI-3
* device that only has one LUN and decided not to implement
* REPORT_LUNS. In either case, we're safe just probing LUNs
* 0-7 with INQUIRY, since SCSI-2 can't have more than 8 LUNs,
* and SCSI-3 should do REPORT_LUNS if it has more than 1 LUN.
*/
for (l = 0; l < 8; l++) {
sc->lun = l;
sc->request_buffer = c->buffer;
make_inquiry(sc, lun0_scsi_level);
/* we'll make a note of the LUN when the rx thread receives the response.
* No need to do it again here.
*/
if (iscsi_do_cmnd(session, c, 6)) {
/* we do need to record the SCSI level so we can build inquiries properly though */
if (l == 0) {
lun0_scsi_level = c->buffer[2] & 0x07;
if (LOG_ENABLED(ISCSI_LOG_INIT))
printk
("iSCSI: session (%u %u %u %u) is SCSI level %d\n",
sc->host->host_no,
sc->channel, sc->target,
sc->lun, lun0_scsi_level);
}
} else {
/* just assume there's no LUN */
}
if (test_bit
(SESSION_TERMINATING, &session->control_bits))
break;
if (signal_pending(current))
break;
}
}
done:
DEBUG_ALLOC
("iSCSI: session (bus %d target %d) hba %p host %p kfree iscsi cmnd %p, bufflen %u\n",
session->iscsi_bus, session->target_id, session->hba,
session->hba->host, c, c->bufflen);
kfree(c);
}
int
iscsi_reset_lun_probing(void)
{
int ret = 0;
spin_lock(&iscsi_lun_probe_lock);
if ((iscsi_currently_probing == NULL) && (iscsi_lun_probe_head == NULL)) {
/* if we're not currently probing, reset */
DEBUG_INIT
("iSCSI: session (bus %d target %d) reset LUN probing at %lu\n",
session->iscsi_bus, session->target_id, jiffies);
iscsi_next_probe = 0;
iscsi_lun_probe_start = 0;
smp_mb();
ret = 1;
} else {
DEBUG_INIT
("iSCSI: session (bus %d target %d)failed to reset LUN probing at %lu, currently probing %p, queue head %p\n",
session->iscsi_bus, session->target_id, jiffies,
iscsi_currently_probing, iscsi_lun_probe_head);
}
spin_unlock(&iscsi_lun_probe_lock);
return ret;
}
next reply other threads:[~2003-09-23 15:23 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2003-09-23 15:23 Jeff Garzik [this message]
2003-09-23 15:53 ` [iscsi 2/2] iscsi-probe.c Christoph Hellwig
2003-09-24 3:43 ` Lincoln Dale
2003-09-24 12:37 ` Christoph Hellwig
2003-09-24 13:42 ` Sachin Mhatre (smhatre)
2003-09-24 13:48 ` 'Christoph Hellwig'
2003-09-24 14:14 ` Jeff Garzik
2003-09-24 16:20 ` Sachin Mhatre (smhatre)
2003-09-24 16:27 ` Jeff Garzik
2003-09-24 16:40 ` 'Christoph Hellwig'
2003-09-24 17:11 ` Patrick Mansfield
2003-09-24 17:30 ` 'Christoph Hellwig'
2003-09-24 18:26 ` Scott M. Ferris
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20030923152306.GA14522@gtf.org \
--to=jgarzik@pobox.com \
--cc=linux-scsi@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.