blob: 96f3d77dfdd2b471b37c5b582a086b0a3eaaf3cc [file] [log] [blame]
/*
* scst_pres.c
*
* Copyright (C) 2009 - 2010 Alexey Obitotskiy <alexeyo1@open-e.com>
* Copyright (C) 2009 - 2010 Open-E, Inc.
* Copyright (C) 2009 - 2018 Vladislav Bolkhovitin <vst@vlnb.net>
*
* 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/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>
#ifndef INSIDE_KERNEL_TREE
#include <linux/version.h>
#endif
#include <linux/vmalloc.h>
#include <asm/unaligned.h>
#include <stdarg.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25)
#include <linux/mount.h>
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
#include <linux/writeback.h>
#endif
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#include <scst/scst_const.h>
#else
#include "scst.h"
#include "scst_const.h"
#endif
#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
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 32) && defined(CONFIG_LOCKDEP)
#define scst_assert_pr_mutex_held(dev) \
do { \
if (dev->dev_list_entry.next && \
!list_empty(&dev->dev_list_entry)) \
lockdep_assert_held(&dev->dev_pr_mutex); \
} while (0)
#else
static inline void scst_assert_pr_mutex_held(struct scst_device *dev)
{
}
#endif
uint32_t scst_tid_size(const uint8_t *tid)
{
sBUG_ON(tid == NULL);
if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI)
return get_unaligned_be16(&tid[2]) + 4;
else
return TID_COMMON_SIZE;
}
/* Secures transport ID by setting 0 in the last byte of ASCII transport IDs */
static bool tid_secure(uint8_t *tid, unsigned int buf_size)
{
if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) {
const uint32_t size = scst_tid_size(tid);
if (size > buf_size)
return false;
tid[size - 1] = '\0';
}
return true;
}
/* Returns false if tid's are not equal, true otherwise */
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;
uint32_t tid_a_len, tid_a_max = scst_tid_size(tid_a) - 4;
uint32_t tid_b_len, tid_b_max = scst_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;
sBUG_ON(tid_a_len > tid_a_max);
} 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;
sBUG_ON(tid_b_len > tid_b_max);
} 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 */
void scst_pr_set_holder(struct scst_device *dev,
struct scst_dev_registrant *holder, uint8_t scope, uint8_t type)
{
scst_assert_pr_mutex_held(dev);
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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:
TRACE_EXIT_RES(res);
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)
{
scst_assert_pr_mutex_held(dev);
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, be64_to_cpu(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, be64_to_cpu(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_ENTRY();
scst_assert_pr_mutex_held(dev);
TRACE_PR("Finding all registered records for device '%s' "
"with exclude reg key %016llx",
dev->virt_name, be64_to_cpu(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, be64_to_cpu(reg->key));
list_add_tail(&reg->aux_list_entry, list);
}
TRACE_EXIT();
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_ENTRY();
scst_assert_pr_mutex_held(dev);
TRACE_PR("Finding registrants for device '%s' with key %016llx",
dev->virt_name, be64_to_cpu(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,
be64_to_cpu(key));
list_add_tail(&reg->aux_list_entry, list);
}
}
TRACE_EXIT();
return;
}
/* dev_pr_mutex must be locked */
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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;
}
}
TRACE_EXIT_HRES(res);
return res;
}
/* Must be called under dev_pr_mutex */
static void scst_pr_clear_reservation(struct scst_device *dev)
{
TRACE_ENTRY();
scst_assert_pr_mutex_held(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;
TRACE_EXIT();
return;
}
/* Must be called under dev_pr_mutex */
void scst_pr_clear_holder(struct scst_device *dev)
{
TRACE_ENTRY();
scst_assert_pr_mutex_held(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;
TRACE_EXIT();
return;
}
/* Must be called under dev_pr_mutex */
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
sBUG_ON(dev == NULL);
sBUG_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;
}
dev->cl_ops->pr_init_reg(dev, reg);
reg->transport_id = kmemdup(transport_id, scst_tid_size(transport_id),
gfp_flags);
if (reg->transport_id == NULL) {
PRINT_ERROR("%s", "Unable to allocate initiator port "
"transport id");
goto out_free;
}
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 !defined(__CHECKER__)
if (!dev_lock_locked)
spin_lock_bh(&dev->dev_lock);
#endif
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 !defined(__CHECKER__)
if (!dev_lock_locked)
spin_unlock_bh(&dev->dev_lock);
#endif
list_add_tail(&reg->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:
TRACE_EXIT_HRES((unsigned long)reg);
return reg;
out_free:
kfree(reg);
reg = NULL;
goto out;
}
/* Must be called under dev_pr_mutex */
void scst_pr_remove_registrant(struct scst_device *dev,
struct scst_dev_registrant *reg)
{
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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, be64_to_cpu(reg->key),
dev->virt_name);
list_del(&reg->dev_registrants_list_entry);
dev->cl_ops->pr_rm_reg(dev, reg);
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);
TRACE_EXIT();
return;
}
static void scst_pr_remove_registrants(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);
}
}
/* 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];
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
scst_set_sense(ua, sizeof(ua), dev->d_sense, key, asc, ascq);
TRACE_PR("Queueing 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, be64_to_cpu(reg->key));
if (reg->tgt_dev)
scst_check_set_UA(reg->tgt_dev, ua, sizeof(ua), 0);
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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);
}
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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, be64_to_cpu(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(&reg->tgt_dev->tgt_dev_cmd_count),
debug_transport_id_to_initiator_name(reg->transport_id),
reg->rel_tgt_id, reg, be64_to_cpu(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,
&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);
goto out;
}
if ((reg->tgt_dev != pr_cmd->tgt_dev) && !dev->tas) {
uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN];
int sl;
sl = scst_set_sense(sense_buffer, sizeof(sense_buffer),
dev->d_sense,
SCST_LOAD_SENSE(scst_sense_cleared_by_another_ini_UA));
/*
* Potentially, setting UA here, when the aborted commands are
* still running, can lead to a situation that one of them could
* take it, then that would be detected and the UA requeued.
* But, meanwhile, one or more subsequent, i.e. not aborted,
* commands can "leak" executed normally. So, as result, the
* UA would be delivered one or more commands "later". However,
* that should be OK, because, if multiple commands are being
* executed in parallel, you can't control exact order of UA
* delivery anyway.
*/
scst_check_set_UA(reg->tgt_dev, sense_buffer, sl, 0);
}
out:
TRACE_EXIT();
return;
}
/* 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;
uint8_t pr_is_set, aptpl;
__be64 key;
uint16_t rel_tgt_id;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
scst_pr_remove_registrants(dev);
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_inode(file);
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 = kernel_read(file, 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 + 1 < file_size) {
uint8_t *tid;
data_size++;
tid = &buf[data_size];
data_size += scst_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 + 1 < file_size) {
uint8_t is_holder;
uint8_t *tid;
struct scst_dev_registrant *reg = NULL;
is_holder = buf[pos++];
tid = &buf[pos];
pos += scst_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);
TRACE_EXIT_RES(res);
return res;
}
static int scst_pr_load_device_file(struct scst_device *dev)
{
int res, rc;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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_dump;
else if (res == -ENOMEM)
goto out;
rc = res;
res = scst_pr_do_load_device_file(dev, dev->pr_file_name1);
if (res != 0) {
if (res == -ENOENT)
res = rc;
goto out;
}
out_dump:
scst_pr_dump_prs(dev, false);
out:
TRACE_EXIT_RES(res);
return res;
}
static void scst_pr_remove_device_files(struct scst_device *dev)
{
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
if (dev->pr_file_name)
scst_remove_file(dev->pr_file_name);
if (dev->pr_file_name1)
scst_remove_file(dev->pr_file_name1);
TRACE_EXIT();
return;
}
/* Must be called under dev_pr_mutex */
void scst_pr_sync_device_file(struct scst_device *dev)
{
int res = 0;
struct file *file;
loff_t pos = 0;
uint64_t sign;
uint64_t version;
uint8_t pr_is_set, aptpl;
struct scst_dev_registrant *reg;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
if ((dev->pr_aptpl == 0) || list_empty(&dev->dev_registrants_list)) {
scst_pr_remove_device_files(dev);
goto out;
}
scst_copy_file(dev->pr_file_name, dev->pr_file_name1);
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;
}
TRACE_PR("Updating pr file '%s'", dev->pr_file_name);
/*
* signature
*/
sign = 0;
pos = 0;
res = kernel_write(file, &sign, sizeof(sign), &pos);
if (res != sizeof(sign))
goto write_error;
/*
* version
*/
version = SCST_PR_FILE_VERSION;
res = kernel_write(file, &version, sizeof(version), &pos);
if (res != sizeof(version))
goto write_error;
/*
* APTPL
*/
aptpl = dev->pr_aptpl;
res = kernel_write(file, &aptpl, sizeof(aptpl), &pos);
if (res != sizeof(aptpl))
goto write_error;
/*
* reservation
*/
pr_is_set = dev->pr_is_set;
res = kernel_write(file, &pr_is_set, sizeof(pr_is_set), &pos);
if (res != sizeof(pr_is_set))
goto write_error;
res = kernel_write(file, &dev->pr_type, sizeof(dev->pr_type), &pos);
if (res != sizeof(dev->pr_type))
goto write_error;
res = kernel_write(file, &dev->pr_scope, sizeof(dev->pr_scope), &pos);
if (res != sizeof(dev->pr_scope))
goto write_error;
/*
* registration records
*/
list_for_each_entry(reg, &dev->dev_registrants_list,
dev_registrants_list_entry) {
uint8_t is_holder = 0;
uint32_t size;
is_holder = (dev->pr_holder == reg);
res = kernel_write(file, &is_holder, sizeof(is_holder), &pos);
if (res != sizeof(is_holder))
goto write_error;
size = scst_tid_size(reg->transport_id);
res = kernel_write(file, reg->transport_id, size, &pos);
if (res != size)
goto write_error;
res = kernel_write(file, &reg->key, sizeof(reg->key), &pos);
if (res != sizeof(reg->key))
goto write_error;
res = kernel_write(file, &reg->rel_tgt_id,
sizeof(reg->rel_tgt_id), &pos);
if (res != sizeof(reg->rel_tgt_id))
goto write_error;
}
res = vfs_fsync(file, 1);
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 = kernel_write(file, &sign, sizeof(sign), &pos);
if (res != sizeof(sign))
goto write_error;
res = vfs_fsync(file, 1);
if (res != 0) {
PRINT_ERROR("fsync() of the PR file failed: %d", res);
goto write_error_close;
}
res = 0;
filp_close(file, NULL);
out:
if (res != 0) {
PRINT_CRIT_ERROR("Unable to save persistent information "
"(device %s)", dev->virt_name);
/*
* It's safer to not return any error to the initiator and expect
* operator's intervention to be able to save the PR's state next
* time, than to screw up all the interactions with this initiator.
*/
}
TRACE_EXIT();
return;
write_error:
PRINT_ERROR("Error writing to '%s' - error %d", dev->pr_file_name, res);
write_error_close:
filp_close(file, NULL);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28)
{
struct nameidata nd;
int rc;
rc = path_lookup(dev->pr_file_name, 0, &nd);
if (!rc)
scst_vfs_unlink_and_put_nd(&nd);
else
TRACE_PR("Unable to lookup '%s' - error %d",
dev->pr_file_name, rc);
}
#else
{
struct path path;
int rc;
rc = kern_path(dev->pr_file_name, 0, &path);
if (!rc)
scst_vfs_unlink_and_put(&path);
else
TRACE_PR("Unable to lookup '%s' - error %d",
dev->pr_file_name, rc);
}
#endif
goto out;
}
/**
* scst_pr_set_file_name - set name of file in which to save PR information
* @dev: SCST device.
* @prev: If not NULL, the current path will be stored in *@prev. It is the
* responsibility of the caller to invoke kfree(*@prev) at an
* appropriate time.
* @fmt: Full path of the file in which to save PR info.
*
* This function must be called either while @dev is not on the device list
* or with scst_mutex held.
*/
int scst_pr_set_file_name(struct scst_device *dev, char **prev,
const char *fmt, ...)
{
va_list args;
char *pr_file_name = NULL, *bkp = NULL;
int file_mode, res = -EINVAL;
scst_assert_pr_mutex_held(dev);
sBUG_ON(!fmt);
res = -ENOMEM;
va_start(args, fmt);
pr_file_name = kvasprintf(GFP_KERNEL, fmt, args);
va_end(args);
if (!pr_file_name) {
PRINT_ERROR("Unable to kvasprintf() new PR file name");
goto out;
}
res = -EINVAL;
if (pr_file_name[0] != '/') {
PRINT_ERROR("PR file name %s must be absolute!", pr_file_name);
goto out;
}
file_mode = scst_get_file_mode(pr_file_name);
if (file_mode >= 0 && !S_ISREG(file_mode) && !S_ISBLK(file_mode)) {
PRINT_ERROR("PR file name %s must be file or block device!",
pr_file_name);
goto out;
}
res = -ENOENT;
if (!scst_parent_dir_exists(pr_file_name)) {
PRINT_ERROR("PR file name %s parent directory doesn't exist",
pr_file_name);
goto out;
}
res = -ENOMEM;
bkp = kasprintf(GFP_KERNEL, "%s.1", pr_file_name);
if (!bkp) {
PRINT_ERROR("Unable to kasprintf() backup PR file name");
goto out;
}
if (prev) {
*prev = dev->pr_file_name;
dev->pr_file_name = pr_file_name;
pr_file_name = NULL;
} else
swap(dev->pr_file_name, pr_file_name);
swap(dev->pr_file_name1, bkp);
res = 0;
out:
kfree(pr_file_name);
kfree(bkp);
return res;
}
/* Initialize the PR members in *dev. */
int scst_pr_init(struct scst_device *dev)
{
mutex_init(&dev->dev_pr_mutex);
dev->cl_ops = &scst_no_dlm_cl_ops;
dev->pr_generation = 0;
dev->pr_is_set = 0;
dev->pr_holder = NULL;
dev->pr_scope = SCOPE_LU;
dev->pr_type = TYPE_UNSPECIFIED;
INIT_LIST_HEAD(&dev->dev_registrants_list);
return 0;
}
/* Free the resources allocated by scst_pr_init(). */
void scst_pr_cleanup(struct scst_device *dev)
{
dev->cl_ops->pr_cleanup(dev);
}
/* Caller must hold scst_mutex and activity must be suspended. */
int scst_pr_set_cluster_mode(struct scst_device *dev, bool cluster_mode,
const char *cl_dev_id)
{
int res = 0;
#if defined(CONFIG_DLM) || defined(CONFIG_DLM_MODULE) && \
!defined(CONFIG_SCST_NO_DLM)
bool cluster_mode_enabled = false;
cluster_mode_enabled = dev->cl_ops == &scst_dlm_cl_ops;
if (cluster_mode_enabled == cluster_mode)
goto out;
PRINT_INFO("%s: changing cluster_mode from %d into %d", dev->virt_name,
cluster_mode_enabled, cluster_mode);
dev->cl_ops->pr_cleanup(dev);
dev->cl_ops = cluster_mode ? &scst_dlm_cl_ops : &scst_no_dlm_cl_ops;
res = dev->cl_ops->pr_init(dev, cl_dev_id);
if (res) {
PRINT_ERROR("%s: changing cluster_mode into %d failed: %d",
dev->virt_name, cluster_mode, res);
dev->cl_ops = &scst_no_dlm_cl_ops;
}
out:
#else
res = cluster_mode ? -EOPNOTSUPP : 0;
#endif
return res;
}
EXPORT_SYMBOL(scst_pr_set_cluster_mode);
/* Must be called under dev_pr_mutex or before dev is on the device list. */
int scst_pr_init_dev(struct scst_device *dev)
{
int res = 0;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
sBUG_ON(!dev->pr_file_name || !dev->pr_file_name1);
res = scst_pr_load_device_file(dev);
if (res == -ENOENT)
res = 0;
TRACE_EXIT_RES(res);
return res;
}
/* Called under scst_mutex */
void scst_pr_clear_dev(struct scst_device *dev)
{
TRACE_ENTRY();
scst_pr_remove_registrants(dev);
kfree(dev->pr_file_name);
kfree(dev->pr_file_name1);
TRACE_EXIT();
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;
TRACE_ENTRY();
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:
TRACE_EXIT_RES(res);
return res;
}
/* Called under scst_mutex */
void scst_pr_clear_tgt_dev(struct scst_tgt_dev *tgt_dev)
{
struct scst_device *dev = tgt_dev->dev;
struct scst_dev_registrant *reg;
struct scst_tgt_dev *t;
TRACE_ENTRY();
scst_pr_write_lock(dev);
reg = tgt_dev->registrant;
if (reg) {
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);
TRACE_EXIT();
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;
unsigned int ext_size;
__be64 action_key;
struct scst_device *dev = cmd->dev;
struct scst_dev_registrant *reg;
scst_assert_pr_mutex_held(cmd->dev);
action_key = get_unaligned((__be64 *)&buffer[8]);
ext_size = 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) {
uint8_t *const transport_id = &buffer[28 + offset];
const uint32_t tid_size = scst_tid_size(transport_id);
if (offset + tid_size > ext_size) {
TRACE_PR("Invalid transport_id size %d (max %d)",
tid_size, ext_size - offset);
scst_set_invalid_field_in_parm_list(cmd, 24, 0);
res = -EINVAL;
goto out;
}
WARN_ON_ONCE(!tid_secure(transport_id, tid_size));
offset += tid_size;
}
offset = 0;
while (offset < ext_size) {
uint8_t *transport_id = &buffer[28 + offset];
struct scst_tgt_dev *t;
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 the caller
* already holds 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(&reg->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(&reg->aux_list_entry,
rollback_list);
}
next:
offset += scst_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_ENTRY();
scst_assert_pr_mutex_held(dev);
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;
}
}
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
/*
* We can't use scst_mutex here since the caller already holds
* 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;
list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) {
struct scst_dev_registrant *reg;
if (tgtt->get_initiator_port_transport_id(tgt, NULL, NULL) != proto_id)
continue;
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);
TRACE_EXIT();
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_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
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(&reg->aux_list_entry, rollback_list);
}
res = 0;
out:
TRACE_EXIT_RES(res);
return res;
}
/* Add registrants for remote ports. Called with dev_pr_mutex locked, no IRQ. */
static int scst_register_remote_ports(struct scst_cmd *cmd, uint8_t *buffer,
int buffer_size, bool spec_i_pt, struct list_head *rollback_list)
{
struct scst_dev_group *dg;
struct scst_target_group *tg;
struct scst_tg_tgt *tgtgt;
int res = 0;
scst_alua_lock();
dg = scst_lookup_dg_by_dev(cmd->dev);
if (!dg)
goto out_unlock;
list_for_each_entry(tg, &dg->tg_list, entry) {
list_for_each_entry(tgtgt, &tg->tgt_list, entry) {
/* Skip local target ports */
if (tgtgt->tgt)
continue;
/* To do: check the initiator port transport ID. */
if (tgtgt->rel_tgt_id == 0)
continue;
res = scst_pr_register_on_tgt_id(cmd, tgtgt->rel_tgt_id,
buffer, buffer_size,
spec_i_pt,
rollback_list);
if (res != 0)
goto out_unlock;
}
}
out_unlock:
scst_alua_unlock();
return res;
}
/* Register all target ports. 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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
/*
* We can't use scst_mutex here because the caller already holds
* 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;
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 (tgtt->get_initiator_port_transport_id(tgt, NULL, NULL) != proto_id)
continue;
if (tgt->rel_tgt_id == 0)
continue;
if (tgt->tgt_forward_dst) {
TRACE_PR("ALL_TG_PT: skipping forwarding "
"target %s", tgt->tgt_name);
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;
}
}
res = scst_register_remote_ports(cmd, buffer, buffer_size, spec_i_pt,
rollback_list);
out_unlock:
mutex_unlock(&scst_mutex2);
TRACE_EXIT_RES(res);
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);
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
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:
TRACE_EXIT_RES(res);
return res;
out_rollback:
list_for_each_entry_safe(reg, treg, &rollback_list, aux_list_entry) {
list_del(&reg->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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
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, be64_to_cpu(key),
be64_to_cpu(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,
be64_to_cpu(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_invalid_field_in_parm_list(cmd, 20,
SCST_INVAL_FIELD_BIT_OFFS_VALID | 3);
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:
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
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, be64_to_cpu(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:
TRACE_EXIT();
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;
unsigned 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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
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 = 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_invalid_field_in_parm_list(cmd, 20, 0);
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, be64_to_cpu(reg->key),
be64_to_cpu(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_invalid_field_in_cdb(cmd, 8, 0);
goto out;
}
transport_id = sess->transport_id;
transport_id_move = (uint8_t *)&buffer[24];
rel_tgt_id_move = get_unaligned_be16(&buffer[18]);
if ((scst_tid_size(transport_id_move) + 24) > buffer_size) {
TRACE_PR("Invalid buffer size %d (%d)",
buffer_size, scst_tid_size(transport_id_move) + 24);
scst_set_cmd_error(cmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
goto out;
}
if (!tid_secure(transport_id_move, buffer_size - 24)) {
TRACE_PR("Transport ID length %d exceeds buffer size %d",
scst_tid_size(transport_id_move), buffer_size - 24);
scst_set_cmd_error(cmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
goto out;
}
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_invalid_field_in_parm_list(cmd, 24, 0);
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,
be64_to_cpu(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:
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
key = get_unaligned((__be64 *)&buffer[0]);
scope = cmd->cdb[2] >> 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_invalid_field_in_cdb(cmd, 2,
SCST_INVAL_FIELD_BIT_OFFS_VALID | 0);
goto out;
}
if (scope != SCOPE_LU) {
TRACE_PR("Invalid reservation scope %d", scope);
scst_set_invalid_field_in_cdb(cmd, 2,
SCST_INVAL_FIELD_BIT_OFFS_VALID | 4);
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, be64_to_cpu(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, be64_to_cpu(reg->key), be64_to_cpu(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:
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
key = get_unaligned((__be64 *)&buffer[0]);
scope = cmd->cdb[2] >> 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, be64_to_cpu(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, be64_to_cpu(reg->key), be64_to_cpu(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:
TRACE_EXIT();
return;
}
/* Called with dev_pr_mutex locked, no IRQ */
void scst_pr_clear(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size)
{
__be64 key;
struct scst_device *dev = cmd->dev;
struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
struct scst_dev_registrant *reg, *r, *t;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
key = get_unaligned((__be64 *)&buffer[0]);
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, be64_to_cpu(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, be64_to_cpu(reg->key), be64_to_cpu(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:
TRACE_EXIT();
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;
int existing_pr_type = dev->pr_type;
int existing_pr_scope = dev->pr_scope;
LIST_HEAD(preempt_list);
TRACE_ENTRY();
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;
}
key = get_unaligned((__be64 *)&buffer[0]);
action_key = get_unaligned((__be64 *)&buffer[8]);
scope = cmd->cdb[2] >> 4;
type = cmd->cdb[2] & 0x0f;
if (!scst_pr_type_valid(type)) {
TRACE_PR("Invalid reservation type %d", type);
scst_set_invalid_field_in_cdb(cmd, 1,
SCST_INVAL_FIELD_BIT_OFFS_VALID | 0);
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, be64_to_cpu(key),
be64_to_cpu(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, be64_to_cpu(reg->key), be64_to_cpu(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 (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));
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) {
sBUG_ON(r == reg);
if (abort)
scst_pr_abort_reg(dev, cmd, r);
scst_pr_send_ua_reg(dev, r,
SCST_LOAD_SENSE(
scst_sense_registrations_preempted));
scst_pr_remove_registrant(dev, r);
}
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 (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));
scst_pr_remove_registrant(dev, r);
}
}
}
goto done;
}
if (dev->pr_holder->key != action_key) {
if (action_key == 0) {
scst_set_invalid_field_in_parm_list(cmd, 8, 0);
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 (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));
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));
scst_pr_remove_registrant(dev, r);
}
}
scst_pr_set_holder(dev, reg, scope, type);
if (existing_pr_type != type || existing_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));
}
}
done:
dev->pr_generation++;
scst_pr_dump_prs(dev, false);
out:
TRACE_EXIT();
return;
out_error:
TRACE_PR("Invalid key %016llx", be64_to_cpu(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)
{
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
scst_pr_do_preempt(cmd, buffer, buffer_size, false);
TRACE_EXIT();
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);
TRACE_ENTRY();
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:
TRACE_EXIT();
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)
{
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
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:
TRACE_EXIT();
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_ENTRY();
TRACE_DBG("Test if there is a CRH case for command %s (%s) from "
"%s", cmd->op_name, scst_get_opcode_name(cmd),
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 (%s) from %s rejected due to not CRH "
"reservation", cmd->op_name, scst_get_opcode_name(cmd),
cmd->sess->initiator_name);
else
TRACE_DBG("Command %s (%s) from %s is allowed to execute "
"due to CRH", cmd->op_name, scst_get_opcode_name(cmd),
cmd->sess->initiator_name);
out:
TRACE_EXIT_RES(allowed);
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;
TRACE_ENTRY();
scst_pr_read_lock(dev);
TRACE_DBG("Testing if command %s (%s) from %s allowed to execute",
cmd->op_name, scst_get_opcode_name(cmd), 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 (%s) from %s rejected due "
"to PR", cmd->op_name, scst_get_opcode_name(cmd),
cmd->sess->initiator_name);
else
TRACE_DBG("Command %s (%s) from %s is allowed to execute",
cmd->op_name, scst_get_opcode_name(cmd),
cmd->sess->initiator_name);
out_unlock:
scst_pr_read_unlock(dev);
TRACE_EXIT_RES(allowed);
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 offset = 0, size, size_max;
struct scst_device *dev = cmd->dev;
struct scst_dev_registrant *reg;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
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_be32(dev->pr_generation, &buffer[0]);
offset = 8;
size = 0;
size_max = buffer_size - 8;
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]);
offset += 8;
}
size += 8;
}
put_unaligned_be32(size, &buffer[4]);
skip:
scst_set_resp_data_len(cmd, offset);
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(dev);
if (buffer_size < 8) {
TRACE_PR("buffer_size too small: %d. expected >= 8 "
"(buffer %p)", buffer_size, buffer);
goto out;
}
put_unaligned_be32(dev->pr_generation, &b[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,
be64_to_cpu(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;
}
out:
size = min(size, buffer_size);
memcpy(buffer, b, size);
memset(buffer + size, 0, buffer_size - size);
scst_set_resp_data_len(cmd, size);
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(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) | (4 << 4) | (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);
TRACE_EXIT();
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;
TRACE_ENTRY();
scst_assert_pr_mutex_held(cmd->dev);
if (buffer_size < 8)
goto skip;
put_unaligned_be32(dev->pr_generation, &buffer[0]);
offset += 8;
size = 0;
size_max = buffer_size - 8;
list_for_each_entry(reg, &dev->dev_registrants_list,
dev_registrants_list_entry) {
const uint32_t ts = scst_tid_size(reg->transport_id);
const uint32_t 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 << 4) | dev->pr_type;
}
put_unaligned_be16(reg->rel_tgt_id,
&buffer[offset + 18]);
put_unaligned_be32(ts, &buffer[offset + 20]);
memcpy(&buffer[offset + 24], reg->transport_id, ts);
offset += rec_len;
}
size += rec_len;
}
put_unaligned_be32(size, &buffer[4]);
skip:
scst_set_resp_data_len(cmd, offset);
TRACE_EXIT();
return;
}