diff --git a/scst/include/backport.h b/scst/include/backport.h index 64e8b16d7..45c6c75d8 100644 --- a/scst/include/backport.h +++ b/scst/include/backport.h @@ -29,6 +29,7 @@ #include /* kmalloc() */ #include #include +#include #include /* sync_page_range() */ #include #include /* struct scsi_cmnd */ @@ -63,6 +64,24 @@ #endif #endif +/* */ + +#ifdef CONFIG_X86 +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) +static __always_inline unsigned long long rdtsc(void) +{ + return native_read_tsc(); +} +#endif +#else +static __always_inline unsigned long long rdtsc(void) +{ + return 0; +} +#define tsc_khz 1000 +#endif + /* */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) @@ -378,6 +397,23 @@ static inline int __must_check kstrtol(const char *s, unsigned int base, } #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0) +/* Suboptimal algorithm for computing the square root of a 64-bit number */ +static inline u32 int_sqrt64(u64 x) +{ + u32 r = 0, s; + int i; + + for (i = 8 * sizeof(r) - 2; i >= 0;i --) { + s = r + (1 << i); + if (1ll * s * s <= x) + r = s; + } + + return r; +} +#endif + /* */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23) @@ -811,6 +847,20 @@ static inline void put_unaligned_be64(uint64_t i, void *p) } #endif +/* */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) +static inline void *vzalloc(size_t size) +{ + void *p; + + p = vmalloc(size); + if (p) + memset(p, 0, size); + return p; +} +#endif + /* */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) diff --git a/scst/include/scst.h b/scst/include/scst.h index dc0eb4529..a465f6dd8 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -149,6 +149,8 @@ enum scst_cmd_state { /* Checks 1 before target driver's xmit_response() is called */ SCST_CMD_STATE_PRE_XMIT_RESP1, + SCST_CMD_STATE_CSW2, + /* Checks 2 before target driver's xmit_response() is called */ SCST_CMD_STATE_PRE_XMIT_RESP2, @@ -172,6 +174,8 @@ enum scst_cmd_state { /* LUN translation (cmd->tgt_dev assignment) */ SCST_CMD_STATE_INIT, + SCST_CMD_STATE_CSW1, + /* Waiting for scst_restart_cmd() */ SCST_CMD_STATE_PREPROCESSING_DONE_CALLED, @@ -197,6 +201,10 @@ enum scst_cmd_state { SCST_CMD_STATE_XMIT_WAIT, }; +enum { + SCST_CMD_STATE_COUNT = SCST_CMD_STATE_XMIT_WAIT + 1 +}; + /************************************************************* * Can be returned instead of cmd's state by dev handlers' * functions, if the command's state should be set by default @@ -1844,6 +1852,57 @@ struct scst_tgt { #endif }; +/** + * struct scst_lat_stat_entry - SCST command processing latency data + * @last_update: Time of last update of this data structure in 100 ns. + * @count: Number of samples for which statistics have been gathered. + * @last_update_tsc: Time of the last update of this data structure in 100 + * clock cycles. + * @min: Minimum processing time in nanoseconds. + * @max: Maximum processing time. + * @sum: Processing time sum. + * @sumsq: Sum of the squares of the processing times. + * @minc, @maxc, @sumc, @sumsqc: Similar to the above but in hundred clock + * cycles instead of nanoseconds. + * + * Time unit for the uint64_t numbers: 100 ns. + * + * Size: 96 bytes. + */ +struct scst_lat_stat_entry { + ktime_t last_update; + uint32_t count; + uint32_t padding; +#ifdef SCST_MEASURE_CLOCK_CYCLES + uint64_t last_update_tsc; +#endif + uint64_t min; + uint64_t max; + uint64_t sum; + uint64_t sumsq; +#ifdef SCST_MEASURE_CLOCK_CYCLES + uint64_t minc; + uint64_t maxc; + uint64_t sumc; + uint64_t sumsqc; +#endif +}; + +/* + * lat_stats is an array with three indices: + * - Logarithm base 2 of the data length minus 9. + * - Data direction (SCST_DATA_*). + * - SCST command state. + * + * Size: 11 * 4 * 25 * 96 = 105600 bytes. + */ +#define SCST_STATS_LOG2_SZ_OFFSET 9 +#define SCST_STATS_MAX_LOG2_SZ 11 +struct scst_lat_stats { + struct scst_lat_stat_entry + ls[SCST_STATS_MAX_LOG2_SZ][4][SCST_CMD_STATE_COUNT]; +}; + struct scst_io_stat_entry { uint64_t cmd_count; uint64_t io_byte_count; @@ -1971,6 +2030,7 @@ struct scst_session { unsigned int sess_kobj_ready:1; struct kobject sess_kobj; /* session sysfs entry */ + struct kobject *lat_kobj; #endif /* @@ -1981,6 +2041,12 @@ struct scst_session { void (*init_result_fn)(struct scst_session *sess, void *data, int result); void (*unreg_done_fn)(struct scst_session *sess); + + /* + * Latency measurement data. + */ + spinlock_t lat_stats_lock; + struct scst_lat_stats *lat_stats; }; /* @@ -2318,6 +2384,9 @@ struct scst_cmd { unsigned long start_time; + ktime_t init_wait_time; + uint64_t init_wait_tsc; + /* List entry for tgt_dev's deferred (SN, ACA, etc.) lists */ struct list_head deferred_cmd_list_entry; diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index 7c590ac1b..ec3b0f0a0 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -3063,6 +3063,7 @@ int scst_get_cmd_abnormal_done_state(struct scst_cmd *cmd) case SCST_CMD_STATE_INIT_WAIT: case SCST_CMD_STATE_INIT: case SCST_CMD_STATE_PARSE: + case SCST_CMD_STATE_CSW1: if (cmd->preprocessing_only) { res = SCST_CMD_STATE_PREPROCESSING_DONE; break; @@ -3176,6 +3177,7 @@ void scst_set_cmd_abnormal_done_state(struct scst_cmd *cmd) case SCST_CMD_STATE_INIT_WAIT: case SCST_CMD_STATE_INIT: case SCST_CMD_STATE_PARSE: + case SCST_CMD_STATE_CSW1: case SCST_CMD_STATE_PREPROCESSING_DONE: case SCST_CMD_STATE_PREPROCESSING_DONE_CALLED: case SCST_CMD_STATE_PREPARE_SPACE: @@ -6964,6 +6966,7 @@ struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask, sess_cm_list_id_cleanup_work_fn, sess); INIT_WORK(&sess->hw_pending_work, scst_hw_pending_work_fn, sess); #endif + spin_lock_init(&sess->lat_stats_lock); sess->initiator_name = kstrdup(initiator_name, gfp_mask); if (sess->initiator_name == NULL) { @@ -6971,10 +6974,19 @@ struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask, goto out_free; } + if (atomic_read(&scst_measure_latency)) { + sess->lat_stats = vzalloc(sizeof(*sess->lat_stats)); + if (!sess->lat_stats) + goto out_free_name; + } + out: TRACE_EXIT(); return sess; +out_free_name: + kfree(sess->initiator_name); + out_free: kmem_cache_free(scst_sess_cachep, sess); sess = NULL; @@ -7021,6 +7033,7 @@ void scst_free_session(struct scst_session *sess) mutex_unlock(&scst_mutex); kfree(sess->transport_id); + vfree(sess->lat_stats); kfree(sess->initiator_name); if (sess->sess_name != sess->initiator_name) kfree(sess->sess_name); @@ -15683,3 +15696,120 @@ void scst_check_debug_sn(struct scst_cmd *cmd) return; } #endif /* CONFIG_SCST_DEBUG_SN */ + +static void __scst_update_latency_stats(struct scst_cmd *cmd, + struct scst_lat_stat_entry *stat, + struct scst_lat_stat_entry *new_stat, + const ktime_t now, uint64_t nowc) +{ + int64_t delta; +#ifdef SCST_MEASURE_CLOCK_CYCLES + int64_t deltac; +#endif + + if (stat && stat->last_update) { + delta = ktime_to_ns(ktime_sub(now, stat->last_update)); + if (delta < 0 || delta > NSEC_PER_SEC) { + printk_once(KERN_INFO "%d: ignoring large time delta %lld\n", + cmd->state, delta); + delta = 0; + } + delta /= 100; +#ifdef SCST_MEASURE_CLOCK_CYCLES + deltac = nowc - stat->last_update_tsc; + if (deltac < 0 || deltac > tsc_khz * 1000) { + printk_once(KERN_INFO "%d: ignoring large cc delta %lld\n", + cmd->state, deltac); + deltac = 0; + } + deltac /= 100; +#endif + if (stat->count++ > 0) { + if (delta < stat->min) + stat->min = delta; + if (delta > stat->max) + stat->max = delta; +#ifdef SCST_MEASURE_CLOCK_CYCLES + if (deltac < stat->minc) + stat->minc = deltac; + if (deltac > stat->maxc) + stat->maxc = deltac; +#endif + } else { + stat->min = stat->max = delta; +#ifdef SCST_MEASURE_CLOCK_CYCLES + stat->minc = stat->maxc = deltac; +#endif + } + stat->sum += delta; + stat->sumsq += delta * delta; +#ifdef SCST_MEASURE_CLOCK_CYCLES + stat->sumc += deltac; + stat->sumsqc += deltac * deltac; +#endif + } + new_stat->last_update = now; +#ifdef SCST_MEASURE_CLOCK_CYCLES + new_stat->last_update_tsc = nowc; +#endif +} + +/* + * Note: in the code below it has been assumed that expected_data_direction + * and expected_transfer_len_full have been set before scst_cmd_init_done() + * has been called and that these are not changed later on. + */ +void scst_update_latency_stats(struct scst_cmd *cmd, int new_state) +{ + ktime_t now; + uint64_t nowc; + int sz, dir; + struct scst_lat_stat_entry *prev_stat = NULL, *new_stat; + unsigned long flags; + + sBUG_ON(new_state >= SCST_CMD_STATE_COUNT); + + now = ktime_get(); +#ifdef SCST_MEASURE_CLOCK_CYCLES + nowc = rdtsc(); +#else + nowc = 0; +#endif + + /* + * expected_transfer_len_full is only available once the state + * SCST_CMD_STATE_INIT has been reached. + */ + if (new_state == SCST_CMD_STATE_INIT_WAIT) { + cmd->init_wait_time = now; +#ifdef SCST_MEASURE_CLOCK_CYCLES + cmd->init_wait_tsc = nowc; +#endif + return; + } + + WARN_ON_ONCE(!cmd->sess); + + if (!cmd->sess->lat_stats) + return; + + /* To do: subtract size of T10 PI data from data length */ + sz = ilog2(roundup_pow_of_two(cmd->expected_transfer_len_full)) - + SCST_STATS_LOG2_SZ_OFFSET; + if (sz < 0) + sz = 0; + else if (sz >= SCST_STATS_MAX_LOG2_SZ) + sz = SCST_STATS_MAX_LOG2_SZ - 1; + dir = cmd->expected_data_direction & 3; + if (new_state != SCST_CMD_STATE_INIT_WAIT) + prev_stat = &cmd->sess->lat_stats->ls[sz][dir][cmd->state]; + new_stat = &cmd->sess->lat_stats->ls[sz][dir][new_state]; + + spin_lock_irqsave(&cmd->sess->lat_stats_lock, flags); + if (new_state == SCST_CMD_STATE_INIT) + __scst_update_latency_stats(cmd, NULL, prev_stat, + cmd->init_wait_time, + cmd->init_wait_tsc); + __scst_update_latency_stats(cmd, prev_stat, new_stat, now, nowc); + spin_unlock_irqrestore(&cmd->sess->lat_stats_lock, flags); +} diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index 6e8ae20b9..3c3916d58 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -177,6 +177,9 @@ unsigned int scst_max_dev_cmd_mem; int scst_forcibly_close_sessions; int scst_auto_cm_assignment = true; +spinlock_t scst_measure_latency_lock; +atomic_t scst_measure_latency; + module_param_named(scst_threads, scst_threads, int, S_IRUGO); MODULE_PARM_DESC(scst_threads, "SCSI target threads count"); @@ -715,11 +718,15 @@ static const char *const scst_cmd_state_name[] = { [SCST_CMD_STATE_MODE_SELECT_CHECKS] = "MODE_SELECT_CHECKS", [SCST_CMD_STATE_DEV_DONE] = "DEV_DONE", [SCST_CMD_STATE_PRE_XMIT_RESP] = "PRE_XMIT_RESP", + [SCST_CMD_STATE_PRE_XMIT_RESP1] = "PRE_XMIT_RESP1", + [SCST_CMD_STATE_CSW2] = "CSW2", + [SCST_CMD_STATE_PRE_XMIT_RESP2] = "PRE_XMIT_RESP2", [SCST_CMD_STATE_XMIT_RESP] = "XMIT_RESP", [SCST_CMD_STATE_FINISHED] = "FINISHED", [SCST_CMD_STATE_FINISHED_INTERNAL] = "FINISHED_INTERNAL", [SCST_CMD_STATE_INIT_WAIT] = "INIT_WAIT", [SCST_CMD_STATE_INIT] = "INIT", + [SCST_CMD_STATE_CSW1] = "CSW1", [SCST_CMD_STATE_PREPROCESSING_DONE_CALLED] = "PREP_DONE_CALLED", [SCST_CMD_STATE_DATA_WAIT] = "DATA_WAIT", [SCST_CMD_STATE_EXEC_CHECK_BLOCKING] = "EXEC_CHECK_BLOCKING", @@ -2544,6 +2551,7 @@ static int __init init_scst(void) mutex_init(&scst_cmd_threads_mutex); INIT_LIST_HEAD(&scst_cmd_threads_list); cpumask_setall(&default_cpu_mask); + spin_lock_init(&scst_measure_latency_lock); scst_init_threads(&scst_main_cmd_threads); diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index c3e69a81f..d5d5d2ec5 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -87,8 +87,15 @@ extern unsigned long scst_trace_flag; /* Set if new commands initialization is suspended for a while */ #define SCST_FLAG_SUSPENDED 1 -static inline void scst_set_cmd_state(struct scst_cmd *cmd, enum scst_cmd_state new_state) +extern spinlock_t scst_measure_latency_lock; +extern atomic_t scst_measure_latency; +void scst_update_latency_stats(struct scst_cmd *cmd, int new_state); + +static inline void scst_set_cmd_state(struct scst_cmd *cmd, + enum scst_cmd_state new_state) { + if (unlikely(atomic_read(&scst_measure_latency))) + scst_update_latency_stats(cmd, new_state); cmd->state = new_state; } diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index f3fc0f6bd..51baefd45 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -4313,6 +4313,132 @@ void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev) ** Sessions subdirectory implementation **/ +static u64 calc_stddev(u64 sumsq, u64 sum, u32 count) +{ + return int_sqrt64((sumsq - sum * sum / count) / count); +} + +static ssize_t scst_sess_latency_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct scst_session *sess = + container_of(kobj->parent, struct scst_session, sess_kobj); + int res = 0, i, j, k; + long sz; + struct scst_lat_stat_entry *d; + uint64_t avg, stddev; +#ifdef SCST_MEASURE_CLOCK_CYCLES + uint64_t min, max, sumc = 0, sumsqc = 0; +#else + uint64_t sum = 0, sumsq = 0; +#endif + unsigned count = 0, numst = 0; + char state_name[32]; + + switch (attr->attr.name[0]) { + case 'n': j = SCST_DATA_NONE & 3; break; + case 'r': j = SCST_DATA_READ; break; + case 'w': j = SCST_DATA_WRITE; break; + case 'b': j = SCST_DATA_BIDI; break; + default: + return -EINVAL; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) + res = kstrtol(attr->attr.name + 1, 0, &sz); +#else + res = strict_strtol(attr->attr.name + 1, 0, &sz); +#endif + if (WARN_ON(res < 0)) + goto out; + i = ilog2(sz) - SCST_STATS_LOG2_SZ_OFFSET; + if (WARN_ON(i < 0 || i >= SCST_STATS_MAX_LOG2_SZ)) { + res = -EINVAL; + goto out; + } + + res += scnprintf(buf + res, PAGE_SIZE - res, + "state count min max avg stddev\n"); + + spin_lock_irq(&sess->lat_stats_lock); + for (k = 0; k < SCST_CMD_STATE_COUNT; k++) { + struct scst_lat_stats *lat_stats = sess->lat_stats; + + d = &lat_stats->ls[i][j][k]; + if (!lat_stats || d->count == 0 || res >= PAGE_SIZE) + continue; + scst_get_cmd_state_name(state_name, sizeof(state_name), + k); + avg = d->sum / d->count; + stddev = calc_stddev(d->sumsq, d->sum, d->count); + res += scnprintf(buf + res, PAGE_SIZE - res, + "%s %d %lld.%01lld %lld.%01lld %lld.%01lld %lld.%01lld us\n", + state_name, d->count, + d->min / 10, d->min % 10, + d->max / 10, d->max % 10, + avg / 10, avg % 10, + stddev / 10, stddev % 10); +#ifdef SCST_MEASURE_CLOCK_CYCLES + min = d->minc * 10000 / (tsc_khz / 100); + max = d->maxc * 10000 / (tsc_khz / 100); + avg = d->sumc * 10000 / (d->count * 1ull * tsc_khz / 100); + stddev = calc_stddev(d->sumsqc, d->sumc, d->count) + * 1000000 / tsc_khz; + res += scnprintf(buf + res, PAGE_SIZE - res, + "%s %d %lld.%01lld %lld.%01lld %lld.%01lld %lld.%01lld cc -> us\n", + state_name, d->count, + min / 10, min % 10, + max / 10, max % 10, + avg / 10, avg % 10, + stddev / 10, stddev % 10); + sumc += d->sumc; + sumsqc += d->sumsqc; +#else + sum += d->sum; + sumsq += d->sumsq; +#endif + count += d->count; + numst++; + } + spin_unlock_irq(&sess->lat_stats_lock); + + if (count != 0) { +#ifdef SCST_MEASURE_CLOCK_CYCLES + avg = numst * sumc / (count * 1ull * tsc_khz / 1000000); + stddev = calc_stddev(sumsqc, sumc, count) * numst * + 1000000 / tsc_khz; + res += scnprintf(buf + res, PAGE_SIZE - res, + "total %d - - %lld.%01lld %lld.%01lld cc -> us\n", + count / numst, avg / 10, avg % 10, stddev / 10, + stddev % 10); +#else + avg = numst * sum / count; + stddev = calc_stddev(sumsq, sum, count) * numst; + res += scnprintf(buf + res, PAGE_SIZE - res, + "total %d - - %lld.%01lld %lld.%01lld us\n", + count / numst, avg / 10, avg % 10, stddev / 10, + stddev % 10); +#endif + } + +out: + return res; +} + +static ssize_t scst_sess_latency_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct scst_session *sess = + container_of(kobj->parent, struct scst_session, sess_kobj); + + spin_lock_irq(&sess->lat_stats_lock); + BUILD_BUG_ON(sizeof(*sess->lat_stats) != sizeof(struct scst_lat_stats)); + memset(sess->lat_stats, 0, sizeof(*sess->lat_stats)); + spin_unlock_irq(&sess->lat_stats_lock); + + return count; +} + static ssize_t scst_sess_sysfs_commands_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -4679,6 +4805,74 @@ static struct kobj_type scst_session_ktype = { .default_attrs = scst_session_attrs, }; +#define SCST_LAT_ATTRS(size) \ + &sess_lat_attr_n##size.attr, \ + &sess_lat_attr_r##size.attr, \ + &sess_lat_attr_w##size.attr, \ + &sess_lat_attr_b##size.attr + +#define SCST_LAT_ATTR(size) \ + static struct kobj_attribute sess_lat_attr_n##size = \ + __ATTR(n##size, S_IRUGO | S_IWUSR, scst_sess_latency_show,\ + scst_sess_latency_store); \ + static struct kobj_attribute sess_lat_attr_r##size = \ + __ATTR(r##size, S_IRUGO | S_IWUSR, scst_sess_latency_show, \ + scst_sess_latency_store); \ + static struct kobj_attribute sess_lat_attr_w##size = \ + __ATTR(w##size, S_IRUGO | S_IWUSR, scst_sess_latency_show, \ + scst_sess_latency_store); \ + static struct kobj_attribute sess_lat_attr_b##size = \ + __ATTR(b##size, S_IRUGO | S_IWUSR, scst_sess_latency_show, \ + scst_sess_latency_store); +SCST_LAT_ATTR(512); +SCST_LAT_ATTR(1024); +SCST_LAT_ATTR(2048); +SCST_LAT_ATTR(4096); +SCST_LAT_ATTR(8192); +SCST_LAT_ATTR(16384); +SCST_LAT_ATTR(32768); +SCST_LAT_ATTR(65536); +SCST_LAT_ATTR(131072); +SCST_LAT_ATTR(262144); +SCST_LAT_ATTR(524288); + +static const struct attribute *scst_sess_lat_attr[] = { + SCST_LAT_ATTRS(512), + SCST_LAT_ATTRS(1024), + SCST_LAT_ATTRS(2048), + SCST_LAT_ATTRS(4096), + SCST_LAT_ATTRS(8192), + SCST_LAT_ATTRS(16384), + SCST_LAT_ATTRS(32768), + SCST_LAT_ATTRS(65536), + SCST_LAT_ATTRS(131072), + SCST_LAT_ATTRS(262144), + SCST_LAT_ATTRS(524288), + NULL, +}; + +static int scst_create_latency_attrs(struct scst_session *sess) +{ + int res; + + res = -ENOMEM; + sess->lat_kobj = kobject_create_and_add("latency", &sess->sess_kobj); + if (sess->lat_kobj == NULL) + goto out; + + res = sysfs_create_files(sess->lat_kobj, scst_sess_lat_attr); + if (res < 0) + goto out; + +out: + return res; +} + +static void scst_remove_latency_attrs(struct scst_session *sess) +{ + kobject_del(sess->lat_kobj); +} + static int scst_create_sess_luns_link(struct scst_session *sess) { int res; @@ -4764,6 +4958,10 @@ int scst_sess_sysfs_create(struct scst_session *sess) goto out_del; } + res = scst_create_latency_attrs(sess); + if (res != 0) + goto out_del; + out: TRACE_EXIT_RES(res); return res; @@ -4794,6 +4992,7 @@ void scst_sess_sysfs_del(struct scst_session *sess) sess->sess_kobj_release_cmpl = &c; + scst_remove_latency_attrs(sess); kobject_del(&sess->sess_kobj); SCST_KOBJECT_PUT_AND_WAIT(&sess->sess_kobj, "session", &c, @@ -6689,6 +6888,112 @@ static const struct attribute *scst_device_groups_attrs[] = { static struct kobject scst_sysfs_root_kobj; +static ssize_t scst_measure_latency_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", atomic_read(&scst_measure_latency)); +} + +static void scst_free_lat_stats_mem(void) +{ + struct scst_tgt_template *tt; + struct scst_tgt *tgt; + struct scst_session *sess; + + lockdep_assert_held(&scst_mutex); + + list_for_each_entry(tt, &scst_template_list, scst_template_list_entry) { + list_for_each_entry(tgt, &tt->tgt_list, tgt_list_entry) { + list_for_each_entry(sess, &tgt->sess_list, + sess_list_entry) { + vfree(sess->lat_stats); + sess->lat_stats = NULL; + } + } + } +} + +static int scst_alloc_lat_stats_mem(void) +{ + struct scst_tgt_template *tt; + struct scst_tgt *tgt; + struct scst_session *sess; + + lockdep_assert_held(&scst_mutex); + + list_for_each_entry(tt, &scst_template_list, scst_template_list_entry) { + list_for_each_entry(tgt, &tt->tgt_list, tgt_list_entry) { + list_for_each_entry(sess, &tgt->sess_list, + sess_list_entry) { + sess->lat_stats = + vzalloc(sizeof(*sess->lat_stats)); + if (!sess->lat_stats) { + scst_free_lat_stats_mem(); + return -ENOMEM; + } + } + } + } + + return 0; +} + +static ssize_t scst_measure_latency_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + bool prev_val; + long val; + int res; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) + res = kstrtol(buf, 0, &val); +#else + res = strict_strtol(buf, 0, &val); +#endif + if (res < 0) + goto out; + + val = !!val; + + res = scst_suspend_activity(true); + if (res) + goto out; + res = mutex_lock_interruptible(&scst_mutex); + if (res) + goto out_resume; + + spin_lock(&scst_measure_latency_lock); + prev_val = atomic_read(&scst_measure_latency); + atomic_set(&scst_measure_latency, val); + spin_unlock(&scst_measure_latency_lock); + + if (prev_val != val) { + if (val) { + res = scst_alloc_lat_stats_mem(); + if (res) + goto out_unlock; + } else { + scst_free_lat_stats_mem(); + } + } + + res = count; + +out_unlock: + mutex_unlock(&scst_mutex); + +out_resume: + scst_resume_activity(); + +out: + return res; +} + +static struct kobj_attribute scst_measure_latency_attr = + __ATTR(measure_latency, S_IRUGO | S_IWUSR, + scst_measure_latency_show, + scst_measure_latency_store); + static ssize_t scst_threads_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -7238,6 +7543,7 @@ static struct kobj_attribute scst_last_sysfs_mgmt_res_attr = scst_last_sysfs_mgmt_res_show, NULL); static struct attribute *scst_sysfs_root_default_attrs[] = { + &scst_measure_latency_attr.attr, &scst_threads_attr.attr, &scst_setup_id_attr.attr, &scst_max_tasklet_cmd_attr.attr, diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 430c81c40..94f19b202 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -872,6 +872,9 @@ set_state: if (unlikely(rc < 0)) goto out; + if (cmd->state == SCST_CMD_STATE_PARSE) + scst_set_cmd_state(cmd, SCST_CMD_STATE_CSW1); + active: /* Here cmd must not be in any cmd list, no locks */ switch (pref_context) { @@ -4383,6 +4386,9 @@ static int scst_dev_done(struct scst_cmd *cmd) #endif out: + if (cmd->state == SCST_CMD_STATE_PRE_XMIT_RESP1) + scst_set_cmd_state(cmd, SCST_CMD_STATE_CSW2); + TRACE_EXIT_HRES(res); return res; } @@ -5430,6 +5436,7 @@ void scst_process_active_cmd(struct scst_cmd *cmd, bool atomic) do { switch (cmd->state) { + case SCST_CMD_STATE_CSW1: case SCST_CMD_STATE_PARSE: res = scst_parse_cmd(cmd); break; @@ -5507,6 +5514,7 @@ void scst_process_active_cmd(struct scst_cmd *cmd, bool atomic) res = scst_dev_done(cmd); break; + case SCST_CMD_STATE_CSW2: case SCST_CMD_STATE_PRE_XMIT_RESP1: res = scst_pre_xmit_response1(cmd); EXTRACHECKS_BUG_ON(res == SCST_CMD_STATE_RES_NEED_THREAD);