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 |
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 00000000000..fc145307a7d --- /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 00000000000..6a43322ccb0 --- /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_des |