diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/scsi | |
download | blackbird-op-linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz blackbird-op-linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/scsi')
-rw-r--r-- | drivers/s390/scsi/Makefile | 9 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_aux.c | 1977 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_ccw.c | 312 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_def.h | 1121 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_erp.c | 3585 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_ext.h | 186 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_fsf.c | 5087 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_fsf.h | 472 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_qdio.c | 868 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_scsi.c | 949 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_sysfs_adapter.c | 298 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_sysfs_driver.c | 135 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_sysfs_port.c | 311 | ||||
-rw-r--r-- | drivers/s390/scsi/zfcp_sysfs_unit.c | 179 |
14 files changed, 15489 insertions, 0 deletions
diff --git a/drivers/s390/scsi/Makefile b/drivers/s390/scsi/Makefile new file mode 100644 index 000000000000..fc145307a7d4 --- /dev/null +++ b/drivers/s390/scsi/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the S/390 specific device drivers +# + +zfcp-objs := zfcp_aux.o zfcp_ccw.o zfcp_scsi.o zfcp_erp.o zfcp_qdio.o \ + zfcp_fsf.o zfcp_sysfs_adapter.o zfcp_sysfs_port.o \ + zfcp_sysfs_unit.o zfcp_sysfs_driver.o + +obj-$(CONFIG_ZFCP) += zfcp.o diff --git a/drivers/s390/scsi/zfcp_aux.c b/drivers/s390/scsi/zfcp_aux.c new file mode 100644 index 000000000000..6a43322ccb0a --- /dev/null +++ b/drivers/s390/scsi/zfcp_aux.c @@ -0,0 +1,1977 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_aux.c + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_AUX_REVISION "$Revision: 1.145 $" + +#include "zfcp_ext.h" + +/* accumulated log level (module parameter) */ +static u32 loglevel = ZFCP_LOG_LEVEL_DEFAULTS; +static char *device; +/*********************** FUNCTION PROTOTYPES *********************************/ + +/* written against the module interface */ +static int __init zfcp_module_init(void); + +/* FCP related */ +static void zfcp_ns_gid_pn_handler(unsigned long); + +/* miscellaneous */ +static inline int zfcp_sg_list_alloc(struct zfcp_sg_list *, size_t); +static inline void zfcp_sg_list_free(struct zfcp_sg_list *); +static inline int zfcp_sg_list_copy_from_user(struct zfcp_sg_list *, + void __user *, size_t); +static inline int zfcp_sg_list_copy_to_user(void __user *, + struct zfcp_sg_list *, size_t); + +static int zfcp_cfdc_dev_ioctl(struct inode *, struct file *, + unsigned int, unsigned long); + +#define ZFCP_CFDC_IOC_MAGIC 0xDD +#define ZFCP_CFDC_IOC \ + _IOWR(ZFCP_CFDC_IOC_MAGIC, 0, struct zfcp_cfdc_sense_data) + +#ifdef CONFIG_COMPAT +static struct ioctl_trans zfcp_ioctl_trans = {ZFCP_CFDC_IOC, (void*) sys_ioctl}; +#endif + +static struct file_operations zfcp_cfdc_fops = { + .ioctl = zfcp_cfdc_dev_ioctl +}; + +static struct miscdevice zfcp_cfdc_misc = { + .minor = ZFCP_CFDC_DEV_MINOR, + .name = ZFCP_CFDC_DEV_NAME, + .fops = &zfcp_cfdc_fops +}; + +/*********************** KERNEL/MODULE PARAMETERS ***************************/ + +/* declare driver module init/cleanup functions */ +module_init(zfcp_module_init); + +MODULE_AUTHOR("Heiko Carstens <heiko.carstens@de.ibm.com>, " + "Andreas Herrman <aherrman@de.ibm.com>, " + "Martin Peschke <mpeschke@de.ibm.com>, " + "Raimund Schroeder <raimund.schroeder@de.ibm.com>, " + "Wolfgang Taphorn <taphorn@de.ibm.com>, " + "Aron Zeh <arzeh@de.ibm.com>, " + "IBM Deutschland Entwicklung GmbH"); +MODULE_DESCRIPTION + ("FCP (SCSI over Fibre Channel) HBA driver for IBM eServer zSeries"); +MODULE_LICENSE("GPL"); + +module_param(device, charp, 0); +MODULE_PARM_DESC(device, "specify initial device"); + +module_param(loglevel, uint, 0); +MODULE_PARM_DESC(loglevel, + "log levels, 8 nibbles: " + "FC ERP QDIO CIO Config FSF SCSI Other, " + "levels: 0=none 1=normal 2=devel 3=trace"); + +#ifdef ZFCP_PRINT_FLAGS +u32 flags_dump = 0; +module_param(flags_dump, uint, 0); +#endif + +/****************************************************************/ +/************** Functions without logging ***********************/ +/****************************************************************/ + +void +_zfcp_hex_dump(char *addr, int count) +{ + int i; + for (i = 0; i < count; i++) { + printk("%02x", addr[i]); + if ((i % 4) == 3) + printk(" "); + if ((i % 32) == 31) + printk("\n"); + } + if (((i-1) % 32) != 31) + printk("\n"); +} + +/****************************************************************/ +/************** Uncategorised Functions *************************/ +/****************************************************************/ + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_OTHER + +static inline int +zfcp_fsf_req_is_scsi_cmnd(struct zfcp_fsf_req *fsf_req) +{ + return ((fsf_req->fsf_command == FSF_QTCB_FCP_CMND) && + !(fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT)); +} + +void +zfcp_cmd_dbf_event_fsf(const char *text, struct zfcp_fsf_req *fsf_req, + void *add_data, int add_length) +{ + struct zfcp_adapter *adapter = fsf_req->adapter; + struct scsi_cmnd *scsi_cmnd; + int level = 3; + int i; + unsigned long flags; + + spin_lock_irqsave(&adapter->dbf_lock, flags); + if (zfcp_fsf_req_is_scsi_cmnd(fsf_req)) { + scsi_cmnd = fsf_req->data.send_fcp_command_task.scsi_cmnd; + debug_text_event(adapter->cmd_dbf, level, "fsferror"); + debug_text_event(adapter->cmd_dbf, level, text); + debug_event(adapter->cmd_dbf, level, &fsf_req, + sizeof (unsigned long)); + debug_event(adapter->cmd_dbf, level, &fsf_req->seq_no, + sizeof (u32)); + debug_event(adapter->cmd_dbf, level, &scsi_cmnd, + sizeof (unsigned long)); + debug_event(adapter->cmd_dbf, level, &scsi_cmnd->cmnd, + min(ZFCP_CMD_DBF_LENGTH, (int)scsi_cmnd->cmd_len)); + for (i = 0; i < add_length; i += ZFCP_CMD_DBF_LENGTH) + debug_event(adapter->cmd_dbf, + level, + (char *) add_data + i, + min(ZFCP_CMD_DBF_LENGTH, add_length - i)); + } + spin_unlock_irqrestore(&adapter->dbf_lock, flags); +} + +/* XXX additionally log unit if available */ +/* ---> introduce new parameter for unit, see 2.4 code */ +void +zfcp_cmd_dbf_event_scsi(const char *text, struct scsi_cmnd *scsi_cmnd) +{ + struct zfcp_adapter *adapter; + union zfcp_req_data *req_data; + struct zfcp_fsf_req *fsf_req; + int level = ((host_byte(scsi_cmnd->result) != 0) ? 1 : 5); + unsigned long flags; + + adapter = (struct zfcp_adapter *) scsi_cmnd->device->host->hostdata[0]; + req_data = (union zfcp_req_data *) scsi_cmnd->host_scribble; + fsf_req = (req_data ? req_data->send_fcp_command_task.fsf_req : NULL); + spin_lock_irqsave(&adapter->dbf_lock, flags); + debug_text_event(adapter->cmd_dbf, level, "hostbyte"); + debug_text_event(adapter->cmd_dbf, level, text); + debug_event(adapter->cmd_dbf, level, &scsi_cmnd->result, sizeof (u32)); + debug_event(adapter->cmd_dbf, level, &scsi_cmnd, + sizeof (unsigned long)); + debug_event(adapter->cmd_dbf, level, &scsi_cmnd->cmnd, + min(ZFCP_CMD_DBF_LENGTH, (int)scsi_cmnd->cmd_len)); + if (likely(fsf_req)) { + debug_event(adapter->cmd_dbf, level, &fsf_req, + sizeof (unsigned long)); + debug_event(adapter->cmd_dbf, level, &fsf_req->seq_no, + sizeof (u32)); + } else { + debug_text_event(adapter->cmd_dbf, level, ""); + debug_text_event(adapter->cmd_dbf, level, ""); + } + spin_unlock_irqrestore(&adapter->dbf_lock, flags); +} + +void +zfcp_in_els_dbf_event(struct zfcp_adapter *adapter, const char *text, + struct fsf_status_read_buffer *status_buffer, int length) +{ + int level = 1; + int i; + + debug_text_event(adapter->in_els_dbf, level, text); + debug_event(adapter->in_els_dbf, level, &status_buffer->d_id, 8); + for (i = 0; i < length; i += ZFCP_IN_ELS_DBF_LENGTH) + debug_event(adapter->in_els_dbf, + level, + (char *) status_buffer->payload + i, + min(ZFCP_IN_ELS_DBF_LENGTH, length - i)); +} + +/** + * zfcp_device_setup - setup function + * @str: pointer to parameter string + * + * Parse "device=..." parameter string. + */ +static int __init +zfcp_device_setup(char *str) +{ + char *tmp; + + if (!str) + return 0; + + tmp = strchr(str, ','); + if (!tmp) + goto err_out; + *tmp++ = '\0'; + strncpy(zfcp_data.init_busid, str, BUS_ID_SIZE); + zfcp_data.init_busid[BUS_ID_SIZE-1] = '\0'; + + zfcp_data.init_wwpn = simple_strtoull(tmp, &tmp, 0); + if (*tmp++ != ',') + goto err_out; + if (*tmp == '\0') + goto err_out; + + zfcp_data.init_fcp_lun = simple_strtoull(tmp, &tmp, 0); + if (*tmp != '\0') + goto err_out; + return 1; + + err_out: + ZFCP_LOG_NORMAL("Parse error for device parameter string %s\n", str); + return 0; +} + +static void __init +zfcp_init_device_configure(void) +{ + struct zfcp_adapter *adapter; + struct zfcp_port *port; + struct zfcp_unit *unit; + + down(&zfcp_data.config_sema); + read_lock_irq(&zfcp_data.config_lock); + adapter = zfcp_get_adapter_by_busid(zfcp_data.init_busid); + if (adapter) + zfcp_adapter_get(adapter); + read_unlock_irq(&zfcp_data.config_lock); + + if (adapter == NULL) + goto out_adapter; + port = zfcp_port_enqueue(adapter, zfcp_data.init_wwpn, 0, 0); + if (!port) + goto out_port; + unit = zfcp_unit_enqueue(port, zfcp_data.init_fcp_lun); + if (!unit) + goto out_unit; + up(&zfcp_data.config_sema); + ccw_device_set_online(adapter->ccw_device); + zfcp_erp_wait(adapter); + down(&zfcp_data.config_sema); + zfcp_unit_put(unit); + out_unit: + zfcp_port_put(port); + out_port: + zfcp_adapter_put(adapter); + out_adapter: + up(&zfcp_data.config_sema); + return; +} + +static int __init +zfcp_module_init(void) +{ + + int retval = 0; + + atomic_set(&zfcp_data.loglevel, loglevel); + + /* initialize adapter list */ + INIT_LIST_HEAD(&zfcp_data.adapter_list_head); + + /* initialize adapters to be removed list head */ + INIT_LIST_HEAD(&zfcp_data.adapter_remove_lh); + + zfcp_transport_template = fc_attach_transport(&zfcp_transport_functions); + if (!zfcp_transport_template) + return -ENODEV; + + retval = register_ioctl32_conversion(zfcp_ioctl_trans.cmd, + zfcp_ioctl_trans.handler); + if (retval != 0) { + ZFCP_LOG_INFO("registration of ioctl32 conversion failed\n"); + goto out; + } + + retval = misc_register(&zfcp_cfdc_misc); + if (retval != 0) { + ZFCP_LOG_INFO("registration of misc device " + "zfcp_cfdc failed\n"); + goto out_misc_register; + } else { + ZFCP_LOG_TRACE("major/minor for zfcp_cfdc: %d/%d\n", + ZFCP_CFDC_DEV_MAJOR, zfcp_cfdc_misc.minor); + } + + /* Initialise proc semaphores */ + sema_init(&zfcp_data.config_sema, 1); + + /* initialise configuration rw lock */ + rwlock_init(&zfcp_data.config_lock); + + /* save address of data structure managing the driver module */ + zfcp_data.scsi_host_template.module = THIS_MODULE; + + /* setup dynamic I/O */ + retval = zfcp_ccw_register(); + if (retval) { + ZFCP_LOG_NORMAL("registration with common I/O layer failed\n"); + goto out_ccw_register; + } + + if (zfcp_device_setup(device)) + zfcp_init_device_configure(); + + goto out; + + out_ccw_register: + misc_deregister(&zfcp_cfdc_misc); + out_misc_register: + unregister_ioctl32_conversion(zfcp_ioctl_trans.cmd); + out: + return retval; +} + +/* + * function: zfcp_cfdc_dev_ioctl + * + * purpose: Handle control file upload/download transaction via IOCTL + * interface + * + * returns: 0 - Operation completed successfuly + * -ENOTTY - Unknown IOCTL command + * -EINVAL - Invalid sense data record + * -ENXIO - The FCP adapter is not available + * -EOPNOTSUPP - The FCP adapter does not have CFDC support + * -ENOMEM - Insufficient memory + * -EFAULT - User space memory I/O operation fault + * -EPERM - Cannot create or queue FSF request or create SBALs + * -ERESTARTSYS- Received signal (is mapped to EAGAIN by VFS) + */ +static int +zfcp_cfdc_dev_ioctl(struct inode *inode, struct file *file, + unsigned int command, unsigned long buffer) +{ + struct zfcp_cfdc_sense_data *sense_data, __user *sense_data_user; + struct zfcp_adapter *adapter = NULL; + struct zfcp_fsf_req *fsf_req = NULL; + struct zfcp_sg_list *sg_list = NULL; + u32 fsf_command, option; + char *bus_id = NULL; + int retval = 0; + + sense_data = kmalloc(sizeof(struct zfcp_cfdc_sense_data), GFP_KERNEL); + if (sense_data == NULL) { + retval = -ENOMEM; + goto out; + } + + sg_list = kmalloc(sizeof(struct zfcp_sg_list), GFP_KERNEL); + if (sg_list == NULL) { + retval = -ENOMEM; + goto out; + } + memset(sg_list, 0, sizeof(*sg_list)); + + if (command != ZFCP_CFDC_IOC) { + ZFCP_LOG_INFO("IOC request code 0x%x invalid\n", command); + retval = -ENOTTY; + goto out; + } + + if ((sense_data_user = (void __user *) buffer) == NULL) { + ZFCP_LOG_INFO("sense data record is required\n"); + retval = -EINVAL; + goto out; + } + + retval = copy_from_user(sense_data, sense_data_user, + sizeof(struct zfcp_cfdc_sense_data)); + if (retval) { + retval = -EFAULT; + goto out; + } + + if (sense_data->signature != ZFCP_CFDC_SIGNATURE) { + ZFCP_LOG_INFO("invalid sense data request signature 0x%08x\n", + ZFCP_CFDC_SIGNATURE); + retval = -EINVAL; + goto out; + } + + switch (sense_data->command) { + + case ZFCP_CFDC_CMND_DOWNLOAD_NORMAL: + fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE; + option = FSF_CFDC_OPTION_NORMAL_MODE; + break; + + case ZFCP_CFDC_CMND_DOWNLOAD_FORCE: + fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE; + option = FSF_CFDC_OPTION_FORCE; + break; + + case ZFCP_CFDC_CMND_FULL_ACCESS: + fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE; + option = FSF_CFDC_OPTION_FULL_ACCESS; + break; + + case ZFCP_CFDC_CMND_RESTRICTED_ACCESS: + fsf_command = FSF_QTCB_DOWNLOAD_CONTROL_FILE; + option = FSF_CFDC_OPTION_RESTRICTED_ACCESS; + break; + + case ZFCP_CFDC_CMND_UPLOAD: + fsf_command = FSF_QTCB_UPLOAD_CONTROL_FILE; + option = 0; + break; + + default: + ZFCP_LOG_INFO("invalid command code 0x%08x\n", + sense_data->command); + retval = -EINVAL; + goto out; + } + + bus_id = kmalloc(BUS_ID_SIZE, GFP_KERNEL); + if (bus_id == NULL) { + retval = -ENOMEM; + goto out; + } + snprintf(bus_id, BUS_ID_SIZE, "%d.%d.%04x", + (sense_data->devno >> 24), + (sense_data->devno >> 16) & 0xFF, + (sense_data->devno & 0xFFFF)); + + read_lock_irq(&zfcp_data.config_lock); + adapter = zfcp_get_adapter_by_busid(bus_id); + if (adapter) + zfcp_adapter_get(adapter); + read_unlock_irq(&zfcp_data.config_lock); + + kfree(bus_id); + + if (adapter == NULL) { + ZFCP_LOG_INFO("invalid adapter\n"); + retval = -ENXIO; + goto out; + } + + if (sense_data->command & ZFCP_CFDC_WITH_CONTROL_FILE) { + retval = zfcp_sg_list_alloc(sg_list, + ZFCP_CFDC_MAX_CONTROL_FILE_SIZE); + if (retval) { + retval = -ENOMEM; + goto out; + } + } + + if ((sense_data->command & ZFCP_CFDC_DOWNLOAD) && + (sense_data->command & ZFCP_CFDC_WITH_CONTROL_FILE)) { + retval = zfcp_sg_list_copy_from_user( + sg_list, &sense_data_user->control_file, + ZFCP_CFDC_MAX_CONTROL_FILE_SIZE); + if (retval) { + retval = -EFAULT; + goto out; + } + } + + retval = zfcp_fsf_control_file(adapter, &fsf_req, fsf_command, + option, sg_list); + if (retval) + goto out; + + if ((fsf_req->qtcb->prefix.prot_status != FSF_PROT_GOOD) && + (fsf_req->qtcb->prefix.prot_status != FSF_PROT_FSF_STATUS_PRESENTED)) { + retval = -ENXIO; + goto out; + } + + sense_data->fsf_status = fsf_req->qtcb->header.fsf_status; + memcpy(&sense_data->fsf_status_qual, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof(union fsf_status_qual)); + memcpy(&sense_data->payloads, &fsf_req->qtcb->bottom.support.els, 256); + + retval = copy_to_user(sense_data_user, sense_data, + sizeof(struct zfcp_cfdc_sense_data)); + if (retval) { + retval = -EFAULT; + goto out; + } + + if (sense_data->command & ZFCP_CFDC_UPLOAD) { + retval = zfcp_sg_list_copy_to_user( + &sense_data_user->control_file, sg_list, + ZFCP_CFDC_MAX_CONTROL_FILE_SIZE); + if (retval) { + retval = -EFAULT; + goto out; + } + } + + out: + if (fsf_req != NULL) + zfcp_fsf_req_cleanup(fsf_req); + + if ((adapter != NULL) && (retval != -ENXIO)) + zfcp_adapter_put(adapter); + + if (sg_list != NULL) { + zfcp_sg_list_free(sg_list); + kfree(sg_list); + } + + if (sense_data != NULL) + kfree(sense_data); + + return retval; +} + + +/** + * zfcp_sg_list_alloc - create a scatter-gather list of the specified size + * @sg_list: structure describing a scatter gather list + * @size: size of scatter-gather list + * Return: 0 on success, else -ENOMEM + * + * In sg_list->sg a pointer to the created scatter-gather list is returned, + * or NULL if we run out of memory. sg_list->count specifies the number of + * elements of the scatter-gather list. The maximum size of a single element + * in the scatter-gather list is PAGE_SIZE. + */ +static inline int +zfcp_sg_list_alloc(struct zfcp_sg_list *sg_list, size_t size) +{ + struct scatterlist *sg; + unsigned int i; + int retval = 0; + void *address; + + BUG_ON(sg_list == NULL); + + sg_list->count = size >> PAGE_SHIFT; + if (size & ~PAGE_MASK) + sg_list->count++; + sg_list->sg = kmalloc(sg_list->count * sizeof(struct scatterlist), + GFP_KERNEL); + if (sg_list->sg == NULL) { + sg_list->count = 0; + retval = -ENOMEM; + goto out; + } + memset(sg_list->sg, 0, sg_list->count * sizeof(struct scatterlist)); + + for (i = 0, sg = sg_list->sg; i < sg_list->count; i++, sg++) { + sg->length = min(size, PAGE_SIZE); + sg->offset = 0; + address = (void *) get_zeroed_page(GFP_KERNEL); + if (address == NULL) { + sg_list->count = i; + zfcp_sg_list_free(sg_list); + retval = -ENOMEM; + goto out; + } + zfcp_address_to_sg(address, sg); + size -= sg->length; + } + + out: + return retval; +} + + +/** + * zfcp_sg_list_free - free memory of a scatter-gather list + * @sg_list: structure describing a scatter-gather list + * + * Memory for each element in the scatter-gather list is freed. + * Finally sg_list->sg is freed itself and sg_list->count is reset. + */ +static inline void +zfcp_sg_list_free(struct zfcp_sg_list *sg_list) +{ + struct scatterlist *sg; + unsigned int i; + + BUG_ON(sg_list == NULL); + + for (i = 0, sg = sg_list->sg; i < sg_list->count; i++, sg++) + free_page((unsigned long) zfcp_sg_to_address(sg)); + + sg_list->count = 0; + kfree(sg_list->sg); +} + +/** + * zfcp_sg_size - determine size of a scatter-gather list + * @sg: array of (struct scatterlist) + * @sg_count: elements in array + * Return: size of entire scatter-gather list + */ +size_t +zfcp_sg_size(struct scatterlist *sg, unsigned int sg_count) +{ + unsigned int i; + struct scatterlist *p; + size_t size; + + size = 0; + for (i = 0, p = sg; i < sg_count; i++, p++) { + BUG_ON(p == NULL); + size += p->length; + } + + return size; +} + + +/** + * zfcp_sg_list_copy_from_user -copy data from user space to scatter-gather list + * @sg_list: structure describing a scatter-gather list + * @user_buffer: pointer to buffer in user space + * @size: number of bytes to be copied + * Return: 0 on success, -EFAULT if copy_from_user fails. + */ +static inline int +zfcp_sg_list_copy_from_user(struct zfcp_sg_list *sg_list, + void __user *user_buffer, + size_t size) +{ + struct scatterlist *sg; + unsigned int length; + void *zfcp_buffer; + int retval = 0; + + BUG_ON(sg_list == NULL); + + if (zfcp_sg_size(sg_list->sg, sg_list->count) < size) + return -EFAULT; + + for (sg = sg_list->sg; size > 0; sg++) { + length = min((unsigned int)size, sg->length); + zfcp_buffer = zfcp_sg_to_address(sg); + if (copy_from_user(zfcp_buffer, user_buffer, length)) { + retval = -EFAULT; + goto out; + } + user_buffer += length; + size -= length; + } + + out: + return retval; +} + + +/** + * zfcp_sg_list_copy_to_user - copy data from scatter-gather list to user space + * @user_buffer: pointer to buffer in user space + * @sg_list: structure describing a scatter-gather list + * @size: number of bytes to be copied + * Return: 0 on success, -EFAULT if copy_to_user fails + */ +static inline int +zfcp_sg_list_copy_to_user(void __user *user_buffer, + struct zfcp_sg_list *sg_list, + size_t size) +{ + struct scatterlist *sg; + unsigned int length; + void *zfcp_buffer; + int retval = 0; + + BUG_ON(sg_list == NULL); + + if (zfcp_sg_size(sg_list->sg, sg_list->count) < size) + return -EFAULT; + + for (sg = sg_list->sg; size > 0; sg++) { + length = min((unsigned int) size, sg->length); + zfcp_buffer = zfcp_sg_to_address(sg); + if (copy_to_user(user_buffer, zfcp_buffer, length)) { + retval = -EFAULT; + goto out; + } + user_buffer += length; + size -= length; + } + + out: + return retval; +} + + +#undef ZFCP_LOG_AREA + +/****************************************************************/ +/****** Functions for configuration/set-up of structures ********/ +/****************************************************************/ + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_CONFIG + +/** + * zfcp_get_unit_by_lun - find unit in unit list of port by FCP LUN + * @port: pointer to port to search for unit + * @fcp_lun: FCP LUN to search for + * Traverse list of all units of a port and return pointer to a unit + * with the given FCP LUN. + */ +struct zfcp_unit * +zfcp_get_unit_by_lun(struct zfcp_port *port, fcp_lun_t fcp_lun) +{ + struct zfcp_unit *unit; + int found = 0; + + list_for_each_entry(unit, &port->unit_list_head, list) { + if ((unit->fcp_lun == fcp_lun) && + !atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status)) + { + found = 1; + break; + } + } + return found ? unit : NULL; +} + +/** + * zfcp_get_port_by_wwpn - find port in port list of adapter by wwpn + * @adapter: pointer to adapter to search for port + * @wwpn: wwpn to search for + * Traverse list of all ports of an adapter and return pointer to a port + * with the given wwpn. + */ +struct zfcp_port * +zfcp_get_port_by_wwpn(struct zfcp_adapter *adapter, wwn_t wwpn) +{ + struct zfcp_port *port; + int found = 0; + + list_for_each_entry(port, &adapter->port_list_head, list) { + if ((port->wwpn == wwpn) && + !(atomic_read(&port->status) & + (ZFCP_STATUS_PORT_NO_WWPN | ZFCP_STATUS_COMMON_REMOVE))) { + found = 1; + break; + } + } + return found ? port : NULL; +} + +/** + * zfcp_get_port_by_did - find port in port list of adapter by d_id + * @adapter: pointer to adapter to search for port + * @d_id: d_id to search for + * Traverse list of all ports of an adapter and return pointer to a port + * with the given d_id. + */ +struct zfcp_port * +zfcp_get_port_by_did(struct zfcp_adapter *adapter, u32 d_id) +{ + struct zfcp_port *port; + int found = 0; + + list_for_each_entry(port, &adapter->port_list_head, list) { + if ((port->d_id == d_id) && + !atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) + { + found = 1; + break; + } + } + return found ? port : NULL; +} + +/** + * zfcp_get_adapter_by_busid - find adpater in adapter list by bus_id + * @bus_id: bus_id to search for + * Traverse list of all adapters and return pointer to an adapter + * with the given bus_id. + */ +struct zfcp_adapter * +zfcp_get_adapter_by_busid(char *bus_id) +{ + struct zfcp_adapter *adapter; + int found = 0; + + list_for_each_entry(adapter, &zfcp_data.adapter_list_head, list) { + if ((strncmp(bus_id, zfcp_get_busid_by_adapter(adapter), + BUS_ID_SIZE) == 0) && + !atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, + &adapter->status)){ + found = 1; + break; + } + } + return found ? adapter : NULL; +} + +/** + * zfcp_unit_enqueue - enqueue unit to unit list of a port. + * @port: pointer to port where unit is added + * @fcp_lun: FCP LUN of unit to be enqueued + * Return: pointer to enqueued unit on success, NULL on error + * Locks: config_sema must be held to serialize changes to the unit list + * + * Sets up some unit internal structures and creates sysfs entry. + */ +struct zfcp_unit * +zfcp_unit_enqueue(struct zfcp_port *port, fcp_lun_t fcp_lun) +{ + struct zfcp_unit *unit, *tmp_unit; + scsi_lun_t scsi_lun; + int found; + + /* + * check that there is no unit with this FCP_LUN already in list + * and enqueue it. + * Note: Unlike for the adapter and the port, this is an error + */ + read_lock_irq(&zfcp_data.config_lock); + unit = zfcp_get_unit_by_lun(port, fcp_lun); + read_unlock_irq(&zfcp_data.config_lock); + if (unit) + return NULL; + + unit = kmalloc(sizeof (struct zfcp_unit), GFP_KERNEL); + if (!unit) + return NULL; + memset(unit, 0, sizeof (struct zfcp_unit)); + + /* initialise reference count stuff */ + atomic_set(&unit->refcount, 0); + init_waitqueue_head(&unit->remove_wq); + + unit->port = port; + unit->fcp_lun = fcp_lun; + + /* setup for sysfs registration */ + snprintf(unit->sysfs_device.bus_id, BUS_ID_SIZE, "0x%016llx", fcp_lun); + unit->sysfs_device.parent = &port->sysfs_device; + unit->sysfs_device.release = zfcp_sysfs_unit_release; + dev_set_drvdata(&unit->sysfs_device, unit); + + /* mark unit unusable as long as sysfs registration is not complete */ + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status); + + if (device_register(&unit->sysfs_device)) { + kfree(unit); + return NULL; + } + + if (zfcp_sysfs_unit_create_files(&unit->sysfs_device)) { + device_unregister(&unit->sysfs_device); + return NULL; + } + + zfcp_unit_get(unit); + + scsi_lun = 0; + found = 0; + write_lock_irq(&zfcp_data.config_lock); + list_for_each_entry(tmp_unit, &port->unit_list_head, list) { + if (tmp_unit->scsi_lun != scsi_lun) { + found = 1; + break; + } + scsi_lun++; + } + unit->scsi_lun = scsi_lun; + if (found) + list_add_tail(&unit->list, &tmp_unit->list); + else + list_add_tail(&unit->list, &port->unit_list_head); + atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status); + atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &unit->status); + write_unlock_irq(&zfcp_data.config_lock); + + port->units++; + zfcp_port_get(port); + + return unit; +} + +void +zfcp_unit_dequeue(struct zfcp_unit *unit) +{ + zfcp_unit_wait(unit); + write_lock_irq(&zfcp_data.config_lock); + list_del(&unit->list); + write_unlock_irq(&zfcp_data.config_lock); + unit->port->units--; + zfcp_port_put(unit->port); + zfcp_sysfs_unit_remove_files(&unit->sysfs_device); + device_unregister(&unit->sysfs_device); +} + +static void * +zfcp_mempool_alloc(unsigned int __nocast gfp_mask, void *size) +{ + return kmalloc((size_t) size, gfp_mask); +} + +static void +zfcp_mempool_free(void *element, void *size) +{ + kfree(element); +} + +/* + * Allocates a combined QTCB/fsf_req buffer for erp actions and fcp/SCSI + * commands. + * It also genrates fcp-nameserver request/response buffer and unsolicited + * status read fsf_req buffers. + * + * locks: must only be called with zfcp_data.config_sema taken + */ +static int +zfcp_allocate_low_mem_buffers(struct zfcp_adapter *adapter) +{ + adapter->pool.fsf_req_erp = + mempool_create(ZFCP_POOL_FSF_REQ_ERP_NR, + zfcp_mempool_alloc, zfcp_mempool_free, (void *) + sizeof(struct zfcp_fsf_req_pool_element)); + + if (NULL == adapter->pool.fsf_req_erp) + return -ENOMEM; + + adapter->pool.fsf_req_scsi = + mempool_create(ZFCP_POOL_FSF_REQ_SCSI_NR, + zfcp_mempool_alloc, zfcp_mempool_free, (void *) + sizeof(struct zfcp_fsf_req_pool_element)); + + if (NULL == adapter->pool.fsf_req_scsi) + return -ENOMEM; + + adapter->pool.fsf_req_abort = + mempool_create(ZFCP_POOL_FSF_REQ_ABORT_NR, + zfcp_mempool_alloc, zfcp_mempool_free, (void *) + sizeof(struct zfcp_fsf_req_pool_element)); + + if (NULL == adapter->pool.fsf_req_abort) + return -ENOMEM; + + adapter->pool.fsf_req_status_read = + mempool_create(ZFCP_POOL_STATUS_READ_NR, + zfcp_mempool_alloc, zfcp_mempool_free, + (void *) sizeof(struct zfcp_fsf_req)); + + if (NULL == adapter->pool.fsf_req_status_read) + return -ENOMEM; + + adapter->pool.data_status_read = + mempool_create(ZFCP_POOL_STATUS_READ_NR, + zfcp_mempool_alloc, zfcp_mempool_free, + (void *) sizeof(struct fsf_status_read_buffer)); + + if (NULL == adapter->pool.data_status_read) + return -ENOMEM; + + adapter->pool.data_gid_pn = + mempool_create(ZFCP_POOL_DATA_GID_PN_NR, + zfcp_mempool_alloc, zfcp_mempool_free, (void *) + sizeof(struct zfcp_gid_pn_data)); + + if (NULL == adapter->pool.data_gid_pn) + return -ENOMEM; + + return 0; +} + +/** + * zfcp_free_low_mem_buffers - free memory pools of an adapter + * @adapter: pointer to zfcp_adapter for which memory pools should be freed + * locking: zfcp_data.config_sema must be held + */ +static void +zfcp_free_low_mem_buffers(struct zfcp_adapter *adapter) +{ + if (adapter->pool.fsf_req_erp) + mempool_destroy(adapter->pool.fsf_req_erp); + if (adapter->pool.fsf_req_scsi) + mempool_destroy(adapter->pool.fsf_req_scsi); + if (adapter->pool.fsf_req_abort) + mempool_destroy(adapter->pool.fsf_req_abort); + if (adapter->pool.fsf_req_status_read) + mempool_destroy(adapter->pool.fsf_req_status_read); + if (adapter->pool.data_status_read) + mempool_destroy(adapter->pool.data_status_read); + if (adapter->pool.data_gid_pn) + mempool_destroy(adapter->pool.data_gid_pn); +} + +/** + * zfcp_adapter_debug_register - registers debug feature for an adapter + * @adapter: pointer to adapter for which debug features should be registered + * return: -ENOMEM on error, 0 otherwise + */ +int +zfcp_adapter_debug_register(struct zfcp_adapter *adapter) +{ + char dbf_name[20]; + + /* debug feature area which records SCSI command failures (hostbyte) */ + spin_lock_init(&adapter->dbf_lock); + + sprintf(dbf_name, ZFCP_CMD_DBF_NAME "%s", + zfcp_get_busid_by_adapter(adapter)); + adapter->cmd_dbf = debug_register(dbf_name, ZFCP_CMD_DBF_INDEX, + ZFCP_CMD_DBF_AREAS, + ZFCP_CMD_DBF_LENGTH); + debug_register_view(adapter->cmd_dbf, &debug_hex_ascii_view); + debug_set_level(adapter->cmd_dbf, ZFCP_CMD_DBF_LEVEL); + + /* debug feature area which records SCSI command aborts */ + sprintf(dbf_name, ZFCP_ABORT_DBF_NAME "%s", + zfcp_get_busid_by_adapter(adapter)); + adapter->abort_dbf = debug_register(dbf_name, ZFCP_ABORT_DBF_INDEX, + ZFCP_ABORT_DBF_AREAS, + ZFCP_ABORT_DBF_LENGTH); + debug_register_view(adapter->abort_dbf, &debug_hex_ascii_view); + debug_set_level(adapter->abort_dbf, ZFCP_ABORT_DBF_LEVEL); + + /* debug feature area which records incoming ELS commands */ + sprintf(dbf_name, ZFCP_IN_ELS_DBF_NAME "%s", + zfcp_get_busid_by_adapter(adapter)); + adapter->in_els_dbf = debug_register(dbf_name, ZFCP_IN_ELS_DBF_INDEX, + ZFCP_IN_ELS_DBF_AREAS, + ZFCP_IN_ELS_DBF_LENGTH); + debug_register_view(adapter->in_els_dbf, &debug_hex_ascii_view); + debug_set_level(adapter->in_els_dbf, ZFCP_IN_ELS_DBF_LEVEL); + + /* debug feature area which records erp events */ + sprintf(dbf_name, ZFCP_ERP_DBF_NAME "%s", + zfcp_get_busid_by_adapter(adapter)); + adapter->erp_dbf = debug_register(dbf_name, ZFCP_ERP_DBF_INDEX, + ZFCP_ERP_DBF_AREAS, + ZFCP_ERP_DBF_LENGTH); + debug_register_view(adapter->erp_dbf, &debug_hex_ascii_view); + debug_set_level(adapter->erp_dbf, ZFCP_ERP_DBF_LEVEL); + + if (!(adapter->cmd_dbf && adapter->abort_dbf && + adapter->in_els_dbf && adapter->erp_dbf)) { + zfcp_adapter_debug_unregister(adapter); + return -ENOMEM; + } + + return 0; + +} + +/** + * zfcp_adapter_debug_unregister - unregisters debug feature for an adapter + * @adapter: pointer to adapter for which debug features should be unregistered + */ +void +zfcp_adapter_debug_unregister(struct zfcp_adapter *adapter) +{ + debug_unregister(adapter->abort_dbf); + debug_unregister(adapter->cmd_dbf); + debug_unregister(adapter->erp_dbf); + debug_unregister(adapter->in_els_dbf); + adapter->abort_dbf = NULL; + adapter->cmd_dbf = NULL; + adapter->erp_dbf = NULL; + adapter->in_els_dbf = NULL; +} + +void +zfcp_dummy_release(struct device *dev) +{ + return; +} + +/* + * Enqueues an adapter at the end of the adapter list in the driver data. + * All adapter internal structures are set up. + * Proc-fs entries are also created. + * + * returns: 0 if a new adapter was successfully enqueued + * ZFCP_KNOWN if an adapter with this devno was already present + * -ENOMEM if alloc failed + * locks: config_sema must be held to serialise changes to the adapter list + */ +struct zfcp_adapter * +zfcp_adapter_enqueue(struct ccw_device *ccw_device) +{ + int retval = 0; + struct zfcp_adapter *adapter; + + /* + * Note: It is safe to release the list_lock, as any list changes + * are protected by the config_sema, which must be held to get here + */ + + /* try to allocate new adapter data structure (zeroed) */ + adapter = kmalloc(sizeof (struct zfcp_adapter), GFP_KERNEL); + if (!adapter) { + ZFCP_LOG_INFO("error: allocation of base adapter " + "structure failed\n"); + goto out; + } + memset(adapter, 0, sizeof (struct zfcp_adapter)); + + ccw_device->handler = NULL; + + /* save ccw_device pointer */ + adapter->ccw_device = ccw_device; + + retval = zfcp_qdio_allocate_queues(adapter); + if (retval) + goto queues_alloc_failed; + + retval = zfcp_qdio_allocate(adapter); + if (retval) + goto qdio_allocate_failed; + + retval = zfcp_allocate_low_mem_buffers(adapter); + if (retval) { + ZFCP_LOG_INFO("error: pool allocation failed\n"); + goto failed_low_mem_buffers; + } + + /* initialise reference count stuff */ + atomic_set(&adapter->refcount, 0); + init_waitqueue_head(&adapter->remove_wq); + + /* initialise list of ports */ + INIT_LIST_HEAD(&adapter->port_list_head); + + /* initialise list of ports to be removed */ + INIT_LIST_HEAD(&adapter->port_remove_lh); + + /* initialize list of fsf requests */ + rwlock_init(&adapter->fsf_req_list_lock); + INIT_LIST_HEAD(&adapter->fsf_req_list_head); + + /* initialize abort lock */ + rwlock_init(&adapter->abort_lock); + + /* initialise some erp stuff */ + init_waitqueue_head(&adapter->erp_thread_wqh); + init_waitqueue_head(&adapter->erp_done_wqh); + + /* initialize lock of associated request queue */ + rwlock_init(&adapter->request_queue.queue_lock); + + /* intitialise SCSI ER timer */ + init_timer(&adapter->scsi_er_timer); + + /* set FC service class used per default */ + adapter->fc_service_class = ZFCP_FC_SERVICE_CLASS_DEFAULT; + + sprintf(adapter->name, "%s", zfcp_get_busid_by_adapter(adapter)); + ASCEBC(adapter->name, strlen(adapter->name)); + + /* mark adapter unusable as long as sysfs registration is not complete */ + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status); + + adapter->ccw_device = ccw_device; + dev_set_drvdata(&ccw_device->dev, adapter); + + if (zfcp_sysfs_adapter_create_files(&ccw_device->dev)) + goto sysfs_failed; + + adapter->generic_services.parent = &adapter->ccw_device->dev; + adapter->generic_services.release = zfcp_dummy_release; + snprintf(adapter->generic_services.bus_id, BUS_ID_SIZE, + "generic_services"); + + if (device_register(&adapter->generic_services)) + goto generic_services_failed; + + /* put allocated adapter at list tail */ + write_lock_irq(&zfcp_data.config_lock); + atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status); + list_add_tail(&adapter->list, &zfcp_data.adapter_list_head); + write_unlock_irq(&zfcp_data.config_lock); + + zfcp_data.adapters++; + + goto out; + + generic_services_failed: + zfcp_sysfs_adapter_remove_files(&adapter->ccw_device->dev); + sysfs_failed: + dev_set_drvdata(&ccw_device->dev, NULL); + failed_low_mem_buffers: + zfcp_free_low_mem_buffers(adapter); + if (qdio_free(ccw_device) != 0) + ZFCP_LOG_NORMAL("bug: qdio_free for adapter %s failed\n", + zfcp_get_busid_by_adapter(adapter)); + qdio_allocate_failed: + zfcp_qdio_free_queues(adapter); + queues_alloc_failed: + kfree(adapter); + adapter = NULL; + out: + return adapter; +} + +/* + * returns: 0 - struct zfcp_adapter data structure successfully removed + * !0 - struct zfcp_adapter data structure could not be removed + * (e.g. still used) + * locks: adapter list write lock is assumed to be held by caller + * adapter->fsf_req_list_lock is taken and released within this + * function and must not be held on entry + */ +void +zfcp_adapter_dequeue(struct zfcp_adapter *adapter) +{ + int retval = 0; + unsigned long flags; + + device_unregister(&adapter->generic_services); + zfcp_sysfs_adapter_remove_files(&adapter->ccw_device->dev); + dev_set_drvdata(&adapter->ccw_device->dev, NULL); + /* sanity check: no pending FSF requests */ + read_lock_irqsave(&adapter->fsf_req_list_lock, flags); + retval = !list_empty(&adapter->fsf_req_list_head); + read_unlock_irqrestore(&adapter->fsf_req_list_lock, flags); + if (retval) { + ZFCP_LOG_NORMAL("bug: adapter %s (%p) still in use, " + "%i requests outstanding\n", + zfcp_get_busid_by_adapter(adapter), adapter, + atomic_read(&adapter->fsf_reqs_active)); + retval = -EBUSY; + goto out; + } + + /* remove specified adapter data structure from list */ + write_lock_irq(&zfcp_data.config_lock); + list_del(&adapter->list); + write_unlock_irq(&zfcp_data.config_lock); + + /* decrease number of adapters in list */ + zfcp_data.adapters--; + + ZFCP_LOG_TRACE("adapter %s (%p) removed from list, " + "%i adapters still in list\n", + zfcp_get_busid_by_adapter(adapter), + adapter, zfcp_data.adapters); + + retval = qdio_free(adapter->ccw_device); + if (retval) + ZFCP_LOG_NORMAL("bug: qdio_free for adapter %s failed\n", + zfcp_get_busid_by_adapter(adapter)); + + zfcp_free_low_mem_buffers(adapter); + /* free memory of adapter data structure and queues */ + zfcp_qdio_free_queues(adapter); + ZFCP_LOG_TRACE("freeing adapter structure\n"); + kfree(adapter); + out: + return; +} + +/** + * zfcp_port_enqueue - enqueue port to port list of adapter + * @adapter: adapter where remote port is added + * @wwpn: WWPN of the remote port to be enqueued + * @status: initial status for the port + * @d_id: destination id of the remote port to be enqueued + * Return: pointer to enqueued port on success, NULL on error + * Locks: config_sema must be held to serialize changes to the port list + * + * All port internal structures are set up and the sysfs entry is generated. + * d_id is used to enqueue ports with a well known address like the Directory + * Service for nameserver lookup. + */ +struct zfcp_port * +zfcp_port_enqueue(struct zfcp_adapter *adapter, wwn_t wwpn, u32 status, + u32 d_id) +{ + struct zfcp_port *port, *tmp_port; + int check_wwpn; + scsi_id_t scsi_id; + int found; + + check_wwpn = !(status & ZFCP_STATUS_PORT_NO_WWPN); + + /* + * check that there is no port with this WWPN already in list + */ + if (check_wwpn) { + read_lock_irq(&zfcp_data.config_lock); + port = zfcp_get_port_by_wwpn(adapter, wwpn); + read_unlock_irq(&zfcp_data.config_lock); + if (port) + return NULL; + } + + port = kmalloc(sizeof (struct zfcp_port), GFP_KERNEL); + if (!port) + return NULL; + memset(port, 0, sizeof (struct zfcp_port)); + + /* initialise reference count stuff */ + atomic_set(&port->refcount, 0); + init_waitqueue_head(&port->remove_wq); + + INIT_LIST_HEAD(&port->unit_list_head); + INIT_LIST_HEAD(&port->unit_remove_lh); + + port->adapter = adapter; + + if (check_wwpn) + port->wwpn = wwpn; + + atomic_set_mask(status, &port->status); + + /* setup for sysfs registration */ + if (status & ZFCP_STATUS_PORT_WKA) { + switch (d_id) { + case ZFCP_DID_DIRECTORY_SERVICE: + snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE, + "directory"); + break; + case ZFCP_DID_MANAGEMENT_SERVICE: + snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE, + "management"); + break; + case ZFCP_DID_KEY_DISTRIBUTION_SERVICE: + snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE, + "key_distribution"); + break; + case ZFCP_DID_ALIAS_SERVICE: + snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE, + "alias"); + break; + case ZFCP_DID_TIME_SERVICE: + snprintf(port->sysfs_device.bus_id, BUS_ID_SIZE, + "time"); + break; + default: + kfree(port); + return NULL; + } + port->d_id = d_id; + port->sysfs_device.parent = &adapter->generic_services; + } else { + snprintf(port->sysfs_device.bus_id, + BUS_ID_SIZE, "0x%016llx", wwpn); + port->sysfs_device.parent = &adapter->ccw_device->dev; + } + port->sysfs_device.release = zfcp_sysfs_port_release; + dev_set_drvdata(&port->sysfs_device, port); + + /* mark port unusable as long as sysfs registration is not complete */ + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status); + + if (device_register(&port->sysfs_device)) { + kfree(port); + return NULL; + } + + if (zfcp_sysfs_port_create_files(&port->sysfs_device, status)) { + device_unregister(&port->sysfs_device); + return NULL; + } + + zfcp_port_get(port); + + scsi_id = 1; + found = 0; + write_lock_irq(&zfcp_data.config_lock); + list_for_each_entry(tmp_port, &adapter->port_list_head, list) { + if (atomic_test_mask(ZFCP_STATUS_PORT_NO_SCSI_ID, + &tmp_port->status)) + continue; + if (tmp_port->scsi_id != scsi_id) { + found = 1; + break; + } + scsi_id++; + } + port->scsi_id = scsi_id; + if (found) + list_add_tail(&port->list, &tmp_port->list); + else + list_add_tail(&port->list, &adapter->port_list_head); + atomic_clear_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status); + atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, &port->status); + if (d_id == ZFCP_DID_DIRECTORY_SERVICE) + if (!adapter->nameserver_port) + adapter->nameserver_port = port; + adapter->ports++; + write_unlock_irq(&zfcp_data.config_lock); + + zfcp_adapter_get(adapter); + + return port; +} + +void +zfcp_port_dequeue(struct zfcp_port *port) +{ + zfcp_port_wait(port); + write_lock_irq(&zfcp_data.config_lock); + list_del(&port->list); + port->adapter->ports--; + write_unlock_irq(&zfcp_data.config_lock); + zfcp_adapter_put(port->adapter); + zfcp_sysfs_port_remove_files(&port->sysfs_device, + atomic_read(&port->status)); + device_unregister(&port->sysfs_device); +} + +/* Enqueues a nameserver port */ +int +zfcp_nameserver_enqueue(struct zfcp_adapter *adapter) +{ + struct zfcp_port *port; + + port = zfcp_port_enqueue(adapter, 0, ZFCP_STATUS_PORT_WKA, + ZFCP_DID_DIRECTORY_SERVICE); + if (!port) { + ZFCP_LOG_INFO("error: enqueue of nameserver port for " + "adapter %s failed\n", + zfcp_get_busid_by_adapter(adapter)); + return -ENXIO; + } + zfcp_port_put(port); + + return 0; +} + +#undef ZFCP_LOG_AREA + +/****************************************************************/ +/******* Fibre Channel Standard related Functions **************/ +/****************************************************************/ + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_FC + +void +zfcp_fsf_incoming_els_rscn(struct zfcp_adapter *adapter, + struct fsf_status_read_buffer *status_buffer) +{ + struct fcp_rscn_head *fcp_rscn_head; + struct fcp_rscn_element *fcp_rscn_element; + struct zfcp_port *port; + u16 i; + u16 no_entries; + u32 range_mask; + unsigned long flags; + + fcp_rscn_head = (struct fcp_rscn_head *) status_buffer->payload; + fcp_rscn_element = (struct fcp_rscn_element *) status_buffer->payload; + + /* see FC-FS */ + no_entries = (fcp_rscn_head->payload_len / 4); + + zfcp_in_els_dbf_event(adapter, "##rscn", status_buffer, + fcp_rscn_head->payload_len); + + debug_text_event(adapter->erp_dbf, 1, "unsol_els_rscn:"); + for (i = 1; i < no_entries; i++) { + /* skip head and start with 1st element */ + fcp_rscn_element++; + switch (fcp_rscn_element->addr_format) { + case ZFCP_PORT_ADDRESS: + ZFCP_LOG_FLAGS(1, "ZFCP_PORT_ADDRESS\n"); + range_mask = ZFCP_PORTS_RANGE_PORT; + break; + case ZFCP_AREA_ADDRESS: + ZFCP_LOG_FLAGS(1, "ZFCP_AREA_ADDRESS\n"); + range_mask = ZFCP_PORTS_RANGE_AREA; + break; + case ZFCP_DOMAIN_ADDRESS: + ZFCP_LOG_FLAGS(1, "ZFCP_DOMAIN_ADDRESS\n"); + range_mask = ZFCP_PORTS_RANGE_DOMAIN; + break; + case ZFCP_FABRIC_ADDRESS: + ZFCP_LOG_FLAGS(1, "ZFCP_FABRIC_ADDRESS\n"); + range_mask = ZFCP_PORTS_RANGE_FABRIC; + break; + default: + ZFCP_LOG_INFO("incoming RSCN with unknown " + "address format\n"); + continue; + } + read_lock_irqsave(&zfcp_data.config_lock, flags); + list_for_each_entry(port, &adapter->port_list_head, list) { + if (atomic_test_mask + (ZFCP_STATUS_PORT_WKA, &port->status)) + continue; + /* Do we know this port? If not skip it. */ + if (!atomic_test_mask + (ZFCP_STATUS_PORT_DID_DID, &port->status)) { + ZFCP_LOG_INFO("incoming RSCN, trying to open " + "port 0x%016Lx\n", port->wwpn); + debug_text_event(adapter->erp_dbf, 1, + "unsol_els_rscnu:"); + zfcp_erp_port_reopen(port, + ZFCP_STATUS_COMMON_ERP_FAILED); + continue; + } + + /* + * FIXME: race: d_id might being invalidated + * (...DID_DID reset) + */ + if ((port->d_id & range_mask) + == (fcp_rscn_element->nport_did & range_mask)) { + ZFCP_LOG_TRACE("reopen did 0x%08x\n", + fcp_rscn_element->nport_did); + /* + * Unfortunately, an RSCN does not specify the + * type of change a target underwent. We assume + * that it makes sense to reopen the link. + * FIXME: Shall we try to find out more about + * the target and link state before closing it? + * How to accomplish this? (nameserver?) + * Where would such code be put in? + * (inside or outside erp) + */ + ZFCP_LOG_INFO("incoming RSCN, trying to open " + "port 0x%016Lx\n", port->wwpn); + debug_text_event(adapter->erp_dbf, 1, + "unsol_els_rscnk:"); + zfcp_test_link(port); + } + } + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + } +} + +static void +zfcp_fsf_incoming_els_plogi(struct zfcp_adapter *adapter, + struct fsf_status_read_buffer *status_buffer) +{ + logi *els_logi = (logi *) status_buffer->payload; + struct zfcp_port *port; + unsigned long flags; + + zfcp_in_els_dbf_event(adapter, "##plogi", status_buffer, 28); + + read_lock_irqsave(&zfcp_data.config_lock, flags); + list_for_each_entry(port, &adapter->port_list_head, list) { + if (port->wwpn == (*(wwn_t *) & els_logi->nport_wwn)) + break; + } + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + if (!port || (port->wwpn != (*(wwn_t *) & els_logi->nport_wwn))) { + ZFCP_LOG_DEBUG("ignored incoming PLOGI for nonexisting port " + "with d_id 0x%08x on adapter %s\n", + status_buffer->d_id, + zfcp_get_busid_by_adapter(adapter)); + } else { + debug_text_event(adapter->erp_dbf, 1, "unsol_els_plogi:"); + debug_event(adapter->erp_dbf, 1, &els_logi->nport_wwn, 8); + zfcp_erp_port_forced_reopen(port, 0); + } +} + +static void +zfcp_fsf_incoming_els_logo(struct zfcp_adapter *adapter, + struct fsf_status_read_buffer *status_buffer) +{ + struct fcp_logo *els_logo = (struct fcp_logo *) status_buffer->payload; + struct zfcp_port *port; + unsigned long flags; + + zfcp_in_els_dbf_event(adapter, "##logo", status_buffer, 16); + + read_lock_irqsave(&zfcp_data.config_lock, flags); + list_for_each_entry(port, &adapter->port_list_head, list) { + if (port->wwpn == els_logo->nport_wwpn) + break; + } + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + if (!port || (port->wwpn != els_logo->nport_wwpn)) { + ZFCP_LOG_DEBUG("ignored incoming LOGO for nonexisting port " + "with d_id 0x%08x on adapter %s\n", + status_buffer->d_id, + zfcp_get_busid_by_adapter(adapter)); + } else { + debug_text_event(adapter->erp_dbf, 1, "unsol_els_logo:"); + debug_event(adapter->erp_dbf, 1, &els_logo->nport_wwpn, 8); + zfcp_erp_port_forced_reopen(port, 0); + } +} + +static void +zfcp_fsf_incoming_els_unknown(struct zfcp_adapter *adapter, + struct fsf_status_read_buffer *status_buffer) +{ + zfcp_in_els_dbf_event(adapter, "##undef", status_buffer, 24); + ZFCP_LOG_NORMAL("warning: unknown incoming ELS 0x%08x " + "for adapter %s\n", *(u32 *) (status_buffer->payload), + zfcp_get_busid_by_adapter(adapter)); + +} + +void +zfcp_fsf_incoming_els(struct zfcp_fsf_req *fsf_req) +{ + struct fsf_status_read_buffer *status_buffer; + u32 els_type; + struct zfcp_adapter *adapter; + + status_buffer = fsf_req->data.status_read.buffer; + els_type = *(u32 *) (status_buffer->payload); + adapter = fsf_req->adapter; + + if (els_type == LS_PLOGI) + zfcp_fsf_incoming_els_plogi(adapter, status_buffer); + else if (els_type == LS_LOGO) + zfcp_fsf_incoming_els_logo(adapter, status_buffer); + else if ((els_type & 0xffff0000) == LS_RSCN) + /* we are only concerned with the command, not the length */ + zfcp_fsf_incoming_els_rscn(adapter, status_buffer); + else + zfcp_fsf_incoming_els_unknown(adapter, status_buffer); +} + + +/** + * zfcp_gid_pn_buffers_alloc - allocate buffers for GID_PN nameserver request + * @gid_pn: pointer to return pointer to struct zfcp_gid_pn_data + * @pool: pointer to mempool_t if non-null memory pool is used for allocation + */ +static int +zfcp_gid_pn_buffers_alloc(struct zfcp_gid_pn_data **gid_pn, mempool_t *pool) +{ + struct zfcp_gid_pn_data *data; + + if (pool != NULL) { + data = mempool_alloc(pool, GFP_ATOMIC); + if (likely(data != NULL)) { + data->ct.pool = pool; + } + } else { + data = kmalloc(sizeof(struct zfcp_gid_pn_data), GFP_ATOMIC); + } + + if (NULL == data) + return -ENOMEM; + + memset(data, 0, sizeof(*data)); + data->ct.req = &data->req; + data->ct.resp = &data->resp; + data->ct.req_count = data->ct.resp_count = 1; + zfcp_address_to_sg(&data->ct_iu_req, &data->req); + zfcp_address_to_sg(&data->ct_iu_resp, &data->resp); + data->req.length = sizeof(struct ct_iu_gid_pn_req); + data->resp.length = sizeof(struct ct_iu_gid_pn_resp); + + *gid_pn = data; + return 0; +} + +/** + * zfcp_gid_pn_buffers_free - free buffers for GID_PN nameserver request + * @gid_pn: pointer to struct zfcp_gid_pn_data which has to be freed + */ +static void +zfcp_gid_pn_buffers_free(struct zfcp_gid_pn_data *gid_pn) +{ + if ((gid_pn->ct.pool != 0)) + mempool_free(gid_pn, gid_pn->ct.pool); + else + kfree(gid_pn); + + return; +} + +/** + * zfcp_ns_gid_pn_request - initiate GID_PN nameserver request + * @erp_action: pointer to zfcp_erp_action where GID_PN request is needed + */ +int +zfcp_ns_gid_pn_request(struct zfcp_erp_action *erp_action) +{ + int ret; + struct ct_iu_gid_pn_req *ct_iu_req; + struct zfcp_gid_pn_data *gid_pn; + struct zfcp_adapter *adapter = erp_action->adapter; + + ret = zfcp_gid_pn_buffers_alloc(&gid_pn, adapter->pool.data_gid_pn); + if (ret < 0) { + ZFCP_LOG_INFO("error: buffer allocation for gid_pn nameserver " + "request failed for adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + goto out; + } + + /* setup nameserver request */ + ct_iu_req = zfcp_sg_to_address(gid_pn->ct.req); + ct_iu_req->header.revision = ZFCP_CT_REVISION; + ct_iu_req->header.gs_type = ZFCP_CT_DIRECTORY_SERVICE; + ct_iu_req->header.gs_subtype = ZFCP_CT_NAME_SERVER; + ct_iu_req->header.options = ZFCP_CT_SYNCHRONOUS; + ct_iu_req->header.cmd_rsp_code = ZFCP_CT_GID_PN; + ct_iu_req->header.max_res_size = ZFCP_CT_MAX_SIZE; + ct_iu_req->wwpn = erp_action->port->wwpn; + + /* setup parameters for send generic command */ + gid_pn->ct.port = adapter->nameserver_port; + gid_pn->ct.handler = zfcp_ns_gid_pn_handler; + gid_pn->ct.handler_data = (unsigned long) gid_pn; + gid_pn->ct.timeout = ZFCP_NS_GID_PN_TIMEOUT; + gid_pn->ct.timer = &erp_action->timer; + gid_pn->port = erp_action->port; + + ret = zfcp_fsf_send_ct(&gid_pn->ct, adapter->pool.fsf_req_erp, + erp_action); + if (ret) { + ZFCP_LOG_INFO("error: initiation of gid_pn nameserver request " + "failed for adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + + zfcp_gid_pn_buffers_free(gid_pn); + } + + out: + return ret; +} + +/** + * zfcp_ns_gid_pn_handler - handler for GID_PN nameserver request + * @data: unsigned long, contains pointer to struct zfcp_gid_pn_data + */ +static void zfcp_ns_gid_pn_handler(unsigned long data) +{ + struct zfcp_port *port; + struct zfcp_send_ct *ct; + struct ct_iu_gid_pn_req *ct_iu_req; + struct ct_iu_gid_pn_resp *ct_iu_resp; + struct zfcp_gid_pn_data *gid_pn; + + + gid_pn = (struct zfcp_gid_pn_data *) data; + port = gid_pn->port; + ct = &gid_pn->ct; + ct_iu_req = zfcp_sg_to_address(ct->req); + ct_iu_resp = zfcp_sg_to_address(ct->resp); + + if ((ct->status != 0) || zfcp_check_ct_response(&ct_iu_resp->header)) { + /* FIXME: do we need some specific erp entry points */ + atomic_set_mask(ZFCP_STATUS_PORT_INVALID_WWPN, &port->status); + goto failed; + } + /* paranoia */ + if (ct_iu_req->wwpn != port->wwpn) { + ZFCP_LOG_NORMAL("bug: wwpn 0x%016Lx returned by nameserver " + "lookup does not match expected wwpn 0x%016Lx " + "for adapter %s\n", ct_iu_req->wwpn, port->wwpn, + zfcp_get_busid_by_port(port)); + goto mismatch; + } + + /* looks like a valid d_id */ + port->d_id = ct_iu_resp->d_id & ZFCP_DID_MASK; + atomic_set_mask(ZFCP_STATUS_PORT_DID_DID, &port->status); + ZFCP_LOG_DEBUG("adapter %s: wwpn=0x%016Lx ---> d_id=0x%08x\n", + zfcp_get_busid_by_port(port), port->wwpn, port->d_id); + goto out; + + mismatch: + ZFCP_LOG_DEBUG("CT IUs do not match:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, (char *) ct_iu_req, + sizeof(struct ct_iu_gid_pn_req)); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, (char *) ct_iu_resp, + sizeof(struct ct_iu_gid_pn_resp)); + + failed: + ZFCP_LOG_NORMAL("warning: failed gid_pn nameserver request for wwpn " + "0x%016Lx for adapter %s\n", + port->wwpn, zfcp_get_busid_by_port(port)); + out: + zfcp_gid_pn_buffers_free(gid_pn); + return; +} + +/* reject CT_IU reason codes acc. to FC-GS-4 */ +static const struct zfcp_rc_entry zfcp_ct_rc[] = { + {0x01, "invalid command code"}, + {0x02, "invalid version level"}, + {0x03, "logical error"}, + {0x04, "invalid CT_IU size"}, + {0x05, "logical busy"}, + {0x07, "protocol error"}, + {0x09, "unable to perform command request"}, + {0x0b, "command not supported"}, + {0x0d, "server not available"}, + {0x0e, "session could not be established"}, + {0xff, "vendor specific error"}, + {0, NULL}, +}; + +/* LS_RJT reason codes acc. to FC-FS */ +static const struct zfcp_rc_entry zfcp_ls_rjt_rc[] = { + {0x01, "invalid LS_Command code"}, + {0x03, "logical error"}, + {0x05, "logical busy"}, + {0x07, "protocol error"}, + {0x09, "unable to perform command request"}, + {0x0b, "command not supported"}, + {0x0e, "command already in progress"}, + {0xff, "vendor specific error"}, + {0, NULL}, +}; + +/* reject reason codes according to FC-PH/FC-FS */ +static const struct zfcp_rc_entry zfcp_p_rjt_rc[] = { + {0x01, "invalid D_ID"}, + {0x02, "invalid S_ID"}, + {0x03, "Nx_Port not available, temporary"}, + {0x04, "Nx_Port not available, permament"}, + {0x05, "class not supported"}, + {0x06, "delimiter usage error"}, + {0x07, "TYPE not supported"}, + {0x08, "invalid Link_Control"}, + {0x09, "invalid R_CTL field"}, + {0x0a, "invalid F_CTL field"}, + {0x0b, "invalid OX_ID"}, + {0x0c, "invalid RX_ID"}, + {0x0d, "invalid SEQ_ID"}, + {0x0e, "invalid DF_CTL"}, + {0x0f, "invalid SEQ_CNT"}, + {0x10, "invalid parameter field"}, + {0x11, "exchange error"}, + {0x12, "protocol error"}, + {0x13, "incorrect length"}, + {0x14, "unsupported ACK"}, + {0x15, "class of service not supported by entity at FFFFFE"}, + {0x16, "login required"}, + {0x17, "excessive sequences attempted"}, + {0x18, "unable to establish exchange"}, + {0x1a, "fabric path not available"}, + {0x1b, "invalid VC_ID (class 4)"}, + {0x1c, "invalid CS_CTL field"}, + {0x1d, "insufficient resources for VC (class 4)"}, + {0x1f, "invalid class of service"}, + {0x20, "preemption request rejected"}, + {0x21, "preemption not enabled"}, + {0x22, "multicast error"}, + {0x23, "multicast error terminate"}, + {0x24, "process login required"}, + {0xff, "vendor specific reject"}, + {0, NULL}, +}; + +/** + * zfcp_rc_description - return description for given reaon code + * @code: reason code + * @rc_table: table of reason codes and descriptions + */ +static inline const char * +zfcp_rc_description(u8 code, const struct zfcp_rc_entry *rc_table) +{ + const char *descr = "unknown reason code"; + + do { + if (code == rc_table->code) { + descr = rc_table->description; + break; + } + rc_table++; + } while (rc_table->code && rc_table->description); + + return descr; +} + +/** + * zfcp_check_ct_response - evaluate reason code for CT_IU + * @rjt: response payload to an CT_IU request + * Return: 0 for accept CT_IU, 1 for reject CT_IU or invlid response code + */ +int +zfcp_check_ct_response(struct ct_hdr *rjt) +{ + if (rjt->cmd_rsp_code == ZFCP_CT_ACCEPT) + return 0; + + if (rjt->cmd_rsp_code != ZFCP_CT_REJECT) { + ZFCP_LOG_NORMAL("error: invalid Generic Service command/" + "response code (0x%04hx)\n", + rjt->cmd_rsp_code); + return 1; + } + + ZFCP_LOG_INFO("Generic Service command rejected\n"); + ZFCP_LOG_INFO("%s (0x%02x, 0x%02x, 0x%02x)\n", + zfcp_rc_description(rjt->reason_code, zfcp_ct_rc), + (u32) rjt->reason_code, (u32) rjt->reason_code_expl, + (u32) rjt->vendor_unique); + + return 1; +} + +/** + * zfcp_print_els_rjt - print reject parameter and description for ELS reject + * @rjt_par: reject parameter acc. to FC-PH/FC-FS + * @rc_table: table of reason codes and descriptions + */ +static inline void +zfcp_print_els_rjt(struct zfcp_ls_rjt_par *rjt_par, + const struct zfcp_rc_entry *rc_table) +{ + ZFCP_LOG_INFO("%s (%02x %02x %02x %02x)\n", + zfcp_rc_description(rjt_par->reason_code, rc_table), + (u32) rjt_par->action, (u32) rjt_par->reason_code, + (u32) rjt_par->reason_expl, (u32) rjt_par->vendor_unique); +} + +/** + * zfcp_fsf_handle_els_rjt - evaluate status qualifier/reason code on ELS reject + * @sq: status qualifier word + * @rjt_par: reject parameter as described in FC-PH and FC-FS + * Return: -EROMTEIO for LS_RJT, -EREMCHG for invalid D_ID, -EIO else + */ +int +zfcp_handle_els_rjt(u32 sq, struct zfcp_ls_rjt_par *rjt_par) +{ + int ret = -EIO; + + if (sq == FSF_IOSTAT_NPORT_RJT) { + ZFCP_LOG_INFO("ELS rejected (P_RJT)\n"); + zfcp_print_els_rjt(rjt_par, zfcp_p_rjt_rc); + /* invalid d_id */ + if (rjt_par->reason_code == 0x01) + ret = -EREMCHG; + } else if (sq == FSF_IOSTAT_FABRIC_RJT) { + ZFCP_LOG_INFO("ELS rejected (F_RJT)\n"); + zfcp_print_els_rjt(rjt_par, zfcp_p_rjt_rc); + /* invalid d_id */ + if (rjt_par->reason_code == 0x01) + ret = -EREMCHG; + } else if (sq == FSF_IOSTAT_LS_RJT) { + ZFCP_LOG_INFO("ELS rejected (LS_RJT)\n"); + zfcp_print_els_rjt(rjt_par, zfcp_ls_rjt_rc); + ret = -EREMOTEIO; + } else + ZFCP_LOG_INFO("unexpected SQ: 0x%02x\n", sq); + + return ret; +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_ccw.c b/drivers/s390/scsi/zfcp_ccw.c new file mode 100644 index 000000000000..0fc46381fc22 --- /dev/null +++ b/drivers/s390/scsi/zfcp_ccw.c @@ -0,0 +1,312 @@ +/* + * linux/drivers/s390/scsi/zfcp_ccw.c + * + * FCP adapter driver for IBM eServer zSeries + * + * CCW driver related routines + * + * (C) Copyright IBM Corp. 2003, 2004 + * + * Authors: + * Martin Peschke <mpeschke@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_CCW_C_REVISION "$Revision: 1.58 $" + +#include "zfcp_ext.h" + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_CONFIG + +static int zfcp_ccw_probe(struct ccw_device *); +static void zfcp_ccw_remove(struct ccw_device *); +static int zfcp_ccw_set_online(struct ccw_device *); +static int zfcp_ccw_set_offline(struct ccw_device *); +static int zfcp_ccw_notify(struct ccw_device *, int); +static void zfcp_ccw_shutdown(struct device *); + +static struct ccw_device_id zfcp_ccw_device_id[] = { + {CCW_DEVICE_DEVTYPE(ZFCP_CONTROL_UNIT_TYPE, + ZFCP_CONTROL_UNIT_MODEL, + ZFCP_DEVICE_TYPE, + ZFCP_DEVICE_MODEL)}, + {CCW_DEVICE_DEVTYPE(ZFCP_CONTROL_UNIT_TYPE, + ZFCP_CONTROL_UNIT_MODEL, + ZFCP_DEVICE_TYPE, + ZFCP_DEVICE_MODEL_PRIV)}, + {}, +}; + +static struct ccw_driver zfcp_ccw_driver = { + .owner = THIS_MODULE, + .name = ZFCP_NAME, + .ids = zfcp_ccw_device_id, + .probe = zfcp_ccw_probe, + .remove = zfcp_ccw_remove, + .set_online = zfcp_ccw_set_online, + .set_offline = zfcp_ccw_set_offline, + .notify = zfcp_ccw_notify, + .driver = { + .shutdown = zfcp_ccw_shutdown, + }, +}; + +MODULE_DEVICE_TABLE(ccw, zfcp_ccw_device_id); + +/** + * zfcp_ccw_probe - probe function of zfcp driver + * @ccw_device: pointer to belonging ccw device + * + * This function gets called by the common i/o layer and sets up the initial + * data structures for each fcp adapter, which was detected by the system. + * Also the sysfs files for this adapter will be created by this function. + * In addition the nameserver port will be added to the ports of the adapter + * and its sysfs representation will be created too. + */ +static int +zfcp_ccw_probe(struct ccw_device *ccw_device) +{ + struct zfcp_adapter *adapter; + int retval = 0; + + down(&zfcp_data.config_sema); + adapter = zfcp_adapter_enqueue(ccw_device); + if (!adapter) + retval = -EINVAL; + else + ZFCP_LOG_DEBUG("Probed adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + up(&zfcp_data.config_sema); + return retval; +} + +/** + * zfcp_ccw_remove - remove function of zfcp driver + * @ccw_device: pointer to belonging ccw device + * + * This function gets called by the common i/o layer and removes an adapter + * from the system. Task of this function is to get rid of all units and + * ports that belong to this adapter. And in addition all resources of this + * adapter will be freed too. + */ +static void +zfcp_ccw_remove(struct ccw_device *ccw_device) +{ + struct zfcp_adapter *adapter; + struct zfcp_port *port, *p; + struct zfcp_unit *unit, *u; + + ccw_device_set_offline(ccw_device); + down(&zfcp_data.config_sema); + adapter = dev_get_drvdata(&ccw_device->dev); + + ZFCP_LOG_DEBUG("Removing adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + write_lock_irq(&zfcp_data.config_lock); + list_for_each_entry_safe(port, p, &adapter->port_list_head, list) { + list_for_each_entry_safe(unit, u, &port->unit_list_head, list) { + list_move(&unit->list, &port->unit_remove_lh); + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, + &unit->status); + } + list_move(&port->list, &adapter->port_remove_lh); + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status); + } + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status); + write_unlock_irq(&zfcp_data.config_lock); + + list_for_each_entry_safe(port, p, &adapter->port_remove_lh, list) { + list_for_each_entry_safe(unit, u, &port->unit_remove_lh, list) { + zfcp_unit_dequeue(unit); + } + zfcp_port_dequeue(port); + } + zfcp_adapter_wait(adapter); + zfcp_adapter_dequeue(adapter); + + up(&zfcp_data.config_sema); +} + +/** + * zfcp_ccw_set_online - set_online function of zfcp driver + * @ccw_device: pointer to belonging ccw device + * + * This function gets called by the common i/o layer and sets an adapter + * into state online. Setting an fcp device online means that it will be + * registered with the SCSI stack, that the QDIO queues will be set up + * and that the adapter will be opened (asynchronously). + */ +static int +zfcp_ccw_set_online(struct ccw_device *ccw_device) +{ + struct zfcp_adapter *adapter; + int retval; + + down(&zfcp_data.config_sema); + adapter = dev_get_drvdata(&ccw_device->dev); + + retval = zfcp_adapter_debug_register(adapter); + if (retval) + goto out; + retval = zfcp_erp_thread_setup(adapter); + if (retval) { + ZFCP_LOG_INFO("error: start of error recovery thread for " + "adapter %s failed\n", + zfcp_get_busid_by_adapter(adapter)); + goto out_erp_thread; + } + + retval = zfcp_adapter_scsi_register(adapter); + if (retval) + goto out_scsi_register; + zfcp_erp_modify_adapter_status(adapter, ZFCP_STATUS_COMMON_RUNNING, + ZFCP_SET); + zfcp_erp_adapter_reopen(adapter, ZFCP_STATUS_COMMON_ERP_FAILED); + zfcp_erp_wait(adapter); + goto out; + + out_scsi_register: + zfcp_erp_thread_kill(adapter); + out_erp_thread: + zfcp_adapter_debug_unregister(adapter); + out: + up(&zfcp_data.config_sema); + return retval; +} + +/** + * zfcp_ccw_set_offline - set_offline function of zfcp driver + * @ccw_device: pointer to belonging ccw device + * + * This function gets called by the common i/o layer and sets an adapter + * into state offline. Setting an fcp device offline means that it will be + * unregistered from the SCSI stack and that the adapter will be shut down + * asynchronously. + */ +static int +zfcp_ccw_set_offline(struct ccw_device *ccw_device) +{ + struct zfcp_adapter *adapter; + + down(&zfcp_data.config_sema); + adapter = dev_get_drvdata(&ccw_device->dev); + zfcp_erp_adapter_shutdown(adapter, 0); + zfcp_erp_wait(adapter); + zfcp_adapter_scsi_unregister(adapter); + zfcp_erp_thread_kill(adapter); + zfcp_adapter_debug_unregister(adapter); + up(&zfcp_data.config_sema); + return 0; +} + +/** + * zfcp_ccw_notify + * @ccw_device: pointer to belonging ccw device + * @event: indicates if adapter was detached or attached + * + * This function gets called by the common i/o layer if an adapter has gone + * or reappeared. + */ +static int +zfcp_ccw_notify(struct ccw_device *ccw_device, int event) +{ + struct zfcp_adapter *adapter; + + down(&zfcp_data.config_sema); + adapter = dev_get_drvdata(&ccw_device->dev); + switch (event) { + case CIO_GONE: + ZFCP_LOG_NORMAL("adapter %s: device gone\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf,1,"dev_gone"); + zfcp_erp_adapter_shutdown(adapter, 0); + break; + case CIO_NO_PATH: + ZFCP_LOG_NORMAL("adapter %s: no path\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf,1,"no_path"); + zfcp_erp_adapter_shutdown(adapter, 0); + break; + case CIO_OPER: + ZFCP_LOG_NORMAL("adapter %s: operational again\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf,1,"dev_oper"); + zfcp_erp_modify_adapter_status(adapter, + ZFCP_STATUS_COMMON_RUNNING, + ZFCP_SET); + zfcp_erp_adapter_reopen(adapter, + ZFCP_STATUS_COMMON_ERP_FAILED); + break; + } + zfcp_erp_wait(adapter); + up(&zfcp_data.config_sema); + return 1; +} + +/** + * zfcp_ccw_register - ccw register function + * + * Registers the driver at the common i/o layer. This function will be called + * at module load time/system start. + */ +int __init +zfcp_ccw_register(void) +{ + int retval; + + retval = ccw_driver_register(&zfcp_ccw_driver); + if (retval) + goto out; + retval = zfcp_sysfs_driver_create_files(&zfcp_ccw_driver.driver); + if (retval) + ccw_driver_unregister(&zfcp_ccw_driver); + out: + return retval; +} + +/** + * zfcp_ccw_unregister - ccw unregister function + * + * Unregisters the driver from common i/o layer. Function will be called at + * module unload/system shutdown. + */ +void __exit +zfcp_ccw_unregister(void) +{ + zfcp_sysfs_driver_remove_files(&zfcp_ccw_driver.driver); + ccw_driver_unregister(&zfcp_ccw_driver); +} + +/** + * zfcp_ccw_shutdown - gets called on reboot/shutdown + * + * Makes sure that QDIO queues are down when the system gets stopped. + */ +static void +zfcp_ccw_shutdown(struct device *dev) +{ + struct zfcp_adapter *adapter; + + down(&zfcp_data.config_sema); + adapter = dev_get_drvdata(dev); + zfcp_erp_adapter_shutdown(adapter, 0); + zfcp_erp_wait(adapter); + up(&zfcp_data.config_sema); +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_def.h b/drivers/s390/scsi/zfcp_def.h new file mode 100644 index 000000000000..53fcccbb424c --- /dev/null +++ b/drivers/s390/scsi/zfcp_def.h @@ -0,0 +1,1121 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_def.h + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.com> + * Volker Sameske <sameske@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#ifndef ZFCP_DEF_H +#define ZFCP_DEF_H + +#define ZFCP_DEF_REVISION "$Revision: 1.111 $" + +/*************************** INCLUDES *****************************************/ + +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/major.h> +#include <linux/blkdev.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fc.h> +#include "../../fc4/fc.h" +#include "zfcp_fsf.h" +#include <asm/ccwdev.h> +#include <asm/qdio.h> +#include <asm/debug.h> +#include <asm/ebcdic.h> +#include <linux/mempool.h> +#include <linux/syscalls.h> +#include <linux/ioctl.h> +#include <linux/ioctl32.h> + +/************************ DEBUG FLAGS *****************************************/ + +#define ZFCP_PRINT_FLAGS + +/********************* GENERAL DEFINES *********************************/ + +/* zfcp version number, it consists of major, minor, and patch-level number */ +#define ZFCP_VERSION "4.2.0" + +/** + * zfcp_sg_to_address - determine kernel address from struct scatterlist + * @list: struct scatterlist + * Return: kernel address + */ +static inline void * +zfcp_sg_to_address(struct scatterlist *list) +{ + return (void *) (page_address(list->page) + list->offset); +} + +/** + * zfcp_address_to_sg - set up struct scatterlist from kernel address + * @address: kernel address + * @list: struct scatterlist + */ +static inline void +zfcp_address_to_sg(void *address, struct scatterlist *list) +{ + list->page = virt_to_page(address); + list->offset = ((unsigned long) address) & (PAGE_SIZE - 1); +} + +/********************* SCSI SPECIFIC DEFINES *********************************/ + +/* 32 bit for SCSI ID and LUN as long as the SCSI stack uses this type */ +typedef u32 scsi_id_t; +typedef u32 scsi_lun_t; + +#define ZFCP_ERP_SCSI_LOW_MEM_TIMEOUT (100*HZ) +#define ZFCP_SCSI_ER_TIMEOUT (100*HZ) + +/********************* CIO/QDIO SPECIFIC DEFINES *****************************/ + +/* Adapter Identification Parameters */ +#define ZFCP_CONTROL_UNIT_TYPE 0x1731 +#define ZFCP_CONTROL_UNIT_MODEL 0x03 +#define ZFCP_DEVICE_TYPE 0x1732 +#define ZFCP_DEVICE_MODEL 0x03 +#define ZFCP_DEVICE_MODEL_PRIV 0x04 + +/* allow as many chained SBALs as are supported by hardware */ +#define ZFCP_MAX_SBALS_PER_REQ FSF_MAX_SBALS_PER_REQ +#define ZFCP_MAX_SBALS_PER_CT_REQ FSF_MAX_SBALS_PER_REQ +#define ZFCP_MAX_SBALS_PER_ELS_REQ FSF_MAX_SBALS_PER_ELS_REQ + +/* DMQ bug workaround: don't use last SBALE */ +#define ZFCP_MAX_SBALES_PER_SBAL (QDIO_MAX_ELEMENTS_PER_BUFFER - 1) + +/* index of last SBALE (with respect to DMQ bug workaround) */ +#define ZFCP_LAST_SBALE_PER_SBAL (ZFCP_MAX_SBALES_PER_SBAL - 1) + +/* max. number of (data buffer) SBALEs in largest SBAL chain */ +#define ZFCP_MAX_SBALES_PER_REQ \ + (ZFCP_MAX_SBALS_PER_REQ * ZFCP_MAX_SBALES_PER_SBAL - 2) + /* request ID + QTCB in SBALE 0 + 1 of first SBAL in chain */ + +/* FIXME(tune): free space should be one max. SBAL chain plus what? */ +#define ZFCP_QDIO_PCI_INTERVAL (QDIO_MAX_BUFFERS_PER_Q \ + - (ZFCP_MAX_SBALS_PER_REQ + 4)) + +#define ZFCP_SBAL_TIMEOUT (5*HZ) + +#define ZFCP_TYPE2_RECOVERY_TIME (8*HZ) + +/* queue polling (values in microseconds) */ +#define ZFCP_MAX_INPUT_THRESHOLD 5000 /* FIXME: tune */ +#define ZFCP_MAX_OUTPUT_THRESHOLD 1000 /* FIXME: tune */ +#define ZFCP_MIN_INPUT_THRESHOLD 1 /* ignored by QDIO layer */ +#define ZFCP_MIN_OUTPUT_THRESHOLD 1 /* ignored by QDIO layer */ + +#define QDIO_SCSI_QFMT 1 /* 1 for FSF */ + +/********************* FSF SPECIFIC DEFINES *********************************/ + +#define ZFCP_ULP_INFO_VERSION 26 +#define ZFCP_QTCB_VERSION FSF_QTCB_CURRENT_VERSION +/* ATTENTION: value must not be used by hardware */ +#define FSF_QTCB_UNSOLICITED_STATUS 0x6305 +#define ZFCP_STATUS_READ_FAILED_THRESHOLD 3 +#define ZFCP_STATUS_READS_RECOM FSF_STATUS_READS_RECOM +#define ZFCP_EXCHANGE_CONFIG_DATA_RETRIES 6 +#define ZFCP_EXCHANGE_CONFIG_DATA_SLEEP 50 + +/* timeout value for "default timer" for fsf requests */ +#define ZFCP_FSF_REQUEST_TIMEOUT (60*HZ); + +/*************** FIBRE CHANNEL PROTOCOL SPECIFIC DEFINES ********************/ + +typedef unsigned long long wwn_t; +typedef unsigned int fc_id_t; +typedef unsigned long long fcp_lun_t; +/* data length field may be at variable position in FCP-2 FCP_CMND IU */ +typedef unsigned int fcp_dl_t; + +#define ZFCP_FC_SERVICE_CLASS_DEFAULT FSF_CLASS_3 + +/* timeout for name-server lookup (in seconds) */ +#define ZFCP_NS_GID_PN_TIMEOUT 10 + +/* largest SCSI command we can process */ +/* FCP-2 (FCP_CMND IU) allows up to (255-3+16) */ +#define ZFCP_MAX_SCSI_CMND_LENGTH 255 +/* maximum number of commands in LUN queue (tagged queueing) */ +#define ZFCP_CMND_PER_LUN 32 + +/* task attribute values in FCP-2 FCP_CMND IU */ +#define SIMPLE_Q 0 +#define HEAD_OF_Q 1 +#define ORDERED_Q 2 +#define ACA_Q 4 +#define UNTAGGED 5 + +/* task management flags in FCP-2 FCP_CMND IU */ +#define FCP_CLEAR_ACA 0x40 +#define FCP_TARGET_RESET 0x20 +#define FCP_LOGICAL_UNIT_RESET 0x10 +#define FCP_CLEAR_TASK_SET 0x04 +#define FCP_ABORT_TASK_SET 0x02 + +#define FCP_CDB_LENGTH 16 + +#define ZFCP_DID_MASK 0x00FFFFFF + +/* FCP(-2) FCP_CMND IU */ +struct fcp_cmnd_iu { + fcp_lun_t fcp_lun; /* FCP logical unit number */ + u8 crn; /* command reference number */ + u8 reserved0:5; /* reserved */ + u8 task_attribute:3; /* task attribute */ + u8 task_management_flags; /* task management flags */ + u8 add_fcp_cdb_length:6; /* additional FCP_CDB length */ + u8 rddata:1; /* read data */ + u8 wddata:1; /* write data */ + u8 fcp_cdb[FCP_CDB_LENGTH]; +} __attribute__((packed)); + +/* FCP(-2) FCP_RSP IU */ +struct fcp_rsp_iu { + u8 reserved0[10]; + union { + struct { + u8 reserved1:3; + u8 fcp_conf_req:1; + u8 fcp_resid_under:1; + u8 fcp_resid_over:1; + u8 fcp_sns_len_valid:1; + u8 fcp_rsp_len_valid:1; + } bits; + u8 value; + } validity; + u8 scsi_status; + u32 fcp_resid; + u32 fcp_sns_len; + u32 fcp_rsp_len; +} __attribute__((packed)); + + +#define RSP_CODE_GOOD 0 +#define RSP_CODE_LENGTH_MISMATCH 1 +#define RSP_CODE_FIELD_INVALID 2 +#define RSP_CODE_RO_MISMATCH 3 +#define RSP_CODE_TASKMAN_UNSUPP 4 +#define RSP_CODE_TASKMAN_FAILED 5 + +/* see fc-fs */ +#define LS_FAN 0x60000000 +#define LS_RSCN 0x61040000 + +struct fcp_rscn_head { + u8 command; + u8 page_length; /* always 0x04 */ + u16 payload_len; +} __attribute__((packed)); + +struct fcp_rscn_element { + u8 reserved:2; + u8 event_qual:4; + u8 addr_format:2; + u32 nport_did:24; +} __attribute__((packed)); + +#define ZFCP_PORT_ADDRESS 0x0 +#define ZFCP_AREA_ADDRESS 0x1 +#define ZFCP_DOMAIN_ADDRESS 0x2 +#define ZFCP_FABRIC_ADDRESS 0x3 + +#define ZFCP_PORTS_RANGE_PORT 0xFFFFFF +#define ZFCP_PORTS_RANGE_AREA 0xFFFF00 +#define ZFCP_PORTS_RANGE_DOMAIN 0xFF0000 +#define ZFCP_PORTS_RANGE_FABRIC 0x000000 + +#define ZFCP_NO_PORTS_PER_AREA 0x100 +#define ZFCP_NO_PORTS_PER_DOMAIN 0x10000 +#define ZFCP_NO_PORTS_PER_FABRIC 0x1000000 + +struct fcp_fan { + u32 command; + u32 fport_did; + wwn_t fport_wwpn; + wwn_t fport_wwname; +} __attribute__((packed)); + +/* see fc-ph */ +struct fcp_logo { + u32 command; + u32 nport_did; + wwn_t nport_wwpn; +} __attribute__((packed)); + +/* + * FC-FS stuff + */ +#define R_A_TOV 10 /* seconds */ +#define ZFCP_ELS_TIMEOUT (2 * R_A_TOV) + +#define ZFCP_LS_RLS 0x0f +#define ZFCP_LS_ADISC 0x52 +#define ZFCP_LS_RPS 0x56 +#define ZFCP_LS_RSCN 0x61 +#define ZFCP_LS_RNID 0x78 + +struct zfcp_ls_rjt_par { + u8 action; + u8 reason_code; + u8 reason_expl; + u8 vendor_unique; +} __attribute__ ((packed)); + +struct zfcp_ls_adisc { + u8 code; + u8 field[3]; + u32 hard_nport_id; + u64 wwpn; + u64 wwnn; + u32 nport_id; +} __attribute__ ((packed)); + +struct zfcp_ls_adisc_acc { + u8 code; + u8 field[3]; + u32 hard_nport_id; + u64 wwpn; + u64 wwnn; + u32 nport_id; +} __attribute__ ((packed)); + +struct zfcp_rc_entry { + u8 code; + const char *description; +}; + +/* + * FC-GS-2 stuff + */ +#define ZFCP_CT_REVISION 0x01 +#define ZFCP_CT_DIRECTORY_SERVICE 0xFC +#define ZFCP_CT_NAME_SERVER 0x02 +#define ZFCP_CT_SYNCHRONOUS 0x00 +#define ZFCP_CT_GID_PN 0x0121 +#define ZFCP_CT_MAX_SIZE 0x1020 +#define ZFCP_CT_ACCEPT 0x8002 +#define ZFCP_CT_REJECT 0x8001 + +/* + * FC-GS-4 stuff + */ +#define ZFCP_CT_TIMEOUT (3 * R_A_TOV) + + +/***************** S390 DEBUG FEATURE SPECIFIC DEFINES ***********************/ + +/* debug feature entries per adapter */ +#define ZFCP_ERP_DBF_INDEX 1 +#define ZFCP_ERP_DBF_AREAS 2 +#define ZFCP_ERP_DBF_LENGTH 16 +#define ZFCP_ERP_DBF_LEVEL 3 +#define ZFCP_ERP_DBF_NAME "zfcperp" + +#define ZFCP_CMD_DBF_INDEX 2 +#define ZFCP_CMD_DBF_AREAS 1 +#define ZFCP_CMD_DBF_LENGTH 8 +#define ZFCP_CMD_DBF_LEVEL 3 +#define ZFCP_CMD_DBF_NAME "zfcpcmd" + +#define ZFCP_ABORT_DBF_INDEX 2 +#define ZFCP_ABORT_DBF_AREAS 1 +#define ZFCP_ABORT_DBF_LENGTH 8 +#define ZFCP_ABORT_DBF_LEVEL 6 +#define ZFCP_ABORT_DBF_NAME "zfcpabt" + +#define ZFCP_IN_ELS_DBF_INDEX 2 +#define ZFCP_IN_ELS_DBF_AREAS 1 +#define ZFCP_IN_ELS_DBF_LENGTH 8 +#define ZFCP_IN_ELS_DBF_LEVEL 6 +#define ZFCP_IN_ELS_DBF_NAME "zfcpels" + +/******************** LOGGING MACROS AND DEFINES *****************************/ + +/* + * Logging may be applied on certain kinds of driver operations + * independently. Additionally, different log-levels are supported for + * each of these areas. + */ + +#define ZFCP_NAME "zfcp" + +/* read-only LUN sharing switch initial value */ +#define ZFCP_RO_LUN_SHARING_DEFAULTS 0 + +/* independent log areas */ +#define ZFCP_LOG_AREA_OTHER 0 +#define ZFCP_LOG_AREA_SCSI 1 +#define ZFCP_LOG_AREA_FSF 2 +#define ZFCP_LOG_AREA_CONFIG 3 +#define ZFCP_LOG_AREA_CIO 4 +#define ZFCP_LOG_AREA_QDIO 5 +#define ZFCP_LOG_AREA_ERP 6 +#define ZFCP_LOG_AREA_FC 7 + +/* log level values*/ +#define ZFCP_LOG_LEVEL_NORMAL 0 +#define ZFCP_LOG_LEVEL_INFO 1 +#define ZFCP_LOG_LEVEL_DEBUG 2 +#define ZFCP_LOG_LEVEL_TRACE 3 + +/* + * this allows removal of logging code by the preprocessor + * (the most detailed log level still to be compiled in is specified, + * higher log levels are removed) + */ +#define ZFCP_LOG_LEVEL_LIMIT ZFCP_LOG_LEVEL_TRACE + +/* get "loglevel" nibble assignment */ +#define ZFCP_GET_LOG_VALUE(zfcp_lognibble) \ + ((atomic_read(&zfcp_data.loglevel) >> (zfcp_lognibble<<2)) & 0xF) + +/* set "loglevel" nibble */ +#define ZFCP_SET_LOG_NIBBLE(value, zfcp_lognibble) \ + (value << (zfcp_lognibble << 2)) + +/* all log-level defaults are combined to generate initial log-level */ +#define ZFCP_LOG_LEVEL_DEFAULTS \ + (ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_OTHER) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_SCSI) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_FSF) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_CONFIG) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_CIO) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_QDIO) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_ERP) | \ + ZFCP_SET_LOG_NIBBLE(ZFCP_LOG_LEVEL_NORMAL, ZFCP_LOG_AREA_FC)) + +/* check whether we have the right level for logging */ +#define ZFCP_LOG_CHECK(level) \ + ((ZFCP_GET_LOG_VALUE(ZFCP_LOG_AREA)) >= level) + +/* logging routine for zfcp */ +#define _ZFCP_LOG(fmt, args...) \ + printk(KERN_ERR ZFCP_NAME": %s(%d): " fmt, __FUNCTION__, \ + __LINE__ , ##args) + +#define ZFCP_LOG(level, fmt, args...) \ +do { \ + if (ZFCP_LOG_CHECK(level)) \ + _ZFCP_LOG(fmt, ##args); \ +} while (0) + +#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_NORMAL +# define ZFCP_LOG_NORMAL(fmt, args...) +#else +# define ZFCP_LOG_NORMAL(fmt, args...) \ +do { \ + if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_NORMAL)) \ + printk(KERN_ERR ZFCP_NAME": " fmt, ##args); \ +} while (0) +#endif + +#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_INFO +# define ZFCP_LOG_INFO(fmt, args...) +#else +# define ZFCP_LOG_INFO(fmt, args...) \ +do { \ + if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_INFO)) \ + printk(KERN_ERR ZFCP_NAME": " fmt, ##args); \ +} while (0) +#endif + +#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_DEBUG +# define ZFCP_LOG_DEBUG(fmt, args...) +#else +# define ZFCP_LOG_DEBUG(fmt, args...) \ + ZFCP_LOG(ZFCP_LOG_LEVEL_DEBUG, fmt , ##args) +#endif + +#if ZFCP_LOG_LEVEL_LIMIT < ZFCP_LOG_LEVEL_TRACE +# define ZFCP_LOG_TRACE(fmt, args...) +#else +# define ZFCP_LOG_TRACE(fmt, args...) \ + ZFCP_LOG(ZFCP_LOG_LEVEL_TRACE, fmt , ##args) +#endif + +#ifndef ZFCP_PRINT_FLAGS +# define ZFCP_LOG_FLAGS(level, fmt, args...) +#else +extern u32 flags_dump; +# define ZFCP_LOG_FLAGS(level, fmt, args...) \ +do { \ + if (level <= flags_dump) \ + _ZFCP_LOG(fmt, ##args); \ +} while (0) +#endif + +/*************** ADAPTER/PORT/UNIT AND FSF_REQ STATUS FLAGS ******************/ + +/* + * Note, the leftmost status byte is common among adapter, port + * and unit + */ +#define ZFCP_COMMON_FLAGS 0xfff00000 +#define ZFCP_SPECIFIC_FLAGS 0x000fffff + +/* common status bits */ +#define ZFCP_STATUS_COMMON_REMOVE 0x80000000 +#define ZFCP_STATUS_COMMON_RUNNING 0x40000000 +#define ZFCP_STATUS_COMMON_ERP_FAILED 0x20000000 +#define ZFCP_STATUS_COMMON_UNBLOCKED 0x10000000 +#define ZFCP_STATUS_COMMON_OPENING 0x08000000 +#define ZFCP_STATUS_COMMON_OPEN 0x04000000 +#define ZFCP_STATUS_COMMON_CLOSING 0x02000000 +#define ZFCP_STATUS_COMMON_ERP_INUSE 0x01000000 +#define ZFCP_STATUS_COMMON_ACCESS_DENIED 0x00800000 + +/* adapter status */ +#define ZFCP_STATUS_ADAPTER_QDIOUP 0x00000002 +#define ZFCP_STATUS_ADAPTER_REGISTERED 0x00000004 +#define ZFCP_STATUS_ADAPTER_XCONFIG_OK 0x00000008 +#define ZFCP_STATUS_ADAPTER_HOST_CON_INIT 0x00000010 +#define ZFCP_STATUS_ADAPTER_ERP_THREAD_UP 0x00000020 +#define ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL 0x00000080 +#define ZFCP_STATUS_ADAPTER_ERP_PENDING 0x00000100 +#define ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED 0x00000200 + +#define ZFCP_STATUS_ADAPTER_SCSI_UP \ + (ZFCP_STATUS_COMMON_UNBLOCKED | \ + ZFCP_STATUS_ADAPTER_REGISTERED) + + +/* FC-PH/FC-GS well-known address identifiers for generic services */ +#define ZFCP_DID_MANAGEMENT_SERVICE 0xFFFFFA +#define ZFCP_DID_TIME_SERVICE 0xFFFFFB +#define ZFCP_DID_DIRECTORY_SERVICE 0xFFFFFC +#define ZFCP_DID_ALIAS_SERVICE 0xFFFFF8 +#define ZFCP_DID_KEY_DISTRIBUTION_SERVICE 0xFFFFF7 + +/* remote port status */ +#define ZFCP_STATUS_PORT_PHYS_OPEN 0x00000001 +#define ZFCP_STATUS_PORT_DID_DID 0x00000002 +#define ZFCP_STATUS_PORT_PHYS_CLOSING 0x00000004 +#define ZFCP_STATUS_PORT_NO_WWPN 0x00000008 +#define ZFCP_STATUS_PORT_NO_SCSI_ID 0x00000010 +#define ZFCP_STATUS_PORT_INVALID_WWPN 0x00000020 +#define ZFCP_STATUS_PORT_ACCESS_DENIED 0x00000040 + +/* for ports with well known addresses */ +#define ZFCP_STATUS_PORT_WKA \ + (ZFCP_STATUS_PORT_NO_WWPN | \ + ZFCP_STATUS_PORT_NO_SCSI_ID) + +/* logical unit status */ +#define ZFCP_STATUS_UNIT_NOTSUPPUNITRESET 0x00000001 +#define ZFCP_STATUS_UNIT_TEMPORARY 0x00000002 +#define ZFCP_STATUS_UNIT_SHARED 0x00000004 +#define ZFCP_STATUS_UNIT_READONLY 0x00000008 + +/* FSF request status (this does not have a common part) */ +#define ZFCP_STATUS_FSFREQ_NOT_INIT 0x00000000 +#define ZFCP_STATUS_FSFREQ_POOL 0x00000001 +#define ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT 0x00000002 +#define ZFCP_STATUS_FSFREQ_COMPLETED 0x00000004 +#define ZFCP_STATUS_FSFREQ_ERROR 0x00000008 +#define ZFCP_STATUS_FSFREQ_CLEANUP 0x00000010 +#define ZFCP_STATUS_FSFREQ_ABORTING 0x00000020 +#define ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED 0x00000040 +#define ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED 0x00000080 +#define ZFCP_STATUS_FSFREQ_ABORTED 0x00000100 +#define ZFCP_STATUS_FSFREQ_TMFUNCFAILED 0x00000200 +#define ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP 0x00000400 +#define ZFCP_STATUS_FSFREQ_RETRY 0x00000800 +#define ZFCP_STATUS_FSFREQ_DISMISSED 0x00001000 + +/*********************** ERROR RECOVERY PROCEDURE DEFINES ********************/ + +#define ZFCP_MAX_ERPS 3 + +#define ZFCP_ERP_FSFREQ_TIMEOUT (30 * HZ) +#define ZFCP_ERP_MEMWAIT_TIMEOUT HZ + +#define ZFCP_STATUS_ERP_TIMEDOUT 0x10000000 +#define ZFCP_STATUS_ERP_CLOSE_ONLY 0x01000000 +#define ZFCP_STATUS_ERP_DISMISSING 0x00100000 +#define ZFCP_STATUS_ERP_DISMISSED 0x00200000 +#define ZFCP_STATUS_ERP_LOWMEM 0x00400000 + +#define ZFCP_ERP_STEP_UNINITIALIZED 0x00000000 +#define ZFCP_ERP_STEP_FSF_XCONFIG 0x00000001 +#define ZFCP_ERP_STEP_PHYS_PORT_CLOSING 0x00000010 +#define ZFCP_ERP_STEP_PORT_CLOSING 0x00000100 +#define ZFCP_ERP_STEP_NAMESERVER_OPEN 0x00000200 +#define ZFCP_ERP_STEP_NAMESERVER_LOOKUP 0x00000400 +#define ZFCP_ERP_STEP_PORT_OPENING 0x00000800 +#define ZFCP_ERP_STEP_UNIT_CLOSING 0x00001000 +#define ZFCP_ERP_STEP_UNIT_OPENING 0x00002000 + +/* Ordered by escalation level (necessary for proper erp-code operation) */ +#define ZFCP_ERP_ACTION_REOPEN_ADAPTER 0x4 +#define ZFCP_ERP_ACTION_REOPEN_PORT_FORCED 0x3 +#define ZFCP_ERP_ACTION_REOPEN_PORT 0x2 +#define ZFCP_ERP_ACTION_REOPEN_UNIT 0x1 + +#define ZFCP_ERP_ACTION_RUNNING 0x1 +#define ZFCP_ERP_ACTION_READY 0x2 + +#define ZFCP_ERP_SUCCEEDED 0x0 +#define ZFCP_ERP_FAILED 0x1 +#define ZFCP_ERP_CONTINUES 0x2 +#define ZFCP_ERP_EXIT 0x3 +#define ZFCP_ERP_DISMISSED 0x4 +#define ZFCP_ERP_NOMEM 0x5 + + +/******************** CFDC SPECIFIC STUFF *****************************/ + +/* Firewall data channel sense data record */ +struct zfcp_cfdc_sense_data { + u32 signature; /* Request signature */ + u32 devno; /* FCP adapter device number */ + u32 command; /* Command code */ + u32 fsf_status; /* FSF request status and status qualifier */ + u8 fsf_status_qual[FSF_STATUS_QUALIFIER_SIZE]; + u8 payloads[256]; /* Access conflicts list */ + u8 control_file[0]; /* Access control table */ +}; + +#define ZFCP_CFDC_SIGNATURE 0xCFDCACDF + +#define ZFCP_CFDC_CMND_DOWNLOAD_NORMAL 0x00010001 +#define ZFCP_CFDC_CMND_DOWNLOAD_FORCE 0x00010101 +#define ZFCP_CFDC_CMND_FULL_ACCESS 0x00000201 +#define ZFCP_CFDC_CMND_RESTRICTED_ACCESS 0x00000401 +#define ZFCP_CFDC_CMND_UPLOAD 0x00010002 + +#define ZFCP_CFDC_DOWNLOAD 0x00000001 +#define ZFCP_CFDC_UPLOAD 0x00000002 +#define ZFCP_CFDC_WITH_CONTROL_FILE 0x00010000 + +#define ZFCP_CFDC_DEV_NAME "zfcp_cfdc" +#define ZFCP_CFDC_DEV_MAJOR MISC_MAJOR +#define ZFCP_CFDC_DEV_MINOR MISC_DYNAMIC_MINOR + +#define ZFCP_CFDC_MAX_CONTROL_FILE_SIZE 127 * 1024 + +/************************* STRUCTURE DEFINITIONS *****************************/ + +struct zfcp_fsf_req; + +/* holds various memory pools of an adapter */ +struct zfcp_adapter_mempool { + mempool_t *fsf_req_erp; + mempool_t *fsf_req_scsi; + mempool_t *fsf_req_abort; + mempool_t *fsf_req_status_read; + mempool_t *data_status_read; + mempool_t *data_gid_pn; +}; + +struct zfcp_exchange_config_data{ +}; + +struct zfcp_open_port { + struct zfcp_port *port; +}; + +struct zfcp_close_port { + struct zfcp_port *port; +}; + +struct zfcp_open_unit { + struct zfcp_unit *unit; +}; + +struct zfcp_close_unit { + struct zfcp_unit *unit; +}; + +struct zfcp_close_physical_port { + struct zfcp_port *port; +}; + +struct zfcp_send_fcp_command_task { + struct zfcp_fsf_req *fsf_req; + struct zfcp_unit *unit; + struct scsi_cmnd *scsi_cmnd; + unsigned long start_jiffies; +}; + +struct zfcp_send_fcp_command_task_management { + struct zfcp_unit *unit; +}; + +struct zfcp_abort_fcp_command { + struct zfcp_fsf_req *fsf_req; + struct zfcp_unit *unit; +}; + +/* + * header for CT_IU + */ +struct ct_hdr { + u8 revision; // 0x01 + u8 in_id[3]; // 0x00 + u8 gs_type; // 0xFC Directory Service + u8 gs_subtype; // 0x02 Name Server + u8 options; // 0x00 single bidirectional exchange + u8 reserved0; + u16 cmd_rsp_code; // 0x0121 GID_PN, or 0x0100 GA_NXT + u16 max_res_size; // <= (4096 - 16) / 4 + u8 reserved1; + u8 reason_code; + u8 reason_code_expl; + u8 vendor_unique; +} __attribute__ ((packed)); + +/* nameserver request CT_IU -- for requests where + * a port name is required */ +struct ct_iu_gid_pn_req { + struct ct_hdr header; + wwn_t wwpn; +} __attribute__ ((packed)); + +/* FS_ACC IU and data unit for GID_PN nameserver request */ +struct ct_iu_gid_pn_resp { + struct ct_hdr header; + fc_id_t d_id; +} __attribute__ ((packed)); + +typedef void (*zfcp_send_ct_handler_t)(unsigned long); + +/** + * struct zfcp_send_ct - used to pass parameters to function zfcp_fsf_send_ct + * @port: port where the request is sent to + * @req: scatter-gather list for request + * @resp: scatter-gather list for response + * @req_count: number of elements in request scatter-gather list + * @resp_count: number of elements in response scatter-gather list + * @handler: handler function (called for response to the request) + * @handler_data: data passed to handler function + * @pool: pointer to memory pool for ct request structure + * @timeout: FSF timeout for this request + * @timer: timer (e.g. for request initiated by erp) + * @completion: completion for synchronization purposes + * @status: used to pass error status to calling function + */ +struct zfcp_send_ct { + struct zfcp_port *port; + struct scatterlist *req; + struct scatterlist *resp; + unsigned int req_count; + unsigned int resp_count; + zfcp_send_ct_handler_t handler; + unsigned long handler_data; + mempool_t *pool; + int timeout; + struct timer_list *timer; + struct completion *completion; + int status; +}; + +/* used for name server requests in error recovery */ +struct zfcp_gid_pn_data { + struct zfcp_send_ct ct; + struct scatterlist req; + struct scatterlist resp; + struct ct_iu_gid_pn_req ct_iu_req; + struct ct_iu_gid_pn_resp ct_iu_resp; + struct zfcp_port *port; +}; + +typedef void (*zfcp_send_els_handler_t)(unsigned long); + +/** + * struct zfcp_send_els - used to pass parameters to function zfcp_fsf_send_els + * @adapter: adapter where request is sent from + * @d_id: destiniation id of port where request is sent to + * @req: scatter-gather list for request + * @resp: scatter-gather list for response + * @req_count: number of elements in request scatter-gather list + * @resp_count: number of elements in response scatter-gather list + * @handler: handler function (called for response to the request) + * @handler_data: data passed to handler function + * @timer: timer (e.g. for request initiated by erp) + * @completion: completion for synchronization purposes + * @ls_code: hex code of ELS command + * @status: used to pass error status to calling function + */ +struct zfcp_send_els { + struct zfcp_adapter *adapter; + fc_id_t d_id; + struct scatterlist *req; + struct scatterlist *resp; + unsigned int req_count; + unsigned int resp_count; + zfcp_send_els_handler_t handler; + unsigned long handler_data; + struct timer_list *timer; + struct completion *completion; + int ls_code; + int status; +}; + +struct zfcp_status_read { + struct fsf_status_read_buffer *buffer; +}; + +struct zfcp_fsf_done { + struct completion *complete; + int status; +}; + +/* request specific data */ +union zfcp_req_data { + struct zfcp_exchange_config_data exchange_config_data; + struct zfcp_open_port open_port; + struct zfcp_close_port close_port; + struct zfcp_open_unit open_unit; + struct zfcp_close_unit close_unit; + struct zfcp_close_physical_port close_physical_port; + struct zfcp_send_fcp_command_task send_fcp_command_task; + struct zfcp_send_fcp_command_task_management + send_fcp_command_task_management; + struct zfcp_abort_fcp_command abort_fcp_command; + struct zfcp_send_ct *send_ct; + struct zfcp_send_els *send_els; + struct zfcp_status_read status_read; + struct fsf_qtcb_bottom_port *port_data; +}; + +struct zfcp_qdio_queue { + struct qdio_buffer *buffer[QDIO_MAX_BUFFERS_PER_Q]; /* SBALs */ + u8 free_index; /* index of next free bfr + in queue (free_count>0) */ + atomic_t free_count; /* number of free buffers + in queue */ + rwlock_t queue_lock; /* lock for operations on queue */ + int distance_from_int; /* SBALs used since PCI indication + was last set */ +}; + +struct zfcp_erp_action { + struct list_head list; + int action; /* requested action code */ + struct zfcp_adapter *adapter; /* device which should be recovered */ + struct zfcp_port *port; + struct zfcp_unit *unit; + volatile u32 status; /* recovery status */ + u32 step; /* active step of this erp action */ + struct zfcp_fsf_req *fsf_req; /* fsf request currently pending + for this action */ + struct timer_list timer; +}; + + +struct zfcp_adapter { + struct list_head list; /* list of adapters */ + atomic_t refcount; /* reference count */ + wait_queue_head_t remove_wq; /* can be used to wait for + refcount drop to zero */ + wwn_t wwnn; /* WWNN */ + wwn_t wwpn; /* WWPN */ + fc_id_t s_id; /* N_Port ID */ + struct ccw_device *ccw_device; /* S/390 ccw device */ + u8 fc_service_class; + u32 fc_topology; /* FC topology */ + u32 fc_link_speed; /* FC interface speed */ + u32 hydra_version; /* Hydra version */ + u32 fsf_lic_version; + u32 supported_features;/* of FCP channel */ + u32 hardware_version; /* of FCP channel */ + u8 serial_number[32]; /* of hardware */ + struct Scsi_Host *scsi_host; /* Pointer to mid-layer */ + unsigned short scsi_host_no; /* Assigned host number */ + unsigned char name[9]; + struct list_head port_list_head; /* remote port list */ + struct list_head port_remove_lh; /* head of ports to be + removed */ + u32 ports; /* number of remote ports */ + struct timer_list scsi_er_timer; /* SCSI err recovery watch */ + struct list_head fsf_req_list_head; /* head of FSF req list */ + rwlock_t fsf_req_list_lock; /* lock for ops on list of + FSF requests */ + atomic_t fsf_reqs_active; /* # active FSF reqs */ + struct zfcp_qdio_queue request_queue; /* request queue */ + u32 fsf_req_seq_no; /* FSF cmnd seq number */ + wait_queue_head_t request_wq; /* can be used to wait for + more avaliable SBALs */ + struct zfcp_qdio_queue response_queue; /* response queue */ + rwlock_t abort_lock; /* Protects against SCSI + stack abort/command + completion races */ + u16 status_read_failed; /* # failed status reads */ + atomic_t status; /* status of this adapter */ + struct list_head erp_ready_head; /* error recovery for this + adapter/devices */ + struct list_head erp_running_head; + rwlock_t erp_lock; + struct semaphore erp_ready_sem; + wait_queue_head_t erp_thread_wqh; + wait_queue_head_t erp_done_wqh; + struct zfcp_erp_action erp_action; /* pending error recovery */ + atomic_t erp_counter; + u32 erp_total_count; /* total nr of enqueued erp + actions */ + u32 erp_low_mem_count; /* nr of erp actions waiting + for memory */ + struct zfcp_port *nameserver_port; /* adapter's nameserver */ + debug_info_t *erp_dbf; /* S/390 debug features */ + debug_info_t *abort_dbf; + debug_info_t *in_els_dbf; + debug_info_t *cmd_dbf; + spinlock_t dbf_lock; + struct zfcp_adapter_mempool pool; /* Adapter memory pools */ + struct qdio_initialize qdio_init_data; /* for qdio_establish */ + struct device generic_services; /* directory for WKA ports */ +}; + +/* + * the struct device sysfs_device must be at the beginning of this structure. + * pointer to struct device is used to free port structure in release function + * of the device. don't change! + */ +struct zfcp_port { + struct device sysfs_device; /* sysfs device */ + struct list_head list; /* list of remote ports */ + atomic_t refcount; /* reference count */ + wait_queue_head_t remove_wq; /* can be used to wait for + refcount drop to zero */ + struct zfcp_adapter *adapter; /* adapter used to access port */ + struct list_head unit_list_head; /* head of logical unit list */ + struct list_head unit_remove_lh; /* head of luns to be removed + list */ + u32 units; /* # of logical units in list */ + atomic_t status; /* status of this remote port */ + scsi_id_t scsi_id; /* own SCSI ID */ + wwn_t wwnn; /* WWNN if known */ + wwn_t wwpn; /* WWPN */ + fc_id_t d_id; /* D_ID */ + u32 handle; /* handle assigned by FSF */ + struct zfcp_erp_action erp_action; /* pending error recovery */ + atomic_t erp_counter; +}; + +/* the struct device sysfs_device must be at the beginning of this structure. + * pointer to struct device is used to free unit structure in release function + * of the device. don't change! + */ +struct zfcp_unit { + struct device sysfs_device; /* sysfs device */ + struct list_head list; /* list of logical units */ + atomic_t refcount; /* reference count */ + wait_queue_head_t remove_wq; /* can be used to wait for + refcount drop to zero */ + struct zfcp_port *port; /* remote port of unit */ + atomic_t status; /* status of this logical unit */ + scsi_lun_t scsi_lun; /* own SCSI LUN */ + fcp_lun_t fcp_lun; /* own FCP_LUN */ + u32 handle; /* handle assigned by FSF */ + struct scsi_device *device; /* scsi device struct pointer */ + struct zfcp_erp_action erp_action; /* pending error recovery */ + atomic_t erp_counter; +}; + +/* FSF request */ +struct zfcp_fsf_req { + struct list_head list; /* list of FSF requests */ + struct zfcp_adapter *adapter; /* adapter request belongs to */ + u8 sbal_number; /* nr of SBALs free for use */ + u8 sbal_first; /* first SBAL for this request */ + u8 sbal_last; /* last possible SBAL for + this reuest */ + u8 sbal_curr; /* current SBAL during creation + of request */ + u8 sbale_curr; /* current SBALE during creation + of request */ + wait_queue_head_t completion_wq; /* can be used by a routine + to wait for completion */ + volatile u32 status; /* status of this request */ + u32 fsf_command; /* FSF Command copy */ + struct fsf_qtcb *qtcb; /* address of associated QTCB */ + u32 seq_no; /* Sequence number of request */ + union zfcp_req_data data; /* Info fields of request */ + struct zfcp_erp_action *erp_action; /* used if this request is + issued on behalf of erp */ + mempool_t *pool; /* used if request was alloacted + from emergency pool */ +}; + +typedef void zfcp_fsf_req_handler_t(struct zfcp_fsf_req*); + +/* driver data */ +struct zfcp_data { + struct scsi_host_template scsi_host_template; + atomic_t status; /* Module status flags */ + struct list_head adapter_list_head; /* head of adapter list */ + struct list_head adapter_remove_lh; /* head of adapters to be + removed */ + rwlock_t status_read_lock; /* for status read thread */ + struct list_head status_read_receive_head; + struct list_head status_read_send_head; + struct semaphore status_read_sema; + wait_queue_head_t status_read_thread_wqh; + u32 adapters; /* # of adapters in list */ + rwlock_t config_lock; /* serialises changes + to adapter/port/unit + lists */ + struct semaphore config_sema; /* serialises configuration + changes */ + atomic_t loglevel; /* current loglevel */ + char init_busid[BUS_ID_SIZE]; + wwn_t init_wwpn; + fcp_lun_t init_fcp_lun; + char *driver_version; +}; + +/** + * struct zfcp_sg_list - struct describing a scatter-gather list + * @sg: pointer to array of (struct scatterlist) + * @count: number of elements in scatter-gather list + */ +struct zfcp_sg_list { + struct scatterlist *sg; + unsigned int count; +}; + +/* number of elements for various memory pools */ +#define ZFCP_POOL_FSF_REQ_ERP_NR 1 +#define ZFCP_POOL_FSF_REQ_SCSI_NR 1 +#define ZFCP_POOL_FSF_REQ_ABORT_NR 1 +#define ZFCP_POOL_STATUS_READ_NR ZFCP_STATUS_READS_RECOM +#define ZFCP_POOL_DATA_GID_PN_NR 1 + +/* struct used by memory pools for fsf_requests */ +struct zfcp_fsf_req_pool_element { + struct zfcp_fsf_req fsf_req; + struct fsf_qtcb qtcb; +}; + +/********************** ZFCP SPECIFIC DEFINES ********************************/ + +#define ZFCP_FSFREQ_CLEANUP_TIMEOUT HZ/10 + +#define ZFCP_KNOWN 0x00000001 +#define ZFCP_REQ_AUTO_CLEANUP 0x00000002 +#define ZFCP_WAIT_FOR_SBAL 0x00000004 +#define ZFCP_REQ_NO_QTCB 0x00000008 + +#define ZFCP_SET 0x00000100 +#define ZFCP_CLEAR 0x00000200 + +#define ZFCP_INTERRUPTIBLE 1 +#define ZFCP_UNINTERRUPTIBLE 0 + +#ifndef atomic_test_mask +#define atomic_test_mask(mask, target) \ + ((atomic_read(target) & mask) == mask) +#endif + +extern void _zfcp_hex_dump(char *, int); +#define ZFCP_HEX_DUMP(level, addr, count) \ + if (ZFCP_LOG_CHECK(level)) { \ + _zfcp_hex_dump(addr, count); \ + } + +#define zfcp_get_busid_by_adapter(adapter) (adapter->ccw_device->dev.bus_id) +#define zfcp_get_busid_by_port(port) (zfcp_get_busid_by_adapter(port->adapter)) +#define zfcp_get_busid_by_unit(unit) (zfcp_get_busid_by_port(unit->port)) + +/* + * functions needed for reference/usage counting + */ + +static inline void +zfcp_unit_get(struct zfcp_unit *unit) +{ + atomic_inc(&unit->refcount); +} + +static inline void +zfcp_unit_put(struct zfcp_unit *unit) +{ + if (atomic_dec_return(&unit->refcount) == 0) + wake_up(&unit->remove_wq); +} + +static inline void +zfcp_unit_wait(struct zfcp_unit *unit) +{ + wait_event(unit->remove_wq, atomic_read(&unit->refcount) == 0); +} + +static inline void +zfcp_port_get(struct zfcp_port *port) +{ + atomic_inc(&port->refcount); +} + +static inline void +zfcp_port_put(struct zfcp_port *port) +{ + if (atomic_dec_return(&port->refcount) == 0) + wake_up(&port->remove_wq); +} + +static inline void +zfcp_port_wait(struct zfcp_port *port) +{ + wait_event(port->remove_wq, atomic_read(&port->refcount) == 0); +} + +static inline void +zfcp_adapter_get(struct zfcp_adapter *adapter) +{ + atomic_inc(&adapter->refcount); +} + +static inline void +zfcp_adapter_put(struct zfcp_adapter *adapter) +{ + if (atomic_dec_return(&adapter->refcount) == 0) + wake_up(&adapter->remove_wq); +} + +static inline void +zfcp_adapter_wait(struct zfcp_adapter *adapter) +{ + wait_event(adapter->remove_wq, atomic_read(&adapter->refcount) == 0); +} + +#endif /* ZFCP_DEF_H */ diff --git a/drivers/s390/scsi/zfcp_erp.c b/drivers/s390/scsi/zfcp_erp.c new file mode 100644 index 000000000000..cfc0d8c588df --- /dev/null +++ b/drivers/s390/scsi/zfcp_erp.c @@ -0,0 +1,3585 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_erp.c + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_ERP + +#define ZFCP_ERP_REVISION "$Revision: 1.86 $" + +#include "zfcp_ext.h" + +static int zfcp_erp_adisc(struct zfcp_adapter *, fc_id_t); +static void zfcp_erp_adisc_handler(unsigned long); + +static int zfcp_erp_adapter_reopen_internal(struct zfcp_adapter *, int); +static int zfcp_erp_port_forced_reopen_internal(struct zfcp_port *, int); +static int zfcp_erp_port_reopen_internal(struct zfcp_port *, int); +static int zfcp_erp_unit_reopen_internal(struct zfcp_unit *, int); + +static int zfcp_erp_port_reopen_all_internal(struct zfcp_adapter *, int); +static int zfcp_erp_unit_reopen_all_internal(struct zfcp_port *, int); + +static void zfcp_erp_adapter_block(struct zfcp_adapter *, int); +static void zfcp_erp_adapter_unblock(struct zfcp_adapter *); +static void zfcp_erp_port_block(struct zfcp_port *, int); +static void zfcp_erp_port_unblock(struct zfcp_port *); +static void zfcp_erp_unit_block(struct zfcp_unit *, int); +static void zfcp_erp_unit_unblock(struct zfcp_unit *); + +static int zfcp_erp_thread(void *); + +static int zfcp_erp_strategy(struct zfcp_erp_action *); + +static int zfcp_erp_strategy_do_action(struct zfcp_erp_action *); +static int zfcp_erp_strategy_memwait(struct zfcp_erp_action *); +static int zfcp_erp_strategy_check_target(struct zfcp_erp_action *, int); +static int zfcp_erp_strategy_check_unit(struct zfcp_unit *, int); +static int zfcp_erp_strategy_check_port(struct zfcp_port *, int); +static int zfcp_erp_strategy_check_adapter(struct zfcp_adapter *, int); +static int zfcp_erp_strategy_statechange(int, u32, struct zfcp_adapter *, + struct zfcp_port *, + struct zfcp_unit *, int); +static inline int zfcp_erp_strategy_statechange_detected(atomic_t *, u32); +static int zfcp_erp_strategy_followup_actions(int, struct zfcp_adapter *, + struct zfcp_port *, + struct zfcp_unit *, int); +static int zfcp_erp_strategy_check_queues(struct zfcp_adapter *); +static int zfcp_erp_strategy_check_action(struct zfcp_erp_action *, int); + +static int zfcp_erp_adapter_strategy(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_generic(struct zfcp_erp_action *, int); +static int zfcp_erp_adapter_strategy_close(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_close_qdio(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_close_fsf(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_open(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_open_qdio(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_open_fsf(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *); +static int zfcp_erp_adapter_strategy_open_fsf_statusread( + struct zfcp_erp_action *); + +static int zfcp_erp_port_forced_strategy(struct zfcp_erp_action *); +static int zfcp_erp_port_forced_strategy_close(struct zfcp_erp_action *); + +static int zfcp_erp_port_strategy(struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_clearstati(struct zfcp_port *); +static int zfcp_erp_port_strategy_close(struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_open(struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_open_nameserver(struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_open_nameserver_wakeup( + struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_open_common(struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_open_common_lookup(struct zfcp_erp_action *); +static int zfcp_erp_port_strategy_open_port(struct zfcp_erp_action *); + +static int zfcp_erp_unit_strategy(struct zfcp_erp_action *); +static int zfcp_erp_unit_strategy_clearstati(struct zfcp_unit *); +static int zfcp_erp_unit_strategy_close(struct zfcp_erp_action *); +static int zfcp_erp_unit_strategy_open(struct zfcp_erp_action *); + +static int zfcp_erp_action_dismiss_adapter(struct zfcp_adapter *); +static int zfcp_erp_action_dismiss_port(struct zfcp_port *); +static int zfcp_erp_action_dismiss_unit(struct zfcp_unit *); +static int zfcp_erp_action_dismiss(struct zfcp_erp_action *); + +static int zfcp_erp_action_enqueue(int, struct zfcp_adapter *, + struct zfcp_port *, struct zfcp_unit *); +static int zfcp_erp_action_dequeue(struct zfcp_erp_action *); +static void zfcp_erp_action_cleanup(int, struct zfcp_adapter *, + struct zfcp_port *, struct zfcp_unit *, + int); + +static void zfcp_erp_action_ready(struct zfcp_erp_action *); +static int zfcp_erp_action_exists(struct zfcp_erp_action *); + +static inline void zfcp_erp_action_to_ready(struct zfcp_erp_action *); +static inline void zfcp_erp_action_to_running(struct zfcp_erp_action *); + +static void zfcp_erp_memwait_handler(unsigned long); +static void zfcp_erp_timeout_handler(unsigned long); +static inline void zfcp_erp_timeout_init(struct zfcp_erp_action *); + +/** + * zfcp_fsf_request_timeout_handler - called if a request timed out + * @data: pointer to adapter for handler function + * + * This function needs to be called if requests (ELS, Generic Service, + * or SCSI commands) exceed a certain time limit. The assumption is + * that after the time limit the adapter get stuck. So we trigger a reopen of + * the adapter. This should not be used for error recovery, SCSI abort + * commands and SCSI requests from SCSI mid-layer. + */ +void +zfcp_fsf_request_timeout_handler(unsigned long data) +{ + struct zfcp_adapter *adapter; + + adapter = (struct zfcp_adapter *) data; + + zfcp_erp_adapter_reopen(adapter, 0); +} + +/* + * function: zfcp_fsf_scsi_er_timeout_handler + * + * purpose: This function needs to be called whenever a SCSI error recovery + * action (abort/reset) does not return. + * Re-opening the adapter means that the command can be returned + * by zfcp (it is guarranteed that it does not return via the + * adapter anymore). The buffer can then be used again. + * + * returns: sod all + */ +void +zfcp_fsf_scsi_er_timeout_handler(unsigned long data) +{ + struct zfcp_adapter *adapter = (struct zfcp_adapter *) data; + + ZFCP_LOG_NORMAL("warning: SCSI error recovery timed out. " + "Restarting all operations on the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 1, "eh_lmem_tout"); + zfcp_erp_adapter_reopen(adapter, 0); + + return; +} + +/* + * function: + * + * purpose: called if an adapter failed, + * initiates adapter recovery which is done + * asynchronously + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +int +zfcp_erp_adapter_reopen_internal(struct zfcp_adapter *adapter, int clear_mask) +{ + int retval; + + debug_text_event(adapter->erp_dbf, 5, "a_ro"); + ZFCP_LOG_DEBUG("reopen adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + + zfcp_erp_adapter_block(adapter, clear_mask); + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) { + ZFCP_LOG_DEBUG("skipped reopen of failed adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 5, "a_ro_f"); + /* ensure propagation of failed status to new devices */ + zfcp_erp_adapter_failed(adapter); + retval = -EIO; + goto out; + } + retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_ADAPTER, + adapter, NULL, NULL); + + out: + return retval; +} + +/* + * function: + * + * purpose: Wrappper for zfcp_erp_adapter_reopen_internal + * used to ensure the correct locking + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +int +zfcp_erp_adapter_reopen(struct zfcp_adapter *adapter, int clear_mask) +{ + int retval; + unsigned long flags; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + retval = zfcp_erp_adapter_reopen_internal(adapter, clear_mask); + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +int +zfcp_erp_adapter_shutdown(struct zfcp_adapter *adapter, int clear_mask) +{ + int retval; + + retval = zfcp_erp_adapter_reopen(adapter, + ZFCP_STATUS_COMMON_RUNNING | + ZFCP_STATUS_COMMON_ERP_FAILED | + clear_mask); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +int +zfcp_erp_port_shutdown(struct zfcp_port *port, int clear_mask) +{ + int retval; + + retval = zfcp_erp_port_reopen(port, + ZFCP_STATUS_COMMON_RUNNING | + ZFCP_STATUS_COMMON_ERP_FAILED | + clear_mask); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +int +zfcp_erp_unit_shutdown(struct zfcp_unit *unit, int clear_mask) +{ + int retval; + + retval = zfcp_erp_unit_reopen(unit, + ZFCP_STATUS_COMMON_RUNNING | + ZFCP_STATUS_COMMON_ERP_FAILED | + clear_mask); + + return retval; +} + + +/** + * zfcp_erp_adisc - send ADISC ELS command + * @adapter: adapter structure + * @d_id: d_id of port where ADISC is sent to + */ +int +zfcp_erp_adisc(struct zfcp_adapter *adapter, fc_id_t d_id) +{ + struct zfcp_send_els *send_els; + struct zfcp_ls_adisc *adisc; + void *address = NULL; + int retval = 0; + struct timer_list *timer; + + send_els = kmalloc(sizeof(struct zfcp_send_els), GFP_ATOMIC); + if (send_els == NULL) + goto nomem; + memset(send_els, 0, sizeof(*send_els)); + + send_els->req = kmalloc(sizeof(struct scatterlist), GFP_ATOMIC); + if (send_els->req == NULL) + goto nomem; + memset(send_els->req, 0, sizeof(*send_els->req)); + + send_els->resp = kmalloc(sizeof(struct scatterlist), GFP_ATOMIC); + if (send_els->resp == NULL) + goto nomem; + memset(send_els->resp, 0, sizeof(*send_els->resp)); + + address = (void *) get_zeroed_page(GFP_ATOMIC); + if (address == NULL) + goto nomem; + + zfcp_address_to_sg(address, send_els->req); + address += PAGE_SIZE >> 1; + zfcp_address_to_sg(address, send_els->resp); + send_els->req_count = send_els->resp_count = 1; + + send_els->adapter = adapter; + send_els->d_id = d_id; + send_els->handler = zfcp_erp_adisc_handler; + send_els->handler_data = (unsigned long) send_els; + + adisc = zfcp_sg_to_address(send_els->req); + send_els->ls_code = adisc->code = ZFCP_LS_ADISC; + + send_els->req->length = sizeof(struct zfcp_ls_adisc); + send_els->resp->length = sizeof(struct zfcp_ls_adisc_acc); + + /* acc. to FC-FS, hard_nport_id in ADISC should not be set for ports + without FC-AL-2 capability, so we don't set it */ + adisc->wwpn = adapter->wwpn; + adisc->wwnn = adapter->wwnn; + adisc->nport_id = adapter->s_id; + ZFCP_LOG_INFO("ADISC request from s_id 0x%08x to d_id 0x%08x " + "(wwpn=0x%016Lx, wwnn=0x%016Lx, " + "hard_nport_id=0x%08x, nport_id=0x%08x)\n", + adapter->s_id, d_id, (wwn_t) adisc->wwpn, + (wwn_t) adisc->wwnn, adisc->hard_nport_id, + adisc->nport_id); + + timer = kmalloc(sizeof(struct timer_list), GFP_ATOMIC); + if (!timer) + goto nomem; + + init_timer(timer); + timer->function = zfcp_fsf_request_timeout_handler; + timer->data = (unsigned long) adapter; + timer->expires = ZFCP_FSF_REQUEST_TIMEOUT; + send_els->timer = timer; + + retval = zfcp_fsf_send_els(send_els); + if (retval != 0) { + ZFCP_LOG_NORMAL("error: initiation of Send ELS failed for port " + "0x%08x on adapter %s\n", d_id, + zfcp_get_busid_by_adapter(adapter)); + del_timer(send_els->timer); + goto freemem; + } + + goto out; + + nomem: + retval = -ENOMEM; + freemem: + if (address != NULL) + __free_pages(send_els->req->page, 0); + if (send_els != NULL) { + kfree(send_els->timer); + kfree(send_els->req); + kfree(send_els->resp); + kfree(send_els); + } + out: + return retval; +} + + +/** + * zfcp_erp_adisc_handler - handler for ADISC ELS command + * @data: pointer to struct zfcp_send_els + * + * If ADISC failed (LS_RJT or timed out) forced reopen of the port is triggered. + */ +void +zfcp_erp_adisc_handler(unsigned long data) +{ + struct zfcp_send_els *send_els; + struct zfcp_port *port; + struct zfcp_adapter *adapter; + fc_id_t d_id; + struct zfcp_ls_adisc_acc *adisc; + + send_els = (struct zfcp_send_els *) data; + + del_timer(send_els->timer); + + adapter = send_els->adapter; + d_id = send_els->d_id; + + read_lock(&zfcp_data.config_lock); + port = zfcp_get_port_by_did(send_els->adapter, send_els->d_id); + read_unlock(&zfcp_data.config_lock); + + BUG_ON(port == NULL); + + /* request rejected or timed out */ + if (send_els->status != 0) { + ZFCP_LOG_NORMAL("ELS request rejected/timed out, " + "force physical port reopen " + "(adapter %s, port d_id=0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + debug_text_event(adapter->erp_dbf, 3, "forcreop"); + if (zfcp_erp_port_forced_reopen(port, 0)) + ZFCP_LOG_NORMAL("failed reopen of port " + "(adapter %s, wwpn=0x%016Lx)\n", + zfcp_get_busid_by_port(port), + port->wwpn); + goto out; + } + + adisc = zfcp_sg_to_address(send_els->resp); + + ZFCP_LOG_INFO("ADISC response from d_id 0x%08x to s_id " + "0x%08x (wwpn=0x%016Lx, wwnn=0x%016Lx, " + "hard_nport_id=0x%08x, nport_id=0x%08x)\n", + d_id, adapter->s_id, (wwn_t) adisc->wwpn, + (wwn_t) adisc->wwnn, adisc->hard_nport_id, + adisc->nport_id); + + /* set wwnn for port */ + if (port->wwnn == 0) + port->wwnn = adisc->wwnn; + + if (port->wwpn != adisc->wwpn) { + ZFCP_LOG_NORMAL("d_id assignment changed, reopening " + "port (adapter %s, wwpn=0x%016Lx, " + "adisc_resp_wwpn=0x%016Lx)\n", + zfcp_get_busid_by_port(port), + port->wwpn, (wwn_t) adisc->wwpn); + if (zfcp_erp_port_reopen(port, 0)) + ZFCP_LOG_NORMAL("failed reopen of port " + "(adapter %s, wwpn=0x%016Lx)\n", + zfcp_get_busid_by_port(port), + port->wwpn); + } + + out: + zfcp_port_put(port); + __free_pages(send_els->req->page, 0); + kfree(send_els->timer); + kfree(send_els->req); + kfree(send_els->resp); + kfree(send_els); +} + + +/** + * zfcp_test_link - lightweight link test procedure + * @port: port to be tested + * + * Test status of a link to a remote port using the ELS command ADISC. + */ +int +zfcp_test_link(struct zfcp_port *port) +{ + int retval; + + zfcp_port_get(port); + retval = zfcp_erp_adisc(port->adapter, port->d_id); + if (retval != 0) { + zfcp_port_put(port); + ZFCP_LOG_NORMAL("reopen needed for port 0x%016Lx " + "on adapter %s\n ", port->wwpn, + zfcp_get_busid_by_port(port)); + retval = zfcp_erp_port_forced_reopen(port, 0); + if (retval != 0) { + ZFCP_LOG_NORMAL("reopen of remote port 0x%016Lx " + "on adapter %s failed\n", port->wwpn, + zfcp_get_busid_by_port(port)); + retval = -EPERM; + } + } + + return retval; +} + + +/* + * function: + * + * purpose: called if a port failed to be opened normally + * initiates Forced Reopen recovery which is done + * asynchronously + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +static int +zfcp_erp_port_forced_reopen_internal(struct zfcp_port *port, int clear_mask) +{ + int retval; + struct zfcp_adapter *adapter = port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "pf_ro"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + + ZFCP_LOG_DEBUG("forced reopen of port 0x%016Lx on adapter %s\n", + port->wwpn, zfcp_get_busid_by_port(port)); + + zfcp_erp_port_block(port, clear_mask); + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) { + ZFCP_LOG_DEBUG("skipped forced reopen of failed port 0x%016Lx " + "on adapter %s\n", port->wwpn, + zfcp_get_busid_by_port(port)); + debug_text_event(adapter->erp_dbf, 5, "pf_ro_f"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + retval = -EIO; + goto out; + } + + retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_PORT_FORCED, + port->adapter, port, NULL); + + out: + return retval; +} + +/* + * function: + * + * purpose: Wrappper for zfcp_erp_port_forced_reopen_internal + * used to ensure the correct locking + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +int +zfcp_erp_port_forced_reopen(struct zfcp_port *port, int clear_mask) +{ + int retval; + unsigned long flags; + struct zfcp_adapter *adapter; + + adapter = port->adapter; + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + retval = zfcp_erp_port_forced_reopen_internal(port, clear_mask); + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + return retval; +} + +/* + * function: + * + * purpose: called if a port is to be opened + * initiates Reopen recovery which is done + * asynchronously + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +static int +zfcp_erp_port_reopen_internal(struct zfcp_port *port, int clear_mask) +{ + int retval; + struct zfcp_adapter *adapter = port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "p_ro"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + + ZFCP_LOG_DEBUG("reopen of port 0x%016Lx on adapter %s\n", + port->wwpn, zfcp_get_busid_by_port(port)); + + zfcp_erp_port_block(port, clear_mask); + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) { + ZFCP_LOG_DEBUG("skipped reopen of failed port 0x%016Lx " + "on adapter %s\n", port->wwpn, + zfcp_get_busid_by_port(port)); + debug_text_event(adapter->erp_dbf, 5, "p_ro_f"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + /* ensure propagation of failed status to new devices */ + zfcp_erp_port_failed(port); + retval = -EIO; + goto out; + } + + retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_PORT, + port->adapter, port, NULL); + + out: + return retval; +} + +/** + * zfcp_erp_port_reopen - initiate reopen of a remote port + * @port: port to be reopened + * @clear_mask: specifies flags in port status to be cleared + * Return: 0 on success, < 0 on error + * + * This is a wrappper function for zfcp_erp_port_reopen_internal. It ensures + * correct locking. An error recovery task is initiated to do the reopen. + * To wait for the completion of the reopen zfcp_erp_wait should be used. + */ +int +zfcp_erp_port_reopen(struct zfcp_port *port, int clear_mask) +{ + int retval; + unsigned long flags; + struct zfcp_adapter *adapter = port->adapter; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + retval = zfcp_erp_port_reopen_internal(port, clear_mask); + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + return retval; +} + +/* + * function: + * + * purpose: called if a unit is to be opened + * initiates Reopen recovery which is done + * asynchronously + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +static int +zfcp_erp_unit_reopen_internal(struct zfcp_unit *unit, int clear_mask) +{ + int retval; + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "u_ro"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, sizeof (fcp_lun_t)); + ZFCP_LOG_DEBUG("reopen of unit 0x%016Lx on port 0x%016Lx " + "on adapter %s\n", unit->fcp_lun, + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + + zfcp_erp_unit_block(unit, clear_mask); + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status)) { + ZFCP_LOG_DEBUG("skipped reopen of failed unit 0x%016Lx " + "on port 0x%016Lx on adapter %s\n", + unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + debug_text_event(adapter->erp_dbf, 5, "u_ro_f"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, + sizeof (fcp_lun_t)); + retval = -EIO; + goto out; + } + + retval = zfcp_erp_action_enqueue(ZFCP_ERP_ACTION_REOPEN_UNIT, + unit->port->adapter, unit->port, unit); + out: + return retval; +} + +/** + * zfcp_erp_unit_reopen - initiate reopen of a unit + * @unit: unit to be reopened + * @clear_mask: specifies flags in unit status to be cleared + * Return: 0 on success, < 0 on error + * + * This is a wrappper for zfcp_erp_unit_reopen_internal. It ensures correct + * locking. An error recovery task is initiated to do the reopen. + * To wait for the completion of the reopen zfcp_erp_wait should be used. + */ +int +zfcp_erp_unit_reopen(struct zfcp_unit *unit, int clear_mask) +{ + int retval; + unsigned long flags; + struct zfcp_adapter *adapter; + struct zfcp_port *port; + + port = unit->port; + adapter = port->adapter; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + retval = zfcp_erp_unit_reopen_internal(unit, clear_mask); + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + return retval; +} + +/* + * function: + * + * purpose: disable I/O, + * return any open requests and clean them up, + * aim: no pending and incoming I/O + * + * returns: + */ +static void +zfcp_erp_adapter_block(struct zfcp_adapter *adapter, int clear_mask) +{ + debug_text_event(adapter->erp_dbf, 6, "a_bl"); + zfcp_erp_modify_adapter_status(adapter, + ZFCP_STATUS_COMMON_UNBLOCKED | + clear_mask, ZFCP_CLEAR); +} + +/* + * function: + * + * purpose: enable I/O + * + * returns: + */ +static void +zfcp_erp_adapter_unblock(struct zfcp_adapter *adapter) +{ + debug_text_event(adapter->erp_dbf, 6, "a_ubl"); + atomic_set_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &adapter->status); +} + +/* + * function: + * + * purpose: disable I/O, + * return any open requests and clean them up, + * aim: no pending and incoming I/O + * + * returns: + */ +static void +zfcp_erp_port_block(struct zfcp_port *port, int clear_mask) +{ + struct zfcp_adapter *adapter = port->adapter; + + debug_text_event(adapter->erp_dbf, 6, "p_bl"); + debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t)); + zfcp_erp_modify_port_status(port, + ZFCP_STATUS_COMMON_UNBLOCKED | clear_mask, + ZFCP_CLEAR); +} + +/* + * function: + * + * purpose: enable I/O + * + * returns: + */ +static void +zfcp_erp_port_unblock(struct zfcp_port *port) +{ + struct zfcp_adapter *adapter = port->adapter; + + debug_text_event(adapter->erp_dbf, 6, "p_ubl"); + debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t)); + atomic_set_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &port->status); +} + +/* + * function: + * + * purpose: disable I/O, + * return any open requests and clean them up, + * aim: no pending and incoming I/O + * + * returns: + */ +static void +zfcp_erp_unit_block(struct zfcp_unit *unit, int clear_mask) +{ + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 6, "u_bl"); + debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t)); + zfcp_erp_modify_unit_status(unit, + ZFCP_STATUS_COMMON_UNBLOCKED | clear_mask, + ZFCP_CLEAR); +} + +/* + * function: + * + * purpose: enable I/O + * + * returns: + */ +static void +zfcp_erp_unit_unblock(struct zfcp_unit *unit) +{ + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 6, "u_ubl"); + debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t)); + atomic_set_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &unit->status); +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static void +zfcp_erp_action_ready(struct zfcp_erp_action *erp_action) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 4, "a_ar"); + debug_event(adapter->erp_dbf, 4, &erp_action->action, sizeof (int)); + + zfcp_erp_action_to_ready(erp_action); + up(&adapter->erp_ready_sem); +} + +/* + * function: + * + * purpose: + * + * returns: <0 erp_action not found in any list + * ZFCP_ERP_ACTION_READY erp_action is in ready list + * ZFCP_ERP_ACTION_RUNNING erp_action is in running list + * + * locks: erp_lock must be held + */ +static int +zfcp_erp_action_exists(struct zfcp_erp_action *erp_action) +{ + int retval = -EINVAL; + struct list_head *entry; + struct zfcp_erp_action *entry_erp_action; + struct zfcp_adapter *adapter = erp_action->adapter; + + /* search in running list */ + list_for_each(entry, &adapter->erp_running_head) { + entry_erp_action = + list_entry(entry, struct zfcp_erp_action, list); + if (entry_erp_action == erp_action) { + retval = ZFCP_ERP_ACTION_RUNNING; + goto out; + } + } + /* search in ready list */ + list_for_each(entry, &adapter->erp_ready_head) { + entry_erp_action = + list_entry(entry, struct zfcp_erp_action, list); + if (entry_erp_action == erp_action) { + retval = ZFCP_ERP_ACTION_READY; + goto out; + } + } + + out: + return retval; +} + +/* + * purpose: checks current status of action (timed out, dismissed, ...) + * and does appropriate preparations (dismiss fsf request, ...) + * + * locks: called under erp_lock (disabled interrupts) + * + * returns: 0 + */ +static int +zfcp_erp_strategy_check_fsfreq(struct zfcp_erp_action *erp_action) +{ + int retval = 0; + struct zfcp_fsf_req *fsf_req; + struct zfcp_adapter *adapter = erp_action->adapter; + + if (erp_action->fsf_req) { + /* take lock to ensure that request is not being deleted meanwhile */ + write_lock(&adapter->fsf_req_list_lock); + /* check whether fsf req does still exist */ + list_for_each_entry(fsf_req, &adapter->fsf_req_list_head, list) + if (fsf_req == erp_action->fsf_req) + break; + if (fsf_req == erp_action->fsf_req) { + /* fsf_req still exists */ + debug_text_event(adapter->erp_dbf, 3, "a_ca_req"); + debug_event(adapter->erp_dbf, 3, &fsf_req, + sizeof (unsigned long)); + /* dismiss fsf_req of timed out or dismissed erp_action */ + if (erp_action->status & (ZFCP_STATUS_ERP_DISMISSED | + ZFCP_STATUS_ERP_TIMEDOUT)) { + debug_text_event(adapter->erp_dbf, 3, + "a_ca_disreq"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_DISMISSED; + } + if (erp_action->status & ZFCP_STATUS_ERP_TIMEDOUT) { + ZFCP_LOG_NORMAL("error: erp step timed out " + "(action=%d, fsf_req=%p)\n ", + erp_action->action, + erp_action->fsf_req); + } + /* + * If fsf_req is neither dismissed nor completed + * then keep it running asynchronously and don't mess + * with the association of erp_action and fsf_req. + */ + if (fsf_req->status & (ZFCP_STATUS_FSFREQ_COMPLETED | + ZFCP_STATUS_FSFREQ_DISMISSED)) { + /* forget about association between fsf_req + and erp_action */ + fsf_req->erp_action = NULL; + erp_action->fsf_req = NULL; + } + } else { + debug_text_event(adapter->erp_dbf, 3, "a_ca_gonereq"); + /* + * even if this fsf_req has gone, forget about + * association between erp_action and fsf_req + */ + erp_action->fsf_req = NULL; + } + write_unlock(&adapter->fsf_req_list_lock); + } else + debug_text_event(adapter->erp_dbf, 3, "a_ca_noreq"); + + return retval; +} + +/* + * purpose: generic handler for asynchronous events related to erp_action events + * (normal completion, time-out, dismissing, retry after + * low memory condition) + * + * note: deletion of timer is not required (e.g. in case of a time-out), + * but a second try does no harm, + * we leave it in here to allow for greater simplification + * + * returns: 0 - there was an action to handle + * !0 - otherwise + */ +static int +zfcp_erp_async_handler_nolock(struct zfcp_erp_action *erp_action, + unsigned long set_mask) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + + if (zfcp_erp_action_exists(erp_action) == ZFCP_ERP_ACTION_RUNNING) { + debug_text_event(adapter->erp_dbf, 2, "a_asyh_ex"); + debug_event(adapter->erp_dbf, 2, &erp_action->action, + sizeof (int)); + if (!(set_mask & ZFCP_STATUS_ERP_TIMEDOUT)) + del_timer(&erp_action->timer); + erp_action->status |= set_mask; + zfcp_erp_action_ready(erp_action); + retval = 0; + } else { + /* action is ready or gone - nothing to do */ + debug_text_event(adapter->erp_dbf, 3, "a_asyh_gone"); + debug_event(adapter->erp_dbf, 3, &erp_action->action, + sizeof (int)); + retval = 1; + } + + return retval; +} + +/* + * purpose: generic handler for asynchronous events related to erp_action + * events (normal completion, time-out, dismissing, retry after + * low memory condition) + * + * note: deletion of timer is not required (e.g. in case of a time-out), + * but a second try does no harm, + * we leave it in here to allow for greater simplification + * + * returns: 0 - there was an action to handle + * !0 - otherwise + */ +int +zfcp_erp_async_handler(struct zfcp_erp_action *erp_action, + unsigned long set_mask) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + unsigned long flags; + int retval; + + write_lock_irqsave(&adapter->erp_lock, flags); + retval = zfcp_erp_async_handler_nolock(erp_action, set_mask); + write_unlock_irqrestore(&adapter->erp_lock, flags); + + return retval; +} + +/* + * purpose: is called for erp_action which was slept waiting for + * memory becoming avaliable, + * will trigger that this action will be continued + */ +static void +zfcp_erp_memwait_handler(unsigned long data) +{ + struct zfcp_erp_action *erp_action = (struct zfcp_erp_action *) data; + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 2, "a_mwh"); + debug_event(adapter->erp_dbf, 2, &erp_action->action, sizeof (int)); + + zfcp_erp_async_handler(erp_action, 0); +} + +/* + * purpose: is called if an asynchronous erp step timed out, + * action gets an appropriate flag and will be processed + * accordingly + */ +static void +zfcp_erp_timeout_handler(unsigned long data) +{ + struct zfcp_erp_action *erp_action = (struct zfcp_erp_action *) data; + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 2, "a_th"); + debug_event(adapter->erp_dbf, 2, &erp_action->action, sizeof (int)); + + zfcp_erp_async_handler(erp_action, ZFCP_STATUS_ERP_TIMEDOUT); +} + +/* + * purpose: is called for an erp_action which needs to be ended + * though not being done, + * this is usually required if an higher is generated, + * action gets an appropriate flag and will be processed + * accordingly + * + * locks: erp_lock held (thus we need to call another handler variant) + */ +static int +zfcp_erp_action_dismiss(struct zfcp_erp_action *erp_action) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 2, "a_adis"); + debug_event(adapter->erp_dbf, 2, &erp_action->action, sizeof (int)); + + zfcp_erp_async_handler_nolock(erp_action, ZFCP_STATUS_ERP_DISMISSED); + + return 0; +} + +int +zfcp_erp_thread_setup(struct zfcp_adapter *adapter) +{ + int retval = 0; + + atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, &adapter->status); + + rwlock_init(&adapter->erp_lock); + INIT_LIST_HEAD(&adapter->erp_ready_head); + INIT_LIST_HEAD(&adapter->erp_running_head); + sema_init(&adapter->erp_ready_sem, 0); + + retval = kernel_thread(zfcp_erp_thread, adapter, SIGCHLD); + if (retval < 0) { + ZFCP_LOG_NORMAL("error: creation of erp thread failed for " + "adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 5, "a_thset_fail"); + } else { + wait_event(adapter->erp_thread_wqh, + atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, + &adapter->status)); + debug_text_event(adapter->erp_dbf, 5, "a_thset_ok"); + } + + return (retval < 0); +} + +/* + * function: + * + * purpose: + * + * returns: + * + * context: process (i.e. proc-fs or rmmod/insmod) + * + * note: The caller of this routine ensures that the specified + * adapter has been shut down and that this operation + * has been completed. Thus, there are no pending erp_actions + * which would need to be handled here. + */ +int +zfcp_erp_thread_kill(struct zfcp_adapter *adapter) +{ + int retval = 0; + + atomic_set_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL, &adapter->status); + up(&adapter->erp_ready_sem); + + wait_event(adapter->erp_thread_wqh, + !atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, + &adapter->status)); + + atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL, + &adapter->status); + + debug_text_event(adapter->erp_dbf, 5, "a_thki_ok"); + + return retval; +} + +/* + * purpose: is run as a kernel thread, + * goes through list of error recovery actions of associated adapter + * and delegates single action to execution + * + * returns: 0 + */ +static int +zfcp_erp_thread(void *data) +{ + struct zfcp_adapter *adapter = (struct zfcp_adapter *) data; + struct list_head *next; + struct zfcp_erp_action *erp_action; + unsigned long flags; + + daemonize("zfcperp%s", zfcp_get_busid_by_adapter(adapter)); + /* Block all signals */ + siginitsetinv(¤t->blocked, 0); + atomic_set_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, &adapter->status); + debug_text_event(adapter->erp_dbf, 5, "a_th_run"); + wake_up(&adapter->erp_thread_wqh); + + while (!atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_KILL, + &adapter->status)) { + + write_lock_irqsave(&adapter->erp_lock, flags); + next = adapter->erp_ready_head.prev; + write_unlock_irqrestore(&adapter->erp_lock, flags); + + if (next != &adapter->erp_ready_head) { + erp_action = + list_entry(next, struct zfcp_erp_action, list); + /* + * process action (incl. [re]moving it + * from 'ready' queue) + */ + zfcp_erp_strategy(erp_action); + } + + /* + * sleep as long as there is nothing to do, i.e. + * no action in 'ready' queue to be processed and + * thread is not to be killed + */ + down_interruptible(&adapter->erp_ready_sem); + debug_text_event(adapter->erp_dbf, 5, "a_th_woken"); + } + + atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, &adapter->status); + debug_text_event(adapter->erp_dbf, 5, "a_th_stop"); + wake_up(&adapter->erp_thread_wqh); + + return 0; +} + +/* + * function: + * + * purpose: drives single error recovery action and schedules higher and + * subordinate actions, if necessary + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_SUCCEEDED - action finished successfully (deqd) + * ZFCP_ERP_FAILED - action finished unsuccessfully (deqd) + * ZFCP_ERP_EXIT - action finished (dequeued), offline + * ZFCP_ERP_DISMISSED - action canceled (dequeued) + */ +static int +zfcp_erp_strategy(struct zfcp_erp_action *erp_action) +{ + int retval = 0; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + struct zfcp_unit *unit = erp_action->unit; + int action = erp_action->action; + u32 status = erp_action->status; + unsigned long flags; + + /* serialise dismissing, timing out, moving, enqueueing */ + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + + /* dequeue dismissed action and leave, if required */ + retval = zfcp_erp_strategy_check_action(erp_action, retval); + if (retval == ZFCP_ERP_DISMISSED) { + debug_text_event(adapter->erp_dbf, 4, "a_st_dis1"); + goto unlock; + } + + /* + * move action to 'running' queue before processing it + * (to avoid a race condition regarding moving the + * action to the 'running' queue and back) + */ + zfcp_erp_action_to_running(erp_action); + + /* + * try to process action as far as possible, + * no lock to allow for blocking operations (kmalloc, qdio, ...), + * afterwards the lock is required again for the following reasons: + * - dequeueing of finished action and enqueueing of + * follow-up actions must be atomic so that any other + * reopen-routine does not believe there is nothing to do + * and that it is safe to enqueue something else, + * - we want to force any control thread which is dismissing + * actions to finish this before we decide about + * necessary steps to be taken here further + */ + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + retval = zfcp_erp_strategy_do_action(erp_action); + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + + /* + * check for dismissed status again to avoid follow-up actions, + * failing of targets and so on for dismissed actions + */ + retval = zfcp_erp_strategy_check_action(erp_action, retval); + + switch (retval) { + case ZFCP_ERP_DISMISSED: + /* leave since this action has ridden to its ancestors */ + debug_text_event(adapter->erp_dbf, 6, "a_st_dis2"); + goto unlock; + case ZFCP_ERP_NOMEM: + /* no memory to continue immediately, let it sleep */ + if (!(erp_action->status & ZFCP_STATUS_ERP_LOWMEM)) { + ++adapter->erp_low_mem_count; + erp_action->status |= ZFCP_STATUS_ERP_LOWMEM; + } + /* This condition is true if there is no memory available + for any erp_action on this adapter. This implies that there + are no elements in the memory pool(s) left for erp_actions. + This might happen if an erp_action that used a memory pool + element was timed out. + */ + if (adapter->erp_total_count == adapter->erp_low_mem_count) { + debug_text_event(adapter->erp_dbf, 3, "a_st_lowmem"); + ZFCP_LOG_NORMAL("error: no mempool elements available, " + "restarting I/O on adapter %s " + "to free mempool\n", + zfcp_get_busid_by_adapter(adapter)); + zfcp_erp_adapter_reopen_internal(adapter, 0); + } else { + debug_text_event(adapter->erp_dbf, 2, "a_st_memw"); + retval = zfcp_erp_strategy_memwait(erp_action); + } + goto unlock; + case ZFCP_ERP_CONTINUES: + /* leave since this action runs asynchronously */ + debug_text_event(adapter->erp_dbf, 6, "a_st_cont"); + if (erp_action->status & ZFCP_STATUS_ERP_LOWMEM) { + --adapter->erp_low_mem_count; + erp_action->status &= ~ZFCP_STATUS_ERP_LOWMEM; + } + goto unlock; + } + /* ok, finished action (whatever its result is) */ + + /* check for unrecoverable targets */ + retval = zfcp_erp_strategy_check_target(erp_action, retval); + + /* action must be dequeued (here to allow for further ones) */ + zfcp_erp_action_dequeue(erp_action); + + /* + * put this target through the erp mill again if someone has + * requested to change the status of a target being online + * to offline or the other way around + * (old retval is preserved if nothing has to be done here) + */ + retval = zfcp_erp_strategy_statechange(action, status, adapter, + port, unit, retval); + + /* + * leave if target is in permanent error state or if + * action is repeated in order to process state change + */ + if (retval == ZFCP_ERP_EXIT) { + debug_text_event(adapter->erp_dbf, 2, "a_st_exit"); + goto unlock; + } + + /* trigger follow up actions */ + zfcp_erp_strategy_followup_actions(action, adapter, port, unit, retval); + + unlock: + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + if (retval != ZFCP_ERP_CONTINUES) + zfcp_erp_action_cleanup(action, adapter, port, unit, retval); + + /* + * a few tasks remain when the erp queues are empty + * (don't do that if the last action evaluated was dismissed + * since this clearly indicates that there is more to come) : + * - close the name server port if it is open yet + * (enqueues another [probably] final action) + * - otherwise, wake up whoever wants to be woken when we are + * done with erp + */ + if (retval != ZFCP_ERP_DISMISSED) + zfcp_erp_strategy_check_queues(adapter); + + debug_text_event(adapter->erp_dbf, 6, "a_st_done"); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_DISMISSED - if action has been dismissed + * retval - otherwise + */ +static int +zfcp_erp_strategy_check_action(struct zfcp_erp_action *erp_action, int retval) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + + zfcp_erp_strategy_check_fsfreq(erp_action); + + debug_event(adapter->erp_dbf, 5, &erp_action->action, sizeof (int)); + if (erp_action->status & ZFCP_STATUS_ERP_DISMISSED) { + debug_text_event(adapter->erp_dbf, 3, "a_stcd_dis"); + zfcp_erp_action_dequeue(erp_action); + retval = ZFCP_ERP_DISMISSED; + } else + debug_text_event(adapter->erp_dbf, 5, "a_stcd_nodis"); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_strategy_do_action(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_FAILED; + struct zfcp_adapter *adapter = erp_action->adapter; + + /* + * try to execute/continue action as far as possible, + * note: no lock in subsequent strategy routines + * (this allows these routine to call schedule, e.g. + * kmalloc with such flags or qdio_initialize & friends) + * Note: in case of timeout, the seperate strategies will fail + * anyhow. No need for a special action. Even worse, a nameserver + * failure would not wake up waiting ports without the call. + */ + switch (erp_action->action) { + + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + retval = zfcp_erp_adapter_strategy(erp_action); + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + retval = zfcp_erp_port_forced_strategy(erp_action); + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT: + retval = zfcp_erp_port_strategy(erp_action); + break; + + case ZFCP_ERP_ACTION_REOPEN_UNIT: + retval = zfcp_erp_unit_strategy(erp_action); + break; + + default: + debug_text_exception(adapter->erp_dbf, 1, "a_stda_bug"); + debug_event(adapter->erp_dbf, 1, &erp_action->action, + sizeof (int)); + ZFCP_LOG_NORMAL("bug: unknown erp action requested on " + "adapter %s (action=%d)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->action); + } + + return retval; +} + +/* + * function: + * + * purpose: triggers retry of this action after a certain amount of time + * by means of timer provided by erp_action + * + * returns: ZFCP_ERP_CONTINUES - erp_action sleeps in erp running queue + */ +static int +zfcp_erp_strategy_memwait(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_CONTINUES; + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 6, "a_mwinit"); + debug_event(adapter->erp_dbf, 6, &erp_action->action, sizeof (int)); + init_timer(&erp_action->timer); + erp_action->timer.function = zfcp_erp_memwait_handler; + erp_action->timer.data = (unsigned long) erp_action; + erp_action->timer.expires = jiffies + ZFCP_ERP_MEMWAIT_TIMEOUT; + add_timer(&erp_action->timer); + + return retval; +} + +/* + * function: zfcp_erp_adapter_failed + * + * purpose: sets the adapter and all underlying devices to ERP_FAILED + * + */ +void +zfcp_erp_adapter_failed(struct zfcp_adapter *adapter) +{ + zfcp_erp_modify_adapter_status(adapter, + ZFCP_STATUS_COMMON_ERP_FAILED, ZFCP_SET); + ZFCP_LOG_NORMAL("adapter erp failed on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 2, "a_afail"); +} + +/* + * function: zfcp_erp_port_failed + * + * purpose: sets the port and all underlying devices to ERP_FAILED + * + */ +void +zfcp_erp_port_failed(struct zfcp_port *port) +{ + zfcp_erp_modify_port_status(port, + ZFCP_STATUS_COMMON_ERP_FAILED, ZFCP_SET); + + if (atomic_test_mask(ZFCP_STATUS_PORT_WKA, &port->status)) + ZFCP_LOG_NORMAL("port erp failed (adapter %s, " + "port d_id=0x%08x)\n", + zfcp_get_busid_by_port(port), port->d_id); + else + ZFCP_LOG_NORMAL("port erp failed (adapter %s, wwpn=0x%016Lx)\n", + zfcp_get_busid_by_port(port), port->wwpn); + + debug_text_event(port->adapter->erp_dbf, 2, "p_pfail"); + debug_event(port->adapter->erp_dbf, 2, &port->wwpn, sizeof (wwn_t)); +} + +/* + * function: zfcp_erp_unit_failed + * + * purpose: sets the unit to ERP_FAILED + * + */ +void +zfcp_erp_unit_failed(struct zfcp_unit *unit) +{ + zfcp_erp_modify_unit_status(unit, + ZFCP_STATUS_COMMON_ERP_FAILED, ZFCP_SET); + + ZFCP_LOG_NORMAL("unit erp failed on unit 0x%016Lx on port 0x%016Lx " + " on adapter %s\n", unit->fcp_lun, + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + debug_text_event(unit->port->adapter->erp_dbf, 2, "u_ufail"); + debug_event(unit->port->adapter->erp_dbf, 2, + &unit->fcp_lun, sizeof (fcp_lun_t)); +} + +/* + * function: zfcp_erp_strategy_check_target + * + * purpose: increments the erp action count on the device currently in + * recovery if the action failed or resets the count in case of + * success. If a maximum count is exceeded the device is marked + * as ERP_FAILED. + * The 'blocked' state of a target which has been recovered + * successfully is reset. + * + * returns: ZFCP_ERP_CONTINUES - action continues (not considered) + * ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_EXIT - action failed and will not continue + */ +static int +zfcp_erp_strategy_check_target(struct zfcp_erp_action *erp_action, int result) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + struct zfcp_unit *unit = erp_action->unit; + + debug_text_event(adapter->erp_dbf, 5, "a_stct_norm"); + debug_event(adapter->erp_dbf, 5, &erp_action->action, sizeof (int)); + debug_event(adapter->erp_dbf, 5, &result, sizeof (int)); + + switch (erp_action->action) { + + case ZFCP_ERP_ACTION_REOPEN_UNIT: + result = zfcp_erp_strategy_check_unit(unit, result); + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + case ZFCP_ERP_ACTION_REOPEN_PORT: + result = zfcp_erp_strategy_check_port(port, result); + break; + + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + result = zfcp_erp_strategy_check_adapter(adapter, result); + break; + } + + return result; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_strategy_statechange(int action, + u32 status, + struct zfcp_adapter *adapter, + struct zfcp_port *port, + struct zfcp_unit *unit, int retval) +{ + debug_text_event(adapter->erp_dbf, 3, "a_stsc"); + debug_event(adapter->erp_dbf, 3, &action, sizeof (int)); + + switch (action) { + + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + if (zfcp_erp_strategy_statechange_detected(&adapter->status, + status)) { + zfcp_erp_adapter_reopen_internal(adapter, ZFCP_STATUS_COMMON_ERP_FAILED); + retval = ZFCP_ERP_EXIT; + } + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + case ZFCP_ERP_ACTION_REOPEN_PORT: + if (zfcp_erp_strategy_statechange_detected(&port->status, + status)) { + zfcp_erp_port_reopen_internal(port, ZFCP_STATUS_COMMON_ERP_FAILED); + retval = ZFCP_ERP_EXIT; + } + break; + + case ZFCP_ERP_ACTION_REOPEN_UNIT: + if (zfcp_erp_strategy_statechange_detected(&unit->status, + status)) { + zfcp_erp_unit_reopen_internal(unit, ZFCP_STATUS_COMMON_ERP_FAILED); + retval = ZFCP_ERP_EXIT; + } + break; + } + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static inline int +zfcp_erp_strategy_statechange_detected(atomic_t * target_status, u32 erp_status) +{ + return + /* take it online */ + (atomic_test_mask(ZFCP_STATUS_COMMON_RUNNING, target_status) && + (ZFCP_STATUS_ERP_CLOSE_ONLY & erp_status)) || + /* take it offline */ + (!atomic_test_mask(ZFCP_STATUS_COMMON_RUNNING, target_status) && + !(ZFCP_STATUS_ERP_CLOSE_ONLY & erp_status)); +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_strategy_check_unit(struct zfcp_unit *unit, int result) +{ + debug_text_event(unit->port->adapter->erp_dbf, 5, "u_stct"); + debug_event(unit->port->adapter->erp_dbf, 5, &unit->fcp_lun, + sizeof (fcp_lun_t)); + + switch (result) { + case ZFCP_ERP_SUCCEEDED : + atomic_set(&unit->erp_counter, 0); + zfcp_erp_unit_unblock(unit); + break; + case ZFCP_ERP_FAILED : + atomic_inc(&unit->erp_counter); + if (atomic_read(&unit->erp_counter) > ZFCP_MAX_ERPS) + zfcp_erp_unit_failed(unit); + break; + case ZFCP_ERP_EXIT : + /* nothing */ + break; + } + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status)) { + zfcp_erp_unit_block(unit, 0); /* for ZFCP_ERP_SUCCEEDED */ + result = ZFCP_ERP_EXIT; + } + + return result; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_strategy_check_port(struct zfcp_port *port, int result) +{ + debug_text_event(port->adapter->erp_dbf, 5, "p_stct"); + debug_event(port->adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + + switch (result) { + case ZFCP_ERP_SUCCEEDED : + atomic_set(&port->erp_counter, 0); + zfcp_erp_port_unblock(port); + break; + case ZFCP_ERP_FAILED : + atomic_inc(&port->erp_counter); + if (atomic_read(&port->erp_counter) > ZFCP_MAX_ERPS) + zfcp_erp_port_failed(port); + break; + case ZFCP_ERP_EXIT : + /* nothing */ + break; + } + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) { + zfcp_erp_port_block(port, 0); /* for ZFCP_ERP_SUCCEEDED */ + result = ZFCP_ERP_EXIT; + } + + return result; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_strategy_check_adapter(struct zfcp_adapter *adapter, int result) +{ + debug_text_event(adapter->erp_dbf, 5, "a_stct"); + + switch (result) { + case ZFCP_ERP_SUCCEEDED : + atomic_set(&adapter->erp_counter, 0); + zfcp_erp_adapter_unblock(adapter); + break; + case ZFCP_ERP_FAILED : + atomic_inc(&adapter->erp_counter); + if (atomic_read(&adapter->erp_counter) > ZFCP_MAX_ERPS) + zfcp_erp_adapter_failed(adapter); + break; + case ZFCP_ERP_EXIT : + /* nothing */ + break; + } + + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) { + zfcp_erp_adapter_block(adapter, 0); /* for ZFCP_ERP_SUCCEEDED */ + result = ZFCP_ERP_EXIT; + } + + return result; +} + +/* + * function: + * + * purpose: remaining things in good cases, + * escalation in bad cases + * + * returns: + */ +static int +zfcp_erp_strategy_followup_actions(int action, + struct zfcp_adapter *adapter, + struct zfcp_port *port, + struct zfcp_unit *unit, int status) +{ + debug_text_event(adapter->erp_dbf, 5, "a_stfol"); + debug_event(adapter->erp_dbf, 5, &action, sizeof (int)); + + /* initiate follow-up actions depending on success of finished action */ + switch (action) { + + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + if (status == ZFCP_ERP_SUCCEEDED) + zfcp_erp_port_reopen_all_internal(adapter, 0); + else + zfcp_erp_adapter_reopen_internal(adapter, 0); + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + if (status == ZFCP_ERP_SUCCEEDED) + zfcp_erp_port_reopen_internal(port, 0); + else + zfcp_erp_adapter_reopen_internal(adapter, 0); + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT: + if (status == ZFCP_ERP_SUCCEEDED) + zfcp_erp_unit_reopen_all_internal(port, 0); + else + zfcp_erp_port_forced_reopen_internal(port, 0); + break; + + case ZFCP_ERP_ACTION_REOPEN_UNIT: + if (status == ZFCP_ERP_SUCCEEDED) ; /* no further action */ + else + zfcp_erp_port_reopen_internal(unit->port, 0); + break; + } + + return 0; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_strategy_check_queues(struct zfcp_adapter *adapter) +{ + unsigned long flags; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + read_lock(&adapter->erp_lock); + if (list_empty(&adapter->erp_ready_head) && + list_empty(&adapter->erp_running_head)) { + debug_text_event(adapter->erp_dbf, 4, "a_cq_wake"); + atomic_clear_mask(ZFCP_STATUS_ADAPTER_ERP_PENDING, + &adapter->status); + wake_up(&adapter->erp_done_wqh); + } else + debug_text_event(adapter->erp_dbf, 5, "a_cq_notempty"); + read_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + return 0; +} + +/** + * zfcp_erp_wait - wait for completion of error recovery on an adapter + * @adapter: adapter for which to wait for completion of its error recovery + * Return: 0 + */ +int +zfcp_erp_wait(struct zfcp_adapter *adapter) +{ + int retval = 0; + + wait_event(adapter->erp_done_wqh, + !atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_PENDING, + &adapter->status)); + + return retval; +} + +/* + * function: zfcp_erp_modify_adapter_status + * + * purpose: + * + */ +void +zfcp_erp_modify_adapter_status(struct zfcp_adapter *adapter, + u32 mask, int set_or_clear) +{ + struct zfcp_port *port; + u32 common_mask = mask & ZFCP_COMMON_FLAGS; + + if (set_or_clear == ZFCP_SET) { + atomic_set_mask(mask, &adapter->status); + debug_text_event(adapter->erp_dbf, 3, "a_mod_as_s"); + } else { + atomic_clear_mask(mask, &adapter->status); + if (mask & ZFCP_STATUS_COMMON_ERP_FAILED) + atomic_set(&adapter->erp_counter, 0); + debug_text_event(adapter->erp_dbf, 3, "a_mod_as_c"); + } + debug_event(adapter->erp_dbf, 3, &mask, sizeof (u32)); + + /* Deal with all underlying devices, only pass common_mask */ + if (common_mask) + list_for_each_entry(port, &adapter->port_list_head, list) + zfcp_erp_modify_port_status(port, common_mask, + set_or_clear); +} + +/* + * function: zfcp_erp_modify_port_status + * + * purpose: sets the port and all underlying devices to ERP_FAILED + * + */ +void +zfcp_erp_modify_port_status(struct zfcp_port *port, u32 mask, int set_or_clear) +{ + struct zfcp_unit *unit; + u32 common_mask = mask & ZFCP_COMMON_FLAGS; + + if (set_or_clear == ZFCP_SET) { + atomic_set_mask(mask, &port->status); + debug_text_event(port->adapter->erp_dbf, 3, "p_mod_ps_s"); + } else { + atomic_clear_mask(mask, &port->status); + if (mask & ZFCP_STATUS_COMMON_ERP_FAILED) + atomic_set(&port->erp_counter, 0); + debug_text_event(port->adapter->erp_dbf, 3, "p_mod_ps_c"); + } + debug_event(port->adapter->erp_dbf, 3, &port->wwpn, sizeof (wwn_t)); + debug_event(port->adapter->erp_dbf, 3, &mask, sizeof (u32)); + + /* Modify status of all underlying devices, only pass common mask */ + if (common_mask) + list_for_each_entry(unit, &port->unit_list_head, list) + zfcp_erp_modify_unit_status(unit, common_mask, + set_or_clear); +} + +/* + * function: zfcp_erp_modify_unit_status + * + * purpose: sets the unit to ERP_FAILED + * + */ +void +zfcp_erp_modify_unit_status(struct zfcp_unit *unit, u32 mask, int set_or_clear) +{ + if (set_or_clear == ZFCP_SET) { + atomic_set_mask(mask, &unit->status); + debug_text_event(unit->port->adapter->erp_dbf, 3, "u_mod_us_s"); + } else { + atomic_clear_mask(mask, &unit->status); + if (mask & ZFCP_STATUS_COMMON_ERP_FAILED) { + atomic_set(&unit->erp_counter, 0); + } + debug_text_event(unit->port->adapter->erp_dbf, 3, "u_mod_us_c"); + } + debug_event(unit->port->adapter->erp_dbf, 3, &unit->fcp_lun, + sizeof (fcp_lun_t)); + debug_event(unit->port->adapter->erp_dbf, 3, &mask, sizeof (u32)); +} + +/* + * function: + * + * purpose: Wrappper for zfcp_erp_port_reopen_all_internal + * used to ensure the correct locking + * + * returns: 0 - initiated action succesfully + * <0 - failed to initiate action + */ +int +zfcp_erp_port_reopen_all(struct zfcp_adapter *adapter, int clear_mask) +{ + int retval; + unsigned long flags; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + write_lock(&adapter->erp_lock); + retval = zfcp_erp_port_reopen_all_internal(adapter, clear_mask); + write_unlock(&adapter->erp_lock); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: FIXME + */ +static int +zfcp_erp_port_reopen_all_internal(struct zfcp_adapter *adapter, int clear_mask) +{ + int retval = 0; + struct zfcp_port *port; + + list_for_each_entry(port, &adapter->port_list_head, list) + if (!atomic_test_mask(ZFCP_STATUS_PORT_WKA, &port->status)) + zfcp_erp_port_reopen_internal(port, clear_mask); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: FIXME + */ +static int +zfcp_erp_unit_reopen_all_internal(struct zfcp_port *port, int clear_mask) +{ + int retval = 0; + struct zfcp_unit *unit; + + list_for_each_entry(unit, &port->unit_list_head, list) + zfcp_erp_unit_reopen_internal(unit, clear_mask); + + return retval; +} + +/* + * function: + * + * purpose: this routine executes the 'Reopen Adapter' action + * (the entire action is processed synchronously, since + * there are no actions which might be run concurrently + * per definition) + * + * returns: ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_adapter_strategy(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + + retval = zfcp_erp_adapter_strategy_close(erp_action); + if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY) + retval = ZFCP_ERP_EXIT; + else + retval = zfcp_erp_adapter_strategy_open(erp_action); + + debug_text_event(adapter->erp_dbf, 3, "a_ast/ret"); + debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int)); + debug_event(adapter->erp_dbf, 3, &retval, sizeof (int)); + + if (retval == ZFCP_ERP_FAILED) { + ZFCP_LOG_INFO("Waiting to allow the adapter %s " + "to recover itself\n", + zfcp_get_busid_by_adapter(adapter)); + msleep(jiffies_to_msecs(ZFCP_TYPE2_RECOVERY_TIME)); + } + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_adapter_strategy_close(struct zfcp_erp_action *erp_action) +{ + int retval; + + atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, + &erp_action->adapter->status); + retval = zfcp_erp_adapter_strategy_generic(erp_action, 1); + atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, + &erp_action->adapter->status); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_adapter_strategy_open(struct zfcp_erp_action *erp_action) +{ + int retval; + + atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, + &erp_action->adapter->status); + retval = zfcp_erp_adapter_strategy_generic(erp_action, 0); + atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, + &erp_action->adapter->status); + + return retval; +} + +/* + * function: zfcp_register_adapter + * + * purpose: allocate the irq associated with this devno and register + * the FSF adapter with the SCSI stack + * + * returns: + */ +static int +zfcp_erp_adapter_strategy_generic(struct zfcp_erp_action *erp_action, int close) +{ + int retval = ZFCP_ERP_SUCCEEDED; + + if (close) + goto close_only; + + retval = zfcp_erp_adapter_strategy_open_qdio(erp_action); + if (retval != ZFCP_ERP_SUCCEEDED) + goto failed_qdio; + + retval = zfcp_erp_adapter_strategy_open_fsf(erp_action); + if (retval != ZFCP_ERP_SUCCEEDED) + goto failed_openfcp; + + atomic_set_mask(ZFCP_STATUS_COMMON_OPEN, &erp_action->adapter->status); + goto out; + + close_only: + atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, + &erp_action->adapter->status); + + failed_openfcp: + zfcp_erp_adapter_strategy_close_qdio(erp_action); + zfcp_erp_adapter_strategy_close_fsf(erp_action); + failed_qdio: + out: + return retval; +} + +/* + * function: zfcp_qdio_init + * + * purpose: setup QDIO operation for specified adapter + * + * returns: 0 - successful setup + * !0 - failed setup + */ +int +zfcp_erp_adapter_strategy_open_qdio(struct zfcp_erp_action *erp_action) +{ + int retval; + int i; + volatile struct qdio_buffer_element *sbale; + struct zfcp_adapter *adapter = erp_action->adapter; + + if (atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) { + ZFCP_LOG_NORMAL("bug: second attempt to set up QDIO on " + "adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + goto failed_sanity; + } + + if (qdio_establish(&adapter->qdio_init_data) != 0) { + ZFCP_LOG_INFO("error: establishment of QDIO queues failed " + "on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + goto failed_qdio_establish; + } + debug_text_event(adapter->erp_dbf, 3, "qdio_est"); + + if (qdio_activate(adapter->ccw_device, 0) != 0) { + ZFCP_LOG_INFO("error: activation of QDIO queues failed " + "on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + goto failed_qdio_activate; + } + debug_text_event(adapter->erp_dbf, 3, "qdio_act"); + + /* + * put buffers into response queue, + */ + for (i = 0; i < QDIO_MAX_BUFFERS_PER_Q; i++) { + sbale = &(adapter->response_queue.buffer[i]->element[0]); + sbale->length = 0; + sbale->flags = SBAL_FLAGS_LAST_ENTRY; + sbale->addr = 0; + } + + ZFCP_LOG_TRACE("calling do_QDIO on adapter %s (flags=0x%x, " + "queue_no=%i, index_in_queue=%i, count=%i)\n", + zfcp_get_busid_by_adapter(adapter), + QDIO_FLAG_SYNC_INPUT, 0, 0, QDIO_MAX_BUFFERS_PER_Q); + + retval = do_QDIO(adapter->ccw_device, + QDIO_FLAG_SYNC_INPUT, + 0, 0, QDIO_MAX_BUFFERS_PER_Q, NULL); + + if (retval) { + ZFCP_LOG_NORMAL("bug: setup of QDIO failed (retval=%d)\n", + retval); + goto failed_do_qdio; + } else { + adapter->response_queue.free_index = 0; + atomic_set(&adapter->response_queue.free_count, 0); + ZFCP_LOG_DEBUG("%i buffers successfully enqueued to " + "response queue\n", QDIO_MAX_BUFFERS_PER_Q); + } + /* set index of first avalable SBALS / number of available SBALS */ + adapter->request_queue.free_index = 0; + atomic_set(&adapter->request_queue.free_count, QDIO_MAX_BUFFERS_PER_Q); + adapter->request_queue.distance_from_int = 0; + + /* initialize waitqueue used to wait for free SBALs in requests queue */ + init_waitqueue_head(&adapter->request_wq); + + /* ok, we did it - skip all cleanups for different failures */ + atomic_set_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status); + retval = ZFCP_ERP_SUCCEEDED; + goto out; + + failed_do_qdio: + /* NOP */ + + failed_qdio_activate: + debug_text_event(adapter->erp_dbf, 3, "qdio_down1a"); + while (qdio_shutdown(adapter->ccw_device, + QDIO_FLAG_CLEANUP_USING_CLEAR) == -EINPROGRESS) + msleep(1000); + debug_text_event(adapter->erp_dbf, 3, "qdio_down1b"); + + failed_qdio_establish: + failed_sanity: + retval = ZFCP_ERP_FAILED; + + out: + return retval; +} + +/* + * function: zfcp_qdio_cleanup + * + * purpose: cleans up QDIO operation for the specified adapter + * + * returns: 0 - successful cleanup + * !0 - failed cleanup + */ +int +zfcp_erp_adapter_strategy_close_qdio(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_SUCCEEDED; + int first_used; + int used_count; + struct zfcp_adapter *adapter = erp_action->adapter; + + if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) { + ZFCP_LOG_DEBUG("error: attempt to shut down inactive QDIO " + "queues on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + retval = ZFCP_ERP_FAILED; + goto out; + } + + /* + * Get queue_lock and clear QDIOUP flag. Thus it's guaranteed that + * do_QDIO won't be called while qdio_shutdown is in progress. + */ + + write_lock_irq(&adapter->request_queue.queue_lock); + atomic_clear_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status); + write_unlock_irq(&adapter->request_queue.queue_lock); + + debug_text_event(adapter->erp_dbf, 3, "qdio_down2a"); + while (qdio_shutdown(adapter->ccw_device, + QDIO_FLAG_CLEANUP_USING_CLEAR) == -EINPROGRESS) + msleep(1000); + debug_text_event(adapter->erp_dbf, 3, "qdio_down2b"); + + /* + * First we had to stop QDIO operation. + * Now it is safe to take the following actions. + */ + + /* Cleanup only necessary when there are unacknowledged buffers */ + if (atomic_read(&adapter->request_queue.free_count) + < QDIO_MAX_BUFFERS_PER_Q) { + first_used = (adapter->request_queue.free_index + + atomic_read(&adapter->request_queue.free_count)) + % QDIO_MAX_BUFFERS_PER_Q; + used_count = QDIO_MAX_BUFFERS_PER_Q - + atomic_read(&adapter->request_queue.free_count); + zfcp_qdio_zero_sbals(adapter->request_queue.buffer, + first_used, used_count); + } + adapter->response_queue.free_index = 0; + atomic_set(&adapter->response_queue.free_count, 0); + adapter->request_queue.free_index = 0; + atomic_set(&adapter->request_queue.free_count, 0); + adapter->request_queue.distance_from_int = 0; + out: + return retval; +} + +/* + * function: zfcp_fsf_init + * + * purpose: initializes FSF operation for the specified adapter + * + * returns: 0 - succesful initialization of FSF operation + * !0 - failed to initialize FSF operation + */ +static int +zfcp_erp_adapter_strategy_open_fsf(struct zfcp_erp_action *erp_action) +{ + int retval; + + /* do 'exchange configuration data' */ + retval = zfcp_erp_adapter_strategy_open_fsf_xconfig(erp_action); + if (retval == ZFCP_ERP_FAILED) + return retval; + + /* start the desired number of Status Reads */ + retval = zfcp_erp_adapter_strategy_open_fsf_statusread(erp_action); + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_adapter_strategy_open_fsf_xconfig(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_SUCCEEDED; + int retries; + struct zfcp_adapter *adapter = erp_action->adapter; + + atomic_clear_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK, &adapter->status); + retries = ZFCP_EXCHANGE_CONFIG_DATA_RETRIES; + + do { + atomic_clear_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT, + &adapter->status); + ZFCP_LOG_DEBUG("Doing exchange config data\n"); + zfcp_erp_action_to_running(erp_action); + zfcp_erp_timeout_init(erp_action); + if (zfcp_fsf_exchange_config_data(erp_action)) { + retval = ZFCP_ERP_FAILED; + debug_text_event(adapter->erp_dbf, 5, "a_fstx_xf"); + ZFCP_LOG_INFO("error: initiation of exchange of " + "configuration data failed for " + "adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + break; + } + debug_text_event(adapter->erp_dbf, 6, "a_fstx_xok"); + ZFCP_LOG_DEBUG("Xchange underway\n"); + + /* + * Why this works: + * Both the normal completion handler as well as the timeout + * handler will do an 'up' when the 'exchange config data' + * request completes or times out. Thus, the signal to go on + * won't be lost utilizing this semaphore. + * Furthermore, this 'adapter_reopen' action is + * guaranteed to be the only action being there (highest action + * which prevents other actions from being created). + * Resulting from that, the wake signal recognized here + * _must_ be the one belonging to the 'exchange config + * data' request. + */ + down(&adapter->erp_ready_sem); + if (erp_action->status & ZFCP_STATUS_ERP_TIMEDOUT) { + ZFCP_LOG_INFO("error: exchange of configuration data " + "for adapter %s timed out\n", + zfcp_get_busid_by_adapter(adapter)); + break; + } + if (atomic_test_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT, + &adapter->status)) { + ZFCP_LOG_DEBUG("host connection still initialising... " + "waiting and retrying...\n"); + /* sleep a little bit before retry */ + msleep(jiffies_to_msecs(ZFCP_EXCHANGE_CONFIG_DATA_SLEEP)); + } + } while ((retries--) && + atomic_test_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT, + &adapter->status)); + + if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK, + &adapter->status)) { + ZFCP_LOG_INFO("error: exchange of configuration data for " + "adapter %s failed\n", + zfcp_get_busid_by_adapter(adapter)); + retval = ZFCP_ERP_FAILED; + } + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_adapter_strategy_open_fsf_statusread(struct zfcp_erp_action + *erp_action) +{ + int retval = ZFCP_ERP_SUCCEEDED; + int temp_ret; + struct zfcp_adapter *adapter = erp_action->adapter; + int i; + + adapter->status_read_failed = 0; + for (i = 0; i < ZFCP_STATUS_READS_RECOM; i++) { + temp_ret = zfcp_fsf_status_read(adapter, ZFCP_WAIT_FOR_SBAL); + if (temp_ret < 0) { + ZFCP_LOG_INFO("error: set-up of unsolicited status " + "notification failed on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + retval = ZFCP_ERP_FAILED; + i--; + break; + } + } + + return retval; +} + +/* + * function: zfcp_fsf_cleanup + * + * purpose: cleanup FSF operation for specified adapter + * + * returns: 0 - FSF operation successfully cleaned up + * !0 - failed to cleanup FSF operation for this adapter + */ +static int +zfcp_erp_adapter_strategy_close_fsf(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_SUCCEEDED; + struct zfcp_adapter *adapter = erp_action->adapter; + + /* + * wake waiting initiators of requests, + * return SCSI commands (with error status), + * clean up all requests (synchronously) + */ + zfcp_fsf_req_dismiss_all(adapter); + /* reset FSF request sequence number */ + adapter->fsf_req_seq_no = 0; + /* all ports and units are closed */ + zfcp_erp_modify_adapter_status(adapter, + ZFCP_STATUS_COMMON_OPEN, ZFCP_CLEAR); + + return retval; +} + +/* + * function: + * + * purpose: this routine executes the 'Reopen Physical Port' action + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_port_forced_strategy(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_FAILED; + struct zfcp_port *port = erp_action->port; + struct zfcp_adapter *adapter = erp_action->adapter; + + switch (erp_action->step) { + + /* + * FIXME: + * the ULP spec. begs for waiting for oustanding commands + */ + case ZFCP_ERP_STEP_UNINITIALIZED: + zfcp_erp_port_strategy_clearstati(port); + /* + * it would be sufficient to test only the normal open flag + * since the phys. open flag cannot be set if the normal + * open flag is unset - however, this is for readabilty ... + */ + if (atomic_test_mask((ZFCP_STATUS_PORT_PHYS_OPEN | + ZFCP_STATUS_COMMON_OPEN), + &port->status)) { + ZFCP_LOG_DEBUG("port 0x%016Lx is open -> trying " + "close physical\n", port->wwpn); + retval = + zfcp_erp_port_forced_strategy_close(erp_action); + } else + retval = ZFCP_ERP_FAILED; + break; + + case ZFCP_ERP_STEP_PHYS_PORT_CLOSING: + if (atomic_test_mask(ZFCP_STATUS_PORT_PHYS_OPEN, + &port->status)) { + ZFCP_LOG_DEBUG("close physical failed for port " + "0x%016Lx\n", port->wwpn); + retval = ZFCP_ERP_FAILED; + } else + retval = ZFCP_ERP_SUCCEEDED; + break; + } + + debug_text_event(adapter->erp_dbf, 3, "p_pfst/ret"); + debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof (wwn_t)); + debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int)); + debug_event(adapter->erp_dbf, 3, &retval, sizeof (int)); + + return retval; +} + +/* + * function: + * + * purpose: this routine executes the 'Reopen Port' action + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_port_strategy(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_FAILED; + struct zfcp_port *port = erp_action->port; + struct zfcp_adapter *adapter = erp_action->adapter; + + switch (erp_action->step) { + + /* + * FIXME: + * the ULP spec. begs for waiting for oustanding commands + */ + case ZFCP_ERP_STEP_UNINITIALIZED: + zfcp_erp_port_strategy_clearstati(port); + if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &port->status)) { + ZFCP_LOG_DEBUG("port 0x%016Lx is open -> trying " + "close\n", port->wwpn); + retval = zfcp_erp_port_strategy_close(erp_action); + goto out; + } /* else it's already closed, open it */ + break; + + case ZFCP_ERP_STEP_PORT_CLOSING: + if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &port->status)) { + ZFCP_LOG_DEBUG("close failed for port 0x%016Lx\n", + port->wwpn); + retval = ZFCP_ERP_FAILED; + goto out; + } /* else it's closed now, open it */ + break; + } + if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY) + retval = ZFCP_ERP_EXIT; + else + retval = zfcp_erp_port_strategy_open(erp_action); + + out: + debug_text_event(adapter->erp_dbf, 3, "p_pst/ret"); + debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof (wwn_t)); + debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int)); + debug_event(adapter->erp_dbf, 3, &retval, sizeof (int)); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_port_strategy_open(struct zfcp_erp_action *erp_action) +{ + int retval; + + if (atomic_test_mask(ZFCP_STATUS_PORT_WKA, + &erp_action->port->status)) + retval = zfcp_erp_port_strategy_open_nameserver(erp_action); + else + retval = zfcp_erp_port_strategy_open_common(erp_action); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + * + * FIXME(design): currently only prepared for fabric (nameserver!) + */ +static int +zfcp_erp_port_strategy_open_common(struct zfcp_erp_action *erp_action) +{ + int retval = 0; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + + switch (erp_action->step) { + + case ZFCP_ERP_STEP_UNINITIALIZED: + case ZFCP_ERP_STEP_PHYS_PORT_CLOSING: + case ZFCP_ERP_STEP_PORT_CLOSING: + if (!(adapter->nameserver_port)) { + retval = zfcp_nameserver_enqueue(adapter); + if (retval != 0) { + ZFCP_LOG_NORMAL("error: nameserver port " + "unavailable for adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + retval = ZFCP_ERP_FAILED; + break; + } + } + if (!atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED, + &adapter->nameserver_port->status)) { + ZFCP_LOG_DEBUG("nameserver port is not open -> open " + "nameserver port\n"); + /* nameserver port may live again */ + atomic_set_mask(ZFCP_STATUS_COMMON_RUNNING, + &adapter->nameserver_port->status); + if (zfcp_erp_port_reopen(adapter->nameserver_port, 0) + >= 0) { + erp_action->step = + ZFCP_ERP_STEP_NAMESERVER_OPEN; + retval = ZFCP_ERP_CONTINUES; + } else + retval = ZFCP_ERP_FAILED; + break; + } + /* else nameserver port is already open, fall through */ + case ZFCP_ERP_STEP_NAMESERVER_OPEN: + if (!atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, + &adapter->nameserver_port->status)) { + ZFCP_LOG_DEBUG("open failed for nameserver port\n"); + retval = ZFCP_ERP_FAILED; + } else { + ZFCP_LOG_DEBUG("nameserver port is open -> " + "nameserver look-up for port 0x%016Lx\n", + port->wwpn); + retval = zfcp_erp_port_strategy_open_common_lookup + (erp_action); + } + break; + + case ZFCP_ERP_STEP_NAMESERVER_LOOKUP: + if (!atomic_test_mask(ZFCP_STATUS_PORT_DID_DID, &port->status)) { + if (atomic_test_mask + (ZFCP_STATUS_PORT_INVALID_WWPN, &port->status)) { + ZFCP_LOG_DEBUG("nameserver look-up failed " + "for port 0x%016Lx " + "(misconfigured WWPN?)\n", + port->wwpn); + zfcp_erp_port_failed(port); + retval = ZFCP_ERP_EXIT; + } else { + ZFCP_LOG_DEBUG("nameserver look-up failed for " + "port 0x%016Lx\n", port->wwpn); + retval = ZFCP_ERP_FAILED; + } + } else { + ZFCP_LOG_DEBUG("port 0x%016Lx has d_id=0x%08x -> " + "trying open\n", port->wwpn, port->d_id); + retval = zfcp_erp_port_strategy_open_port(erp_action); + } + break; + + case ZFCP_ERP_STEP_PORT_OPENING: + /* D_ID might have changed during open */ + if (atomic_test_mask((ZFCP_STATUS_COMMON_OPEN | + ZFCP_STATUS_PORT_DID_DID), + &port->status)) { + ZFCP_LOG_DEBUG("port 0x%016Lx is open\n", port->wwpn); + retval = ZFCP_ERP_SUCCEEDED; + } else { + ZFCP_LOG_DEBUG("open failed for port 0x%016Lx\n", + port->wwpn); + retval = ZFCP_ERP_FAILED; + } + break; + + default: + ZFCP_LOG_NORMAL("bug: unknown erp step 0x%08x\n", + erp_action->step); + retval = ZFCP_ERP_FAILED; + } + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_port_strategy_open_nameserver(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_port *port = erp_action->port; + + switch (erp_action->step) { + + case ZFCP_ERP_STEP_UNINITIALIZED: + case ZFCP_ERP_STEP_PHYS_PORT_CLOSING: + case ZFCP_ERP_STEP_PORT_CLOSING: + ZFCP_LOG_DEBUG("port 0x%016Lx has d_id=0x%08x -> trying open\n", + port->wwpn, port->d_id); + retval = zfcp_erp_port_strategy_open_port(erp_action); + break; + + case ZFCP_ERP_STEP_PORT_OPENING: + if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &port->status)) { + ZFCP_LOG_DEBUG("WKA port is open\n"); + retval = ZFCP_ERP_SUCCEEDED; + } else { + ZFCP_LOG_DEBUG("open failed for WKA port\n"); + retval = ZFCP_ERP_FAILED; + } + /* this is needed anyway (dont care for retval of wakeup) */ + ZFCP_LOG_DEBUG("continue other open port operations\n"); + zfcp_erp_port_strategy_open_nameserver_wakeup(erp_action); + break; + + default: + ZFCP_LOG_NORMAL("bug: unknown erp step 0x%08x\n", + erp_action->step); + retval = ZFCP_ERP_FAILED; + } + + return retval; +} + +/* + * function: + * + * purpose: makes the erp thread continue with reopen (physical) port + * actions which have been paused until the name server port + * is opened (or failed) + * + * returns: 0 (a kind of void retval, its not used) + */ +static int +zfcp_erp_port_strategy_open_nameserver_wakeup(struct zfcp_erp_action + *ns_erp_action) +{ + int retval = 0; + unsigned long flags; + struct zfcp_adapter *adapter = ns_erp_action->adapter; + struct zfcp_erp_action *erp_action, *tmp; + + read_lock_irqsave(&adapter->erp_lock, flags); + list_for_each_entry_safe(erp_action, tmp, &adapter->erp_running_head, + list) { + debug_text_event(adapter->erp_dbf, 4, "p_pstnsw_n"); + debug_event(adapter->erp_dbf, 4, &erp_action->port->wwpn, + sizeof (wwn_t)); + if (erp_action->step == ZFCP_ERP_STEP_NAMESERVER_OPEN) { + debug_text_event(adapter->erp_dbf, 3, "p_pstnsw_w"); + debug_event(adapter->erp_dbf, 3, + &erp_action->port->wwpn, sizeof (wwn_t)); + if (atomic_test_mask( + ZFCP_STATUS_COMMON_ERP_FAILED, + &adapter->nameserver_port->status)) + zfcp_erp_port_failed(erp_action->port); + zfcp_erp_action_ready(erp_action); + } + } + read_unlock_irqrestore(&adapter->erp_lock, flags); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_port_forced_strategy_close(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + + zfcp_erp_timeout_init(erp_action); + retval = zfcp_fsf_close_physical_port(erp_action); + if (retval == -ENOMEM) { + debug_text_event(adapter->erp_dbf, 5, "o_pfstc_nomem"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_NOMEM; + goto out; + } + erp_action->step = ZFCP_ERP_STEP_PHYS_PORT_CLOSING; + if (retval != 0) { + debug_text_event(adapter->erp_dbf, 5, "o_pfstc_cpf"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + /* could not send 'open', fail */ + retval = ZFCP_ERP_FAILED; + goto out; + } + debug_text_event(adapter->erp_dbf, 6, "o_pfstc_cpok"); + debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_CONTINUES; + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_port_strategy_clearstati(struct zfcp_port *port) +{ + int retval = 0; + struct zfcp_adapter *adapter = port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "p_pstclst"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + + atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING | + ZFCP_STATUS_COMMON_CLOSING | + ZFCP_STATUS_COMMON_ACCESS_DENIED | + ZFCP_STATUS_PORT_DID_DID | + ZFCP_STATUS_PORT_PHYS_CLOSING | + ZFCP_STATUS_PORT_INVALID_WWPN, + &port->status); + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_port_strategy_close(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + + zfcp_erp_timeout_init(erp_action); + retval = zfcp_fsf_close_port(erp_action); + if (retval == -ENOMEM) { + debug_text_event(adapter->erp_dbf, 5, "p_pstc_nomem"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_NOMEM; + goto out; + } + erp_action->step = ZFCP_ERP_STEP_PORT_CLOSING; + if (retval != 0) { + debug_text_event(adapter->erp_dbf, 5, "p_pstc_cpf"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + /* could not send 'close', fail */ + retval = ZFCP_ERP_FAILED; + goto out; + } + debug_text_event(adapter->erp_dbf, 6, "p_pstc_cpok"); + debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_CONTINUES; + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_port_strategy_open_port(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + + zfcp_erp_timeout_init(erp_action); + retval = zfcp_fsf_open_port(erp_action); + if (retval == -ENOMEM) { + debug_text_event(adapter->erp_dbf, 5, "p_psto_nomem"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_NOMEM; + goto out; + } + erp_action->step = ZFCP_ERP_STEP_PORT_OPENING; + if (retval != 0) { + debug_text_event(adapter->erp_dbf, 5, "p_psto_opf"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + /* could not send 'open', fail */ + retval = ZFCP_ERP_FAILED; + goto out; + } + debug_text_event(adapter->erp_dbf, 6, "p_psto_opok"); + debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_CONTINUES; + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_port_strategy_open_common_lookup(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_port *port = erp_action->port; + + zfcp_erp_timeout_init(erp_action); + retval = zfcp_ns_gid_pn_request(erp_action); + if (retval == -ENOMEM) { + debug_text_event(adapter->erp_dbf, 5, "p_pstn_nomem"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_NOMEM; + goto out; + } + erp_action->step = ZFCP_ERP_STEP_NAMESERVER_LOOKUP; + if (retval != 0) { + debug_text_event(adapter->erp_dbf, 5, "p_pstn_ref"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + /* could not send nameserver request, fail */ + retval = ZFCP_ERP_FAILED; + goto out; + } + debug_text_event(adapter->erp_dbf, 6, "p_pstn_reok"); + debug_event(adapter->erp_dbf, 6, &port->wwpn, sizeof (wwn_t)); + retval = ZFCP_ERP_CONTINUES; + out: + return retval; +} + +/* + * function: + * + * purpose: this routine executes the 'Reopen Unit' action + * currently no retries + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_SUCCEEDED - action finished successfully + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_unit_strategy(struct zfcp_erp_action *erp_action) +{ + int retval = ZFCP_ERP_FAILED; + struct zfcp_unit *unit = erp_action->unit; + struct zfcp_adapter *adapter = erp_action->adapter; + + switch (erp_action->step) { + + /* + * FIXME: + * the ULP spec. begs for waiting for oustanding commands + */ + case ZFCP_ERP_STEP_UNINITIALIZED: + zfcp_erp_unit_strategy_clearstati(unit); + if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status)) { + ZFCP_LOG_DEBUG("unit 0x%016Lx is open -> " + "trying close\n", unit->fcp_lun); + retval = zfcp_erp_unit_strategy_close(erp_action); + break; + } + /* else it's already closed, fall through */ + case ZFCP_ERP_STEP_UNIT_CLOSING: + if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status)) { + ZFCP_LOG_DEBUG("close failed for unit 0x%016Lx\n", + unit->fcp_lun); + retval = ZFCP_ERP_FAILED; + } else { + if (erp_action->status & ZFCP_STATUS_ERP_CLOSE_ONLY) + retval = ZFCP_ERP_EXIT; + else { + ZFCP_LOG_DEBUG("unit 0x%016Lx is not open -> " + "trying open\n", unit->fcp_lun); + retval = + zfcp_erp_unit_strategy_open(erp_action); + } + } + break; + + case ZFCP_ERP_STEP_UNIT_OPENING: + if (atomic_test_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status)) { + ZFCP_LOG_DEBUG("unit 0x%016Lx is open\n", + unit->fcp_lun); + retval = ZFCP_ERP_SUCCEEDED; + } else { + ZFCP_LOG_DEBUG("open failed for unit 0x%016Lx\n", + unit->fcp_lun); + retval = ZFCP_ERP_FAILED; + } + break; + } + + debug_text_event(adapter->erp_dbf, 3, "u_ust/ret"); + debug_event(adapter->erp_dbf, 3, &unit->fcp_lun, sizeof (fcp_lun_t)); + debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int)); + debug_event(adapter->erp_dbf, 3, &retval, sizeof (int)); + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_unit_strategy_clearstati(struct zfcp_unit *unit) +{ + int retval = 0; + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "u_ustclst"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, sizeof (fcp_lun_t)); + + atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING | + ZFCP_STATUS_COMMON_CLOSING | + ZFCP_STATUS_COMMON_ACCESS_DENIED | + ZFCP_STATUS_UNIT_SHARED | + ZFCP_STATUS_UNIT_READONLY, + &unit->status); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_unit_strategy_close(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_unit *unit = erp_action->unit; + + zfcp_erp_timeout_init(erp_action); + retval = zfcp_fsf_close_unit(erp_action); + if (retval == -ENOMEM) { + debug_text_event(adapter->erp_dbf, 5, "u_ustc_nomem"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, + sizeof (fcp_lun_t)); + retval = ZFCP_ERP_NOMEM; + goto out; + } + erp_action->step = ZFCP_ERP_STEP_UNIT_CLOSING; + if (retval != 0) { + debug_text_event(adapter->erp_dbf, 5, "u_ustc_cuf"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, + sizeof (fcp_lun_t)); + /* could not send 'close', fail */ + retval = ZFCP_ERP_FAILED; + goto out; + } + debug_text_event(adapter->erp_dbf, 6, "u_ustc_cuok"); + debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t)); + retval = ZFCP_ERP_CONTINUES; + + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: ZFCP_ERP_CONTINUES - action continues (asynchronously) + * ZFCP_ERP_FAILED - action finished unsuccessfully + */ +static int +zfcp_erp_unit_strategy_open(struct zfcp_erp_action *erp_action) +{ + int retval; + struct zfcp_adapter *adapter = erp_action->adapter; + struct zfcp_unit *unit = erp_action->unit; + + zfcp_erp_timeout_init(erp_action); + retval = zfcp_fsf_open_unit(erp_action); + if (retval == -ENOMEM) { + debug_text_event(adapter->erp_dbf, 5, "u_usto_nomem"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, + sizeof (fcp_lun_t)); + retval = ZFCP_ERP_NOMEM; + goto out; + } + erp_action->step = ZFCP_ERP_STEP_UNIT_OPENING; + if (retval != 0) { + debug_text_event(adapter->erp_dbf, 5, "u_usto_ouf"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, + sizeof (fcp_lun_t)); + /* could not send 'open', fail */ + retval = ZFCP_ERP_FAILED; + goto out; + } + debug_text_event(adapter->erp_dbf, 6, "u_usto_ouok"); + debug_event(adapter->erp_dbf, 6, &unit->fcp_lun, sizeof (fcp_lun_t)); + retval = ZFCP_ERP_CONTINUES; + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static inline void +zfcp_erp_timeout_init(struct zfcp_erp_action *erp_action) +{ + init_timer(&erp_action->timer); + erp_action->timer.function = zfcp_erp_timeout_handler; + erp_action->timer.data = (unsigned long) erp_action; + /* jiffies will be added in zfcp_fsf_req_send */ + erp_action->timer.expires = ZFCP_ERP_FSFREQ_TIMEOUT; +} + +/* + * function: + * + * purpose: enqueue the specified error recovery action, if needed + * + * returns: + */ +static int +zfcp_erp_action_enqueue(int action, + struct zfcp_adapter *adapter, + struct zfcp_port *port, struct zfcp_unit *unit) +{ + int retval = 1; + struct zfcp_erp_action *erp_action = NULL; + int stronger_action = 0; + u32 status = 0; + + /* + * We need some rules here which check whether we really need + * this action or whether we should just drop it. + * E.g. if there is a unfinished 'Reopen Port' request then we drop a + * 'Reopen Unit' request for an associated unit since we can't + * satisfy this request now. A 'Reopen Port' action will trigger + * 'Reopen Unit' actions when it completes. + * Thus, there are only actions in the queue which can immediately be + * executed. This makes the processing of the action queue more + * efficient. + */ + + if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_ERP_THREAD_UP, + &adapter->status)) + return -EIO; + + debug_event(adapter->erp_dbf, 4, &action, sizeof (int)); + /* check whether we really need this */ + switch (action) { + case ZFCP_ERP_ACTION_REOPEN_UNIT: + if (atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status)) { + debug_text_event(adapter->erp_dbf, 4, "u_actenq_drp"); + debug_event(adapter->erp_dbf, 4, &port->wwpn, + sizeof (wwn_t)); + debug_event(adapter->erp_dbf, 4, &unit->fcp_lun, + sizeof (fcp_lun_t)); + goto out; + } + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_RUNNING, &port->status) || + atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) { + goto out; + } + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_UNBLOCKED, &port->status)) { + stronger_action = ZFCP_ERP_ACTION_REOPEN_PORT; + unit = NULL; + } + /* fall through !!! */ + + case ZFCP_ERP_ACTION_REOPEN_PORT: + if (atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &port->status)) { + debug_text_event(adapter->erp_dbf, 4, "p_actenq_drp"); + debug_event(adapter->erp_dbf, 4, &port->wwpn, + sizeof (wwn_t)); + goto out; + } + /* fall through !!! */ + + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + if (atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &port->status) + && port->erp_action.action == + ZFCP_ERP_ACTION_REOPEN_PORT_FORCED) { + debug_text_event(adapter->erp_dbf, 4, "pf_actenq_drp"); + debug_event(adapter->erp_dbf, 4, &port->wwpn, + sizeof (wwn_t)); + goto out; + } + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_RUNNING, &adapter->status) || + atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) { + goto out; + } + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_UNBLOCKED, &adapter->status)) { + stronger_action = ZFCP_ERP_ACTION_REOPEN_ADAPTER; + port = NULL; + } + /* fall through !!! */ + + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + if (atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status)) { + debug_text_event(adapter->erp_dbf, 4, "a_actenq_drp"); + goto out; + } + break; + + default: + debug_text_exception(adapter->erp_dbf, 1, "a_actenq_bug"); + debug_event(adapter->erp_dbf, 1, &action, sizeof (int)); + ZFCP_LOG_NORMAL("bug: unknown erp action requested " + "on adapter %s (action=%d)\n", + zfcp_get_busid_by_adapter(adapter), action); + goto out; + } + + /* check whether we need something stronger first */ + if (stronger_action) { + debug_text_event(adapter->erp_dbf, 4, "a_actenq_str"); + debug_event(adapter->erp_dbf, 4, &stronger_action, + sizeof (int)); + ZFCP_LOG_DEBUG("stronger erp action %d needed before " + "erp action %d on adapter %s\n", + stronger_action, action, + zfcp_get_busid_by_adapter(adapter)); + action = stronger_action; + } + + /* mark adapter to have some error recovery pending */ + atomic_set_mask(ZFCP_STATUS_ADAPTER_ERP_PENDING, &adapter->status); + + /* setup error recovery action */ + switch (action) { + + case ZFCP_ERP_ACTION_REOPEN_UNIT: + zfcp_unit_get(unit); + atomic_set_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status); + erp_action = &unit->erp_action; + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_RUNNING, &unit->status)) + status = ZFCP_STATUS_ERP_CLOSE_ONLY; + break; + + case ZFCP_ERP_ACTION_REOPEN_PORT: + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + zfcp_port_get(port); + zfcp_erp_action_dismiss_port(port); + atomic_set_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &port->status); + erp_action = &port->erp_action; + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_RUNNING, &port->status)) + status = ZFCP_STATUS_ERP_CLOSE_ONLY; + break; + + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + zfcp_adapter_get(adapter); + zfcp_erp_action_dismiss_adapter(adapter); + atomic_set_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status); + erp_action = &adapter->erp_action; + if (!atomic_test_mask + (ZFCP_STATUS_COMMON_RUNNING, &adapter->status)) + status = ZFCP_STATUS_ERP_CLOSE_ONLY; + break; + } + + debug_text_event(adapter->erp_dbf, 4, "a_actenq"); + + memset(erp_action, 0, sizeof (struct zfcp_erp_action)); + erp_action->adapter = adapter; + erp_action->port = port; + erp_action->unit = unit; + erp_action->action = action; + erp_action->status = status; + + ++adapter->erp_total_count; + + /* finally put it into 'ready' queue and kick erp thread */ + list_add(&erp_action->list, &adapter->erp_ready_head); + up(&adapter->erp_ready_sem); + retval = 0; + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static int +zfcp_erp_action_dequeue(struct zfcp_erp_action *erp_action) +{ + int retval = 0; + struct zfcp_adapter *adapter = erp_action->adapter; + + --adapter->erp_total_count; + if (erp_action->status & ZFCP_STATUS_ERP_LOWMEM) { + --adapter->erp_low_mem_count; + erp_action->status &= ~ZFCP_STATUS_ERP_LOWMEM; + } + + debug_text_event(adapter->erp_dbf, 4, "a_actdeq"); + debug_event(adapter->erp_dbf, 4, &erp_action->action, sizeof (int)); + list_del(&erp_action->list); + switch (erp_action->action) { + case ZFCP_ERP_ACTION_REOPEN_UNIT: + atomic_clear_mask(ZFCP_STATUS_COMMON_ERP_INUSE, + &erp_action->unit->status); + break; + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + case ZFCP_ERP_ACTION_REOPEN_PORT: + atomic_clear_mask(ZFCP_STATUS_COMMON_ERP_INUSE, + &erp_action->port->status); + break; + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + atomic_clear_mask(ZFCP_STATUS_COMMON_ERP_INUSE, + &erp_action->adapter->status); + break; + default: + /* bug */ + break; + } + return retval; +} + +/** + * zfcp_erp_action_cleanup + * + * Register unit with scsi stack if appropiate and fix reference counts. + * Note: Temporary units are not registered with scsi stack. + */ +static void +zfcp_erp_action_cleanup(int action, struct zfcp_adapter *adapter, + struct zfcp_port *port, struct zfcp_unit *unit, + int result) +{ + switch (action) { + case ZFCP_ERP_ACTION_REOPEN_UNIT: + if ((result == ZFCP_ERP_SUCCEEDED) + && (!atomic_test_mask(ZFCP_STATUS_UNIT_TEMPORARY, + &unit->status)) + && (!unit->device)) + scsi_add_device(unit->port->adapter->scsi_host, 0, + unit->port->scsi_id, unit->scsi_lun); + zfcp_unit_put(unit); + break; + case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED: + case ZFCP_ERP_ACTION_REOPEN_PORT: + zfcp_port_put(port); + break; + case ZFCP_ERP_ACTION_REOPEN_ADAPTER: + zfcp_adapter_put(adapter); + break; + default: + break; + } +} + + +/* + * function: + * + * purpose: + * + * returns: FIXME + */ +static int +zfcp_erp_action_dismiss_adapter(struct zfcp_adapter *adapter) +{ + int retval = 0; + struct zfcp_port *port; + + debug_text_event(adapter->erp_dbf, 5, "a_actab"); + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status)) + zfcp_erp_action_dismiss(&adapter->erp_action); + else + list_for_each_entry(port, &adapter->port_list_head, list) + zfcp_erp_action_dismiss_port(port); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: FIXME + */ +static int +zfcp_erp_action_dismiss_port(struct zfcp_port *port) +{ + int retval = 0; + struct zfcp_unit *unit; + struct zfcp_adapter *adapter = port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "p_actab"); + debug_event(adapter->erp_dbf, 5, &port->wwpn, sizeof (wwn_t)); + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &port->status)) + zfcp_erp_action_dismiss(&port->erp_action); + else + list_for_each_entry(unit, &port->unit_list_head, list) + zfcp_erp_action_dismiss_unit(unit); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: FIXME + */ +static int +zfcp_erp_action_dismiss_unit(struct zfcp_unit *unit) +{ + int retval = 0; + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 5, "u_actab"); + debug_event(adapter->erp_dbf, 5, &unit->fcp_lun, sizeof (fcp_lun_t)); + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status)) + zfcp_erp_action_dismiss(&unit->erp_action); + + return retval; +} + +/* + * function: + * + * purpose: moves erp_action to 'erp running list' + * + * returns: + */ +static inline void +zfcp_erp_action_to_running(struct zfcp_erp_action *erp_action) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 6, "a_toru"); + debug_event(adapter->erp_dbf, 6, &erp_action->action, sizeof (int)); + list_move(&erp_action->list, &erp_action->adapter->erp_running_head); +} + +/* + * function: + * + * purpose: moves erp_action to 'erp ready list' + * + * returns: + */ +static inline void +zfcp_erp_action_to_ready(struct zfcp_erp_action *erp_action) +{ + struct zfcp_adapter *adapter = erp_action->adapter; + + debug_text_event(adapter->erp_dbf, 6, "a_tore"); + debug_event(adapter->erp_dbf, 6, &erp_action->action, sizeof (int)); + list_move(&erp_action->list, &erp_action->adapter->erp_ready_head); +} + +/* + * function: zfcp_erp_port_access_denied + * + * purpose: + */ +void +zfcp_erp_port_access_denied(struct zfcp_port *port) +{ + struct zfcp_adapter *adapter = port->adapter; + unsigned long flags; + + debug_text_event(adapter->erp_dbf, 3, "p_access_block"); + debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof(wwn_t)); + read_lock_irqsave(&zfcp_data.config_lock, flags); + zfcp_erp_modify_port_status(port, ZFCP_STATUS_COMMON_ERP_FAILED | + ZFCP_STATUS_COMMON_ACCESS_DENIED, ZFCP_SET); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); +} + +/* + * function: zfcp_erp_unit_access_denied + * + * purpose: + */ +void +zfcp_erp_unit_access_denied(struct zfcp_unit *unit) +{ + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 3, "u_access_block"); + debug_event(adapter->erp_dbf, 3, &unit->fcp_lun, sizeof(fcp_lun_t)); + zfcp_erp_modify_unit_status(unit, ZFCP_STATUS_COMMON_ERP_FAILED | + ZFCP_STATUS_COMMON_ACCESS_DENIED, ZFCP_SET); +} + +/* + * function: zfcp_erp_adapter_access_changed + * + * purpose: + */ +void +zfcp_erp_adapter_access_changed(struct zfcp_adapter *adapter) +{ + struct zfcp_port *port; + unsigned long flags; + + debug_text_event(adapter->erp_dbf, 3, "a_access_unblock"); + debug_event(adapter->erp_dbf, 3, &adapter->name, 8); + + zfcp_erp_port_access_changed(adapter->nameserver_port); + read_lock_irqsave(&zfcp_data.config_lock, flags); + list_for_each_entry(port, &adapter->port_list_head, list) + if (port != adapter->nameserver_port) + zfcp_erp_port_access_changed(port); + read_unlock_irqrestore(&zfcp_data.config_lock, flags); +} + +/* + * function: zfcp_erp_port_access_changed + * + * purpose: + */ +void +zfcp_erp_port_access_changed(struct zfcp_port *port) +{ + struct zfcp_adapter *adapter = port->adapter; + struct zfcp_unit *unit; + + debug_text_event(adapter->erp_dbf, 3, "p_access_unblock"); + debug_event(adapter->erp_dbf, 3, &port->wwpn, sizeof(wwn_t)); + + if (!atomic_test_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED, + &port->status)) { + if (!atomic_test_mask(ZFCP_STATUS_PORT_WKA, &port->status)) + list_for_each_entry(unit, &port->unit_list_head, list) + zfcp_erp_unit_access_changed(unit); + return; + } + + ZFCP_LOG_NORMAL("reopen of port 0x%016Lx on adapter %s " + "(due to ACT update)\n", + port->wwpn, zfcp_get_busid_by_adapter(adapter)); + if (zfcp_erp_port_reopen(port, ZFCP_STATUS_COMMON_ERP_FAILED) != 0) + ZFCP_LOG_NORMAL("failed reopen of port" + "(adapter %s, wwpn=0x%016Lx)\n", + zfcp_get_busid_by_adapter(adapter), port->wwpn); +} + +/* + * function: zfcp_erp_unit_access_changed + * + * purpose: + */ +void +zfcp_erp_unit_access_changed(struct zfcp_unit *unit) +{ + struct zfcp_adapter *adapter = unit->port->adapter; + + debug_text_event(adapter->erp_dbf, 3, "u_access_unblock"); + debug_event(adapter->erp_dbf, 3, &unit->fcp_lun, sizeof(fcp_lun_t)); + + if (!atomic_test_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED, &unit->status)) + return; + + ZFCP_LOG_NORMAL("reopen of unit 0x%016Lx on port 0x%016Lx " + " on adapter %s (due to ACT update)\n", + unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_adapter(adapter)); + if (zfcp_erp_unit_reopen(unit, ZFCP_STATUS_COMMON_ERP_FAILED) != 0) + ZFCP_LOG_NORMAL("failed reopen of unit (adapter %s, " + "wwpn=0x%016Lx, fcp_lun=0x%016Lx)\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, unit->fcp_lun); +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_ext.h b/drivers/s390/scsi/zfcp_ext.h new file mode 100644 index 000000000000..d5fd43352071 --- /dev/null +++ b/drivers/s390/scsi/zfcp_ext.h @@ -0,0 +1,186 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_ext.h + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef ZFCP_EXT_H +#define ZFCP_EXT_H + +#define ZFCP_EXT_REVISION "$Revision: 1.62 $" + +#include "zfcp_def.h" + +extern struct zfcp_data zfcp_data; + +/******************************** SYSFS *************************************/ +extern int zfcp_sysfs_driver_create_files(struct device_driver *); +extern void zfcp_sysfs_driver_remove_files(struct device_driver *); +extern int zfcp_sysfs_adapter_create_files(struct device *); +extern void zfcp_sysfs_adapter_remove_files(struct device *); +extern int zfcp_sysfs_port_create_files(struct device *, u32); +extern void zfcp_sysfs_port_remove_files(struct device *, u32); +extern int zfcp_sysfs_unit_create_files(struct device *); +extern void zfcp_sysfs_unit_remove_files(struct device *); +extern void zfcp_sysfs_port_release(struct device *); +extern void zfcp_sysfs_unit_release(struct device *); + +/**************************** CONFIGURATION *********************************/ +extern struct zfcp_unit *zfcp_get_unit_by_lun(struct zfcp_port *, fcp_lun_t); +extern struct zfcp_port *zfcp_get_port_by_wwpn(struct zfcp_adapter *, wwn_t); +extern struct zfcp_port *zfcp_get_port_by_did(struct zfcp_adapter *, u32); +struct zfcp_adapter *zfcp_get_adapter_by_busid(char *); +extern struct zfcp_adapter *zfcp_adapter_enqueue(struct ccw_device *); +extern int zfcp_adapter_debug_register(struct zfcp_adapter *); +extern void zfcp_adapter_dequeue(struct zfcp_adapter *); +extern void zfcp_adapter_debug_unregister(struct zfcp_adapter *); +extern struct zfcp_port *zfcp_port_enqueue(struct zfcp_adapter *, wwn_t, + u32, u32); +extern void zfcp_port_dequeue(struct zfcp_port *); +extern struct zfcp_unit *zfcp_unit_enqueue(struct zfcp_port *, fcp_lun_t); +extern void zfcp_unit_dequeue(struct zfcp_unit *); + +/******************************* S/390 IO ************************************/ +extern int zfcp_ccw_register(void); +extern void zfcp_ccw_unregister(void); + +extern void zfcp_qdio_zero_sbals(struct qdio_buffer **, int, int); +extern int zfcp_qdio_allocate(struct zfcp_adapter *); +extern int zfcp_qdio_allocate_queues(struct zfcp_adapter *); +extern void zfcp_qdio_free_queues(struct zfcp_adapter *); +extern int zfcp_qdio_determine_pci(struct zfcp_qdio_queue *, + struct zfcp_fsf_req *); +extern int zfcp_qdio_reqid_check(struct zfcp_adapter *, void *); + +extern volatile struct qdio_buffer_element *zfcp_qdio_sbale_req + (struct zfcp_fsf_req *, int, int); +extern volatile struct qdio_buffer_element *zfcp_qdio_sbale_curr + (struct zfcp_fsf_req *); +extern int zfcp_qdio_sbals_from_sg + (struct zfcp_fsf_req *, unsigned long, struct scatterlist *, int, int); +extern int zfcp_qdio_sbals_from_scsicmnd + (struct zfcp_fsf_req *, unsigned long, struct scsi_cmnd *); + + +/******************************** FSF ****************************************/ +extern int zfcp_fsf_open_port(struct zfcp_erp_action *); +extern int zfcp_fsf_close_port(struct zfcp_erp_action *); +extern int zfcp_fsf_close_physical_port(struct zfcp_erp_action *); + +extern int zfcp_fsf_open_unit(struct zfcp_erp_action *); +extern int zfcp_fsf_close_unit(struct zfcp_erp_action *); + +extern int zfcp_fsf_exchange_config_data(struct zfcp_erp_action *); +extern int zfcp_fsf_exchange_port_data(struct zfcp_adapter *, + struct fsf_qtcb_bottom_port *); +extern int zfcp_fsf_control_file(struct zfcp_adapter *, struct zfcp_fsf_req **, + u32, u32, struct zfcp_sg_list *); +extern void zfcp_fsf_request_timeout_handler(unsigned long); +extern void zfcp_fsf_scsi_er_timeout_handler(unsigned long); +extern int zfcp_fsf_req_dismiss_all(struct zfcp_adapter *); +extern int zfcp_fsf_status_read(struct zfcp_adapter *, int); +extern int zfcp_fsf_req_create(struct zfcp_adapter *, u32, int, mempool_t *, + unsigned long *, struct zfcp_fsf_req **); +extern int zfcp_fsf_send_ct(struct zfcp_send_ct *, mempool_t *, + struct zfcp_erp_action *); +extern int zfcp_fsf_send_els(struct zfcp_send_els *); +extern int zfcp_fsf_req_wait_and_cleanup(struct zfcp_fsf_req *, int, u32 *); +extern int zfcp_fsf_send_fcp_command_task(struct zfcp_adapter *, + struct zfcp_unit *, + struct scsi_cmnd *, + struct timer_list*, int); +extern int zfcp_fsf_req_complete(struct zfcp_fsf_req *); +extern void zfcp_fsf_incoming_els(struct zfcp_fsf_req *); +extern void zfcp_fsf_req_cleanup(struct zfcp_fsf_req *); +extern struct zfcp_fsf_req *zfcp_fsf_send_fcp_command_task_management( + struct zfcp_adapter *, struct zfcp_unit *, u8, int); +extern struct zfcp_fsf_req *zfcp_fsf_abort_fcp_command( + unsigned long, struct zfcp_adapter *, struct zfcp_unit *, int); + +/******************************* FC/FCP **************************************/ +extern int zfcp_nameserver_enqueue(struct zfcp_adapter *); +extern int zfcp_ns_gid_pn_request(struct zfcp_erp_action *); +extern int zfcp_check_ct_response(struct ct_hdr *); +extern int zfcp_handle_els_rjt(u32, struct zfcp_ls_rjt_par *); + +/******************************* SCSI ****************************************/ +extern int zfcp_adapter_scsi_register(struct zfcp_adapter *); +extern void zfcp_adapter_scsi_unregister(struct zfcp_adapter *); +extern void zfcp_set_fcp_dl(struct fcp_cmnd_iu *, fcp_dl_t); +extern char *zfcp_get_fcp_rsp_info_ptr(struct fcp_rsp_iu *); +extern void set_host_byte(u32 *, char); +extern void set_driver_byte(u32 *, char); +extern char *zfcp_get_fcp_sns_info_ptr(struct fcp_rsp_iu *); +extern void zfcp_fsf_start_scsi_er_timer(struct zfcp_adapter *); +extern fcp_dl_t zfcp_get_fcp_dl(struct fcp_cmnd_iu *); + +extern int zfcp_scsi_command_async(struct zfcp_adapter *,struct zfcp_unit *, + struct scsi_cmnd *, struct timer_list *); +extern int zfcp_scsi_command_sync(struct zfcp_unit *, struct scsi_cmnd *, + struct timer_list *); +extern struct scsi_transport_template *zfcp_transport_template; +extern struct fc_function_template zfcp_transport_functions; + +/******************************** ERP ****************************************/ +extern void zfcp_erp_modify_adapter_status(struct zfcp_adapter *, u32, int); +extern int zfcp_erp_adapter_reopen(struct zfcp_adapter *, int); +extern int zfcp_erp_adapter_shutdown(struct zfcp_adapter *, int); +extern void zfcp_erp_adapter_failed(struct zfcp_adapter *); + +extern void zfcp_erp_modify_port_status(struct zfcp_port *, u32, int); +extern int zfcp_erp_port_reopen(struct zfcp_port *, int); +extern int zfcp_erp_port_shutdown(struct zfcp_port *, int); +extern int zfcp_erp_port_forced_reopen(struct zfcp_port *, int); +extern void zfcp_erp_port_failed(struct zfcp_port *); +extern int zfcp_erp_port_reopen_all(struct zfcp_adapter *, int); + +extern void zfcp_erp_modify_unit_status(struct zfcp_unit *, u32, int); +extern int zfcp_erp_unit_reopen(struct zfcp_unit *, int); +extern int zfcp_erp_unit_shutdown(struct zfcp_unit *, int); +extern void zfcp_erp_unit_failed(struct zfcp_unit *); + +extern int zfcp_erp_thread_setup(struct zfcp_adapter *); +extern int zfcp_erp_thread_kill(struct zfcp_adapter *); +extern int zfcp_erp_wait(struct zfcp_adapter *); +extern int zfcp_erp_async_handler(struct zfcp_erp_action *, unsigned long); + +extern int zfcp_test_link(struct zfcp_port *); + +extern void zfcp_erp_port_access_denied(struct zfcp_port *); +extern void zfcp_erp_unit_access_denied(struct zfcp_unit *); +extern void zfcp_erp_adapter_access_changed(struct zfcp_adapter *); +extern void zfcp_erp_port_access_changed(struct zfcp_port *); +extern void zfcp_erp_unit_access_changed(struct zfcp_unit *); + +/******************************** AUX ****************************************/ +extern void zfcp_cmd_dbf_event_fsf(const char *, struct zfcp_fsf_req *, + void *, int); +extern void zfcp_cmd_dbf_event_scsi(const char *, struct scsi_cmnd *); +extern void zfcp_in_els_dbf_event(struct zfcp_adapter *, const char *, + struct fsf_status_read_buffer *, int); +#endif /* ZFCP_EXT_H */ diff --git a/drivers/s390/scsi/zfcp_fsf.c b/drivers/s390/scsi/zfcp_fsf.c new file mode 100644 index 000000000000..578b9fbe5206 --- /dev/null +++ b/drivers/s390/scsi/zfcp_fsf.c @@ -0,0 +1,5087 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_fsf.c + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.com> + * Volker Sameske <sameske@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_FSF_C_REVISION "$Revision: 1.92 $" + +#include "zfcp_ext.h" + +static int zfcp_fsf_exchange_config_data_handler(struct zfcp_fsf_req *); +static void zfcp_fsf_exchange_port_data_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_open_port_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_close_port_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_close_physical_port_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_open_unit_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_close_unit_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_send_fcp_command_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_send_fcp_command_task_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_send_fcp_command_task_management_handler( + struct zfcp_fsf_req *); +static int zfcp_fsf_abort_fcp_command_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_status_read_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_send_ct_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_send_els_handler(struct zfcp_fsf_req *); +static int zfcp_fsf_control_file_handler(struct zfcp_fsf_req *); +static inline int zfcp_fsf_req_sbal_check( + unsigned long *, struct zfcp_qdio_queue *, int); +static inline int zfcp_use_one_sbal( + struct scatterlist *, int, struct scatterlist *, int); +static struct zfcp_fsf_req *zfcp_fsf_req_alloc(mempool_t *, int); +static int zfcp_fsf_req_send(struct zfcp_fsf_req *, struct timer_list *); +static int zfcp_fsf_protstatus_eval(struct zfcp_fsf_req *); +static int zfcp_fsf_fsfstatus_eval(struct zfcp_fsf_req *); +static int zfcp_fsf_fsfstatus_qual_eval(struct zfcp_fsf_req *); +static int zfcp_fsf_req_dispatch(struct zfcp_fsf_req *); +static void zfcp_fsf_req_dismiss(struct zfcp_fsf_req *); +static void zfcp_fsf_req_free(struct zfcp_fsf_req *); + +/* association between FSF command and FSF QTCB type */ +static u32 fsf_qtcb_type[] = { + [FSF_QTCB_FCP_CMND] = FSF_IO_COMMAND, + [FSF_QTCB_ABORT_FCP_CMND] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_OPEN_PORT_WITH_DID] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_OPEN_LUN] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_CLOSE_LUN] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_CLOSE_PORT] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_CLOSE_PHYSICAL_PORT] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_SEND_ELS] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_SEND_GENERIC] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_EXCHANGE_CONFIG_DATA] = FSF_CONFIG_COMMAND, + [FSF_QTCB_EXCHANGE_PORT_DATA] = FSF_PORT_COMMAND, + [FSF_QTCB_DOWNLOAD_CONTROL_FILE] = FSF_SUPPORT_COMMAND, + [FSF_QTCB_UPLOAD_CONTROL_FILE] = FSF_SUPPORT_COMMAND +}; + +static const char zfcp_act_subtable_type[5][8] = { + "unknown", "OS", "WWPN", "DID", "LUN" +}; + +/****************************************************************/ +/*************** FSF related Functions *************************/ +/****************************************************************/ + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_FSF + +/* + * function: zfcp_fsf_req_alloc + * + * purpose: Obtains an fsf_req and potentially a qtcb (for all but + * unsolicited requests) via helper functions + * Does some initial fsf request set-up. + * + * returns: pointer to allocated fsf_req if successfull + * NULL otherwise + * + * locks: none + * + */ +static struct zfcp_fsf_req * +zfcp_fsf_req_alloc(mempool_t *pool, int req_flags) +{ + size_t size; + void *ptr; + struct zfcp_fsf_req *fsf_req = NULL; + + if (req_flags & ZFCP_REQ_NO_QTCB) + size = sizeof(struct zfcp_fsf_req); + else + size = sizeof(struct zfcp_fsf_req_pool_element); + + if (likely(pool != NULL)) + ptr = mempool_alloc(pool, GFP_ATOMIC); + else + ptr = kmalloc(size, GFP_ATOMIC); + + if (unlikely(NULL == ptr)) + goto out; + + memset(ptr, 0, size); + + if (req_flags & ZFCP_REQ_NO_QTCB) { + fsf_req = (struct zfcp_fsf_req *) ptr; + } else { + fsf_req = &((struct zfcp_fsf_req_pool_element *) ptr)->fsf_req; + fsf_req->qtcb = + &((struct zfcp_fsf_req_pool_element *) ptr)->qtcb; + } + + fsf_req->pool = pool; + + out: + return fsf_req; +} + +/* + * function: zfcp_fsf_req_free + * + * purpose: Frees the memory of an fsf_req (and potentially a qtcb) or + * returns it into the pool via helper functions. + * + * returns: sod all + * + * locks: none + */ +static void +zfcp_fsf_req_free(struct zfcp_fsf_req *fsf_req) +{ + if (likely(fsf_req->pool != NULL)) + mempool_free(fsf_req, fsf_req->pool); + else + kfree(fsf_req); +} + +/* + * function: + * + * purpose: + * + * returns: + * + * note: qdio queues shall be down (no ongoing inbound processing) + */ +int +zfcp_fsf_req_dismiss_all(struct zfcp_adapter *adapter) +{ + int retval = 0; + struct zfcp_fsf_req *fsf_req, *tmp; + + list_for_each_entry_safe(fsf_req, tmp, &adapter->fsf_req_list_head, + list) + zfcp_fsf_req_dismiss(fsf_req); + /* wait_event_timeout? */ + while (!list_empty(&adapter->fsf_req_list_head)) { + ZFCP_LOG_DEBUG("fsf req list of adapter %s not yet empty\n", + zfcp_get_busid_by_adapter(adapter)); + /* wait for woken intiators to clean up their requests */ + msleep(jiffies_to_msecs(ZFCP_FSFREQ_CLEANUP_TIMEOUT)); + } + + /* consistency check */ + if (atomic_read(&adapter->fsf_reqs_active)) { + ZFCP_LOG_NORMAL("bug: There are still %d FSF requests pending " + "on adapter %s after cleanup.\n", + atomic_read(&adapter->fsf_reqs_active), + zfcp_get_busid_by_adapter(adapter)); + atomic_set(&adapter->fsf_reqs_active, 0); + } + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +static void +zfcp_fsf_req_dismiss(struct zfcp_fsf_req *fsf_req) +{ + fsf_req->status |= ZFCP_STATUS_FSFREQ_DISMISSED; + zfcp_fsf_req_complete(fsf_req); +} + +/* + * function: zfcp_fsf_req_complete + * + * purpose: Updates active counts and timers for openfcp-reqs + * May cleanup request after req_eval returns + * + * returns: 0 - success + * !0 - failure + * + * context: + */ +int +zfcp_fsf_req_complete(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + int cleanup; + struct zfcp_adapter *adapter = fsf_req->adapter; + + /* do some statistics */ + atomic_dec(&adapter->fsf_reqs_active); + + if (unlikely(fsf_req->fsf_command == FSF_QTCB_UNSOLICITED_STATUS)) { + ZFCP_LOG_DEBUG("Status read response received\n"); + /* + * Note: all cleanup handling is done in the callchain of + * the function call-chain below. + */ + zfcp_fsf_status_read_handler(fsf_req); + goto out; + } else + zfcp_fsf_protstatus_eval(fsf_req); + + /* + * fsf_req may be deleted due to waking up functions, so + * cleanup is saved here and used later + */ + if (likely(fsf_req->status & ZFCP_STATUS_FSFREQ_CLEANUP)) + cleanup = 1; + else + cleanup = 0; + + fsf_req->status |= ZFCP_STATUS_FSFREQ_COMPLETED; + + /* cleanup request if requested by initiator */ + if (likely(cleanup)) { + ZFCP_LOG_TRACE("removing FSF request %p\n", fsf_req); + /* + * lock must not be held here since it will be + * grabed by the called routine, too + */ + zfcp_fsf_req_cleanup(fsf_req); + } else { + /* notify initiator waiting for the requests completion */ + ZFCP_LOG_TRACE("waking initiator of FSF request %p\n",fsf_req); + /* + * FIXME: Race! We must not access fsf_req here as it might have been + * cleaned up already due to the set ZFCP_STATUS_FSFREQ_COMPLETED + * flag. It's an improbable case. But, we have the same paranoia for + * the cleanup flag already. + * Might better be handled using complete()? + * (setting the flag and doing wakeup ought to be atomic + * with regard to checking the flag as long as waitqueue is + * part of the to be released structure) + */ + wake_up(&fsf_req->completion_wq); + } + + out: + return retval; +} + +/* + * function: zfcp_fsf_protstatus_eval + * + * purpose: evaluates the QTCB of the finished FSF request + * and initiates appropriate actions + * (usually calling FSF command specific handlers) + * + * returns: + * + * context: + * + * locks: + */ +static int +zfcp_fsf_protstatus_eval(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + struct zfcp_adapter *adapter = fsf_req->adapter; + + ZFCP_LOG_DEBUG("QTCB is at %p\n", fsf_req->qtcb); + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) { + ZFCP_LOG_DEBUG("fsf_req 0x%lx has been dismissed\n", + (unsigned long) fsf_req); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR | + ZFCP_STATUS_FSFREQ_RETRY; /* only for SCSI cmnds. */ + zfcp_cmd_dbf_event_fsf("dismiss", fsf_req, NULL, 0); + goto skip_protstatus; + } + + /* log additional information provided by FSF (if any) */ + if (unlikely(fsf_req->qtcb->header.log_length)) { + /* do not trust them ;-) */ + if (fsf_req->qtcb->header.log_start > sizeof(struct fsf_qtcb)) { + ZFCP_LOG_NORMAL + ("bug: ULP (FSF logging) log data starts " + "beyond end of packet header. Ignored. " + "(start=%i, size=%li)\n", + fsf_req->qtcb->header.log_start, + sizeof(struct fsf_qtcb)); + goto forget_log; + } + if ((size_t) (fsf_req->qtcb->header.log_start + + fsf_req->qtcb->header.log_length) + > sizeof(struct fsf_qtcb)) { + ZFCP_LOG_NORMAL("bug: ULP (FSF logging) log data ends " + "beyond end of packet header. Ignored. " + "(start=%i, length=%i, size=%li)\n", + fsf_req->qtcb->header.log_start, + fsf_req->qtcb->header.log_length, + sizeof(struct fsf_qtcb)); + goto forget_log; + } + ZFCP_LOG_TRACE("ULP log data: \n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, + (char *) fsf_req->qtcb + + fsf_req->qtcb->header.log_start, + fsf_req->qtcb->header.log_length); + } + forget_log: + + /* evaluate FSF Protocol Status */ + switch (fsf_req->qtcb->prefix.prot_status) { + + case FSF_PROT_GOOD: + ZFCP_LOG_TRACE("FSF_PROT_GOOD\n"); + break; + + case FSF_PROT_FSF_STATUS_PRESENTED: + ZFCP_LOG_TRACE("FSF_PROT_FSF_STATUS_PRESENTED\n"); + break; + + case FSF_PROT_QTCB_VERSION_ERROR: + ZFCP_LOG_FLAGS(0, "FSF_PROT_QTCB_VERSION_ERROR\n"); + ZFCP_LOG_NORMAL("error: The adapter %s contains " + "microcode of version 0x%x, the device driver " + "only supports 0x%x. Aborting.\n", + zfcp_get_busid_by_adapter(adapter), + fsf_req->qtcb->prefix.prot_status_qual. + version_error.fsf_version, ZFCP_QTCB_VERSION); + /* stop operation for this adapter */ + debug_text_exception(adapter->erp_dbf, 0, "prot_ver_err"); + zfcp_erp_adapter_shutdown(adapter, 0); + zfcp_cmd_dbf_event_fsf("qverserr", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PROT_SEQ_NUMB_ERROR: + ZFCP_LOG_FLAGS(0, "FSF_PROT_SEQ_NUMB_ERROR\n"); + ZFCP_LOG_NORMAL("bug: Sequence number mismatch between " + "driver (0x%x) and adapter %s (0x%x). " + "Restarting all operations on this adapter.\n", + fsf_req->qtcb->prefix.req_seq_no, + zfcp_get_busid_by_adapter(adapter), + fsf_req->qtcb->prefix.prot_status_qual. + sequence_error.exp_req_seq_no); + debug_text_exception(adapter->erp_dbf, 0, "prot_seq_err"); + /* restart operation on this adapter */ + zfcp_erp_adapter_reopen(adapter, 0); + zfcp_cmd_dbf_event_fsf("seqnoerr", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_RETRY; + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PROT_UNSUPP_QTCB_TYPE: + ZFCP_LOG_FLAGS(0, "FSF_PROT_UNSUP_QTCB_TYPE\n"); + ZFCP_LOG_NORMAL("error: Packet header type used by the " + "device driver is incompatible with " + "that used on adapter %s. " + "Stopping all operations on this adapter.\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_exception(adapter->erp_dbf, 0, "prot_unsup_qtcb"); + zfcp_erp_adapter_shutdown(adapter, 0); + zfcp_cmd_dbf_event_fsf("unsqtcbt", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PROT_HOST_CONNECTION_INITIALIZING: + ZFCP_LOG_FLAGS(1, "FSF_PROT_HOST_CONNECTION_INITIALIZING\n"); + zfcp_cmd_dbf_event_fsf("hconinit", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + atomic_set_mask(ZFCP_STATUS_ADAPTER_HOST_CON_INIT, + &(adapter->status)); + debug_text_event(adapter->erp_dbf, 3, "prot_con_init"); + break; + + case FSF_PROT_DUPLICATE_REQUEST_ID: + ZFCP_LOG_FLAGS(0, "FSF_PROT_DUPLICATE_REQUEST_IDS\n"); + if (fsf_req->qtcb) { + ZFCP_LOG_NORMAL("bug: The request identifier 0x%Lx " + "to the adapter %s is ambiguous. " + "Stopping all operations on this " + "adapter.\n", + *(unsigned long long *) + (&fsf_req->qtcb->bottom.support. + req_handle), + zfcp_get_busid_by_adapter(adapter)); + } else { + ZFCP_LOG_NORMAL("bug: The request identifier %p " + "to the adapter %s is ambiguous. " + "Stopping all operations on this " + "adapter. " + "(bug: got this for an unsolicited " + "status read request)\n", + fsf_req, + zfcp_get_busid_by_adapter(adapter)); + } + debug_text_exception(adapter->erp_dbf, 0, "prot_dup_id"); + zfcp_erp_adapter_shutdown(adapter, 0); + zfcp_cmd_dbf_event_fsf("dupreqid", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PROT_LINK_DOWN: + ZFCP_LOG_FLAGS(1, "FSF_PROT_LINK_DOWN\n"); + /* + * 'test and set' is not atomic here - + * it's ok as long as calls to our response queue handler + * (and thus execution of this code here) are serialized + * by the qdio module + */ + if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED, + &adapter->status)) { + switch (fsf_req->qtcb->prefix.prot_status_qual. + locallink_error.code) { + case FSF_PSQ_LINK_NOLIGHT: + ZFCP_LOG_INFO("The local link to adapter %s " + "is down (no light detected).\n", + zfcp_get_busid_by_adapter( + adapter)); + break; + case FSF_PSQ_LINK_WRAPPLUG: + ZFCP_LOG_INFO("The local link to adapter %s " + "is down (wrap plug detected).\n", + zfcp_get_busid_by_adapter( + adapter)); + break; + case FSF_PSQ_LINK_NOFCP: + ZFCP_LOG_INFO("The local link to adapter %s " + "is down (adjacent node on " + "link does not support FCP).\n", + zfcp_get_busid_by_adapter( + adapter)); + break; + default: + ZFCP_LOG_INFO("The local link to adapter %s " + "is down " + "(warning: unknown reason " + "code).\n", + zfcp_get_busid_by_adapter( + adapter)); + break; + + } + /* + * Due to the 'erp failed' flag the adapter won't + * be recovered but will be just set to 'blocked' + * state. All subordinary devices will have state + * 'blocked' and 'erp failed', too. + * Thus the adapter is still able to provide + * 'link up' status without being flooded with + * requests. + * (note: even 'close port' is not permitted) + */ + ZFCP_LOG_INFO("Stopping all operations for adapter " + "%s.\n", + zfcp_get_busid_by_adapter(adapter)); + atomic_set_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED | + ZFCP_STATUS_COMMON_ERP_FAILED, + &adapter->status); + zfcp_erp_adapter_reopen(adapter, 0); + debug_text_event(adapter->erp_dbf, 1, "prot_link_down"); + } + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PROT_REEST_QUEUE: + ZFCP_LOG_FLAGS(1, "FSF_PROT_REEST_QUEUE\n"); + debug_text_event(adapter->erp_dbf, 1, "prot_reest_queue"); + ZFCP_LOG_INFO("The local link to adapter with " + "%s was re-plugged. " + "Re-starting operations on this adapter.\n", + zfcp_get_busid_by_adapter(adapter)); + /* All ports should be marked as ready to run again */ + zfcp_erp_modify_adapter_status(adapter, + ZFCP_STATUS_COMMON_RUNNING, + ZFCP_SET); + zfcp_erp_adapter_reopen(adapter, + ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED + | ZFCP_STATUS_COMMON_ERP_FAILED); + zfcp_cmd_dbf_event_fsf("reestque", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PROT_ERROR_STATE: + ZFCP_LOG_FLAGS(0, "FSF_PROT_ERROR_STATE\n"); + ZFCP_LOG_NORMAL("error: The adapter %s " + "has entered the error state. " + "Restarting all operations on this " + "adapter.\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 0, "prot_err_sta"); + /* restart operation on this adapter */ + zfcp_erp_adapter_reopen(adapter, 0); + zfcp_cmd_dbf_event_fsf("proterrs", fsf_req, + &fsf_req->qtcb->prefix.prot_status_qual, + sizeof (union fsf_prot_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_RETRY; + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + default: + ZFCP_LOG_NORMAL("bug: Transfer protocol status information " + "provided by the adapter %s " + "is not compatible with the device driver. " + "Stopping all operations on this adapter. " + "(debug info 0x%x).\n", + zfcp_get_busid_by_adapter(adapter), + fsf_req->qtcb->prefix.prot_status); + debug_text_event(adapter->erp_dbf, 0, "prot_inval:"); + debug_exception(adapter->erp_dbf, 0, + &fsf_req->qtcb->prefix.prot_status, + sizeof (u32)); + zfcp_erp_adapter_shutdown(adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + } + + skip_protstatus: + /* + * always call specific handlers to give them a chance to do + * something meaningful even in error cases + */ + zfcp_fsf_fsfstatus_eval(fsf_req); + return retval; +} + +/* + * function: zfcp_fsf_fsfstatus_eval + * + * purpose: evaluates FSF status of completed FSF request + * and acts accordingly + * + * returns: + */ +static int +zfcp_fsf_fsfstatus_eval(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + + if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) { + goto skip_fsfstatus; + } + + /* evaluate FSF Status */ + switch (fsf_req->qtcb->header.fsf_status) { + case FSF_UNKNOWN_COMMAND: + ZFCP_LOG_FLAGS(0, "FSF_UNKNOWN_COMMAND\n"); + ZFCP_LOG_NORMAL("bug: Command issued by the device driver is " + "not known by the adapter %s " + "Stopping all operations on this adapter. " + "(debug info 0x%x).\n", + zfcp_get_busid_by_adapter(fsf_req->adapter), + fsf_req->qtcb->header.fsf_command); + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "fsf_s_unknown"); + zfcp_erp_adapter_shutdown(fsf_req->adapter, 0); + zfcp_cmd_dbf_event_fsf("unknownc", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_FCP_RSP_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_FCP_RSP_AVAILABLE\n"); + ZFCP_LOG_DEBUG("FCP Sense data will be presented to the " + "SCSI stack.\n"); + debug_text_event(fsf_req->adapter->erp_dbf, 3, "fsf_s_rsp"); + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_s_astatus"); + zfcp_fsf_fsfstatus_qual_eval(fsf_req); + break; + + default: + break; + } + + skip_fsfstatus: + /* + * always call specific handlers to give them a chance to do + * something meaningful even in error cases + */ + zfcp_fsf_req_dispatch(fsf_req); + + return retval; +} + +/* + * function: zfcp_fsf_fsfstatus_qual_eval + * + * purpose: evaluates FSF status-qualifier of completed FSF request + * and acts accordingly + * + * returns: + */ +static int +zfcp_fsf_fsfstatus_qual_eval(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + + switch (fsf_req->qtcb->header.fsf_status_qual.word[0]) { + case FSF_SQ_FCP_RSP_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_SQ_FCP_RSP_AVAILABLE\n"); + debug_text_event(fsf_req->adapter->erp_dbf, 4, "fsf_sq_rsp"); + break; + case FSF_SQ_RETRY_IF_POSSIBLE: + ZFCP_LOG_FLAGS(2, "FSF_SQ_RETRY_IF_POSSIBLE\n"); + /* The SCSI-stack may now issue retries or escalate */ + debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_sq_retry"); + zfcp_cmd_dbf_event_fsf("sqretry", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_COMMAND_ABORTED: + ZFCP_LOG_FLAGS(2, "FSF_SQ_COMMAND_ABORTED\n"); + /* Carry the aborted state on to upper layer */ + debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_sq_abort"); + zfcp_cmd_dbf_event_fsf("sqabort", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTED; + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_NO_RECOM: + ZFCP_LOG_FLAGS(0, "FSF_SQ_NO_RECOM\n"); + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_no_rec"); + ZFCP_LOG_NORMAL("bug: No recommendation could be given for a" + "problem on the adapter %s " + "Stopping all operations on this adapter. ", + zfcp_get_busid_by_adapter(fsf_req->adapter)); + zfcp_erp_adapter_shutdown(fsf_req->adapter, 0); + zfcp_cmd_dbf_event_fsf("sqnrecom", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_PROGRAMMING_ERROR: + ZFCP_LOG_FLAGS(0, "FSF_SQ_ULP_PROGRAMMING_ERROR\n"); + ZFCP_LOG_NORMAL("error: not enough SBALs for data transfer " + "(adapter %s)\n", + zfcp_get_busid_by_adapter(fsf_req->adapter)); + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_ulp_err"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + case FSF_SQ_NO_RETRY_POSSIBLE: + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + /* dealt with in the respective functions */ + break; + default: + ZFCP_LOG_NORMAL("bug: Additional status info could " + "not be interpreted properly.\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_sq_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &fsf_req->qtcb->header.fsf_status_qual.word[0], + sizeof (u32)); + zfcp_cmd_dbf_event_fsf("squndef", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + } + + return retval; +} + +/* + * function: zfcp_fsf_req_dispatch + * + * purpose: calls the appropriate command specific handler + * + * returns: + */ +static int +zfcp_fsf_req_dispatch(struct zfcp_fsf_req *fsf_req) +{ + struct zfcp_erp_action *erp_action = fsf_req->erp_action; + struct zfcp_adapter *adapter = fsf_req->adapter; + int retval = 0; + + if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) { + ZFCP_LOG_TRACE("fsf_req=%p, QTCB=%p\n", fsf_req, fsf_req->qtcb); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, + (char *) fsf_req->qtcb, sizeof(struct fsf_qtcb)); + } + + switch (fsf_req->fsf_command) { + + case FSF_QTCB_FCP_CMND: + ZFCP_LOG_FLAGS(3, "FSF_QTCB_FCP_CMND\n"); + zfcp_fsf_send_fcp_command_handler(fsf_req); + break; + + case FSF_QTCB_ABORT_FCP_CMND: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_ABORT_FCP_CMND\n"); + zfcp_fsf_abort_fcp_command_handler(fsf_req); + break; + + case FSF_QTCB_SEND_GENERIC: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_SEND_GENERIC\n"); + zfcp_fsf_send_ct_handler(fsf_req); + break; + + case FSF_QTCB_OPEN_PORT_WITH_DID: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_OPEN_PORT_WITH_DID\n"); + zfcp_fsf_open_port_handler(fsf_req); + break; + + case FSF_QTCB_OPEN_LUN: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_OPEN_LUN\n"); + zfcp_fsf_open_unit_handler(fsf_req); + break; + + case FSF_QTCB_CLOSE_LUN: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_CLOSE_LUN\n"); + zfcp_fsf_close_unit_handler(fsf_req); + break; + + case FSF_QTCB_CLOSE_PORT: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_CLOSE_PORT\n"); + zfcp_fsf_close_port_handler(fsf_req); + break; + + case FSF_QTCB_CLOSE_PHYSICAL_PORT: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_CLOSE_PHYSICAL_PORT\n"); + zfcp_fsf_close_physical_port_handler(fsf_req); + break; + + case FSF_QTCB_EXCHANGE_CONFIG_DATA: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_EXCHANGE_CONFIG_DATA\n"); + zfcp_fsf_exchange_config_data_handler(fsf_req); + break; + + case FSF_QTCB_EXCHANGE_PORT_DATA: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_EXCHANGE_PORT_DATA\n"); + zfcp_fsf_exchange_port_data_handler(fsf_req); + break; + + case FSF_QTCB_SEND_ELS: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_SEND_ELS\n"); + zfcp_fsf_send_els_handler(fsf_req); + break; + + case FSF_QTCB_DOWNLOAD_CONTROL_FILE: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_DOWNLOAD_CONTROL_FILE\n"); + zfcp_fsf_control_file_handler(fsf_req); + break; + + case FSF_QTCB_UPLOAD_CONTROL_FILE: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_UPLOAD_CONTROL_FILE\n"); + zfcp_fsf_control_file_handler(fsf_req); + break; + + default: + ZFCP_LOG_FLAGS(2, "FSF_QTCB_UNKNOWN\n"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + ZFCP_LOG_NORMAL("bug: Command issued by the device driver is " + "not supported by the adapter %s\n", + zfcp_get_busid_by_adapter(fsf_req->adapter)); + if (fsf_req->fsf_command != fsf_req->qtcb->header.fsf_command) + ZFCP_LOG_NORMAL + ("bug: Command issued by the device driver differs " + "from the command returned by the adapter %s " + "(debug info 0x%x, 0x%x).\n", + zfcp_get_busid_by_adapter(fsf_req->adapter), + fsf_req->fsf_command, + fsf_req->qtcb->header.fsf_command); + } + + if (!erp_action) + return retval; + + debug_text_event(adapter->erp_dbf, 3, "a_frh"); + debug_event(adapter->erp_dbf, 3, &erp_action->action, sizeof (int)); + zfcp_erp_async_handler(erp_action, 0); + + return retval; +} + +/* + * function: zfcp_fsf_status_read + * + * purpose: initiates a Status Read command at the specified adapter + * + * returns: + */ +int +zfcp_fsf_status_read(struct zfcp_adapter *adapter, int req_flags) +{ + struct zfcp_fsf_req *fsf_req; + struct fsf_status_read_buffer *status_buffer; + unsigned long lock_flags; + volatile struct qdio_buffer_element *sbale; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(adapter, FSF_QTCB_UNSOLICITED_STATUS, + req_flags | ZFCP_REQ_NO_QTCB, + adapter->pool.fsf_req_status_read, + &lock_flags, &fsf_req); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create unsolicited status " + "buffer for adapter %s.\n", + zfcp_get_busid_by_adapter(adapter)); + goto failed_req_create; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_STATUS; + sbale[2].flags |= SBAL_FLAGS_LAST_ENTRY; + fsf_req->sbale_curr = 2; + + status_buffer = + mempool_alloc(adapter->pool.data_status_read, GFP_ATOMIC); + if (!status_buffer) { + ZFCP_LOG_NORMAL("bug: could not get some buffer\n"); + goto failed_buf; + } + memset(status_buffer, 0, sizeof (struct fsf_status_read_buffer)); + fsf_req->data.status_read.buffer = status_buffer; + + /* insert pointer to respective buffer */ + sbale = zfcp_qdio_sbale_curr(fsf_req); + sbale->addr = (void *) status_buffer; + sbale->length = sizeof(struct fsf_status_read_buffer); + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(fsf_req, NULL); + if (retval) { + ZFCP_LOG_DEBUG("error: Could not set-up unsolicited status " + "environment.\n"); + goto failed_req_send; + } + + ZFCP_LOG_TRACE("Status Read request initiated (adapter%s)\n", + zfcp_get_busid_by_adapter(adapter)); + goto out; + + failed_req_send: + mempool_free(status_buffer, adapter->pool.data_status_read); + + failed_buf: + zfcp_fsf_req_free(fsf_req); + failed_req_create: + out: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags); + return retval; +} + +static int +zfcp_fsf_status_read_port_closed(struct zfcp_fsf_req *fsf_req) +{ + struct fsf_status_read_buffer *status_buffer; + struct zfcp_adapter *adapter; + struct zfcp_port *port; + unsigned long flags; + + status_buffer = fsf_req->data.status_read.buffer; + adapter = fsf_req->adapter; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + list_for_each_entry(port, &adapter->port_list_head, list) + if (port->d_id == (status_buffer->d_id & ZFCP_DID_MASK)) + break; + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + + if (!port || (port->d_id != (status_buffer->d_id & ZFCP_DID_MASK))) { + ZFCP_LOG_NORMAL("bug: Reopen port indication received for" + "nonexisting port with d_id 0x%08x on " + "adapter %s. Ignored.\n", + status_buffer->d_id & ZFCP_DID_MASK, + zfcp_get_busid_by_adapter(adapter)); + goto out; + } + + switch (status_buffer->status_subtype) { + + case FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT: + ZFCP_LOG_FLAGS(2, "FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT\n"); + debug_text_event(adapter->erp_dbf, 3, "unsol_pc_phys:"); + zfcp_erp_port_reopen(port, 0); + break; + + case FSF_STATUS_READ_SUB_ERROR_PORT: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_SUB_ERROR_PORT\n"); + debug_text_event(adapter->erp_dbf, 1, "unsol_pc_err:"); + zfcp_erp_port_shutdown(port, 0); + break; + + default: + debug_text_event(adapter->erp_dbf, 0, "unsol_unk_sub:"); + debug_exception(adapter->erp_dbf, 0, + &status_buffer->status_subtype, sizeof (u32)); + ZFCP_LOG_NORMAL("bug: Undefined status subtype received " + "for a reopen indication on port with " + "d_id 0x%08x on the adapter %s. " + "Ignored. (debug info 0x%x)\n", + status_buffer->d_id, + zfcp_get_busid_by_adapter(adapter), + status_buffer->status_subtype); + } + out: + return 0; +} + +/* + * function: zfcp_fsf_status_read_handler + * + * purpose: is called for finished Open Port command + * + * returns: + */ +static int +zfcp_fsf_status_read_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + struct zfcp_adapter *adapter = fsf_req->adapter; + struct fsf_status_read_buffer *status_buffer = + fsf_req->data.status_read.buffer; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_DISMISSED) { + mempool_free(status_buffer, adapter->pool.data_status_read); + zfcp_fsf_req_cleanup(fsf_req); + goto out; + } + + switch (status_buffer->status_type) { + + case FSF_STATUS_READ_PORT_CLOSED: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_PORT_CLOSED\n"); + debug_text_event(adapter->erp_dbf, 3, "unsol_pclosed:"); + debug_event(adapter->erp_dbf, 3, + &status_buffer->d_id, sizeof (u32)); + zfcp_fsf_status_read_port_closed(fsf_req); + break; + + case FSF_STATUS_READ_INCOMING_ELS: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_INCOMING_ELS\n"); + debug_text_event(adapter->erp_dbf, 3, "unsol_els:"); + zfcp_fsf_incoming_els(fsf_req); + break; + + case FSF_STATUS_READ_SENSE_DATA_AVAIL: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_SENSE_DATA_AVAIL\n"); + debug_text_event(adapter->erp_dbf, 3, "unsol_sense:"); + ZFCP_LOG_INFO("unsolicited sense data received (adapter %s)\n", + zfcp_get_busid_by_adapter(adapter)); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, (char *) status_buffer, + sizeof(struct fsf_status_read_buffer)); + break; + + case FSF_STATUS_READ_BIT_ERROR_THRESHOLD: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_BIT_ERROR_THRESHOLD\n"); + debug_text_event(adapter->erp_dbf, 3, "unsol_bit_err:"); + ZFCP_LOG_NORMAL("Bit error threshold data received:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) status_buffer, + sizeof (struct fsf_status_read_buffer)); + break; + + case FSF_STATUS_READ_LINK_DOWN: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_LINK_DOWN\n"); + debug_text_event(adapter->erp_dbf, 0, "unsol_link_down:"); + ZFCP_LOG_INFO("Local link to adapter %s is down\n", + zfcp_get_busid_by_adapter(adapter)); + atomic_set_mask(ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED, + &adapter->status); + zfcp_erp_adapter_failed(adapter); + break; + + case FSF_STATUS_READ_LINK_UP: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_LINK_UP\n"); + debug_text_event(adapter->erp_dbf, 2, "unsol_link_up:"); + ZFCP_LOG_INFO("Local link to adapter %s was replugged. " + "Restarting operations on this adapter\n", + zfcp_get_busid_by_adapter(adapter)); + /* All ports should be marked as ready to run again */ + zfcp_erp_modify_adapter_status(adapter, + ZFCP_STATUS_COMMON_RUNNING, + ZFCP_SET); + zfcp_erp_adapter_reopen(adapter, + ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED + | ZFCP_STATUS_COMMON_ERP_FAILED); + break; + + case FSF_STATUS_READ_CFDC_UPDATED: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_CFDC_UPDATED\n"); + debug_text_event(adapter->erp_dbf, 2, "unsol_cfdc_update:"); + ZFCP_LOG_INFO("CFDC has been updated on the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + zfcp_erp_adapter_access_changed(adapter); + break; + + case FSF_STATUS_READ_CFDC_HARDENED: + ZFCP_LOG_FLAGS(1, "FSF_STATUS_READ_CFDC_HARDENED\n"); + debug_text_event(adapter->erp_dbf, 2, "unsol_cfdc_harden:"); + switch (status_buffer->status_subtype) { + case FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE: + ZFCP_LOG_INFO("CFDC of adapter %s saved on SE\n", + zfcp_get_busid_by_adapter(adapter)); + break; + case FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE2: + ZFCP_LOG_INFO("CFDC of adapter %s has been copied " + "to the secondary SE\n", + zfcp_get_busid_by_adapter(adapter)); + break; + default: + ZFCP_LOG_INFO("CFDC of adapter %s has been hardened\n", + zfcp_get_busid_by_adapter(adapter)); + } + break; + + default: + debug_text_event(adapter->erp_dbf, 0, "unsol_unknown:"); + debug_exception(adapter->erp_dbf, 0, + &status_buffer->status_type, sizeof (u32)); + ZFCP_LOG_NORMAL("bug: An unsolicited status packet of unknown " + "type was received (debug info 0x%x)\n", + status_buffer->status_type); + ZFCP_LOG_DEBUG("Dump of status_read_buffer %p:\n", + status_buffer); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) status_buffer, + sizeof (struct fsf_status_read_buffer)); + break; + } + mempool_free(status_buffer, adapter->pool.data_status_read); + zfcp_fsf_req_cleanup(fsf_req); + /* + * recycle buffer and start new request repeat until outbound + * queue is empty or adapter shutdown is requested + */ + /* + * FIXME(qdio): + * we may wait in the req_create for 5s during shutdown, so + * qdio_cleanup will have to wait at least that long before returning + * with failure to allow us a proper cleanup under all circumstances + */ + /* + * FIXME: + * allocation failure possible? (Is this code needed?) + */ + retval = zfcp_fsf_status_read(adapter, 0); + if (retval < 0) { + ZFCP_LOG_INFO("Failed to create unsolicited status read " + "request for the adapter %s.\n", + zfcp_get_busid_by_adapter(adapter)); + /* temporary fix to avoid status read buffer shortage */ + adapter->status_read_failed++; + if ((ZFCP_STATUS_READS_RECOM - adapter->status_read_failed) + < ZFCP_STATUS_READ_FAILED_THRESHOLD) { + ZFCP_LOG_INFO("restart adapter %s due to status read " + "buffer shortage\n", + zfcp_get_busid_by_adapter(adapter)); + zfcp_erp_adapter_reopen(adapter, 0); + } + } + out: + return retval; +} + +/* + * function: zfcp_fsf_abort_fcp_command + * + * purpose: tells FSF to abort a running SCSI command + * + * returns: address of initiated FSF request + * NULL - request could not be initiated + * + * FIXME(design): should be watched by a timeout !!! + * FIXME(design) shouldn't this be modified to return an int + * also...don't know how though + */ +struct zfcp_fsf_req * +zfcp_fsf_abort_fcp_command(unsigned long old_req_id, + struct zfcp_adapter *adapter, + struct zfcp_unit *unit, int req_flags) +{ + volatile struct qdio_buffer_element *sbale; + unsigned long lock_flags; + struct zfcp_fsf_req *fsf_req = NULL; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(adapter, FSF_QTCB_ABORT_FCP_CMND, + req_flags, adapter->pool.fsf_req_abort, + &lock_flags, &fsf_req); + if (retval < 0) { + ZFCP_LOG_INFO("error: Failed to create an abort command " + "request for lun 0x%016Lx on port 0x%016Lx " + "on adapter %s.\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_adapter(adapter)); + goto out; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + fsf_req->data.abort_fcp_command.unit = unit; + + /* set handles of unit and its parent port in QTCB */ + fsf_req->qtcb->header.lun_handle = unit->handle; + fsf_req->qtcb->header.port_handle = unit->port->handle; + + /* set handle of request which should be aborted */ + fsf_req->qtcb->bottom.support.req_handle = (u64) old_req_id; + + /* start QDIO request for this FSF request */ + + zfcp_fsf_start_scsi_er_timer(adapter); + retval = zfcp_fsf_req_send(fsf_req, NULL); + if (retval) { + del_timer(&adapter->scsi_er_timer); + ZFCP_LOG_INFO("error: Failed to send abort command request " + "on adapter %s, port 0x%016Lx, unit 0x%016Lx\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, unit->fcp_lun); + zfcp_fsf_req_free(fsf_req); + fsf_req = NULL; + goto out; + } + + ZFCP_LOG_DEBUG("Abort FCP Command request initiated " + "(adapter%s, port d_id=0x%08x, " + "unit x%016Lx, old_req_id=0x%lx)\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->d_id, + unit->fcp_lun, old_req_id); + out: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags); + return fsf_req; +} + +/* + * function: zfcp_fsf_abort_fcp_command_handler + * + * purpose: is called for finished Abort FCP Command request + * + * returns: + */ +static int +zfcp_fsf_abort_fcp_command_handler(struct zfcp_fsf_req *new_fsf_req) +{ + int retval = -EINVAL; + struct zfcp_unit *unit = new_fsf_req->data.abort_fcp_command.unit; + unsigned char status_qual = + new_fsf_req->qtcb->header.fsf_status_qual.word[0]; + + del_timer(&new_fsf_req->adapter->scsi_er_timer); + + if (new_fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + /* do not set ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED */ + goto skip_fsfstatus; + } + + /* evaluate FSF status in QTCB */ + switch (new_fsf_req->qtcb->header.fsf_status) { + + case FSF_PORT_HANDLE_NOT_VALID: + if (status_qual >> 4 != status_qual % 0xf) { + ZFCP_LOG_FLAGS(2, "FSF_PORT_HANDLE_NOT_VALID\n"); + debug_text_event(new_fsf_req->adapter->erp_dbf, 3, + "fsf_s_phand_nv0"); + /* + * In this case a command that was sent prior to a port + * reopen was aborted (handles are different). This is + * fine. + */ + } else { + ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary port identifier 0x%x for " + "port 0x%016Lx on adapter %s invalid. " + "This may happen occasionally.\n", + unit->port->handle, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + ZFCP_LOG_INFO("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO, + (char *) &new_fsf_req->qtcb->header. + fsf_status_qual, + sizeof (union fsf_status_qual)); + /* Let's hope this sorts out the mess */ + debug_text_event(new_fsf_req->adapter->erp_dbf, 1, + "fsf_s_phand_nv1"); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + } + break; + + case FSF_LUN_HANDLE_NOT_VALID: + if (status_qual >> 4 != status_qual % 0xf) { + /* 2 */ + ZFCP_LOG_FLAGS(0, "FSF_LUN_HANDLE_NOT_VALID\n"); + debug_text_event(new_fsf_req->adapter->erp_dbf, 3, + "fsf_s_lhand_nv0"); + /* + * In this case a command that was sent prior to a unit + * reopen was aborted (handles are different). + * This is fine. + */ + } else { + ZFCP_LOG_FLAGS(1, "FSF_LUN_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO + ("Warning: Temporary LUN identifier 0x%x of LUN " + "0x%016Lx on port 0x%016Lx on adapter %s is " + "invalid. This may happen in rare cases. " + "Trying to re-establish link.\n", + unit->handle, + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + ZFCP_LOG_DEBUG("Status qualifier data:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &new_fsf_req->qtcb->header. + fsf_status_qual, + sizeof (union fsf_status_qual)); + /* Let's hope this sorts out the mess */ + debug_text_event(new_fsf_req->adapter->erp_dbf, 1, + "fsf_s_lhand_nv1"); + zfcp_erp_port_reopen(unit->port, 0); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + } + break; + + case FSF_FCP_COMMAND_DOES_NOT_EXIST: + ZFCP_LOG_FLAGS(2, "FSF_FCP_COMMAND_DOES_NOT_EXIST\n"); + retval = 0; + debug_text_event(new_fsf_req->adapter->erp_dbf, 3, + "fsf_s_no_exist"); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED; + break; + + case FSF_PORT_BOXED: + /* 2 */ + ZFCP_LOG_FLAGS(0, "FSF_PORT_BOXED\n"); + ZFCP_LOG_INFO("Remote port 0x%016Lx on adapter %s needs to " + "be reopened\n", unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + debug_text_event(new_fsf_req->adapter->erp_dbf, 2, + "fsf_s_pboxed"); + zfcp_erp_port_reopen(unit->port, 0); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR + | ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_LUN_BOXED: + ZFCP_LOG_FLAGS(0, "FSF_LUN_BOXED\n"); + ZFCP_LOG_INFO( + "unit 0x%016Lx on port 0x%016Lx on adapter %s needs " + "to be reopened\n", + unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + debug_text_event(new_fsf_req->adapter->erp_dbf, 1, "fsf_s_lboxed"); + zfcp_erp_unit_reopen(unit, 0); + zfcp_cmd_dbf_event_fsf("unitbox", new_fsf_req, + &new_fsf_req->qtcb->header.fsf_status_qual, + sizeof(union fsf_status_qual)); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR + | ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + /* 2 */ + ZFCP_LOG_FLAGS(0, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (new_fsf_req->qtcb->header.fsf_status_qual.word[0]) { + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + debug_text_event(new_fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ltest"); + /* reopening link to port */ + zfcp_erp_port_reopen(unit->port, 0); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + /* SCSI stack will escalate */ + debug_text_event(new_fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ulp"); + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_NORMAL + ("bug: Wrong status qualifier 0x%x arrived.\n", + new_fsf_req->qtcb->header.fsf_status_qual.word[0]); + debug_text_event(new_fsf_req->adapter->erp_dbf, 0, + "fsf_sq_inval:"); + debug_exception(new_fsf_req->adapter->erp_dbf, 0, + &new_fsf_req->qtcb->header. + fsf_status_qual.word[0], sizeof (u32)); + break; + } + break; + + case FSF_GOOD: + /* 3 */ + ZFCP_LOG_FLAGS(0, "FSF_GOOD\n"); + retval = 0; + new_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", + new_fsf_req->qtcb->header.fsf_status); + debug_text_event(new_fsf_req->adapter->erp_dbf, 0, + "fsf_s_inval:"); + debug_exception(new_fsf_req->adapter->erp_dbf, 0, + &new_fsf_req->qtcb->header.fsf_status, + sizeof (u32)); + break; + } + skip_fsfstatus: + return retval; +} + +/** + * zfcp_use_one_sbal - checks whether req buffer and resp bother each fit into + * one SBALE + * Two scatter-gather lists are passed, one for the reqeust and one for the + * response. + */ +static inline int +zfcp_use_one_sbal(struct scatterlist *req, int req_count, + struct scatterlist *resp, int resp_count) +{ + return ((req_count == 1) && + (resp_count == 1) && + (((unsigned long) zfcp_sg_to_address(&req[0]) & + PAGE_MASK) == + ((unsigned long) (zfcp_sg_to_address(&req[0]) + + req[0].length - 1) & PAGE_MASK)) && + (((unsigned long) zfcp_sg_to_address(&resp[0]) & + PAGE_MASK) == + ((unsigned long) (zfcp_sg_to_address(&resp[0]) + + resp[0].length - 1) & PAGE_MASK))); +} + +/** + * zfcp_fsf_send_ct - initiate a Generic Service request (FC-GS) + * @ct: pointer to struct zfcp_send_ct which conatins all needed data for + * the request + * @pool: pointer to memory pool, if non-null this pool is used to allocate + * a struct zfcp_fsf_req + * @erp_action: pointer to erp_action, if non-null the Generic Service request + * is sent within error recovery + */ +int +zfcp_fsf_send_ct(struct zfcp_send_ct *ct, mempool_t *pool, + struct zfcp_erp_action *erp_action) +{ + volatile struct qdio_buffer_element *sbale; + struct zfcp_port *port; + struct zfcp_adapter *adapter; + struct zfcp_fsf_req *fsf_req; + unsigned long lock_flags; + int bytes; + int ret = 0; + + port = ct->port; + adapter = port->adapter; + + ret = zfcp_fsf_req_create(adapter, FSF_QTCB_SEND_GENERIC, + ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP, + pool, &lock_flags, &fsf_req); + if (ret < 0) { + ZFCP_LOG_INFO("error: Could not create CT request (FC-GS) for " + "adapter: %s\n", + zfcp_get_busid_by_adapter(adapter)); + goto failed_req; + } + + if (erp_action != NULL) { + erp_action->fsf_req = fsf_req; + fsf_req->erp_action = erp_action; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + if (zfcp_use_one_sbal(ct->req, ct->req_count, + ct->resp, ct->resp_count)){ + /* both request buffer and response buffer + fit into one sbale each */ + sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE_READ; + sbale[2].addr = zfcp_sg_to_address(&ct->req[0]); + sbale[2].length = ct->req[0].length; + sbale[3].addr = zfcp_sg_to_address(&ct->resp[0]); + sbale[3].length = ct->resp[0].length; + sbale[3].flags |= SBAL_FLAGS_LAST_ENTRY; + } else if (adapter->supported_features & + FSF_FEATURE_ELS_CT_CHAINED_SBALS) { + /* try to use chained SBALs */ + bytes = zfcp_qdio_sbals_from_sg(fsf_req, + SBAL_FLAGS0_TYPE_WRITE_READ, + ct->req, ct->req_count, + ZFCP_MAX_SBALS_PER_CT_REQ); + if (bytes <= 0) { + ZFCP_LOG_INFO("error: creation of CT request failed " + "on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + if (bytes == 0) + ret = -ENOMEM; + else + ret = bytes; + + goto failed_send; + } + fsf_req->qtcb->bottom.support.req_buf_length = bytes; + fsf_req->sbale_curr = ZFCP_LAST_SBALE_PER_SBAL; + bytes = zfcp_qdio_sbals_from_sg(fsf_req, + SBAL_FLAGS0_TYPE_WRITE_READ, + ct->resp, ct->resp_count, + ZFCP_MAX_SBALS_PER_CT_REQ); + if (bytes <= 0) { + ZFCP_LOG_INFO("error: creation of CT request failed " + "on adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + if (bytes == 0) + ret = -ENOMEM; + else + ret = bytes; + + goto failed_send; + } + fsf_req->qtcb->bottom.support.resp_buf_length = bytes; + } else { + /* reject send generic request */ + ZFCP_LOG_INFO( + "error: microcode does not support chained SBALs," + "CT request too big (adapter %s)\n", + zfcp_get_busid_by_adapter(adapter)); + ret = -EOPNOTSUPP; + goto failed_send; + } + + /* settings in QTCB */ + fsf_req->qtcb->header.port_handle = port->handle; + fsf_req->qtcb->bottom.support.service_class = adapter->fc_service_class; + fsf_req->qtcb->bottom.support.timeout = ct->timeout; + fsf_req->data.send_ct = ct; + + /* start QDIO request for this FSF request */ + ret = zfcp_fsf_req_send(fsf_req, ct->timer); + if (ret) { + ZFCP_LOG_DEBUG("error: initiation of CT request failed " + "(adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(adapter), port->wwpn); + goto failed_send; + } + + ZFCP_LOG_DEBUG("CT request initiated (adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(adapter), port->wwpn); + goto out; + + failed_send: + zfcp_fsf_req_free(fsf_req); + if (erp_action != NULL) { + erp_action->fsf_req = NULL; + } + failed_req: + out: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, + lock_flags); + return ret; +} + +/** + * zfcp_fsf_send_ct_handler - handler for Generic Service requests + * @fsf_req: pointer to struct zfcp_fsf_req + * + * Data specific for the Generic Service request is passed by + * fsf_req->data.send_ct + * Usually a specific handler for the request is called via + * fsf_req->data.send_ct->handler at end of this function. + */ +static int +zfcp_fsf_send_ct_handler(struct zfcp_fsf_req *fsf_req) +{ + struct zfcp_port *port; + struct zfcp_adapter *adapter; + struct zfcp_send_ct *send_ct; + struct fsf_qtcb_header *header; + struct fsf_qtcb_bottom_support *bottom; + int retval = -EINVAL; + u16 subtable, rule, counter; + + adapter = fsf_req->adapter; + send_ct = fsf_req->data.send_ct; + port = send_ct->port; + header = &fsf_req->qtcb->header; + bottom = &fsf_req->qtcb->bottom.support; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) + goto skip_fsfstatus; + + /* evaluate FSF status in QTCB */ + switch (header->fsf_status) { + + case FSF_GOOD: + ZFCP_LOG_FLAGS(2,"FSF_GOOD\n"); + retval = 0; + break; + + case FSF_SERVICE_CLASS_NOT_SUPPORTED: + ZFCP_LOG_FLAGS(2, "FSF_SERVICE_CLASS_NOT_SUPPORTED\n"); + if (adapter->fc_service_class <= 3) { + ZFCP_LOG_INFO("error: adapter %s does not support fc " + "class %d.\n", + zfcp_get_busid_by_port(port), + adapter->fc_service_class); + } else { + ZFCP_LOG_INFO("bug: The fibre channel class at the " + "adapter %s is invalid. " + "(debug info %d)\n", + zfcp_get_busid_by_port(port), + adapter->fc_service_class); + } + /* stop operation for this adapter */ + debug_text_exception(adapter->erp_dbf, 0, "fsf_s_class_nsup"); + zfcp_erp_adapter_shutdown(adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (header->fsf_status_qual.word[0]){ + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2,"FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + /* reopening link to port */ + debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ltest"); + zfcp_test_link(port); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(2,"FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + /* ERP strategy will escalate */ + debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ulp"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_INFO("bug: Wrong status qualifier 0x%x " + "arrived.\n", + header->fsf_status_qual.word[0]); + break; + } + break; + + case FSF_ACCESS_DENIED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n"); + ZFCP_LOG_NORMAL("access denied, cannot send generic service " + "command (adapter %s, port d_id=0x%08x)\n", + zfcp_get_busid_by_port(port), port->d_id); + for (counter = 0; counter < 2; counter++) { + subtable = header->fsf_status_qual.halfword[counter * 2]; + rule = header->fsf_status_qual.halfword[counter * 2 + 1]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_INFO("Access denied (%s rule %d)\n", + zfcp_act_subtable_type[subtable], rule); + break; + } + } + debug_text_event(adapter->erp_dbf, 1, "fsf_s_access"); + zfcp_erp_port_access_denied(port); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_GENERIC_COMMAND_REJECTED: + ZFCP_LOG_FLAGS(2, "FSF_GENERIC_COMMAND_REJECTED\n"); + ZFCP_LOG_INFO("generic service command rejected " + "(adapter %s, port d_id=0x%08x)\n", + zfcp_get_busid_by_port(port), port->d_id); + ZFCP_LOG_INFO("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(adapter->erp_dbf, 1, "fsf_s_gcom_rej"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PORT_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(2, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_DEBUG("Temporary port identifier 0x%x for port " + "0x%016Lx on adapter %s invalid. This may " + "happen occasionally.\n", port->handle, + port->wwpn, zfcp_get_busid_by_port(port)); + ZFCP_LOG_INFO("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(adapter->erp_dbf, 1, "fsf_s_phandle_nv"); + zfcp_erp_adapter_reopen(adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PORT_BOXED: + ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n"); + ZFCP_LOG_INFO("port needs to be reopened " + "(adapter %s, port d_id=0x%08x)\n", + zfcp_get_busid_by_port(port), port->d_id); + debug_text_event(adapter->erp_dbf, 2, "fsf_s_pboxed"); + zfcp_erp_port_reopen(port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR + | ZFCP_STATUS_FSFREQ_RETRY; + break; + + /* following states should never occure, all cases avoided + in zfcp_fsf_send_ct - but who knows ... */ + case FSF_PAYLOAD_SIZE_MISMATCH: + ZFCP_LOG_FLAGS(2, "FSF_PAYLOAD_SIZE_MISMATCH\n"); + ZFCP_LOG_INFO("payload size mismatch (adapter: %s, " + "req_buf_length=%d, resp_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->req_buf_length, bottom->resp_buf_length); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_REQUEST_SIZE_TOO_LARGE: + ZFCP_LOG_FLAGS(2, "FSF_REQUEST_SIZE_TOO_LARGE\n"); + ZFCP_LOG_INFO("request size too large (adapter: %s, " + "req_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->req_buf_length); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_RESPONSE_SIZE_TOO_LARGE: + ZFCP_LOG_FLAGS(2, "FSF_RESPONSE_SIZE_TOO_LARGE\n"); + ZFCP_LOG_INFO("response size too large (adapter: %s, " + "resp_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->resp_buf_length); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SBAL_MISMATCH: + ZFCP_LOG_FLAGS(2, "FSF_SBAL_MISMATCH\n"); + ZFCP_LOG_INFO("SBAL mismatch (adapter: %s, req_buf_length=%d, " + "resp_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->req_buf_length, bottom->resp_buf_length); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", header->fsf_status); + debug_text_event(adapter->erp_dbf, 0, "fsf_sq_inval:"); + debug_exception(adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], sizeof (u32)); + break; + } + +skip_fsfstatus: + send_ct->status = retval; + + if (send_ct->handler != NULL) + send_ct->handler(send_ct->handler_data); + + return retval; +} + +/** + * zfcp_fsf_send_els - initiate an ELS command (FC-FS) + * @els: pointer to struct zfcp_send_els which contains all needed data for + * the command. + */ +int +zfcp_fsf_send_els(struct zfcp_send_els *els) +{ + volatile struct qdio_buffer_element *sbale; + struct zfcp_fsf_req *fsf_req; + fc_id_t d_id; + struct zfcp_adapter *adapter; + unsigned long lock_flags; + int bytes; + int ret = 0; + + d_id = els->d_id; + adapter = els->adapter; + + ret = zfcp_fsf_req_create(adapter, FSF_QTCB_SEND_ELS, + ZFCP_REQ_AUTO_CLEANUP, + NULL, &lock_flags, &fsf_req); + if (ret < 0) { + ZFCP_LOG_INFO("error: creation of ELS request failed " + "(adapter %s, port d_id: 0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + goto failed_req; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + if (zfcp_use_one_sbal(els->req, els->req_count, + els->resp, els->resp_count)){ + /* both request buffer and response buffer + fit into one sbale each */ + sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE_READ; + sbale[2].addr = zfcp_sg_to_address(&els->req[0]); + sbale[2].length = els->req[0].length; + sbale[3].addr = zfcp_sg_to_address(&els->resp[0]); + sbale[3].length = els->resp[0].length; + sbale[3].flags |= SBAL_FLAGS_LAST_ENTRY; + } else if (adapter->supported_features & + FSF_FEATURE_ELS_CT_CHAINED_SBALS) { + /* try to use chained SBALs */ + bytes = zfcp_qdio_sbals_from_sg(fsf_req, + SBAL_FLAGS0_TYPE_WRITE_READ, + els->req, els->req_count, + ZFCP_MAX_SBALS_PER_ELS_REQ); + if (bytes <= 0) { + ZFCP_LOG_INFO("error: creation of ELS request failed " + "(adapter %s, port d_id: 0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + if (bytes == 0) { + ret = -ENOMEM; + } else { + ret = bytes; + } + goto failed_send; + } + fsf_req->qtcb->bottom.support.req_buf_length = bytes; + fsf_req->sbale_curr = ZFCP_LAST_SBALE_PER_SBAL; + bytes = zfcp_qdio_sbals_from_sg(fsf_req, + SBAL_FLAGS0_TYPE_WRITE_READ, + els->resp, els->resp_count, + ZFCP_MAX_SBALS_PER_ELS_REQ); + if (bytes <= 0) { + ZFCP_LOG_INFO("error: creation of ELS request failed " + "(adapter %s, port d_id: 0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + if (bytes == 0) { + ret = -ENOMEM; + } else { + ret = bytes; + } + goto failed_send; + } + fsf_req->qtcb->bottom.support.resp_buf_length = bytes; + } else { + /* reject request */ + ZFCP_LOG_INFO("error: microcode does not support chained SBALs" + ", ELS request too big (adapter %s, " + "port d_id: 0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + ret = -EOPNOTSUPP; + goto failed_send; + } + + /* settings in QTCB */ + fsf_req->qtcb->bottom.support.d_id = d_id; + fsf_req->qtcb->bottom.support.service_class = adapter->fc_service_class; + fsf_req->qtcb->bottom.support.timeout = ZFCP_ELS_TIMEOUT; + fsf_req->data.send_els = els; + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + + /* start QDIO request for this FSF request */ + ret = zfcp_fsf_req_send(fsf_req, els->timer); + if (ret) { + ZFCP_LOG_DEBUG("error: initiation of ELS request failed " + "(adapter %s, port d_id: 0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + goto failed_send; + } + + ZFCP_LOG_DEBUG("ELS request initiated (adapter %s, port d_id: " + "0x%08x)\n", zfcp_get_busid_by_adapter(adapter), d_id); + goto out; + + failed_send: + zfcp_fsf_req_free(fsf_req); + + failed_req: + out: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, + lock_flags); + + return ret; +} + +/** + * zfcp_fsf_send_els_handler - handler for ELS commands + * @fsf_req: pointer to struct zfcp_fsf_req + * + * Data specific for the ELS command is passed by + * fsf_req->data.send_els + * Usually a specific handler for the command is called via + * fsf_req->data.send_els->handler at end of this function. + */ +static int zfcp_fsf_send_els_handler(struct zfcp_fsf_req *fsf_req) +{ + struct zfcp_adapter *adapter; + fc_id_t d_id; + struct zfcp_port *port; + struct fsf_qtcb_header *header; + struct fsf_qtcb_bottom_support *bottom; + struct zfcp_send_els *send_els; + int retval = -EINVAL; + u16 subtable, rule, counter; + + send_els = fsf_req->data.send_els; + adapter = send_els->adapter; + d_id = send_els->d_id; + header = &fsf_req->qtcb->header; + bottom = &fsf_req->qtcb->bottom.support; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) + goto skip_fsfstatus; + + switch (header->fsf_status) { + + case FSF_GOOD: + ZFCP_LOG_FLAGS(2, "FSF_GOOD\n"); + retval = 0; + break; + + case FSF_SERVICE_CLASS_NOT_SUPPORTED: + ZFCP_LOG_FLAGS(2, "FSF_SERVICE_CLASS_NOT_SUPPORTED\n"); + if (adapter->fc_service_class <= 3) { + ZFCP_LOG_INFO("error: adapter %s does " + "not support fibrechannel class %d.\n", + zfcp_get_busid_by_adapter(adapter), + adapter->fc_service_class); + } else { + ZFCP_LOG_INFO("bug: The fibrechannel class at " + "adapter %s is invalid. " + "(debug info %d)\n", + zfcp_get_busid_by_adapter(adapter), + adapter->fc_service_class); + } + /* stop operation for this adapter */ + debug_text_exception(adapter->erp_dbf, 0, "fsf_s_class_nsup"); + zfcp_erp_adapter_shutdown(adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (header->fsf_status_qual.word[0]){ + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2,"FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ltest"); + if (send_els->ls_code != ZFCP_LS_ADISC) { + read_lock(&zfcp_data.config_lock); + port = zfcp_get_port_by_did(adapter, d_id); + if (port) + zfcp_test_link(port); + read_unlock(&zfcp_data.config_lock); + } + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(2,"FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + debug_text_event(adapter->erp_dbf, 1, "fsf_sq_ulp"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = + zfcp_handle_els_rjt(header->fsf_status_qual.word[1], + (struct zfcp_ls_rjt_par *) + &header->fsf_status_qual.word[2]); + break; + case FSF_SQ_RETRY_IF_POSSIBLE: + ZFCP_LOG_FLAGS(2, "FSF_SQ_RETRY_IF_POSSIBLE\n"); + debug_text_event(adapter->erp_dbf, 1, "fsf_sq_retry"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_INFO("bug: Wrong status qualifier 0x%x\n", + header->fsf_status_qual.word[0]); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_INFO, + (char*)header->fsf_status_qual.word, 16); + } + break; + + case FSF_ELS_COMMAND_REJECTED: + ZFCP_LOG_FLAGS(2, "FSF_ELS_COMMAND_REJECTED\n"); + ZFCP_LOG_INFO("ELS has been rejected because command filter " + "prohibited sending " + "(adapter: %s, port d_id: 0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + + break; + + case FSF_PAYLOAD_SIZE_MISMATCH: + ZFCP_LOG_FLAGS(2, "FSF_PAYLOAD_SIZE_MISMATCH\n"); + ZFCP_LOG_INFO( + "ELS request size and ELS response size must be either " + "both 0, or both greater than 0 " + "(adapter: %s, req_buf_length=%d resp_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->req_buf_length, + bottom->resp_buf_length); + break; + + case FSF_REQUEST_SIZE_TOO_LARGE: + ZFCP_LOG_FLAGS(2, "FSF_REQUEST_SIZE_TOO_LARGE\n"); + ZFCP_LOG_INFO( + "Length of the ELS request buffer, " + "specified in QTCB bottom, " + "exceeds the size of the buffers " + "that have been allocated for ELS request data " + "(adapter: %s, req_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->req_buf_length); + break; + + case FSF_RESPONSE_SIZE_TOO_LARGE: + ZFCP_LOG_FLAGS(2, "FSF_RESPONSE_SIZE_TOO_LARGE\n"); + ZFCP_LOG_INFO( + "Length of the ELS response buffer, " + "specified in QTCB bottom, " + "exceeds the size of the buffers " + "that have been allocated for ELS response data " + "(adapter: %s, resp_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->resp_buf_length); + break; + + case FSF_SBAL_MISMATCH: + /* should never occure, avoided in zfcp_fsf_send_els */ + ZFCP_LOG_FLAGS(2, "FSF_SBAL_MISMATCH\n"); + ZFCP_LOG_INFO("SBAL mismatch (adapter: %s, req_buf_length=%d, " + "resp_buf_length=%d)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->req_buf_length, bottom->resp_buf_length); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ACCESS_DENIED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n"); + ZFCP_LOG_NORMAL("access denied, cannot send ELS command " + "(adapter %s, port d_id=0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), d_id); + for (counter = 0; counter < 2; counter++) { + subtable = header->fsf_status_qual.halfword[counter * 2]; + rule = header->fsf_status_qual.halfword[counter * 2 + 1]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_INFO("Access denied (%s rule %d)\n", + zfcp_act_subtable_type[subtable], rule); + break; + } + } + debug_text_event(adapter->erp_dbf, 1, "fsf_s_access"); + read_lock(&zfcp_data.config_lock); + port = zfcp_get_port_by_did(adapter, d_id); + if (port != NULL) + zfcp_erp_port_access_denied(port); + read_unlock(&zfcp_data.config_lock); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + default: + ZFCP_LOG_NORMAL( + "bug: An unknown FSF Status was presented " + "(adapter: %s, fsf_status=0x%08x)\n", + zfcp_get_busid_by_adapter(adapter), + header->fsf_status); + debug_text_event(adapter->erp_dbf, 0, "fsf_sq_inval"); + debug_exception(adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], sizeof(u32)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + } + +skip_fsfstatus: + send_els->status = retval; + + if (send_els->handler != 0) + send_els->handler(send_els->handler_data); + + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: address of initiated FSF request + * NULL - request could not be initiated + */ +int +zfcp_fsf_exchange_config_data(struct zfcp_erp_action *erp_action) +{ + volatile struct qdio_buffer_element *sbale; + unsigned long lock_flags; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(erp_action->adapter, + FSF_QTCB_EXCHANGE_CONFIG_DATA, + ZFCP_REQ_AUTO_CLEANUP, + erp_action->adapter->pool.fsf_req_erp, + &lock_flags, &(erp_action->fsf_req)); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create exchange configuration " + "data request for adapter %s.\n", + zfcp_get_busid_by_adapter(erp_action->adapter)); + goto out; + } + + sbale = zfcp_qdio_sbale_req(erp_action->fsf_req, + erp_action->fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + erp_action->fsf_req->erp_action = erp_action; + erp_action->fsf_req->qtcb->bottom.config.feature_selection = + (FSF_FEATURE_CFDC | FSF_FEATURE_LUN_SHARING); + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer); + if (retval) { + ZFCP_LOG_INFO + ("error: Could not send exchange configuration data " + "command on the adapter %s\n", + zfcp_get_busid_by_adapter(erp_action->adapter)); + zfcp_fsf_req_free(erp_action->fsf_req); + erp_action->fsf_req = NULL; + goto out; + } + + ZFCP_LOG_DEBUG("exchange configuration data request initiated " + "(adapter %s)\n", + zfcp_get_busid_by_adapter(erp_action->adapter)); + + out: + write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock, + lock_flags); + return retval; +} + +/** + * zfcp_fsf_exchange_config_evaluate + * @fsf_req: fsf_req which belongs to xchg config data request + * @xchg_ok: specifies if xchg config data was incomplete or complete (0/1) + * + * returns: -EIO on error, 0 otherwise + */ +static int +zfcp_fsf_exchange_config_evaluate(struct zfcp_fsf_req *fsf_req, int xchg_ok) +{ + struct fsf_qtcb_bottom_config *bottom; + struct zfcp_adapter *adapter = fsf_req->adapter; + + bottom = &fsf_req->qtcb->bottom.config; + ZFCP_LOG_DEBUG("low/high QTCB version 0x%x/0x%x of FSF\n", + bottom->low_qtcb_version, bottom->high_qtcb_version); + adapter->fsf_lic_version = bottom->lic_version; + adapter->supported_features = bottom->supported_features; + + if (xchg_ok) { + adapter->wwnn = bottom->nport_serv_param.wwnn; + adapter->wwpn = bottom->nport_serv_param.wwpn; + adapter->s_id = bottom->s_id & ZFCP_DID_MASK; + adapter->fc_topology = bottom->fc_topology; + adapter->fc_link_speed = bottom->fc_link_speed; + adapter->hydra_version = bottom->adapter_type; + } else { + adapter->wwnn = 0; + adapter->wwpn = 0; + adapter->s_id = 0; + adapter->fc_topology = 0; + adapter->fc_link_speed = 0; + adapter->hydra_version = 0; + } + + if(adapter->supported_features & FSF_FEATURE_HBAAPI_MANAGEMENT){ + adapter->hardware_version = bottom->hardware_version; + memcpy(adapter->serial_number, bottom->serial_number, 17); + EBCASC(adapter->serial_number, sizeof(adapter->serial_number)); + } + + ZFCP_LOG_INFO("The adapter %s reported the following characteristics:\n" + "WWNN 0x%016Lx, " + "WWPN 0x%016Lx, " + "S_ID 0x%08x,\n" + "adapter version 0x%x, " + "LIC version 0x%x, " + "FC link speed %d Gb/s\n", + zfcp_get_busid_by_adapter(adapter), + adapter->wwnn, + adapter->wwpn, + (unsigned int) adapter->s_id, + adapter->hydra_version, + adapter->fsf_lic_version, + adapter->fc_link_speed); + if (ZFCP_QTCB_VERSION < bottom->low_qtcb_version) { + ZFCP_LOG_NORMAL("error: the adapter %s " + "only supports newer control block " + "versions in comparison to this device " + "driver (try updated device driver)\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 0, "low_qtcb_ver"); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + } + if (ZFCP_QTCB_VERSION > bottom->high_qtcb_version) { + ZFCP_LOG_NORMAL("error: the adapter %s " + "only supports older control block " + "versions than this device driver uses" + "(consider a microcode upgrade)\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(adapter->erp_dbf, 0, "high_qtcb_ver"); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + } + return 0; +} + +/* + * function: zfcp_fsf_exchange_config_data_handler + * + * purpose: is called for finished Exchange Configuration Data command + * + * returns: + */ +static int +zfcp_fsf_exchange_config_data_handler(struct zfcp_fsf_req *fsf_req) +{ + struct fsf_qtcb_bottom_config *bottom; + struct zfcp_adapter *adapter = fsf_req->adapter; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) + return -EIO; + + switch (fsf_req->qtcb->header.fsf_status) { + + case FSF_GOOD: + ZFCP_LOG_FLAGS(2, "FSF_GOOD\n"); + + if (zfcp_fsf_exchange_config_evaluate(fsf_req, 1)) + return -EIO; + + switch (adapter->fc_topology) { + case FSF_TOPO_P2P: + ZFCP_LOG_FLAGS(1, "FSF_TOPO_P2P\n"); + ZFCP_LOG_NORMAL("error: Point-to-point fibrechannel " + "configuration detected at adapter %s " + "unsupported, shutting down adapter\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "top-p-to-p"); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + case FSF_TOPO_AL: + ZFCP_LOG_FLAGS(1, "FSF_TOPO_AL\n"); + ZFCP_LOG_NORMAL("error: Arbitrated loop fibrechannel " + "topology detected at adapter %s " + "unsupported, shutting down adapter\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "top-al"); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + case FSF_TOPO_FABRIC: + ZFCP_LOG_FLAGS(1, "FSF_TOPO_FABRIC\n"); + ZFCP_LOG_INFO("Switched fabric fibrechannel " + "network detected at adapter %s.\n", + zfcp_get_busid_by_adapter(adapter)); + break; + default: + ZFCP_LOG_NORMAL("bug: The fibrechannel topology " + "reported by the exchange " + "configuration command for " + "the adapter %s is not " + "of a type known to the zfcp " + "driver, shutting down adapter\n", + zfcp_get_busid_by_adapter(adapter)); + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "unknown-topo"); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + } + bottom = &fsf_req->qtcb->bottom.config; + if (bottom->max_qtcb_size < sizeof(struct fsf_qtcb)) { + ZFCP_LOG_NORMAL("bug: Maximum QTCB size (%d bytes) " + "allowed by the adapter %s " + "is lower than the minimum " + "required by the driver (%ld bytes).\n", + bottom->max_qtcb_size, + zfcp_get_busid_by_adapter(adapter), + sizeof(struct fsf_qtcb)); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "qtcb-size"); + debug_event(fsf_req->adapter->erp_dbf, 0, + &bottom->max_qtcb_size, sizeof (u32)); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + } + atomic_set_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK, + &adapter->status); + break; + case FSF_EXCHANGE_CONFIG_DATA_INCOMPLETE: + debug_text_event(adapter->erp_dbf, 0, "xchg-inco"); + + if (zfcp_fsf_exchange_config_evaluate(fsf_req, 0)) + return -EIO; + + ZFCP_LOG_INFO("Local link to adapter %s is down\n", + zfcp_get_busid_by_adapter(adapter)); + atomic_set_mask(ZFCP_STATUS_ADAPTER_XCONFIG_OK | + ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED, + &adapter->status); + zfcp_erp_adapter_failed(adapter); + break; + default: + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf-stat-ng"); + debug_event(fsf_req->adapter->erp_dbf, 0, + &fsf_req->qtcb->header.fsf_status, sizeof (u32)); + zfcp_erp_adapter_shutdown(adapter, 0); + return -EIO; + } + return 0; +} + +/** + * zfcp_fsf_exchange_port_data - request information about local port + * @adapter: for which port data is requested + * @data: response to exchange port data request + */ +int +zfcp_fsf_exchange_port_data(struct zfcp_adapter *adapter, + struct fsf_qtcb_bottom_port *data) +{ + volatile struct qdio_buffer_element *sbale; + int retval = 0; + unsigned long lock_flags; + struct zfcp_fsf_req *fsf_req; + struct timer_list *timer; + + if(!(adapter->supported_features & FSF_FEATURE_HBAAPI_MANAGEMENT)){ + ZFCP_LOG_INFO("error: exchange port data " + "command not supported by adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + return -EOPNOTSUPP; + } + + timer = kmalloc(sizeof(struct timer_list), GFP_KERNEL); + if (!timer) + return -ENOMEM; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(adapter, FSF_QTCB_EXCHANGE_PORT_DATA, + 0, 0, &lock_flags, &fsf_req); + if (retval < 0) { + ZFCP_LOG_INFO("error: Out of resources. Could not create an " + "exchange port data request for" + "the adapter %s.\n", + zfcp_get_busid_by_adapter(adapter)); + write_unlock_irqrestore(&adapter->request_queue.queue_lock, + lock_flags); + goto out; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + fsf_req->data.port_data = data; + + init_timer(timer); + timer->function = zfcp_fsf_request_timeout_handler; + timer->data = (unsigned long) adapter; + timer->expires = ZFCP_FSF_REQUEST_TIMEOUT; + + retval = zfcp_fsf_req_send(fsf_req, timer); + if (retval) { + ZFCP_LOG_INFO("error: Could not send an exchange port data " + "command on the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + zfcp_fsf_req_free(fsf_req); + write_unlock_irqrestore(&adapter->request_queue.queue_lock, + lock_flags); + goto out; + } + + ZFCP_LOG_DEBUG("Exchange Port Data request initiated (adapter %s)\n", + zfcp_get_busid_by_adapter(adapter)); + + write_unlock_irqrestore(&adapter->request_queue.queue_lock, + lock_flags); + + wait_event(fsf_req->completion_wq, + fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED); + del_timer_sync(timer); + zfcp_fsf_req_cleanup(fsf_req); + out: + kfree(timer); + return retval; +} + + +/** + * zfcp_fsf_exchange_port_data_handler - handler for exchange_port_data request + * @fsf_req: pointer to struct zfcp_fsf_req + */ +static void +zfcp_fsf_exchange_port_data_handler(struct zfcp_fsf_req *fsf_req) +{ + struct fsf_qtcb_bottom_port *bottom; + struct fsf_qtcb_bottom_port *data = fsf_req->data.port_data; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) + return; + + switch (fsf_req->qtcb->header.fsf_status) { + case FSF_GOOD: + ZFCP_LOG_FLAGS(2,"FSF_GOOD\n"); + bottom = &fsf_req->qtcb->bottom.port; + memcpy(data, bottom, sizeof(*data)); + break; + + default: + debug_text_event(fsf_req->adapter->erp_dbf, 0, "xchg-port-ng"); + debug_event(fsf_req->adapter->erp_dbf, 0, + &fsf_req->qtcb->header.fsf_status, sizeof(u32)); + } +} + + +/* + * function: zfcp_fsf_open_port + * + * purpose: + * + * returns: address of initiated FSF request + * NULL - request could not be initiated + */ +int +zfcp_fsf_open_port(struct zfcp_erp_action *erp_action) +{ + volatile struct qdio_buffer_element *sbale; + unsigned long lock_flags; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(erp_action->adapter, + FSF_QTCB_OPEN_PORT_WITH_DID, + ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP, + erp_action->adapter->pool.fsf_req_erp, + &lock_flags, &(erp_action->fsf_req)); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create open port request " + "for port 0x%016Lx on adapter %s.\n", + erp_action->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + goto out; + } + + sbale = zfcp_qdio_sbale_req(erp_action->fsf_req, + erp_action->fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + erp_action->fsf_req->qtcb->bottom.support.d_id = erp_action->port->d_id; + atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, &erp_action->port->status); + erp_action->fsf_req->data.open_port.port = erp_action->port; + erp_action->fsf_req->erp_action = erp_action; + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer); + if (retval) { + ZFCP_LOG_INFO("error: Could not send open port request for " + "port 0x%016Lx on adapter %s.\n", + erp_action->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + zfcp_fsf_req_free(erp_action->fsf_req); + erp_action->fsf_req = NULL; + goto out; + } + + ZFCP_LOG_DEBUG("open port request initiated " + "(adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn); + out: + write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock, + lock_flags); + return retval; +} + +/* + * function: zfcp_fsf_open_port_handler + * + * purpose: is called for finished Open Port command + * + * returns: + */ +static int +zfcp_fsf_open_port_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = -EINVAL; + struct zfcp_port *port; + struct fsf_plogi *plogi; + struct fsf_qtcb_header *header; + u16 subtable, rule, counter; + + port = fsf_req->data.open_port.port; + header = &fsf_req->qtcb->header; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + /* don't change port status in our bookkeeping */ + goto skip_fsfstatus; + } + + /* evaluate FSF status in QTCB */ + switch (header->fsf_status) { + + case FSF_PORT_ALREADY_OPEN: + ZFCP_LOG_FLAGS(0, "FSF_PORT_ALREADY_OPEN\n"); + ZFCP_LOG_NORMAL("bug: remote port 0x%016Lx on adapter %s " + "is already open.\n", + port->wwpn, zfcp_get_busid_by_port(port)); + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "fsf_s_popen"); + /* + * This is a bug, however operation should continue normally + * if it is simply ignored + */ + break; + + case FSF_ACCESS_DENIED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n"); + ZFCP_LOG_NORMAL("Access denied, cannot open port 0x%016Lx " + "on adapter %s\n", + port->wwpn, zfcp_get_busid_by_port(port)); + for (counter = 0; counter < 2; counter++) { + subtable = header->fsf_status_qual.halfword[counter * 2]; + rule = header->fsf_status_qual.halfword[counter * 2 + 1]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_INFO("Access denied (%s rule %d)\n", + zfcp_act_subtable_type[subtable], rule); + break; + } + } + debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_access"); + zfcp_erp_port_access_denied(port); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED: + ZFCP_LOG_FLAGS(1, "FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED\n"); + ZFCP_LOG_INFO("error: The FSF adapter is out of resources. " + "The remote port 0x%016Lx on adapter %s " + "could not be opened. Disabling it.\n", + port->wwpn, zfcp_get_busid_by_port(port)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_max_ports"); + zfcp_erp_port_failed(port); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (header->fsf_status_qual.word[0]) { + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ltest"); + /* ERP strategy will escalate */ + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + /* ERP strategy will escalate */ + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ulp"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_NO_RETRY_POSSIBLE: + ZFCP_LOG_FLAGS(0, "FSF_SQ_NO_RETRY_POSSIBLE\n"); + ZFCP_LOG_NORMAL("The remote port 0x%016Lx on " + "adapter %s could not be opened. " + "Disabling it.\n", + port->wwpn, + zfcp_get_busid_by_port(port)); + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_no_retry"); + zfcp_erp_port_failed(port); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_NORMAL + ("bug: Wrong status qualifier 0x%x arrived.\n", + header->fsf_status_qual.word[0]); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_inval:"); + debug_exception( + fsf_req->adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], + sizeof (u32)); + break; + } + break; + + case FSF_GOOD: + ZFCP_LOG_FLAGS(3, "FSF_GOOD\n"); + /* save port handle assigned by FSF */ + port->handle = header->port_handle; + ZFCP_LOG_INFO("The remote port 0x%016Lx via adapter %s " + "was opened, it's port handle is 0x%x\n", + port->wwpn, zfcp_get_busid_by_port(port), + port->handle); + /* mark port as open */ + atomic_set_mask(ZFCP_STATUS_COMMON_OPEN | + ZFCP_STATUS_PORT_PHYS_OPEN, &port->status); + retval = 0; + /* check whether D_ID has changed during open */ + /* + * FIXME: This check is not airtight, as the FCP channel does + * not monitor closures of target port connections caused on + * the remote side. Thus, they might miss out on invalidating + * locally cached WWPNs (and other N_Port parameters) of gone + * target ports. So, our heroic attempt to make things safe + * could be undermined by 'open port' response data tagged with + * obsolete WWPNs. Another reason to monitor potential + * connection closures ourself at least (by interpreting + * incoming ELS' and unsolicited status). It just crosses my + * mind that one should be able to cross-check by means of + * another GID_PN straight after a port has been opened. + * Alternately, an ADISC/PDISC ELS should suffice, as well. + */ + plogi = (struct fsf_plogi *) fsf_req->qtcb->bottom.support.els; + if (!atomic_test_mask(ZFCP_STATUS_PORT_NO_WWPN, &port->status)) + { + if (fsf_req->qtcb->bottom.support.els1_length < + ((((unsigned long) &plogi->serv_param.wwpn) - + ((unsigned long) plogi)) + sizeof (u64))) { + ZFCP_LOG_INFO( + "warning: insufficient length of " + "PLOGI payload (%i)\n", + fsf_req->qtcb->bottom.support.els1_length); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_s_short_plogi:"); + /* skip sanity check and assume wwpn is ok */ + } else { + if (plogi->serv_param.wwpn != port->wwpn) { + ZFCP_LOG_INFO("warning: d_id of port " + "0x%016Lx changed during " + "open\n", port->wwpn); + debug_text_event( + fsf_req->adapter->erp_dbf, 0, + "fsf_s_did_change:"); + atomic_clear_mask( + ZFCP_STATUS_PORT_DID_DID, + &port->status); + } else + port->wwnn = plogi->serv_param.wwnn; + } + } + break; + + case FSF_UNKNOWN_OP_SUBTYPE: + /* should never occure, subtype not set in zfcp_fsf_open_port */ + ZFCP_LOG_FLAGS(2, "FSF_UNKNOWN_OP_SUBTYPE\n"); + ZFCP_LOG_INFO("unknown operation subtype (adapter: %s, " + "op_subtype=0x%x)\n", + zfcp_get_busid_by_port(port), + fsf_req->qtcb->bottom.support.operation_subtype); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", + header->fsf_status); + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &header->fsf_status, sizeof (u32)); + break; + } + + skip_fsfstatus: + atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, &port->status); + return retval; +} + +/* + * function: zfcp_fsf_close_port + * + * purpose: submit FSF command "close port" + * + * returns: address of initiated FSF request + * NULL - request could not be initiated + */ +int +zfcp_fsf_close_port(struct zfcp_erp_action *erp_action) +{ + volatile struct qdio_buffer_element *sbale; + unsigned long lock_flags; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(erp_action->adapter, + FSF_QTCB_CLOSE_PORT, + ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP, + erp_action->adapter->pool.fsf_req_erp, + &lock_flags, &(erp_action->fsf_req)); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create a close port request " + "for port 0x%016Lx on adapter %s.\n", + erp_action->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + goto out; + } + + sbale = zfcp_qdio_sbale_req(erp_action->fsf_req, + erp_action->fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, &erp_action->port->status); + erp_action->fsf_req->data.close_port.port = erp_action->port; + erp_action->fsf_req->erp_action = erp_action; + erp_action->fsf_req->qtcb->header.port_handle = + erp_action->port->handle; + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer); + if (retval) { + ZFCP_LOG_INFO("error: Could not send a close port request for " + "port 0x%016Lx on adapter %s.\n", + erp_action->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + zfcp_fsf_req_free(erp_action->fsf_req); + erp_action->fsf_req = NULL; + goto out; + } + + ZFCP_LOG_TRACE("close port request initiated " + "(adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn); + out: + write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock, + lock_flags); + return retval; +} + +/* + * function: zfcp_fsf_close_port_handler + * + * purpose: is called for finished Close Port FSF command + * + * returns: + */ +static int +zfcp_fsf_close_port_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = -EINVAL; + struct zfcp_port *port; + + port = fsf_req->data.close_port.port; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + /* don't change port status in our bookkeeping */ + goto skip_fsfstatus; + } + + /* evaluate FSF status in QTCB */ + switch (fsf_req->qtcb->header.fsf_status) { + + case FSF_PORT_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary port identifier 0x%x for port " + "0x%016Lx on adapter %s invalid. This may happen " + "occasionally.\n", port->handle, + port->wwpn, zfcp_get_busid_by_port(port)); + ZFCP_LOG_DEBUG("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_phand_nv"); + zfcp_erp_adapter_reopen(port->adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + /* Note: FSF has actually closed the port in this case. + * The status code is just daft. Fingers crossed for a change + */ + retval = 0; + break; + + case FSF_GOOD: + ZFCP_LOG_FLAGS(3, "FSF_GOOD\n"); + ZFCP_LOG_TRACE("remote port 0x016%Lx on adapter %s closed, " + "port handle 0x%x\n", port->wwpn, + zfcp_get_busid_by_port(port), port->handle); + zfcp_erp_modify_port_status(port, + ZFCP_STATUS_COMMON_OPEN, + ZFCP_CLEAR); + retval = 0; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", + fsf_req->qtcb->header.fsf_status); + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &fsf_req->qtcb->header.fsf_status, + sizeof (u32)); + break; + } + + skip_fsfstatus: + atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, &port->status); + return retval; +} + +/* + * function: zfcp_fsf_close_physical_port + * + * purpose: submit FSF command "close physical port" + * + * returns: address of initiated FSF request + * NULL - request could not be initiated + */ +int +zfcp_fsf_close_physical_port(struct zfcp_erp_action *erp_action) +{ + int retval = 0; + unsigned long lock_flags; + volatile struct qdio_buffer_element *sbale; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(erp_action->adapter, + FSF_QTCB_CLOSE_PHYSICAL_PORT, + ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP, + erp_action->adapter->pool.fsf_req_erp, + &lock_flags, &erp_action->fsf_req); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create close physical port " + "request (adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn); + + goto out; + } + + sbale = zfcp_qdio_sbale_req(erp_action->fsf_req, + erp_action->fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + /* mark port as being closed */ + atomic_set_mask(ZFCP_STATUS_PORT_PHYS_CLOSING, + &erp_action->port->status); + /* save a pointer to this port */ + erp_action->fsf_req->data.close_physical_port.port = erp_action->port; + /* port to be closeed */ + erp_action->fsf_req->qtcb->header.port_handle = + erp_action->port->handle; + erp_action->fsf_req->erp_action = erp_action; + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer); + if (retval) { + ZFCP_LOG_INFO("error: Could not send close physical port " + "request (adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn); + zfcp_fsf_req_free(erp_action->fsf_req); + erp_action->fsf_req = NULL; + goto out; + } + + ZFCP_LOG_TRACE("close physical port request initiated " + "(adapter %s, port 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn); + out: + write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock, + lock_flags); + return retval; +} + +/* + * function: zfcp_fsf_close_physical_port_handler + * + * purpose: is called for finished Close Physical Port FSF command + * + * returns: + */ +static int +zfcp_fsf_close_physical_port_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = -EINVAL; + struct zfcp_port *port; + struct zfcp_unit *unit; + struct fsf_qtcb_header *header; + u16 subtable, rule, counter; + + port = fsf_req->data.close_physical_port.port; + header = &fsf_req->qtcb->header; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + /* don't change port status in our bookkeeping */ + goto skip_fsfstatus; + } + + /* evaluate FSF status in QTCB */ + switch (header->fsf_status) { + + case FSF_PORT_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary port identifier 0x%x invalid" + "(adapter %s, port 0x%016Lx). " + "This may happen occasionally.\n", + port->handle, + zfcp_get_busid_by_port(port), + port->wwpn); + ZFCP_LOG_DEBUG("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_phand_nv"); + zfcp_erp_adapter_reopen(port->adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ACCESS_DENIED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n"); + ZFCP_LOG_NORMAL("Access denied, cannot close " + "physical port 0x%016Lx on adapter %s\n", + port->wwpn, zfcp_get_busid_by_port(port)); + for (counter = 0; counter < 2; counter++) { + subtable = header->fsf_status_qual.halfword[counter * 2]; + rule = header->fsf_status_qual.halfword[counter * 2 + 1]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_INFO("Access denied (%s rule %d)\n", + zfcp_act_subtable_type[subtable], rule); + break; + } + } + debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_access"); + zfcp_erp_port_access_denied(port); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PORT_BOXED: + ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n"); + ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter " + "%s needs to be reopened but it was attempted " + "to close it physically.\n", + port->wwpn, + zfcp_get_busid_by_port(port)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_pboxed"); + zfcp_erp_port_reopen(port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR | + ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (header->fsf_status_qual.word[0]) { + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ltest"); + /* This will now be escalated by ERP */ + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + /* ERP strategy will escalate */ + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ulp"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_NORMAL + ("bug: Wrong status qualifier 0x%x arrived.\n", + header->fsf_status_qual.word[0]); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_inval:"); + debug_exception( + fsf_req->adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], sizeof (u32)); + break; + } + break; + + case FSF_GOOD: + ZFCP_LOG_FLAGS(3, "FSF_GOOD\n"); + ZFCP_LOG_DEBUG("Remote port 0x%016Lx via adapter %s " + "physically closed, port handle 0x%x\n", + port->wwpn, + zfcp_get_busid_by_port(port), port->handle); + /* can't use generic zfcp_erp_modify_port_status because + * ZFCP_STATUS_COMMON_OPEN must not be reset for the port + */ + atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_OPEN, &port->status); + list_for_each_entry(unit, &port->unit_list_head, list) + atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status); + retval = 0; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", + header->fsf_status); + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &header->fsf_status, sizeof (u32)); + break; + } + + skip_fsfstatus: + atomic_clear_mask(ZFCP_STATUS_PORT_PHYS_CLOSING, &port->status); + return retval; +} + +/* + * function: zfcp_fsf_open_unit + * + * purpose: + * + * returns: + * + * assumptions: This routine does not check whether the associated + * remote port has already been opened. This should be + * done by calling routines. Otherwise some status + * may be presented by FSF + */ +int +zfcp_fsf_open_unit(struct zfcp_erp_action *erp_action) +{ + volatile struct qdio_buffer_element *sbale; + unsigned long lock_flags; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(erp_action->adapter, + FSF_QTCB_OPEN_LUN, + ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP, + erp_action->adapter->pool.fsf_req_erp, + &lock_flags, &(erp_action->fsf_req)); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create open unit request for " + "unit 0x%016Lx on port 0x%016Lx on adapter %s.\n", + erp_action->unit->fcp_lun, + erp_action->unit->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + goto out; + } + + sbale = zfcp_qdio_sbale_req(erp_action->fsf_req, + erp_action->fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + erp_action->fsf_req->qtcb->header.port_handle = + erp_action->port->handle; + erp_action->fsf_req->qtcb->bottom.support.fcp_lun = + erp_action->unit->fcp_lun; + erp_action->fsf_req->qtcb->bottom.support.option = + FSF_OPEN_LUN_SUPPRESS_BOXING; + atomic_set_mask(ZFCP_STATUS_COMMON_OPENING, &erp_action->unit->status); + erp_action->fsf_req->data.open_unit.unit = erp_action->unit; + erp_action->fsf_req->erp_action = erp_action; + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer); + if (retval) { + ZFCP_LOG_INFO("error: Could not send an open unit request " + "on the adapter %s, port 0x%016Lx for " + "unit 0x%016Lx\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn, + erp_action->unit->fcp_lun); + zfcp_fsf_req_free(erp_action->fsf_req); + erp_action->fsf_req = NULL; + goto out; + } + + ZFCP_LOG_TRACE("Open LUN request initiated (adapter %s, " + "port 0x%016Lx, unit 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn, erp_action->unit->fcp_lun); + out: + write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock, + lock_flags); + return retval; +} + +/* + * function: zfcp_fsf_open_unit_handler + * + * purpose: is called for finished Open LUN command + * + * returns: + */ +static int +zfcp_fsf_open_unit_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = -EINVAL; + struct zfcp_adapter *adapter; + struct zfcp_unit *unit; + struct fsf_qtcb_header *header; + struct fsf_qtcb_bottom_support *bottom; + struct fsf_queue_designator *queue_designator; + u16 subtable, rule, counter; + u32 allowed, exclusive, readwrite; + + unit = fsf_req->data.open_unit.unit; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + /* don't change unit status in our bookkeeping */ + goto skip_fsfstatus; + } + + adapter = fsf_req->adapter; + header = &fsf_req->qtcb->header; + bottom = &fsf_req->qtcb->bottom.support; + queue_designator = &header->fsf_status_qual.fsf_queue_designator; + + allowed = bottom->lun_access_info & FSF_UNIT_ACCESS_OPEN_LUN_ALLOWED; + exclusive = bottom->lun_access_info & FSF_UNIT_ACCESS_EXCLUSIVE; + readwrite = bottom->lun_access_info & FSF_UNIT_ACCESS_OUTBOUND_TRANSFER; + + atomic_clear_mask(ZFCP_STATUS_COMMON_ACCESS_DENIED | + ZFCP_STATUS_UNIT_SHARED | + ZFCP_STATUS_UNIT_READONLY, + &unit->status); + + /* evaluate FSF status in QTCB */ + switch (header->fsf_status) { + + case FSF_PORT_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary port identifier 0x%x " + "for port 0x%016Lx on adapter %s invalid " + "This may happen occasionally\n", + unit->port->handle, + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + ZFCP_LOG_DEBUG("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(adapter->erp_dbf, 1, "fsf_s_ph_nv"); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_LUN_ALREADY_OPEN: + ZFCP_LOG_FLAGS(0, "FSF_LUN_ALREADY_OPEN\n"); + ZFCP_LOG_NORMAL("bug: Attempted to open unit 0x%016Lx on " + "remote port 0x%016Lx on adapter %s twice.\n", + unit->fcp_lun, + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + debug_text_exception(adapter->erp_dbf, 0, + "fsf_s_uopen"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ACCESS_DENIED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n"); + ZFCP_LOG_NORMAL("Access denied, cannot open unit 0x%016Lx on " + "remote port 0x%016Lx on adapter %s\n", + unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + for (counter = 0; counter < 2; counter++) { + subtable = header->fsf_status_qual.halfword[counter * 2]; + rule = header->fsf_status_qual.halfword[counter * 2 + 1]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_INFO("Access denied (%s rule %d)\n", + zfcp_act_subtable_type[subtable], rule); + break; + } + } + debug_text_event(adapter->erp_dbf, 1, "fsf_s_access"); + zfcp_erp_unit_access_denied(unit); + atomic_clear_mask(ZFCP_STATUS_UNIT_SHARED, &unit->status); + atomic_clear_mask(ZFCP_STATUS_UNIT_READONLY, &unit->status); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PORT_BOXED: + ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n"); + ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s " + "needs to be reopened\n", + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + debug_text_event(adapter->erp_dbf, 2, "fsf_s_pboxed"); + zfcp_erp_port_reopen(unit->port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR | + ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_LUN_SHARING_VIOLATION: + ZFCP_LOG_FLAGS(2, "FSF_LUN_SHARING_VIOLATION\n"); + if (header->fsf_status_qual.word[0] != 0) { + ZFCP_LOG_NORMAL("FCP-LUN 0x%Lx at the remote port " + "with WWPN 0x%Lx " + "connected to the adapter %s " + "is already in use in LPAR%d, CSS%d\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + queue_designator->hla, + queue_designator->cssid); + } else { + subtable = header->fsf_status_qual.halfword[4]; + rule = header->fsf_status_qual.halfword[5]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_NORMAL("Access to FCP-LUN 0x%Lx at the " + "remote port with WWPN 0x%Lx " + "connected to the adapter %s " + "is denied (%s rule %d)\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + zfcp_act_subtable_type[subtable], + rule); + break; + } + } + ZFCP_LOG_DEBUG("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(adapter->erp_dbf, 2, + "fsf_s_l_sh_vio"); + zfcp_erp_unit_access_denied(unit); + atomic_clear_mask(ZFCP_STATUS_UNIT_SHARED, &unit->status); + atomic_clear_mask(ZFCP_STATUS_UNIT_READONLY, &unit->status); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED: + ZFCP_LOG_FLAGS(1, "FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED\n"); + ZFCP_LOG_INFO("error: The adapter ran out of resources. " + "There is no handle (temporary port identifier) " + "available for unit 0x%016Lx on port 0x%016Lx " + "on adapter %s\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + debug_text_event(adapter->erp_dbf, 1, + "fsf_s_max_units"); + zfcp_erp_unit_failed(unit); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (header->fsf_status_qual.word[0]) { + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + /* Re-establish link to port */ + debug_text_event(adapter->erp_dbf, 1, + "fsf_sq_ltest"); + zfcp_erp_port_reopen(unit->port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + /* ERP strategy will escalate */ + debug_text_event(adapter->erp_dbf, 1, + "fsf_sq_ulp"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_NORMAL + ("bug: Wrong status qualifier 0x%x arrived.\n", + header->fsf_status_qual.word[0]); + debug_text_event(adapter->erp_dbf, 0, + "fsf_sq_inval:"); + debug_exception(adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], + sizeof (u32)); + } + break; + + case FSF_INVALID_COMMAND_OPTION: + ZFCP_LOG_FLAGS(2, "FSF_INVALID_COMMAND_OPTION\n"); + ZFCP_LOG_NORMAL( + "Invalid option 0x%x has been specified " + "in QTCB bottom sent to the adapter %s\n", + bottom->option, + zfcp_get_busid_by_adapter(adapter)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EINVAL; + break; + + case FSF_GOOD: + ZFCP_LOG_FLAGS(3, "FSF_GOOD\n"); + /* save LUN handle assigned by FSF */ + unit->handle = header->lun_handle; + ZFCP_LOG_TRACE("unit 0x%016Lx on remote port 0x%016Lx on " + "adapter %s opened, port handle 0x%x\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + unit->handle); + /* mark unit as open */ + atomic_set_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status); + + if (adapter->supported_features & FSF_FEATURE_LUN_SHARING){ + if (!exclusive) + atomic_set_mask(ZFCP_STATUS_UNIT_SHARED, + &unit->status); + + if (!readwrite) { + atomic_set_mask(ZFCP_STATUS_UNIT_READONLY, + &unit->status); + ZFCP_LOG_NORMAL("read-only access for unit " + "(adapter %s, wwpn=0x%016Lx, " + "fcp_lun=0x%016Lx)\n", + zfcp_get_busid_by_unit(unit), + unit->port->wwpn, + unit->fcp_lun); + } + + if (exclusive && !readwrite) { + ZFCP_LOG_NORMAL("exclusive access of read-only " + "unit not supported\n"); + zfcp_erp_unit_failed(unit); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + zfcp_erp_unit_shutdown(unit, 0); + } else if (!exclusive && readwrite) { + ZFCP_LOG_NORMAL("shared access of read-write " + "unit not supported\n"); + zfcp_erp_unit_failed(unit); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + zfcp_erp_unit_shutdown(unit, 0); + } + } + + retval = 0; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", + header->fsf_status); + debug_text_event(adapter->erp_dbf, 0, "fsf_s_inval:"); + debug_exception(adapter->erp_dbf, 0, + &header->fsf_status, sizeof (u32)); + break; + } + + skip_fsfstatus: + atomic_clear_mask(ZFCP_STATUS_COMMON_OPENING, &unit->status); + return retval; +} + +/* + * function: zfcp_fsf_close_unit + * + * purpose: + * + * returns: address of fsf_req - request successfully initiated + * NULL - + * + * assumptions: This routine does not check whether the associated + * remote port/lun has already been opened. This should be + * done by calling routines. Otherwise some status + * may be presented by FSF + */ +int +zfcp_fsf_close_unit(struct zfcp_erp_action *erp_action) +{ + volatile struct qdio_buffer_element *sbale; + unsigned long lock_flags; + int retval = 0; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(erp_action->adapter, + FSF_QTCB_CLOSE_LUN, + ZFCP_WAIT_FOR_SBAL | ZFCP_REQ_AUTO_CLEANUP, + erp_action->adapter->pool.fsf_req_erp, + &lock_flags, &(erp_action->fsf_req)); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create close unit request for " + "unit 0x%016Lx on port 0x%016Lx on adapter %s.\n", + erp_action->unit->fcp_lun, + erp_action->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + goto out; + } + + sbale = zfcp_qdio_sbale_req(erp_action->fsf_req, + erp_action->fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_READ; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + erp_action->fsf_req->qtcb->header.port_handle = + erp_action->port->handle; + erp_action->fsf_req->qtcb->header.lun_handle = erp_action->unit->handle; + atomic_set_mask(ZFCP_STATUS_COMMON_CLOSING, &erp_action->unit->status); + erp_action->fsf_req->data.close_unit.unit = erp_action->unit; + erp_action->fsf_req->erp_action = erp_action; + + /* start QDIO request for this FSF request */ + retval = zfcp_fsf_req_send(erp_action->fsf_req, &erp_action->timer); + if (retval) { + ZFCP_LOG_INFO("error: Could not send a close unit request for " + "unit 0x%016Lx on port 0x%016Lx onadapter %s.\n", + erp_action->unit->fcp_lun, + erp_action->port->wwpn, + zfcp_get_busid_by_adapter(erp_action->adapter)); + zfcp_fsf_req_free(erp_action->fsf_req); + erp_action->fsf_req = NULL; + goto out; + } + + ZFCP_LOG_TRACE("Close LUN request initiated (adapter %s, " + "port 0x%016Lx, unit 0x%016Lx)\n", + zfcp_get_busid_by_adapter(erp_action->adapter), + erp_action->port->wwpn, erp_action->unit->fcp_lun); + out: + write_unlock_irqrestore(&erp_action->adapter->request_queue.queue_lock, + lock_flags); + return retval; +} + +/* + * function: zfcp_fsf_close_unit_handler + * + * purpose: is called for finished Close LUN FSF command + * + * returns: + */ +static int +zfcp_fsf_close_unit_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = -EINVAL; + struct zfcp_unit *unit; + + unit = fsf_req->data.close_unit.unit; /* restore unit */ + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + /* don't change unit status in our bookkeeping */ + goto skip_fsfstatus; + } + + /* evaluate FSF status in QTCB */ + switch (fsf_req->qtcb->header.fsf_status) { + + case FSF_PORT_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary port identifier 0x%x for port " + "0x%016Lx on adapter %s invalid. This may " + "happen in rare circumstances\n", + unit->port->handle, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + ZFCP_LOG_DEBUG("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_phand_nv"); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + zfcp_cmd_dbf_event_fsf("porthinv", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_LUN_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_LUN_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary LUN identifier 0x%x of unit " + "0x%016Lx on port 0x%016Lx on adapter %s is " + "invalid. This may happen occasionally.\n", + unit->handle, + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + ZFCP_LOG_DEBUG("Status qualifier data:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_lhand_nv"); + zfcp_erp_port_reopen(unit->port, 0); + zfcp_cmd_dbf_event_fsf("lunhinv", fsf_req, + &fsf_req->qtcb->header.fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PORT_BOXED: + ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n"); + ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s " + "needs to be reopened\n", + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_s_pboxed"); + zfcp_erp_port_reopen(unit->port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR | + ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (fsf_req->qtcb->header.fsf_status_qual.word[0]) { + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + /* re-establish link to port */ + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ltest"); + zfcp_erp_port_reopen(unit->port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + /* ERP strategy will escalate */ + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ulp"); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + ZFCP_LOG_NORMAL + ("bug: Wrong status qualifier 0x%x arrived.\n", + fsf_req->qtcb->header.fsf_status_qual.word[0]); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_inval:"); + debug_exception( + fsf_req->adapter->erp_dbf, 0, + &fsf_req->qtcb->header.fsf_status_qual.word[0], + sizeof (u32)); + break; + } + break; + + case FSF_GOOD: + ZFCP_LOG_FLAGS(3, "FSF_GOOD\n"); + ZFCP_LOG_TRACE("unit 0x%016Lx on port 0x%016Lx on adapter %s " + "closed, port handle 0x%x\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + unit->handle); + /* mark unit as closed */ + atomic_clear_mask(ZFCP_STATUS_COMMON_OPEN, &unit->status); + retval = 0; + break; + + default: + ZFCP_LOG_NORMAL("bug: An unknown FSF Status was presented " + "(debug info 0x%x)\n", + fsf_req->qtcb->header.fsf_status); + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &fsf_req->qtcb->header.fsf_status, + sizeof (u32)); + break; + } + + skip_fsfstatus: + atomic_clear_mask(ZFCP_STATUS_COMMON_CLOSING, &unit->status); + return retval; +} + +/** + * zfcp_fsf_send_fcp_command_task - initiate an FCP command (for a SCSI command) + * @adapter: adapter where scsi command is issued + * @unit: unit where command is sent to + * @scsi_cmnd: scsi command to be sent + * @timer: timer to be started when request is initiated + * @req_flags: flags for fsf_request + */ +int +zfcp_fsf_send_fcp_command_task(struct zfcp_adapter *adapter, + struct zfcp_unit *unit, + struct scsi_cmnd * scsi_cmnd, + struct timer_list *timer, int req_flags) +{ + struct zfcp_fsf_req *fsf_req = NULL; + struct fcp_cmnd_iu *fcp_cmnd_iu; + unsigned int sbtype; + unsigned long lock_flags; + int real_bytes = 0; + int retval = 0; + int mask; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(adapter, FSF_QTCB_FCP_CMND, req_flags, + adapter->pool.fsf_req_scsi, + &lock_flags, &fsf_req); + if (unlikely(retval < 0)) { + ZFCP_LOG_DEBUG("error: Could not create FCP command request " + "for unit 0x%016Lx on port 0x%016Lx on " + "adapter %s\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_adapter(adapter)); + goto failed_req_create; + } + + /* + * associate FSF request with SCSI request + * (need this for look up on abort) + */ + fsf_req->data.send_fcp_command_task.fsf_req = fsf_req; + scsi_cmnd->host_scribble = (char *) &(fsf_req->data); + + /* + * associate SCSI command with FSF request + * (need this for look up on normal command completion) + */ + fsf_req->data.send_fcp_command_task.scsi_cmnd = scsi_cmnd; + fsf_req->data.send_fcp_command_task.start_jiffies = jiffies; + fsf_req->data.send_fcp_command_task.unit = unit; + ZFCP_LOG_DEBUG("unit=%p, fcp_lun=0x%016Lx\n", unit, unit->fcp_lun); + + /* set handles of unit and its parent port in QTCB */ + fsf_req->qtcb->header.lun_handle = unit->handle; + fsf_req->qtcb->header.port_handle = unit->port->handle; + + /* FSF does not define the structure of the FCP_CMND IU */ + fcp_cmnd_iu = (struct fcp_cmnd_iu *) + &(fsf_req->qtcb->bottom.io.fcp_cmnd); + + /* + * set depending on data direction: + * data direction bits in SBALE (SB Type) + * data direction bits in QTCB + * data direction bits in FCP_CMND IU + */ + switch (scsi_cmnd->sc_data_direction) { + case DMA_NONE: + ZFCP_LOG_FLAGS(3, "DMA_NONE\n"); + fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_CMND; + /* + * FIXME(qdio): + * what is the correct type for commands + * without 'real' data buffers? + */ + sbtype = SBAL_FLAGS0_TYPE_READ; + break; + case DMA_FROM_DEVICE: + ZFCP_LOG_FLAGS(3, "DMA_FROM_DEVICE\n"); + fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_READ; + sbtype = SBAL_FLAGS0_TYPE_READ; + fcp_cmnd_iu->rddata = 1; + break; + case DMA_TO_DEVICE: + ZFCP_LOG_FLAGS(3, "DMA_TO_DEVICE\n"); + fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_WRITE; + sbtype = SBAL_FLAGS0_TYPE_WRITE; + fcp_cmnd_iu->wddata = 1; + break; + case DMA_BIDIRECTIONAL: + ZFCP_LOG_FLAGS(0, "DMA_BIDIRECTIONAL not supported\n"); + default: + /* + * dummy, catch this condition earlier + * in zfcp_scsi_queuecommand + */ + goto failed_scsi_cmnd; + } + + /* set FC service class in QTCB (3 per default) */ + fsf_req->qtcb->bottom.io.service_class = adapter->fc_service_class; + + /* set FCP_LUN in FCP_CMND IU in QTCB */ + fcp_cmnd_iu->fcp_lun = unit->fcp_lun; + + mask = ZFCP_STATUS_UNIT_READONLY | ZFCP_STATUS_UNIT_SHARED; + + /* set task attributes in FCP_CMND IU in QTCB */ + if (likely((scsi_cmnd->device->simple_tags) || + (atomic_test_mask(mask, &unit->status)))) + fcp_cmnd_iu->task_attribute = SIMPLE_Q; + else + fcp_cmnd_iu->task_attribute = UNTAGGED; + + /* set additional length of FCP_CDB in FCP_CMND IU in QTCB, if needed */ + if (unlikely(scsi_cmnd->cmd_len > FCP_CDB_LENGTH)) { + fcp_cmnd_iu->add_fcp_cdb_length + = (scsi_cmnd->cmd_len - FCP_CDB_LENGTH) >> 2; + ZFCP_LOG_TRACE("SCSI CDB length is 0x%x, " + "additional FCP_CDB length is 0x%x " + "(shifted right 2 bits)\n", + scsi_cmnd->cmd_len, + fcp_cmnd_iu->add_fcp_cdb_length); + } + /* + * copy SCSI CDB (including additional length, if any) to + * FCP_CDB in FCP_CMND IU in QTCB + */ + memcpy(fcp_cmnd_iu->fcp_cdb, scsi_cmnd->cmnd, scsi_cmnd->cmd_len); + + /* FCP CMND IU length in QTCB */ + fsf_req->qtcb->bottom.io.fcp_cmnd_length = + sizeof (struct fcp_cmnd_iu) + + fcp_cmnd_iu->add_fcp_cdb_length + sizeof (fcp_dl_t); + + /* generate SBALEs from data buffer */ + real_bytes = zfcp_qdio_sbals_from_scsicmnd(fsf_req, sbtype, scsi_cmnd); + if (unlikely(real_bytes < 0)) { + if (fsf_req->sbal_number < ZFCP_MAX_SBALS_PER_REQ) { + ZFCP_LOG_DEBUG( + "Data did not fit into available buffer(s), " + "waiting for more...\n"); + retval = -EIO; + } else { + ZFCP_LOG_NORMAL("error: No truncation implemented but " + "required. Shutting down unit " + "(adapter %s, port 0x%016Lx, " + "unit 0x%016Lx)\n", + zfcp_get_busid_by_unit(unit), + unit->port->wwpn, + unit->fcp_lun); + zfcp_erp_unit_shutdown(unit, 0); + retval = -EINVAL; + } + goto no_fit; + } + + /* set length of FCP data length in FCP_CMND IU in QTCB */ + zfcp_set_fcp_dl(fcp_cmnd_iu, real_bytes); + + ZFCP_LOG_DEBUG("Sending SCSI command:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) scsi_cmnd->cmnd, scsi_cmnd->cmd_len); + + /* + * start QDIO request for this FSF request + * covered by an SBALE) + */ + retval = zfcp_fsf_req_send(fsf_req, timer); + if (unlikely(retval < 0)) { + ZFCP_LOG_INFO("error: Could not send FCP command request " + "on adapter %s, port 0x%016Lx, unit 0x%016Lx\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, + unit->fcp_lun); + goto send_failed; + } + + ZFCP_LOG_TRACE("Send FCP Command initiated (adapter %s, " + "port 0x%016Lx, unit 0x%016Lx)\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, + unit->fcp_lun); + goto success; + + send_failed: + no_fit: + failed_scsi_cmnd: + zfcp_fsf_req_free(fsf_req); + fsf_req = NULL; + scsi_cmnd->host_scribble = NULL; + success: + failed_req_create: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags); + return retval; +} + +/* + * function: zfcp_fsf_send_fcp_command_task_management + * + * purpose: + * + * returns: + * + * FIXME(design): should be watched by a timeout!!! + * FIXME(design) shouldn't this be modified to return an int + * also...don't know how though + * + */ +struct zfcp_fsf_req * +zfcp_fsf_send_fcp_command_task_management(struct zfcp_adapter *adapter, + struct zfcp_unit *unit, + u8 tm_flags, int req_flags) +{ + struct zfcp_fsf_req *fsf_req = NULL; + int retval = 0; + struct fcp_cmnd_iu *fcp_cmnd_iu; + unsigned long lock_flags; + volatile struct qdio_buffer_element *sbale; + + /* setup new FSF request */ + retval = zfcp_fsf_req_create(adapter, FSF_QTCB_FCP_CMND, req_flags, + adapter->pool.fsf_req_scsi, + &lock_flags, &fsf_req); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create FCP command (task " + "management) request for adapter %s, port " + " 0x%016Lx, unit 0x%016Lx.\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, unit->fcp_lun); + goto out; + } + + /* + * Used to decide on proper handler in the return path, + * could be either zfcp_fsf_send_fcp_command_task_handler or + * zfcp_fsf_send_fcp_command_task_management_handler */ + + fsf_req->status |= ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT; + + /* + * hold a pointer to the unit being target of this + * task management request + */ + fsf_req->data.send_fcp_command_task_management.unit = unit; + + /* set FSF related fields in QTCB */ + fsf_req->qtcb->header.lun_handle = unit->handle; + fsf_req->qtcb->header.port_handle = unit->port->handle; + fsf_req->qtcb->bottom.io.data_direction = FSF_DATADIR_CMND; + fsf_req->qtcb->bottom.io.service_class = adapter->fc_service_class; + fsf_req->qtcb->bottom.io.fcp_cmnd_length = + sizeof (struct fcp_cmnd_iu) + sizeof (fcp_dl_t); + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale[0].flags |= SBAL_FLAGS0_TYPE_WRITE; + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + /* set FCP related fields in FCP_CMND IU in QTCB */ + fcp_cmnd_iu = (struct fcp_cmnd_iu *) + &(fsf_req->qtcb->bottom.io.fcp_cmnd); + fcp_cmnd_iu->fcp_lun = unit->fcp_lun; + fcp_cmnd_iu->task_management_flags = tm_flags; + + /* start QDIO request for this FSF request */ + zfcp_fsf_start_scsi_er_timer(adapter); + retval = zfcp_fsf_req_send(fsf_req, NULL); + if (retval) { + del_timer(&adapter->scsi_er_timer); + ZFCP_LOG_INFO("error: Could not send an FCP-command (task " + "management) on adapter %s, port 0x%016Lx for " + "unit LUN 0x%016Lx\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, + unit->fcp_lun); + zfcp_fsf_req_free(fsf_req); + fsf_req = NULL; + goto out; + } + + ZFCP_LOG_TRACE("Send FCP Command (task management function) initiated " + "(adapter %s, port 0x%016Lx, unit 0x%016Lx, " + "tm_flags=0x%x)\n", + zfcp_get_busid_by_adapter(adapter), + unit->port->wwpn, + unit->fcp_lun, + tm_flags); + out: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags); + return fsf_req; +} + +/* + * function: zfcp_fsf_send_fcp_command_handler + * + * purpose: is called for finished Send FCP Command + * + * returns: + */ +static int +zfcp_fsf_send_fcp_command_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = -EINVAL; + struct zfcp_unit *unit; + struct fsf_qtcb_header *header; + u16 subtable, rule, counter; + + header = &fsf_req->qtcb->header; + + if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT)) + unit = fsf_req->data.send_fcp_command_task_management.unit; + else + unit = fsf_req->data.send_fcp_command_task.unit; + + if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) { + /* go directly to calls of special handlers */ + goto skip_fsfstatus; + } + + /* evaluate FSF status in QTCB */ + switch (header->fsf_status) { + + case FSF_PORT_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_PORT_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary port identifier 0x%x for port " + "0x%016Lx on adapter %s invalid\n", + unit->port->handle, + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_phand_nv"); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_LUN_HANDLE_NOT_VALID: + ZFCP_LOG_FLAGS(1, "FSF_LUN_HANDLE_NOT_VALID\n"); + ZFCP_LOG_INFO("Temporary LUN identifier 0x%x for unit " + "0x%016Lx on port 0x%016Lx on adapter %s is " + "invalid. This may happen occasionally.\n", + unit->handle, + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + ZFCP_LOG_NORMAL("Status qualifier data:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_uhand_nv"); + zfcp_erp_port_reopen(unit->port, 0); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_HANDLE_MISMATCH: + ZFCP_LOG_FLAGS(0, "FSF_HANDLE_MISMATCH\n"); + ZFCP_LOG_NORMAL("bug: The port handle 0x%x has changed " + "unexpectedly. (adapter %s, port 0x%016Lx, " + "unit 0x%016Lx)\n", + unit->port->handle, + zfcp_get_busid_by_unit(unit), + unit->port->wwpn, + unit->fcp_lun); + ZFCP_LOG_NORMAL("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_hand_mis"); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + zfcp_cmd_dbf_event_fsf("handmism", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_SERVICE_CLASS_NOT_SUPPORTED: + ZFCP_LOG_FLAGS(0, "FSF_SERVICE_CLASS_NOT_SUPPORTED\n"); + if (fsf_req->adapter->fc_service_class <= 3) { + ZFCP_LOG_NORMAL("error: The adapter %s does " + "not support fibrechannel class %d.\n", + zfcp_get_busid_by_unit(unit), + fsf_req->adapter->fc_service_class); + } else { + ZFCP_LOG_NORMAL("bug: The fibrechannel class at " + "adapter %s is invalid. " + "(debug info %d)\n", + zfcp_get_busid_by_unit(unit), + fsf_req->adapter->fc_service_class); + } + /* stop operation for this adapter */ + debug_text_exception(fsf_req->adapter->erp_dbf, 0, + "fsf_s_class_nsup"); + zfcp_erp_adapter_shutdown(unit->port->adapter, 0); + zfcp_cmd_dbf_event_fsf("unsclass", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_FCPLUN_NOT_VALID: + ZFCP_LOG_FLAGS(0, "FSF_FCPLUN_NOT_VALID\n"); + ZFCP_LOG_NORMAL("bug: unit 0x%016Lx on port 0x%016Lx on " + "adapter %s does not have correct unit " + "handle 0x%x\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + unit->handle); + ZFCP_LOG_DEBUG("status qualifier:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_s_fcp_lun_nv"); + zfcp_erp_port_reopen(unit->port, 0); + zfcp_cmd_dbf_event_fsf("fluninv", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_ACCESS_DENIED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_DENIED\n"); + ZFCP_LOG_NORMAL("Access denied, cannot send FCP command to " + "unit 0x%016Lx on port 0x%016Lx on " + "adapter %s\n", unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + for (counter = 0; counter < 2; counter++) { + subtable = header->fsf_status_qual.halfword[counter * 2]; + rule = header->fsf_status_qual.halfword[counter * 2 + 1]; + switch (subtable) { + case FSF_SQ_CFDC_SUBTABLE_OS: + case FSF_SQ_CFDC_SUBTABLE_PORT_WWPN: + case FSF_SQ_CFDC_SUBTABLE_PORT_DID: + case FSF_SQ_CFDC_SUBTABLE_LUN: + ZFCP_LOG_INFO("Access denied (%s rule %d)\n", + zfcp_act_subtable_type[subtable], rule); + break; + } + } + debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_access"); + zfcp_erp_unit_access_denied(unit); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_DIRECTION_INDICATOR_NOT_VALID: + ZFCP_LOG_FLAGS(0, "FSF_DIRECTION_INDICATOR_NOT_VALID\n"); + ZFCP_LOG_INFO("bug: Invalid data direction given for unit " + "0x%016Lx on port 0x%016Lx on adapter %s " + "(debug info %d)\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + fsf_req->qtcb->bottom.io.data_direction); + /* stop operation for this adapter */ + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_s_dir_ind_nv"); + zfcp_erp_adapter_shutdown(unit->port->adapter, 0); + zfcp_cmd_dbf_event_fsf("dirinv", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_CMND_LENGTH_NOT_VALID: + ZFCP_LOG_FLAGS(0, "FSF_CMND_LENGTH_NOT_VALID\n"); + ZFCP_LOG_NORMAL + ("bug: An invalid control-data-block length field " + "was found in a command for unit 0x%016Lx on port " + "0x%016Lx on adapter %s " "(debug info %d)\n", + unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + fsf_req->qtcb->bottom.io.fcp_cmnd_length); + /* stop operation for this adapter */ + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_s_cmd_len_nv"); + zfcp_erp_adapter_shutdown(unit->port->adapter, 0); + zfcp_cmd_dbf_event_fsf("cleninv", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + + case FSF_PORT_BOXED: + ZFCP_LOG_FLAGS(2, "FSF_PORT_BOXED\n"); + ZFCP_LOG_DEBUG("The remote port 0x%016Lx on adapter %s " + "needs to be reopened\n", + unit->port->wwpn, zfcp_get_busid_by_unit(unit)); + debug_text_event(fsf_req->adapter->erp_dbf, 2, "fsf_s_pboxed"); + zfcp_erp_port_reopen(unit->port, 0); + zfcp_cmd_dbf_event_fsf("portbox", fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR | + ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_LUN_BOXED: + ZFCP_LOG_FLAGS(0, "FSF_LUN_BOXED\n"); + ZFCP_LOG_NORMAL("unit needs to be reopened (adapter %s, " + "wwpn=0x%016Lx, fcp_lun=0x%016Lx)\n", + zfcp_get_busid_by_unit(unit), + unit->port->wwpn, unit->fcp_lun); + debug_text_event(fsf_req->adapter->erp_dbf, 1, "fsf_s_lboxed"); + zfcp_erp_unit_reopen(unit, 0); + zfcp_cmd_dbf_event_fsf("unitbox", fsf_req, + &header->fsf_status_qual, + sizeof(union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR + | ZFCP_STATUS_FSFREQ_RETRY; + break; + + case FSF_ADAPTER_STATUS_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_ADAPTER_STATUS_AVAILABLE\n"); + switch (header->fsf_status_qual.word[0]) { + case FSF_SQ_INVOKE_LINK_TEST_PROCEDURE: + ZFCP_LOG_FLAGS(2, + "FSF_SQ_INVOKE_LINK_TEST_PROCEDURE\n"); + /* re-establish link to port */ + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ltest"); + zfcp_erp_port_reopen(unit->port, 0); + zfcp_cmd_dbf_event_fsf( + "sqltest", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + case FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED: + ZFCP_LOG_FLAGS(3, + "FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED\n"); + /* FIXME(hw) need proper specs for proper action */ + /* let scsi stack deal with retries and escalation */ + debug_text_event(fsf_req->adapter->erp_dbf, 1, + "fsf_sq_ulp"); + zfcp_cmd_dbf_event_fsf( + "sqdeperp", + fsf_req, + &header->fsf_status_qual, + sizeof (union fsf_status_qual)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + break; + default: + /* FIXME: shall we consider this a successful transfer? */ + ZFCP_LOG_NORMAL + ("bug: Wrong status qualifier 0x%x arrived.\n", + header->fsf_status_qual.word[0]); + debug_text_event(fsf_req->adapter->erp_dbf, 0, + "fsf_sq_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], + sizeof(u32)); + break; + } + break; + + case FSF_GOOD: + ZFCP_LOG_FLAGS(3, "FSF_GOOD\n"); + break; + + case FSF_FCP_RSP_AVAILABLE: + ZFCP_LOG_FLAGS(2, "FSF_FCP_RSP_AVAILABLE\n"); + break; + + default: + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_s_inval:"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &header->fsf_status, sizeof(u32)); + break; + } + + skip_fsfstatus: + if (fsf_req->status & ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT) { + retval = + zfcp_fsf_send_fcp_command_task_management_handler(fsf_req); + } else { + retval = zfcp_fsf_send_fcp_command_task_handler(fsf_req); + } + return retval; +} + +/* + * function: zfcp_fsf_send_fcp_command_task_handler + * + * purpose: evaluates FCP_RSP IU + * + * returns: + */ +static int +zfcp_fsf_send_fcp_command_task_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + struct scsi_cmnd *scpnt; + struct fcp_rsp_iu *fcp_rsp_iu = (struct fcp_rsp_iu *) + &(fsf_req->qtcb->bottom.io.fcp_rsp); + struct fcp_cmnd_iu *fcp_cmnd_iu = (struct fcp_cmnd_iu *) + &(fsf_req->qtcb->bottom.io.fcp_cmnd); + u32 sns_len; + char *fcp_rsp_info = zfcp_get_fcp_rsp_info_ptr(fcp_rsp_iu); + unsigned long flags; + struct zfcp_unit *unit = fsf_req->data.send_fcp_command_task.unit; + + read_lock_irqsave(&fsf_req->adapter->abort_lock, flags); + scpnt = fsf_req->data.send_fcp_command_task.scsi_cmnd; + if (unlikely(!scpnt)) { + ZFCP_LOG_DEBUG + ("Command with fsf_req %p is not associated to " + "a scsi command anymore. Aborted?\n", fsf_req); + goto out; + } + if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ABORTED)) { + /* FIXME: (design) mid-layer should handle DID_ABORT like + * DID_SOFT_ERROR by retrying the request for devices + * that allow retries. + */ + ZFCP_LOG_DEBUG("Setting DID_SOFT_ERROR and SUGGEST_RETRY\n"); + set_host_byte(&scpnt->result, DID_SOFT_ERROR); + set_driver_byte(&scpnt->result, SUGGEST_RETRY); + goto skip_fsfstatus; + } + + if (unlikely(fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR)) { + ZFCP_LOG_DEBUG("Setting DID_ERROR\n"); + set_host_byte(&scpnt->result, DID_ERROR); + goto skip_fsfstatus; + } + + /* set message byte of result in SCSI command */ + scpnt->result |= COMMAND_COMPLETE << 8; + + /* + * copy SCSI status code of FCP_STATUS of FCP_RSP IU to status byte + * of result in SCSI command + */ + scpnt->result |= fcp_rsp_iu->scsi_status; + if (unlikely(fcp_rsp_iu->scsi_status)) { + /* DEBUG */ + ZFCP_LOG_DEBUG("status for SCSI Command:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + scpnt->cmnd, scpnt->cmd_len); + ZFCP_LOG_DEBUG("SCSI status code 0x%x\n", + fcp_rsp_iu->scsi_status); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (void *) fcp_rsp_iu, sizeof (struct fcp_rsp_iu)); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + zfcp_get_fcp_sns_info_ptr(fcp_rsp_iu), + fcp_rsp_iu->fcp_sns_len); + } + + /* check FCP_RSP_INFO */ + if (unlikely(fcp_rsp_iu->validity.bits.fcp_rsp_len_valid)) { + ZFCP_LOG_DEBUG("rsp_len is valid\n"); + switch (fcp_rsp_info[3]) { + case RSP_CODE_GOOD: + ZFCP_LOG_FLAGS(3, "RSP_CODE_GOOD\n"); + /* ok, continue */ + ZFCP_LOG_TRACE("no failure or Task Management " + "Function complete\n"); + set_host_byte(&scpnt->result, DID_OK); + break; + case RSP_CODE_LENGTH_MISMATCH: + ZFCP_LOG_FLAGS(0, "RSP_CODE_LENGTH_MISMATCH\n"); + /* hardware bug */ + ZFCP_LOG_NORMAL("bug: FCP response code indictates " + "that the fibrechannel protocol data " + "length differs from the burst length. " + "The problem occured on unit 0x%016Lx " + "on port 0x%016Lx on adapter %s", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + /* dump SCSI CDB as prepared by zfcp */ + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb-> + bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE); + zfcp_cmd_dbf_event_fsf("clenmis", fsf_req, NULL, 0); + set_host_byte(&scpnt->result, DID_ERROR); + goto skip_fsfstatus; + case RSP_CODE_FIELD_INVALID: + ZFCP_LOG_FLAGS(0, "RSP_CODE_FIELD_INVALID\n"); + /* driver or hardware bug */ + ZFCP_LOG_NORMAL("bug: FCP response code indictates " + "that the fibrechannel protocol data " + "fields were incorrectly set up. " + "The problem occured on the unit " + "0x%016Lx on port 0x%016Lx on " + "adapter %s", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + /* dump SCSI CDB as prepared by zfcp */ + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb-> + bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE); + set_host_byte(&scpnt->result, DID_ERROR); + zfcp_cmd_dbf_event_fsf("codeinv", fsf_req, NULL, 0); + goto skip_fsfstatus; + case RSP_CODE_RO_MISMATCH: + ZFCP_LOG_FLAGS(0, "RSP_CODE_RO_MISMATCH\n"); + /* hardware bug */ + ZFCP_LOG_NORMAL("bug: The FCP response code indicates " + "that conflicting values for the " + "fibrechannel payload offset from the " + "header were found. " + "The problem occured on unit 0x%016Lx " + "on port 0x%016Lx on adapter %s.\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + /* dump SCSI CDB as prepared by zfcp */ + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb-> + bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE); + zfcp_cmd_dbf_event_fsf("codemism", fsf_req, NULL, 0); + set_host_byte(&scpnt->result, DID_ERROR); + goto skip_fsfstatus; + default: + ZFCP_LOG_NORMAL("bug: An invalid FCP response " + "code was detected for a command. " + "The problem occured on the unit " + "0x%016Lx on port 0x%016Lx on " + "adapter %s (debug info 0x%x)\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + fcp_rsp_info[3]); + /* dump SCSI CDB as prepared by zfcp */ + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_DEBUG, + (char *) &fsf_req->qtcb-> + bottom.io.fcp_cmnd, FSF_FCP_CMND_SIZE); + zfcp_cmd_dbf_event_fsf("undeffcp", fsf_req, NULL, 0); + set_host_byte(&scpnt->result, DID_ERROR); + } + } + + /* check for sense data */ + if (unlikely(fcp_rsp_iu->validity.bits.fcp_sns_len_valid)) { + sns_len = FSF_FCP_RSP_SIZE - + sizeof (struct fcp_rsp_iu) + fcp_rsp_iu->fcp_rsp_len; + ZFCP_LOG_TRACE("room for %i bytes sense data in QTCB\n", + sns_len); + sns_len = min(sns_len, (u32) SCSI_SENSE_BUFFERSIZE); + ZFCP_LOG_TRACE("room for %i bytes sense data in SCSI command\n", + SCSI_SENSE_BUFFERSIZE); + sns_len = min(sns_len, fcp_rsp_iu->fcp_sns_len); + ZFCP_LOG_TRACE("scpnt->result =0x%x, command was:\n", + scpnt->result); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, + (void *) &scpnt->cmnd, scpnt->cmd_len); + + ZFCP_LOG_TRACE("%i bytes sense data provided by FCP\n", + fcp_rsp_iu->fcp_sns_len); + memcpy(&scpnt->sense_buffer, + zfcp_get_fcp_sns_info_ptr(fcp_rsp_iu), sns_len); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, + (void *) &scpnt->sense_buffer, sns_len); + } + + /* check for overrun */ + if (unlikely(fcp_rsp_iu->validity.bits.fcp_resid_over)) { + ZFCP_LOG_INFO("A data overrun was detected for a command. " + "unit 0x%016Lx, port 0x%016Lx, adapter %s. " + "The response data length is " + "%d, the original length was %d.\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + fcp_rsp_iu->fcp_resid, + (int) zfcp_get_fcp_dl(fcp_cmnd_iu)); + } + + /* check for underrun */ + if (unlikely(fcp_rsp_iu->validity.bits.fcp_resid_under)) { + ZFCP_LOG_INFO("A data underrun was detected for a command. " + "unit 0x%016Lx, port 0x%016Lx, adapter %s. " + "The response data length is " + "%d, the original length was %d.\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + fcp_rsp_iu->fcp_resid, + (int) zfcp_get_fcp_dl(fcp_cmnd_iu)); + + scpnt->resid = fcp_rsp_iu->fcp_resid; + if (scpnt->request_bufflen - scpnt->resid < scpnt->underflow) + scpnt->result |= DID_ERROR << 16; + } + + skip_fsfstatus: + ZFCP_LOG_DEBUG("scpnt->result =0x%x\n", scpnt->result); + + zfcp_cmd_dbf_event_scsi("response", scpnt); + + /* cleanup pointer (need this especially for abort) */ + scpnt->host_scribble = NULL; + + /* + * NOTE: + * according to the outcome of a discussion on linux-scsi we + * don't need to grab the io_request_lock here since we use + * the new eh + */ + /* always call back */ + + (scpnt->scsi_done) (scpnt); + + /* + * We must hold this lock until scsi_done has been called. + * Otherwise we may call scsi_done after abort regarding this + * command has completed. + * Note: scsi_done must not block! + */ + out: + read_unlock_irqrestore(&fsf_req->adapter->abort_lock, flags); + return retval; +} + +/* + * function: zfcp_fsf_send_fcp_command_task_management_handler + * + * purpose: evaluates FCP_RSP IU + * + * returns: + */ +static int +zfcp_fsf_send_fcp_command_task_management_handler(struct zfcp_fsf_req *fsf_req) +{ + int retval = 0; + struct fcp_rsp_iu *fcp_rsp_iu = (struct fcp_rsp_iu *) + &(fsf_req->qtcb->bottom.io.fcp_rsp); + char *fcp_rsp_info = zfcp_get_fcp_rsp_info_ptr(fcp_rsp_iu); + struct zfcp_unit *unit = + fsf_req->data.send_fcp_command_task_management.unit; + + del_timer(&fsf_req->adapter->scsi_er_timer); + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED; + goto skip_fsfstatus; + } + + /* check FCP_RSP_INFO */ + switch (fcp_rsp_info[3]) { + case RSP_CODE_GOOD: + ZFCP_LOG_FLAGS(3, "RSP_CODE_GOOD\n"); + /* ok, continue */ + ZFCP_LOG_DEBUG("no failure or Task Management " + "Function complete\n"); + break; + case RSP_CODE_TASKMAN_UNSUPP: + ZFCP_LOG_FLAGS(0, "RSP_CODE_TASKMAN_UNSUPP\n"); + ZFCP_LOG_NORMAL("bug: A reuested task management function " + "is not supported on the target device " + "unit 0x%016Lx, port 0x%016Lx, adapter %s\n ", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP; + break; + case RSP_CODE_TASKMAN_FAILED: + ZFCP_LOG_FLAGS(0, "RSP_CODE_TASKMAN_FAILED\n"); + ZFCP_LOG_NORMAL("bug: A reuested task management function " + "failed to complete successfully. " + "unit 0x%016Lx, port 0x%016Lx, adapter %s.\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED; + break; + default: + ZFCP_LOG_NORMAL("bug: An invalid FCP response " + "code was detected for a command. " + "unit 0x%016Lx, port 0x%016Lx, adapter %s " + "(debug info 0x%x)\n", + unit->fcp_lun, + unit->port->wwpn, + zfcp_get_busid_by_unit(unit), + fcp_rsp_info[3]); + fsf_req->status |= ZFCP_STATUS_FSFREQ_TMFUNCFAILED; + } + + skip_fsfstatus: + return retval; +} + + +/* + * function: zfcp_fsf_control_file + * + * purpose: Initiator of the control file upload/download FSF requests + * + * returns: 0 - FSF request is successfuly created and queued + * -EOPNOTSUPP - The FCP adapter does not have Control File support + * -EINVAL - Invalid direction specified + * -ENOMEM - Insufficient memory + * -EPERM - Cannot create FSF request or place it in QDIO queue + */ +int +zfcp_fsf_control_file(struct zfcp_adapter *adapter, + struct zfcp_fsf_req **fsf_req_ptr, + u32 fsf_command, + u32 option, + struct zfcp_sg_list *sg_list) +{ + struct zfcp_fsf_req *fsf_req; + struct fsf_qtcb_bottom_support *bottom; + volatile struct qdio_buffer_element *sbale; + struct timer_list *timer; + unsigned long lock_flags; + int req_flags = 0; + int direction; + int retval = 0; + + if (!(adapter->supported_features & FSF_FEATURE_CFDC)) { + ZFCP_LOG_INFO("cfdc not supported (adapter %s)\n", + zfcp_get_busid_by_adapter(adapter)); + retval = -EOPNOTSUPP; + goto out; + } + + switch (fsf_command) { + + case FSF_QTCB_DOWNLOAD_CONTROL_FILE: + direction = SBAL_FLAGS0_TYPE_WRITE; + if ((option != FSF_CFDC_OPTION_FULL_ACCESS) && + (option != FSF_CFDC_OPTION_RESTRICTED_ACCESS)) + req_flags = ZFCP_WAIT_FOR_SBAL; + break; + + case FSF_QTCB_UPLOAD_CONTROL_FILE: + direction = SBAL_FLAGS0_TYPE_READ; + break; + + default: + ZFCP_LOG_INFO("Invalid FSF command code 0x%08x\n", fsf_command); + retval = -EINVAL; + goto out; + } + + timer = kmalloc(sizeof(struct timer_list), GFP_KERNEL); + if (!timer) { + retval = -ENOMEM; + goto out; + } + + retval = zfcp_fsf_req_create(adapter, fsf_command, req_flags, + NULL, &lock_flags, &fsf_req); + if (retval < 0) { + ZFCP_LOG_INFO("error: Could not create FSF request for the " + "adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + retval = -EPERM; + goto unlock_queue_lock; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale[0].flags |= direction; + + bottom = &fsf_req->qtcb->bottom.support; + bottom->operation_subtype = FSF_CFDC_OPERATION_SUBTYPE; + bottom->option = option; + + if (sg_list->count > 0) { + int bytes; + + bytes = zfcp_qdio_sbals_from_sg(fsf_req, direction, + sg_list->sg, sg_list->count, + ZFCP_MAX_SBALS_PER_REQ); + if (bytes != ZFCP_CFDC_MAX_CONTROL_FILE_SIZE) { + ZFCP_LOG_INFO( + "error: Could not create sufficient number of " + "SBALS for an FSF request to the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + retval = -ENOMEM; + goto free_fsf_req; + } + } else + sbale[1].flags |= SBAL_FLAGS_LAST_ENTRY; + + init_timer(timer); + timer->function = zfcp_fsf_request_timeout_handler; + timer->data = (unsigned long) adapter; + timer->expires = ZFCP_FSF_REQUEST_TIMEOUT; + + retval = zfcp_fsf_req_send(fsf_req, timer); + if (retval < 0) { + ZFCP_LOG_INFO("initiation of cfdc up/download failed" + "(adapter %s)\n", + zfcp_get_busid_by_adapter(adapter)); + retval = -EPERM; + goto free_fsf_req; + } + write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags); + + ZFCP_LOG_NORMAL("Control file %s FSF request has been sent to the " + "adapter %s\n", + fsf_command == FSF_QTCB_DOWNLOAD_CONTROL_FILE ? + "download" : "upload", + zfcp_get_busid_by_adapter(adapter)); + + wait_event(fsf_req->completion_wq, + fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED); + + *fsf_req_ptr = fsf_req; + del_timer_sync(timer); + goto free_timer; + + free_fsf_req: + zfcp_fsf_req_free(fsf_req); + unlock_queue_lock: + write_unlock_irqrestore(&adapter->request_queue.queue_lock, lock_flags); + free_timer: + kfree(timer); + out: + return retval; +} + + +/* + * function: zfcp_fsf_control_file_handler + * + * purpose: Handler of the control file upload/download FSF requests + * + * returns: 0 - FSF request successfuly processed + * -EAGAIN - Operation has to be repeated because of a temporary problem + * -EACCES - There is no permission to execute an operation + * -EPERM - The control file is not in a right format + * -EIO - There is a problem with the FCP adapter + * -EINVAL - Invalid operation + * -EFAULT - User space memory I/O operation fault + */ +static int +zfcp_fsf_control_file_handler(struct zfcp_fsf_req *fsf_req) +{ + struct zfcp_adapter *adapter = fsf_req->adapter; + struct fsf_qtcb_header *header = &fsf_req->qtcb->header; + struct fsf_qtcb_bottom_support *bottom = &fsf_req->qtcb->bottom.support; + int retval = 0; + + if (fsf_req->status & ZFCP_STATUS_FSFREQ_ERROR) { + retval = -EINVAL; + goto skip_fsfstatus; + } + + switch (header->fsf_status) { + + case FSF_GOOD: + ZFCP_LOG_FLAGS(2, "FSF_GOOD\n"); + ZFCP_LOG_NORMAL( + "The FSF request has been successfully completed " + "on the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + break; + + case FSF_OPERATION_PARTIALLY_SUCCESSFUL: + ZFCP_LOG_FLAGS(2, "FSF_OPERATION_PARTIALLY_SUCCESSFUL\n"); + if (bottom->operation_subtype == FSF_CFDC_OPERATION_SUBTYPE) { + switch (header->fsf_status_qual.word[0]) { + + case FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE: + ZFCP_LOG_NORMAL( + "CFDC of the adapter %s could not " + "be saved on the SE\n", + zfcp_get_busid_by_adapter(adapter)); + break; + + case FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE2: + ZFCP_LOG_NORMAL( + "CFDC of the adapter %s could not " + "be copied to the secondary SE\n", + zfcp_get_busid_by_adapter(adapter)); + break; + + default: + ZFCP_LOG_NORMAL( + "CFDC could not be hardened " + "on the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + } + } + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EAGAIN; + break; + + case FSF_AUTHORIZATION_FAILURE: + ZFCP_LOG_FLAGS(2, "FSF_AUTHORIZATION_FAILURE\n"); + ZFCP_LOG_NORMAL( + "Adapter %s does not accept privileged commands\n", + zfcp_get_busid_by_adapter(adapter)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EACCES; + break; + + case FSF_CFDC_ERROR_DETECTED: + ZFCP_LOG_FLAGS(2, "FSF_CFDC_ERROR_DETECTED\n"); + ZFCP_LOG_NORMAL( + "Error at position %d in the CFDC, " + "CFDC is discarded by the adapter %s\n", + header->fsf_status_qual.word[0], + zfcp_get_busid_by_adapter(adapter)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EPERM; + break; + + case FSF_CONTROL_FILE_UPDATE_ERROR: + ZFCP_LOG_FLAGS(2, "FSF_CONTROL_FILE_UPDATE_ERROR\n"); + ZFCP_LOG_NORMAL( + "Adapter %s cannot harden the control file, " + "file is discarded\n", + zfcp_get_busid_by_adapter(adapter)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EIO; + break; + + case FSF_CONTROL_FILE_TOO_LARGE: + ZFCP_LOG_FLAGS(2, "FSF_CONTROL_FILE_TOO_LARGE\n"); + ZFCP_LOG_NORMAL( + "Control file is too large, file is discarded " + "by the adapter %s\n", + zfcp_get_busid_by_adapter(adapter)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EIO; + break; + + case FSF_ACCESS_CONFLICT_DETECTED: + ZFCP_LOG_FLAGS(2, "FSF_ACCESS_CONFLICT_DETECTED\n"); + if (bottom->operation_subtype == FSF_CFDC_OPERATION_SUBTYPE) + ZFCP_LOG_NORMAL( + "CFDC has been discarded by the adapter %s, " + "because activation would impact " + "%d active connection(s)\n", + zfcp_get_busid_by_adapter(adapter), + header->fsf_status_qual.word[0]); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EIO; + break; + + case FSF_CONFLICTS_OVERRULED: + ZFCP_LOG_FLAGS(2, "FSF_CONFLICTS_OVERRULED\n"); + if (bottom->operation_subtype == FSF_CFDC_OPERATION_SUBTYPE) + ZFCP_LOG_NORMAL( + "CFDC has been activated on the adapter %s, " + "but activation has impacted " + "%d active connection(s)\n", + zfcp_get_busid_by_adapter(adapter), + header->fsf_status_qual.word[0]); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EIO; + break; + + case FSF_UNKNOWN_OP_SUBTYPE: + ZFCP_LOG_FLAGS(2, "FSF_UNKNOWN_OP_SUBTYPE\n"); + ZFCP_LOG_NORMAL("unknown operation subtype (adapter: %s, " + "op_subtype=0x%x)\n", + zfcp_get_busid_by_adapter(adapter), + bottom->operation_subtype); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EINVAL; + break; + + case FSF_INVALID_COMMAND_OPTION: + ZFCP_LOG_FLAGS(2, "FSF_INVALID_COMMAND_OPTION\n"); + ZFCP_LOG_NORMAL( + "Invalid option 0x%x has been specified " + "in QTCB bottom sent to the adapter %s\n", + bottom->option, + zfcp_get_busid_by_adapter(adapter)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EINVAL; + break; + + default: + ZFCP_LOG_NORMAL( + "bug: An unknown/unexpected FSF status 0x%08x " + "was presented on the adapter %s\n", + header->fsf_status, + zfcp_get_busid_by_adapter(adapter)); + debug_text_event(fsf_req->adapter->erp_dbf, 0, "fsf_sq_inval"); + debug_exception(fsf_req->adapter->erp_dbf, 0, + &header->fsf_status_qual.word[0], sizeof(u32)); + fsf_req->status |= ZFCP_STATUS_FSFREQ_ERROR; + retval = -EINVAL; + break; + } + +skip_fsfstatus: + return retval; +} + + +/* + * function: zfcp_fsf_req_wait_and_cleanup + * + * purpose: + * + * FIXME(design): signal seems to be <0 !!! + * returns: 0 - request completed (*status is valid), cleanup succ. + * <0 - request completed (*status is valid), cleanup failed + * >0 - signal which interrupted waiting (*status invalid), + * request not completed, no cleanup + * + * *status is a copy of status of completed fsf_req + */ +int +zfcp_fsf_req_wait_and_cleanup(struct zfcp_fsf_req *fsf_req, + int interruptible, u32 * status) +{ + int retval = 0; + int signal = 0; + + if (interruptible) { + __wait_event_interruptible(fsf_req->completion_wq, + fsf_req->status & + ZFCP_STATUS_FSFREQ_COMPLETED, + signal); + if (signal) { + ZFCP_LOG_DEBUG("Caught signal %i while waiting for the " + "completion of the request at %p\n", + signal, fsf_req); + retval = signal; + goto out; + } + } else { + __wait_event(fsf_req->completion_wq, + fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED); + } + + *status = fsf_req->status; + + /* cleanup request */ + zfcp_fsf_req_cleanup(fsf_req); + out: + return retval; +} + +static inline int +zfcp_fsf_req_sbal_check(unsigned long *flags, + struct zfcp_qdio_queue *queue, int needed) +{ + write_lock_irqsave(&queue->queue_lock, *flags); + if (likely(atomic_read(&queue->free_count) >= needed)) + return 1; + write_unlock_irqrestore(&queue->queue_lock, *flags); + return 0; +} + +/* + * set qtcb pointer in fsf_req and initialize QTCB + */ +static inline void +zfcp_fsf_req_qtcb_init(struct zfcp_fsf_req *fsf_req, u32 fsf_cmd) +{ + if (likely(fsf_req->qtcb != NULL)) { + fsf_req->qtcb->prefix.req_id = (unsigned long)fsf_req; + fsf_req->qtcb->prefix.ulp_info = ZFCP_ULP_INFO_VERSION; + fsf_req->qtcb->prefix.qtcb_type = fsf_qtcb_type[fsf_cmd]; + fsf_req->qtcb->prefix.qtcb_version = ZFCP_QTCB_VERSION; + fsf_req->qtcb->header.req_handle = (unsigned long)fsf_req; + fsf_req->qtcb->header.fsf_command = fsf_cmd; + } +} + +/** + * zfcp_fsf_req_sbal_get - try to get one SBAL in the request queue + * @adapter: adapter for which request queue is examined + * @req_flags: flags indicating whether to wait for needed SBAL or not + * @lock_flags: lock_flags if queue_lock is taken + * Return: 0 on success, otherwise -EIO, or -ERESTARTSYS + * Locks: lock adapter->request_queue->queue_lock on success + */ +static int +zfcp_fsf_req_sbal_get(struct zfcp_adapter *adapter, int req_flags, + unsigned long *lock_flags) +{ + long ret; + struct zfcp_qdio_queue *req_queue = &adapter->request_queue; + + if (unlikely(req_flags & ZFCP_WAIT_FOR_SBAL)) { + ret = wait_event_interruptible_timeout(adapter->request_wq, + zfcp_fsf_req_sbal_check(lock_flags, req_queue, 1), + ZFCP_SBAL_TIMEOUT); + if (ret < 0) + return ret; + if (!ret) + return -EIO; + } else if (!zfcp_fsf_req_sbal_check(lock_flags, req_queue, 1)) + return -EIO; + + return 0; +} + +/* + * function: zfcp_fsf_req_create + * + * purpose: create an FSF request at the specified adapter and + * setup common fields + * + * returns: -ENOMEM if there was insufficient memory for a request + * -EIO if no qdio buffers could be allocate to the request + * -EINVAL/-EPERM on bug conditions in req_dequeue + * 0 in success + * + * note: The created request is returned by reference. + * + * locks: lock of concerned request queue must not be held, + * but is held on completion (write, irqsave) + */ +int +zfcp_fsf_req_create(struct zfcp_adapter *adapter, u32 fsf_cmd, int req_flags, + mempool_t *pool, unsigned long *lock_flags, + struct zfcp_fsf_req **fsf_req_p) +{ + volatile struct qdio_buffer_element *sbale; + struct zfcp_fsf_req *fsf_req = NULL; + int ret = 0; + struct zfcp_qdio_queue *req_queue = &adapter->request_queue; + + /* allocate new FSF request */ + fsf_req = zfcp_fsf_req_alloc(pool, req_flags); + if (unlikely(NULL == fsf_req)) { + ZFCP_LOG_DEBUG("error: Could not put an FSF request into" + "the outbound (send) queue.\n"); + ret = -ENOMEM; + goto failed_fsf_req; + } + + zfcp_fsf_req_qtcb_init(fsf_req, fsf_cmd); + + /* initialize waitqueue which may be used to wait on + this request completion */ + init_waitqueue_head(&fsf_req->completion_wq); + + ret = zfcp_fsf_req_sbal_get(adapter, req_flags, lock_flags); + if(ret < 0) { + goto failed_sbals; + } + + /* + * We hold queue_lock here. Check if QDIOUP is set and let request fail + * if it is not set (see also *_open_qdio and *_close_qdio). + */ + + if (!atomic_test_mask(ZFCP_STATUS_ADAPTER_QDIOUP, &adapter->status)) { + write_unlock_irqrestore(&req_queue->queue_lock, *lock_flags); + ret = -EIO; + goto failed_sbals; + } + + fsf_req->adapter = adapter; /* pointer to "parent" adapter */ + fsf_req->fsf_command = fsf_cmd; + fsf_req->sbal_number = 1; + fsf_req->sbal_first = req_queue->free_index; + fsf_req->sbal_curr = req_queue->free_index; + fsf_req->sbale_curr = 1; + + if (likely(req_flags & ZFCP_REQ_AUTO_CLEANUP)) { + fsf_req->status |= ZFCP_STATUS_FSFREQ_CLEANUP; + } + + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + + /* setup common SBALE fields */ + sbale[0].addr = fsf_req; + sbale[0].flags |= SBAL_FLAGS0_COMMAND; + if (likely(fsf_req->qtcb != NULL)) { + sbale[1].addr = (void *) fsf_req->qtcb; + sbale[1].length = sizeof(struct fsf_qtcb); + } + + ZFCP_LOG_TRACE("got %i free BUFFERs starting at index %i\n", + fsf_req->sbal_number, fsf_req->sbal_first); + + goto success; + + failed_sbals: +/* dequeue new FSF request previously enqueued */ + zfcp_fsf_req_free(fsf_req); + fsf_req = NULL; + + failed_fsf_req: + write_lock_irqsave(&req_queue->queue_lock, *lock_flags); + success: + *fsf_req_p = fsf_req; + return ret; +} + +/* + * function: zfcp_fsf_req_send + * + * purpose: start transfer of FSF request via QDIO + * + * returns: 0 - request transfer succesfully started + * !0 - start of request transfer failed + */ +static int +zfcp_fsf_req_send(struct zfcp_fsf_req *fsf_req, struct timer_list *timer) +{ + struct zfcp_adapter *adapter; + struct zfcp_qdio_queue *req_queue; + volatile struct qdio_buffer_element *sbale; + int new_distance_from_int; + unsigned long flags; + int inc_seq_no = 1; + int retval = 0; + + adapter = fsf_req->adapter; + req_queue = &adapter->request_queue, + + + /* FIXME(debug): remove it later */ + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_first, 0); + ZFCP_LOG_DEBUG("SBALE0 flags=0x%x\n", sbale[0].flags); + ZFCP_LOG_TRACE("HEX DUMP OF SBALE1 PAYLOAD:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, (char *) sbale[1].addr, + sbale[1].length); + + /* set sequence counter in QTCB */ + if (likely(fsf_req->qtcb)) { + fsf_req->qtcb->prefix.req_seq_no = adapter->fsf_req_seq_no; + fsf_req->seq_no = adapter->fsf_req_seq_no; + ZFCP_LOG_TRACE("FSF request %p of adapter %s gets " + "FSF sequence counter value of %i\n", + fsf_req, + zfcp_get_busid_by_adapter(adapter), + fsf_req->qtcb->prefix.req_seq_no); + } else + inc_seq_no = 0; + + /* put allocated FSF request at list tail */ + write_lock_irqsave(&adapter->fsf_req_list_lock, flags); + list_add_tail(&fsf_req->list, &adapter->fsf_req_list_head); + write_unlock_irqrestore(&adapter->fsf_req_list_lock, flags); + + /* figure out expiration time of timeout and start timeout */ + if (unlikely(timer)) { + timer->expires += jiffies; + add_timer(timer); + } + + ZFCP_LOG_TRACE("request queue of adapter %s: " + "next free SBAL is %i, %i free SBALs\n", + zfcp_get_busid_by_adapter(adapter), + req_queue->free_index, + atomic_read(&req_queue->free_count)); + + ZFCP_LOG_DEBUG("calling do_QDIO adapter %s, flags=0x%x, queue_no=%i, " + "index_in_queue=%i, count=%i, buffers=%p\n", + zfcp_get_busid_by_adapter(adapter), + QDIO_FLAG_SYNC_OUTPUT, + 0, fsf_req->sbal_first, fsf_req->sbal_number, + &req_queue->buffer[fsf_req->sbal_first]); + + /* + * adjust the number of free SBALs in request queue as well as + * position of first one + */ + atomic_sub(fsf_req->sbal_number, &req_queue->free_count); + ZFCP_LOG_TRACE("free_count=%d\n", atomic_read(&req_queue->free_count)); + req_queue->free_index += fsf_req->sbal_number; /* increase */ + req_queue->free_index %= QDIO_MAX_BUFFERS_PER_Q; /* wrap if needed */ + new_distance_from_int = zfcp_qdio_determine_pci(req_queue, fsf_req); + + retval = do_QDIO(adapter->ccw_device, + QDIO_FLAG_SYNC_OUTPUT, + 0, fsf_req->sbal_first, fsf_req->sbal_number, NULL); + + if (unlikely(retval)) { + /* Queues are down..... */ + retval = -EIO; + /* + * FIXME(potential race): + * timer might be expired (absolutely unlikely) + */ + if (timer) + del_timer(timer); + write_lock_irqsave(&adapter->fsf_req_list_lock, flags); + list_del(&fsf_req->list); + write_unlock_irqrestore(&adapter->fsf_req_list_lock, flags); + /* + * adjust the number of free SBALs in request queue as well as + * position of first one + */ + zfcp_qdio_zero_sbals(req_queue->buffer, + fsf_req->sbal_first, fsf_req->sbal_number); + atomic_add(fsf_req->sbal_number, &req_queue->free_count); + req_queue->free_index -= fsf_req->sbal_number; /* increase */ + req_queue->free_index += QDIO_MAX_BUFFERS_PER_Q; + req_queue->free_index %= QDIO_MAX_BUFFERS_PER_Q; /* wrap */ + ZFCP_LOG_DEBUG + ("error: do_QDIO failed. Buffers could not be enqueued " + "to request queue.\n"); + } else { + req_queue->distance_from_int = new_distance_from_int; + /* + * increase FSF sequence counter - + * this must only be done for request successfully enqueued to + * QDIO this rejected requests may be cleaned up by calling + * routines resulting in missing sequence counter values + * otherwise, + */ + /* Don't increase for unsolicited status */ + if (likely(inc_seq_no)) { + adapter->fsf_req_seq_no++; + ZFCP_LOG_TRACE + ("FSF sequence counter value of adapter %s " + "increased to %i\n", + zfcp_get_busid_by_adapter(adapter), + adapter->fsf_req_seq_no); + } + /* count FSF requests pending */ + atomic_inc(&adapter->fsf_reqs_active); + } + return retval; +} + +/* + * function: zfcp_fsf_req_cleanup + * + * purpose: cleans up an FSF request and removes it from the specified list + * + * returns: + * + * assumption: no pending SB in SBALEs other than QTCB + */ +void +zfcp_fsf_req_cleanup(struct zfcp_fsf_req *fsf_req) +{ + struct zfcp_adapter *adapter = fsf_req->adapter; + unsigned long flags; + + write_lock_irqsave(&adapter->fsf_req_list_lock, flags); + list_del(&fsf_req->list); + write_unlock_irqrestore(&adapter->fsf_req_list_lock, flags); + zfcp_fsf_req_free(fsf_req); +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_fsf.h b/drivers/s390/scsi/zfcp_fsf.h new file mode 100644 index 000000000000..5889956bbf08 --- /dev/null +++ b/drivers/s390/scsi/zfcp_fsf.h @@ -0,0 +1,472 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_fsf.h + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.com> + * Volker Sameske <sameske@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef FSF_H +#define FSF_H + +#define FSF_QTCB_VERSION1 0x00000001 +#define FSF_QTCB_CURRENT_VERSION FSF_QTCB_VERSION1 + +/* FSF commands */ +#define FSF_QTCB_FCP_CMND 0x00000001 +#define FSF_QTCB_ABORT_FCP_CMND 0x00000002 +#define FSF_QTCB_OPEN_PORT_WITH_DID 0x00000005 +#define FSF_QTCB_OPEN_LUN 0x00000006 +#define FSF_QTCB_CLOSE_LUN 0x00000007 +#define FSF_QTCB_CLOSE_PORT 0x00000008 +#define FSF_QTCB_CLOSE_PHYSICAL_PORT 0x00000009 +#define FSF_QTCB_SEND_ELS 0x0000000B +#define FSF_QTCB_SEND_GENERIC 0x0000000C +#define FSF_QTCB_EXCHANGE_CONFIG_DATA 0x0000000D +#define FSF_QTCB_EXCHANGE_PORT_DATA 0x0000000E +#define FSF_QTCB_DOWNLOAD_CONTROL_FILE 0x00000012 +#define FSF_QTCB_UPLOAD_CONTROL_FILE 0x00000013 + +/* FSF QTCB types */ +#define FSF_IO_COMMAND 0x00000001 +#define FSF_SUPPORT_COMMAND 0x00000002 +#define FSF_CONFIG_COMMAND 0x00000003 +#define FSF_PORT_COMMAND 0x00000004 + +/* FSF control file upload/download operations' subtype and options */ +#define FSF_CFDC_OPERATION_SUBTYPE 0x00020001 +#define FSF_CFDC_OPTION_NORMAL_MODE 0x00000000 +#define FSF_CFDC_OPTION_FORCE 0x00000001 +#define FSF_CFDC_OPTION_FULL_ACCESS 0x00000002 +#define FSF_CFDC_OPTION_RESTRICTED_ACCESS 0x00000004 + +/* FSF protocol stati */ +#define FSF_PROT_GOOD 0x00000001 +#define FSF_PROT_QTCB_VERSION_ERROR 0x00000010 +#define FSF_PROT_SEQ_NUMB_ERROR 0x00000020 +#define FSF_PROT_UNSUPP_QTCB_TYPE 0x00000040 +#define FSF_PROT_HOST_CONNECTION_INITIALIZING 0x00000080 +#define FSF_PROT_FSF_STATUS_PRESENTED 0x00000100 +#define FSF_PROT_DUPLICATE_REQUEST_ID 0x00000200 +#define FSF_PROT_LINK_DOWN 0x00000400 +#define FSF_PROT_REEST_QUEUE 0x00000800 +#define FSF_PROT_ERROR_STATE 0x01000000 + +/* FSF stati */ +#define FSF_GOOD 0x00000000 +#define FSF_PORT_ALREADY_OPEN 0x00000001 +#define FSF_LUN_ALREADY_OPEN 0x00000002 +#define FSF_PORT_HANDLE_NOT_VALID 0x00000003 +#define FSF_LUN_HANDLE_NOT_VALID 0x00000004 +#define FSF_HANDLE_MISMATCH 0x00000005 +#define FSF_SERVICE_CLASS_NOT_SUPPORTED 0x00000006 +#define FSF_FCPLUN_NOT_VALID 0x00000009 +#define FSF_ACCESS_DENIED 0x00000010 +#define FSF_LUN_SHARING_VIOLATION 0x00000012 +#define FSF_FCP_COMMAND_DOES_NOT_EXIST 0x00000022 +#define FSF_DIRECTION_INDICATOR_NOT_VALID 0x00000030 +#define FSF_CMND_LENGTH_NOT_VALID 0x00000033 +#define FSF_MAXIMUM_NUMBER_OF_PORTS_EXCEEDED 0x00000040 +#define FSF_MAXIMUM_NUMBER_OF_LUNS_EXCEEDED 0x00000041 +#define FSF_ELS_COMMAND_REJECTED 0x00000050 +#define FSF_GENERIC_COMMAND_REJECTED 0x00000051 +#define FSF_OPERATION_PARTIALLY_SUCCESSFUL 0x00000052 +#define FSF_AUTHORIZATION_FAILURE 0x00000053 +#define FSF_CFDC_ERROR_DETECTED 0x00000054 +#define FSF_CONTROL_FILE_UPDATE_ERROR 0x00000055 +#define FSF_CONTROL_FILE_TOO_LARGE 0x00000056 +#define FSF_ACCESS_CONFLICT_DETECTED 0x00000057 +#define FSF_CONFLICTS_OVERRULED 0x00000058 +#define FSF_PORT_BOXED 0x00000059 +#define FSF_LUN_BOXED 0x0000005A +#define FSF_EXCHANGE_CONFIG_DATA_INCOMPLETE 0x0000005B +#define FSF_PAYLOAD_SIZE_MISMATCH 0x00000060 +#define FSF_REQUEST_SIZE_TOO_LARGE 0x00000061 +#define FSF_RESPONSE_SIZE_TOO_LARGE 0x00000062 +#define FSF_SBAL_MISMATCH 0x00000063 +#define FSF_OPEN_PORT_WITHOUT_PRLI 0x00000064 +#define FSF_ADAPTER_STATUS_AVAILABLE 0x000000AD +#define FSF_FCP_RSP_AVAILABLE 0x000000AF +#define FSF_UNKNOWN_COMMAND 0x000000E2 +#define FSF_UNKNOWN_OP_SUBTYPE 0x000000E3 +#define FSF_INVALID_COMMAND_OPTION 0x000000E5 +/* #define FSF_ERROR 0x000000FF */ + +#define FSF_STATUS_QUALIFIER_SIZE 16 + +/* FSF status qualifier, recommendations */ +#define FSF_SQ_NO_RECOM 0x00 +#define FSF_SQ_FCP_RSP_AVAILABLE 0x01 +#define FSF_SQ_RETRY_IF_POSSIBLE 0x02 +#define FSF_SQ_ULP_DEPENDENT_ERP_REQUIRED 0x03 +#define FSF_SQ_INVOKE_LINK_TEST_PROCEDURE 0x04 +#define FSF_SQ_ULP_PROGRAMMING_ERROR 0x05 +#define FSF_SQ_COMMAND_ABORTED 0x06 +#define FSF_SQ_NO_RETRY_POSSIBLE 0x07 + +/* FSF status qualifier for CFDC commands */ +#define FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE 0x00000001 +#define FSF_SQ_CFDC_COULD_NOT_HARDEN_ON_SE2 0x00000002 +/* CFDC subtable codes */ +#define FSF_SQ_CFDC_SUBTABLE_OS 0x0001 +#define FSF_SQ_CFDC_SUBTABLE_PORT_WWPN 0x0002 +#define FSF_SQ_CFDC_SUBTABLE_PORT_DID 0x0003 +#define FSF_SQ_CFDC_SUBTABLE_LUN 0x0004 + +/* FSF status qualifier (most significant 4 bytes), local link down */ +#define FSF_PSQ_LINK_NOLIGHT 0x00000004 +#define FSF_PSQ_LINK_WRAPPLUG 0x00000008 +#define FSF_PSQ_LINK_NOFCP 0x00000010 + +/* payload size in status read buffer */ +#define FSF_STATUS_READ_PAYLOAD_SIZE 4032 + +/* number of status read buffers that should be sent by ULP */ +#define FSF_STATUS_READS_RECOM 16 + +/* status types in status read buffer */ +#define FSF_STATUS_READ_PORT_CLOSED 0x00000001 +#define FSF_STATUS_READ_INCOMING_ELS 0x00000002 +#define FSF_STATUS_READ_SENSE_DATA_AVAIL 0x00000003 +#define FSF_STATUS_READ_BIT_ERROR_THRESHOLD 0x00000004 +#define FSF_STATUS_READ_LINK_DOWN 0x00000005 /* FIXME: really? */ +#define FSF_STATUS_READ_LINK_UP 0x00000006 +#define FSF_STATUS_READ_CFDC_UPDATED 0x0000000A +#define FSF_STATUS_READ_CFDC_HARDENED 0x0000000B + +/* status subtypes in status read buffer */ +#define FSF_STATUS_READ_SUB_CLOSE_PHYS_PORT 0x00000001 +#define FSF_STATUS_READ_SUB_ERROR_PORT 0x00000002 + +/* status subtypes for CFDC */ +#define FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE 0x00000002 +#define FSF_STATUS_READ_SUB_CFDC_HARDENED_ON_SE2 0x0000000F + +/* topologie that is detected by the adapter */ +#define FSF_TOPO_ERROR 0x00000000 +#define FSF_TOPO_P2P 0x00000001 +#define FSF_TOPO_FABRIC 0x00000002 +#define FSF_TOPO_AL 0x00000003 +#define FSF_TOPO_FABRIC_VIRT 0x00000004 + +/* data direction for FCP commands */ +#define FSF_DATADIR_WRITE 0x00000001 +#define FSF_DATADIR_READ 0x00000002 +#define FSF_DATADIR_READ_WRITE 0x00000003 +#define FSF_DATADIR_CMND 0x00000004 + +/* fc service class */ +#define FSF_CLASS_1 0x00000001 +#define FSF_CLASS_2 0x00000002 +#define FSF_CLASS_3 0x00000003 + +/* SBAL chaining */ +#define FSF_MAX_SBALS_PER_REQ 36 +#define FSF_MAX_SBALS_PER_ELS_REQ 2 + +/* logging space behind QTCB */ +#define FSF_QTCB_LOG_SIZE 1024 + +/* channel features */ +#define FSF_FEATURE_QTCB_SUPPRESSION 0x00000001 +#define FSF_FEATURE_CFDC 0x00000002 +#define FSF_FEATURE_LUN_SHARING 0x00000004 +#define FSF_FEATURE_HBAAPI_MANAGEMENT 0x00000010 +#define FSF_FEATURE_ELS_CT_CHAINED_SBALS 0x00000020 + +/* option */ +#define FSF_OPEN_LUN_SUPPRESS_BOXING 0x00000001 +#define FSF_OPEN_LUN_REPLICATE_SENSE 0x00000002 + +/* adapter types */ +#define FSF_ADAPTER_TYPE_FICON 0x00000001 +#define FSF_ADAPTER_TYPE_FICON_EXPRESS 0x00000002 + +/* port types */ +#define FSF_HBA_PORTTYPE_UNKNOWN 0x00000001 +#define FSF_HBA_PORTTYPE_NOTPRESENT 0x00000003 +#define FSF_HBA_PORTTYPE_NPORT 0x00000005 +#define FSF_HBA_PORTTYPE_PTP 0x00000021 +/* following are not defined and used by FSF Spec + but are additionally defined by FC-HBA */ +#define FSF_HBA_PORTTYPE_OTHER 0x00000002 +#define FSF_HBA_PORTTYPE_NOTPRESENT 0x00000003 +#define FSF_HBA_PORTTYPE_NLPORT 0x00000006 +#define FSF_HBA_PORTTYPE_FLPORT 0x00000007 +#define FSF_HBA_PORTTYPE_FPORT 0x00000008 +#define FSF_HBA_PORTTYPE_LPORT 0x00000020 + +/* port states */ +#define FSF_HBA_PORTSTATE_UNKNOWN 0x00000001 +#define FSF_HBA_PORTSTATE_ONLINE 0x00000002 +#define FSF_HBA_PORTSTATE_OFFLINE 0x00000003 +#define FSF_HBA_PORTSTATE_LINKDOWN 0x00000006 +#define FSF_HBA_PORTSTATE_ERROR 0x00000007 + +/* IO states of adapter */ +#define FSF_IOSTAT_NPORT_RJT 0x00000004 +#define FSF_IOSTAT_FABRIC_RJT 0x00000005 +#define FSF_IOSTAT_LS_RJT 0x00000009 + +/* open LUN access flags*/ +#define FSF_UNIT_ACCESS_OPEN_LUN_ALLOWED 0x01000000 +#define FSF_UNIT_ACCESS_EXCLUSIVE 0x02000000 +#define FSF_UNIT_ACCESS_OUTBOUND_TRANSFER 0x10000000 + +struct fsf_queue_designator; +struct fsf_status_read_buffer; +struct fsf_port_closed_payload; +struct fsf_bit_error_payload; +union fsf_prot_status_qual; +struct fsf_qual_version_error; +struct fsf_qual_sequence_error; +struct fsf_qtcb_prefix; +struct fsf_qtcb_header; +struct fsf_qtcb_bottom_config; +struct fsf_qtcb_bottom_support; +struct fsf_qtcb_bottom_io; +union fsf_qtcb_bottom; + +struct fsf_queue_designator { + u8 cssid; + u8 chpid; + u8 hla; + u8 ua; + u32 res1; +} __attribute__ ((packed)); + +struct fsf_port_closed_payload { + struct fsf_queue_designator queue_designator; + u32 port_handle; +} __attribute__ ((packed)); + +struct fsf_bit_error_payload { + u32 res1; + u32 link_failure_error_count; + u32 loss_of_sync_error_count; + u32 loss_of_signal_error_count; + u32 primitive_sequence_error_count; + u32 invalid_transmission_word_error_count; + u32 crc_error_count; + u32 primitive_sequence_event_timeout_count; + u32 elastic_buffer_overrun_error_count; + u32 fcal_arbitration_timeout_count; + u32 advertised_receive_b2b_credit; + u32 current_receive_b2b_credit; + u32 advertised_transmit_b2b_credit; + u32 current_transmit_b2b_credit; +} __attribute__ ((packed)); + +struct fsf_status_read_buffer { + u32 status_type; + u32 status_subtype; + u32 length; + u32 res1; + struct fsf_queue_designator queue_designator; + u32 d_id; + u32 class; + u64 fcp_lun; + u8 res3[24]; + u8 payload[FSF_STATUS_READ_PAYLOAD_SIZE]; +} __attribute__ ((packed)); + +struct fsf_qual_version_error { + u32 fsf_version; + u32 res1[3]; +} __attribute__ ((packed)); + +struct fsf_qual_sequence_error { + u32 exp_req_seq_no; + u32 res1[3]; +} __attribute__ ((packed)); + +struct fsf_qual_locallink_error { + u32 code; + u32 res1[3]; +} __attribute__ ((packed)); + +union fsf_prot_status_qual { + struct fsf_qual_version_error version_error; + struct fsf_qual_sequence_error sequence_error; + struct fsf_qual_locallink_error locallink_error; +} __attribute__ ((packed)); + +struct fsf_qtcb_prefix { + u64 req_id; + u32 qtcb_version; + u32 ulp_info; + u32 qtcb_type; + u32 req_seq_no; + u32 prot_status; + union fsf_prot_status_qual prot_status_qual; + u8 res1[20]; +} __attribute__ ((packed)); + +union fsf_status_qual { + u8 byte[FSF_STATUS_QUALIFIER_SIZE]; + u16 halfword[FSF_STATUS_QUALIFIER_SIZE / sizeof (u16)]; + u32 word[FSF_STATUS_QUALIFIER_SIZE / sizeof (u32)]; + struct fsf_queue_designator fsf_queue_designator; +} __attribute__ ((packed)); + +struct fsf_qtcb_header { + u64 req_handle; + u32 fsf_command; + u32 res1; + u32 port_handle; + u32 lun_handle; + u32 res2; + u32 fsf_status; + union fsf_status_qual fsf_status_qual; + u8 res3[28]; + u16 log_start; + u16 log_length; + u8 res4[16]; +} __attribute__ ((packed)); + +struct fsf_nport_serv_param { + u8 common_serv_param[16]; + u64 wwpn; + u64 wwnn; + u8 class1_serv_param[16]; + u8 class2_serv_param[16]; + u8 class3_serv_param[16]; + u8 class4_serv_param[16]; + u8 vendor_version_level[16]; + u8 res1[16]; +} __attribute__ ((packed)); + +struct fsf_plogi { + u32 code; + struct fsf_nport_serv_param serv_param; +} __attribute__ ((packed)); + +#define FSF_FCP_CMND_SIZE 288 +#define FSF_FCP_RSP_SIZE 128 + +struct fsf_qtcb_bottom_io { + u32 data_direction; + u32 service_class; + u8 res1[8]; + u32 fcp_cmnd_length; + u8 res2[12]; + u8 fcp_cmnd[FSF_FCP_CMND_SIZE]; + u8 fcp_rsp[FSF_FCP_RSP_SIZE]; + u8 res3[64]; +} __attribute__ ((packed)); + +struct fsf_qtcb_bottom_support { + u32 operation_subtype; + u8 res1[12]; + u32 d_id; + u32 option; + u64 fcp_lun; + u64 res2; + u64 req_handle; + u32 service_class; + u8 res3[3]; + u8 timeout; + u32 lun_access_info; + u8 res4[180]; + u32 els1_length; + u32 els2_length; + u32 req_buf_length; + u32 resp_buf_length; + u8 els[256]; +} __attribute__ ((packed)); + +struct fsf_qtcb_bottom_config { + u32 lic_version; + u32 feature_selection; + u32 high_qtcb_version; + u32 low_qtcb_version; + u32 max_qtcb_size; + u32 max_data_transfer_size; + u32 supported_features; + u8 res1[4]; + u32 fc_topology; + u32 fc_link_speed; + u32 adapter_type; + u32 peer_d_id; + u8 res2[12]; + u32 s_id; + struct fsf_nport_serv_param nport_serv_param; + u8 res3[8]; + u32 adapter_ports; + u32 hardware_version; + u8 serial_number[32]; + u8 res4[272]; +} __attribute__ ((packed)); + +struct fsf_qtcb_bottom_port { + u8 res1[8]; + u32 fc_port_id; + u32 port_type; + u32 port_state; + u32 class_of_service; /* should be 0x00000006 for class 2 and 3 */ + u8 supported_fc4_types[32]; /* should be 0x00000100 for scsi fcp */ + u8 active_fc4_types[32]; + u32 supported_speed; /* 0x0001 for 1 GBit/s or 0x0002 for 2 GBit/s */ + u32 maximum_frame_size; /* fixed value of 2112 */ + u64 seconds_since_last_reset; + u64 tx_frames; + u64 tx_words; + u64 rx_frames; + u64 rx_words; + u64 lip; /* 0 */ + u64 nos; /* currently 0 */ + u64 error_frames; /* currently 0 */ + u64 dumped_frames; /* currently 0 */ + u64 link_failure; + u64 loss_of_sync; + u64 loss_of_signal; + u64 psp_error_counts; + u64 invalid_tx_words; + u64 invalid_crcs; + u64 input_requests; + u64 output_requests; + u64 control_requests; + u64 input_mb; /* where 1 MByte == 1.000.000 Bytes */ + u64 output_mb; /* where 1 MByte == 1.000.000 Bytes */ + u8 res2[256]; +} __attribute__ ((packed)); + +union fsf_qtcb_bottom { + struct fsf_qtcb_bottom_io io; + struct fsf_qtcb_bottom_support support; + struct fsf_qtcb_bottom_config config; + struct fsf_qtcb_bottom_port port; +}; + +struct fsf_qtcb { + struct fsf_qtcb_prefix prefix; + struct fsf_qtcb_header header; + union fsf_qtcb_bottom bottom; + u8 log[FSF_QTCB_LOG_SIZE]; +} __attribute__ ((packed)); + +#endif /* FSF_H */ diff --git a/drivers/s390/scsi/zfcp_qdio.c b/drivers/s390/scsi/zfcp_qdio.c new file mode 100644 index 000000000000..06e862d7bc90 --- /dev/null +++ b/drivers/s390/scsi/zfcp_qdio.c @@ -0,0 +1,868 @@ +/* + * linux/drivers/s390/scsi/zfcp_qdio.c + * + * FCP adapter driver for IBM eServer zSeries + * + * QDIO related routines + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Authors: + * Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Wolfgang Taphorn + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_QDIO_C_REVISION "$Revision: 1.20 $" + +#include "zfcp_ext.h" + +static inline void zfcp_qdio_sbal_limit(struct zfcp_fsf_req *, int); +static inline volatile struct qdio_buffer_element *zfcp_qdio_sbale_get + (struct zfcp_qdio_queue *, int, int); +static inline volatile struct qdio_buffer_element *zfcp_qdio_sbale_resp + (struct zfcp_fsf_req *, int, int); +static inline volatile struct qdio_buffer_element *zfcp_qdio_sbal_chain + (struct zfcp_fsf_req *, unsigned long); +static inline volatile struct qdio_buffer_element *zfcp_qdio_sbale_next + (struct zfcp_fsf_req *, unsigned long); +static inline int zfcp_qdio_sbals_zero(struct zfcp_qdio_queue *, int, int); +static inline int zfcp_qdio_sbals_wipe(struct zfcp_fsf_req *); +static inline void zfcp_qdio_sbale_fill + (struct zfcp_fsf_req *, unsigned long, void *, int); +static inline int zfcp_qdio_sbals_from_segment + (struct zfcp_fsf_req *, unsigned long, void *, unsigned long); +static inline int zfcp_qdio_sbals_from_buffer + (struct zfcp_fsf_req *, unsigned long, void *, unsigned long, int); + +static qdio_handler_t zfcp_qdio_request_handler; +static qdio_handler_t zfcp_qdio_response_handler; +static int zfcp_qdio_handler_error_check(struct zfcp_adapter *, + unsigned int, + unsigned int, unsigned int); + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_QDIO + +/* + * Allocates BUFFER memory to each of the pointers of the qdio_buffer_t + * array in the adapter struct. + * Cur_buf is the pointer array and count can be any number of required + * buffers, the page-fitting arithmetic is done entirely within this funciton. + * + * returns: number of buffers allocated + * locks: must only be called with zfcp_data.config_sema taken + */ +static int +zfcp_qdio_buffers_enqueue(struct qdio_buffer **cur_buf, int count) +{ + int buf_pos; + int qdio_buffers_per_page; + int page_pos = 0; + struct qdio_buffer *first_in_page = NULL; + + qdio_buffers_per_page = PAGE_SIZE / sizeof (struct qdio_buffer); + ZFCP_LOG_TRACE("buffers_per_page=%d\n", qdio_buffers_per_page); + + for (buf_pos = 0; buf_pos < count; buf_pos++) { + if (page_pos == 0) { + cur_buf[buf_pos] = (struct qdio_buffer *) + get_zeroed_page(GFP_KERNEL); + if (cur_buf[buf_pos] == NULL) { + ZFCP_LOG_INFO("error: allocation of " + "QDIO buffer failed \n"); + goto out; + } + first_in_page = cur_buf[buf_pos]; + } else { + cur_buf[buf_pos] = first_in_page + page_pos; + + } + /* was initialised to zero */ + page_pos++; + page_pos %= qdio_buffers_per_page; + } + out: + return buf_pos; +} + +/* + * Frees BUFFER memory for each of the pointers of the struct qdio_buffer array + * in the adapter struct cur_buf is the pointer array and count can be any + * number of buffers in the array that should be freed starting from buffer 0 + * + * locks: must only be called with zfcp_data.config_sema taken + */ +static void +zfcp_qdio_buffers_dequeue(struct qdio_buffer **cur_buf, int count) +{ + int buf_pos; + int qdio_buffers_per_page; + + qdio_buffers_per_page = PAGE_SIZE / sizeof (struct qdio_buffer); + ZFCP_LOG_TRACE("buffers_per_page=%d\n", qdio_buffers_per_page); + + for (buf_pos = 0; buf_pos < count; buf_pos += qdio_buffers_per_page) + free_page((unsigned long) cur_buf[buf_pos]); + return; +} + +/* locks: must only be called with zfcp_data.config_sema taken */ +int +zfcp_qdio_allocate_queues(struct zfcp_adapter *adapter) +{ + int buffer_count; + int retval = 0; + + buffer_count = + zfcp_qdio_buffers_enqueue(&(adapter->request_queue.buffer[0]), + QDIO_MAX_BUFFERS_PER_Q); + if (buffer_count < QDIO_MAX_BUFFERS_PER_Q) { + ZFCP_LOG_DEBUG("only %d QDIO buffers allocated for request " + "queue\n", buffer_count); + zfcp_qdio_buffers_dequeue(&(adapter->request_queue.buffer[0]), + buffer_count); + retval = -ENOMEM; + goto out; + } + + buffer_count = + zfcp_qdio_buffers_enqueue(&(adapter->response_queue.buffer[0]), + QDIO_MAX_BUFFERS_PER_Q); + if (buffer_count < QDIO_MAX_BUFFERS_PER_Q) { + ZFCP_LOG_DEBUG("only %d QDIO buffers allocated for response " + "queue", buffer_count); + zfcp_qdio_buffers_dequeue(&(adapter->response_queue.buffer[0]), + buffer_count); + ZFCP_LOG_TRACE("freeing request_queue buffers\n"); + zfcp_qdio_buffers_dequeue(&(adapter->request_queue.buffer[0]), + QDIO_MAX_BUFFERS_PER_Q); + retval = -ENOMEM; + goto out; + } + out: + return retval; +} + +/* locks: must only be called with zfcp_data.config_sema taken */ +void +zfcp_qdio_free_queues(struct zfcp_adapter *adapter) +{ + ZFCP_LOG_TRACE("freeing request_queue buffers\n"); + zfcp_qdio_buffers_dequeue(&(adapter->request_queue.buffer[0]), + QDIO_MAX_BUFFERS_PER_Q); + + ZFCP_LOG_TRACE("freeing response_queue buffers\n"); + zfcp_qdio_buffers_dequeue(&(adapter->response_queue.buffer[0]), + QDIO_MAX_BUFFERS_PER_Q); +} + +int +zfcp_qdio_allocate(struct zfcp_adapter *adapter) +{ + struct qdio_initialize *init_data; + + init_data = &adapter->qdio_init_data; + + init_data->cdev = adapter->ccw_device; + init_data->q_format = QDIO_SCSI_QFMT; + memcpy(init_data->adapter_name, &adapter->name, 8); + init_data->qib_param_field_format = 0; + init_data->qib_param_field = NULL; + init_data->input_slib_elements = NULL; + init_data->output_slib_elements = NULL; + init_data->min_input_threshold = ZFCP_MIN_INPUT_THRESHOLD; + init_data->max_input_threshold = ZFCP_MAX_INPUT_THRESHOLD; + init_data->min_output_threshold = ZFCP_MIN_OUTPUT_THRESHOLD; + init_data->max_output_threshold = ZFCP_MAX_OUTPUT_THRESHOLD; + init_data->no_input_qs = 1; + init_data->no_output_qs = 1; + init_data->input_handler = zfcp_qdio_response_handler; + init_data->output_handler = zfcp_qdio_request_handler; + init_data->int_parm = (unsigned long) adapter; + init_data->flags = QDIO_INBOUND_0COPY_SBALS | + QDIO_OUTBOUND_0COPY_SBALS | QDIO_USE_OUTBOUND_PCIS; + init_data->input_sbal_addr_array = + (void **) (adapter->response_queue.buffer); + init_data->output_sbal_addr_array = + (void **) (adapter->request_queue.buffer); + + return qdio_allocate(init_data); +} + +/* + * function: zfcp_qdio_handler_error_check + * + * purpose: called by the response handler to determine error condition + * + * returns: error flag + * + */ +static inline int +zfcp_qdio_handler_error_check(struct zfcp_adapter *adapter, + unsigned int status, + unsigned int qdio_error, unsigned int siga_error) +{ + int retval = 0; + + if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_TRACE)) { + if (status & QDIO_STATUS_INBOUND_INT) { + ZFCP_LOG_TRACE("status is" + " QDIO_STATUS_INBOUND_INT \n"); + } + if (status & QDIO_STATUS_OUTBOUND_INT) { + ZFCP_LOG_TRACE("status is" + " QDIO_STATUS_OUTBOUND_INT \n"); + } + } // if (ZFCP_LOG_CHECK(ZFCP_LOG_LEVEL_TRACE)) + if (unlikely(status & QDIO_STATUS_LOOK_FOR_ERROR)) { + retval = -EIO; + + ZFCP_LOG_FLAGS(1, "QDIO_STATUS_LOOK_FOR_ERROR \n"); + + ZFCP_LOG_INFO("QDIO problem occurred (status=0x%x, " + "qdio_error=0x%x, siga_error=0x%x)\n", + status, qdio_error, siga_error); + + if (status & QDIO_STATUS_ACTIVATE_CHECK_CONDITION) { + ZFCP_LOG_FLAGS(2, + "QDIO_STATUS_ACTIVATE_CHECK_CONDITION\n"); + } + if (status & QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR) { + ZFCP_LOG_FLAGS(2, + "QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR\n"); + } + if (status & QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR) { + ZFCP_LOG_FLAGS(2, + "QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR\n"); + } + + if (siga_error & QDIO_SIGA_ERROR_ACCESS_EXCEPTION) { + ZFCP_LOG_FLAGS(2, "QDIO_SIGA_ERROR_ACCESS_EXCEPTION\n"); + } + + if (siga_error & QDIO_SIGA_ERROR_B_BIT_SET) { + ZFCP_LOG_FLAGS(2, "QDIO_SIGA_ERROR_B_BIT_SET\n"); + } + + switch (qdio_error) { + case 0: + ZFCP_LOG_FLAGS(3, "QDIO_OK"); + break; + case SLSB_P_INPUT_ERROR: + ZFCP_LOG_FLAGS(1, "SLSB_P_INPUT_ERROR\n"); + break; + case SLSB_P_OUTPUT_ERROR: + ZFCP_LOG_FLAGS(1, "SLSB_P_OUTPUT_ERROR\n"); + break; + default: + ZFCP_LOG_NORMAL("bug: unknown QDIO error 0x%x\n", + qdio_error); + break; + } + /* Restarting IO on the failed adapter from scratch */ + debug_text_event(adapter->erp_dbf, 1, "qdio_err"); + /* + * Since we have been using this adapter, it is save to assume + * that it is not failed but recoverable. The card seems to + * report link-up events by self-initiated queue shutdown. + * That is why we need to clear the the link-down flag + * which is set again in case we have missed by a mile. + */ + zfcp_erp_adapter_reopen( + adapter, + ZFCP_STATUS_ADAPTER_LINK_UNPLUGGED | + ZFCP_STATUS_COMMON_ERP_FAILED); + } + return retval; +} + +/* + * function: zfcp_qdio_request_handler + * + * purpose: is called by QDIO layer for completed SBALs in request queue + * + * returns: (void) + */ +static void +zfcp_qdio_request_handler(struct ccw_device *ccw_device, + unsigned int status, + unsigned int qdio_error, + unsigned int siga_error, + unsigned int queue_number, + int first_element, + int elements_processed, + unsigned long int_parm) +{ + struct zfcp_adapter *adapter; + struct zfcp_qdio_queue *queue; + + adapter = (struct zfcp_adapter *) int_parm; + queue = &adapter->request_queue; + + ZFCP_LOG_DEBUG("adapter %s, first=%d, elements_processed=%d\n", + zfcp_get_busid_by_adapter(adapter), + first_element, elements_processed); + + if (unlikely(zfcp_qdio_handler_error_check(adapter, status, qdio_error, + siga_error))) + goto out; + /* + * we stored address of struct zfcp_adapter data structure + * associated with irq in int_parm + */ + + /* cleanup all SBALs being program-owned now */ + zfcp_qdio_zero_sbals(queue->buffer, first_element, elements_processed); + + /* increase free space in outbound queue */ + atomic_add(elements_processed, &queue->free_count); + ZFCP_LOG_DEBUG("free_count=%d\n", atomic_read(&queue->free_count)); + wake_up(&adapter->request_wq); + ZFCP_LOG_DEBUG("elements_processed=%d, free count=%d\n", + elements_processed, atomic_read(&queue->free_count)); + out: + return; +} + +/* + * function: zfcp_qdio_response_handler + * + * purpose: is called by QDIO layer for completed SBALs in response queue + * + * returns: (void) + */ +static void +zfcp_qdio_response_handler(struct ccw_device *ccw_device, + unsigned int status, + unsigned int qdio_error, + unsigned int siga_error, + unsigned int queue_number, + int first_element, + int elements_processed, + unsigned long int_parm) +{ + struct zfcp_adapter *adapter; + struct zfcp_qdio_queue *queue; + int buffer_index; + int i; + struct qdio_buffer *buffer; + int retval = 0; + u8 count; + u8 start; + volatile struct qdio_buffer_element *buffere = NULL; + int buffere_index; + + adapter = (struct zfcp_adapter *) int_parm; + queue = &adapter->response_queue; + + if (unlikely(zfcp_qdio_handler_error_check(adapter, status, qdio_error, + siga_error))) + goto out; + + /* + * we stored address of struct zfcp_adapter data structure + * associated with irq in int_parm + */ + + buffere = &(queue->buffer[first_element]->element[0]); + ZFCP_LOG_DEBUG("first BUFFERE flags=0x%x\n", buffere->flags); + /* + * go through all SBALs from input queue currently + * returned by QDIO layer + */ + + for (i = 0; i < elements_processed; i++) { + + buffer_index = first_element + i; + buffer_index %= QDIO_MAX_BUFFERS_PER_Q; + buffer = queue->buffer[buffer_index]; + + /* go through all SBALEs of SBAL */ + for (buffere_index = 0; + buffere_index < QDIO_MAX_ELEMENTS_PER_BUFFER; + buffere_index++) { + + /* look for QDIO request identifiers in SB */ + buffere = &buffer->element[buffere_index]; + retval = zfcp_qdio_reqid_check(adapter, + (void *) buffere->addr); + + if (retval) { + ZFCP_LOG_NORMAL("bug: unexpected inbound " + "packet on adapter %s " + "(reqid=0x%lx, " + "first_element=%d, " + "elements_processed=%d)\n", + zfcp_get_busid_by_adapter(adapter), + (unsigned long) buffere->addr, + first_element, + elements_processed); + ZFCP_LOG_NORMAL("hex dump of inbound buffer " + "at address %p " + "(buffer_index=%d, " + "buffere_index=%d)\n", buffer, + buffer_index, buffere_index); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) buffer, SBAL_SIZE); + } + /* + * A single used SBALE per inbound SBALE has been + * implemented by QDIO so far. Hope they will + * do some optimisation. Will need to change to + * unlikely() then. + */ + if (likely(buffere->flags & SBAL_FLAGS_LAST_ENTRY)) + break; + }; + + if (unlikely(!(buffere->flags & SBAL_FLAGS_LAST_ENTRY))) { + ZFCP_LOG_NORMAL("bug: End of inbound data " + "not marked!\n"); + } + } + + /* + * put range of SBALs back to response queue + * (including SBALs which have already been free before) + */ + count = atomic_read(&queue->free_count) + elements_processed; + start = queue->free_index; + + ZFCP_LOG_TRACE("calling do_QDIO on adapter %s (flags=0x%x, " + "queue_no=%i, index_in_queue=%i, count=%i, " + "buffers=0x%lx\n", + zfcp_get_busid_by_adapter(adapter), + QDIO_FLAG_SYNC_INPUT | QDIO_FLAG_UNDER_INTERRUPT, + 0, start, count, (unsigned long) &queue->buffer[start]); + + retval = do_QDIO(ccw_device, + QDIO_FLAG_SYNC_INPUT | QDIO_FLAG_UNDER_INTERRUPT, + 0, start, count, NULL); + + if (unlikely(retval)) { + atomic_set(&queue->free_count, count); + ZFCP_LOG_DEBUG("clearing of inbound data regions failed, " + "queues may be down " + "(count=%d, start=%d, retval=%d)\n", + count, start, retval); + } else { + queue->free_index += count; + queue->free_index %= QDIO_MAX_BUFFERS_PER_Q; + atomic_set(&queue->free_count, 0); + ZFCP_LOG_TRACE("%i buffers enqueued to response " + "queue at position %i\n", count, start); + } + out: + return; +} + +/* + * function: zfcp_qdio_reqid_check + * + * purpose: checks for valid reqids or unsolicited status + * + * returns: 0 - valid request id or unsolicited status + * !0 - otherwise + */ +int +zfcp_qdio_reqid_check(struct zfcp_adapter *adapter, void *sbale_addr) +{ + struct zfcp_fsf_req *fsf_req; + int retval = 0; + + /* invalid (per convention used in this driver) */ + if (unlikely(!sbale_addr)) { + ZFCP_LOG_NORMAL("bug: invalid reqid\n"); + retval = -EINVAL; + goto out; + } + + /* valid request id and thus (hopefully :) valid fsf_req address */ + fsf_req = (struct zfcp_fsf_req *) sbale_addr; + + if (unlikely(adapter != fsf_req->adapter)) { + ZFCP_LOG_NORMAL("bug: invalid reqid (fsf_req=%p, " + "fsf_req->adapter=%p, adapter=%p)\n", + fsf_req, fsf_req->adapter, adapter); + retval = -EINVAL; + goto out; + } + + ZFCP_LOG_TRACE("fsf_req at %p, QTCB at %p\n", fsf_req, fsf_req->qtcb); + if (likely(fsf_req->qtcb)) { + ZFCP_LOG_TRACE("hex dump of QTCB:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_TRACE, (char *) fsf_req->qtcb, + sizeof(struct fsf_qtcb)); + } + + /* finish the FSF request */ + zfcp_fsf_req_complete(fsf_req); + out: + return retval; +} + +/** + * zfcp_qdio_sbale_get - return pointer to SBALE of qdio_queue + * @queue: queue from which SBALE should be returned + * @sbal: specifies number of SBAL in queue + * @sbale: specifes number of SBALE in SBAL + */ +static inline volatile struct qdio_buffer_element * +zfcp_qdio_sbale_get(struct zfcp_qdio_queue *queue, int sbal, int sbale) +{ + return &queue->buffer[sbal]->element[sbale]; +} + +/** + * zfcp_qdio_sbale_req - return pointer to SBALE of request_queue for + * a struct zfcp_fsf_req + */ +inline volatile struct qdio_buffer_element * +zfcp_qdio_sbale_req(struct zfcp_fsf_req *fsf_req, int sbal, int sbale) +{ + return zfcp_qdio_sbale_get(&fsf_req->adapter->request_queue, + sbal, sbale); +} + +/** + * zfcp_qdio_sbale_resp - return pointer to SBALE of response_queue for + * a struct zfcp_fsf_req + */ +static inline volatile struct qdio_buffer_element * +zfcp_qdio_sbale_resp(struct zfcp_fsf_req *fsf_req, int sbal, int sbale) +{ + return zfcp_qdio_sbale_get(&fsf_req->adapter->response_queue, + sbal, sbale); +} + +/** + * zfcp_qdio_sbale_curr - return current SBALE on request_queue for + * a struct zfcp_fsf_req + */ +inline volatile struct qdio_buffer_element * +zfcp_qdio_sbale_curr(struct zfcp_fsf_req *fsf_req) +{ + return zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, + fsf_req->sbale_curr); +} + +/** + * zfcp_qdio_sbal_limit - determine maximum number of SBALs that can be used + * on the request_queue for a struct zfcp_fsf_req + * @fsf_req: the number of the last SBAL that can be used is stored herein + * @max_sbals: used to pass an upper limit for the number of SBALs + * + * Note: We can assume at least one free SBAL in the request_queue when called. + */ +static inline void +zfcp_qdio_sbal_limit(struct zfcp_fsf_req *fsf_req, int max_sbals) +{ + int count = atomic_read(&fsf_req->adapter->request_queue.free_count); + count = min(count, max_sbals); + fsf_req->sbal_last = fsf_req->sbal_first; + fsf_req->sbal_last += (count - 1); + fsf_req->sbal_last %= QDIO_MAX_BUFFERS_PER_Q; +} + +/** + * zfcp_qdio_sbal_chain - chain SBALs if more than one SBAL is needed for a + * request + * @fsf_req: zfcp_fsf_req to be processed + * @sbtype: SBAL flags which have to be set in first SBALE of new SBAL + * + * This function changes sbal_curr, sbale_curr, sbal_number of fsf_req. + */ +static inline volatile struct qdio_buffer_element * +zfcp_qdio_sbal_chain(struct zfcp_fsf_req *fsf_req, unsigned long sbtype) +{ + volatile struct qdio_buffer_element *sbale; + + /* set last entry flag in current SBALE of current SBAL */ + sbale = zfcp_qdio_sbale_curr(fsf_req); + sbale->flags |= SBAL_FLAGS_LAST_ENTRY; + + /* don't exceed last allowed SBAL */ + if (fsf_req->sbal_curr == fsf_req->sbal_last) + return NULL; + + /* set chaining flag in first SBALE of current SBAL */ + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale->flags |= SBAL_FLAGS0_MORE_SBALS; + + /* calculate index of next SBAL */ + fsf_req->sbal_curr++; + fsf_req->sbal_curr %= QDIO_MAX_BUFFERS_PER_Q; + + /* keep this requests number of SBALs up-to-date */ + fsf_req->sbal_number++; + + /* start at first SBALE of new SBAL */ + fsf_req->sbale_curr = 0; + + /* set storage-block type for new SBAL */ + sbale = zfcp_qdio_sbale_curr(fsf_req); + sbale->flags |= sbtype; + + return sbale; +} + +/** + * zfcp_qdio_sbale_next - switch to next SBALE, chain SBALs if needed + */ +static inline volatile struct qdio_buffer_element * +zfcp_qdio_sbale_next(struct zfcp_fsf_req *fsf_req, unsigned long sbtype) +{ + if (fsf_req->sbale_curr == ZFCP_LAST_SBALE_PER_SBAL) + return zfcp_qdio_sbal_chain(fsf_req, sbtype); + + fsf_req->sbale_curr++; + + return zfcp_qdio_sbale_curr(fsf_req); +} + +/** + * zfcp_qdio_sbals_zero - initialize SBALs between first and last in queue + * with zero from + */ +static inline int +zfcp_qdio_sbals_zero(struct zfcp_qdio_queue *queue, int first, int last) +{ + struct qdio_buffer **buf = queue->buffer; + int curr = first; + int count = 0; + + for(;;) { + curr %= QDIO_MAX_BUFFERS_PER_Q; + count++; + memset(buf[curr], 0, sizeof(struct qdio_buffer)); + if (curr == last) + break; + curr++; + } + return count; +} + + +/** + * zfcp_qdio_sbals_wipe - reset all changes in SBALs for an fsf_req + */ +static inline int +zfcp_qdio_sbals_wipe(struct zfcp_fsf_req *fsf_req) +{ + return zfcp_qdio_sbals_zero(&fsf_req->adapter->request_queue, + fsf_req->sbal_first, fsf_req->sbal_curr); +} + + +/** + * zfcp_qdio_sbale_fill - set address and lenght in current SBALE + * on request_queue + */ +static inline void +zfcp_qdio_sbale_fill(struct zfcp_fsf_req *fsf_req, unsigned long sbtype, + void *addr, int length) +{ + volatile struct qdio_buffer_element *sbale; + + sbale = zfcp_qdio_sbale_curr(fsf_req); + sbale->addr = addr; + sbale->length = length; +} + +/** + * zfcp_qdio_sbals_from_segment - map memory segment to SBALE(s) + * @fsf_req: request to be processed + * @sbtype: SBALE flags + * @start_addr: address of memory segment + * @total_length: length of memory segment + * + * Alignment and length of the segment determine how many SBALEs are needed + * for the memory segment. + */ +static inline int +zfcp_qdio_sbals_from_segment(struct zfcp_fsf_req *fsf_req, unsigned long sbtype, + void *start_addr, unsigned long total_length) +{ + unsigned long remaining, length; + void *addr; + + /* split segment up heeding page boundaries */ + for (addr = start_addr, remaining = total_length; remaining > 0; + addr += length, remaining -= length) { + /* get next free SBALE for new piece */ + if (NULL == zfcp_qdio_sbale_next(fsf_req, sbtype)) { + /* no SBALE left, clean up and leave */ + zfcp_qdio_sbals_wipe(fsf_req); + return -EINVAL; + } + /* calculate length of new piece */ + length = min(remaining, + (PAGE_SIZE - ((unsigned long) addr & + (PAGE_SIZE - 1)))); + /* fill current SBALE with calculated piece */ + zfcp_qdio_sbale_fill(fsf_req, sbtype, addr, length); + } + return total_length; +} + + +/** + * zfcp_qdio_sbals_from_sg - fill SBALs from scatter-gather list + * @fsf_req: request to be processed + * @sbtype: SBALE flags + * @sg: scatter-gather list + * @sg_count: number of elements in scatter-gather list + * @max_sbals: upper bound for number of SBALs to be used + */ +inline int +zfcp_qdio_sbals_from_sg(struct zfcp_fsf_req *fsf_req, unsigned long sbtype, + struct scatterlist *sg, int sg_count, int max_sbals) +{ + int sg_index; + struct scatterlist *sg_segment; + int retval; + volatile struct qdio_buffer_element *sbale; + int bytes = 0; + + /* figure out last allowed SBAL */ + zfcp_qdio_sbal_limit(fsf_req, max_sbals); + + /* set storage-block type for current SBAL */ + sbale = zfcp_qdio_sbale_req(fsf_req, fsf_req->sbal_curr, 0); + sbale->flags |= sbtype; + + /* process all segements of scatter-gather list */ + for (sg_index = 0, sg_segment = sg, bytes = 0; + sg_index < sg_count; + sg_index++, sg_segment++) { + retval = zfcp_qdio_sbals_from_segment( + fsf_req, + sbtype, + zfcp_sg_to_address(sg_segment), + sg_segment->length); + if (retval < 0) { + bytes = retval; + goto out; + } else + bytes += retval; + } + /* assume that no other SBALEs are to follow in the same SBAL */ + sbale = zfcp_qdio_sbale_curr(fsf_req); + sbale->flags |= SBAL_FLAGS_LAST_ENTRY; +out: + return bytes; +} + + +/** + * zfcp_qdio_sbals_from_buffer - fill SBALs from buffer + * @fsf_req: request to be processed + * @sbtype: SBALE flags + * @buffer: data buffer + * @length: length of buffer + * @max_sbals: upper bound for number of SBALs to be used + */ +static inline int +zfcp_qdio_sbals_from_buffer(struct zfcp_fsf_req *fsf_req, unsigned long sbtype, + void *buffer, unsigned long length, int max_sbals) +{ + struct scatterlist sg_segment; + + zfcp_address_to_sg(buffer, &sg_segment); + sg_segment.length = length; + + return zfcp_qdio_sbals_from_sg(fsf_req, sbtype, &sg_segment, 1, + max_sbals); +} + + +/** + * zfcp_qdio_sbals_from_scsicmnd - fill SBALs from scsi command + * @fsf_req: request to be processed + * @sbtype: SBALE flags + * @scsi_cmnd: either scatter-gather list or buffer contained herein is used + * to fill SBALs + */ +inline int +zfcp_qdio_sbals_from_scsicmnd(struct zfcp_fsf_req *fsf_req, + unsigned long sbtype, struct scsi_cmnd *scsi_cmnd) +{ + if (scsi_cmnd->use_sg) { + return zfcp_qdio_sbals_from_sg(fsf_req, sbtype, + (struct scatterlist *) + scsi_cmnd->request_buffer, + scsi_cmnd->use_sg, + ZFCP_MAX_SBALS_PER_REQ); + } else { + return zfcp_qdio_sbals_from_buffer(fsf_req, sbtype, + scsi_cmnd->request_buffer, + scsi_cmnd->request_bufflen, + ZFCP_MAX_SBALS_PER_REQ); + } +} + +/** + * zfcp_qdio_determine_pci - set PCI flag in first SBALE on qdio queue if needed + */ +int +zfcp_qdio_determine_pci(struct zfcp_qdio_queue *req_queue, + struct zfcp_fsf_req *fsf_req) +{ + int new_distance_from_int; + int pci_pos; + volatile struct qdio_buffer_element *sbale; + + new_distance_from_int = req_queue->distance_from_int + + fsf_req->sbal_number; + + if (unlikely(new_distance_from_int >= ZFCP_QDIO_PCI_INTERVAL)) { + new_distance_from_int %= ZFCP_QDIO_PCI_INTERVAL; + pci_pos = fsf_req->sbal_first; + pci_pos += fsf_req->sbal_number; + pci_pos -= new_distance_from_int; + pci_pos -= 1; + pci_pos %= QDIO_MAX_BUFFERS_PER_Q; + sbale = zfcp_qdio_sbale_req(fsf_req, pci_pos, 0); + sbale->flags |= SBAL_FLAGS0_PCI; + } + return new_distance_from_int; +} + +/* + * function: zfcp_zero_sbals + * + * purpose: zeros specified range of SBALs + * + * returns: + */ +void +zfcp_qdio_zero_sbals(struct qdio_buffer *buf[], int first, int clean_count) +{ + int cur_pos; + int index; + + for (cur_pos = first; cur_pos < (first + clean_count); cur_pos++) { + index = cur_pos % QDIO_MAX_BUFFERS_PER_Q; + memset(buf[index], 0, sizeof (struct qdio_buffer)); + ZFCP_LOG_TRACE("zeroing BUFFER %d at address %p\n", + index, buf[index]); + } +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c new file mode 100644 index 000000000000..e21b547fd427 --- /dev/null +++ b/drivers/s390/scsi/zfcp_scsi.c @@ -0,0 +1,949 @@ +/* + * + * linux/drivers/s390/scsi/zfcp_scsi.c + * + * FCP adapter driver for IBM eServer zSeries + * + * (C) Copyright IBM Corp. 2002, 2004 + * + * Author(s): Martin Peschke <mpeschke@de.ibm.com> + * Raimund Schroeder <raimund.schroeder@de.ibm.com> + * Aron Zeh + * Wolfgang Taphorn + * Stefan Bader <stefan.bader@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_SCSI + +#define ZFCP_SCSI_REVISION "$Revision: 1.74 $" + +#include "zfcp_ext.h" + +static void zfcp_scsi_slave_destroy(struct scsi_device *sdp); +static int zfcp_scsi_slave_alloc(struct scsi_device *sdp); +static int zfcp_scsi_slave_configure(struct scsi_device *sdp); +static int zfcp_scsi_queuecommand(struct scsi_cmnd *, + void (*done) (struct scsi_cmnd *)); +static int zfcp_scsi_eh_abort_handler(struct scsi_cmnd *); +static int zfcp_scsi_eh_device_reset_handler(struct scsi_cmnd *); +static int zfcp_scsi_eh_bus_reset_handler(struct scsi_cmnd *); +static int zfcp_scsi_eh_host_reset_handler(struct scsi_cmnd *); +static int zfcp_task_management_function(struct zfcp_unit *, u8); + +static struct zfcp_unit *zfcp_unit_lookup(struct zfcp_adapter *, int, scsi_id_t, + scsi_lun_t); +static struct zfcp_port *zfcp_port_lookup(struct zfcp_adapter *, int, + scsi_id_t); + +static struct device_attribute *zfcp_sysfs_sdev_attrs[]; + +struct scsi_transport_template *zfcp_transport_template; + +struct zfcp_data zfcp_data = { + .scsi_host_template = { + name: ZFCP_NAME, + proc_name: "zfcp", + proc_info: NULL, + detect: NULL, + slave_alloc: zfcp_scsi_slave_alloc, + slave_configure: zfcp_scsi_slave_configure, + slave_destroy: zfcp_scsi_slave_destroy, + queuecommand: zfcp_scsi_queuecommand, + eh_abort_handler: zfcp_scsi_eh_abort_handler, + eh_device_reset_handler: zfcp_scsi_eh_device_reset_handler, + eh_bus_reset_handler: zfcp_scsi_eh_bus_reset_handler, + eh_host_reset_handler: zfcp_scsi_eh_host_reset_handler, + /* FIXME(openfcp): Tune */ + can_queue: 4096, + this_id: 0, + /* + * FIXME: + * one less? can zfcp_create_sbale cope with it? + */ + sg_tablesize: ZFCP_MAX_SBALES_PER_REQ, + cmd_per_lun: 1, + unchecked_isa_dma: 0, + use_clustering: 1, + sdev_attrs: zfcp_sysfs_sdev_attrs, + }, + .driver_version = ZFCP_VERSION, + /* rest initialised with zeros */ +}; + +/* Find start of Response Information in FCP response unit*/ +char * +zfcp_get_fcp_rsp_info_ptr(struct fcp_rsp_iu *fcp_rsp_iu) +{ + char *fcp_rsp_info_ptr; + + fcp_rsp_info_ptr = + (unsigned char *) fcp_rsp_iu + (sizeof (struct fcp_rsp_iu)); + + return fcp_rsp_info_ptr; +} + +/* Find start of Sense Information in FCP response unit*/ +char * +zfcp_get_fcp_sns_info_ptr(struct fcp_rsp_iu *fcp_rsp_iu) +{ + char *fcp_sns_info_ptr; + + fcp_sns_info_ptr = + (unsigned char *) fcp_rsp_iu + (sizeof (struct fcp_rsp_iu)); + if (fcp_rsp_iu->validity.bits.fcp_rsp_len_valid) + fcp_sns_info_ptr = (char *) fcp_sns_info_ptr + + fcp_rsp_iu->fcp_rsp_len; + + return fcp_sns_info_ptr; +} + +fcp_dl_t * +zfcp_get_fcp_dl_ptr(struct fcp_cmnd_iu * fcp_cmd) +{ + int additional_length = fcp_cmd->add_fcp_cdb_length << 2; + fcp_dl_t *fcp_dl_addr; + + fcp_dl_addr = (fcp_dl_t *) + ((unsigned char *) fcp_cmd + + sizeof (struct fcp_cmnd_iu) + additional_length); + /* + * fcp_dl_addr = start address of fcp_cmnd structure + + * size of fixed part + size of dynamically sized add_dcp_cdb field + * SEE FCP-2 documentation + */ + return fcp_dl_addr; +} + +fcp_dl_t +zfcp_get_fcp_dl(struct fcp_cmnd_iu * fcp_cmd) +{ + return *zfcp_get_fcp_dl_ptr(fcp_cmd); +} + +void +zfcp_set_fcp_dl(struct fcp_cmnd_iu *fcp_cmd, fcp_dl_t fcp_dl) +{ + *zfcp_get_fcp_dl_ptr(fcp_cmd) = fcp_dl; +} + +/* + * note: it's a bit-or operation not an assignment + * regarding the specified byte + */ +static inline void +set_byte(u32 * result, char status, char pos) +{ + *result |= status << (pos * 8); +} + +void +set_host_byte(u32 * result, char status) +{ + set_byte(result, status, 2); +} + +void +set_driver_byte(u32 * result, char status) +{ + set_byte(result, status, 3); +} + +/* + * function: zfcp_scsi_slave_alloc + * + * purpose: + * + * returns: + */ + +static int +zfcp_scsi_slave_alloc(struct scsi_device *sdp) +{ + struct zfcp_adapter *adapter; + struct zfcp_unit *unit; + unsigned long flags; + int retval = -ENODEV; + + adapter = (struct zfcp_adapter *) sdp->host->hostdata[0]; + if (!adapter) + goto out; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + unit = zfcp_unit_lookup(adapter, sdp->channel, sdp->id, sdp->lun); + if (unit) { + sdp->hostdata = unit; + unit->device = sdp; + zfcp_unit_get(unit); + retval = 0; + } + read_unlock_irqrestore(&zfcp_data.config_lock, flags); + out: + return retval; +} + +/* + * function: zfcp_scsi_slave_destroy + * + * purpose: + * + * returns: + */ + +static void +zfcp_scsi_slave_destroy(struct scsi_device *sdpnt) +{ + struct zfcp_unit *unit = (struct zfcp_unit *) sdpnt->hostdata; + + if (unit) { + sdpnt->hostdata = NULL; + unit->device = NULL; + zfcp_unit_put(unit); + } else { + ZFCP_LOG_NORMAL("bug: no unit associated with SCSI device at " + "address %p\n", sdpnt); + } +} + +/* + * called from scsi midlayer to allow finetuning of a device. + */ +static int +zfcp_scsi_slave_configure(struct scsi_device *sdp) +{ + if (sdp->tagged_supported) + scsi_adjust_queue_depth(sdp, MSG_SIMPLE_TAG, ZFCP_CMND_PER_LUN); + else + scsi_adjust_queue_depth(sdp, 0, 1); + return 0; +} + +/** + * zfcp_scsi_command_fail - set result in scsi_cmnd and call scsi_done function + * @scpnt: pointer to struct scsi_cmnd where result is set + * @result: result to be set in scpnt (e.g. DID_ERROR) + */ +static void +zfcp_scsi_command_fail(struct scsi_cmnd *scpnt, int result) +{ + set_host_byte(&scpnt->result, result); + zfcp_cmd_dbf_event_scsi("failing", scpnt); + /* return directly */ + scpnt->scsi_done(scpnt); +} + +/** + * zfcp_scsi_command_async - worker for zfcp_scsi_queuecommand and + * zfcp_scsi_command_sync + * @adapter: adapter where scsi command is issued + * @unit: unit to which scsi command is sent + * @scpnt: scsi command to be sent + * @timer: timer to be started if request is successfully initiated + * + * Note: In scsi_done function must be set in scpnt. + */ +int +zfcp_scsi_command_async(struct zfcp_adapter *adapter, struct zfcp_unit *unit, + struct scsi_cmnd *scpnt, struct timer_list *timer) +{ + int tmp; + int retval; + + retval = 0; + + BUG_ON((adapter == NULL) || (adapter != unit->port->adapter)); + BUG_ON(scpnt->scsi_done == NULL); + + if (unlikely(NULL == unit)) { + zfcp_scsi_command_fail(scpnt, DID_NO_CONNECT); + goto out; + } + + if (unlikely( + atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status) || + !atomic_test_mask(ZFCP_STATUS_COMMON_RUNNING, &unit->status))) { + ZFCP_LOG_DEBUG("stopping SCSI I/O on unit 0x%016Lx on port " + "0x%016Lx on adapter %s\n", + unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_adapter(adapter)); + zfcp_scsi_command_fail(scpnt, DID_ERROR); + goto out; + } + + if (unlikely( + !atomic_test_mask(ZFCP_STATUS_COMMON_UNBLOCKED, &unit->status))) { + ZFCP_LOG_DEBUG("adapter %s not ready or unit 0x%016Lx " + "on port 0x%016Lx in recovery\n", + zfcp_get_busid_by_unit(unit), + unit->fcp_lun, unit->port->wwpn); + retval = SCSI_MLQUEUE_DEVICE_BUSY; + goto out; + } + + tmp = zfcp_fsf_send_fcp_command_task(adapter, unit, scpnt, timer, + ZFCP_REQ_AUTO_CLEANUP); + + if (unlikely(tmp < 0)) { + ZFCP_LOG_DEBUG("error: initiation of Send FCP Cmnd failed\n"); + retval = SCSI_MLQUEUE_HOST_BUSY; + } + +out: + return retval; +} + +void +zfcp_scsi_command_sync_handler(struct scsi_cmnd *scpnt) +{ + struct completion *wait = (struct completion *) scpnt->SCp.ptr; + complete(wait); +} + + +/** + * zfcp_scsi_command_sync - send a SCSI command and wait for completion + * @unit: unit where command is sent to + * @scpnt: scsi command to be sent + * @timer: timer to be started if request is successfully initiated + * Return: 0 + * + * Errors are indicated in scpnt->result + */ +int +zfcp_scsi_command_sync(struct zfcp_unit *unit, struct scsi_cmnd *scpnt, + struct timer_list *timer) +{ + int ret; + DECLARE_COMPLETION(wait); + + scpnt->SCp.ptr = (void *) &wait; /* silent re-use */ + scpnt->scsi_done = zfcp_scsi_command_sync_handler; + ret = zfcp_scsi_command_async(unit->port->adapter, unit, scpnt, timer); + if (ret == 0) + wait_for_completion(&wait); + + scpnt->SCp.ptr = NULL; + + return 0; +} + +/* + * function: zfcp_scsi_queuecommand + * + * purpose: enqueues a SCSI command to the specified target device + * + * returns: 0 - success, SCSI command enqueued + * !0 - failure + */ +int +zfcp_scsi_queuecommand(struct scsi_cmnd *scpnt, + void (*done) (struct scsi_cmnd *)) +{ + struct zfcp_unit *unit; + struct zfcp_adapter *adapter; + + /* reset the status for this request */ + scpnt->result = 0; + scpnt->host_scribble = NULL; + scpnt->scsi_done = done; + + /* + * figure out adapter and target device + * (stored there by zfcp_scsi_slave_alloc) + */ + adapter = (struct zfcp_adapter *) scpnt->device->host->hostdata[0]; + unit = (struct zfcp_unit *) scpnt->device->hostdata; + + return zfcp_scsi_command_async(adapter, unit, scpnt, NULL); +} + +/* + * function: zfcp_unit_lookup + * + * purpose: + * + * returns: + * + * context: + */ +static struct zfcp_unit * +zfcp_unit_lookup(struct zfcp_adapter *adapter, int channel, scsi_id_t id, + scsi_lun_t lun) +{ + struct zfcp_port *port; + struct zfcp_unit *unit, *retval = NULL; + + list_for_each_entry(port, &adapter->port_list_head, list) { + if (id != port->scsi_id) + continue; + list_for_each_entry(unit, &port->unit_list_head, list) { + if (lun == unit->scsi_lun) { + retval = unit; + goto out; + } + } + } + out: + return retval; +} + +static struct zfcp_port * +zfcp_port_lookup(struct zfcp_adapter *adapter, int channel, scsi_id_t id) +{ + struct zfcp_port *port; + + list_for_each_entry(port, &adapter->port_list_head, list) { + if (id == port->scsi_id) + return port; + } + return (struct zfcp_port *) NULL; +} + +/* + * function: zfcp_scsi_eh_abort_handler + * + * purpose: tries to abort the specified (timed out) SCSI command + * + * note: We do not need to care for a SCSI command which completes + * normally but late during this abort routine runs. + * We are allowed to return late commands to the SCSI stack. + * It tracks the state of commands and will handle late commands. + * (Usually, the normal completion of late commands is ignored with + * respect to the running abort operation. Grep for 'done_late' + * in the SCSI stacks sources.) + * + * returns: SUCCESS - command has been aborted and cleaned up in internal + * bookkeeping, + * SCSI stack won't be called for aborted command + * FAILED - otherwise + */ +int +zfcp_scsi_eh_abort_handler(struct scsi_cmnd *scpnt) +{ + int retval = SUCCESS; + struct zfcp_fsf_req *new_fsf_req, *old_fsf_req; + struct zfcp_adapter *adapter = (struct zfcp_adapter *) scpnt->device->host->hostdata[0]; + struct zfcp_unit *unit = (struct zfcp_unit *) scpnt->device->hostdata; + struct zfcp_port *port = unit->port; + struct Scsi_Host *scsi_host = scpnt->device->host; + union zfcp_req_data *req_data = NULL; + unsigned long flags; + u32 status = 0; + + /* the components of a abort_dbf record (fixed size record) */ + u64 dbf_scsi_cmnd = (unsigned long) scpnt; + char dbf_opcode[ZFCP_ABORT_DBF_LENGTH]; + wwn_t dbf_wwn = port->wwpn; + fcp_lun_t dbf_fcp_lun = unit->fcp_lun; + u64 dbf_retries = scpnt->retries; + u64 dbf_allowed = scpnt->allowed; + u64 dbf_timeout = 0; + u64 dbf_fsf_req = 0; + u64 dbf_fsf_status = 0; + u64 dbf_fsf_qual[2] = { 0, 0 }; + char dbf_result[ZFCP_ABORT_DBF_LENGTH] = "##undef"; + + memset(dbf_opcode, 0, ZFCP_ABORT_DBF_LENGTH); + memcpy(dbf_opcode, + scpnt->cmnd, + min(scpnt->cmd_len, (unsigned char) ZFCP_ABORT_DBF_LENGTH)); + + ZFCP_LOG_INFO("aborting scsi_cmnd=%p on adapter %s\n", + scpnt, zfcp_get_busid_by_adapter(adapter)); + + spin_unlock_irq(scsi_host->host_lock); + + /* + * Race condition between normal (late) completion and abort has + * to be avoided. + * The entirity of all accesses to scsi_req have to be atomic. + * scsi_req is usually part of the fsf_req and thus we block the + * release of fsf_req as long as we need to access scsi_req. + */ + write_lock_irqsave(&adapter->abort_lock, flags); + + /* + * Check whether command has just completed and can not be aborted. + * Even if the command has just been completed late, we can access + * scpnt since the SCSI stack does not release it at least until + * this routine returns. (scpnt is parameter passed to this routine + * and must not disappear during abort even on late completion.) + */ + req_data = (union zfcp_req_data *) scpnt->host_scribble; + /* DEBUG */ + ZFCP_LOG_DEBUG("req_data=%p\n", req_data); + if (!req_data) { + ZFCP_LOG_DEBUG("late command completion overtook abort\n"); + /* + * That's it. + * Do not initiate abort but return SUCCESS. + */ + write_unlock_irqrestore(&adapter->abort_lock, flags); + retval = SUCCESS; + strncpy(dbf_result, "##late1", ZFCP_ABORT_DBF_LENGTH); + goto out; + } + + /* Figure out which fsf_req needs to be aborted. */ + old_fsf_req = req_data->send_fcp_command_task.fsf_req; + + dbf_fsf_req = (unsigned long) old_fsf_req; + dbf_timeout = + (jiffies - req_data->send_fcp_command_task.start_jiffies) / HZ; + + ZFCP_LOG_DEBUG("old_fsf_req=%p\n", old_fsf_req); + if (!old_fsf_req) { + write_unlock_irqrestore(&adapter->abort_lock, flags); + ZFCP_LOG_NORMAL("bug: no old fsf request found\n"); + ZFCP_LOG_NORMAL("req_data:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) req_data, sizeof (union zfcp_req_data)); + ZFCP_LOG_NORMAL("scsi_cmnd:\n"); + ZFCP_HEX_DUMP(ZFCP_LOG_LEVEL_NORMAL, + (char *) scpnt, sizeof (struct scsi_cmnd)); + retval = FAILED; + strncpy(dbf_result, "##bug:r", ZFCP_ABORT_DBF_LENGTH); + goto out; + } + old_fsf_req->data.send_fcp_command_task.scsi_cmnd = NULL; + /* mark old request as being aborted */ + old_fsf_req->status |= ZFCP_STATUS_FSFREQ_ABORTING; + /* + * We have to collect all information (e.g. unit) needed by + * zfcp_fsf_abort_fcp_command before calling that routine + * since that routine is not allowed to access + * fsf_req which it is going to abort. + * This is because of we need to release fsf_req_list_lock + * before calling zfcp_fsf_abort_fcp_command. + * Since this lock will not be held, fsf_req may complete + * late and may be released meanwhile. + */ + ZFCP_LOG_DEBUG("unit 0x%016Lx (%p)\n", unit->fcp_lun, unit); + + /* + * We block (call schedule) + * That's why we must release the lock and enable the + * interrupts before. + * On the other hand we do not need the lock anymore since + * all critical accesses to scsi_req are done. + */ + write_unlock_irqrestore(&adapter->abort_lock, flags); + /* call FSF routine which does the abort */ + new_fsf_req = zfcp_fsf_abort_fcp_command((unsigned long) old_fsf_req, + adapter, unit, 0); + ZFCP_LOG_DEBUG("new_fsf_req=%p\n", new_fsf_req); + if (!new_fsf_req) { + retval = FAILED; + ZFCP_LOG_NORMAL("error: initiation of Abort FCP Cmnd " + "failed\n"); + strncpy(dbf_result, "##nores", ZFCP_ABORT_DBF_LENGTH); + goto out; + } + + /* wait for completion of abort */ + ZFCP_LOG_DEBUG("waiting for cleanup...\n"); +#if 1 + /* + * FIXME: + * copying zfcp_fsf_req_wait_and_cleanup code is not really nice + */ + __wait_event(new_fsf_req->completion_wq, + new_fsf_req->status & ZFCP_STATUS_FSFREQ_COMPLETED); + status = new_fsf_req->status; + dbf_fsf_status = new_fsf_req->qtcb->header.fsf_status; + /* + * Ralphs special debug load provides timestamps in the FSF + * status qualifier. This might be specified later if being + * useful for debugging aborts. + */ + dbf_fsf_qual[0] = + *(u64 *) & new_fsf_req->qtcb->header.fsf_status_qual.word[0]; + dbf_fsf_qual[1] = + *(u64 *) & new_fsf_req->qtcb->header.fsf_status_qual.word[2]; + zfcp_fsf_req_cleanup(new_fsf_req); +#else + retval = zfcp_fsf_req_wait_and_cleanup(new_fsf_req, + ZFCP_UNINTERRUPTIBLE, &status); +#endif + ZFCP_LOG_DEBUG("Waiting for cleanup complete, status=0x%x\n", status); + /* status should be valid since signals were not permitted */ + if (status & ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED) { + retval = SUCCESS; + strncpy(dbf_result, "##succ", ZFCP_ABORT_DBF_LENGTH); + } else if (status & ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED) { + retval = SUCCESS; + strncpy(dbf_result, "##late2", ZFCP_ABORT_DBF_LENGTH); + } else { + retval = FAILED; + strncpy(dbf_result, "##fail", ZFCP_ABORT_DBF_LENGTH); + } + + out: + debug_event(adapter->abort_dbf, 1, &dbf_scsi_cmnd, sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_opcode, ZFCP_ABORT_DBF_LENGTH); + debug_event(adapter->abort_dbf, 1, &dbf_wwn, sizeof (wwn_t)); + debug_event(adapter->abort_dbf, 1, &dbf_fcp_lun, sizeof (fcp_lun_t)); + debug_event(adapter->abort_dbf, 1, &dbf_retries, sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_allowed, sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_timeout, sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_fsf_req, sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_fsf_status, sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_fsf_qual[0], sizeof (u64)); + debug_event(adapter->abort_dbf, 1, &dbf_fsf_qual[1], sizeof (u64)); + debug_text_event(adapter->abort_dbf, 1, dbf_result); + + spin_lock_irq(scsi_host->host_lock); + return retval; +} + +/* + * function: zfcp_scsi_eh_device_reset_handler + * + * purpose: + * + * returns: + */ +int +zfcp_scsi_eh_device_reset_handler(struct scsi_cmnd *scpnt) +{ + int retval; + struct zfcp_unit *unit = (struct zfcp_unit *) scpnt->device->hostdata; + struct Scsi_Host *scsi_host = scpnt->device->host; + + spin_unlock_irq(scsi_host->host_lock); + + if (!unit) { + ZFCP_LOG_NORMAL("bug: Tried reset for nonexistent unit\n"); + retval = SUCCESS; + goto out; + } + ZFCP_LOG_NORMAL("resetting unit 0x%016Lx\n", unit->fcp_lun); + + /* + * If we do not know whether the unit supports 'logical unit reset' + * then try 'logical unit reset' and proceed with 'target reset' + * if 'logical unit reset' fails. + * If the unit is known not to support 'logical unit reset' then + * skip 'logical unit reset' and try 'target reset' immediately. + */ + if (!atomic_test_mask(ZFCP_STATUS_UNIT_NOTSUPPUNITRESET, + &unit->status)) { + retval = + zfcp_task_management_function(unit, FCP_LOGICAL_UNIT_RESET); + if (retval) { + ZFCP_LOG_DEBUG("unit reset failed (unit=%p)\n", unit); + if (retval == -ENOTSUPP) + atomic_set_mask + (ZFCP_STATUS_UNIT_NOTSUPPUNITRESET, + &unit->status); + /* fall through and try 'target reset' next */ + } else { + ZFCP_LOG_DEBUG("unit reset succeeded (unit=%p)\n", + unit); + /* avoid 'target reset' */ + retval = SUCCESS; + goto out; + } + } + retval = zfcp_task_management_function(unit, FCP_TARGET_RESET); + if (retval) { + ZFCP_LOG_DEBUG("target reset failed (unit=%p)\n", unit); + retval = FAILED; + } else { + ZFCP_LOG_DEBUG("target reset succeeded (unit=%p)\n", unit); + retval = SUCCESS; + } + out: + spin_lock_irq(scsi_host->host_lock); + return retval; +} + +static int +zfcp_task_management_function(struct zfcp_unit *unit, u8 tm_flags) +{ + struct zfcp_adapter *adapter = unit->port->adapter; + int retval; + int status; + struct zfcp_fsf_req *fsf_req; + + /* issue task management function */ + fsf_req = zfcp_fsf_send_fcp_command_task_management + (adapter, unit, tm_flags, 0); + if (!fsf_req) { + ZFCP_LOG_INFO("error: creation of task management request " + "failed for unit 0x%016Lx on port 0x%016Lx on " + "adapter %s\n", unit->fcp_lun, unit->port->wwpn, + zfcp_get_busid_by_adapter(adapter)); + retval = -ENOMEM; + goto out; + } + + retval = zfcp_fsf_req_wait_and_cleanup(fsf_req, + ZFCP_UNINTERRUPTIBLE, &status); + /* + * check completion status of task management function + * (status should always be valid since no signals permitted) + */ + if (status & ZFCP_STATUS_FSFREQ_TMFUNCFAILED) + retval = -EIO; + else if (status & ZFCP_STATUS_FSFREQ_TMFUNCNOTSUPP) + retval = -ENOTSUPP; + else + retval = 0; + out: + return retval; +} + +/* + * function: zfcp_scsi_eh_bus_reset_handler + * + * purpose: + * + * returns: + */ +int +zfcp_scsi_eh_bus_reset_handler(struct scsi_cmnd *scpnt) +{ + int retval = 0; + struct zfcp_unit *unit; + struct Scsi_Host *scsi_host = scpnt->device->host; + + spin_unlock_irq(scsi_host->host_lock); + + unit = (struct zfcp_unit *) scpnt->device->hostdata; + ZFCP_LOG_NORMAL("bus reset because of problems with " + "unit 0x%016Lx\n", unit->fcp_lun); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + zfcp_erp_wait(unit->port->adapter); + retval = SUCCESS; + + spin_lock_irq(scsi_host->host_lock); + return retval; +} + +/* + * function: zfcp_scsi_eh_host_reset_handler + * + * purpose: + * + * returns: + */ +int +zfcp_scsi_eh_host_reset_handler(struct scsi_cmnd *scpnt) +{ + int retval = 0; + struct zfcp_unit *unit; + struct Scsi_Host *scsi_host = scpnt->device->host; + + spin_unlock_irq(scsi_host->host_lock); + + unit = (struct zfcp_unit *) scpnt->device->hostdata; + ZFCP_LOG_NORMAL("host reset because of problems with " + "unit 0x%016Lx\n", unit->fcp_lun); + zfcp_erp_adapter_reopen(unit->port->adapter, 0); + zfcp_erp_wait(unit->port->adapter); + retval = SUCCESS; + + spin_lock_irq(scsi_host->host_lock); + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +int +zfcp_adapter_scsi_register(struct zfcp_adapter *adapter) +{ + int retval = 0; + static unsigned int unique_id = 0; + + /* register adapter as SCSI host with mid layer of SCSI stack */ + adapter->scsi_host = scsi_host_alloc(&zfcp_data.scsi_host_template, + sizeof (struct zfcp_adapter *)); + if (!adapter->scsi_host) { + ZFCP_LOG_NORMAL("error: registration with SCSI stack failed " + "for adapter %s ", + zfcp_get_busid_by_adapter(adapter)); + retval = -EIO; + goto out; + } + ZFCP_LOG_DEBUG("host registered, scsi_host=%p\n", adapter->scsi_host); + + /* tell the SCSI stack some characteristics of this adapter */ + adapter->scsi_host->max_id = 1; + adapter->scsi_host->max_lun = 1; + adapter->scsi_host->max_channel = 0; + adapter->scsi_host->unique_id = unique_id++; /* FIXME */ + adapter->scsi_host->max_cmd_len = ZFCP_MAX_SCSI_CMND_LENGTH; + adapter->scsi_host->transportt = zfcp_transport_template; + /* + * Reverse mapping of the host number to avoid race condition + */ + adapter->scsi_host_no = adapter->scsi_host->host_no; + + /* + * save a pointer to our own adapter data structure within + * hostdata field of SCSI host data structure + */ + adapter->scsi_host->hostdata[0] = (unsigned long) adapter; + + if (scsi_add_host(adapter->scsi_host, &adapter->ccw_device->dev)) { + scsi_host_put(adapter->scsi_host); + retval = -EIO; + goto out; + } + atomic_set_mask(ZFCP_STATUS_ADAPTER_REGISTERED, &adapter->status); + out: + return retval; +} + +/* + * function: + * + * purpose: + * + * returns: + */ +void +zfcp_adapter_scsi_unregister(struct zfcp_adapter *adapter) +{ + struct Scsi_Host *shost; + + shost = adapter->scsi_host; + if (!shost) + return; + scsi_remove_host(shost); + scsi_host_put(shost); + adapter->scsi_host = NULL; + adapter->scsi_host_no = 0; + atomic_clear_mask(ZFCP_STATUS_ADAPTER_REGISTERED, &adapter->status); + + return; +} + + +void +zfcp_fsf_start_scsi_er_timer(struct zfcp_adapter *adapter) +{ + adapter->scsi_er_timer.function = zfcp_fsf_scsi_er_timeout_handler; + adapter->scsi_er_timer.data = (unsigned long) adapter; + adapter->scsi_er_timer.expires = jiffies + ZFCP_SCSI_ER_TIMEOUT; + add_timer(&adapter->scsi_er_timer); +} + +/* + * Support functions for FC transport class + */ +static void +zfcp_get_port_id(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct zfcp_adapter *adapter = (struct zfcp_adapter *)shost->hostdata[0]; + struct zfcp_port *port; + unsigned long flags; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + port = zfcp_port_lookup(adapter, starget->channel, starget->id); + if (port) + fc_starget_port_id(starget) = port->d_id; + else + fc_starget_port_id(starget) = -1; + read_unlock_irqrestore(&zfcp_data.config_lock, flags); +} + +static void +zfcp_get_port_name(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct zfcp_adapter *adapter = (struct zfcp_adapter *)shost->hostdata[0]; + struct zfcp_port *port; + unsigned long flags; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + port = zfcp_port_lookup(adapter, starget->channel, starget->id); + if (port) + fc_starget_port_name(starget) = port->wwpn; + else + fc_starget_port_name(starget) = -1; + read_unlock_irqrestore(&zfcp_data.config_lock, flags); +} + +static void +zfcp_get_node_name(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct zfcp_adapter *adapter = (struct zfcp_adapter *)shost->hostdata[0]; + struct zfcp_port *port; + unsigned long flags; + + read_lock_irqsave(&zfcp_data.config_lock, flags); + port = zfcp_port_lookup(adapter, starget->channel, starget->id); + if (port) + fc_starget_node_name(starget) = port->wwnn; + else + fc_starget_node_name(starget) = -1; + read_unlock_irqrestore(&zfcp_data.config_lock, flags); +} + +struct fc_function_template zfcp_transport_functions = { + .get_starget_port_id = zfcp_get_port_id, + .get_starget_port_name = zfcp_get_port_name, + .get_starget_node_name = zfcp_get_node_name, + .show_starget_port_id = 1, + .show_starget_port_name = 1, + .show_starget_node_name = 1, +}; + +/** + * ZFCP_DEFINE_SCSI_ATTR + * @_name: name of show attribute + * @_format: format string + * @_value: value to print + * + * Generates attribute for a unit. + */ +#define ZFCP_DEFINE_SCSI_ATTR(_name, _format, _value) \ +static ssize_t zfcp_sysfs_scsi_##_name##_show(struct device *dev, \ + char *buf) \ +{ \ + struct scsi_device *sdev; \ + struct zfcp_unit *unit; \ + \ + sdev = to_scsi_device(dev); \ + unit = sdev->hostdata; \ + return sprintf(buf, _format, _value); \ +} \ + \ +static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_scsi_##_name##_show, NULL); + +ZFCP_DEFINE_SCSI_ATTR(hba_id, "%s\n", zfcp_get_busid_by_unit(unit)); +ZFCP_DEFINE_SCSI_ATTR(wwpn, "0x%016llx\n", unit->port->wwpn); +ZFCP_DEFINE_SCSI_ATTR(fcp_lun, "0x%016llx\n", unit->fcp_lun); + +static struct device_attribute *zfcp_sysfs_sdev_attrs[] = { + &dev_attr_fcp_lun, + &dev_attr_wwpn, + &dev_attr_hba_id, + NULL +}; + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_sysfs_adapter.c b/drivers/s390/scsi/zfcp_sysfs_adapter.c new file mode 100644 index 000000000000..ff28ade1dfc7 --- /dev/null +++ b/drivers/s390/scsi/zfcp_sysfs_adapter.c @@ -0,0 +1,298 @@ +/* + * linux/drivers/s390/scsi/zfcp_sysfs_adapter.c + * + * FCP adapter driver for IBM eServer zSeries + * + * sysfs adapter related routines + * + * (C) Copyright IBM Corp. 2003, 2004 + * + * Authors: + * Martin Peschke <mpeschke@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_SYSFS_ADAPTER_C_REVISION "$Revision: 1.38 $" + +#include "zfcp_ext.h" + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_CONFIG + +static const char fc_topologies[5][25] = { + "<error>", + "point-to-point", + "fabric", + "arbitrated loop", + "fabric (virt. adapter)" +}; + +/** + * ZFCP_DEFINE_ADAPTER_ATTR + * @_name: name of show attribute + * @_format: format string + * @_value: value to print + * + * Generates attributes for an adapter. + */ +#define ZFCP_DEFINE_ADAPTER_ATTR(_name, _format, _value) \ +static ssize_t zfcp_sysfs_adapter_##_name##_show(struct device *dev, \ + char *buf) \ +{ \ + struct zfcp_adapter *adapter; \ + \ + adapter = dev_get_drvdata(dev); \ + return sprintf(buf, _format, _value); \ +} \ + \ +static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_adapter_##_name##_show, NULL); + +ZFCP_DEFINE_ADAPTER_ATTR(status, "0x%08x\n", atomic_read(&adapter->status)); +ZFCP_DEFINE_ADAPTER_ATTR(wwnn, "0x%016llx\n", adapter->wwnn); +ZFCP_DEFINE_ADAPTER_ATTR(wwpn, "0x%016llx\n", adapter->wwpn); +ZFCP_DEFINE_ADAPTER_ATTR(s_id, "0x%06x\n", adapter->s_id); +ZFCP_DEFINE_ADAPTER_ATTR(card_version, "0x%04x\n", adapter->hydra_version); +ZFCP_DEFINE_ADAPTER_ATTR(lic_version, "0x%08x\n", adapter->fsf_lic_version); +ZFCP_DEFINE_ADAPTER_ATTR(fc_link_speed, "%d Gb/s\n", adapter->fc_link_speed); +ZFCP_DEFINE_ADAPTER_ATTR(fc_service_class, "%d\n", adapter->fc_service_class); +ZFCP_DEFINE_ADAPTER_ATTR(fc_topology, "%s\n", + fc_topologies[adapter->fc_topology]); +ZFCP_DEFINE_ADAPTER_ATTR(hardware_version, "0x%08x\n", + adapter->hardware_version); +ZFCP_DEFINE_ADAPTER_ATTR(serial_number, "%17s\n", adapter->serial_number); +ZFCP_DEFINE_ADAPTER_ATTR(scsi_host_no, "0x%x\n", adapter->scsi_host_no); +ZFCP_DEFINE_ADAPTER_ATTR(in_recovery, "%d\n", atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &adapter->status)); + +/** + * zfcp_sysfs_port_add_store - add a port to sysfs tree + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + * + * Store function of the "port_add" attribute of an adapter. + */ +static ssize_t +zfcp_sysfs_port_add_store(struct device *dev, const char *buf, size_t count) +{ + wwn_t wwpn; + char *endp; + struct zfcp_adapter *adapter; + struct zfcp_port *port; + int retval = -EINVAL; + + down(&zfcp_data.config_sema); + + adapter = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status)) { + retval = -EBUSY; + goto out; + } + + wwpn = simple_strtoull(buf, &endp, 0); + if ((endp + 1) < (buf + count)) + goto out; + + port = zfcp_port_enqueue(adapter, wwpn, 0, 0); + if (!port) + goto out; + + retval = 0; + + zfcp_erp_port_reopen(port, 0); + zfcp_erp_wait(port->adapter); + zfcp_port_put(port); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +static DEVICE_ATTR(port_add, S_IWUSR, NULL, zfcp_sysfs_port_add_store); + +/** + * zfcp_sysfs_port_remove_store - remove a port from sysfs tree + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + * + * Store function of the "port_remove" attribute of an adapter. + */ +static ssize_t +zfcp_sysfs_port_remove_store(struct device *dev, const char *buf, size_t count) +{ + struct zfcp_adapter *adapter; + struct zfcp_port *port; + wwn_t wwpn; + char *endp; + int retval = 0; + + down(&zfcp_data.config_sema); + + adapter = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status)) { + retval = -EBUSY; + goto out; + } + + wwpn = simple_strtoull(buf, &endp, 0); + if ((endp + 1) < (buf + count)) { + retval = -EINVAL; + goto out; + } + + write_lock_irq(&zfcp_data.config_lock); + port = zfcp_get_port_by_wwpn(adapter, wwpn); + if (port && (atomic_read(&port->refcount) == 0)) { + zfcp_port_get(port); + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status); + list_move(&port->list, &adapter->port_remove_lh); + } + else { + port = NULL; + } + write_unlock_irq(&zfcp_data.config_lock); + + if (!port) { + retval = -ENXIO; + goto out; + } + + zfcp_erp_port_shutdown(port, 0); + zfcp_erp_wait(adapter); + zfcp_port_put(port); + zfcp_port_dequeue(port); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +static DEVICE_ATTR(port_remove, S_IWUSR, NULL, zfcp_sysfs_port_remove_store); + +/** + * zfcp_sysfs_adapter_failed_store - failed state of adapter + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + * + * Store function of the "failed" attribute of an adapter. + * If a "0" gets written to "failed", error recovery will be + * started for the belonging adapter. + */ +static ssize_t +zfcp_sysfs_adapter_failed_store(struct device *dev, + const char *buf, size_t count) +{ + struct zfcp_adapter *adapter; + unsigned int val; + char *endp; + int retval = 0; + + down(&zfcp_data.config_sema); + + adapter = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &adapter->status)) { + retval = -EBUSY; + goto out; + } + + val = simple_strtoul(buf, &endp, 0); + if (((endp + 1) < (buf + count)) || (val != 0)) { + retval = -EINVAL; + goto out; + } + + zfcp_erp_modify_adapter_status(adapter, ZFCP_STATUS_COMMON_RUNNING, + ZFCP_SET); + zfcp_erp_adapter_reopen(adapter, ZFCP_STATUS_COMMON_ERP_FAILED); + zfcp_erp_wait(adapter); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +/** + * zfcp_sysfs_adapter_failed_show - failed state of adapter + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * + * Show function of "failed" attribute of adapter. Will be + * "0" if adapter is working, otherwise "1". + */ +static ssize_t +zfcp_sysfs_adapter_failed_show(struct device *dev, char *buf) +{ + struct zfcp_adapter *adapter; + + adapter = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &adapter->status)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static DEVICE_ATTR(failed, S_IWUSR | S_IRUGO, zfcp_sysfs_adapter_failed_show, + zfcp_sysfs_adapter_failed_store); + +static struct attribute *zfcp_adapter_attrs[] = { + &dev_attr_failed.attr, + &dev_attr_in_recovery.attr, + &dev_attr_port_remove.attr, + &dev_attr_port_add.attr, + &dev_attr_wwnn.attr, + &dev_attr_wwpn.attr, + &dev_attr_s_id.attr, + &dev_attr_card_version.attr, + &dev_attr_lic_version.attr, + &dev_attr_fc_link_speed.attr, + &dev_attr_fc_service_class.attr, + &dev_attr_fc_topology.attr, + &dev_attr_scsi_host_no.attr, + &dev_attr_status.attr, + &dev_attr_hardware_version.attr, + &dev_attr_serial_number.attr, + NULL +}; + +static struct attribute_group zfcp_adapter_attr_group = { + .attrs = zfcp_adapter_attrs, +}; + +/** + * zfcp_sysfs_create_adapter_files - create sysfs adapter files + * @dev: pointer to belonging device + * + * Create all attributes of the sysfs representation of an adapter. + */ +int +zfcp_sysfs_adapter_create_files(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &zfcp_adapter_attr_group); +} + +/** + * zfcp_sysfs_remove_adapter_files - remove sysfs adapter files + * @dev: pointer to belonging device + * + * Remove all attributes of the sysfs representation of an adapter. + */ +void +zfcp_sysfs_adapter_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &zfcp_adapter_attr_group); +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_sysfs_driver.c b/drivers/s390/scsi/zfcp_sysfs_driver.c new file mode 100644 index 000000000000..77a5e2dcc0ff --- /dev/null +++ b/drivers/s390/scsi/zfcp_sysfs_driver.c @@ -0,0 +1,135 @@ +/* + * linux/drivers/s390/scsi/zfcp_sysfs_driver.c + * + * FCP adapter driver for IBM eServer zSeries + * + * sysfs driver related routines + * + * (C) Copyright IBM Corp. 2003, 2004 + * + * Authors: + * Martin Peschke <mpeschke@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_SYSFS_DRIVER_C_REVISION "$Revision: 1.17 $" + +#include "zfcp_ext.h" + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_CONFIG + +/** + * ZFCP_DEFINE_DRIVER_ATTR - define for all loglevels sysfs attributes + * @_name: name of attribute + * @_define: name of ZFCP loglevel define + * + * Generates store function for a sysfs loglevel attribute of zfcp driver. + */ +#define ZFCP_DEFINE_DRIVER_ATTR(_name, _define) \ +static ssize_t zfcp_sysfs_loglevel_##_name##_store(struct device_driver *drv, \ + const char *buf, \ + size_t count) \ +{ \ + unsigned int loglevel; \ + unsigned int new_loglevel; \ + char *endp; \ + \ + new_loglevel = simple_strtoul(buf, &endp, 0); \ + if ((endp + 1) < (buf + count)) \ + return -EINVAL; \ + if (new_loglevel > 3) \ + return -EINVAL; \ + down(&zfcp_data.config_sema); \ + loglevel = atomic_read(&zfcp_data.loglevel); \ + loglevel &= ~((unsigned int) 0xf << (ZFCP_LOG_AREA_##_define << 2)); \ + loglevel |= new_loglevel << (ZFCP_LOG_AREA_##_define << 2); \ + atomic_set(&zfcp_data.loglevel, loglevel); \ + up(&zfcp_data.config_sema); \ + return count; \ +} \ + \ +static ssize_t zfcp_sysfs_loglevel_##_name##_show(struct device_driver *dev, \ + char *buf) \ +{ \ + return sprintf(buf,"%d\n", (unsigned int) \ + ZFCP_GET_LOG_VALUE(ZFCP_LOG_AREA_##_define)); \ +} \ + \ +static DRIVER_ATTR(loglevel_##_name, S_IWUSR | S_IRUGO, \ + zfcp_sysfs_loglevel_##_name##_show, \ + zfcp_sysfs_loglevel_##_name##_store); + +ZFCP_DEFINE_DRIVER_ATTR(other, OTHER); +ZFCP_DEFINE_DRIVER_ATTR(scsi, SCSI); +ZFCP_DEFINE_DRIVER_ATTR(fsf, FSF); +ZFCP_DEFINE_DRIVER_ATTR(config, CONFIG); +ZFCP_DEFINE_DRIVER_ATTR(cio, CIO); +ZFCP_DEFINE_DRIVER_ATTR(qdio, QDIO); +ZFCP_DEFINE_DRIVER_ATTR(erp, ERP); +ZFCP_DEFINE_DRIVER_ATTR(fc, FC); + +static ssize_t zfcp_sysfs_version_show(struct device_driver *dev, + char *buf) +{ + return sprintf(buf, "%s\n", zfcp_data.driver_version); +} + +static DRIVER_ATTR(version, S_IRUGO, zfcp_sysfs_version_show, NULL); + +static struct attribute *zfcp_driver_attrs[] = { + &driver_attr_loglevel_other.attr, + &driver_attr_loglevel_scsi.attr, + &driver_attr_loglevel_fsf.attr, + &driver_attr_loglevel_config.attr, + &driver_attr_loglevel_cio.attr, + &driver_attr_loglevel_qdio.attr, + &driver_attr_loglevel_erp.attr, + &driver_attr_loglevel_fc.attr, + &driver_attr_version.attr, + NULL +}; + +static struct attribute_group zfcp_driver_attr_group = { + .attrs = zfcp_driver_attrs, +}; + +/** + * zfcp_sysfs_create_driver_files - create sysfs driver files + * @dev: pointer to belonging device + * + * Create all sysfs attributes of the zfcp device driver + */ +int +zfcp_sysfs_driver_create_files(struct device_driver *drv) +{ + return sysfs_create_group(&drv->kobj, &zfcp_driver_attr_group); +} + +/** + * zfcp_sysfs_remove_driver_files - remove sysfs driver files + * @dev: pointer to belonging device + * + * Remove all sysfs attributes of the zfcp device driver + */ +void +zfcp_sysfs_driver_remove_files(struct device_driver *drv) +{ + sysfs_remove_group(&drv->kobj, &zfcp_driver_attr_group); +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_sysfs_port.c b/drivers/s390/scsi/zfcp_sysfs_port.c new file mode 100644 index 000000000000..6aafb2abb4b5 --- /dev/null +++ b/drivers/s390/scsi/zfcp_sysfs_port.c @@ -0,0 +1,311 @@ +/* + * linux/drivers/s390/scsi/zfcp_sysfs_port.c + * + * FCP adapter driver for IBM eServer zSeries + * + * sysfs port related routines + * + * (C) Copyright IBM Corp. 2003, 2004 + * + * Authors: + * Martin Peschke <mpeschke@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.com> + * Volker Sameske <sameske@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_SYSFS_PORT_C_REVISION "$Revision: 1.47 $" + +#include "zfcp_ext.h" + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_CONFIG + +/** + * zfcp_sysfs_port_release - gets called when a struct device port is released + * @dev: pointer to belonging device + */ +void +zfcp_sysfs_port_release(struct device *dev) +{ + kfree(dev); +} + +/** + * ZFCP_DEFINE_PORT_ATTR + * @_name: name of show attribute + * @_format: format string + * @_value: value to print + * + * Generates attributes for a port. + */ +#define ZFCP_DEFINE_PORT_ATTR(_name, _format, _value) \ +static ssize_t zfcp_sysfs_port_##_name##_show(struct device *dev, \ + char *buf) \ +{ \ + struct zfcp_port *port; \ + \ + port = dev_get_drvdata(dev); \ + return sprintf(buf, _format, _value); \ +} \ + \ +static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_port_##_name##_show, NULL); + +ZFCP_DEFINE_PORT_ATTR(status, "0x%08x\n", atomic_read(&port->status)); +ZFCP_DEFINE_PORT_ATTR(wwnn, "0x%016llx\n", port->wwnn); +ZFCP_DEFINE_PORT_ATTR(d_id, "0x%06x\n", port->d_id); +ZFCP_DEFINE_PORT_ATTR(scsi_id, "0x%x\n", port->scsi_id); +ZFCP_DEFINE_PORT_ATTR(in_recovery, "%d\n", atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &port->status)); +ZFCP_DEFINE_PORT_ATTR(access_denied, "%d\n", atomic_test_mask + (ZFCP_STATUS_COMMON_ACCESS_DENIED, &port->status)); + +/** + * zfcp_sysfs_unit_add_store - add a unit to sysfs tree + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + * + * Store function of the "unit_add" attribute of a port. + */ +static ssize_t +zfcp_sysfs_unit_add_store(struct device *dev, const char *buf, size_t count) +{ + fcp_lun_t fcp_lun; + char *endp; + struct zfcp_port *port; + struct zfcp_unit *unit; + int retval = -EINVAL; + + down(&zfcp_data.config_sema); + + port = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) { + retval = -EBUSY; + goto out; + } + + fcp_lun = simple_strtoull(buf, &endp, 0); + if ((endp + 1) < (buf + count)) + goto out; + + unit = zfcp_unit_enqueue(port, fcp_lun); + if (!unit) + goto out; + + retval = 0; + + zfcp_erp_unit_reopen(unit, 0); + zfcp_erp_wait(unit->port->adapter); + zfcp_unit_put(unit); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +static DEVICE_ATTR(unit_add, S_IWUSR, NULL, zfcp_sysfs_unit_add_store); + +/** + * zfcp_sysfs_unit_remove_store - remove a unit from sysfs tree + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + */ +static ssize_t +zfcp_sysfs_unit_remove_store(struct device *dev, const char *buf, size_t count) +{ + struct zfcp_port *port; + struct zfcp_unit *unit; + fcp_lun_t fcp_lun; + char *endp; + int retval = 0; + + down(&zfcp_data.config_sema); + + port = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) { + retval = -EBUSY; + goto out; + } + + fcp_lun = simple_strtoull(buf, &endp, 0); + if ((endp + 1) < (buf + count)) { + retval = -EINVAL; + goto out; + } + + write_lock_irq(&zfcp_data.config_lock); + unit = zfcp_get_unit_by_lun(port, fcp_lun); + if (unit && (atomic_read(&unit->refcount) == 0)) { + zfcp_unit_get(unit); + atomic_set_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status); + list_move(&unit->list, &port->unit_remove_lh); + } + else { + unit = NULL; + } + write_unlock_irq(&zfcp_data.config_lock); + + if (!unit) { + retval = -ENXIO; + goto out; + } + + zfcp_erp_unit_shutdown(unit, 0); + zfcp_erp_wait(unit->port->adapter); + zfcp_unit_put(unit); + zfcp_unit_dequeue(unit); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +static DEVICE_ATTR(unit_remove, S_IWUSR, NULL, zfcp_sysfs_unit_remove_store); + +/** + * zfcp_sysfs_port_failed_store - failed state of port + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + * + * Store function of the "failed" attribute of a port. + * If a "0" gets written to "failed", error recovery will be + * started for the belonging port. + */ +static ssize_t +zfcp_sysfs_port_failed_store(struct device *dev, const char *buf, size_t count) +{ + struct zfcp_port *port; + unsigned int val; + char *endp; + int retval = 0; + + down(&zfcp_data.config_sema); + + port = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &port->status)) { + retval = -EBUSY; + goto out; + } + + val = simple_strtoul(buf, &endp, 0); + if (((endp + 1) < (buf + count)) || (val != 0)) { + retval = -EINVAL; + goto out; + } + + zfcp_erp_modify_port_status(port, ZFCP_STATUS_COMMON_RUNNING, ZFCP_SET); + zfcp_erp_port_reopen(port, ZFCP_STATUS_COMMON_ERP_FAILED); + zfcp_erp_wait(port->adapter); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +/** + * zfcp_sysfs_port_failed_show - failed state of port + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * + * Show function of "failed" attribute of port. Will be + * "0" if port is working, otherwise "1". + */ +static ssize_t +zfcp_sysfs_port_failed_show(struct device *dev, char *buf) +{ + struct zfcp_port *port; + + port = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &port->status)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static DEVICE_ATTR(failed, S_IWUSR | S_IRUGO, zfcp_sysfs_port_failed_show, + zfcp_sysfs_port_failed_store); + +/** + * zfcp_port_common_attrs + * sysfs attributes that are common for all kind of fc ports. + */ +static struct attribute *zfcp_port_common_attrs[] = { + &dev_attr_failed.attr, + &dev_attr_in_recovery.attr, + &dev_attr_status.attr, + &dev_attr_wwnn.attr, + &dev_attr_d_id.attr, + &dev_attr_access_denied.attr, + NULL +}; + +static struct attribute_group zfcp_port_common_attr_group = { + .attrs = zfcp_port_common_attrs, +}; + +/** + * zfcp_port_no_ns_attrs + * sysfs attributes not to be used for nameserver ports. + */ +static struct attribute *zfcp_port_no_ns_attrs[] = { + &dev_attr_unit_add.attr, + &dev_attr_unit_remove.attr, + &dev_attr_scsi_id.attr, + NULL +}; + +static struct attribute_group zfcp_port_no_ns_attr_group = { + .attrs = zfcp_port_no_ns_attrs, +}; + +/** + * zfcp_sysfs_port_create_files - create sysfs port files + * @dev: pointer to belonging device + * + * Create all attributes of the sysfs representation of a port. + */ +int +zfcp_sysfs_port_create_files(struct device *dev, u32 flags) +{ + int retval; + + retval = sysfs_create_group(&dev->kobj, &zfcp_port_common_attr_group); + + if ((flags & ZFCP_STATUS_PORT_WKA) || retval) + return retval; + + retval = sysfs_create_group(&dev->kobj, &zfcp_port_no_ns_attr_group); + if (retval) + sysfs_remove_group(&dev->kobj, &zfcp_port_common_attr_group); + + return retval; +} + +/** + * zfcp_sysfs_port_remove_files - remove sysfs port files + * @dev: pointer to belonging device + * + * Remove all attributes of the sysfs representation of a port. + */ +void +zfcp_sysfs_port_remove_files(struct device *dev, u32 flags) +{ + sysfs_remove_group(&dev->kobj, &zfcp_port_common_attr_group); + if (!(flags & ZFCP_STATUS_PORT_WKA)) + sysfs_remove_group(&dev->kobj, &zfcp_port_no_ns_attr_group); +} + +#undef ZFCP_LOG_AREA diff --git a/drivers/s390/scsi/zfcp_sysfs_unit.c b/drivers/s390/scsi/zfcp_sysfs_unit.c new file mode 100644 index 000000000000..87c0b461831f --- /dev/null +++ b/drivers/s390/scsi/zfcp_sysfs_unit.c @@ -0,0 +1,179 @@ +/* + * linux/drivers/s390/scsi/zfcp_sysfs_unit.c + * + * FCP adapter driver for IBM eServer zSeries + * + * sysfs unit related routines + * + * (C) Copyright IBM Corp. 2003, 2004 + * + * Authors: + * Martin Peschke <mpeschke@de.ibm.com> + * Heiko Carstens <heiko.carstens@de.ibm.com> + * Andreas Herrmann <aherrman@de.ibm.com> + * Volker Sameske <sameske@de.ibm.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, 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define ZFCP_SYSFS_UNIT_C_REVISION "$Revision: 1.30 $" + +#include "zfcp_ext.h" + +#define ZFCP_LOG_AREA ZFCP_LOG_AREA_CONFIG + +/** + * zfcp_sysfs_unit_release - gets called when a struct device unit is released + * @dev: pointer to belonging device + */ +void +zfcp_sysfs_unit_release(struct device *dev) +{ + kfree(dev); +} + +/** + * ZFCP_DEFINE_UNIT_ATTR + * @_name: name of show attribute + * @_format: format string + * @_value: value to print + * + * Generates attribute for a unit. + */ +#define ZFCP_DEFINE_UNIT_ATTR(_name, _format, _value) \ +static ssize_t zfcp_sysfs_unit_##_name##_show(struct device *dev, \ + char *buf) \ +{ \ + struct zfcp_unit *unit; \ + \ + unit = dev_get_drvdata(dev); \ + return sprintf(buf, _format, _value); \ +} \ + \ +static DEVICE_ATTR(_name, S_IRUGO, zfcp_sysfs_unit_##_name##_show, NULL); + +ZFCP_DEFINE_UNIT_ATTR(status, "0x%08x\n", atomic_read(&unit->status)); +ZFCP_DEFINE_UNIT_ATTR(scsi_lun, "0x%x\n", unit->scsi_lun); +ZFCP_DEFINE_UNIT_ATTR(in_recovery, "%d\n", atomic_test_mask + (ZFCP_STATUS_COMMON_ERP_INUSE, &unit->status)); +ZFCP_DEFINE_UNIT_ATTR(access_denied, "%d\n", atomic_test_mask + (ZFCP_STATUS_COMMON_ACCESS_DENIED, &unit->status)); +ZFCP_DEFINE_UNIT_ATTR(access_shared, "%d\n", atomic_test_mask + (ZFCP_STATUS_UNIT_SHARED, &unit->status)); +ZFCP_DEFINE_UNIT_ATTR(access_readonly, "%d\n", atomic_test_mask + (ZFCP_STATUS_UNIT_READONLY, &unit->status)); + +/** + * zfcp_sysfs_unit_failed_store - failed state of unit + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * @count: number of bytes in buffer + * + * Store function of the "failed" attribute of a unit. + * If a "0" gets written to "failed", error recovery will be + * started for the belonging unit. + */ +static ssize_t +zfcp_sysfs_unit_failed_store(struct device *dev, const char *buf, size_t count) +{ + struct zfcp_unit *unit; + unsigned int val; + char *endp; + int retval = 0; + + down(&zfcp_data.config_sema); + unit = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_REMOVE, &unit->status)) { + retval = -EBUSY; + goto out; + } + + val = simple_strtoul(buf, &endp, 0); + if (((endp + 1) < (buf + count)) || (val != 0)) { + retval = -EINVAL; + goto out; + } + + zfcp_erp_modify_unit_status(unit, ZFCP_STATUS_COMMON_RUNNING, ZFCP_SET); + zfcp_erp_unit_reopen(unit, ZFCP_STATUS_COMMON_ERP_FAILED); + zfcp_erp_wait(unit->port->adapter); + out: + up(&zfcp_data.config_sema); + return retval ? retval : (ssize_t) count; +} + +/** + * zfcp_sysfs_unit_failed_show - failed state of unit + * @dev: pointer to belonging device + * @buf: pointer to input buffer + * + * Show function of "failed" attribute of unit. Will be + * "0" if unit is working, otherwise "1". + */ +static ssize_t +zfcp_sysfs_unit_failed_show(struct device *dev, char *buf) +{ + struct zfcp_unit *unit; + + unit = dev_get_drvdata(dev); + if (atomic_test_mask(ZFCP_STATUS_COMMON_ERP_FAILED, &unit->status)) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static DEVICE_ATTR(failed, S_IWUSR | S_IRUGO, zfcp_sysfs_unit_failed_show, + zfcp_sysfs_unit_failed_store); + +static struct attribute *zfcp_unit_attrs[] = { + &dev_attr_scsi_lun.attr, + &dev_attr_failed.attr, + &dev_attr_in_recovery.attr, + &dev_attr_status.attr, + &dev_attr_access_denied.attr, + &dev_attr_access_shared.attr, + &dev_attr_access_readonly.attr, + NULL +}; + +static struct attribute_group zfcp_unit_attr_group = { + .attrs = zfcp_unit_attrs, +}; + +/** + * zfcp_sysfs_create_unit_files - create sysfs unit files + * @dev: pointer to belonging device + * + * Create all attributes of the sysfs representation of a unit. + */ +int +zfcp_sysfs_unit_create_files(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &zfcp_unit_attr_group); +} + +/** + * zfcp_sysfs_remove_unit_files - remove sysfs unit files + * @dev: pointer to belonging device + * + * Remove all attributes of the sysfs representation of a unit. + */ +void +zfcp_sysfs_unit_remove_files(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &zfcp_unit_attr_group); +} + +#undef ZFCP_LOG_AREA |