diff --git a/scst/README b/scst/README index 73331a9ec..b14a4a510 100644 --- a/scst/README +++ b/scst/README @@ -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 diff --git a/scst/README_in-tree b/scst/README_in-tree index 14255f774..667d82709 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -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 diff --git a/scst/include/scst.h b/scst/include/scst.h index feb76eff7..69ccd12ba 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -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; diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h index 33ab06e86..f77300126 100644 --- a/scst/include/scst_event.h +++ b/scst/include/scst_event.h @@ -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 */ diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c index fb108cd71..15aad6a6d 100644 --- a/scst/src/scst_event.c +++ b/scst/src/scst_event.c @@ -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) { diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index f9f8139d5..4da51033a 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -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) diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index 0c3afadf2..308c9d9f8 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -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, ...); diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 614ba3312..6f406c900 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -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, }; diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 44539099b..2c5c3e9ee 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -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; }