This patch contains Persistent Reservations implementation Signed-off-by: Alexey Obitotskiy <alexeyo1@xxxxxxxxxx> Signed-off-by: Vladislav Bolkhovitin <vst@xxxxxxxx> --- scst_pres.c | 2497 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ scst_pres.h | 159 +++ 2 files changed, 2656 insertions(+) diff -uprN orig/linux-2.6.35/drivers/scst/scst_pres.c linux-2.6.35/drivers/scst/scst_pres.c --- orig/linux-2.6.35/drivers/scst/scst_pres.c +++ linux-2.6.35/drivers/scst/scst_pres.c @@ -0,0 +1,2497 @@ +/* + * scst_pres.c + * + * Copyright (C) 2009 - 2010 Alexey Obitotskiy <alexeyo1@xxxxxxxxxx> + * Copyright (C) 2009 - 2010 Open-E, Inc. + * Copyright (C) 2009 - 2010 Vladislav Bolkhovitin <vst@xxxxxxxx> + * + * 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, version 2 + * of the License. + * + * 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. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/unistd.h> +#include <linux/string.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/ctype.h> +#include <asm/byteorder.h> +#include <linux/syscalls.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <linux/uaccess.h> +#include <linux/namei.h> +#include <linux/version.h> +#include <asm/unaligned.h> + +#include <scst/scst.h> +#include <scst/scst_const.h> +#include "scst_priv.h" +#include "scst_pres.h" + +#define SCST_PR_ROOT_ENTRY "pr" +#define SCST_PR_FILE_SIGN 0xBBEEEEAAEEBBDD77LLU +#define SCST_PR_FILE_VERSION 1LLU + +#define FILE_BUFFER_SIZE 512 + +#ifndef isblank +#define isblank(c) ((c) == ' ' || (c) == '\t') +#endif + +static inline int tid_size(const uint8_t *tid) +{ + BUG_ON(tid == NULL); + + if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) + return be16_to_cpu(get_unaligned((__be16 *)&tid[2])) + 4; + else + return TID_COMMON_SIZE; +} + +/* Secures tid by setting 0 in the last byte of NULL-terminated tid's */ +static inline void tid_secure(uint8_t *tid) +{ + if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) { + int size = tid_size(tid); + tid[size - 1] = '\0'; + } + + return; +} + +/* Returns false if tid's are not equal, true otherwise */ +static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) +{ + int len; + + if (tid_a == NULL || tid_b == NULL) + return false; + + if ((tid_a[0] & 0x0f) != (tid_b[0] & 0x0f)) { + TRACE_DBG("%s", "Different protocol IDs"); + return false; + } + + if ((tid_a[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) { + const uint8_t tid_a_fmt = tid_a[0] & 0xc0; + const uint8_t tid_b_fmt = tid_b[0] & 0xc0; + int tid_a_len, tid_a_max = tid_size(tid_a) - 4; + int tid_b_len, tid_b_max = tid_size(tid_b) - 4; + int i; + + tid_a += 4; + tid_b += 4; + + if (tid_a_fmt == 0x00) + tid_a_len = strnlen(tid_a, tid_a_max); + else if (tid_a_fmt == 0x40) { + if (tid_a_fmt != tid_b_fmt) { + uint8_t *p = strnchr(tid_a, tid_a_max, ','); + if (p == NULL) + goto out_error; + tid_a_len = p - tid_a; + + BUG_ON(tid_a_len > tid_a_max); + BUG_ON(tid_a_len < 0); + } else + tid_a_len = strnlen(tid_a, tid_a_max); + } else + goto out_error; + + if (tid_b_fmt == 0x00) + tid_b_len = strnlen(tid_b, tid_b_max); + else if (tid_b_fmt == 0x40) { + if (tid_a_fmt != tid_b_fmt) { + uint8_t *p = strnchr(tid_b, tid_b_max, ','); + if (p == NULL) + goto out_error; + tid_b_len = p - tid_b; + + BUG_ON(tid_b_len > tid_b_max); + BUG_ON(tid_b_len < 0); + } else + tid_b_len = strnlen(tid_b, tid_b_max); + } else + goto out_error; + + if (tid_a_len != tid_b_len) + return false; + + len = tid_a_len; + + /* ISCSI names are case insensitive */ + for (i = 0; i < len; i++) + if (tolower(tid_a[i]) != tolower(tid_b[i])) + return false; + return true; + } else + len = TID_COMMON_SIZE; + + return memcmp(tid_a, tid_b, len) == 0; + +out_error: + PRINT_ERROR("%s", "Invalid initiator port transport id"); + return false; +} + +/* Must be called under dev_pr_mutex */ +static inline void scst_pr_set_holder(struct scst_device *dev, + struct scst_dev_registrant *holder, uint8_t scope, uint8_t type) +{ + dev->pr_is_set = 1; + dev->pr_scope = scope; + dev->pr_type = type; + if (dev->pr_type != TYPE_EXCLUSIVE_ACCESS_ALL_REG && + dev->pr_type != TYPE_WRITE_EXCLUSIVE_ALL_REG) + dev->pr_holder = holder; +} + +/* Must be called under dev_pr_mutex */ +static bool scst_pr_is_holder(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ + bool res = false; + + if (!dev->pr_is_set) + goto out; + + if (dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG || + dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG) { + res = (reg != NULL); + } else + res = (dev->pr_holder == reg); + +out: + return res; +} + +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + +/* Must be called under dev_pr_mutex */ +void scst_pr_dump_prs(struct scst_device *dev, bool force) +{ + if (!force) { +#if defined(CONFIG_SCST_DEBUG) + if ((trace_flag & TRACE_PRES) == 0) +#endif + goto out; + } + + PRINT_INFO("Persistent reservations for device %s:", dev->virt_name); + + if (list_empty(&dev->dev_registrants_list)) + PRINT_INFO("%s", " No registrants"); + else { + struct scst_dev_registrant *reg; + int i = 0; + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + PRINT_INFO(" [%d] registrant %s/%d, key %016llx " + "(reg %p, tgt_dev %p)", i++, + debug_transport_id_to_initiator_name( + reg->transport_id), + reg->rel_tgt_id, reg->key, reg, reg->tgt_dev); + } + } + + if (dev->pr_is_set) { + struct scst_dev_registrant *holder = dev->pr_holder; + if (holder != NULL) + PRINT_INFO("Reservation holder is %s/%d (key %016llx, " + "scope %x, type %x, reg %p, tgt_dev %p)", + debug_transport_id_to_initiator_name( + holder->transport_id), + holder->rel_tgt_id, holder->key, dev->pr_scope, + dev->pr_type, holder, holder->tgt_dev); + else + PRINT_INFO("All registrants are reservation holders " + "(scope %x, type %x)", dev->pr_scope, + dev->pr_type); + } else + PRINT_INFO("%s", "Not reserved"); + +out: + return; +} + +#endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */ + +/* dev_pr_mutex must be locked */ +static void scst_pr_find_registrants_list_all(struct scst_device *dev, + struct scst_dev_registrant *exclude_reg, struct list_head *list) +{ + struct scst_dev_registrant *reg; + + TRACE_PR("Finding all registered records for device '%s' " + "with exclude reg key %016llx", + dev->virt_name, exclude_reg->key); + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (reg == exclude_reg) + continue; + TRACE_PR("Adding registrant %s/%d (%p) to find list (key %016llx)", + debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->key); + list_add_tail(®->aux_list_entry, list); + } + return; +} + +/* dev_pr_mutex must be locked */ +static void scst_pr_find_registrants_list_key(struct scst_device *dev, + __be64 key, struct list_head *list) +{ + struct scst_dev_registrant *reg; + + TRACE_PR("Finding registrants for device '%s' with key %016llx", + dev->virt_name, key); + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (reg->key == key) { + TRACE_PR("Adding registrant %s/%d (%p) to the find " + "list (key %016llx)", + debug_transport_id_to_initiator_name( + reg->transport_id), + reg->rel_tgt_id, reg->tgt_dev, key); + list_add_tail(®->aux_list_entry, list); + } + } + return; +} + +/* dev_pr_mutex must be locked */ +static struct scst_dev_registrant *scst_pr_find_reg( + struct scst_device *dev, const uint8_t *transport_id, + const uint16_t rel_tgt_id) +{ + struct scst_dev_registrant *reg, *res = NULL; + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if ((reg->rel_tgt_id == rel_tgt_id) && + tid_equal(reg->transport_id, transport_id)) { + res = reg; + break; + } + } + return res; +} + +/* Must be called under dev_pr_mutex */ +static void scst_pr_clear_reservation(struct scst_device *dev) +{ + + WARN_ON(!dev->pr_is_set); + + dev->pr_is_set = 0; + dev->pr_scope = SCOPE_LU; + dev->pr_type = TYPE_UNSPECIFIED; + + dev->pr_holder = NULL; + return; +} + +/* Must be called under dev_pr_mutex */ +static void scst_pr_clear_holder(struct scst_device *dev) +{ + + WARN_ON(!dev->pr_is_set); + + if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG || + dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) { + if (list_empty(&dev->dev_registrants_list)) + scst_pr_clear_reservation(dev); + } else + scst_pr_clear_reservation(dev); + + dev->pr_holder = NULL; + return; +} + +/* Must be called under dev_pr_mutex */ +static struct scst_dev_registrant *scst_pr_add_registrant( + struct scst_device *dev, const uint8_t *transport_id, + const uint16_t rel_tgt_id, __be64 key, + bool dev_lock_locked) +{ + struct scst_dev_registrant *reg; + struct scst_tgt_dev *t; + gfp_t gfp_flags = dev_lock_locked ? GFP_ATOMIC : GFP_KERNEL; + + BUG_ON(dev == NULL); + BUG_ON(transport_id == NULL); + + TRACE_PR("Registering %s/%d (dev %s)", + debug_transport_id_to_initiator_name(transport_id), + rel_tgt_id, dev->virt_name); + + reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id); + if (reg != NULL) { + /* + * It might happen when a target driver would make >1 session + * from the same initiator to the same target. + */ + PRINT_ERROR("Registrant %p/%d (dev %s) already exists!", reg, + rel_tgt_id, dev->virt_name); + PRINT_BUFFER("TransportID", transport_id, 24); + WARN_ON(1); + reg = NULL; + goto out; + } + + reg = kzalloc(sizeof(*reg), gfp_flags); + if (reg == NULL) { + PRINT_ERROR("%s", "Unable to allocate registration record"); + goto out; + } + + reg->transport_id = kmalloc(tid_size(transport_id), gfp_flags); + if (reg->transport_id == NULL) { + PRINT_ERROR("%s", "Unable to allocate initiator port " + "transport id"); + goto out_free; + } + memcpy(reg->transport_id, transport_id, tid_size(transport_id)); + + reg->rel_tgt_id = rel_tgt_id; + reg->key = key; + + /* + * We can't use scst_mutex here, because of the circular + * locking dependency with dev_pr_mutex. + */ + if (!dev_lock_locked) + spin_lock_bh(&dev->dev_lock); + list_for_each_entry(t, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { + if (tid_equal(t->sess->transport_id, transport_id) && + (t->sess->tgt->rel_tgt_id == rel_tgt_id) && + (t->registrant == NULL)) { + /* + * We must assign here, because t can die + * immediately after we release dev_lock. + */ + TRACE_PR("Found tgt_dev %p", t); + reg->tgt_dev = t; + t->registrant = reg; + break; + } + } + if (!dev_lock_locked) + spin_unlock_bh(&dev->dev_lock); + + list_add_tail(®->dev_registrants_list_entry, + &dev->dev_registrants_list); + + TRACE_PR("Reg %p registered (dev %s, tgt_dev %p)", reg, + dev->virt_name, reg->tgt_dev); + +out: + return reg; + +out_free: + kfree(reg); + reg = NULL; + goto out; +} + +/* Must be called under dev_pr_mutex */ +static void scst_pr_remove_registrant(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ + + TRACE_PR("Removing registrant %s/%d (reg %p, tgt_dev %p, key %016llx, " + "dev %s)", debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->tgt_dev, reg->key, dev->virt_name); + + list_del(®->dev_registrants_list_entry); + + if (scst_pr_is_holder(dev, reg)) + scst_pr_clear_holder(dev); + + if (reg->tgt_dev) + reg->tgt_dev->registrant = NULL; + + kfree(reg->transport_id); + kfree(reg); + return; +} + +/* Must be called under dev_pr_mutex */ +static void scst_pr_send_ua_reg(struct scst_device *dev, + struct scst_dev_registrant *reg, + int key, int asc, int ascq) +{ + static uint8_t ua[SCST_STANDARD_SENSE_LEN]; + + scst_set_sense(ua, sizeof(ua), dev->d_sense, key, asc, ascq); + + TRACE_PR("Queuing UA [%x %x %x]: registrant %s/%d (%p), tgt_dev %p, " + "key %016llx", ua[2], ua[12], ua[13], + debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->tgt_dev, reg->key); + + if (reg->tgt_dev) + scst_check_set_UA(reg->tgt_dev, ua, sizeof(ua), 0); + return; +} + +/* Must be called under dev_pr_mutex */ +static void scst_pr_send_ua_all(struct scst_device *dev, + struct scst_dev_registrant *exclude_reg, + int key, int asc, int ascq) +{ + struct scst_dev_registrant *reg; + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (reg != exclude_reg) + scst_pr_send_ua_reg(dev, reg, key, asc, ascq); + } + return; +} + +/* Must be called under dev_pr_mutex */ +static void scst_pr_abort_reg(struct scst_device *dev, + struct scst_cmd *pr_cmd, struct scst_dev_registrant *reg) +{ + struct scst_session *sess; + __be64 packed_lun; + int rc; + + if (reg->tgt_dev == NULL) { + TRACE_PR("Registrant %s/%d (%p, key 0x%016llx) has no session", + debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->key); + goto out; + } + + sess = reg->tgt_dev->sess; + + TRACE_PR("Aborting %d commands for %s/%d (reg %p, key 0x%016llx, " + "tgt_dev %p, sess %p)", + atomic_read(®->tgt_dev->tgt_dev_cmd_count), + debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->key, reg->tgt_dev, sess); + + packed_lun = scst_pack_lun(reg->tgt_dev->lun, sess->acg->addr_method); + + rc = scst_rx_mgmt_fn_lun(sess, SCST_PR_ABORT_ALL, + (uint8_t *)&packed_lun, sizeof(packed_lun), SCST_NON_ATOMIC, + pr_cmd); + if (rc != 0) { + /* + * There's nothing more we can do here... Hopefully, it would + * never happen. + */ + PRINT_ERROR("SCST_PR_ABORT_ALL failed %d (sess %p)", + rc, sess); + } + +out: + return; +} + +/* Abstract vfs_unlink & path_put for different kernel versions */ +static inline void scst_pr_vfs_unlink_and_put(struct nameidata *nd) +{ + vfs_unlink(nd->path.dentry->d_parent->d_inode, + nd->path.dentry); + path_put(&nd->path); +} + +static inline void scst_pr_path_put(struct nameidata *nd) +{ + path_put(&nd->path); +} + +/* Called under scst_mutex */ +static int scst_pr_do_load_device_file(struct scst_device *dev, + const char *file_name) +{ + int res = 0, rc; + struct file *file = NULL; + struct inode *inode; + char *buf = NULL; + loff_t file_size, pos, data_size; + uint64_t sign, version; + mm_segment_t old_fs; + uint8_t pr_is_set, aptpl; + __be64 key; + uint16_t rel_tgt_id; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + TRACE_PR("Loading persistent file '%s'", file_name); + + file = filp_open(file_name, O_RDONLY, 0); + if (IS_ERR(file)) { + res = PTR_ERR(file); + TRACE_PR("Unable to open file '%s' - error %d", file_name, res); + goto out; + } + + inode = file->f_dentry->d_inode; + + if (S_ISREG(inode->i_mode)) + /* Nothing to do */; + else if (S_ISBLK(inode->i_mode)) + inode = inode->i_bdev->bd_inode; + else { + PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); + goto out_close; + } + + file_size = inode->i_size; + + /* Let's limit the file size by some reasonable number */ + if ((file_size == 0) || (file_size >= 15*1024*1024)) { + PRINT_ERROR("Invalid PR file size %d", (int)file_size); + res = -EINVAL; + goto out_close; + } + + buf = vmalloc(file_size); + if (buf == NULL) { + res = -ENOMEM; + PRINT_ERROR("%s", "Unable to allocate buffer"); + goto out_close; + } + + pos = 0; + rc = vfs_read(file, (void __force __user *)buf, file_size, &pos); + if (rc != file_size) { + PRINT_ERROR("Unable to read file '%s' - error %d", file_name, + rc); + res = rc; + goto out_close; + } + + data_size = 0; + data_size += sizeof(sign); + data_size += sizeof(version); + data_size += sizeof(aptpl); + data_size += sizeof(pr_is_set); + data_size += sizeof(dev->pr_type); + data_size += sizeof(dev->pr_scope); + + if (file_size < data_size) { + res = -EINVAL; + PRINT_ERROR("Invalid file '%s' - size too small", file_name); + goto out_close; + } + + pos = 0; + + sign = get_unaligned((uint64_t *)&buf[pos]); + if (sign != SCST_PR_FILE_SIGN) { + res = -EINVAL; + PRINT_ERROR("Invalid persistent file signature %016llx " + "(expected %016llx)", sign, SCST_PR_FILE_SIGN); + goto out_close; + } + pos += sizeof(sign); + + version = get_unaligned((uint64_t *)&buf[pos]); + if (version != SCST_PR_FILE_VERSION) { + res = -EINVAL; + PRINT_ERROR("Invalid persistent file version %016llx " + "(expected %016llx)", version, SCST_PR_FILE_VERSION); + goto out_close; + } + pos += sizeof(version); + + while (data_size < file_size) { + uint8_t *tid; + + data_size++; + tid = &buf[data_size]; + data_size += tid_size(tid); + data_size += sizeof(key); + data_size += sizeof(rel_tgt_id); + + if (data_size > file_size) { + res = -EINVAL; + PRINT_ERROR("Invalid file '%s' - size mismatch have " + "%lld expected %lld", file_name, file_size, + data_size); + goto out_close; + } + } + + aptpl = buf[pos]; + dev->pr_aptpl = aptpl ? 1 : 0; + pos += sizeof(aptpl); + + pr_is_set = buf[pos]; + dev->pr_is_set = pr_is_set ? 1 : 0; + pos += sizeof(pr_is_set); + + dev->pr_type = buf[pos]; + pos += sizeof(dev->pr_type); + + dev->pr_scope = buf[pos]; + pos += sizeof(dev->pr_scope); + + while (pos < file_size) { + uint8_t is_holder; + uint8_t *tid; + struct scst_dev_registrant *reg = NULL; + + is_holder = buf[pos++]; + + tid = &buf[pos]; + pos += tid_size(tid); + + key = get_unaligned((__be64 *)&buf[pos]); + pos += sizeof(key); + + rel_tgt_id = get_unaligned((uint16_t *)&buf[pos]); + pos += sizeof(rel_tgt_id); + + reg = scst_pr_add_registrant(dev, tid, rel_tgt_id, key, false); + if (reg == NULL) { + res = -ENOMEM; + goto out_close; + } + + if (is_holder) + dev->pr_holder = reg; + } + +out_close: + filp_close(file, NULL); + +out: + if (buf != NULL) + vfree(buf); + + set_fs(old_fs); + return res; +} + +static int scst_pr_load_device_file(struct scst_device *dev) +{ + int res; + + if (dev->pr_file_name == NULL || dev->pr_file_name1 == NULL) { + PRINT_ERROR("Invalid file paths for '%s'", dev->virt_name); + res = -EINVAL; + goto out; + } + + res = scst_pr_do_load_device_file(dev, dev->pr_file_name); + if (res == 0) + goto out; + else if (res == -ENOMEM) + goto out; + + res = scst_pr_do_load_device_file(dev, dev->pr_file_name1); + + scst_pr_dump_prs(dev, false); + +out: + return res; +} + +static int scst_pr_copy_file(const char *src, const char *dest) +{ + int res = 0; + struct inode *inode; + loff_t file_size, pos; + uint8_t *buf = NULL; + struct file *file_src = NULL, *file_dest = NULL; + mm_segment_t old_fs = get_fs(); + + if (src == NULL || dest == NULL) { + res = -EINVAL; + PRINT_ERROR("%s", "Invalid persistent files path - backup " + "skipped"); + goto out; + } + + TRACE_PR("Copying '%s' into '%s'", src, dest); + + set_fs(KERNEL_DS); + + file_src = filp_open(src, O_RDONLY, 0); + if (IS_ERR(file_src)) { + res = PTR_ERR(file_src); + TRACE_PR("Unable to open file '%s' - error %d", src, + res); + goto out_free; + } + + file_dest = filp_open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (IS_ERR(file_dest)) { + res = PTR_ERR(file_dest); + TRACE_PR("Unable to open backup file '%s' - error %d", dest, + res); + goto out_close; + } + + inode = file_src->f_dentry->d_inode; + + if (S_ISREG(inode->i_mode)) + /* Nothing to do */; + else if (S_ISBLK(inode->i_mode)) + inode = inode->i_bdev->bd_inode; + else { + PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); + res = -EINVAL; + set_fs(old_fs); + goto out_skip; + } + + file_size = inode->i_size; + + buf = vmalloc(file_size); + if (buf == NULL) { + res = -ENOMEM; + PRINT_ERROR("%s", "Unable to allocate temporary buffer"); + goto out_skip; + } + + pos = 0; + res = vfs_read(file_src, (void __force __user *)buf, file_size, &pos); + if (res != file_size) { + PRINT_ERROR("Unable to read file '%s' - error %d", src, res); + goto out_skip; + } + + pos = 0; + res = vfs_write(file_dest, (void __force __user *)buf, file_size, &pos); + if (res != file_size) { + PRINT_ERROR("Unable to write to '%s' - error %d", dest, res); + goto out_skip; + } + + res = vfs_fsync(file_dest, 0); + if (res != 0) { + PRINT_ERROR("fsync() of the backup PR file failed: %d", res); + goto out_skip; + } + +out_skip: + filp_close(file_dest, NULL); + +out_close: + filp_close(file_src, NULL); + +out_free: + if (buf != NULL) + vfree(buf); + + set_fs(old_fs); + +out: + return res; +} + +static void scst_pr_remove_device_files(struct scst_tgt_dev *tgt_dev) +{ + int res = 0; + struct scst_device *dev = tgt_dev->dev; + struct nameidata nd; + mm_segment_t old_fs = get_fs(); + + set_fs(KERNEL_DS); + + res = path_lookup(dev->pr_file_name, 0, &nd); + if (!res) + scst_pr_vfs_unlink_and_put(&nd); + else + TRACE_DBG("Unable to lookup file '%s' - error %d", + dev->pr_file_name, res); + + res = path_lookup(dev->pr_file_name1, 0, &nd); + if (!res) + scst_pr_vfs_unlink_and_put(&nd); + else + TRACE_DBG("Unable to lookup file '%s' - error %d", + dev->pr_file_name1, res); + + set_fs(old_fs); + return; +} + +/* Must be called under dev_pr_mutex */ +void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd) +{ + int res = 0; + struct scst_device *dev = tgt_dev->dev; + struct file *file; + mm_segment_t old_fs = get_fs(); + loff_t pos = 0; + uint64_t sign; + uint64_t version; + uint8_t pr_is_set, aptpl; + + if ((dev->pr_aptpl == 0) || list_empty(&dev->dev_registrants_list)) { + scst_pr_remove_device_files(tgt_dev); + goto out; + } + + scst_pr_copy_file(dev->pr_file_name, dev->pr_file_name1); + + set_fs(KERNEL_DS); + + file = filp_open(dev->pr_file_name, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (IS_ERR(file)) { + res = PTR_ERR(file); + PRINT_ERROR("Unable to (re)create PR file '%s' - error %d", + dev->pr_file_name, res); + goto out_set_fs; + } + + TRACE_PR("Updating pr file '%s'", dev->pr_file_name); + + /* + * signature + */ + sign = 0; + pos = 0; + res = vfs_write(file, (void __force __user *)&sign, sizeof(sign), &pos); + if (res != sizeof(sign)) + goto write_error; + + /* + * version + */ + version = SCST_PR_FILE_VERSION; + res = vfs_write(file, (void __force __user *)&version, sizeof(version), &pos); + if (res != sizeof(version)) + goto write_error; + + /* + * APTPL + */ + aptpl = dev->pr_aptpl; + res = vfs_write(file, (void __force __user *)&aptpl, sizeof(aptpl), &pos); + if (res != sizeof(aptpl)) + goto write_error; + + /* + * reservation + */ + pr_is_set = dev->pr_is_set; + res = vfs_write(file, (void __force __user *)&pr_is_set, sizeof(pr_is_set), &pos); + if (res != sizeof(pr_is_set)) + goto write_error; + + res = vfs_write(file, (void __force __user *)&dev->pr_type, sizeof(dev->pr_type), &pos); + if (res != sizeof(dev->pr_type)) + goto write_error; + + res = vfs_write(file, (void __force __user *)&dev->pr_scope, sizeof(dev->pr_scope), &pos); + if (res != sizeof(dev->pr_scope)) + goto write_error; + + /* + * registration records + */ + if (!list_empty(&dev->dev_registrants_list)) { + struct scst_dev_registrant *reg; + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + uint8_t is_holder = 0; + int size; + + is_holder = (dev->pr_holder == reg); + + res = vfs_write(file, (void __force __user *)&is_holder, sizeof(is_holder), + &pos); + if (res != sizeof(is_holder)) + goto write_error; + + size = tid_size(reg->transport_id); + res = vfs_write(file, (void __force __user *)reg->transport_id, size, &pos); + if (res != size) + goto write_error; + + res = vfs_write(file, (void __force __user *)®->key, + sizeof(reg->key), &pos); + if (res != sizeof(reg->key)) + goto write_error; + + res = vfs_write(file, (void __force __user *)®->rel_tgt_id, + sizeof(reg->rel_tgt_id), &pos); + if (res != sizeof(reg->rel_tgt_id)) + goto write_error; + } + } + + res = vfs_fsync(file, 0); + if (res != 0) { + PRINT_ERROR("fsync() of the PR file failed: %d", res); + goto write_error_close; + } + + sign = SCST_PR_FILE_SIGN; + pos = 0; + res = vfs_write(file, (void __force __user *)&sign, sizeof(sign), &pos); + if (res != sizeof(sign)) + goto write_error; + + res = vfs_fsync(file, 0); + if (res != 0) { + PRINT_ERROR("fsync() of the PR file failed: %d", res); + goto write_error_close; + } + + res = 0; + + filp_close(file, NULL); + +out_set_fs: + set_fs(old_fs); + +out: + if (res != 0) { + PRINT_CRIT_ERROR("Unable to save persistent information " + "(target %s, initiator %s, device %s)", + tgt_dev->sess->tgt->tgt_name, + tgt_dev->sess->initiator_name, dev->virt_name); +#if 0 /* + * Looks like it's safer to return SUCCESS and expect operator's + * intervention to be able to save the PR's state next time, than + * to return HARDWARE ERROR and screw up all the interaction with + * the affected initiator. + */ + if (cmd != NULL) + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); +#endif + } + return; + +write_error: + PRINT_ERROR("Error writing to '%s' - error %d", dev->pr_file_name, res); + +write_error_close: + filp_close(file, NULL); + { + struct nameidata nd; + int rc; + + rc = path_lookup(dev->pr_file_name, 0, &nd); + if (!rc) + scst_pr_vfs_unlink_and_put(&nd); + else + TRACE_PR("Unable to lookup '%s' - error %d", + dev->pr_file_name, rc); + } + goto out_set_fs; +} + +int scst_pr_check_pr_path(void) +{ + int res; + struct nameidata nd; + mm_segment_t old_fs = get_fs(); + + set_fs(KERNEL_DS); + + res = path_lookup(SCST_PR_DIR, 0, &nd); + if (res != 0) { + PRINT_ERROR("Unable to find %s (err %d), you should create " + "this directory manually or reinstall SCST", + SCST_PR_DIR, res); + goto out_setfs; + } + + scst_pr_path_put(&nd); + +out_setfs: + set_fs(old_fs); + return res; +} + +/* Called under scst_mutex */ +int scst_pr_init_dev(struct scst_device *dev) +{ + int res = 0; + uint8_t q; + int name_len; + + name_len = snprintf(&q, sizeof(q), "%s/%s", SCST_PR_DIR, dev->virt_name) + 1; + dev->pr_file_name = kmalloc(name_len, GFP_KERNEL); + if (dev->pr_file_name == NULL) { + PRINT_ERROR("Allocation of device '%s' file path failed", + dev->virt_name); + res = -ENOMEM; + goto out; + } else + snprintf(dev->pr_file_name, name_len, "%s/%s", SCST_PR_DIR, + dev->virt_name); + + name_len = snprintf(&q, sizeof(q), "%s/%s.1", SCST_PR_DIR, dev->virt_name) + 1; + dev->pr_file_name1 = kmalloc(name_len, GFP_KERNEL); + if (dev->pr_file_name1 == NULL) { + PRINT_ERROR("Allocation of device '%s' backup file path failed", + dev->virt_name); + res = -ENOMEM; + goto out_free_name; + } else + snprintf(dev->pr_file_name1, name_len, "%s/%s.1", SCST_PR_DIR, + dev->virt_name); + + res = scst_pr_load_device_file(dev); + if (res == -ENOENT) + res = 0; + + if (res != 0) + goto out_free_name1; + +out: + return res; + +out_free_name1: + kfree(dev->pr_file_name1); + dev->pr_file_name1 = NULL; + +out_free_name: + kfree(dev->pr_file_name); + dev->pr_file_name = NULL; + goto out; +} + +/* Called under scst_mutex */ +void scst_pr_clear_dev(struct scst_device *dev) +{ + struct scst_dev_registrant *reg, *tmp_reg; + + list_for_each_entry_safe(reg, tmp_reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + scst_pr_remove_registrant(dev, reg); + } + + kfree(dev->pr_file_name); + kfree(dev->pr_file_name1); + return; +} + +/* Called under scst_mutex */ +int scst_pr_init_tgt_dev(struct scst_tgt_dev *tgt_dev) +{ + int res = 0; + struct scst_dev_registrant *reg; + struct scst_device *dev = tgt_dev->dev; + const uint8_t *transport_id = tgt_dev->sess->transport_id; + const uint16_t rel_tgt_id = tgt_dev->sess->tgt->rel_tgt_id; + + if (tgt_dev->sess->transport_id == NULL) + goto out; + + scst_pr_write_lock(dev); + + reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id); + if ((reg != NULL) && (reg->tgt_dev == NULL)) { + TRACE_PR("Assigning reg %s/%d (%p) to tgt_dev %p (dev %s)", + debug_transport_id_to_initiator_name(transport_id), + rel_tgt_id, reg, tgt_dev, dev->virt_name); + tgt_dev->registrant = reg; + reg->tgt_dev = tgt_dev; + } + + scst_pr_write_unlock(dev); + +out: + return res; +} + +/* Called under scst_mutex */ +void scst_pr_clear_tgt_dev(struct scst_tgt_dev *tgt_dev) +{ + + if (tgt_dev->registrant != NULL) { + struct scst_dev_registrant *reg = tgt_dev->registrant; + struct scst_device *dev = tgt_dev->dev; + struct scst_tgt_dev *t; + + scst_pr_write_lock(dev); + + tgt_dev->registrant = NULL; + reg->tgt_dev = NULL; + + /* Just in case, actually. It should never happen. */ + list_for_each_entry(t, &dev->dev_tgt_dev_list, + dev_tgt_dev_list_entry) { + if (t == tgt_dev) + continue; + if ((t->sess->tgt->rel_tgt_id == reg->rel_tgt_id) && + tid_equal(t->sess->transport_id, reg->transport_id)) { + TRACE_PR("Reassigning reg %s/%d (%p) to tgt_dev " + "%p (being cleared tgt_dev %p)", + debug_transport_id_to_initiator_name( + reg->transport_id), + reg->rel_tgt_id, reg, t, tgt_dev); + t->registrant = reg; + reg->tgt_dev = t; + break; + } + } + + scst_pr_write_unlock(dev); + } + return; +} + +/* Called with dev_pr_mutex locked. Might also be called under scst_mutex2. */ +static int scst_pr_register_with_spec_i_pt(struct scst_cmd *cmd, + const uint16_t rel_tgt_id, uint8_t *buffer, int buffer_size, + struct list_head *rollback_list) +{ + int res = 0; + int offset, ext_size; + __be64 action_key; + struct scst_device *dev = cmd->dev; + struct scst_dev_registrant *reg; + uint8_t *transport_id; + + action_key = get_unaligned((__be64 *)&buffer[8]); + + ext_size = be32_to_cpu(get_unaligned((__be32 *)&buffer[24])); + if ((ext_size + 28) > buffer_size) { + TRACE_PR("Invalid buffer size %d (max %d)", buffer_size, + ext_size + 28); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + res = -EINVAL; + goto out; + } + + offset = 0; + while (offset < ext_size) { + transport_id = &buffer[28 + offset]; + + if ((offset + tid_size(transport_id)) > ext_size) { + TRACE_PR("Invalid transport_id size %d (max %d)", + tid_size(transport_id), ext_size - offset); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list)); + res = -EINVAL; + goto out; + } + tid_secure(transport_id); + offset += tid_size(transport_id); + } + + offset = 0; + while (offset < ext_size) { + struct scst_tgt_dev *t; + + transport_id = &buffer[28 + offset]; + + TRACE_PR("rel_tgt_id %d, transport_id %s", rel_tgt_id, + debug_transport_id_to_initiator_name(transport_id)); + + if ((transport_id[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI && + (transport_id[0] & 0xc0) == 0) { + TRACE_PR("Wildcard iSCSI TransportID %s", + &transport_id[4]); + /* + * We can't use scst_mutex here, because of the + * circular locking dependency with dev_pr_mutex. + */ + spin_lock_bh(&dev->dev_lock); + list_for_each_entry(t, &dev->dev_tgt_dev_list, + dev_tgt_dev_list_entry) { + /* + * We must go over all matching tgt_devs and + * register them on the requested rel_tgt_id + */ + if (!tid_equal(t->sess->transport_id, + transport_id)) + continue; + + reg = scst_pr_find_reg(dev, + t->sess->transport_id, rel_tgt_id); + if (reg == NULL) { + reg = scst_pr_add_registrant(dev, + t->sess->transport_id, + rel_tgt_id, action_key, true); + if (reg == NULL) { + spin_unlock_bh(&dev->dev_lock); + scst_set_busy(cmd); + res = -ENOMEM; + goto out; + } + } else if (reg->key != action_key) { + TRACE_PR("Changing key of reg %p " + "(tgt_dev %p)", reg, t); + reg->rollback_key = reg->key; + reg->key = action_key; + } else + continue; + + list_add_tail(®->aux_list_entry, + rollback_list); + } + spin_unlock_bh(&dev->dev_lock); + } else { + reg = scst_pr_find_reg(dev, transport_id, rel_tgt_id); + if (reg != NULL) { + if (reg->key == action_key) + goto next; + TRACE_PR("Changing key of reg %p (tgt_dev %p)", + reg, reg->tgt_dev); + reg->rollback_key = reg->key; + reg->key = action_key; + } else { + reg = scst_pr_add_registrant(dev, transport_id, + rel_tgt_id, action_key, false); + if (reg == NULL) { + scst_set_busy(cmd); + res = -ENOMEM; + goto out; + } + } + + list_add_tail(®->aux_list_entry, + rollback_list); + } +next: + offset += tid_size(transport_id); + } +out: + return res; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +static void scst_pr_unregister(struct scst_device *dev, + struct scst_dev_registrant *reg) +{ + bool is_holder; + uint8_t pr_type; + + TRACE_PR("Unregistering key %0llx", reg->key); + + is_holder = scst_pr_is_holder(dev, reg); + pr_type = dev->pr_type; + + scst_pr_remove_registrant(dev, reg); + + if (is_holder && !dev->pr_is_set) { + /* A registration just released */ + switch (pr_type) { + case TYPE_WRITE_EXCLUSIVE_REGONLY: + case TYPE_EXCLUSIVE_ACCESS_REGONLY: + scst_pr_send_ua_all(dev, NULL, + SCST_LOAD_SENSE(scst_sense_reservation_released)); + break; + } + } + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +static void scst_pr_unregister_all_tg_pt(struct scst_device *dev, + const uint8_t *transport_id) +{ + struct scst_tgt_template *tgtt; + uint8_t proto_id = transport_id[0] & 0x0f; + + /* + * We can't use scst_mutex here, because of the circular locking + * dependency with dev_pr_mutex. + */ + mutex_lock(&scst_mutex2); + + list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { + struct scst_tgt *tgt; + + if (tgtt->get_initiator_port_transport_id == NULL) + continue; + + if (tgtt->get_initiator_port_transport_id(NULL, NULL) != proto_id) + continue; + + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { + struct scst_dev_registrant *reg; + + reg = scst_pr_find_reg(dev, transport_id, + tgt->rel_tgt_id); + if (reg == NULL) + continue; + + scst_pr_unregister(dev, reg); + } + } + + mutex_unlock(&scst_mutex2); + return; +} + +/* Called with dev_pr_mutex locked. Might also be called under scst_mutex2. */ +static int scst_pr_register_on_tgt_id(struct scst_cmd *cmd, + const uint16_t rel_tgt_id, uint8_t *buffer, int buffer_size, + bool spec_i_pt, struct list_head *rollback_list) +{ + int res; + + TRACE_PR("rel_tgt_id %d, spec_i_pt %d", rel_tgt_id, spec_i_pt); + + if (spec_i_pt) { + res = scst_pr_register_with_spec_i_pt(cmd, rel_tgt_id, buffer, + buffer_size, rollback_list); + if (res != 0) + goto out; + } + + /* tgt_dev can be among TIDs for scst_pr_register_with_spec_i_pt() */ + + if (scst_pr_find_reg(cmd->dev, cmd->sess->transport_id, rel_tgt_id) == NULL) { + __be64 action_key; + struct scst_dev_registrant *reg; + + action_key = get_unaligned((__be64 *)&buffer[8]); + + reg = scst_pr_add_registrant(cmd->dev, cmd->sess->transport_id, + rel_tgt_id, action_key, false); + if (reg == NULL) { + res = -ENOMEM; + scst_set_busy(cmd); + goto out; + } + + list_add_tail(®->aux_list_entry, rollback_list); + } + +out: + return res; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +static int scst_pr_register_all_tg_pt(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size, bool spec_i_pt, struct list_head *rollback_list) +{ + int res = 0; + struct scst_tgt_template *tgtt; + uint8_t proto_id = cmd->sess->transport_id[0] & 0x0f; + + /* + * We can't use scst_mutex here, because of the circular locking + * dependency with dev_pr_mutex. + */ + mutex_lock(&scst_mutex2); + + list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { + struct scst_tgt *tgt; + + if (tgtt->get_initiator_port_transport_id == NULL) + continue; + + if (tgtt->get_initiator_port_transport_id(NULL, NULL) != proto_id) + continue; + + TRACE_PR("tgtt %s, spec_i_pt %d", tgtt->name, spec_i_pt); + + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { + if (tgt->rel_tgt_id == 0) + continue; + TRACE_PR("tgt %s, rel_tgt_id %d", tgt->tgt_name, + tgt->rel_tgt_id); + res = scst_pr_register_on_tgt_id(cmd, tgt->rel_tgt_id, + buffer, buffer_size, spec_i_pt, rollback_list); + if (res != 0) + goto out_unlock; + } + } + +out_unlock: + mutex_unlock(&scst_mutex2); + return res; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +static int __scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size, bool spec_i_pt, bool all_tg_pt) +{ + int res; + struct scst_dev_registrant *reg, *treg; + LIST_HEAD(rollback_list); + + if (all_tg_pt) { + res = scst_pr_register_all_tg_pt(cmd, buffer, buffer_size, + spec_i_pt, &rollback_list); + if (res != 0) + goto out_rollback; + } else { + res = scst_pr_register_on_tgt_id(cmd, + cmd->sess->tgt->rel_tgt_id, buffer, buffer_size, + spec_i_pt, &rollback_list); + if (res != 0) + goto out_rollback; + } + + list_for_each_entry(reg, &rollback_list, aux_list_entry) { + reg->rollback_key = 0; + } + +out: + return res; + +out_rollback: + list_for_each_entry_safe(reg, treg, &rollback_list, aux_list_entry) { + list_del(®->aux_list_entry); + if (reg->rollback_key == 0) + scst_pr_remove_registrant(cmd->dev, reg); + else { + reg->key = reg->rollback_key; + reg->rollback_key = 0; + } + } + goto out; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + int aptpl, spec_i_pt, all_tg_pt; + __be64 key, action_key; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_session *sess = cmd->sess; + struct scst_dev_registrant *reg; + + aptpl = buffer[20] & 0x01; + spec_i_pt = (buffer[20] >> 3) & 0x01; + all_tg_pt = (buffer[20] >> 2) & 0x01; + key = get_unaligned((__be64 *)&buffer[0]); + action_key = get_unaligned((__be64 *)&buffer[8]); + + if (spec_i_pt == 0 && buffer_size != 24) { + TRACE_PR("Invalid buffer size %d", buffer_size); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out; + } + + reg = tgt_dev->registrant; + + TRACE_PR("Register: initiator %s/%d (%p), key %0llx, action_key %0llx " + "(tgt_dev %p)", + debug_transport_id_to_initiator_name(sess->transport_id), + sess->tgt->rel_tgt_id, reg, key, action_key, tgt_dev); + + if (reg == NULL) { + TRACE_PR("tgt_dev %p is not registered yet - registering", + tgt_dev); + if (key) { + TRACE_PR("%s", "Key must be zero on new registration"); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + if (action_key) { + int rc = __scst_pr_register(cmd, buffer, buffer_size, + spec_i_pt, all_tg_pt); + if (rc != 0) + goto out; + } else + TRACE_PR("%s", "Doing nothing - action_key is zero"); + } else { + if (reg->key != key) { + TRACE_PR("tgt_dev %p already registered - reservation " + "key %0llx mismatch", tgt_dev, reg->key); + scst_set_cmd_error_status(cmd, + SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + if (spec_i_pt) { + TRACE_PR("%s", "spec_i_pt must be zero in this case"); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_invalid_field_in_cdb)); + goto out; + } + if (action_key == 0) { + if (all_tg_pt) + scst_pr_unregister_all_tg_pt(dev, + sess->transport_id); + else + scst_pr_unregister(dev, reg); + } else + reg->key = action_key; + } + + dev->pr_generation++; + + dev->pr_aptpl = aptpl; + + scst_pr_dump_prs(dev, false); + +out: + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_register_and_ignore(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size) +{ + int aptpl, all_tg_pt; + __be64 action_key; + struct scst_dev_registrant *reg = NULL; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_session *sess = cmd->sess; + + aptpl = buffer[20] & 0x01; + all_tg_pt = (buffer[20] >> 2) & 0x01; + action_key = get_unaligned((__be64 *)&buffer[8]); + + if (buffer_size != 24) { + TRACE_PR("Invalid buffer size %d", buffer_size); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out; + } + + reg = tgt_dev->registrant; + + TRACE_PR("Register and ignore: initiator %s/%d (%p), action_key " + "%016llx (tgt_dev %p)", + debug_transport_id_to_initiator_name(sess->transport_id), + sess->tgt->rel_tgt_id, reg, action_key, tgt_dev); + + if (reg == NULL) { + TRACE_PR("Tgt_dev %p is not registered yet - trying to " + "register", tgt_dev); + if (action_key) { + int rc = __scst_pr_register(cmd, buffer, buffer_size, + false, all_tg_pt); + if (rc != 0) + goto out; + } else + TRACE_PR("%s", "Doing nothing, action_key is zero"); + } else { + if (action_key == 0) { + if (all_tg_pt) + scst_pr_unregister_all_tg_pt(dev, + sess->transport_id); + else + scst_pr_unregister(dev, reg); + } else + reg->key = action_key; + } + + dev->pr_generation++; + + dev->pr_aptpl = aptpl; + + scst_pr_dump_prs(dev, false); + +out: + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_register_and_move(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size) +{ + int aptpl; + int unreg; + int tid_buffer_size; + __be64 key, action_key; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_session *sess = cmd->sess; + struct scst_dev_registrant *reg, *reg_move; + const uint8_t *transport_id = NULL; + uint8_t *transport_id_move = NULL; + uint16_t rel_tgt_id_move; + + aptpl = buffer[17] & 0x01; + key = get_unaligned((__be64 *)&buffer[0]); + action_key = get_unaligned((__be64 *)&buffer[8]); + unreg = (buffer[17] >> 1) & 0x01; + tid_buffer_size = be32_to_cpu(get_unaligned((__be32 *)&buffer[20])); + + if ((tid_buffer_size + 24) > buffer_size) { + TRACE_PR("Invalid buffer size %d (%d)", + buffer_size, tid_buffer_size + 24); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list)); + goto out; + } + + if (tid_buffer_size < 24) { + TRACE_PR("%s", "Transport id buffer too small"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list)); + goto out; + } + + reg = tgt_dev->registrant; + /* We already checked reg is not NULL */ + if (reg->key != key) { + TRACE_PR("Registrant's %s/%d (%p) key %016llx mismatch with " + "%016llx (tgt_dev %p)", + debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->key, key, tgt_dev); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + if (!dev->pr_is_set) { + TRACE_PR("%s", "There must be a PR"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + /* + * This check also required by table "PERSISTENT RESERVE OUT service + * actions that are allowed in the presence of various reservations". + */ + if (!scst_pr_is_holder(dev, reg)) { + TRACE_PR("Registrant %s/%d (%p) is not a holder (tgt_dev %p)", + debug_transport_id_to_initiator_name( + reg->transport_id), reg->rel_tgt_id, + reg, tgt_dev); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + if (action_key == 0) { + TRACE_PR("%s", "Action key must be non-zero"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + transport_id = sess->transport_id; + transport_id_move = (uint8_t *)&buffer[24]; + rel_tgt_id_move = be16_to_cpu(get_unaligned((__be16 *)&buffer[18])); + + if ((tid_size(transport_id_move) + 24) > buffer_size) { + TRACE_PR("Invalid buffer size %d (%d)", + buffer_size, tid_size(transport_id_move) + 24); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list)); + goto out; + } + + tid_secure(transport_id_move); + + if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG || + dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) { + TRACE_PR("Unable to finish operation due to wrong reservation " + "type %02x", dev->pr_type); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + if (tid_equal(transport_id, transport_id_move)) { + TRACE_PR("%s", "Equal transport id's"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list)); + goto out; + } + + reg_move = scst_pr_find_reg(dev, transport_id_move, rel_tgt_id_move); + if (reg_move == NULL) { + reg_move = scst_pr_add_registrant(dev, transport_id_move, + rel_tgt_id_move, action_key, false); + if (reg_move == NULL) { + scst_set_busy(cmd); + goto out; + } + } else if (reg_move->key != action_key) { + TRACE_PR("Changing key for reg %p", reg); + reg_move->key = action_key; + } + + TRACE_PR("Register and move: from initiator %s/%d (%p, tgt_dev %p) to " + "initiator %s/%d (%p, tgt_dev %p), key %016llx (unreg %d)", + debug_transport_id_to_initiator_name(reg->transport_id), + reg->rel_tgt_id, reg, reg->tgt_dev, + debug_transport_id_to_initiator_name(transport_id_move), + rel_tgt_id_move, reg_move, reg_move->tgt_dev, action_key, + unreg); + + /* Move the holder */ + scst_pr_set_holder(dev, reg_move, dev->pr_scope, dev->pr_type); + + if (unreg) + scst_pr_remove_registrant(dev, reg); + + dev->pr_generation++; + + dev->pr_aptpl = aptpl; + + scst_pr_dump_prs(dev, false); + +out: + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_reserve(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + uint8_t scope, type; + __be64 key; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_dev_registrant *reg; + + key = get_unaligned((__be64 *)&buffer[0]); + scope = (cmd->cdb[2] & 0x0f) >> 4; + type = cmd->cdb[2] & 0x0f; + + if (buffer_size != 24) { + TRACE_PR("Invalid buffer size %d", buffer_size); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out; + } + + if (!scst_pr_type_valid(type)) { + TRACE_PR("Invalid reservation type %d", type); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + if (((cmd->cdb[2] & 0x0f) >> 4) != SCOPE_LU) { + TRACE_PR("Invalid reservation scope %d", scope); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + reg = tgt_dev->registrant; + + TRACE_PR("Reserve: initiator %s/%d (%p), key %016llx, scope %d, " + "type %d (tgt_dev %p)", + debug_transport_id_to_initiator_name(cmd->sess->transport_id), + cmd->sess->tgt->rel_tgt_id, reg, key, scope, type, tgt_dev); + + /* We already checked reg is not NULL */ + if (reg->key != key) { + TRACE_PR("Registrant's %p key %016llx mismatch with %016llx", + reg, reg->key, key); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + if (!dev->pr_is_set) + scst_pr_set_holder(dev, reg, scope, type); + else { + if (!scst_pr_is_holder(dev, reg)) { + /* + * This check also required by table "PERSISTENT + * RESERVE OUT service actions that are allowed in the + * presence of various reservations". + */ + TRACE_PR("Only holder can override - reg %p is not a " + "holder", reg); + scst_set_cmd_error_status(cmd, + SAM_STAT_RESERVATION_CONFLICT); + goto out; + } else { + if (dev->pr_scope != scope || dev->pr_type != type) { + TRACE_PR("Error overriding scope or type for " + "reg %p", reg); + scst_set_cmd_error_status(cmd, + SAM_STAT_RESERVATION_CONFLICT); + goto out; + } else + TRACE_PR("Do nothing: reservation of reg %p " + "is the same", reg); + } + } + + scst_pr_dump_prs(dev, false); + +out: + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_release(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + int scope, type; + __be64 key; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_dev_registrant *reg; + uint8_t cur_pr_type; + + key = get_unaligned((__be64 *)&buffer[0]); + scope = (cmd->cdb[2] & 0x0f) >> 4; + type = cmd->cdb[2] & 0x0f; + + if (buffer_size != 24) { + TRACE_PR("Invalid buffer size %d", buffer_size); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out; + } + + if (!dev->pr_is_set) { + TRACE_PR("%s", "There is no PR - do nothing"); + goto out; + } + + reg = tgt_dev->registrant; + + TRACE_PR("Release: initiator %s/%d (%p), key %016llx, scope %d, type " + "%d (tgt_dev %p)", debug_transport_id_to_initiator_name( + cmd->sess->transport_id), + cmd->sess->tgt->rel_tgt_id, reg, key, scope, type, tgt_dev); + + /* We already checked reg is not NULL */ + if (reg->key != key) { + TRACE_PR("Registrant's %p key %016llx mismatch with %016llx", + reg, reg->key, key); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + if (!scst_pr_is_holder(dev, reg)) { + TRACE_PR("Registrant %p is not a holder - do nothing", reg); + goto out; + } + + if (dev->pr_scope != scope || dev->pr_type != type) { + TRACE_PR("%s", "Released scope or type do not match with " + "holder"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_release)); + goto out; + } + + cur_pr_type = dev->pr_type; /* it will be cleared */ + + scst_pr_clear_reservation(dev); + + switch (cur_pr_type) { + case TYPE_WRITE_EXCLUSIVE_REGONLY: + case TYPE_EXCLUSIVE_ACCESS_REGONLY: + case TYPE_WRITE_EXCLUSIVE_ALL_REG: + case TYPE_EXCLUSIVE_ACCESS_ALL_REG: + scst_pr_send_ua_all(dev, reg, + SCST_LOAD_SENSE(scst_sense_reservation_released)); + } + + scst_pr_dump_prs(dev, false); + +out: + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + int scope, type; + __be64 key; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_dev_registrant *reg, *r, *t; + + key = get_unaligned((__be64 *)&buffer[0]); + scope = (cmd->cdb[2] & 0x0f) >> 4; + type = cmd->cdb[2] & 0x0f; + + if (buffer_size != 24) { + TRACE_PR("Invalid buffer size %d", buffer_size); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out; + } + + reg = tgt_dev->registrant; + + TRACE_PR("Clear: initiator %s/%d (%p), key %016llx (tgt_dev %p)", + debug_transport_id_to_initiator_name(cmd->sess->transport_id), + cmd->sess->tgt->rel_tgt_id, reg, key, tgt_dev); + + /* We already checked reg is not NULL */ + if (reg->key != key) { + TRACE_PR("Registrant's %p key %016llx mismatch with %016llx", + reg, reg->key, key); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + scst_pr_send_ua_all(dev, reg, + SCST_LOAD_SENSE(scst_sense_reservation_preempted)); + + list_for_each_entry_safe(r, t, &dev->dev_registrants_list, + dev_registrants_list_entry) { + scst_pr_remove_registrant(dev, r); + } + + dev->pr_generation++; + + scst_pr_dump_prs(dev, false); + +out: + return; +} + +static void scst_pr_do_preempt(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size, bool abort) +{ + __be64 key, action_key; + int scope, type; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_dev_registrant *reg, *r, *rt; + LIST_HEAD(preempt_list); + + key = get_unaligned((__be64 *)&buffer[0]); + action_key = get_unaligned((__be64 *)&buffer[8]); + scope = (cmd->cdb[2] & 0x0f) >> 4; + type = cmd->cdb[2] & 0x0f; + + if (buffer_size != 24) { + TRACE_PR("Invalid buffer size %d", buffer_size); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out; + } + + if (!scst_pr_type_valid(type)) { + TRACE_PR("Invalid reservation type %d", type); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + reg = tgt_dev->registrant; + + TRACE_PR("Preempt%s: initiator %s/%d (%p), key %016llx, action_key " + "%016llx, scope %x type %x (tgt_dev %p)", + abort ? " and abort" : "", + debug_transport_id_to_initiator_name(cmd->sess->transport_id), + cmd->sess->tgt->rel_tgt_id, reg, key, action_key, scope, type, + tgt_dev); + + /* We already checked reg is not NULL */ + if (reg->key != key) { + TRACE_PR("Registrant's %p key %016llx mismatch with %016llx", + reg, reg->key, key); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; + } + + if (!dev->pr_is_set) { + scst_pr_find_registrants_list_key(dev, action_key, + &preempt_list); + if (list_empty(&preempt_list)) + goto out_error; + list_for_each_entry_safe(r, rt, &preempt_list, aux_list_entry) { + if (r != reg) + scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE( + scst_sense_registrations_preempted)); + scst_pr_remove_registrant(dev, r); + } + goto done; + } + + if (dev->pr_type == TYPE_WRITE_EXCLUSIVE_ALL_REG || + dev->pr_type == TYPE_EXCLUSIVE_ACCESS_ALL_REG) { + if (action_key == 0) { + scst_pr_find_registrants_list_all(dev, reg, + &preempt_list); + list_for_each_entry_safe(r, rt, &preempt_list, + aux_list_entry) { + if (r != reg) + scst_pr_send_ua_reg(dev, r, + SCST_LOAD_SENSE( + scst_sense_registrations_preempted)); + else + reg = NULL; + scst_pr_remove_registrant(dev, r); + } + if (reg != NULL) + scst_pr_set_holder(dev, reg, scope, type); + } else { + scst_pr_find_registrants_list_key(dev, action_key, + &preempt_list); + if (list_empty(&preempt_list)) + goto out_error; + list_for_each_entry_safe(r, rt, &preempt_list, + aux_list_entry) { + if (r != reg) + scst_pr_send_ua_reg(dev, r, + SCST_LOAD_SENSE( + scst_sense_registrations_preempted)); + else + reg = NULL; + scst_pr_remove_registrant(dev, r); + } + } + goto done; + } + + BUG_ON(dev->pr_holder == NULL); + + if (dev->pr_holder->key != action_key) { + if (action_key == 0) { + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_invalid_field_in_parm_list)); + goto out; + } else { + scst_pr_find_registrants_list_key(dev, action_key, + &preempt_list); + if (list_empty(&preempt_list)) + goto out_error; + list_for_each_entry_safe(r, rt, &preempt_list, + aux_list_entry) { + if (r != reg) + scst_pr_send_ua_reg(dev, r, + SCST_LOAD_SENSE( + scst_sense_registrations_preempted)); + else + reg = NULL; + scst_pr_remove_registrant(dev, r); + } + goto done; + } + } + + scst_pr_find_registrants_list_key(dev, action_key, + &preempt_list); + + list_for_each_entry_safe(r, rt, &preempt_list, aux_list_entry) { + if (abort) + scst_pr_abort_reg(dev, cmd, r); + if (r != reg) + scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE( + scst_sense_registrations_preempted)); + else + reg = NULL; + scst_pr_remove_registrant(dev, r); + } + + if (dev->pr_type != type || dev->pr_scope != scope) + list_for_each_entry(r, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (r != reg) + scst_pr_send_ua_reg(dev, r, SCST_LOAD_SENSE( + scst_sense_reservation_released)); + } + + if (reg != NULL) + scst_pr_set_holder(dev, reg, scope, type); + +done: + dev->pr_generation++; + + scst_pr_dump_prs(dev, false); + +out: + return; + +out_error: + TRACE_PR("Invalid key %016llx", action_key); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_preempt(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + + scst_pr_do_preempt(cmd, buffer, buffer_size, false); + return; +} + +static void scst_cmd_done_pr_preempt(struct scst_cmd *cmd, int next_state, + enum scst_exec_context pref_context) +{ + void (*saved_cmd_done) (struct scst_cmd *cmd, int next_state, + enum scst_exec_context pref_context); + + saved_cmd_done = NULL; /* to remove warning that it's used not inited */ + + if (cmd->pr_abort_counter != NULL) { + if (!atomic_dec_and_test(&cmd->pr_abort_counter->pr_abort_pending_cnt)) + goto out; + saved_cmd_done = cmd->pr_abort_counter->saved_cmd_done; + kfree(cmd->pr_abort_counter); + cmd->pr_abort_counter = NULL; + } + + saved_cmd_done(cmd, next_state, pref_context); + +out: + return; +} + +/* + * Called with dev_pr_mutex locked, no IRQ. Expects session_list_lock + * not locked + */ +void scst_pr_preempt_and_abort(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size) +{ + + cmd->pr_abort_counter = kzalloc(sizeof(*cmd->pr_abort_counter), + GFP_KERNEL); + if (cmd->pr_abort_counter == NULL) { + PRINT_ERROR("Unable to allocate PR abort counter (size %zd)", + sizeof(*cmd->pr_abort_counter)); + scst_set_busy(cmd); + goto out; + } + + /* 1 to protect cmd from be done by the TM thread too early */ + atomic_set(&cmd->pr_abort_counter->pr_abort_pending_cnt, 1); + atomic_set(&cmd->pr_abort_counter->pr_aborting_cnt, 1); + init_completion(&cmd->pr_abort_counter->pr_aborting_cmpl); + + cmd->pr_abort_counter->saved_cmd_done = cmd->scst_cmd_done; + cmd->scst_cmd_done = scst_cmd_done_pr_preempt; + + scst_pr_do_preempt(cmd, buffer, buffer_size, true); + + if (!atomic_dec_and_test(&cmd->pr_abort_counter->pr_aborting_cnt)) + wait_for_completion(&cmd->pr_abort_counter->pr_aborting_cmpl); + +out: + return; +} + +/* Checks if this is a Compatible Reservation Handling (CRH) case */ +bool scst_pr_crh_case(struct scst_cmd *cmd) +{ + bool allowed; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_dev_registrant *reg; + uint8_t type; + + TRACE_DBG("Test if there is a CRH case for command %s (0x%x) from " + "%s", cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name); + + if (!dev->pr_is_set) { + TRACE_PR("%s", "PR not set"); + allowed = false; + goto out; + } + + reg = tgt_dev->registrant; + type = dev->pr_type; + + switch (type) { + case TYPE_WRITE_EXCLUSIVE: + case TYPE_EXCLUSIVE_ACCESS: + WARN_ON(dev->pr_holder == NULL); + if (reg == dev->pr_holder) + allowed = true; + else + allowed = false; + break; + + case TYPE_WRITE_EXCLUSIVE_REGONLY: + case TYPE_EXCLUSIVE_ACCESS_REGONLY: + case TYPE_WRITE_EXCLUSIVE_ALL_REG: + case TYPE_EXCLUSIVE_ACCESS_ALL_REG: + allowed = (reg != NULL); + break; + + default: + PRINT_ERROR("Invalid PR type %x", type); + allowed = false; + break; + } + + if (!allowed) + TRACE_PR("Command %s (0x%x) from %s rejected due to not CRH " + "reservation", cmd->op_name, cmd->cdb[0], + cmd->sess->initiator_name); + else + TRACE_DBG("Command %s (0x%x) from %s is allowed to execute " + "due to CRH", cmd->op_name, cmd->cdb[0], + cmd->sess->initiator_name); + +out: + return allowed; + +} + +/* Check if command allowed in presence of reservation */ +bool scst_pr_is_cmd_allowed(struct scst_cmd *cmd) +{ + bool allowed; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_dev_registrant *reg; + uint8_t type; + bool unlock; + + unlock = scst_pr_read_lock(dev); + + TRACE_DBG("Testing if command %s (0x%x) from %s allowed to execute", + cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name); + + /* Recheck, because it can change while we were waiting for the lock */ + if (unlikely(!dev->pr_is_set)) { + allowed = true; + goto out_unlock; + } + + reg = tgt_dev->registrant; + type = dev->pr_type; + + switch (type) { + case TYPE_WRITE_EXCLUSIVE: + if (reg && reg == dev->pr_holder) + allowed = true; + else + allowed = (cmd->op_flags & SCST_WRITE_EXCL_ALLOWED) != 0; + break; + + case TYPE_EXCLUSIVE_ACCESS: + if (reg && reg == dev->pr_holder) + allowed = true; + else + allowed = (cmd->op_flags & SCST_EXCL_ACCESS_ALLOWED) != 0; + break; + + case TYPE_WRITE_EXCLUSIVE_REGONLY: + case TYPE_WRITE_EXCLUSIVE_ALL_REG: + if (reg) + allowed = true; + else + allowed = (cmd->op_flags & SCST_WRITE_EXCL_ALLOWED) != 0; + break; + + case TYPE_EXCLUSIVE_ACCESS_REGONLY: + case TYPE_EXCLUSIVE_ACCESS_ALL_REG: + if (reg) + allowed = true; + else + allowed = (cmd->op_flags & SCST_EXCL_ACCESS_ALLOWED) != 0; + break; + + default: + PRINT_ERROR("Invalid PR type %x", type); + allowed = false; + break; + } + + if (!allowed) + TRACE_PR("Command %s (0x%x) from %s rejected due " + "to PR", cmd->op_name, cmd->cdb[0], + cmd->sess->initiator_name); + else + TRACE_DBG("Command %s (0x%x) from %s is allowed to execute", + cmd->op_name, cmd->cdb[0], cmd->sess->initiator_name); + +out_unlock: + scst_pr_read_unlock(dev, unlock); + return allowed; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_read_keys(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + int i, offset = 0, size, size_max; + struct scst_device *dev = cmd->dev; + struct scst_dev_registrant *reg; + + if (buffer_size < 8) { + TRACE_PR("buffer_size too small: %d. expected >= 8 " + "(buffer %p)", buffer_size, buffer); + goto skip; + } + + TRACE_PR("Read Keys (dev %s): PRGen %d", dev->virt_name, + dev->pr_generation); + + put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]); + + offset = 8; + size = 0; + size_max = buffer_size - 8; + + i = 0; + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + if (size_max - size >= 8) { + TRACE_PR("Read Keys (dev %s): key 0x%llx", + dev->virt_name, reg->key); + + WARN_ON(reg->key == 0); + + put_unaligned(reg->key, + (__be64 *)&buffer[offset + 8 * i]); + + offset += 8; + } + size += 8; + } + + put_unaligned(cpu_to_be32(size), (__be32 *)&buffer[4]); + +skip: + scst_set_resp_data_len(cmd, offset); + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_read_reservation(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size) +{ + struct scst_device *dev = cmd->dev; + uint8_t b[24]; + int size = 0; + + if (buffer_size < 8) { + TRACE_PR("buffer_size too small: %d. expected >= 8 " + "(buffer %p)", buffer_size, buffer); + goto skip; + } + + memset(b, 0, sizeof(b)); + + put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]); + + if (!dev->pr_is_set) { + TRACE_PR("Read Reservation: no reservations for dev %s", + dev->virt_name); + b[4] = + b[5] = + b[6] = + b[7] = 0; + + size = 8; + } else { + __be64 key = dev->pr_holder ? dev->pr_holder->key : 0; + + TRACE_PR("Read Reservation: dev %s, holder %p, key 0x%llx, " + "scope %d, type %d", dev->virt_name, dev->pr_holder, + key, dev->pr_scope, dev->pr_type); + + b[4] = + b[5] = + b[6] = 0; + b[7] = 0x10; + + put_unaligned(key, (__be64 *)&b[8]); + b[21] = dev->pr_scope << 4 | dev->pr_type; + + size = 24; + } + + memset(buffer, 0, buffer_size); + memcpy(buffer, b, min(size, buffer_size)); + +skip: + scst_set_resp_data_len(cmd, size); + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size) +{ + int offset = 0; + unsigned int crh = 1; + unsigned int atp_c = 1; + unsigned int sip_c = 1; + unsigned int ptpl_c = 1; + struct scst_device *dev = cmd->dev; + + if (buffer_size < 8) { + TRACE_PR("buffer_size too small: %d. expected >= 8 " + "(buffer %p)", buffer_size, buffer); + goto skip; + } + + TRACE_PR("Reporting capabilities (dev %s): crh %x, sip_c %x, " + "atp_c %x, ptpl_c %x, pr_aptpl %x", dev->virt_name, + crh, sip_c, atp_c, ptpl_c, dev->pr_aptpl); + + buffer[0] = 0; + buffer[1] = 8; + + buffer[2] = crh << 4 | sip_c << 3 | atp_c << 2 | ptpl_c; + buffer[3] = (1 << 7) | (dev->pr_aptpl > 0 ? 1 : 0); + + /* All commands supported */ + buffer[4] = 0xEA; + buffer[5] = 0x1; + + offset += 8; + +skip: + scst_set_resp_data_len(cmd, offset); + return; +} + +/* Called with dev_pr_mutex locked, no IRQ */ +void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size) +{ + int offset = 0, size, size_max; + struct scst_device *dev = cmd->dev; + struct scst_dev_registrant *reg; + + if (buffer_size < 8) + goto skip; + + put_unaligned(cpu_to_be32(dev->pr_generation), (__be32 *)&buffer[0]); + offset += 8; + + size = 0; + size_max = buffer_size - 8; + + list_for_each_entry(reg, &dev->dev_registrants_list, + dev_registrants_list_entry) { + int ts; + int rec_len; + + ts = tid_size(reg->transport_id); + rec_len = 24 + ts; + + if (size_max - size > rec_len) { + memset(&buffer[offset], 0, rec_len); + + put_unaligned(reg->key, (__be64 *)(&buffer[offset])); + + if (dev->pr_is_set && scst_pr_is_holder(dev, reg)) { + buffer[offset + 12] = 1; + buffer[offset + 13] = (dev->pr_scope << 8) | dev->pr_type; + } + + put_unaligned(cpu_to_be16(reg->rel_tgt_id), + (__be16 *)&buffer[offset + 18]); + put_unaligned(cpu_to_be32(ts), + (__be32 *)&buffer[offset + 20]); + + memcpy(&buffer[offset + 24], reg->transport_id, ts); + + offset += rec_len; + } + size += rec_len; + } + + put_unaligned(cpu_to_be32(size), (__be32 *)&buffer[4]); + +skip: + scst_set_resp_data_len(cmd, offset); + return; +} diff -uprN orig/linux-2.6.35/drivers/scst/scst_pres.h linux-2.6.35/drivers/scst/scst_pres.h --- orig/linux-2.6.35/drivers/scst/scst_pres.h +++ linux-2.6.35/drivers/scst/scst_pres.h @@ -0,0 +1,159 @@ +/* + * scst_pres.c + * + * Copyright (C) 2009 - 2010 Alexey Obitotskiy <alexeyo1@xxxxxxxxxx> + * Copyright (C) 2009 - 2010 Open-E, Inc. + * Copyright (C) 2009 - 2010 Vladislav Bolkhovitin <vst@xxxxxxxx> + * + * 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, version 2 + * of the License. + * + * 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. + */ + +#ifndef SCST_PRES_H_ +#define SCST_PRES_H_ + +#include <linux/delay.h> + +#define PR_REGISTER 0x00 +#define PR_RESERVE 0x01 +#define PR_RELEASE 0x02 +#define PR_CLEAR 0x03 +#define PR_PREEMPT 0x04 +#define PR_PREEMPT_AND_ABORT 0x05 +#define PR_REGISTER_AND_IGNORE 0x06 +#define PR_REGISTER_AND_MOVE 0x07 + +#define PR_READ_KEYS 0x00 +#define PR_READ_RESERVATION 0x01 +#define PR_REPORT_CAPS 0x02 +#define PR_READ_FULL_STATUS 0x03 + +#define TYPE_UNSPECIFIED (-1) +#define TYPE_WRITE_EXCLUSIVE 0x01 +#define TYPE_EXCLUSIVE_ACCESS 0x03 +#define TYPE_WRITE_EXCLUSIVE_REGONLY 0x05 +#define TYPE_EXCLUSIVE_ACCESS_REGONLY 0x06 +#define TYPE_WRITE_EXCLUSIVE_ALL_REG 0x07 +#define TYPE_EXCLUSIVE_ACCESS_ALL_REG 0x08 + +#define SCOPE_LU 0x00 + +static inline bool scst_pr_type_valid(uint8_t type) +{ + switch (type) { + case TYPE_WRITE_EXCLUSIVE: + case TYPE_EXCLUSIVE_ACCESS: + case TYPE_WRITE_EXCLUSIVE_REGONLY: + case TYPE_EXCLUSIVE_ACCESS_REGONLY: + case TYPE_WRITE_EXCLUSIVE_ALL_REG: + case TYPE_EXCLUSIVE_ACCESS_ALL_REG: + return true; + default: + return false; + } +} + +int scst_pr_check_pr_path(void); + +static inline bool scst_pr_read_lock(struct scst_device *dev) +{ + bool unlock = false; + + atomic_inc(&dev->pr_readers_count); + smp_mb__after_atomic_inc(); /* to sync with scst_pr_write_lock() */ + + if (unlikely(dev->pr_writer_active)) { + unlock = true; + atomic_dec(&dev->pr_readers_count); + mutex_lock(&dev->dev_pr_mutex); + } + return unlock; +} + +static inline void scst_pr_read_unlock(struct scst_device *dev, bool unlock) +{ + + if (unlikely(unlock)) + mutex_unlock(&dev->dev_pr_mutex); + else { + /* + * To sync with scst_pr_write_lock(). We need it to ensure + * order of our reads with the writer's writes. + */ + smp_mb__before_atomic_dec(); + atomic_dec(&dev->pr_readers_count); + } + return; +} + +static inline void scst_pr_write_lock(struct scst_device *dev) +{ + + mutex_lock(&dev->dev_pr_mutex); + + dev->pr_writer_active = 1; + + /* to sync with scst_pr_read_lock() and unlock() */ + smp_mb(); + + while (atomic_read(&dev->pr_readers_count) != 0) { + TRACE_DBG("Waiting for %d readers (dev %p)", + atomic_read(&dev->pr_readers_count), dev); + msleep(1); + } + return; +} + +static inline void scst_pr_write_unlock(struct scst_device *dev) +{ + + dev->pr_writer_active = 0; + + mutex_unlock(&dev->dev_pr_mutex); + return; +} + +int scst_pr_init_dev(struct scst_device *dev); +void scst_pr_clear_dev(struct scst_device *dev); + +int scst_pr_init_tgt_dev(struct scst_tgt_dev *tgt_dev); +void scst_pr_clear_tgt_dev(struct scst_tgt_dev *tgt_dev); + +bool scst_pr_crh_case(struct scst_cmd *cmd); +bool scst_pr_is_cmd_allowed(struct scst_cmd *cmd); + +void scst_pr_register(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_register_and_ignore(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size); +void scst_pr_register_and_move(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size); +void scst_pr_reserve(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_release(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_preempt(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_preempt_and_abort(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size); + +void scst_pr_read_keys(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_read_reservation(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size); +void scst_pr_report_caps(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); +void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer, + int buffer_size); + +void scst_pr_sync_device_file(struct scst_tgt_dev *tgt_dev, struct scst_cmd *cmd); + +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) +void scst_pr_dump_prs(struct scst_device *dev, bool force); +#else +static inline void scst_pr_dump_prs(struct scst_device *dev, bool force) {} +#endif + +#endif /* SCST_PRES_H_ */ -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html