diff --git a/scst/include/scsi_tgt.h b/scst/include/scsi_tgt.h index 4a5b6a6ec..51d57ac17 100644 --- a/scst/include/scsi_tgt.h +++ b/scst/include/scsi_tgt.h @@ -719,6 +719,15 @@ struct scst_dev_type */ int (*attach_tgt) (struct scst_tgt_dev *tgt_dev); + /* + * Called when a session, corresponding to a tgt_dev, is about to be + * unregistered and the tgt_dev - detached. Supposed to be used to + * clean out "stalled" commands, which otherwise could prevent SCST + * from entering into the suspended activity state and, so, + * unregistering the device. + */ + void (*pre_unreg_sess) (struct scst_tgt_dev *tgt_dev); + /* Called when tgt_dev (session) is detaching from the dev handler */ void (*detach_tgt) (struct scst_tgt_dev *tgt_dev); @@ -851,6 +860,9 @@ struct scst_session /* Used if scst_unregister_session() called in wait mode */ struct completion *shutdown_compl; + /* Used to push some unregister_session() works out of IRQ */ + struct work_struct unreg_work; + /* * Functions and data for user callbacks from scst_register_session() * and scst_unregister_session() diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index c1c0874db..b52d19782 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -653,9 +653,9 @@ static int vdisk_do_job(struct scst_cmd *cmd) case WRITE_12: case WRITE_16: fua = (cdb[1] & 0x8); - if (cdb[1] & 0x8) { - TRACE(TRACE_ORDER, "FUA(%d): loff=%Ld, " - "data_len=%Ld", fua, (uint64_t)loff, + if (fua) { + TRACE(TRACE_ORDER, "FUA: loff=%Ld, " + "data_len=%Ld", (uint64_t)loff, (uint64_t)data_len); } break; diff --git a/scst/src/scst_mem.c b/scst/src/scst_mem.c index 6a0618715..60c5cfab3 100644 --- a/scst/src/scst_mem.c +++ b/scst/src/scst_mem.c @@ -41,6 +41,15 @@ * of the existing SLAB code. */ +/* Chosen to have one page per slab for all orders */ +#ifdef CONFIG_DEBUG_SLAB +#define SGV_MAX_LOCAL_SLAB_ORDER 4 +#else +#define SGV_MAX_LOCAL_SLAB_ORDER 5 +#endif + +static int sgv_max_local_order, sgv_max_trans_order; + atomic_t sgv_other_total_alloc; DECLARE_MUTEX(scst_sgv_pool_mutex); @@ -229,20 +238,55 @@ out_no_mem: goto out; } -static inline struct scatterlist *sgv_alloc_sg_entries(int pages_to_alloc, - unsigned long gfp_mask) +static int sgv_alloc_sg_entries(struct sgv_pool_obj *obj, + int pages_to_alloc, int order, unsigned long gfp_mask) { - int esz; - struct scatterlist *res; + int sz, tsz = 0; + int res = 0; - esz = pages_to_alloc * sizeof(*res); - res = (struct scatterlist*)kzalloc(esz, gfp_mask); - if (unlikely(res == NULL)) { + TRACE_ENTRY(); + + sz = pages_to_alloc * sizeof(obj->sg_entries[0]); + + obj->sg_entries = (struct scatterlist*)kzalloc(sz, gfp_mask); + if (unlikely(obj->sg_entries == NULL)) { TRACE(TRACE_OUT_OF_MEM, "Allocation of sgv_pool_obj " - "SG vector failed (size %d)", esz); + "SG vector failed (size %d)", sz); + res = -ENOMEM; + goto out; + } + + if (obj->owner_pool->clustered) { + if (order <= sgv_max_trans_order) { + obj->trans_tbl = (struct trans_tbl_ent*)obj->sg_entries_data; + /* + * No need to clear trans_tbl, if needed, it will be + * fully rewritten in scst_alloc_sg_entries() + */ + } else { + tsz = pages_to_alloc * sizeof(obj->trans_tbl[0]); + obj->trans_tbl = (struct trans_tbl_ent*)kzalloc(tsz, gfp_mask); + if (unlikely(obj->trans_tbl == NULL)) { + TRACE(TRACE_OUT_OF_MEM, "Allocation of trans_tbl " + "failed (size %d)", tsz); + res = -ENOMEM; + goto out_free; + } + } } + TRACE_MEM("pages_to_alloc %d, order %d, sz %d, tsz %d, obj %p, " + "sg_entries %p, trans_tbl %p", pages_to_alloc, order, + sz, tsz, obj, obj->sg_entries, obj->trans_tbl); + +out: + TRACE_EXIT_RES(res); return res; + +out_free: + kfree(obj->sg_entries); + obj->sg_entries = NULL; + goto out; } struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, @@ -254,7 +298,6 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, struct scatterlist *res; int pages_to_alloc; struct kmem_cache *cache; - struct trans_tbl_ent *trans_tbl; int no_cached = flags & SCST_POOL_ALLOC_NO_CACHED; sBUG_ON(size == 0); @@ -268,13 +311,10 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, if (*sgv != NULL) { obj = *sgv; TRACE_MEM("Supplied sgv_obj %p", obj); - pages_to_alloc = obj->sg_count; + pages_to_alloc = (1 << order); cache = obj->owner_cache; - trans_tbl = obj->trans_tbl; - EXTRACHECKS_BUG_ON(obj->sg_entries != NULL); - obj->sg_entries = sgv_alloc_sg_entries(pages_to_alloc, gfp_mask); - if (unlikely(obj->sg_entries == NULL)) - goto out_fail_free; + EXTRACHECKS_BUG_ON(cache != pool->caches[order]); + EXTRACHECKS_BUG_ON(obj->sg_count != 0); goto alloc; } @@ -287,30 +327,51 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, "sgv_pool_obj failed (size %d)", size); goto out_fail; } - if (obj->sg_entries != NULL) { + if (obj->sg_count != 0) { TRACE_MEM("Cached sgv_obj %p", obj); EXTRACHECKS_BUG_ON(obj->owner_cache != cache); atomic_inc(&pool->acc.hit_alloc); atomic_inc(&pool->cache_acc[order].hit_alloc); goto success; } - obj->owner_cache = cache; pages_to_alloc = (1 << order); if (flags & SCST_POOL_NO_ALLOC_ON_CACHE_MISS) { - if (flags & SCST_POOL_RETURN_OBJ_ON_ALLOC_FAIL) - goto out_return; - else + if (!(flags & SCST_POOL_RETURN_OBJ_ON_ALLOC_FAIL)) goto out_fail_free; } TRACE_MEM("Brand new sgv_obj %p", obj); - obj->sg_entries = sgv_alloc_sg_entries(pages_to_alloc, gfp_mask); - if (unlikely(obj->sg_entries == NULL)) - goto out_fail_free; - trans_tbl = obj->trans_tbl; - /* - * No need to clear trans_tbl, if needed, it will be fully - * rewritten in scst_alloc_sg_entries() - */ + obj->owner_cache = cache; + obj->owner_pool = pool; + if (order <= sgv_max_local_order) { + obj->sg_entries = obj->sg_entries_data; + TRACE_MEM("sg_entries %p", obj->sg_entries); + memset(obj->sg_entries, 0, + pages_to_alloc*sizeof(obj->sg_entries[0])); + if (pool->clustered) { + obj->trans_tbl = (struct trans_tbl_ent*) + (obj->sg_entries + pages_to_alloc); + TRACE_MEM("trans_tbl %p", obj->trans_tbl); + /* We want to have all the data on the same page */ + EXTRACHECKS_BUG_ON(((unsigned long)obj->sg_entries & PAGE_MASK) != + ((unsigned long)&obj->trans_tbl[pages_to_alloc-1] & PAGE_MASK)); + /* + * No need to clear trans_tbl, if needed, it will + * be fully rewritten in scst_alloc_sg_entries() + */ + } else { + /* We want to have all the data on the same page */ + EXTRACHECKS_BUG_ON(((unsigned long)obj->sg_entries & PAGE_MASK) != + ((unsigned long)&obj->sg_entries[pages_to_alloc-1] & PAGE_MASK)); + } + } else { + if (unlikely(sgv_alloc_sg_entries(obj, pages_to_alloc, + order, gfp_mask) != 0)) + goto out_fail_free; + } + + if ((flags & SCST_POOL_NO_ALLOC_ON_CACHE_MISS) && + (flags & SCST_POOL_RETURN_OBJ_ON_ALLOC_FAIL)) + goto out_return; } else { int sz; pages_to_alloc = pages; @@ -324,24 +385,22 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, "sgv_pool_obj failed (size %d)", size); goto out_fail; } - obj->sg_entries = (struct scatterlist*)obj->trans_tbl; - trans_tbl = NULL; + obj->owner_pool = pool; + obj->sg_entries = obj->sg_entries_data; TRACE_MEM("Big or no_cached sgv_obj %p (size %d)", obj, sz); } obj->allocator_priv = priv; - obj->owner_pool = pool; alloc: obj->sg_count = scst_alloc_sg_entries(obj->sg_entries, - pages_to_alloc, gfp_mask, pool->clustered, trans_tbl, + pages_to_alloc, gfp_mask, pool->clustered, obj->trans_tbl, &pool->alloc_fns, priv); if (unlikely(obj->sg_count <= 0)) { - if ((flags & SCST_POOL_RETURN_OBJ_ON_ALLOC_FAIL) && cache) { - kfree(obj->sg_entries); - obj->sg_entries = NULL; + obj->sg_count = 0; + if ((flags & SCST_POOL_RETURN_OBJ_ON_ALLOC_FAIL) && cache) goto out_return1; - } else + else goto out_fail_free_sg_entries; } @@ -353,7 +412,7 @@ success: if (pool->clustered) cnt = obj->trans_tbl[pages-1].sg_num; else - cnt = obj->sg_count; + cnt = pages; sg = cnt-1; obj->orig_sg = sg; obj->orig_length = obj->sg_entries[sg].length; @@ -389,7 +448,6 @@ out_return: out_return1: *sgv = obj; - obj->sg_count = pages_to_alloc; TRACE_MEM("Returning failed sgv_obj %p (count %d)", obj, *count); out_return2: @@ -398,7 +456,12 @@ out_return2: goto out; out_fail_free_sg_entries: - if (cache) { + if (obj->sg_entries != obj->sg_entries_data) { + if (obj->trans_tbl != (struct trans_tbl_ent*)obj->sg_entries_data) { + /* kfree() handles NULL parameter */ + kfree(obj->trans_tbl); + obj->trans_tbl = NULL; + } kfree(obj->sg_entries); obj->sg_entries = NULL; } @@ -428,8 +491,7 @@ void sgv_pool_free(struct sgv_pool_obj *sgv) "sg_count %d, allocator_priv %p", sgv, sgv->owner_cache, sgv->sg_entries, sgv->sg_count, sgv->allocator_priv); if (sgv->owner_cache != NULL) { - if (likely(sgv->sg_entries != NULL)) - sgv->sg_entries[sgv->orig_sg].length = sgv->orig_length; + sgv->sg_entries[sgv->orig_sg].length = sgv->orig_length; kmem_cache_free(sgv->owner_cache, sgv); } else { sgv->owner_pool->alloc_fns.free_pages_fn(sgv->sg_entries, @@ -455,9 +517,16 @@ static void sgv_ctor(void *data, struct kmem_cache *c, unsigned long flags) static void sgv_dtor(void *data, struct kmem_cache *k, unsigned long f) { struct sgv_pool_obj *obj = data; - if (obj->sg_entries) { + if (obj->sg_count != 0) { obj->owner_pool->alloc_fns.free_pages_fn(obj->sg_entries, obj->sg_count, obj->allocator_priv); + } + if (obj->sg_entries != obj->sg_entries_data) { + if (obj->trans_tbl != (struct trans_tbl_ent*)obj->sg_entries_data) { + /* kfree() handles NULL parameter */ + kfree(obj->trans_tbl); + obj->trans_tbl = NULL; + } kfree(obj->sg_entries); } return; @@ -522,23 +591,31 @@ int sgv_pool_init(struct sgv_pool *pool, const char *name, int clustered) pool->alloc_fns.alloc_pages_fn = scst_alloc_sys_pages; pool->alloc_fns.free_pages_fn = scst_free_sys_sg_entries; - TRACE_MEM("name %s, sizeof(*obj)=%zd, clustered=%d, " - "sizeof(obj->trans_tbl[0])=%zd", name, sizeof(*obj), clustered, - sizeof(obj->trans_tbl[0])); + TRACE_MEM("name %s, sizeof(*obj)=%zd, clustered=%d", name, sizeof(*obj), + clustered); strncpy(pool->name, name, sizeof(pool->name)-1); pool->name[sizeof(pool->name)-1] = '\0'; for(i = 0; i < SGV_POOL_ELEMENTS; i++) { - int size, pages; + int size; atomic_set(&pool->cache_acc[i].total_alloc, 0); atomic_set(&pool->cache_acc[i].hit_alloc, 0); - pages = 1 << i; - size = sizeof(*obj) + pages * - (clustered ? sizeof(obj->trans_tbl[0]) : 0); - TRACE_MEM("pages=%d, size=%d", pages, size); + /* + * We need one page per SLAB. That's hackish, but is there + * any other choice? + */ + if (i <= SGV_MAX_LOCAL_SLAB_ORDER) { + int pages = 1 << i; + size = sizeof(*obj) + pages * + (sizeof(obj->sg_entries[0]) + + (clustered ? sizeof(obj->trans_tbl[0]) : 0)); + } else + size = PAGE_SIZE - 96; + + TRACE_MEM("pages=%d, size=%d", 1 << i, size); scnprintf(pool->cache_names[i], sizeof(pool->cache_names[i]), "%s-%luK", name, (PAGE_SIZE >> 10) << i); @@ -643,6 +720,18 @@ int scst_sgv_pools_init(struct scst_sgv_pools *pools) TRACE_ENTRY(); + sgv_max_local_order = get_order( + ((((PAGE_SIZE - sizeof(struct sgv_pool_obj)) / + (sizeof(struct trans_tbl_ent) + sizeof(struct scatterlist))) * + PAGE_SIZE) & PAGE_MASK)); + + sgv_max_trans_order = get_order( + ((((PAGE_SIZE - sizeof(struct sgv_pool_obj)) / + (sizeof(struct trans_tbl_ent))) * PAGE_SIZE) & PAGE_MASK)); + + TRACE_MEM("sgv_max_local_order %d, sgv_max_trans_order %d", + sgv_max_local_order, sgv_max_trans_order); + atomic_set(&sgv_other_total_alloc, 0); res = sgv_pool_init(&pools->norm, "sgv", 0); diff --git a/scst/src/scst_mem.h b/scst/src/scst_mem.h index 2d59b9228..0460b64b0 100644 --- a/scst/src/scst_mem.h +++ b/scst/src/scst_mem.h @@ -41,9 +41,10 @@ struct sgv_pool_obj int orig_sg; int orig_length; int sg_count; - struct scatterlist *sg_entries; void *allocator_priv; - struct trans_tbl_ent trans_tbl[0]; + struct trans_tbl_ent *trans_tbl; + struct scatterlist *sg_entries; + struct scatterlist sg_entries_data[0]; }; struct sgv_pool_acc diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 7ad7aa398..f59841736 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -3536,13 +3536,13 @@ static int scst_abort_all_nexus_loss_sess(struct scst_mgmt_cmd *mcmd, __scst_block_dev(dev); spin_unlock_bh(&dev->dev_lock); - rc = scst_call_dev_task_mgmt_fn(mcmd, tgt_dev, 0); - if ((rc < 0) && (mcmd->status == SCST_MGMT_STATUS_SUCCESS)) - mcmd->status = rc; - __scst_abort_task_set(mcmd, tgt_dev, !nexus_loss, 1); if (nexus_loss) scst_reset_tgt_dev(tgt_dev, 1); + + rc = scst_call_dev_task_mgmt_fn(mcmd, tgt_dev, 0); + if ((rc < 0) && (mcmd->status == SCST_MGMT_STATUS_SUCCESS)) + mcmd->status = rc; } up(&scst_mutex); @@ -3586,14 +3586,14 @@ static int scst_abort_all_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd, { int rc; + __scst_abort_task_set(mcmd, tgt_dev, !nexus_loss, 1); + if (nexus_loss) + scst_reset_tgt_dev(tgt_dev, 1); + rc = scst_call_dev_task_mgmt_fn(mcmd, tgt_dev, 0); if ((rc < 0) && (mcmd->status == SCST_MGMT_STATUS_SUCCESS)) mcmd->status = rc; - - __scst_abort_task_set(mcmd, tgt_dev, !nexus_loss, 1); - if (nexus_loss) - scst_reset_tgt_dev(tgt_dev, 1); } } @@ -4197,6 +4197,42 @@ out_free: goto out; } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static void scst_unreg_work_fn(void *p) +#else +static void scst_unreg_work_fn(struct work_struct *work) +#endif +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + struct scst_session *sess = (struct scst_session*)p; +#else + struct scst_session *sess = container_of(work, struct scst_session, + unreg_work); +#endif + struct scst_tgt_dev *tgt_dev; + + TRACE_ENTRY(); + + down(&scst_mutex); + list_for_each_entry(tgt_dev, &sess->sess_tgt_dev_list, + sess_tgt_dev_list_entry) { + struct scst_dev_type *handler = tgt_dev->dev->handler; + if (handler && handler->pre_unreg_sess) { + TRACE_DBG("Calling dev handler's pre_unreg_sess(%p)", + tgt_dev); + handler->pre_unreg_sess(tgt_dev); + TRACE_DBG("%s", "Dev handler's pre_unreg_sess() " + "returned"); + } + } + up(&scst_mutex); + + scst_sess_put(sess); + + TRACE_EXIT(); + return; +} + /* * Must not been called in parallel with scst_rx_cmd() or * scst_rx_mgmt_fn_*() for the same sess @@ -4235,7 +4271,13 @@ void scst_unregister_session(struct scst_session *sess, int wait, tm_dbg_task_mgmt("UNREGISTER SESSION", 1); - scst_sess_put(sess); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + INIT_WORK(&sess->unreg_work, scst_unreg_work_fn, sess); +#else + INIT_WORK(&sess->unreg_work, scst_unreg_work_fn); +#endif + + schedule_work(&sess->unreg_work); if (wait) { TRACE_DBG("Waiting for session %p to complete", sess);