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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox