Devices external blocking support

Prepared with help from Prasidh Srikanth <Prasidh.Srikanth@sandisk.com>



git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@6575 d57e44dd-8a1f-0410-8b47-8ef2f437770f
This commit is contained in:
Vladislav Bolkhovitin
2015-11-06 03:27:39 +00:00
parent 297d268a7a
commit 2fdbaf0fae
9 changed files with 530 additions and 0 deletions
+32
View File
@@ -508,6 +508,8 @@ documentation for your dev handlers for more info about it as well as
SysfsRules file for more info about common to all dev handlers rules.
SCST dev handlers can have the following common entries:
- block - allows to temporary block and unblock this device. See below.
- exported - subdirectory containing links to all LUNs where this
device was exported.
@@ -531,6 +533,35 @@ SCST dev handlers can have the following common entries:
- type - SCSI type of this device
Attribute "block" allows to temporary block and unblock this device.
"Blocking" means that no new commands for this device will go into the
execution stage, but instead will be suspended just before it. The
blocked state is not reached until queue of the corresponding device is
completely drained. You can also call this state "frozen". It is useful
in many cases, like consistent snapshots and graceful shutdown.
On write "block" entry allows the following 3 types of parameters:
- 1 - block device synchronously, i.e. don't return until this device
becomes blocked, i.e. until queue of it is not completely drained. Can
be called as many times as needed.
- 11 params - block device asynchronously, i.e. return immediately.
Notification about completing is delivered using SCST_EVENT_EXT_BLOCKING_DONE
event. "Params" delivered to it as is in "data" payload. Can be
called as many times as needed. Alternatively, status of blocking could be
polled by reading this attributes until the second number reaches 0
(see below).
- 0 - unblock this device.
Reading from "block" entry returns two numbers separated by space:
1. How many times this device was blocked, i.e. how many times writing
"0" to it is needed to unblock this device.
2. Boolean (0 or 1) if blocking, if any, is done (0) or still pending (1).
See below for more information about other entries of this subdirectory
of the standard SCST dev handlers.
@@ -1257,6 +1288,7 @@ Each vdisk_fileio's device has the following attributes in
For example:
/sys/kernel/scst_tgt/devices/disk1
|-- block
|-- blocksize
|-- exported
| |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0
+32
View File
@@ -367,6 +367,8 @@ documentation for your dev handlers for more info about it as well as
SysfsRules file for more info about common to all dev handlers rules.
SCST dev handlers can have the following common entries:
- block - allows to temporary block and unblock this device. See below.
- exported - subdirectory containing links to all LUNs where this
device was exported.
@@ -390,6 +392,35 @@ SCST dev handlers can have the following common entries:
- type - SCSI type of this device
Attribute "block" allows to temporary block and unblock this device.
"Blocking" means that no new commands for this device will go into the
execution stage, but instead will be suspended just before it. The
blocked state is not reached until queue of the corresponding device is
completely drained. You can also call this state "frozen". It is useful
in many cases, like consistent snapshots and graceful shutdown.
On write "block" entry allows the following 3 types of parameters:
- 1 - block device synchronously, i.e. don't return until this device
becomes blocked, i.e. until queue of it is not completely drained. Can
be called as many times as needed.
- 11 params - block device asynchronously, i.e. return immediately.
Notification about completing is delivered using SCST_EVENT_EXT_BLOCKING_DONE
event. "Params" delivered to it as is in "data" payload. Can be
called as many times as needed. Alternatively, status of blocking could be
polled by reading this attributes until the second number reaches 0
(see below).
- 0 - unblock this device.
Reading from "block" entry returns two numbers separated by space:
1. How many times this device was blocked, i.e. how many times writing
"0" to it is needed to unblock this device.
2. Boolean (0 or 1) if blocking, if any, is done (0) or still pending (1).
See below for more information about other entries of this subdirectory
of the standard SCST dev handlers.
@@ -1110,6 +1141,7 @@ Each vdisk_fileio's device has the following attributes in
For example:
/sys/kernel/scst_tgt/devices/disk1
|-- block
|-- blocksize
|-- exported
| |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0
+32
View File
@@ -2607,6 +2607,20 @@ struct scst_cl_ops {
void (*reserve)(struct scst_device *dev, struct scst_session *sess);
};
/*
* Extended, e.g. via sysfs, blockers
*/
typedef void (*ext_blocker_done_fn_t) (struct scst_device *dev,
uint8_t *data, int len);
struct scst_ext_blocker {
struct list_head ext_blockers_list_entry;
ext_blocker_done_fn_t ext_blocker_done_fn;
int ext_blocker_data_len;
uint8_t ext_blocker_data[];
};
/*
* SCST device
*/
@@ -2635,6 +2649,15 @@ struct scst_device {
*/
unsigned int dev_unregistering:1;
/*
* Set if ext blocking is pending. It if just shortcut for
* !list_empty(&dev->ext_blockers_list) to save a cache miss.
*/
unsigned int ext_blocking_pending:1;
/* Used to serialize invocations of __scst_ext_blocking_done() */
unsigned int ext_unblock_scheduled:1;
/* Set if this device does not support DIF IP checking */
unsigned int dev_dif_ip_not_supported:1;
@@ -2858,6 +2881,15 @@ struct scst_device {
/* List of blocked commands, protected by dev_lock. */
struct list_head blocked_cmd_list;
/* Number of ext blocking requests, protected by dev_lock */
int ext_blocks_cnt;
/* List of ext blockers, protected by dev_lock */
struct list_head ext_blockers_list;
/* Work to notify ext blockers out of dev_lock context */
struct work_struct ext_blockers_work;
/* MAXIMUM WRITE SAME LENGTH in bytes */
uint64_t max_write_same_len;
+7
View File
@@ -114,6 +114,13 @@ struct scst_event_negative_luns_inquiry_payload {
uint8_t target_name[SCST_MAX_EXTERNAL_NAME];
};
#define SCST_EVENT_EXT_BLOCKING_DONE 3
struct scst_event_ext_blocking_done_payload {
uint8_t device_name[SCST_MAX_EXTERNAL_NAME];
uint32_t data_len;
uint8_t data[];
};
#define SCST_EVENT_TM_FN_RECEIVED 4
struct scst_event_tm_fn_received_payload {
uint32_t fn; /* TM fn */
+41
View File
@@ -362,6 +362,47 @@ out:
return res;
}
/* No locks */
int scst_event_queue_ext_blocking_done(struct scst_device *dev, void *data, int len)
{
int res, event_entry_len;
struct scst_event_entry *event_entry;
struct scst_event *event;
struct scst_event_ext_blocking_done_payload *payload;
TRACE_ENTRY();
event_entry_len = sizeof(*event_entry) + sizeof(*payload) + len;
event_entry = kzalloc(event_entry_len, GFP_ATOMIC);
if (event_entry == NULL) {
PRINT_CRIT_ERROR("Unable to allocate event. Ext blocking "
"done event is lost (device %s, size %zd)!", dev->virt_name,
sizeof(*event_entry) + sizeof(*payload) + len);
res = -ENOMEM;
goto out;
}
TRACE_MEM("event_entry %p (len %d) allocated", event_entry,
event_entry_len);
event = &event_entry->event;
event->payload_len = sizeof(*payload) + len;
payload = (struct scst_event_ext_blocking_done_payload *)event->payload;
strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name));
if (len > 0)
memcpy(payload->data, data, len);
scst_event_queue(SCST_EVENT_EXT_BLOCKING_DONE,
SCST_EVENT_SCST_CORE_ISSUER, event_entry);
res = 0;
out:
TRACE_EXIT_RES(res);
return res;
}
/* No locks */
int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd)
{
+242
View File
@@ -3932,6 +3932,8 @@ static void scst_init_order_data(struct scst_order_data *order_data)
return;
}
static void scst_ext_blocking_done_fn(struct work_struct *work);
static int scst_dif_none(struct scst_cmd *cmd);
#ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS
static int scst_dif_none_type1(struct scst_cmd *cmd);
@@ -3963,6 +3965,8 @@ int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev)
INIT_LIST_HEAD(&dev->blocked_cmd_list);
INIT_LIST_HEAD(&dev->dev_tgt_dev_list);
INIT_LIST_HEAD(&dev->dev_acg_dev_list);
INIT_LIST_HEAD(&dev->ext_blockers_list);
INIT_WORK(&dev->ext_blockers_work, scst_ext_blocking_done_fn);
dev->dev_double_ua_possible = 1;
dev->queue_alg = SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER;
@@ -3997,6 +4001,9 @@ void scst_free_device(struct scst_device *dev)
}
#endif
/* Ensure that ext_blockers_work is done */
flush_scheduled_work();
scst_deinit_threads(&dev->dev_cmd_threads);
scst_pr_cleanup(dev);
@@ -13111,6 +13118,241 @@ out_too_many:
}
EXPORT_SYMBOL_GPL(scst_restore_global_mode_pages);
/* Must be called under dev_lock and BHs off. Might release it, then reacquire. */
void __scst_ext_blocking_done(struct scst_device *dev)
{
bool stop;
TRACE_ENTRY();
TRACE_BLOCK("Notifying ext blockers for dev %s (ext_blocks_cnt %d)",
dev->virt_name, dev->ext_blocks_cnt);
stop = list_empty(&dev->ext_blockers_list);
while (!stop) {
struct scst_ext_blocker *b;
b = list_first_entry(&dev->ext_blockers_list, typeof(*b),
ext_blockers_list_entry);
TRACE_DBG("Notifying async ext blocker %p (cnt %d)", b,
dev->ext_blocks_cnt);
list_del(&b->ext_blockers_list_entry);
stop = list_empty(&dev->ext_blockers_list);
if (stop)
dev->ext_blocking_pending = 0;
spin_unlock_bh(&dev->dev_lock);
b->ext_blocker_done_fn(dev, b->ext_blocker_data,
b->ext_blocker_data_len);
kfree(b);
spin_lock_bh(&dev->dev_lock);
}
TRACE_EXIT();
return;
}
static void scst_ext_blocking_done_fn(struct work_struct *work)
{
struct scst_device *dev = container_of(work, struct scst_device,
ext_blockers_work);
TRACE_ENTRY();
spin_lock_bh(&dev->dev_lock);
WARN_ON(!dev->ext_unblock_scheduled);
/* We might have new ext blocker any time w/o dev_lock */
while (!list_empty(&dev->ext_blockers_list))
__scst_ext_blocking_done(dev);
WARN_ON(!dev->ext_unblock_scheduled);
dev->ext_unblock_scheduled = 0;
spin_unlock_bh(&dev->dev_lock);
TRACE_EXIT();
return;
}
/* Must be called under dev_lock and BHs off */
void scst_ext_blocking_done(struct scst_device *dev)
{
TRACE_ENTRY();
lockdep_assert_held(&dev->dev_lock);
if (dev->ext_unblock_scheduled)
goto out;
TRACE_DBG("Scheduling ext_blockers_work for dev %s", dev->virt_name);
dev->ext_unblock_scheduled = 1;
schedule_work(&dev->ext_blockers_work);
out:
TRACE_EXIT();
return;
}
static void scst_sync_ext_blocking_done(struct scst_device *dev,
uint8_t *data, int len)
{
wait_queue_head_t *w;
TRACE_ENTRY();
w = (void *)*((unsigned long *)data);
wake_up_all(w);
TRACE_EXIT();
return;
}
int scst_ext_block_dev(struct scst_device *dev, bool sync,
ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len)
{
int res;
struct scst_ext_blocker *b;
TRACE_ENTRY();
if (sync)
priv_len = sizeof(void *);
b = kzalloc(sizeof(*b) + priv_len, GFP_KERNEL);
if (b == NULL) {
PRINT_ERROR("Unable to alloc struct scst_ext_blocker with data "
"(size %zd)", sizeof(*b) + priv_len);
res = -ENOMEM;
goto out;
}
TRACE_MGMT_DBG("New (%d) %s ext blocker %p for dev %s", dev->ext_blocks_cnt+1,
sync ? "sync" : "async", b, dev->virt_name);
spin_lock_bh(&dev->dev_lock);
if (dev->strictly_serialized_cmd_waiting) {
/*
* Avoid deadlock when this strictly serialized cmd
* will not proceed stopped on our blocking, so our
* blocking does not proceed as well.
*/
TRACE_DBG("Unstrictlyserialize dev %s", dev->virt_name);
dev->strictly_serialized_cmd_waiting = 0;
/* We will reuse blocking done by the strictly serialized cmd */
} else
scst_block_dev(dev);
dev->ext_blocks_cnt++;
TRACE_DBG("ext_blocks_cnt %d", dev->ext_blocks_cnt);
if (sync && (dev->on_dev_cmd_count == 0)) {
TRACE_DBG("No commands to wait for sync blocking (dev %s)",
dev->virt_name);
spin_unlock_bh(&dev->dev_lock);
goto out_free_success;
}
list_add_tail(&b->ext_blockers_list_entry, &dev->ext_blockers_list);
dev->ext_blocking_pending = 1;
if (sync) {
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(w);
b->ext_blocker_done_fn = scst_sync_ext_blocking_done;
*((void **)&b->ext_blocker_data[0]) = &w;
wait_event_locked(w, (dev->on_dev_cmd_count == 0),
lock_bh, dev->dev_lock);
spin_unlock_bh(&dev->dev_lock);
} else {
b->ext_blocker_done_fn = done_fn;
if (priv_len > 0) {
b->ext_blocker_data_len = priv_len;
memcpy(b->ext_blocker_data, priv, priv_len);
}
if (dev->on_dev_cmd_count == 0) {
TRACE_DBG("No commands to wait for async blocking "
"(dev %s)", dev->virt_name);
if (!dev->ext_unblock_scheduled)
__scst_ext_blocking_done(dev);
spin_unlock_bh(&dev->dev_lock);
} else
spin_unlock_bh(&dev->dev_lock);
}
out_success:
res = 0;
out:
TRACE_EXIT_RES(res);
return res;
out_free_success:
sBUG_ON(!sync);
kfree(b);
goto out_success;
}
void scst_ext_unblock_dev(struct scst_device *dev)
{
TRACE_ENTRY();
spin_lock_bh(&dev->dev_lock);
if (dev->ext_blocks_cnt == 0) {
TRACE_DBG("Nothing to unblock (dev %p)", dev);
goto out_unlock;
}
dev->ext_blocks_cnt--;
TRACE_MGMT_DBG("Ext unblocking for dev %s (left: %d, pending %d)",
dev->virt_name, dev->ext_blocks_cnt,
dev->ext_blocking_pending);
if ((dev->ext_blocks_cnt == 0) && dev->ext_blocking_pending) {
int rc;
/* Wait pending ext blocking to finish */
spin_unlock_bh(&dev->dev_lock);
TRACE_DBG("Ext unblock (dev %s): still pending...",
dev->virt_name);
rc = scst_ext_block_dev(dev, true, NULL, NULL, 0);
if (rc != 0) {
/* Oops, have to poll */
PRINT_WARNING("scst_ext_block_dev(dev %s) failed, "
"switch to polling", dev->virt_name);
spin_lock_bh(&dev->dev_lock);
while (dev->ext_blocking_pending) {
spin_unlock_bh(&dev->dev_lock);
msleep(10);
spin_lock_bh(&dev->dev_lock);
}
spin_unlock_bh(&dev->dev_lock);
} else {
TRACE_DBG("Ext unblock: pending done, unblocking...");
scst_ext_unblock_dev(dev);
}
spin_lock_bh(&dev->dev_lock);
}
scst_unblock_dev(dev);
out_unlock:
spin_unlock_bh(&dev->dev_lock);
TRACE_EXIT();
return;
}
/* Abstract vfs_unlink() for different kernel versions (as possible) */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39)
+7
View File
@@ -715,6 +715,12 @@ void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev,
void scst_block_dev(struct scst_device *dev);
void scst_unblock_dev(struct scst_device *dev);
int scst_ext_block_dev(struct scst_device *dev, bool sync,
ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len);
void scst_ext_unblock_dev(struct scst_device *dev);
void __scst_ext_blocking_done(struct scst_device *dev);
void scst_ext_blocking_done(struct scst_device *dev);
bool __scst_check_blocked_dev(struct scst_cmd *cmd);
/*
@@ -857,6 +863,7 @@ void scst_event_exit(void);
int scst_event_queue_lun_not_found(const struct scst_cmd *cmd);
int scst_event_queue_negative_luns_inquiry(const struct scst_tgt *tgt,
const char *initiator_name);
int scst_event_queue_ext_blocking_done(struct scst_device *dev, void *data, int len);
int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd);
typedef void __printf(2, 3) (*scst_show_fn)(void *arg, const char *fmt, ...);
+129
View File
@@ -3471,8 +3471,137 @@ static struct kobj_attribute dev_threads_pool_type_attr =
scst_dev_sysfs_threads_pool_type_show,
scst_dev_sysfs_threads_pool_type_store);
static ssize_t scst_dev_block_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int pos = 0;
struct scst_device *dev;
TRACE_ENTRY();
dev = container_of(kobj, struct scst_device, dev_kobj);
pos = sprintf(buf, "%d %d\n", ACCESS_ONCE(dev->ext_blocks_cnt),
dev->ext_blocking_pending);
TRACE_EXIT_RES(pos);
return pos;
}
static void scst_sysfs_ext_blocking_done(struct scst_device *dev,
uint8_t *data, int len)
{
scst_event_queue_ext_blocking_done(dev, data, len);
}
static ssize_t scst_dev_block_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int res, data_len = 0, pos = 0;
struct scst_device *dev;
const char *p = buf, *data_start = NULL;
bool sync;
TRACE_ENTRY();
dev = container_of(kobj, struct scst_device, dev_kobj);
switch (*p) {
case '0':
p++;
pos++;
while ((pos < count) && isspace(*p) && (*p != '\0')) {
p++;
pos++;
}
if ((pos != count) && (*p != '\0')) {
PRINT_ERROR("Parse error on %c", *p);
res = -EINVAL;
goto out;
}
TRACE_DBG("Sysfs unblocking (dev %s)", dev->virt_name);
scst_ext_unblock_dev(dev);
res = 0;
goto out;
case '1':
p++;
pos++;
while ((pos < count) && isspace(*p) && (*p != '\0')) {
p++;
pos++;
}
if ((pos == count) || (*p == '\0')) {
data_len = sizeof(void *);
sync = true;
break;
} else if (*p != '1') {
PRINT_ERROR("Parse error on %c", *p);
res = -EINVAL;
goto out;
}
sync = false;
p++;
pos++;
if ((pos == count) || (*p == '\0'))
break;
while ((pos < count) && isspace(*p) && (*p != '\0')) {
p++;
pos++;
}
if ((pos == count) || (*p == '\0'))
break;
data_start = p;
while ((pos < count) && (*p != '\0')) {
p++;
pos++;
data_len++;
}
/* Skip trailing spaces, if any */
while (isspace(*(p-1))) {
p--;
data_len--;
}
break;
default:
PRINT_ERROR("Illegal blocking value %c", *p);
res = -EINVAL;
goto out;
}
TRACE_DBG("Sysfs blocking dev %s (sync %d, data_start %p, "
"data_len %d)", dev->virt_name, sync, data_start, data_len);
if (sync)
res = scst_ext_block_dev(dev, true, NULL, NULL, 0);
else
res = scst_ext_block_dev(dev, false, scst_sysfs_ext_blocking_done,
data_start, data_len);
if (res != 0)
goto out;
res = 0;
out:
if (res == 0)
res = count;
TRACE_EXIT_RES(res);
return res;
}
static struct kobj_attribute dev_block_attr =
__ATTR(block, S_IRUGO | S_IWUSR, scst_dev_block_show,
scst_dev_block_store);
static struct attribute *scst_dev_attrs[] = {
&dev_type_attr.attr,
&dev_block_attr.attr,
NULL,
};
+8
View File
@@ -176,6 +176,14 @@ static void scst_check_unblock_dev(struct scst_cmd *cmd)
}
}
if (unlikely(dev->ext_blocking_pending)) {
if (dev->on_dev_cmd_count == 0) {
TRACE_MGMT_DBG("Releasing pending dev %s extended "
"blocks", dev->virt_name);
scst_ext_blocking_done(dev);
}
}
spin_unlock_bh(&dev->dev_lock);
return;
}