From mboxrd@z Thu Jan 1 00:00:00 1970 From: Jeff Garzik Subject: [iscsi 2/2] iscsi-probe.c Date: Tue, 23 Sep 2003 11:23:06 -0400 Sender: linux-scsi-owner@vger.kernel.org Message-ID: <20030923152306.GA14522@gtf.org> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Return-path: Received: from havoc.gtf.org ([63.247.75.124]:39099 "EHLO havoc.gtf.org") by vger.kernel.org with ESMTP id S261653AbTIWPXH (ORCPT ); Tue, 23 Sep 2003 11:23:07 -0400 Content-Disposition: inline List-Id: linux-scsi@vger.kernel.org To: linux-scsi@vger.kernel.org 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include # include #include #include #include /* these are from $(TOPDIR)/drivers/scsi, not $(TOPDIR)/include */ #include #include #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; }