/* * scst_sysfs.c * * Copyright (C) 2009 Daniel Henrique Debonzi * Copyright (C) 2009 - 2018 Vladislav Bolkhovitin * Copyright (C) 2007 - 2018 Western Digital Corporation * * 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 #include #include #include #include #include #include #include #ifdef INSIDE_KERNEL_TREE #include #else #include "scst.h" #endif #include "scst_priv.h" #include "scst_pres.h" #include "scst_mem.h" #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) #ifdef CONFIG_LOCKDEP static struct lock_class_key scst_tgtt_key; static struct lockdep_map scst_tgtt_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_tgtt_kref", &scst_tgtt_key); static struct lock_class_key scst_tgt_key; static struct lockdep_map scst_tgt_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_tgt_kref", &scst_tgt_key); static struct lock_class_key scst_devt_key; static struct lockdep_map scst_devt_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_devt_kref", &scst_devt_key); static struct lock_class_key scst_dev_key; struct lockdep_map scst_dev_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_dev_kref", &scst_dev_key); EXPORT_SYMBOL(scst_dev_dep_map); static struct lock_class_key scst_sess_key; static struct lockdep_map scst_sess_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_sess_kref", &scst_sess_key); static struct lock_class_key scst_acg_dev_key; static struct lockdep_map scst_acg_dev_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_acg_dev_kref", &scst_acg_dev_key); static struct lock_class_key scst_acg_key; static struct lockdep_map scst_acg_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_acg_kref", &scst_acg_key); static struct lock_class_key scst_tgt_dev_key; static struct lockdep_map scst_tgt_dev_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_tgt_dev_kref", &scst_tgt_dev_key); static struct lock_class_key scst_dg_key; static struct lockdep_map scst_dg_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_dg_kref", &scst_dg_key); static struct lock_class_key scst_tg_key; static struct lockdep_map scst_tg_dep_map = STATIC_LOCKDEP_MAP_INIT("scst_tg_kref", &scst_tg_key); #endif #endif static DECLARE_COMPLETION(scst_sysfs_root_release_completion); static struct kobject *scst_targets_kobj; static struct kobject *scst_devices_kobj; static struct kobject *scst_handlers_kobj; static struct kobject *scst_device_groups_kobj; static const char *const scst_dev_handler_types[] = { "Direct-access device (e.g., magnetic disk)", "Sequential-access device (e.g., magnetic tape)", "Printer device", "Processor device", "Write-once device (e.g., some optical disks)", "CD-ROM device", "Scanner device (obsolete)", "Optical memory device (e.g., some optical disks)", "Medium changer device (e.g., jukeboxes)", "Communications device (obsolete)", "Defined by ASC IT8 (Graphic arts pre-press devices)", "Defined by ASC IT8 (Graphic arts pre-press devices)", "Storage array controller device (e.g., RAID)", "Enclosure services device", "Simplified direct-access device (e.g., magnetic disk)", "Optical card reader/writer device" }; #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) static DEFINE_MUTEX(scst_log_mutex); static struct scst_trace_log scst_trace_tbl[] = { { TRACE_OUT_OF_MEM, "out_of_mem" }, { TRACE_MINOR, "minor" }, { TRACE_SG_OP, "sg" }, { TRACE_MEMORY, "mem" }, { TRACE_BUFF, "buff" }, #ifndef GENERATING_UPSTREAM_PATCH { TRACE_ENTRYEXIT, "entryexit" }, #endif { TRACE_PID, "pid" }, { TRACE_LINE, "line" }, { TRACE_FUNCTION, "function" }, { TRACE_DEBUG, "debug" }, { TRACE_SPECIAL, "special" }, { TRACE_SCSI, "scsi" }, { TRACE_MGMT, "mgmt" }, { TRACE_MGMT_DEBUG, "mgmt_dbg" }, { TRACE_FLOW_CONTROL, "flow_control" }, { TRACE_PRES, "pr" }, { 0, NULL } }; static struct scst_trace_log scst_local_trace_tbl[] = { { TRACE_RTRY, "retry" }, { TRACE_SCSI_SERIALIZING, "scsi_serializing" }, { TRACE_DATA_SEND, "data_send" }, { TRACE_DATA_RECEIVED, "data_received" }, { TRACE_BLOCKING, "block" }, { 0, NULL } }; static void scst_read_trace_tbl(const struct scst_trace_log *tbl, char *buf, unsigned long log_level, int *pos) { const struct scst_trace_log *t = tbl; if (t == NULL) goto out; while (t->token) { if (log_level & t->val) { *pos += sprintf(&buf[*pos], "%s%s", (*pos == 0) ? "" : " | ", t->token); } t++; } out: return; } static ssize_t scst_trace_level_show(const struct scst_trace_log *local_tbl, unsigned long log_level, char *buf, const char *help) { int pos = 0; scst_read_trace_tbl(scst_trace_tbl, buf, log_level, &pos); scst_read_trace_tbl(local_tbl, buf, log_level, &pos); pos += sprintf(&buf[pos], "\n\n\nUsage:\n" " echo \"all|none|default\" >trace_level\n" " echo \"value DEC|0xHEX|0OCT\" >trace_level\n" " echo \"add|del TOKEN\" >trace_level\n" #ifdef CONFIG_SCST_DEBUG "\nwhere TOKEN is one of [debug, function, line, pid,\n" #ifndef GENERATING_UPSTREAM_PATCH " entryexit, buff, mem, sg, out_of_mem,\n" #else " buff, mem, sg, out_of_mem,\n" #endif " special, scsi, mgmt, minor,\n" " mgmt_dbg, scsi_serializing,\n" " retry, pr, block%s]\n", #else /* CONFIG_SCST_DEBUG */ "\nwhere TOKEN is one of [function, line, pid," "out_of_mem, special, scsi, mgmt, minor," "scsi_serializing, retry, pr%s]\n", #endif /* CONFIG_SCST_DEBUG */ help != NULL ? help : ""); return pos; } static int scst_write_trace(const char *buf, size_t length, unsigned long *log_level, unsigned long default_level, const char *name, const struct scst_trace_log *tbl) { int res; int action; unsigned long level = 0, oldlevel; char *buffer, *p, *pp; const struct scst_trace_log *t; enum { SCST_TRACE_ACTION_ALL = 1, SCST_TRACE_ACTION_NONE = 2, SCST_TRACE_ACTION_DEFAULT = 3, SCST_TRACE_ACTION_ADD = 4, SCST_TRACE_ACTION_DEL = 5, SCST_TRACE_ACTION_VALUE = 6, }; TRACE_ENTRY(); if ((buf == NULL) || (length == 0)) { res = -EINVAL; goto out; } buffer = kasprintf(GFP_KERNEL, "%.*s", (int)length, buf); if (buffer == NULL) { PRINT_ERROR("Unable to alloc intermediate buffer (size %zd)", length+1); res = -ENOMEM; goto out; } TRACE_DBG("buffer %s", buffer); pp = buffer; p = scst_get_next_lexem(&pp); if (strcasecmp("all", p) == 0) { action = SCST_TRACE_ACTION_ALL; } else if (strcasecmp("none", p) == 0 || strcasecmp("null", p) == 0) { action = SCST_TRACE_ACTION_NONE; } else if (strcasecmp("default", p) == 0) { action = SCST_TRACE_ACTION_DEFAULT; } else if (strcasecmp("add", p) == 0) { action = SCST_TRACE_ACTION_ADD; } else if (strcasecmp("del", p) == 0) { action = SCST_TRACE_ACTION_DEL; } else if (strcasecmp("value", p) == 0) { action = SCST_TRACE_ACTION_VALUE; } else { PRINT_ERROR("Unknown action \"%s\"", p); res = -EINVAL; goto out_free; } p = scst_get_next_lexem(&pp); switch (action) { case SCST_TRACE_ACTION_ALL: level = TRACE_ALL; break; case SCST_TRACE_ACTION_DEFAULT: level = default_level; break; case SCST_TRACE_ACTION_NONE: level = TRACE_NULL; break; case SCST_TRACE_ACTION_ADD: case SCST_TRACE_ACTION_DEL: if (tbl) { t = tbl; while (t->token) { if (!strcasecmp(p, t->token)) { level = t->val; break; } t++; } } if (level == 0) { t = scst_trace_tbl; while (t->token) { if (!strcasecmp(p, t->token)) { level = t->val; break; } t++; } } if (level == 0) { PRINT_ERROR("Unknown token \"%s\"", p); res = -EINVAL; goto out_free; } break; case SCST_TRACE_ACTION_VALUE: res = kstrtoul(p, 0, &level); if (res != 0) { PRINT_ERROR("Invalid trace value \"%s\"", p); res = -EINVAL; goto out_free; } break; } oldlevel = *log_level; switch (action) { case SCST_TRACE_ACTION_ADD: *log_level |= level; break; case SCST_TRACE_ACTION_DEL: *log_level &= ~level; break; default: *log_level = level; break; } PRINT_INFO("Changed trace level for \"%s\": old 0x%08lx, new 0x%08lx", name, oldlevel, *log_level); res = length; out_free: kfree(buffer); out: TRACE_EXIT_RES(res); return res; } #endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 34) && \ (!defined(RHEL_MAJOR) || RHEL_MAJOR -0 < 6 || \ (RHEL_MAJOR -0 == 6 && RHEL_MINOR -0 < 6)) /* ** Backported sysfs functions. **/ static int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr) { int err = 0; int i; for (i = 0; ptr[i] && !err; i++) err = sysfs_create_file(kobj, ptr[i]); if (err) while (--i >= 0) sysfs_remove_file(kobj, ptr[i]); return err; } static void sysfs_remove_files(struct kobject *kobj, const struct attribute **ptr) { int i; for (i = 0; ptr[i]; i++) sysfs_remove_file(kobj, ptr[i]); } #endif /* ** Sysfs work **/ static DEFINE_SPINLOCK(sysfs_work_lock); static LIST_HEAD(sysfs_work_list); static DECLARE_WAIT_QUEUE_HEAD(sysfs_work_waitQ); static int active_sysfs_works; static int last_sysfs_work_res; static struct task_struct *sysfs_work_thread; /* * scst_alloc_sysfs_work() - allocates a sysfs work */ int scst_alloc_sysfs_work(int (*sysfs_work_fn)(struct scst_sysfs_work_item *), bool read_only_action, struct scst_sysfs_work_item **res_work) { int res = 0; struct scst_sysfs_work_item *work; TRACE_ENTRY(); if (sysfs_work_fn == NULL) { PRINT_ERROR("%s", "sysfs_work_fn is NULL"); res = -EINVAL; goto out; } *res_work = NULL; work = kzalloc(sizeof(*work), GFP_KERNEL); if (work == NULL) { PRINT_ERROR("Unable to alloc sysfs work (size %zd)", sizeof(*work)); res = -ENOMEM; goto out; } work->read_only_action = read_only_action; kref_init(&work->sysfs_work_kref); init_completion(&work->sysfs_work_done); work->sysfs_work_fn = sysfs_work_fn; *res_work = work; out: TRACE_EXIT_RES(res); return res; } EXPORT_SYMBOL(scst_alloc_sysfs_work); static void scst_sysfs_work_release(struct kref *kref) { struct scst_sysfs_work_item *work; TRACE_ENTRY(); work = container_of(kref, struct scst_sysfs_work_item, sysfs_work_kref); TRACE_DBG("Freeing sysfs work %p (buf %p)", work, work->buf); kfree(work->buf); kfree(work->res_buf); kfree(work); TRACE_EXIT(); return; } /* * scst_sysfs_work_get() - increases ref counter of the sysfs work */ void scst_sysfs_work_get(struct scst_sysfs_work_item *work) { kref_get(&work->sysfs_work_kref); } EXPORT_SYMBOL(scst_sysfs_work_get); /* * scst_sysfs_work_put() - decreases ref counter of the sysfs work */ void scst_sysfs_work_put(struct scst_sysfs_work_item *work) { kref_put(&work->sysfs_work_kref, scst_sysfs_work_release); } EXPORT_SYMBOL(scst_sysfs_work_put); /* Called under sysfs_work_lock and drops/reacquire it inside */ static void scst_process_sysfs_works(void) __releases(&sysfs_work_lock) __acquires(&sysfs_work_lock) { struct scst_sysfs_work_item *work; TRACE_ENTRY(); while (!list_empty(&sysfs_work_list)) { work = list_first_entry(&sysfs_work_list, struct scst_sysfs_work_item, sysfs_work_list_entry); list_del(&work->sysfs_work_list_entry); spin_unlock(&sysfs_work_lock); TRACE_DBG("Sysfs work %p", work); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) if (work->dep_map) { mutex_acquire(work->dep_map, 0, 0, _RET_IP_); lock_acquired(work->dep_map, _RET_IP_); } #endif work->work_res = work->sysfs_work_fn(work); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) if (work->dep_map) mutex_release(work->dep_map, 0, _RET_IP_); #endif spin_lock(&sysfs_work_lock); if (!work->read_only_action) last_sysfs_work_res = work->work_res; active_sysfs_works--; spin_unlock(&sysfs_work_lock); complete_all(&work->sysfs_work_done); kref_put(&work->sysfs_work_kref, scst_sysfs_work_release); spin_lock(&sysfs_work_lock); } TRACE_EXIT(); return; } static inline int test_sysfs_work_list(void) { int res = !list_empty(&sysfs_work_list) || unlikely(kthread_should_stop()); return res; } static int sysfs_work_thread_fn(void *arg) { bool one_time_only = (bool)arg; TRACE_ENTRY(); if (!one_time_only) PRINT_INFO("User interface thread started"); current->flags |= PF_NOFREEZE; set_user_nice(current, -10); spin_lock(&sysfs_work_lock); while (!kthread_should_stop()) { if (one_time_only && !test_sysfs_work_list()) break; wait_event_locked(sysfs_work_waitQ, test_sysfs_work_list(), lock, sysfs_work_lock); scst_process_sysfs_works(); } spin_unlock(&sysfs_work_lock); if (!one_time_only) { /* * If kthread_should_stop() is true, we are guaranteed to be * on the module unload, so both lists must be empty. */ sBUG_ON(!list_empty(&sysfs_work_list)); PRINT_INFO("User interface thread finished"); } TRACE_EXIT(); return 0; } /* * scst_sysfs_queue_wait_work() - waits for the work to complete * * Returns status of the completed work or -EAGAIN if the work not * completed before timeout. In the latter case a user should poll * last_sysfs_mgmt_res until it returns the result of the processing. */ int scst_sysfs_queue_wait_work(struct scst_sysfs_work_item *work) { int res = 0, rc; unsigned long timeout = 15*HZ; struct task_struct *t; static atomic_t uid_thread_name = ATOMIC_INIT(0); TRACE_ENTRY(); spin_lock(&sysfs_work_lock); TRACE_DBG("Adding sysfs work %p to the list", work); list_add_tail(&work->sysfs_work_list_entry, &sysfs_work_list); active_sysfs_works++; kref_get(&work->sysfs_work_kref); spin_unlock(&sysfs_work_lock); wake_up(&sysfs_work_waitQ); /* * We can have a dead lock possibility like: the sysfs thread is waiting * for the last put during some object unregistration and at the same * time another queued work is having reference on that object taken and * waiting for attention from the sysfs thread. Generally, all sysfs * functions calling kobject_get() and then queuing sysfs thread job * affected by this. This is especially dangerous in read only cases, * like vdev_sysfs_filename_show(). * * So, to eliminate that deadlock we will create an extra sysfs thread * for each queued sysfs work. This thread will quit as soon as it will * see that there is not more queued works to process. */ t = kthread_run(sysfs_work_thread_fn, (void *)true, "scst_uid%d", atomic_inc_return(&uid_thread_name)); if (IS_ERR(t)) PRINT_ERROR("kthread_run() for user interface thread %d " "failed: %d", atomic_read(&uid_thread_name), (int)PTR_ERR(t)); #ifdef CONFIG_SCST_DEBUG_SYSFS_EAGAIN { static int cnt; if (!work->read_only_action || cnt++ % 4 < 3) { /* * Helps testing user space code that writes to or * reads from SCST sysfs variables. */ timeout = 0; rc = 0; res = -EAGAIN; goto out_put; } } #endif while (1) { rc = wait_for_completion_interruptible_timeout( &work->sysfs_work_done, timeout); if (rc == 0) { if (!mutex_is_locked(&scst_mutex)) { TRACE_DBG("scst_mutex not locked, continue " "waiting (work %p)", work); timeout = 5*HZ; continue; } TRACE_MGMT_DBG("Time out waiting for work %p", work); res = -EAGAIN; goto out_put; } else if (rc < 0) { res = rc; goto out_put; } break; } res = work->work_res; out_put: kref_put(&work->sysfs_work_kref, scst_sysfs_work_release); TRACE_EXIT_RES(res); return res; } EXPORT_SYMBOL(scst_sysfs_queue_wait_work); /* No locks */ static int scst_check_grab_tgtt_ptr(struct scst_tgt_template *tgtt) { int res = 0; struct scst_tgt_template *tt; TRACE_ENTRY(); mutex_lock(&scst_mutex); list_for_each_entry(tt, &scst_template_list, scst_template_list_entry) { if (tt == tgtt) { tgtt->tgtt_active_sysfs_works_count++; goto out_unlock; } } TRACE_DBG("Tgtt %p not found", tgtt); res = -ENOENT; out_unlock: mutex_unlock(&scst_mutex); TRACE_EXIT_RES(res); return res; } /* No locks */ static void scst_ungrab_tgtt_ptr(struct scst_tgt_template *tgtt) { TRACE_ENTRY(); mutex_lock(&scst_mutex); tgtt->tgtt_active_sysfs_works_count--; mutex_unlock(&scst_mutex); TRACE_EXIT(); return; } /* scst_mutex supposed to be locked */ static int scst_check_tgt_acg_ptrs(struct scst_tgt *tgt, struct scst_acg *acg) { int res = 0; struct scst_tgt_template *tgtt; list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *t; list_for_each_entry(t, &tgtt->tgt_list, tgt_list_entry) { if (t == tgt) { struct scst_acg *a; if (acg == NULL) goto out; if (acg == tgt->default_acg) goto out; list_for_each_entry(a, &tgt->tgt_acg_list, acg_list_entry) { if (a == acg) goto out; } } } } TRACE_DBG("Tgt %p/ACG %p not found", tgt, acg); res = -ENOENT; out: TRACE_EXIT_RES(res); return res; } /* scst_mutex supposed to be locked */ static int scst_check_devt_ptr(struct scst_dev_type *devt, struct list_head *list) { int res = 0; struct scst_dev_type *dt; TRACE_ENTRY(); list_for_each_entry(dt, list, dev_type_list_entry) { if (dt == devt) goto out; } TRACE_DBG("Devt %p not found", devt); res = -ENOENT; out: TRACE_EXIT_RES(res); return res; } /* scst_mutex supposed to be locked */ static int scst_check_dev_ptr(struct scst_device *dev) { int res = 0; struct scst_device *d; TRACE_ENTRY(); list_for_each_entry(d, &scst_dev_list, dev_list_entry) { if (d == dev) goto out; } TRACE_DBG("Dev %p not found", dev); res = -ENOENT; out: TRACE_EXIT_RES(res); return res; } /* No locks */ static int scst_check_grab_devt_ptr(struct scst_dev_type *devt, struct list_head *list) { int res = 0; struct scst_dev_type *dt; TRACE_ENTRY(); mutex_lock(&scst_mutex); list_for_each_entry(dt, list, dev_type_list_entry) { if (dt == devt) { devt->devt_active_sysfs_works_count++; goto out_unlock; } } TRACE_DBG("Devt %p not found", devt); res = -ENOENT; out_unlock: mutex_unlock(&scst_mutex); TRACE_EXIT_RES(res); return res; } /* No locks */ static void scst_ungrab_devt_ptr(struct scst_dev_type *devt) { TRACE_ENTRY(); mutex_lock(&scst_mutex); devt->devt_active_sysfs_works_count--; mutex_unlock(&scst_mutex); TRACE_EXIT(); return; } /* ** Regular SCST sysfs ops **/ static ssize_t scst_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct kobj_attribute *kobj_attr; kobj_attr = container_of(attr, struct kobj_attribute, attr); return kobj_attr->show(kobj, kobj_attr, buf); } static ssize_t scst_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct kobj_attribute *kobj_attr; kobj_attr = container_of(attr, struct kobj_attribute, attr); if (kobj_attr->store) return kobj_attr->store(kobj, kobj_attr, buf, count); else return -EIO; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34)) const struct sysfs_ops scst_sysfs_ops = { #else struct sysfs_ops scst_sysfs_ops = { #endif .show = scst_show, .store = scst_store, }; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34)) const struct sysfs_ops *scst_sysfs_get_sysfs_ops(void) #else struct sysfs_ops *scst_sysfs_get_sysfs_ops(void) #endif { return &scst_sysfs_ops; } EXPORT_SYMBOL_GPL(scst_sysfs_get_sysfs_ops); /* ** Target Template **/ static void scst_tgtt_release(struct kobject *kobj) { struct scst_tgt_template *tgtt; TRACE_ENTRY(); tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); if (tgtt->tgtt_kobj_release_cmpl) complete_all(tgtt->tgtt_kobj_release_cmpl); TRACE_EXIT(); return; } static struct kobj_type tgtt_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_tgtt_release, }; #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) static ssize_t scst_tgtt_trace_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt_template *tgtt; tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); return scst_trace_level_show(tgtt->trace_tbl, tgtt->trace_flags ? *tgtt->trace_flags : 0, buf, tgtt->trace_tbl_help); } static ssize_t scst_tgtt_trace_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_tgt_template *tgtt; TRACE_ENTRY(); tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); res = mutex_lock_interruptible(&scst_log_mutex); if (res != 0) goto out; res = scst_write_trace(buf, count, tgtt->trace_flags, tgtt->default_trace_flags, tgtt->name, tgtt->trace_tbl); mutex_unlock(&scst_log_mutex); out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute tgtt_trace_attr = __ATTR(trace_level, S_IRUGO | S_IWUSR, scst_tgtt_trace_level_show, scst_tgtt_trace_level_store); #endif /* #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */ static ssize_t scst_tgtt_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add_target target_name [parameters]\" >mgmt\n" " echo \"del_target target_name\" >mgmt\n" "%s%s" "%s" "\n" "where parameters are one or more " "param_name=value pairs separated by ';'\n\n" "%s%s%s%s%s%s%s%s%s%s\n"; struct scst_tgt_template *tgtt; tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, help, (tgtt->tgtt_optional_attributes != NULL) ? " echo \"add_attribute \" >mgmt\n" " echo \"del_attribute \" >mgmt\n" : "", (tgtt->tgt_optional_attributes != NULL) ? " echo \"add_target_attribute target_name \" >mgmt\n" " echo \"del_target_attribute target_name \" >mgmt\n" : "", (tgtt->mgmt_cmd_help) ? tgtt->mgmt_cmd_help : "", (tgtt->mgmt_cmd_help) ? "\n" : "", (tgtt->add_target_parameters != NULL) ? "The following parameters available: " : "", (tgtt->add_target_parameters != NULL) ? tgtt->add_target_parameters : "", (tgtt->add_target_parameters != NULL) ? "\n" : "", (tgtt->tgtt_optional_attributes != NULL) ? "The following target driver attributes available: " : "", (tgtt->tgtt_optional_attributes != NULL) ? tgtt->tgtt_optional_attributes : "", (tgtt->tgtt_optional_attributes != NULL) ? "\n" : "", (tgtt->tgt_optional_attributes != NULL) ? "The following target attributes available: " : "", (tgtt->tgt_optional_attributes != NULL) ? tgtt->tgt_optional_attributes : "", (tgtt->tgt_optional_attributes != NULL) ? "\n" : ""); } static int scst_process_tgtt_mgmt_store(char *buffer, struct scst_tgt_template *tgtt) { int res = 0; char *p, *pp, *target_name; TRACE_ENTRY(); TRACE_DBG("buffer %s", buffer); /* Check if our pointer is still alive and, if yes, grab it */ if (scst_check_grab_tgtt_ptr(tgtt) != 0) goto out; pp = buffer; p = scst_get_next_lexem(&pp); if (strcasecmp("add_target", p) == 0) { target_name = scst_get_next_lexem(&pp); if (*target_name == '\0') { PRINT_ERROR("%s", "Target name required"); res = -EINVAL; goto out_ungrab; } res = tgtt->add_target(target_name, pp); } else if (strcasecmp("del_target", p) == 0) { target_name = scst_get_next_lexem(&pp); if (*target_name == '\0') { PRINT_ERROR("%s", "Target name required"); res = -EINVAL; goto out_ungrab; } p = scst_get_next_lexem(&pp); if (*p != '\0') goto out_syntax_err; res = tgtt->del_target(target_name); } else if (tgtt->mgmt_cmd != NULL) { scst_restore_token_str(p, pp); res = tgtt->mgmt_cmd(buffer); } else { PRINT_ERROR("Unknown action \"%s\"", p); res = -EINVAL; goto out_ungrab; } out_ungrab: scst_ungrab_tgtt_ptr(tgtt); out: TRACE_EXIT_RES(res); return res; out_syntax_err: PRINT_ERROR("Syntax error on \"%s\"", p); res = -EINVAL; goto out_ungrab; } static int scst_tgtt_mgmt_store_work_fn(struct scst_sysfs_work_item *work) { return scst_process_tgtt_mgmt_store(work->buf, work->tgtt); } static ssize_t scst_tgtt_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; char *buffer; struct scst_sysfs_work_item *work; struct scst_tgt_template *tgtt; TRACE_ENTRY(); tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (buffer == NULL) { res = -ENOMEM; goto out; } res = scst_alloc_sysfs_work(scst_tgtt_mgmt_store_work_fn, false, &work); if (res != 0) goto out_free; work->buf = buffer; work->tgtt = tgtt; res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; out_free: kfree(buffer); goto out; } static struct kobj_attribute scst_tgtt_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tgtt_mgmt_show, scst_tgtt_mgmt_store); static ssize_t scst_tgtt_dif_capable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_tgt_template *tgtt; TRACE_ENTRY(); tgtt = container_of(kobj, struct scst_tgt_template, tgtt_kobj); EXTRACHECKS_BUG_ON(!tgtt->dif_supported); pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "dif_supported"); if (tgtt->hw_dif_type1_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_type1_supported"); if (tgtt->hw_dif_type2_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_type2_supported"); if (tgtt->hw_dif_type3_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_type3_supported"); if (tgtt->hw_dif_ip_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_ip_supported"); if (tgtt->hw_dif_same_sg_layout_required) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_same_sg_layout_required"); pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "\n"); if (tgtt->supported_dif_block_sizes) { const int *p = tgtt->supported_dif_block_sizes; int j; pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "Supported blocks: "); j = pos; while (*p != 0) { pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "%s%d", (j == pos) ? "" : ", ", *p); p++; } } TRACE_EXIT_RES(pos); return pos; } static struct kobj_attribute scst_tgtt_dif_capable_attr = __ATTR(dif_capabilities, S_IRUGO, scst_tgtt_dif_capable_show, NULL); /* * Creates an attribute entry for target driver. */ int scst_create_tgtt_attr(struct scst_tgt_template *tgtt, struct kobj_attribute *attribute) { int res; res = sysfs_create_file(&tgtt->tgtt_kobj, &attribute->attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for target driver %s", attribute->attr.name, tgtt->name); goto out; } out: return res; } EXPORT_SYMBOL(scst_create_tgtt_attr); int scst_tgtt_sysfs_create(struct scst_tgt_template *tgtt) { int res = 0; TRACE_ENTRY(); res = kobject_init_and_add(&tgtt->tgtt_kobj, &tgtt_ktype, scst_targets_kobj, tgtt->name); if (res != 0) { PRINT_ERROR("Can't add tgtt %s to sysfs", tgtt->name); goto out; } if (tgtt->add_target != NULL) { res = sysfs_create_file(&tgtt->tgtt_kobj, &scst_tgtt_mgmt.attr); if (res != 0) { PRINT_ERROR("Can't add mgmt attr for target driver %s", tgtt->name); goto out_del; } } if (tgtt->tgtt_attrs) { res = sysfs_create_files(&tgtt->tgtt_kobj, tgtt->tgtt_attrs); if (res != 0) { PRINT_ERROR("Can't add attributes for target " "driver %s", tgtt->name); goto out_del; } } if (tgtt->dif_supported) { res = sysfs_create_file(&tgtt->tgtt_kobj, &scst_tgtt_dif_capable_attr.attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for target driver %s", scst_tgtt_dif_capable_attr.attr.name, tgtt->name); goto out; } } #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) if (tgtt->trace_flags != NULL) { res = sysfs_create_file(&tgtt->tgtt_kobj, &tgtt_trace_attr.attr); if (res != 0) { PRINT_ERROR("Can't add trace_flag for target " "driver %s", tgtt->name); goto out_del; } } #endif out: TRACE_EXIT_RES(res); return res; out_del: scst_tgtt_sysfs_del(tgtt); goto out; } void scst_kobject_put_and_wait(struct kobject *kobj, const char *category, struct completion *c #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) && defined(CONFIG_LOCKDEP) , struct lockdep_map *dep_map #endif ) { char *name; TRACE_ENTRY(); name = kstrdup(kobject_name(kobj), GFP_KERNEL); kobject_put(kobj); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) mutex_acquire(dep_map, 0, 0, _RET_IP_); #endif if (wait_for_completion_timeout(c, HZ) > 0) goto out_free; PRINT_INFO("Waiting for release of sysfs entry for %s %s (%d refs)", category, name ? : "(?)", kref_read(&kobj->kref)); wait_for_completion(c); PRINT_INFO("Finished waiting for release of %s %s sysfs entry", category, name ? : "(?)"); out_free: #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) lock_acquired(dep_map, _RET_IP_); mutex_release(dep_map, 0, _RET_IP_); #endif kfree(name); TRACE_EXIT(); return; } EXPORT_SYMBOL(scst_kobject_put_and_wait); /* * Must not be called under scst_mutex, due to possible deadlock with * sysfs ref counting in sysfs works (it is waiting for the last put, but * the last ref counter holder is waiting for scst_mutex) */ void scst_tgtt_sysfs_del(struct scst_tgt_template *tgtt) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); tgtt->tgtt_kobj_release_cmpl = &c; kobject_del(&tgtt->tgtt_kobj); SCST_KOBJECT_PUT_AND_WAIT(&tgtt->tgtt_kobj, "target template", &c, &scst_tgtt_dep_map); TRACE_EXIT(); return; } /* ** Target directory implementation **/ static void scst_tgt_release(struct kobject *kobj) { struct scst_tgt *tgt; TRACE_ENTRY(); tgt = container_of(kobj, struct scst_tgt, tgt_kobj); if (tgt->tgt_kobj_release_cmpl) complete_all(tgt->tgt_kobj_release_cmpl); TRACE_EXIT(); return; } static int scst_parse_add_repl_param(struct scst_acg *acg, struct scst_device *dev, char *pp, unsigned long *virt_lun, bool *read_only) { int res; char *e; *read_only = false; e = scst_get_next_lexem(&pp); res = kstrtoul(e, 0, virt_lun); if (res != 0) { PRINT_ERROR("Valid LUN required for dev %s (res %d)", dev->virt_name, res); goto out; } else if (*virt_lun > SCST_MAX_LUN) { PRINT_ERROR("Too big LUN %ld (max %d)", *virt_lun, SCST_MAX_LUN); res = -EINVAL; goto out; } while (1) { unsigned long val; char *param = scst_get_next_token_str(&pp); char *p, *pp; if (param == NULL) break; p = scst_get_next_lexem(¶m); if (*p == '\0') { PRINT_ERROR("Syntax error at %s (device %s)", param, dev->virt_name); res = -EINVAL; goto out; } pp = scst_get_next_lexem(¶m); if (*pp == '\0') { PRINT_ERROR("Parameter %s value missed for device %s", p, dev->virt_name); res = -EINVAL; goto out; } if (scst_get_next_lexem(¶m)[0] != '\0') { PRINT_ERROR("Too many parameter %s values (device %s)", p, dev->virt_name); res = -EINVAL; goto out; } res = kstrtoul(pp, 0, &val); if (res != 0) { PRINT_ERROR("kstrtoul() for %s failed: %d " "(device %s)", pp, res, dev->virt_name); goto out; } if (strcasecmp("read_only", p) == 0) { *read_only = !!val; TRACE_DBG("READ ONLY %d", *read_only); } else { PRINT_ERROR("Unknown parameter %s (device %s)", p, dev->virt_name); res = -EINVAL; goto out; } } res = 0; out: return res; } static int __scst_process_luns_mgmt_store(char *buffer, struct scst_tgt *tgt, struct scst_acg *acg, bool tgt_kobj) { int res, action; bool read_only; char *p, *pp; unsigned long virt_lun; struct scst_acg_dev *acg_dev = NULL, *acg_dev_tmp; struct scst_device *d, *dev = NULL; enum { SCST_LUN_ACTION_ADD = 1, SCST_LUN_ACTION_DEL = 2, SCST_LUN_ACTION_REPLACE = 3, SCST_LUN_ACTION_CLEAR = 4, }; bool replace_gen_ua = true; TRACE_ENTRY(); TRACE_DBG("buffer %s", buffer); pp = buffer; p = scst_get_next_lexem(&pp); if (strcasecmp("add", p) == 0) { action = SCST_LUN_ACTION_ADD; } else if (strcasecmp("del", p) == 0) { action = SCST_LUN_ACTION_DEL; } else if (strcasecmp("replace", p) == 0) { action = SCST_LUN_ACTION_REPLACE; replace_gen_ua = true; } else if (strcasecmp("replace_no_ua", p) == 0) { action = SCST_LUN_ACTION_REPLACE; replace_gen_ua = false; } else if (strcasecmp("clear", p) == 0) { action = SCST_LUN_ACTION_CLEAR; } else { PRINT_ERROR("Unknown action \"%s\"", p); res = -EINVAL; goto out; } res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out; /* Check if tgt and acg not already freed while we were coming here */ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0) goto out_unlock; if ((action != SCST_LUN_ACTION_CLEAR) && (action != SCST_LUN_ACTION_DEL)) { p = scst_get_next_lexem(&pp); list_for_each_entry(d, &scst_dev_list, dev_list_entry) { if (!strcmp(d->virt_name, p)) { dev = d; TRACE_DBG("Device %p (%s) found", dev, p); break; } } if (dev == NULL) { PRINT_ERROR("Device '%s' not found", p); res = -EINVAL; goto out_unlock; } } switch (action) { case SCST_LUN_ACTION_ADD: { unsigned int flags = SCST_ADD_LUN_GEN_UA; res = scst_parse_add_repl_param(acg, dev, pp, &virt_lun, &read_only); if (res != 0) goto out_unlock; acg_dev = NULL; list_for_each_entry(acg_dev_tmp, &acg->acg_dev_list, acg_dev_list_entry) { if (acg_dev_tmp->lun == virt_lun) { acg_dev = acg_dev_tmp; break; } } if (acg_dev != NULL) { PRINT_ERROR("virt lun %ld already exists in group %s", virt_lun, acg->acg_name); res = -EEXIST; goto out_unlock; } if (read_only) flags |= SCST_ADD_LUN_READ_ONLY; res = scst_acg_add_lun(acg, tgt_kobj ? tgt->tgt_luns_kobj : acg->luns_kobj, dev, virt_lun, flags, NULL); if (res != 0) goto out_unlock; break; } case SCST_LUN_ACTION_REPLACE: { unsigned int flags = replace_gen_ua ? SCST_REPL_LUN_GEN_UA : 0; res = scst_parse_add_repl_param(acg, dev, pp, &virt_lun, &read_only); if (res != 0) goto out_unlock; flags |= read_only ? SCST_ADD_LUN_READ_ONLY : 0; res = scst_acg_repl_lun(acg, tgt_kobj ? tgt->tgt_luns_kobj : acg->luns_kobj, dev, virt_lun, flags); if (res != 0) goto out_unlock; break; } case SCST_LUN_ACTION_DEL: p = scst_get_next_lexem(&pp); res = kstrtoul(p, 0, &virt_lun); if (res != 0) goto out_unlock; if (scst_get_next_lexem(&pp)[0] != '\0') { PRINT_ERROR("Too many parameters for del LUN %ld: %s", virt_lun, p); res = -EINVAL; goto out_unlock; } res = scst_acg_del_lun(acg, virt_lun, true); if (res != 0) goto out_unlock; break; case SCST_LUN_ACTION_CLEAR: if (scst_get_next_lexem(&pp)[0] != '\0') { PRINT_ERROR("Too many parameters for clear: %s", p); res = -EINVAL; goto out_unlock; } PRINT_INFO("Removed all devices from group %s", acg->acg_name); list_for_each_entry_safe(acg_dev, acg_dev_tmp, &acg->acg_dev_list, acg_dev_list_entry) { res = scst_acg_del_lun(acg, acg_dev->lun, list_is_last(&acg_dev->acg_dev_list_entry, &acg->acg_dev_list)); if (res != 0) goto out_unlock; } break; } res = 0; out_unlock: mutex_unlock(&scst_mutex); out: TRACE_EXIT_RES(res); return res; } static int scst_luns_mgmt_store_work_fn(struct scst_sysfs_work_item *work) { return __scst_process_luns_mgmt_store(work->buf, work->tgt, work->acg, work->is_tgt_kobj); } static ssize_t __scst_acg_mgmt_store(struct scst_acg *acg, const char *buf, size_t count, bool is_tgt_kobj, int (*sysfs_work_fn)(struct scst_sysfs_work_item *)) { int res; char *buffer; struct scst_sysfs_work_item *work; TRACE_ENTRY(); buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (buffer == NULL) { res = -ENOMEM; goto out; } res = scst_alloc_sysfs_work(sysfs_work_fn, false, &work); if (res != 0) goto out_free; work->buf = buffer; work->tgt = acg->tgt; work->acg = acg; work->is_tgt_kobj = is_tgt_kobj; res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; out_free: kfree(buffer); goto out; } static ssize_t __scst_luns_mgmt_store(struct scst_acg *acg, bool tgt_kobj, const char *buf, size_t count) { return __scst_acg_mgmt_store(acg, buf, count, tgt_kobj, scst_luns_mgmt_store_work_fn); } static ssize_t scst_luns_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add H:C:I:L lun [parameters]\" >mgmt\n" " echo \"add VNAME lun [parameters]\" >mgmt\n" " echo \"del lun\" >mgmt\n" " echo \"replace H:C:I:L lun [parameters]\" >mgmt\n" " echo \"replace VNAME lun [parameters]\" >mgmt\n" " echo \"replace_no_ua H:C:I:L lun [parameters]\" >mgmt\n" " echo \"replace_no_ua VNAME lun [parameters]\" >mgmt\n" " echo \"clear\" >mgmt\n" "\n" "where parameters are one or more " "param_name=value pairs separated by ';'\n" "\nThe following parameters available: read_only.\n"; return sprintf(buf, "%s", help); } static ssize_t scst_luns_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj->parent, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; res = __scst_luns_mgmt_store(acg, true, buf, count); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_luns_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_luns_mgmt_show, scst_luns_mgmt_store); static ssize_t __scst_acg_addr_method_show(struct scst_acg *acg, char *buf) { int res; switch (acg->addr_method) { case SCST_LUN_ADDR_METHOD_FLAT: res = sprintf(buf, "FLAT\n"); break; case SCST_LUN_ADDR_METHOD_PERIPHERAL: res = sprintf(buf, "PERIPHERAL\n"); break; case SCST_LUN_ADDR_METHOD_LUN: res = sprintf(buf, "LUN\n"); break; default: res = sprintf(buf, "UNKNOWN\n"); break; } if (acg->addr_method != acg->tgt->tgtt->preferred_addr_method) res += sprintf(&buf[res], "%s\n", SCST_SYSFS_KEY_MARK); return res; } static ssize_t __scst_acg_addr_method_store(struct scst_acg *acg, const char *buf, size_t count) { int res = count; if (strncasecmp(buf, "FLAT", min_t(int, 4, count)) == 0) acg->addr_method = SCST_LUN_ADDR_METHOD_FLAT; else if (strncasecmp(buf, "PERIPHERAL", min_t(int, 10, count)) == 0) acg->addr_method = SCST_LUN_ADDR_METHOD_PERIPHERAL; else if (strncasecmp(buf, "LUN", min_t(int, 3, count)) == 0) acg->addr_method = SCST_LUN_ADDR_METHOD_LUN; else { PRINT_ERROR("Unknown address method %s", buf); res = -EINVAL; } TRACE_DBG("acg %p, addr_method %d", acg, acg->addr_method); return res; } static ssize_t scst_tgt_addr_method_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; return __scst_acg_addr_method_show(acg, buf); } static ssize_t scst_tgt_addr_method_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; res = __scst_acg_addr_method_store(acg, buf, count); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tgt_addr_method = __ATTR(addr_method, S_IRUGO | S_IWUSR, scst_tgt_addr_method_show, scst_tgt_addr_method_store); static ssize_t __scst_acg_io_grouping_type_show(struct scst_acg *acg, char *buf) { int res; switch (acg->acg_io_grouping_type) { case SCST_IO_GROUPING_AUTO: res = sprintf(buf, "%s\n", SCST_IO_GROUPING_AUTO_STR); break; case SCST_IO_GROUPING_THIS_GROUP_ONLY: res = sprintf(buf, "%s\n%s\n", SCST_IO_GROUPING_THIS_GROUP_ONLY_STR, SCST_SYSFS_KEY_MARK); break; case SCST_IO_GROUPING_NEVER: res = sprintf(buf, "%s\n%s\n", SCST_IO_GROUPING_NEVER_STR, SCST_SYSFS_KEY_MARK); break; default: res = sprintf(buf, "%d\n%s\n", acg->acg_io_grouping_type, SCST_SYSFS_KEY_MARK); break; } return res; } static int __scst_acg_process_io_grouping_type_store(struct scst_tgt *tgt, struct scst_acg *acg, int io_grouping_type) { int res = 0; struct scst_acg_dev *acg_dev; TRACE_DBG("tgt %p, acg %p, io_grouping_type %d", tgt, acg, io_grouping_type); res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); if (res != 0) goto out; res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out_resume; /* Check if tgt and acg not already freed while we were coming here */ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0) goto out_unlock; acg->acg_io_grouping_type = io_grouping_type; list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) { int rc; scst_stop_dev_threads(acg_dev->dev); rc = scst_create_dev_threads(acg_dev->dev); if (rc != 0) res = rc; } out_unlock: mutex_unlock(&scst_mutex); out_resume: scst_resume_activity(); out: return res; } static int __scst_acg_io_grouping_type_store_work_fn(struct scst_sysfs_work_item *work) { return __scst_acg_process_io_grouping_type_store(work->tgt, work->acg, work->io_grouping_type); } static ssize_t __scst_acg_io_grouping_type_store(struct scst_acg *acg, const char *buf, size_t count) { int res = 0; int prev = acg->acg_io_grouping_type; long io_grouping_type; struct scst_sysfs_work_item *work; if (strncasecmp(buf, SCST_IO_GROUPING_AUTO_STR, min_t(int, strlen(SCST_IO_GROUPING_AUTO_STR), count)) == 0) io_grouping_type = SCST_IO_GROUPING_AUTO; else if (strncasecmp(buf, SCST_IO_GROUPING_THIS_GROUP_ONLY_STR, min_t(int, strlen(SCST_IO_GROUPING_THIS_GROUP_ONLY_STR), count)) == 0) io_grouping_type = SCST_IO_GROUPING_THIS_GROUP_ONLY; else if (strncasecmp(buf, SCST_IO_GROUPING_NEVER_STR, min_t(int, strlen(SCST_IO_GROUPING_NEVER_STR), count)) == 0) io_grouping_type = SCST_IO_GROUPING_NEVER; else { res = kstrtol(buf, 0, &io_grouping_type); if ((res != 0) || (io_grouping_type <= 0)) { PRINT_ERROR("Unknown or not allowed I/O grouping type " "%s", buf); res = -EINVAL; goto out; } } if (prev == io_grouping_type) goto out; res = scst_alloc_sysfs_work(__scst_acg_io_grouping_type_store_work_fn, false, &work); if (res != 0) goto out; work->tgt = acg->tgt; work->acg = acg; work->io_grouping_type = io_grouping_type; res = scst_sysfs_queue_wait_work(work); out: return res; } static ssize_t scst_tgt_io_grouping_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; return __scst_acg_io_grouping_type_show(acg, buf); } static ssize_t scst_tgt_io_grouping_type_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; res = __scst_acg_io_grouping_type_store(acg, buf, count); if (res != 0) goto out; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tgt_io_grouping_type = __ATTR(io_grouping_type, S_IRUGO | S_IWUSR, scst_tgt_io_grouping_type_show, scst_tgt_io_grouping_type_store); static ssize_t __scst_acg_black_hole_show(struct scst_acg *acg, char *buf) { int res, t = acg->acg_black_hole_type; res = sprintf(buf, "%d\n", t); return res; } static ssize_t __scst_acg_black_hole_store(struct scst_acg *acg, const char *buf, size_t count) { int res = 0; int prev, t; struct scst_session *sess; prev = acg->acg_black_hole_type; if ((buf == NULL) || (count == 0)) { res = 0; goto out; } mutex_lock(&scst_mutex); BUILD_BUG_ON((SCST_ACG_BLACK_HOLE_NONE != 0) || (SCST_ACG_BLACK_HOLE_CMD != 1) || (SCST_ACG_BLACK_HOLE_ALL != 2) || (SCST_ACG_BLACK_HOLE_DATA_CMD != 3) || (SCST_ACG_BLACK_HOLE_DATA_MCMD != 4)); switch (buf[0]) { case '0': acg->acg_black_hole_type = SCST_ACG_BLACK_HOLE_NONE; break; case '1': acg->acg_black_hole_type = SCST_ACG_BLACK_HOLE_CMD; break; case '2': acg->acg_black_hole_type = SCST_ACG_BLACK_HOLE_ALL; break; case '3': acg->acg_black_hole_type = SCST_ACG_BLACK_HOLE_DATA_CMD; break; case '4': acg->acg_black_hole_type = SCST_ACG_BLACK_HOLE_DATA_MCMD; break; default: PRINT_ERROR("%s: Requested action not understood: %s", __func__, buf); res = -EINVAL; goto out_unlock; } t = acg->acg_black_hole_type; if (prev == t) goto out_unlock; list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) { int i; rcu_read_lock(); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { if (t != SCST_ACG_BLACK_HOLE_NONE) set_bit(SCST_TGT_DEV_BLACK_HOLE, &tgt_dev->tgt_dev_flags); else clear_bit(SCST_TGT_DEV_BLACK_HOLE, &tgt_dev->tgt_dev_flags); } } rcu_read_unlock(); } PRINT_INFO("Black hole set to %d for ACG %s", t, acg->acg_name); out_unlock: mutex_unlock(&scst_mutex); out: return res; } static ssize_t scst_tgt_black_hole_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; return __scst_acg_black_hole_show(acg, buf); } static ssize_t scst_tgt_black_hole_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; res = __scst_acg_black_hole_store(acg, buf, count); if (res != 0) goto out; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tgt_black_hole = __ATTR(black_hole, S_IRUGO | S_IWUSR, scst_tgt_black_hole_show, scst_tgt_black_hole_store); static ssize_t __scst_acg_cpu_mask_show(struct scst_acg *acg, char *buf) { int res; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 28) res = cpumask_scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, acg->acg_cpu_mask); #elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) res = cpumask_scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, &acg->acg_cpu_mask); #else res = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%*pb", cpumask_pr_args(&acg->acg_cpu_mask)); #endif if (!cpumask_equal(&acg->acg_cpu_mask, &default_cpu_mask)) res += sprintf(&buf[res], "\n%s\n", SCST_SYSFS_KEY_MARK); return res; } static int __scst_acg_process_cpu_mask_store(struct scst_tgt *tgt, struct scst_acg *acg, cpumask_t *cpu_mask) { int res = 0; struct scst_session *sess; TRACE_DBG("tgt %p, acg %p", tgt, acg); res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out; /* Check if tgt and acg not already freed while we were coming here */ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0) goto out_unlock; cpumask_copy(&acg->acg_cpu_mask, cpu_mask); list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) { int i; rcu_read_lock(); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct scst_tgt_dev *tgt_dev; struct list_head *head = &sess->sess_tgt_dev_list[i]; list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { int rc; if (tgt_dev->active_cmd_threads != &tgt_dev->tgt_dev_cmd_threads) continue; rc = scst_set_thr_cpu_mask(tgt_dev->active_cmd_threads, cpu_mask); if (rc != 0) PRINT_ERROR("Setting CPU affinity" " failed: %d", rc); } } rcu_read_unlock(); if (tgt->tgtt->report_aen != NULL) { struct scst_aen *aen; int rc; aen = scst_alloc_aen(sess, 0); if (aen == NULL) { PRINT_ERROR("Unable to notify target driver %s " "about cpu_mask change", tgt->tgt_name); continue; } aen->event_fn = SCST_AEN_CPU_MASK_CHANGED; TRACE_DBG("Calling target's %s report_aen(%p)", tgt->tgtt->name, aen); rc = tgt->tgtt->report_aen(aen); TRACE_DBG("Target's %s report_aen(%p) returned %d", tgt->tgtt->name, aen, rc); if (rc != SCST_AEN_RES_SUCCESS) scst_free_aen(aen); } } out_unlock: mutex_unlock(&scst_mutex); out: return res; } static int __scst_acg_cpu_mask_store_work_fn(struct scst_sysfs_work_item *work) { return __scst_acg_process_cpu_mask_store(work->tgt, work->acg, &work->cpu_mask); } static ssize_t __scst_acg_cpu_mask_store(struct scst_acg *acg, const char *buf, size_t count) { int res; struct scst_sysfs_work_item *work; /* cpumask might be too big for stack */ res = scst_alloc_sysfs_work(__scst_acg_cpu_mask_store_work_fn, false, &work); if (res != 0) goto out; /* * We can't use cpumask_parse_user() here, because it expects * buffer in the user space. */ res = __bitmap_parse(buf, count, 0, cpumask_bits(&work->cpu_mask), nr_cpumask_bits); if (res != 0) { PRINT_ERROR("__bitmap_parse() failed: %d", res); goto out_release; } if (cpumask_equal(&acg->acg_cpu_mask, &work->cpu_mask)) goto out; work->tgt = acg->tgt; work->acg = acg; res = scst_sysfs_queue_wait_work(work); out: return res; out_release: scst_sysfs_work_release(&work->sysfs_work_kref); goto out; } static ssize_t scst_tgt_cpu_mask_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; return __scst_acg_cpu_mask_show(acg, buf); } static ssize_t scst_tgt_cpu_mask_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); acg = tgt->default_acg; res = __scst_acg_cpu_mask_store(acg, buf, count); if (res != 0) goto out; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tgt_cpu_mask = __ATTR(cpu_mask, S_IRUGO | S_IWUSR, scst_tgt_cpu_mask_show, scst_tgt_cpu_mask_store); static ssize_t scst_ini_group_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"create GROUP_NAME\" >mgmt\n" " echo \"del GROUP_NAME\" >mgmt\n"; return sprintf(buf, "%s", help); } static int scst_process_ini_group_mgmt_store(char *buffer, struct scst_tgt *tgt) { int res, action; char *p, *pp; struct scst_acg *acg; enum { SCST_INI_GROUP_ACTION_CREATE = 1, SCST_INI_GROUP_ACTION_DEL = 2, }; TRACE_ENTRY(); TRACE_DBG("tgt %p, buffer %s", tgt, buffer); pp = buffer; p = scst_get_next_lexem(&pp); if (strcasecmp("create", p) == 0) { action = SCST_INI_GROUP_ACTION_CREATE; } else if (strcasecmp("del", p) == 0) { action = SCST_INI_GROUP_ACTION_DEL; } else { PRINT_ERROR("Unknown action \"%s\"", p); res = -EINVAL; goto out; } res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); if (res != 0) goto out; res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out_resume; /* Check if our pointer is still alive */ if (scst_check_tgt_acg_ptrs(tgt, NULL) != 0) goto out_unlock; p = scst_get_next_lexem(&pp); if (p[0] == '\0') { PRINT_ERROR("%s", "Group name required"); res = -EINVAL; goto out_unlock; } acg = scst_tgt_find_acg(tgt, p); switch (action) { case SCST_INI_GROUP_ACTION_CREATE: TRACE_DBG("Creating group '%s'", p); if (acg != NULL) { PRINT_ERROR("acg name %s exist", p); res = -EEXIST; goto out_unlock; } res = scst_alloc_add_acg(tgt, p, true, &acg); if (res != 0) goto out_unlock; break; case SCST_INI_GROUP_ACTION_DEL: TRACE_DBG("Deleting group '%s'", p); if (acg == NULL) { PRINT_ERROR("Group %s not found", p); res = -EINVAL; goto out_unlock; } res = scst_del_free_acg(acg, scst_forcibly_close_sessions); if (res) { if (scst_forcibly_close_sessions) PRINT_ERROR("Removing group %s failed", acg->acg_name); else PRINT_ERROR("Group %s is not empty", acg->acg_name); goto out_unlock; } break; } res = 0; out_unlock: mutex_unlock(&scst_mutex); out_resume: scst_resume_activity(); out: TRACE_EXIT_RES(res); return res; } static int scst_ini_group_mgmt_store_work_fn(struct scst_sysfs_work_item *work) { return scst_process_ini_group_mgmt_store(work->buf, work->tgt); } static ssize_t scst_ini_group_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; char *buffer; struct scst_tgt *tgt; struct scst_sysfs_work_item *work; TRACE_ENTRY(); tgt = container_of(kobj->parent, struct scst_tgt, tgt_kobj); buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (buffer == NULL) { res = -ENOMEM; goto out; } res = scst_alloc_sysfs_work(scst_ini_group_mgmt_store_work_fn, false, &work); if (res != 0) goto out_free; work->buf = buffer; work->tgt = tgt; res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; out_free: kfree(buffer); goto out; } static struct kobj_attribute scst_ini_group_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_ini_group_mgmt_show, scst_ini_group_mgmt_store); static ssize_t scst_tgt_enable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt *tgt; int res; bool enabled; TRACE_ENTRY(); tgt = container_of(kobj, struct scst_tgt, tgt_kobj); enabled = tgt->tgtt->is_target_enabled(tgt); res = sprintf(buf, "%d\n", enabled ? 1 : 0); TRACE_EXIT_RES(res); return res; } static int scst_process_tgt_enable_store(struct scst_tgt *tgt, bool enable) { int res; TRACE_ENTRY(); /* Tgt protected by kobject reference */ TRACE_DBG("tgt %s, enable %d", tgt->tgt_name, enable); if (enable) { if (tgt->rel_tgt_id == 0) { res = gen_relative_target_port_id(&tgt->rel_tgt_id); if (res != 0) goto out_put; PRINT_INFO("Using autogenerated relative target id %d " "for target %s", tgt->rel_tgt_id, tgt->tgt_name); } else { if (!scst_is_relative_target_port_id_unique( tgt->rel_tgt_id, tgt)) { PRINT_ERROR("Relative target id %d is not " "unique", tgt->rel_tgt_id); res = -EBADSLT; goto out_put; } } } res = tgt->tgtt->enable_target(tgt, enable); out_put: kobject_put(&tgt->tgt_kobj); TRACE_EXIT_RES(res); return res; } static int scst_tgt_enable_store_work_fn(struct scst_sysfs_work_item *work) { return scst_process_tgt_enable_store(work->tgt, work->enable); } static ssize_t scst_tgt_enable_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_tgt *tgt; bool enable; struct scst_sysfs_work_item *work; TRACE_ENTRY(); if (buf == NULL) { PRINT_ERROR("%s: NULL buffer?", __func__); res = -EINVAL; goto out; } tgt = container_of(kobj, struct scst_tgt, tgt_kobj); switch (buf[0]) { case '0': enable = false; break; case '1': enable = true; break; default: PRINT_ERROR("%s: Requested action not understood: %s", __func__, buf); res = -EINVAL; goto out; } res = scst_alloc_sysfs_work(scst_tgt_enable_store_work_fn, false, &work); if (res != 0) goto out; work->tgt = tgt; work->enable = enable; SCST_SET_DEP_MAP(work, &scst_tgt_dep_map); kobject_get(&tgt->tgt_kobj); res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute tgt_enable_attr = __ATTR(enabled, S_IRUGO | S_IWUSR, scst_tgt_enable_show, scst_tgt_enable_store); static ssize_t scst_rel_tgt_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt *tgt; int res; TRACE_ENTRY(); tgt = container_of(kobj, struct scst_tgt, tgt_kobj); res = sprintf(buf, "%d\n%s", tgt->rel_tgt_id, (tgt->rel_tgt_id != 0) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(res); return res; } static int scst_process_rel_tgt_id_store(struct scst_sysfs_work_item *work) { int res = 0; struct scst_tgt *tgt = work->tgt_r; unsigned long rel_tgt_id = work->rel_tgt_id; bool enabled; TRACE_ENTRY(); /* tgt protected by kobject_get() */ TRACE_DBG("Trying to set relative target port id %d", (uint16_t)rel_tgt_id); if (tgt->tgtt->is_target_enabled != NULL) enabled = tgt->tgtt->is_target_enabled(tgt); else enabled = true; if (enabled && rel_tgt_id != tgt->rel_tgt_id) { if (!scst_is_relative_target_port_id_unique(rel_tgt_id, tgt)) { PRINT_ERROR("Relative port id %d is not unique", (uint16_t)rel_tgt_id); res = -EBADSLT; goto out_put; } } if (rel_tgt_id < SCST_MIN_REL_TGT_ID || rel_tgt_id > SCST_MAX_REL_TGT_ID) { if ((rel_tgt_id == 0) && !enabled) goto set; PRINT_ERROR("Invalid relative port id %d", (uint16_t)rel_tgt_id); res = -EINVAL; goto out_put; } set: tgt->rel_tgt_id = (uint16_t)rel_tgt_id; out_put: kobject_put(&tgt->tgt_kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_rel_tgt_id_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res = 0; struct scst_tgt *tgt; unsigned long rel_tgt_id; struct scst_sysfs_work_item *work; TRACE_ENTRY(); if (buf == NULL) goto out; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); res = kstrtoul(buf, 0, &rel_tgt_id); if (res != 0) { PRINT_ERROR("%s", "Wrong rel_tgt_id"); res = -EINVAL; goto out; } res = scst_alloc_sysfs_work(scst_process_rel_tgt_id_store, false, &work); if (res != 0) goto out; work->tgt_r = tgt; work->rel_tgt_id = rel_tgt_id; SCST_SET_DEP_MAP(work, &scst_tgt_dep_map); kobject_get(&tgt->tgt_kobj); res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_rel_tgt_id = __ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_rel_tgt_id_show, scst_rel_tgt_id_store); static ssize_t scst_tgt_forwarding_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt *tgt; int res; TRACE_ENTRY(); tgt = container_of(kobj, struct scst_tgt, tgt_kobj); res = sprintf(buf, "%d\n%s", tgt->tgt_forwarding, tgt->tgt_forwarding ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(res); return res; } static ssize_t scst_tgt_forwarding_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res = 0; struct scst_tgt *tgt; struct scst_session *sess; int old; TRACE_ENTRY(); if ((buf == NULL) || (count == 0)) { res = 0; goto out; } tgt = container_of(kobj, struct scst_tgt, tgt_kobj); mutex_lock(&scst_mutex); old = tgt->tgt_forwarding; switch (buf[0]) { case '0': tgt->tgt_forwarding = 0; break; case '1': tgt->tgt_forwarding = 1; break; default: PRINT_ERROR("%s: Requested action not understood: %s", __func__, buf); res = -EINVAL; goto out_unlock; } if (tgt->tgt_forwarding == old) goto out_unlock; list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { int i; for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { if (tgt->tgt_forwarding) set_bit(SCST_TGT_DEV_FORWARDING, &tgt_dev->tgt_dev_flags); else clear_bit(SCST_TGT_DEV_FORWARDING, &tgt_dev->tgt_dev_flags); } } } if (tgt->tgt_forwarding) PRINT_INFO("Set target %s as forwarding", tgt->tgt_name); else PRINT_INFO("Clear target %s as forwarding", tgt->tgt_name); out_unlock: mutex_unlock(&scst_mutex); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tgt_forwarding = __ATTR(forwarding, S_IRUGO | S_IWUSR, scst_tgt_forwarding_show, scst_tgt_forwarding_store); static ssize_t scst_tgt_comment_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt *tgt; int res; TRACE_ENTRY(); tgt = container_of(kobj, struct scst_tgt, tgt_kobj); if (tgt->tgt_comment != NULL) res = sprintf(buf, "%s\n%s", tgt->tgt_comment, SCST_SYSFS_KEY_MARK "\n"); else res = 0; TRACE_EXIT_RES(res); return res; } static ssize_t scst_tgt_comment_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_tgt *tgt; char *p; int len; TRACE_ENTRY(); if ((buf == NULL) || (count == 0)) { res = 0; goto out; } tgt = container_of(kobj, struct scst_tgt, tgt_kobj); len = strnlen(buf, count); if (buf[count-1] == '\n') len--; if (len == 0) { kfree(tgt->tgt_comment); tgt->tgt_comment = NULL; goto out_done; } p = kmalloc(len+1, GFP_KERNEL); if (p == NULL) { PRINT_ERROR("Unable to alloc tgt_comment string (len %d)", len+1); res = -ENOMEM; goto out; } memcpy(p, buf, len); p[len] = '\0'; kfree(tgt->tgt_comment); tgt->tgt_comment = p; out_done: res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tgt_comment = __ATTR(comment, S_IRUGO | S_IWUSR, scst_tgt_comment_show, scst_tgt_comment_store); /* * Creates an attribute entry for one target. Allows for target driver to * create an attribute that is not for every target. */ int scst_create_tgt_attr(struct scst_tgt *tgt, struct kobj_attribute *attribute) { int res; res = sysfs_create_file(&tgt->tgt_kobj, &attribute->attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for tgt %s", attribute->attr.name, tgt->tgt_name); goto out; } out: return res; } EXPORT_SYMBOL(scst_create_tgt_attr); static ssize_t scst_tgt_dif_capable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_tgt *tgt; TRACE_ENTRY(); tgt = container_of(kobj, struct scst_tgt, tgt_kobj); EXTRACHECKS_BUG_ON(!tgt->tgt_dif_supported); pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "dif_supported"); if (tgt->tgt_hw_dif_type1_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_type1_supported"); if (tgt->tgt_hw_dif_type2_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_type2_supported"); if (tgt->tgt_hw_dif_type3_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_type3_supported"); if (tgt->tgt_hw_dif_ip_supported) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_ip_supported"); if (tgt->tgt_hw_dif_same_sg_layout_required) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, ", hw_dif_same_sg_layout_required"); pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "\n"); if (tgt->tgt_supported_dif_block_sizes) { const int *p = tgt->tgt_supported_dif_block_sizes; int j; pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "Supported blocks: "); j = pos; while (*p != 0) { pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "%s%d", (j == pos) ? "" : ", ", *p); p++; } } TRACE_EXIT_RES(pos); return pos; } static struct kobj_attribute scst_tgt_dif_capable_attr = __ATTR(dif_capabilities, S_IRUGO, scst_tgt_dif_capable_show, NULL); static ssize_t scst_tgt_dif_checks_failed_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "\tapp\tref\tguard\n" "tgt\t%d\t%d\t%d\nscst\t%d\t%d\t%d\ndev\t%d\t%d\t%d\n", atomic_read(&tgt->tgt_dif_app_failed_tgt), atomic_read(&tgt->tgt_dif_ref_failed_tgt), atomic_read(&tgt->tgt_dif_guard_failed_tgt), atomic_read(&tgt->tgt_dif_app_failed_scst), atomic_read(&tgt->tgt_dif_ref_failed_scst), atomic_read(&tgt->tgt_dif_guard_failed_scst), atomic_read(&tgt->tgt_dif_app_failed_dev), atomic_read(&tgt->tgt_dif_ref_failed_dev), atomic_read(&tgt->tgt_dif_guard_failed_dev)); return pos; } static ssize_t scst_tgt_dif_checks_failed_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_tgt *tgt; tgt = container_of(kobj, struct scst_tgt, tgt_kobj); PRINT_INFO("Zeroing DIF failures statistics for target %s", tgt->tgt_name); atomic_set(&tgt->tgt_dif_app_failed_tgt, 0); atomic_set(&tgt->tgt_dif_ref_failed_tgt, 0); atomic_set(&tgt->tgt_dif_guard_failed_tgt, 0); atomic_set(&tgt->tgt_dif_app_failed_scst, 0); atomic_set(&tgt->tgt_dif_ref_failed_scst, 0); atomic_set(&tgt->tgt_dif_guard_failed_scst, 0); atomic_set(&tgt->tgt_dif_app_failed_dev, 0); atomic_set(&tgt->tgt_dif_ref_failed_dev, 0); atomic_set(&tgt->tgt_dif_guard_failed_dev, 0); return count; } static struct kobj_attribute scst_tgt_dif_checks_failed_attr = __ATTR(dif_checks_failed, S_IRUGO | S_IWUSR, scst_tgt_dif_checks_failed_show, scst_tgt_dif_checks_failed_store); #define SCST_TGT_SYSFS_STAT_ATTR(member_name, attr, dir, result_op) \ static int scst_tgt_sysfs_##attr##_show_work_fn( \ struct scst_sysfs_work_item *work) \ { \ struct scst_tgt *tgt = work->tgt; \ struct scst_session *sess; \ int res; \ uint64_t c = 0; \ \ BUILD_BUG_ON((unsigned int)(dir) >= ARRAY_SIZE(sess->io_stats));\ \ res = mutex_lock_interruptible(&scst_mutex); \ if (res) \ goto out; \ list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) \ c += sess->io_stats[(dir)].member_name; \ mutex_unlock(&scst_mutex); \ \ work->res_buf = kasprintf(GFP_KERNEL, "%llu\n", c result_op); \ res = work->res_buf ? 0 : -ENOMEM; \ \ out: \ kobject_put(&tgt->tgt_kobj); \ return res; \ } \ \ static ssize_t scst_tgt_sysfs_##attr##_show(struct kobject *kobj, \ struct kobj_attribute *attr, \ char *buf) \ { \ struct scst_tgt *tgt = \ container_of(kobj, struct scst_tgt, tgt_kobj); \ struct scst_sysfs_work_item *work; \ int res; \ \ res = scst_alloc_sysfs_work(scst_tgt_sysfs_##attr##_show_work_fn, \ true, &work); \ if (res) \ goto out; \ \ work->tgt = tgt; \ SCST_SET_DEP_MAP(work, &scst_tgt_dep_map); \ kobject_get(&tgt->tgt_kobj); \ scst_sysfs_work_get(work); \ res = scst_sysfs_queue_wait_work(work); \ if (res == 0) \ res = scnprintf(buf, PAGE_SIZE, "%s", work->res_buf); \ scst_sysfs_work_put(work); \ \ out: \ return res; \ } \ \ static struct kobj_attribute scst_tgt_##attr##_attr = \ __ATTR(attr, S_IRUGO, scst_tgt_sysfs_##attr##_show, NULL) SCST_TGT_SYSFS_STAT_ATTR(cmd_count, unknown_cmd_count, SCST_DATA_UNKNOWN, >> 0); SCST_TGT_SYSFS_STAT_ATTR(cmd_count, write_cmd_count, SCST_DATA_WRITE, >> 0); SCST_TGT_SYSFS_STAT_ATTR(io_byte_count, write_io_count_kb, SCST_DATA_WRITE, >> 10); SCST_TGT_SYSFS_STAT_ATTR(unaligned_cmd_count, write_unaligned_cmd_count, SCST_DATA_WRITE, >> 0); SCST_TGT_SYSFS_STAT_ATTR(cmd_count, read_cmd_count, SCST_DATA_READ, >> 0); SCST_TGT_SYSFS_STAT_ATTR(io_byte_count, read_io_count_kb, SCST_DATA_READ, >> 10); SCST_TGT_SYSFS_STAT_ATTR(unaligned_cmd_count, read_unaligned_cmd_count, SCST_DATA_READ, >> 0); SCST_TGT_SYSFS_STAT_ATTR(cmd_count, bidi_cmd_count, SCST_DATA_BIDI, >> 0); SCST_TGT_SYSFS_STAT_ATTR(io_byte_count, bidi_io_count_kb, SCST_DATA_BIDI, >> 10); SCST_TGT_SYSFS_STAT_ATTR(unaligned_cmd_count, bidi_unaligned_cmd_count, SCST_DATA_BIDI, >> 0); SCST_TGT_SYSFS_STAT_ATTR(cmd_count, none_cmd_count, SCST_DATA_NONE, >> 0); static struct attribute *scst_tgt_attrs[] = { &scst_rel_tgt_id.attr, &scst_tgt_forwarding.attr, &scst_tgt_comment.attr, &scst_tgt_addr_method.attr, &scst_tgt_io_grouping_type.attr, &scst_tgt_black_hole.attr, &scst_tgt_cpu_mask.attr, &scst_tgt_unknown_cmd_count_attr.attr, &scst_tgt_write_cmd_count_attr.attr, &scst_tgt_write_io_count_kb_attr.attr, &scst_tgt_write_unaligned_cmd_count_attr.attr, &scst_tgt_read_cmd_count_attr.attr, &scst_tgt_read_io_count_kb_attr.attr, &scst_tgt_read_unaligned_cmd_count_attr.attr, &scst_tgt_bidi_cmd_count_attr.attr, &scst_tgt_bidi_io_count_kb_attr.attr, &scst_tgt_bidi_unaligned_cmd_count_attr.attr, &scst_tgt_none_cmd_count_attr.attr, NULL, }; static struct kobj_type tgt_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_tgt_release, .default_attrs = scst_tgt_attrs, }; /* * Supposed to be called under scst_mutex. In case of error will drop, * then reacquire it. */ int scst_tgt_sysfs_create(struct scst_tgt *tgt) { int res; TRACE_ENTRY(); res = kobject_init_and_add(&tgt->tgt_kobj, &tgt_ktype, &tgt->tgtt->tgtt_kobj, tgt->tgt_name); if (res != 0) { PRINT_ERROR("Can't add tgt %s to sysfs", tgt->tgt_name); goto out; } if ((tgt->tgtt->enable_target != NULL) && (tgt->tgtt->is_target_enabled != NULL)) { res = sysfs_create_file(&tgt->tgt_kobj, &tgt_enable_attr.attr); if (res != 0) { PRINT_ERROR("Can't add attr %s to sysfs", tgt_enable_attr.attr.name); goto out_err; } } tgt->tgt_sess_kobj = kobject_create_and_add("sessions", &tgt->tgt_kobj); if (tgt->tgt_sess_kobj == NULL) { PRINT_ERROR("Can't create sess kobj for tgt %s", tgt->tgt_name); goto out_nomem; } tgt->tgt_luns_kobj = kobject_create_and_add("luns", &tgt->tgt_kobj); if (tgt->tgt_luns_kobj == NULL) { PRINT_ERROR("Can't create luns kobj for tgt %s", tgt->tgt_name); goto out_nomem; } res = sysfs_create_file(tgt->tgt_luns_kobj, &scst_luns_mgmt.attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for tgt %s", scst_luns_mgmt.attr.name, tgt->tgt_name); goto out_err; } tgt->tgt_ini_grp_kobj = kobject_create_and_add("ini_groups", &tgt->tgt_kobj); if (tgt->tgt_ini_grp_kobj == NULL) { PRINT_ERROR("Can't create ini_grp kobj for tgt %s", tgt->tgt_name); goto out_nomem; } res = sysfs_create_file(tgt->tgt_ini_grp_kobj, &scst_ini_group_mgmt.attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for tgt %s", scst_ini_group_mgmt.attr.name, tgt->tgt_name); goto out_err; } if (tgt->tgt_dif_supported) { res = sysfs_create_file(&tgt->tgt_kobj, &scst_tgt_dif_capable_attr.attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for tgt %s", scst_tgt_dif_capable_attr.attr.name, tgt->tgt_name); goto out_err; } res = sysfs_create_file(&tgt->tgt_kobj, &scst_tgt_dif_checks_failed_attr.attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for tgt %s", scst_tgt_dif_checks_failed_attr.attr.name, tgt->tgt_name); goto out_err; } } if (tgt->tgtt->tgt_attrs) { res = sysfs_create_files(&tgt->tgt_kobj, tgt->tgtt->tgt_attrs); if (res != 0) { PRINT_ERROR("Can't add attributes for tgt %s", tgt->tgt_name); goto out_err; } } out: TRACE_EXIT_RES(res); return res; out_nomem: res = -ENOMEM; out_err: mutex_unlock(&scst_mutex); scst_tgt_sysfs_del(tgt); mutex_lock(&scst_mutex); goto out; } /* * Must not be called under scst_mutex, due to possible deadlock with * sysfs ref counting in sysfs works (it is waiting for the last put, but * the last ref counter holder is waiting for scst_mutex) */ void scst_tgt_sysfs_del(struct scst_tgt *tgt) { TRACE_ENTRY(); kobject_del(tgt->tgt_sess_kobj); kobject_del(tgt->tgt_luns_kobj); kobject_del(tgt->tgt_ini_grp_kobj); kobject_del(&tgt->tgt_kobj); kobject_put(tgt->tgt_sess_kobj); kobject_put(tgt->tgt_luns_kobj); kobject_put(tgt->tgt_ini_grp_kobj); TRACE_EXIT(); return; } void scst_tgt_sysfs_put(struct scst_tgt *tgt) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); tgt->tgt_kobj_release_cmpl = &c; SCST_KOBJECT_PUT_AND_WAIT(&tgt->tgt_kobj, "target", &c, &scst_tgt_dep_map); TRACE_EXIT(); return; } /* ** Devices directory implementation **/ static ssize_t scst_dev_sysfs_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_device *dev; dev = container_of(kobj, struct scst_device, dev_kobj); pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%d - %s\n", dev->type, (unsigned int)dev->type >= ARRAY_SIZE(scst_dev_handler_types) ? "unknown" : scst_dev_handler_types[dev->type]); return pos; } static struct kobj_attribute dev_type_attr = __ATTR(type, S_IRUGO, scst_dev_sysfs_type_show, NULL); static ssize_t scst_dev_sysfs_pr_file_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_device *dev; int res; dev = container_of(kobj, struct scst_device, dev_kobj); res = mutex_lock_interruptible(&dev->dev_pr_mutex); if (res != 0) goto out; res = scnprintf(buf, PAGE_SIZE, "%s\n%s", dev->pr_file_name ? : "", dev->pr_file_name_is_set ? SCST_SYSFS_KEY_MARK "\n" : ""); mutex_unlock(&dev->dev_pr_mutex); out: return res; } static int scst_dev_sysfs_pr_file_name_process_store(struct scst_sysfs_work_item *work) { struct scst_device *dev = work->dev; char *pr_file_name = work->buf, *prev = NULL; int res; res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out; res = -EBUSY; if (scst_device_is_exported(dev)) { PRINT_ERROR("%s: not changing pr_file_name because the device" " has already been exported", dev->virt_name); goto unlock_scst; } res = mutex_lock_interruptible(&dev->dev_pr_mutex); if (res) goto unlock_scst; if (strcmp(dev->pr_file_name, pr_file_name) == 0) goto unlock_dev_pr; res = scst_pr_set_file_name(dev, &prev, "%s", pr_file_name); if (res != 0) goto unlock_dev_pr; res = scst_pr_init_dev(dev); if (res != 0) { PRINT_ERROR("%s: loading PR from %s failed (%d) - restoring %s", dev->virt_name, dev->pr_file_name, res, prev ? : ""); scst_pr_set_file_name(dev, NULL, "%s", prev); scst_pr_init_dev(dev); goto unlock_dev_pr; } dev->pr_file_name_is_set = !work->default_val; unlock_dev_pr: mutex_unlock(&dev->dev_pr_mutex); unlock_scst: mutex_unlock(&scst_mutex); out: kobject_put(&dev->dev_kobj); kfree(prev); return res; } static ssize_t scst_dev_sysfs_pr_file_name_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_sysfs_work_item *work; struct scst_device *dev; char *pr_file_name = NULL, *p; int res = -EPERM; bool def = false; dev = container_of(kobj, struct scst_device, dev_kobj); if (dev->cluster_mode) goto out; res = -ENOMEM; pr_file_name = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!pr_file_name) { PRINT_ERROR("Unable to kasprintf() PR file name"); goto out; } p = pr_file_name; strsep(&p, "\n"); /* strip trailing whitespace */ if (pr_file_name[0] == '\0') { kfree(pr_file_name); pr_file_name = kasprintf(GFP_KERNEL, "%s/%s", SCST_PR_DIR, dev->virt_name); if (!pr_file_name) { PRINT_ERROR("Unable to kasprintf() PR file name"); goto out; } def = true; } res = scst_alloc_sysfs_work(scst_dev_sysfs_pr_file_name_process_store, false, &work); if (res != 0) goto out; kobject_get(&dev->dev_kobj); work->dev = dev; work->default_val = def; swap(work->buf, pr_file_name); res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: kfree(pr_file_name); return res; } static struct kobj_attribute dev_pr_file_name_attr = __ATTR(pr_file_name, S_IWUSR|S_IRUGO, scst_dev_sysfs_pr_file_name_show, scst_dev_sysfs_pr_file_name_store); #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) static ssize_t scst_dev_sysfs_dump_prs(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_device *dev; int res; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); res = mutex_lock_interruptible(&dev->dev_pr_mutex); if (res != 0) goto out; scst_pr_dump_prs(dev, true); mutex_unlock(&dev->dev_pr_mutex); res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute dev_dump_prs_attr = __ATTR(dump_prs, S_IWUSR, NULL, scst_dev_sysfs_dump_prs); #endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */ static int scst_process_dev_sysfs_threads_data_store( struct scst_device *dev, int threads_num, enum scst_dev_type_threads_pool_type threads_pool_type) { int res = 0; int oldtn = dev->threads_num; enum scst_dev_type_threads_pool_type oldtt = dev->threads_pool_type; TRACE_ENTRY(); TRACE_DBG("dev %p, threads_num %d, threads_pool_type %d", dev, threads_num, threads_pool_type); res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); if (res != 0) goto out; res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out_resume; /* Check if our pointer is still alive */ if (scst_check_dev_ptr(dev) != 0) goto out_unlock; scst_stop_dev_threads(dev); dev->threads_num = threads_num; dev->threads_pool_type = threads_pool_type; res = scst_create_dev_threads(dev); if (res != 0) goto out_unlock; if (oldtn != dev->threads_num) PRINT_INFO("Changed cmd threads num to %d", dev->threads_num); else if (oldtt != dev->threads_pool_type) PRINT_INFO("Changed cmd threads pool type to %d", dev->threads_pool_type); out_unlock: mutex_unlock(&scst_mutex); out_resume: scst_resume_activity(); out: TRACE_EXIT_RES(res); return res; } static int scst_dev_sysfs_threads_data_store_work_fn( struct scst_sysfs_work_item *work) { return scst_process_dev_sysfs_threads_data_store(work->dev, work->new_threads_num, work->new_threads_pool_type); } static ssize_t scst_dev_sysfs_check_threads_data( struct scst_device *dev, int threads_num, enum scst_dev_type_threads_pool_type threads_pool_type, bool *stop) { int res = 0; TRACE_ENTRY(); *stop = false; if (dev->threads_num < 0) { PRINT_ERROR("Threads pool disabled for device %s", dev->virt_name); res = -EPERM; goto out; } if ((threads_num == dev->threads_num) && (threads_pool_type == dev->threads_pool_type)) { *stop = true; goto out; } out: TRACE_EXIT_RES(res); return res; } static ssize_t scst_dev_sysfs_threads_num_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\n%s", dev->threads_num, (dev->threads_num != dev->handler->threads_num) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(pos); return pos; } static ssize_t scst_dev_sysfs_threads_num_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_device *dev; long newtn; bool stop; struct scst_sysfs_work_item *work; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); res = kstrtol(buf, 0, &newtn); if (res != 0) { PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn < 0) { PRINT_ERROR("Illegal threads num value %ld", newtn); res = -EINVAL; goto out; } res = scst_dev_sysfs_check_threads_data(dev, newtn, dev->threads_pool_type, &stop); if ((res != 0) || stop) goto out; res = scst_alloc_sysfs_work(scst_dev_sysfs_threads_data_store_work_fn, false, &work); if (res != 0) goto out; work->dev = dev; work->new_threads_num = newtn; work->new_threads_pool_type = dev->threads_pool_type; res = scst_sysfs_queue_wait_work(work); out: if (res == 0) res = count; TRACE_EXIT_RES(res); return res; } static struct kobj_attribute dev_threads_num_attr = __ATTR(threads_num, S_IRUGO | S_IWUSR, scst_dev_sysfs_threads_num_show, scst_dev_sysfs_threads_num_store); static ssize_t scst_dev_sysfs_threads_pool_type_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); if (dev->threads_num == 0) { pos = sprintf(buf, "Async\n"); goto out; } else if (dev->threads_num < 0) { pos = sprintf(buf, "Not valid\n"); goto out; } switch (dev->threads_pool_type) { case SCST_THREADS_POOL_PER_INITIATOR: pos = sprintf(buf, "%s\n%s", SCST_THREADS_POOL_PER_INITIATOR_STR, (dev->threads_pool_type != dev->handler->threads_pool_type) ? SCST_SYSFS_KEY_MARK "\n" : ""); break; case SCST_THREADS_POOL_SHARED: pos = sprintf(buf, "%s\n%s", SCST_THREADS_POOL_SHARED_STR, (dev->threads_pool_type != dev->handler->threads_pool_type) ? SCST_SYSFS_KEY_MARK "\n" : ""); break; default: pos = sprintf(buf, "Unknown\n"); break; } out: TRACE_EXIT_RES(pos); return pos; } static ssize_t scst_dev_sysfs_threads_pool_type_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_device *dev; enum scst_dev_type_threads_pool_type newtpt; struct scst_sysfs_work_item *work; bool stop; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); newtpt = scst_parse_threads_pool_type(buf, count); if (newtpt == SCST_THREADS_POOL_TYPE_INVALID) { PRINT_ERROR("Illegal threads pool type %s", buf); res = -EINVAL; goto out; } TRACE_DBG("buf %s, count %zd, newtpt %d", buf, count, newtpt); res = scst_dev_sysfs_check_threads_data(dev, dev->threads_num, newtpt, &stop); if ((res != 0) || stop) goto out; res = scst_alloc_sysfs_work(scst_dev_sysfs_threads_data_store_work_fn, false, &work); if (res != 0) goto out; work->dev = dev; work->new_threads_num = dev->threads_num; work->new_threads_pool_type = newtpt; res = scst_sysfs_queue_wait_work(work); out: if (res == 0) res = count; TRACE_EXIT_RES(res); return res; } static struct kobj_attribute dev_threads_pool_type_attr = __ATTR(threads_pool_type, S_IRUGO | S_IWUSR, scst_dev_sysfs_threads_pool_type_show, scst_dev_sysfs_threads_pool_type_store); static ssize_t scst_dev_sysfs_max_tgt_dev_commands_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\n%s", dev->max_tgt_dev_commands, (dev->max_tgt_dev_commands != dev->handler->max_tgt_dev_commands) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(pos); return pos; } static ssize_t scst_dev_sysfs_max_tgt_dev_commands_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_device *dev; long newtn; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); res = kstrtol(buf, 0, &newtn); if (res != 0) { PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn < 0) { PRINT_ERROR("Illegal max tgt dev value %ld", newtn); res = -EINVAL; goto out; } if (dev->max_tgt_dev_commands != newtn) { PRINT_INFO("Setting new queue depth %ld for device %s (old %d)", newtn, dev->virt_name, dev->max_tgt_dev_commands); dev->max_tgt_dev_commands = newtn; } out: if (res == 0) res = count; TRACE_EXIT_RES(res); return res; } static struct kobj_attribute dev_max_tgt_dev_commands_attr = __ATTR(max_tgt_dev_commands, S_IRUGO | S_IWUSR, scst_dev_sysfs_max_tgt_dev_commands_show, scst_dev_sysfs_max_tgt_dev_commands_store); static ssize_t scst_dev_numa_node_id_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\n%s", dev->dev_numa_node_id, (dev->dev_numa_node_id != NUMA_NO_NODE) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(pos); return pos; } static ssize_t scst_dev_numa_node_id_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_device *dev; long newtn; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); res = kstrtol(buf, 0, &newtn); if (res != 0) { PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } BUILD_BUG_ON(NUMA_NO_NODE != -1); if (newtn < NUMA_NO_NODE) { PRINT_ERROR("Illegal numa_node_id value %ld", newtn); res = -EINVAL; goto out; } if (dev->dev_numa_node_id != newtn) { PRINT_INFO("Setting new NUMA node id %ld for device %s (old %d)", newtn, dev->virt_name, dev->dev_numa_node_id); dev->dev_numa_node_id = newtn; } out: if (res == 0) res = count; TRACE_EXIT_RES(res); return res; } static struct kobj_attribute dev_numa_node_id_attr = __ATTR(numa_node_id, S_IRUGO | S_IWUSR, scst_dev_numa_node_id_show, scst_dev_numa_node_id_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", READ_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_MGMT_DBG("Sysfs unblocking (dev %s)", dev->virt_name); scst_ext_unblock_dev(dev, false); 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_MGMT_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, NULL, NULL, 0, SCST_EXT_BLOCK_SYNC); else res = scst_ext_block_dev(dev, scst_sysfs_ext_blocking_done, data_start, data_len, 0); 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_max_tgt_dev_commands_attr.attr, &dev_numa_node_id_attr.attr, &dev_block_attr.attr, NULL, }; static void scst_sysfs_dev_release(struct kobject *kobj) { struct scst_device *dev; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); if (dev->dev_kobj_release_cmpl) complete_all(dev->dev_kobj_release_cmpl); TRACE_EXIT(); return; } /* * Creates an attribute entry for one SCST device. Allows for dev handlers to * create an attribute that is not for every device. */ int scst_create_dev_attr(struct scst_device *dev, struct kobj_attribute *attribute) { int res; res = sysfs_create_file(&dev->dev_kobj, &attribute->attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for dev %s", attribute->attr.name, dev->virt_name); goto out; } out: return res; } EXPORT_SYMBOL(scst_create_dev_attr); int scst_devt_dev_sysfs_create(struct scst_device *dev) { int res = 0; TRACE_ENTRY(); if (dev->handler == &scst_null_devtype) goto out; res = sysfs_create_link(&dev->dev_kobj, &dev->handler->devt_kobj, "handler"); if (res != 0) { PRINT_ERROR("Can't create handler link for dev %s", dev->virt_name); goto out; } res = sysfs_create_link(&dev->handler->devt_kobj, &dev->dev_kobj, dev->virt_name); if (res != 0) { PRINT_ERROR("Can't create handler link for dev %s", dev->virt_name); goto out_err; } if (dev->handler->threads_num >= 0) { res = sysfs_create_file(&dev->dev_kobj, &dev_threads_num_attr.attr); if (res != 0) { PRINT_ERROR("Can't add dev attr %s for dev %s", dev_threads_num_attr.attr.name, dev->virt_name); goto out_err; } res = sysfs_create_file(&dev->dev_kobj, &dev_threads_pool_type_attr.attr); if (res != 0) { PRINT_ERROR("Can't add dev attr %s for dev %s", dev_threads_pool_type_attr.attr.name, dev->virt_name); goto out_err; } } if (dev->handler->dev_attrs) { res = sysfs_create_files(&dev->dev_kobj, dev->handler->dev_attrs); if (res != 0) { PRINT_ERROR("Can't add dev attributes for dev %s", dev->virt_name); goto out_err; } } out: TRACE_EXIT_RES(res); return res; out_err: scst_devt_dev_sysfs_del(dev); goto out; } void scst_devt_dev_sysfs_del(struct scst_device *dev) { TRACE_ENTRY(); if (dev->handler == &scst_null_devtype) goto out; if (dev->handler->dev_attrs) sysfs_remove_files(&dev->dev_kobj, dev->handler->dev_attrs); sysfs_remove_link(&dev->dev_kobj, "handler"); sysfs_remove_link(&dev->handler->devt_kobj, dev->virt_name); if (dev->handler->threads_num >= 0) { sysfs_remove_file(&dev->dev_kobj, &dev_threads_num_attr.attr); sysfs_remove_file(&dev->dev_kobj, &dev_threads_pool_type_attr.attr); } out: TRACE_EXIT(); return; } static struct kobj_type scst_dev_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_sysfs_dev_release, .default_attrs = scst_dev_attrs, }; /* * Must not be called under scst_mutex, because it can call * scst_dev_sysfs_del() */ int scst_dev_sysfs_create(struct scst_device *dev) { int res = 0; TRACE_ENTRY(); res = kobject_init_and_add(&dev->dev_kobj, &scst_dev_ktype, scst_devices_kobj, dev->virt_name); if (res != 0) { PRINT_ERROR("Can't add device %s to sysfs", dev->virt_name); goto out; } dev->dev_exp_kobj = kobject_create_and_add("exported", &dev->dev_kobj); if (dev->dev_exp_kobj == NULL) { PRINT_ERROR("Can't create exported link for device %s", dev->virt_name); res = -ENOMEM; goto out_del; } if (dev->scsi_dev != NULL) { res = sysfs_create_link(&dev->dev_kobj, &dev->scsi_dev->sdev_dev.kobj, "scsi_device"); if (res != 0) { PRINT_ERROR("Can't create scsi_device link for dev %s", dev->virt_name); goto out_del; } } if (dev->pr_file_name != NULL) { res = sysfs_create_file(&dev->dev_kobj, &dev_pr_file_name_attr.attr); if (res != 0) { PRINT_ERROR("Can't create attr %s for dev %s", dev_pr_file_name_attr.attr.name, dev->virt_name); goto out_del; } #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) res = sysfs_create_file(&dev->dev_kobj, &dev_dump_prs_attr.attr); if (res != 0) { PRINT_ERROR("Can't create attr %s for dev %s", dev_dump_prs_attr.attr.name, dev->virt_name); goto out_del; } #endif } out: TRACE_EXIT_RES(res); return res; out_del: scst_dev_sysfs_del(dev); goto out; } /* * Must not be called under scst_mutex, due to possible deadlock with * sysfs ref counting in sysfs works (it is waiting for the last put, but * the last ref counter holder is waiting for scst_mutex) */ void scst_dev_sysfs_del(struct scst_device *dev) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); dev->dev_kobj_release_cmpl = &c; kobject_del(dev->dev_exp_kobj); kobject_del(&dev->dev_kobj); kobject_put(dev->dev_exp_kobj); SCST_KOBJECT_PUT_AND_WAIT(&dev->dev_kobj, "device", &c, &scst_dev_dep_map); TRACE_EXIT(); return; } static ssize_t scst_dev_dif_mode_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); if (dev->dev_dif_mode == SCST_DIF_MODE_NONE) pos = sprintf(buf, "None\n"); else { int j = pos; if (dev->dev_dif_mode & SCST_DIF_MODE_TGT) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "%s", SCST_DIF_MODE_TGT_STR); if (dev->dev_dif_mode & SCST_DIF_MODE_SCST) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_SCST_STR); if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_CHECK) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_DEV_CHECK_STR); if (dev->dev_dif_mode & SCST_DIF_MODE_DEV_STORE) pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "%s%s", (j == pos) ? "" : "|", SCST_DIF_MODE_DEV_STORE_STR); pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "\n%s", SCST_SYSFS_KEY_MARK "\n"); } TRACE_EXIT_RES(pos); return pos; } static struct kobj_attribute scst_dev_dif_mode_attr = __ATTR(dif_mode, S_IRUGO, scst_dev_dif_mode_show, NULL); static ssize_t scst_dev_dif_type_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\n%s", dev->dev_dif_type, (dev->dev_dif_type != 0) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(pos); return pos; } static struct kobj_attribute scst_dev_dif_type_attr = __ATTR(dif_type, S_IRUGO, scst_dev_dif_type_show, NULL); static ssize_t scst_dev_sysfs_dif_static_app_tag_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_device *dev; unsigned long long val; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); res = kstrtoull(buf, 0, &val); if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %d (device %s)", buf, res, dev->virt_name); goto out; } scst_dev_set_dif_static_app_tag_combined(dev, cpu_to_be64(val)); res = count; PRINT_INFO("APP TAG for device %s changed to %llx", dev->virt_name, (long long)be64_to_cpu(scst_dev_get_dif_static_app_tag_combined(dev))); out: TRACE_EXIT_RES(res); return res; } static ssize_t scst_dev_sysfs_dif_static_app_tag_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_device *dev; __be64 a; TRACE_ENTRY(); dev = container_of(kobj, struct scst_device, dev_kobj); a = scst_dev_get_dif_static_app_tag_combined(dev); pos = sprintf(buf, "0x%llx\n%s", (unsigned long long)be64_to_cpu(a), (a != SCST_DIF_NO_CHECK_APP_TAG) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT_RES(pos); return pos; } static struct kobj_attribute scst_dev_dif_static_app_tag_attr = __ATTR(dif_static_app_tag, S_IWUSR|S_IRUGO, scst_dev_sysfs_dif_static_app_tag_show, scst_dev_sysfs_dif_static_app_tag_store); int scst_dev_sysfs_dif_create(struct scst_device *dev) { int res; TRACE_ENTRY(); /* * On errors the caller supposed to unregister this device, hence, * perform the cleanup. */ res = sysfs_create_file(&dev->dev_kobj, &scst_dev_dif_mode_attr.attr); if (res != 0) { PRINT_ERROR("Can't create attr %s for dev %s", scst_dev_dif_mode_attr.attr.name, dev->virt_name); goto out; } res = sysfs_create_file(&dev->dev_kobj, &scst_dev_dif_type_attr.attr); if (res != 0) { PRINT_ERROR("Can't create attr %s for dev %s", scst_dev_dif_type_attr.attr.name, dev->virt_name); goto out; } res = sysfs_create_file(&dev->dev_kobj, &scst_dev_dif_static_app_tag_attr.attr); if (res != 0) { PRINT_ERROR("Can't create attr %s for dev %s", scst_dev_dif_static_app_tag_attr.attr.name, dev->virt_name); goto out; } out: TRACE_EXIT_RES(res); return res; } /* ** Tgt_dev implementation **/ static ssize_t scst_tgt_dev_thread_index_show(struct kobject *kobj, struct kobj_attribute *attr, char *buffer) { struct scst_tgt_dev *tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); return sprintf(buffer, "%d\n", tgt_dev->thread_index); } static struct kobj_attribute tgt_dev_thread_idx_attr = __ATTR(thread_index, S_IRUGO, scst_tgt_dev_thread_index_show, NULL); static ssize_t scst_tgt_dev_thread_pid_show(struct kobject *kobj, struct kobj_attribute *attr, char *buffer) { struct scst_tgt_dev *tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); struct scst_cmd_threads *cmd_threads = tgt_dev->active_cmd_threads; struct scst_cmd_thread_t *t; int res = 0; spin_lock(&cmd_threads->thr_lock); list_for_each_entry(t, &cmd_threads->threads_list, thread_list_entry) res += scnprintf(buffer + res, PAGE_SIZE - res, "%d%s", task_pid_vnr(t->cmd_thread), list_is_last(&t->thread_list_entry, &cmd_threads->threads_list) ? "\n" : " "); spin_unlock(&cmd_threads->thr_lock); return res; } static struct kobj_attribute tgt_dev_thread_pid_attr = __ATTR(thread_pid, S_IRUGO, scst_tgt_dev_thread_pid_show, NULL); static ssize_t scst_tgt_dev_active_commands_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_tgt_dev *tgt_dev; tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); pos = sprintf(buf, "%d\n", atomic_read(&tgt_dev->tgt_dev_cmd_count)); return pos; } static struct kobj_attribute tgt_dev_active_commands_attr = __ATTR(active_commands, S_IRUGO, scst_tgt_dev_active_commands_show, NULL); static ssize_t scst_tgt_dev_dif_checks_failed_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos = 0; struct scst_tgt_dev *tgt_dev; tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); pos = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "\tapp\tref\tguard\n" "tgt\t%d\t%d\t%d\nscst\t%d\t%d\t%d\ndev\t%d\t%d\t%d\n", atomic_read(&tgt_dev->tgt_dev_dif_app_failed_tgt), atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_tgt), atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_tgt), atomic_read(&tgt_dev->tgt_dev_dif_app_failed_scst), atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_scst), atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_scst), atomic_read(&tgt_dev->tgt_dev_dif_app_failed_dev), atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_dev), atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_dev)); return pos; } static ssize_t scst_tgt_dev_dif_checks_failed_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_tgt_dev *tgt_dev; tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); PRINT_INFO("Zeroing DIF failures statistics for initiator " "%s, target %s, LUN %lld", tgt_dev->sess->initiator_name, tgt_dev->sess->tgt->tgt_name, (unsigned long long)tgt_dev->lun); atomic_set(&tgt_dev->tgt_dev_dif_app_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_app_failed_scst, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_scst, 0); atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_scst, 0); atomic_set(&tgt_dev->tgt_dev_dif_app_failed_dev, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_dev, 0); atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_dev, 0); return count; } static struct kobj_attribute tgt_dev_dif_checks_failed_attr = __ATTR(dif_checks_failed, S_IRUGO | S_IWUSR, scst_tgt_dev_dif_checks_failed_show, scst_tgt_dev_dif_checks_failed_store); static struct attribute *scst_tgt_dev_attrs[] = { &tgt_dev_thread_idx_attr.attr, &tgt_dev_thread_pid_attr.attr, &tgt_dev_active_commands_attr.attr, NULL, }; static void scst_sysfs_tgt_dev_release(struct kobject *kobj) { struct scst_tgt_dev *tgt_dev; TRACE_ENTRY(); tgt_dev = container_of(kobj, struct scst_tgt_dev, tgt_dev_kobj); if (tgt_dev->tgt_dev_kobj_release_cmpl) complete_all(tgt_dev->tgt_dev_kobj_release_cmpl); TRACE_EXIT(); return; } static struct kobj_type scst_tgt_dev_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_sysfs_tgt_dev_release, .default_attrs = scst_tgt_dev_attrs, }; int scst_tgt_dev_sysfs_create(struct scst_tgt_dev *tgt_dev) { int res = 0; TRACE_ENTRY(); res = kobject_init_and_add(&tgt_dev->tgt_dev_kobj, &scst_tgt_dev_ktype, &tgt_dev->sess->sess_kobj, "lun%lld", (unsigned long long)tgt_dev->lun); if (res != 0) { PRINT_ERROR("Can't add tgt_dev %lld to sysfs", (unsigned long long)tgt_dev->lun); goto out; } if (tgt_dev->sess->tgt->tgt_dif_supported && (tgt_dev->dev->dev_dif_type != 0)) { res = sysfs_create_file(&tgt_dev->tgt_dev_kobj, &tgt_dev_dif_checks_failed_attr.attr); if (res != 0) { PRINT_ERROR("Adding %s sysfs attribute to tgt_dev %lld " "failed (%d)", tgt_dev_dif_checks_failed_attr.attr.name, (unsigned long long)tgt_dev->lun, res); goto out_del; } } out: TRACE_EXIT_RES(res); return res; out_del: kobject_del(&tgt_dev->tgt_dev_kobj); kobject_put(&tgt_dev->tgt_dev_kobj); goto out; } /* * Called with scst_mutex held. * * !! No sysfs works must use kobject_get() to protect tgt_dev, due to possible * !! deadlock with scst_mutex (it is waiting for the last put, but * !! the last ref counter holder is waiting for scst_mutex) */ void scst_tgt_dev_sysfs_del(struct scst_tgt_dev *tgt_dev) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); tgt_dev->tgt_dev_kobj_release_cmpl = &c; kobject_del(&tgt_dev->tgt_dev_kobj); SCST_KOBJECT_PUT_AND_WAIT(&tgt_dev->tgt_dev_kobj, "tgt_dev", &c, &scst_tgt_dev_dep_map); TRACE_EXIT(); return; } /* ** Sessions subdirectory implementation **/ /* Calculate int_sqrt64((sumsq - sum * sum / count) / count) */ static u64 calc_stddev(u64 sumsq, u64 sum, u32 count) { u64 d = sum * sum; do_div(d, count); d = sumsq - d; do_div(d, count); return int_sqrt64(d); } 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; u64 d_min_div_10, d_max_div_10, avg_div_10, stddev_div_10; u32 d_min_mod_10, d_max_mod_10, avg_mod_10, stddev_mod_10; 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; } res = kstrtol(attr->attr.name + 1, 0, &sz); 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; if (!lat_stats || res >= PAGE_SIZE) continue; d = &lat_stats->ls[i][j][k]; if (d->count == 0) continue; scst_get_cmd_state_name(state_name, sizeof(state_name), k); avg = d->sum; do_div(avg, d->count); stddev = calc_stddev(d->sumsq, d->sum, d->count); d_min_div_10 = d->min; d_min_mod_10 = do_div(d_min_div_10, 10); d_max_div_10 = d->max; d_max_mod_10 = do_div(d_max_div_10, 10); avg_div_10 = avg; avg_mod_10 = do_div(avg_div_10, 10); stddev_div_10 = stddev; stddev_mod_10 = do_div(stddev_div_10, 10); res += scnprintf(buf + res, PAGE_SIZE - res, "%s %d %lld.%01d %lld.%01d %lld.%01d %lld.%01d us\n", state_name, d->count, d_min_div_10, d_min_mod_10, d_max_div_10, d_max_mod_10, avg_div_10, avg_mod_10, stddev_div_10, stddev_mod_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; do_div(avg, count); stddev = calc_stddev(sumsq, sum, count) * numst; avg_div_10 = avg; avg_mod_10 = do_div(avg_div_10, 10); stddev_div_10 = stddev; stddev_mod_10 = do_div(stddev_div_10, 10); res += scnprintf(buf + res, PAGE_SIZE - res, "total %d - - %lld.%01d %lld.%01d us\n", count / numst, avg_div_10, avg_mod_10, stddev_div_10, stddev_mod_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) { struct scst_session *sess; sess = container_of(kobj, struct scst_session, sess_kobj); return sprintf(buf, "%i\n", atomic_read(&sess->sess_cmd_count)); } static struct kobj_attribute session_commands_attr = __ATTR(commands, S_IRUGO, scst_sess_sysfs_commands_show, NULL); static int scst_sysfs_sess_get_active_commands(struct scst_session *sess) { int res; int active_cmds = 0, t; TRACE_ENTRY(); rcu_read_lock(); for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { active_cmds += atomic_read(&tgt_dev->tgt_dev_cmd_count); } } rcu_read_unlock(); res = active_cmds; kobject_put(&sess->sess_kobj); TRACE_EXIT_RES(res); return res; } static int scst_sysfs_sess_get_active_commands_work_fn(struct scst_sysfs_work_item *work) { return scst_sysfs_sess_get_active_commands(work->sess); } static ssize_t scst_sess_sysfs_active_commands_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int res; struct scst_session *sess; struct scst_sysfs_work_item *work; sess = container_of(kobj, struct scst_session, sess_kobj); res = scst_alloc_sysfs_work(scst_sysfs_sess_get_active_commands_work_fn, true, &work); if (res != 0) goto out; work->sess = sess; SCST_SET_DEP_MAP(work, &scst_sess_dep_map); kobject_get(&sess->sess_kobj); res = scst_sysfs_queue_wait_work(work); if (res != -EAGAIN) res = sprintf(buf, "%i\n", res); out: return res; } static struct kobj_attribute session_active_commands_attr = __ATTR(active_commands, S_IRUGO, scst_sess_sysfs_active_commands_show, NULL); static int scst_sysfs_sess_get_dif_checks_failed_work_fn(struct scst_sysfs_work_item *work) { int res, t; struct scst_session *sess = work->sess; int app_failed_tgt = 0, ref_failed_tgt = 0, guard_failed_tgt = 0; int app_failed_scst = 0, ref_failed_scst = 0, guard_failed_scst = 0; int app_failed_dev = 0, ref_failed_dev = 0, guard_failed_dev = 0; TRACE_ENTRY(); rcu_read_lock(); for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { app_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_tgt); ref_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_tgt); guard_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_tgt); app_failed_scst += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_scst); ref_failed_scst += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_scst); guard_failed_scst += atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_scst); app_failed_dev += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_dev); ref_failed_dev += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_dev); guard_failed_dev += atomic_read(&tgt_dev->tgt_dev_dif_guard_failed_dev); } } rcu_read_unlock(); work->res_buf = kasprintf(GFP_KERNEL, "\tapp\tref\tguard\n" "tgt\t%d\t%d\t%d\nscst\t%d\t%d\t%d\ndev\t%d\t%d\t%d\n", app_failed_tgt, ref_failed_tgt, guard_failed_tgt, app_failed_scst, ref_failed_scst, guard_failed_scst, app_failed_dev, ref_failed_dev, guard_failed_dev); res = work->res_buf ? 0 : -ENOMEM; kobject_put(&sess->sess_kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_sess_sysfs_dif_checks_failed_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int res; struct scst_session *sess; struct scst_sysfs_work_item *work; sess = container_of(kobj, struct scst_session, sess_kobj); res = scst_alloc_sysfs_work(scst_sysfs_sess_get_dif_checks_failed_work_fn, true, &work); if (res != 0) goto out; work->sess = sess; SCST_SET_DEP_MAP(work, &scst_sess_dep_map); kobject_get(&sess->sess_kobj); scst_sysfs_work_get(work); res = scst_sysfs_queue_wait_work(work); if (res != 0) goto out_put; res = snprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s", work->res_buf); out_put: scst_sysfs_work_put(work); out: return res; } static int scst_sess_zero_dif_checks_failed(struct scst_sysfs_work_item *work) { int res, t; struct scst_session *sess = work->sess; TRACE_ENTRY(); PRINT_INFO("Zeroing DIF failures statistics for initiator " "%s, target %s", sess->initiator_name, sess->tgt->tgt_name); rcu_read_lock(); for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { atomic_set(&tgt_dev->tgt_dev_dif_app_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_tgt, 0); atomic_set(&tgt_dev->tgt_dev_dif_app_failed_scst, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_scst, 0); atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_scst, 0); atomic_set(&tgt_dev->tgt_dev_dif_app_failed_dev, 0); atomic_set(&tgt_dev->tgt_dev_dif_ref_failed_dev, 0); atomic_set(&tgt_dev->tgt_dev_dif_guard_failed_dev, 0); } } rcu_read_unlock(); res = 0; kobject_put(&sess->sess_kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_sess_sysfs_dif_checks_failed_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_session *sess; struct scst_sysfs_work_item *work; TRACE_ENTRY(); sess = container_of(kobj, struct scst_session, sess_kobj); res = scst_alloc_sysfs_work(scst_sess_zero_dif_checks_failed, false, &work); if (res != 0) goto out; work->sess = sess; SCST_SET_DEP_MAP(work, &scst_sess_dep_map); kobject_get(&sess->sess_kobj); res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute session_dif_checks_failed_attr = __ATTR(dif_checks_failed, S_IRUGO | S_IWUSR, scst_sess_sysfs_dif_checks_failed_show, scst_sess_sysfs_dif_checks_failed_store); static ssize_t scst_sess_sysfs_initiator_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_session *sess; sess = container_of(kobj, struct scst_session, sess_kobj); return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", sess->initiator_name); } static struct kobj_attribute session_initiator_name_attr = __ATTR(initiator_name, S_IRUGO, scst_sess_sysfs_initiator_name_show, NULL); #define SCST_SESS_SYSFS_STAT_ATTR(name, exported_name, dir, kb) \ static ssize_t scst_sess_sysfs_##exported_name##_show(struct kobject *kobj, \ struct kobj_attribute *attr, char *buf) \ { \ struct scst_session *sess; \ int res; \ uint64_t v; \ \ BUILD_BUG_ON(SCST_DATA_UNKNOWN != 0); \ BUILD_BUG_ON(SCST_DATA_WRITE != 1); \ BUILD_BUG_ON(SCST_DATA_READ != 2); \ BUILD_BUG_ON(SCST_DATA_BIDI != 3); \ BUILD_BUG_ON(SCST_DATA_NONE != 4); \ \ BUILD_BUG_ON(dir >= SCST_DATA_DIR_MAX); \ \ sess = container_of(kobj, struct scst_session, sess_kobj); \ v = sess->io_stats[dir].name; \ if (kb) \ v >>= 10; \ res = sprintf(buf, "%llu\n", (unsigned long long)v); \ return res; \ } \ \ static ssize_t scst_sess_sysfs_##exported_name##_store(struct kobject *kobj, \ struct kobj_attribute *attr, const char *buf, size_t count) \ { \ struct scst_session *sess; \ sess = container_of(kobj, struct scst_session, sess_kobj); \ spin_lock_irq(&sess->sess_list_lock); \ BUILD_BUG_ON(dir >= SCST_DATA_DIR_MAX); \ sess->io_stats[dir].cmd_count = 0; \ sess->io_stats[dir].io_byte_count = 0; \ sess->io_stats[dir].unaligned_cmd_count = 0; \ spin_unlock_irq(&sess->sess_list_lock); \ return count; \ } \ \ static struct kobj_attribute session_##exported_name##_attr = \ __ATTR(exported_name, S_IRUGO | S_IWUSR, \ scst_sess_sysfs_##exported_name##_show, \ scst_sess_sysfs_##exported_name##_store) SCST_SESS_SYSFS_STAT_ATTR(cmd_count, unknown_cmd_count, SCST_DATA_UNKNOWN, 0); SCST_SESS_SYSFS_STAT_ATTR(cmd_count, write_cmd_count, SCST_DATA_WRITE, 0); SCST_SESS_SYSFS_STAT_ATTR(io_byte_count, write_io_count_kb, SCST_DATA_WRITE, 1); SCST_SESS_SYSFS_STAT_ATTR(unaligned_cmd_count, write_unaligned_cmd_count, SCST_DATA_WRITE, 0); SCST_SESS_SYSFS_STAT_ATTR(cmd_count, read_cmd_count, SCST_DATA_READ, 0); SCST_SESS_SYSFS_STAT_ATTR(io_byte_count, read_io_count_kb, SCST_DATA_READ, 1); SCST_SESS_SYSFS_STAT_ATTR(unaligned_cmd_count, read_unaligned_cmd_count, SCST_DATA_READ, 0); SCST_SESS_SYSFS_STAT_ATTR(cmd_count, bidi_cmd_count, SCST_DATA_BIDI, 0); SCST_SESS_SYSFS_STAT_ATTR(io_byte_count, bidi_io_count_kb, SCST_DATA_BIDI, 1); SCST_SESS_SYSFS_STAT_ATTR(unaligned_cmd_count, bidi_unaligned_cmd_count, SCST_DATA_BIDI, 0); SCST_SESS_SYSFS_STAT_ATTR(cmd_count, none_cmd_count, SCST_DATA_NONE, 0); static ssize_t scst_sess_force_close_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_session *sess = container_of(kobj, struct scst_session, sess_kobj); int res; res = sess->tgt->tgtt->close_session(sess); if (res < 0) goto out; res = count; out: return res; } static struct kobj_attribute session_force_close_attr = __ATTR(force_close, S_IWUSR, NULL, scst_sess_force_close_store); static struct attribute *scst_session_attrs[] = { &session_commands_attr.attr, &session_active_commands_attr.attr, &session_initiator_name_attr.attr, &session_unknown_cmd_count_attr.attr, &session_write_cmd_count_attr.attr, &session_write_io_count_kb_attr.attr, &session_write_unaligned_cmd_count_attr.attr, &session_read_cmd_count_attr.attr, &session_read_io_count_kb_attr.attr, &session_read_unaligned_cmd_count_attr.attr, &session_bidi_cmd_count_attr.attr, &session_bidi_io_count_kb_attr.attr, &session_bidi_unaligned_cmd_count_attr.attr, &session_none_cmd_count_attr.attr, NULL, }; static void scst_sysfs_session_release(struct kobject *kobj) { struct scst_session *sess; TRACE_ENTRY(); sess = container_of(kobj, struct scst_session, sess_kobj); if (sess->sess_kobj_release_cmpl) complete_all(sess->sess_kobj_release_cmpl); TRACE_EXIT(); return; } static struct kobj_type scst_session_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_sysfs_session_release, .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; /* * No locks are needed, because sess supposed to be in acg->acg_sess_list * and tgt->sess_list, so blocking them from disappearing. */ if (sess->acg == sess->tgt->default_acg) res = sysfs_create_link(&sess->sess_kobj, sess->tgt->tgt_luns_kobj, "luns"); else res = sysfs_create_link(&sess->sess_kobj, sess->acg->luns_kobj, "luns"); if (res != 0) PRINT_ERROR("Can't create luns link for initiator %s", sess->initiator_name); return res; } int scst_recreate_sess_luns_link(struct scst_session *sess) { sysfs_remove_link(&sess->sess_kobj, "luns"); return scst_create_sess_luns_link(sess); } /* Supposed to be called under scst_mutex */ int scst_sess_sysfs_create(struct scst_session *sess) { int res = 0; const char *name; TRACE_ENTRY(); name = sess->sess_name; TRACE_DBG("Adding session %s to sysfs", name); res = kobject_init_and_add(&sess->sess_kobj, &scst_session_ktype, sess->tgt->tgt_sess_kobj, name); if (res != 0) { PRINT_ERROR("Can't add session %s to sysfs", name); goto out; } sess->sess_kobj_ready = 1; if (sess->tgt->tgtt->close_session) { res = sysfs_create_file(&sess->sess_kobj, &session_force_close_attr.attr); if (res != 0) { PRINT_ERROR("Adding force_close sysfs attribute to session %s failed (%d)", name, res); goto out_del; } } if (sess->tgt->tgt_dif_supported) { res = sysfs_create_file(&sess->sess_kobj, &session_dif_checks_failed_attr.attr); if (res != 0) { PRINT_ERROR("Adding %s sysfs attribute to session %s " "failed (%d)", session_dif_checks_failed_attr.attr.name, name, res); goto out_del; } } if (sess->tgt->tgtt->sess_attrs) { res = sysfs_create_files(&sess->sess_kobj, sess->tgt->tgtt->sess_attrs); if (res != 0) { PRINT_ERROR("Can't add attributes for session %s", name); goto out_del; } } res = scst_create_sess_luns_link(sess); if (res != 0) { PRINT_ERROR("Can't add LUN links for session %s", name); goto out_del; } res = scst_create_latency_attrs(sess); if (res != 0) goto out_del; out: TRACE_EXIT_RES(res); return res; out_del: kobject_del(&sess->sess_kobj); kobject_put(&sess->sess_kobj); sess->sess_kobj_ready = 0; goto out; } /* * Must not be called under scst_mutex, due to possible deadlock with * sysfs ref counting in sysfs works (it is waiting for the last put, but * the last ref counter holder is waiting for scst_mutex) */ void scst_sess_sysfs_del(struct scst_session *sess) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); if (!sess->sess_kobj_ready) goto out; TRACE_DBG("Deleting session %s from sysfs", kobject_name(&sess->sess_kobj)); 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, &scst_sess_dep_map); out: TRACE_EXIT(); return; } /* ** Target luns directory implementation **/ static void scst_acg_dev_release(struct kobject *kobj) { struct scst_acg_dev *acg_dev; TRACE_ENTRY(); acg_dev = container_of(kobj, struct scst_acg_dev, acg_dev_kobj); if (acg_dev->acg_dev_kobj_release_cmpl) complete_all(acg_dev->acg_dev_kobj_release_cmpl); TRACE_EXIT(); return; } static ssize_t scst_lun_rd_only_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg_dev *acg_dev; acg_dev = container_of(kobj, struct scst_acg_dev, acg_dev_kobj); if (acg_dev->acg_dev_rd_only || acg_dev->dev->dev_rd_only) return sprintf(buf, "%d\n%s\n", 1, SCST_SYSFS_KEY_MARK); else return sprintf(buf, "%d\n", 0); } static struct kobj_attribute lun_options_attr = __ATTR(read_only, S_IRUGO, scst_lun_rd_only_show, NULL); static struct attribute *lun_attrs[] = { &lun_options_attr.attr, NULL, }; static struct kobj_type acg_dev_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_acg_dev_release, .default_attrs = lun_attrs, }; /* * Called with scst_mutex held. * * !! No sysfs works must use kobject_get() to protect acg_dev, due to possible * !! deadlock with scst_mutex (it is waiting for the last put, but * !! the last ref counter holder is waiting for scst_mutex) */ void scst_acg_dev_sysfs_del(struct scst_acg_dev *acg_dev) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); acg_dev->acg_dev_kobj_release_cmpl = &c; if (acg_dev->dev != NULL) { sysfs_remove_link(acg_dev->dev->dev_exp_kobj, acg_dev->acg_dev_link_name); kobject_put(&acg_dev->dev->dev_kobj); } kobject_del(&acg_dev->acg_dev_kobj); SCST_KOBJECT_PUT_AND_WAIT(&acg_dev->acg_dev_kobj, "acg_dev", &c, &scst_acg_dev_dep_map); TRACE_EXIT(); return; } int scst_acg_dev_sysfs_create(struct scst_acg_dev *acg_dev, struct kobject *parent) { int res; TRACE_ENTRY(); res = kobject_init_and_add(&acg_dev->acg_dev_kobj, &acg_dev_ktype, parent, "%llu", acg_dev->lun); if (res != 0) { PRINT_ERROR("Can't add acg_dev %p to sysfs", acg_dev); goto out; } kobject_get(&acg_dev->dev->dev_kobj); snprintf(acg_dev->acg_dev_link_name, sizeof(acg_dev->acg_dev_link_name), "export%u", acg_dev->dev->dev_exported_lun_num++); res = sysfs_create_link(acg_dev->dev->dev_exp_kobj, &acg_dev->acg_dev_kobj, acg_dev->acg_dev_link_name); if (res != 0) { PRINT_ERROR("Can't create acg %s LUN link", acg_dev->acg->acg_name); goto out_del; } res = sysfs_create_link(&acg_dev->acg_dev_kobj, &acg_dev->dev->dev_kobj, "device"); if (res != 0) { PRINT_ERROR("Can't create acg %s device link", acg_dev->acg->acg_name); goto out_del; } out: return res; out_del: scst_acg_dev_sysfs_del(acg_dev); goto out; } /* ** ini_groups directory implementation. **/ static void scst_acg_release(struct kobject *kobj) { struct scst_acg *acg; TRACE_ENTRY(); acg = container_of(kobj, struct scst_acg, acg_kobj); if (acg->acg_kobj_release_cmpl) complete_all(acg->acg_kobj_release_cmpl); TRACE_EXIT(); return; } static struct kobj_type acg_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_acg_release, }; static ssize_t scst_acg_ini_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add INITIATOR_NAME\" >mgmt\n" " echo \"del INITIATOR_NAME\" >mgmt\n" " echo \"move INITIATOR_NAME DEST_GROUP_NAME\" >mgmt\n" " echo \"clear\" >mgmt\n"; return sprintf(buf, "%s", help); } static int scst_process_acg_ini_mgmt_store(char *buffer, struct scst_tgt *tgt, struct scst_acg *acg) { int res, action; char *p, *pp, *name, *group; struct scst_acg *acg_dest = NULL; struct scst_acn *acn = NULL, *acn_tmp; enum { SCST_ACG_ACTION_INI_ADD = 1, SCST_ACG_ACTION_INI_DEL = 2, SCST_ACG_ACTION_INI_CLEAR = 3, SCST_ACG_ACTION_INI_MOVE = 4, }; TRACE_ENTRY(); TRACE_DBG("tgt %p, acg %p, buffer %s", tgt, acg, buffer); pp = buffer; p = scst_get_next_lexem(&pp); if (strcasecmp("add", p) == 0) { action = SCST_ACG_ACTION_INI_ADD; } else if (strcasecmp("del", p) == 0) { action = SCST_ACG_ACTION_INI_DEL; } else if (strcasecmp("clear", p) == 0) { action = SCST_ACG_ACTION_INI_CLEAR; } else if (strcasecmp("move", p) == 0) { action = SCST_ACG_ACTION_INI_MOVE; } else { PRINT_ERROR("Unknown action \"%s\"", p); res = -EINVAL; goto out; } res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); if (res != 0) goto out; res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out_resume; /* Check if tgt and acg not already freed while we were coming here */ if (scst_check_tgt_acg_ptrs(tgt, acg) != 0) goto out_unlock; switch (action) { case SCST_ACG_ACTION_INI_ADD: name = scst_get_next_lexem(&pp); if (name[0] == '\0') { PRINT_ERROR("%s", "Invalid initiator name"); res = -EINVAL; goto out_unlock; } res = scst_acg_add_acn(acg, name); if (res != 0) goto out_unlock; break; case SCST_ACG_ACTION_INI_DEL: name = scst_get_next_lexem(&pp); if (name[0] == '\0') { PRINT_ERROR("%s", "Invalid initiator name"); res = -EINVAL; goto out_unlock; } acn = scst_find_acn(acg, name); if (acn == NULL) { PRINT_ERROR("Unable to find " "initiator '%s' in group '%s'", name, acg->acg_name); res = -EINVAL; goto out_unlock; } scst_del_free_acn(acn, true); break; case SCST_ACG_ACTION_INI_CLEAR: list_for_each_entry_safe(acn, acn_tmp, &acg->acn_list, acn_list_entry) { scst_del_free_acn(acn, false); } scst_check_reassign_sessions(); break; case SCST_ACG_ACTION_INI_MOVE: name = scst_get_next_lexem(&pp); if (name[0] == '\0') { PRINT_ERROR("%s", "Invalid initiator name"); res = -EINVAL; goto out_unlock; } group = scst_get_next_lexem(&pp); if (group[0] == '\0') { PRINT_ERROR("%s", "Invalid group name"); res = -EINVAL; goto out_unlock; } TRACE_DBG("Move initiator '%s' to group '%s'", name, group); acn = scst_find_acn(acg, name); if (acn == NULL) { PRINT_ERROR("Unable to find " "initiator '%s' in group '%s'", name, acg->acg_name); res = -EINVAL; goto out_unlock; } acg_dest = scst_tgt_find_acg(tgt, group); if (acg_dest == NULL) { PRINT_ERROR("Unable to find group '%s' in target '%s'", group, tgt->tgt_name); res = -EINVAL; goto out_unlock; } if (scst_find_acn(acg_dest, name) != NULL) { PRINT_ERROR("Initiator '%s' already exists in group '%s'", name, acg_dest->acg_name); res = -EEXIST; goto out_unlock; } scst_del_free_acn(acn, false); res = scst_acg_add_acn(acg_dest, name); if (res != 0) goto out_unlock; break; } res = 0; out_unlock: mutex_unlock(&scst_mutex); out_resume: scst_resume_activity(); out: TRACE_EXIT_RES(res); return res; } static int scst_acg_ini_mgmt_store_work_fn(struct scst_sysfs_work_item *work) { return scst_process_acg_ini_mgmt_store(work->buf, work->tgt, work->acg); } static ssize_t scst_acg_ini_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_acg *acg; acg = container_of(kobj->parent, struct scst_acg, acg_kobj); return __scst_acg_mgmt_store(acg, buf, count, false, scst_acg_ini_mgmt_store_work_fn); } static struct kobj_attribute scst_acg_ini_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_acg_ini_mgmt_show, scst_acg_ini_mgmt_store); static ssize_t scst_acg_luns_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; acg = container_of(kobj->parent, struct scst_acg, acg_kobj); res = __scst_luns_mgmt_store(acg, false, buf, count); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_acg_luns_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_luns_mgmt_show, scst_acg_luns_mgmt_store); static ssize_t scst_acg_addr_method_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); return __scst_acg_addr_method_show(acg, buf); } static ssize_t scst_acg_addr_method_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); res = __scst_acg_addr_method_store(acg, buf, count); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_acg_addr_method = __ATTR(addr_method, S_IRUGO | S_IWUSR, scst_acg_addr_method_show, scst_acg_addr_method_store); static ssize_t scst_acg_io_grouping_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); return __scst_acg_io_grouping_type_show(acg, buf); } static ssize_t scst_acg_io_grouping_type_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); res = __scst_acg_io_grouping_type_store(acg, buf, count); if (res != 0) goto out; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_acg_io_grouping_type = __ATTR(io_grouping_type, S_IRUGO | S_IWUSR, scst_acg_io_grouping_type_show, scst_acg_io_grouping_type_store); static ssize_t scst_acg_black_hole_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); return __scst_acg_black_hole_show(acg, buf); } static ssize_t scst_acg_black_hole_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); res = __scst_acg_black_hole_store(acg, buf, count); if (res != 0) goto out; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_acg_black_hole = __ATTR(black_hole, S_IRUGO | S_IWUSR, scst_acg_black_hole_show, scst_acg_black_hole_store); static ssize_t scst_acg_cpu_mask_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); return __scst_acg_cpu_mask_show(acg, buf); } static ssize_t scst_acg_cpu_mask_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_acg *acg; acg = container_of(kobj, struct scst_acg, acg_kobj); res = __scst_acg_cpu_mask_store(acg, buf, count); if (res != 0) goto out; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_acg_cpu_mask = __ATTR(cpu_mask, S_IRUGO | S_IWUSR, scst_acg_cpu_mask_show, scst_acg_cpu_mask_store); /* * Called with scst_mutex held. * * !! No sysfs works must use kobject_get() to protect acg, due to possible * !! deadlock with scst_mutex (it is waiting for the last put, but * !! the last ref counter holder is waiting for scst_mutex) */ void scst_acg_sysfs_del(struct scst_acg *acg) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); acg->acg_kobj_release_cmpl = &c; kobject_del(acg->luns_kobj); kobject_del(acg->initiators_kobj); kobject_del(&acg->acg_kobj); kobject_put(acg->luns_kobj); kobject_put(acg->initiators_kobj); SCST_KOBJECT_PUT_AND_WAIT(&acg->acg_kobj, "acg", &c, &scst_acg_dep_map); TRACE_EXIT(); return; } int scst_acg_sysfs_create(struct scst_tgt *tgt, struct scst_acg *acg) { int res = 0; TRACE_ENTRY(); res = kobject_init_and_add(&acg->acg_kobj, &acg_ktype, tgt->tgt_ini_grp_kobj, acg->acg_name); if (res != 0) { PRINT_ERROR("Can't add acg '%s' to sysfs", acg->acg_name); goto out; } acg->luns_kobj = kobject_create_and_add("luns", &acg->acg_kobj); if (acg->luns_kobj == NULL) { PRINT_ERROR("Can't create luns kobj for tgt %s", tgt->tgt_name); res = -ENOMEM; goto out_del; } res = sysfs_create_file(acg->luns_kobj, &scst_acg_luns_mgmt.attr); if (res != 0) { PRINT_ERROR("Can't add tgt attr %s for tgt %s", scst_acg_luns_mgmt.attr.name, tgt->tgt_name); goto out_del; } acg->initiators_kobj = kobject_create_and_add("initiators", &acg->acg_kobj); if (acg->initiators_kobj == NULL) { PRINT_ERROR("Can't create initiators kobj for tgt %s", tgt->tgt_name); res = -ENOMEM; goto out_del; } res = sysfs_create_file(acg->initiators_kobj, &scst_acg_ini_mgmt.attr); if (res != 0) { PRINT_ERROR("Can't add tgt attr %s for tgt %s", scst_acg_ini_mgmt.attr.name, tgt->tgt_name); goto out_del; } res = sysfs_create_file(&acg->acg_kobj, &scst_acg_addr_method.attr); if (res != 0) { PRINT_ERROR("Can't add tgt attr %s for tgt %s", scst_acg_addr_method.attr.name, tgt->tgt_name); goto out_del; } res = sysfs_create_file(&acg->acg_kobj, &scst_acg_io_grouping_type.attr); if (res != 0) { PRINT_ERROR("Can't add tgt attr %s for tgt %s", scst_acg_io_grouping_type.attr.name, tgt->tgt_name); goto out_del; } res = sysfs_create_file(&acg->acg_kobj, &scst_acg_black_hole.attr); if (res != 0) { PRINT_ERROR("Can't add tgt attr %s for tgt %s", scst_acg_black_hole.attr.name, tgt->tgt_name); goto out_del; } res = sysfs_create_file(&acg->acg_kobj, &scst_acg_cpu_mask.attr); if (res != 0) { PRINT_ERROR("Can't add tgt attr %s for tgt %s", scst_acg_cpu_mask.attr.name, tgt->tgt_name); goto out_del; } if (acg->tgt->tgtt->acg_attrs) { res = sysfs_create_files(&acg->acg_kobj, acg->tgt->tgtt->acg_attrs); if (res != 0) { PRINT_ERROR("Can't add attributes for acg %s", acg->acg_name); goto out_del; } } out: TRACE_EXIT_RES(res); return res; out_del: scst_acg_sysfs_del(acg); goto out; } /* ** acn **/ static ssize_t scst_acn_file_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", attr->attr.name); } int scst_acn_sysfs_create(struct scst_acn *acn) { int res = 0; struct scst_acg *acg = acn->acg; struct kobj_attribute *attr = NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) #ifdef CONFIG_DEBUG_LOCK_ALLOC static struct lock_class_key __key; #endif #endif TRACE_ENTRY(); acn->acn_attr = NULL; attr = kzalloc(sizeof(struct kobj_attribute), GFP_KERNEL); if (attr == NULL) { PRINT_ERROR("Unable to allocate attributes for initiator '%s'", acn->name); res = -ENOMEM; goto out; } attr->attr.name = kstrdup(acn->name, GFP_KERNEL); if (attr->attr.name == NULL) { PRINT_ERROR("Unable to allocate attributes for initiator '%s'", acn->name); res = -ENOMEM; goto out_free; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) attr->attr.owner = THIS_MODULE; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) #ifdef CONFIG_DEBUG_LOCK_ALLOC attr->attr.key = &__key; #endif #endif attr->attr.mode = S_IRUGO; attr->show = scst_acn_file_show; attr->store = NULL; res = sysfs_create_file(acg->initiators_kobj, &attr->attr); if (res != 0) { PRINT_ERROR("Unable to create acn '%s' for group '%s'", acn->name, acg->acg_name); kfree(attr->attr.name); goto out_free; } acn->acn_attr = attr; out: TRACE_EXIT_RES(res); return res; out_free: kfree(attr); goto out; } void scst_acn_sysfs_del(struct scst_acn *acn) { struct scst_acg *acg = acn->acg; TRACE_ENTRY(); if (acn->acn_attr != NULL) { sysfs_remove_file(acg->initiators_kobj, &acn->acn_attr->attr); kfree(acn->acn_attr->attr.name); kfree(acn->acn_attr); } TRACE_EXIT(); return; } /* ** Dev handlers **/ static void scst_devt_release(struct kobject *kobj) { struct scst_dev_type *devt; TRACE_ENTRY(); devt = container_of(kobj, struct scst_dev_type, devt_kobj); if (devt->devt_kobj_release_compl) complete_all(devt->devt_kobj_release_compl); TRACE_EXIT(); return; } #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) static ssize_t scst_devt_trace_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_dev_type *devt; devt = container_of(kobj, struct scst_dev_type, devt_kobj); return scst_trace_level_show(devt->trace_tbl, devt->trace_flags ? *devt->trace_flags : 0, buf, devt->trace_tbl_help); } static ssize_t scst_devt_trace_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; struct scst_dev_type *devt; TRACE_ENTRY(); devt = container_of(kobj, struct scst_dev_type, devt_kobj); res = mutex_lock_interruptible(&scst_log_mutex); if (res != 0) goto out; res = scst_write_trace(buf, count, devt->trace_flags, devt->default_trace_flags, devt->name, devt->trace_tbl); mutex_unlock(&scst_log_mutex); out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute devt_trace_attr = __ATTR(trace_level, S_IRUGO | S_IWUSR, scst_devt_trace_level_show, scst_devt_trace_level_store); #endif /* #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */ static ssize_t scst_devt_type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int pos; struct scst_dev_type *devt; devt = container_of(kobj, struct scst_dev_type, devt_kobj); pos = sprintf(buf, "%d - %s\n", devt->type, (unsigned int)devt->type >= ARRAY_SIZE(scst_dev_handler_types) ? "unknown" : scst_dev_handler_types[devt->type]); return pos; } static struct kobj_attribute scst_devt_type_attr = __ATTR(type, S_IRUGO, scst_devt_type_show, NULL); static struct attribute *scst_devt_default_attrs[] = { &scst_devt_type_attr.attr, NULL, }; static struct kobj_type scst_devt_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_devt_release, .default_attrs = scst_devt_default_attrs, }; static char *scst_dev_params(struct scst_dev_type *devt) { char *p, *r; const char *const *q; bool comma = false; if (!devt->add_device_parameters) return NULL; p = kstrdup("The following parameters available: ", GFP_KERNEL); if (!p) return NULL; for (q = devt->add_device_parameters; *q; q++) { r = kasprintf(GFP_KERNEL, "%s%s%s", p, comma ? ", " : "", *q); kfree(p); if (!r) return NULL; p = r; comma = true; } return p; } static ssize_t scst_devt_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add_device device_name [parameters]\" >mgmt\n" " echo \"del_device device_name\" >mgmt\n" "%s%s" "%s" "\n" "where parameters are one or more " "param_name=value pairs separated by ';'\n\n" "%s%s%s%s%s%s%s%s%s\n"; struct scst_dev_type *devt; char *p; int res; devt = container_of(kobj, struct scst_dev_type, devt_kobj); p = scst_dev_params(devt); res = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, help, (devt->devt_optional_attributes != NULL) ? " echo \"add_attribute \" >mgmt\n" " echo \"del_attribute \" >mgmt\n" : "", (devt->dev_optional_attributes != NULL) ? " echo \"add_device_attribute device_name \" >mgmt\n" " echo \"del_device_attribute device_name \" >mgmt\n" : "", (devt->mgmt_cmd_help) ? devt->mgmt_cmd_help : "", (devt->mgmt_cmd_help) ? "\n" : "", p ? : "", (devt->add_device_parameters != NULL) ? "\n" : "", (devt->devt_optional_attributes != NULL) ? "The following dev handler attributes available: " : "", (devt->devt_optional_attributes != NULL) ? devt->devt_optional_attributes : "", (devt->devt_optional_attributes != NULL) ? "\n" : "", (devt->dev_optional_attributes != NULL) ? "The following device attributes available: " : "", (devt->dev_optional_attributes != NULL) ? devt->dev_optional_attributes : "", (devt->dev_optional_attributes != NULL) ? "\n" : ""); kfree(p); return res; } static int scst_process_devt_mgmt_store(char *buffer, struct scst_dev_type *devt) { int res = 0; char *p, *pp, *dev_name; TRACE_ENTRY(); /* Check if our pointer is still alive and, if yes, grab it */ if (scst_check_grab_devt_ptr(devt, &scst_virtual_dev_type_list) != 0) goto out; TRACE_DBG("devt %p, buffer %s", devt, buffer); pp = buffer; p = scst_get_next_lexem(&pp); if (strcasecmp("add_device", p) == 0) { dev_name = scst_get_next_lexem(&pp); if (*dev_name == '\0') { PRINT_ERROR("%s", "Device name required"); res = -EINVAL; goto out_ungrab; } res = devt->add_device(dev_name, pp); } else if (strcasecmp("del_device", p) == 0) { dev_name = scst_get_next_lexem(&pp); if (*dev_name == '\0') { PRINT_ERROR("%s", "Device name required"); res = -EINVAL; goto out_ungrab; } p = scst_get_next_lexem(&pp); if (*p != '\0') goto out_syntax_err; res = devt->del_device(dev_name); } else if (devt->mgmt_cmd != NULL) { scst_restore_token_str(p, pp); res = devt->mgmt_cmd(buffer); } else { PRINT_ERROR("Unknown action \"%s\"", p); res = -EINVAL; goto out_ungrab; } out_ungrab: scst_ungrab_devt_ptr(devt); out: TRACE_EXIT_RES(res); return res; out_syntax_err: PRINT_ERROR("Syntax error on \"%s\"", p); res = -EINVAL; goto out_ungrab; } static int scst_devt_mgmt_store_work_fn(struct scst_sysfs_work_item *work) { return scst_process_devt_mgmt_store(work->buf, work->devt); } static ssize_t __scst_devt_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count, int (*sysfs_work_fn)(struct scst_sysfs_work_item *work)) { int res; char *buffer; struct scst_dev_type *devt; struct scst_sysfs_work_item *work; TRACE_ENTRY(); devt = container_of(kobj, struct scst_dev_type, devt_kobj); buffer = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (buffer == NULL) { res = -ENOMEM; goto out; } res = scst_alloc_sysfs_work(sysfs_work_fn, false, &work); if (res != 0) goto out_free; work->buf = buffer; work->devt = devt; res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; out_free: kfree(buffer); goto out; } static ssize_t scst_devt_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { return __scst_devt_mgmt_store(kobj, attr, buf, count, scst_devt_mgmt_store_work_fn); } static struct kobj_attribute scst_devt_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_devt_mgmt_show, scst_devt_mgmt_store); static ssize_t scst_devt_pass_through_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add_device H:C:I:L\" >mgmt\n" " echo \"del_device H:C:I:L\" >mgmt\n"; return sprintf(buf, "%s", help); } static int scst_process_devt_pass_through_mgmt_store(char *buffer, struct scst_dev_type *devt) { int res = 0; char *pp, *action, *devstr; unsigned int host, channel, id; u64 lun; struct scst_device *d, *dev = NULL; TRACE_ENTRY(); TRACE_DBG("devt %p, buffer %s", devt, buffer); pp = buffer; action = scst_get_next_lexem(&pp); devstr = scst_get_next_lexem(&pp); if (*devstr == '\0') { PRINT_ERROR("%s", "Device required"); res = -EINVAL; goto out; } if (*scst_get_next_lexem(&pp) != '\0') { PRINT_ERROR("%s", "Too many parameters"); res = -EINVAL; goto out_syntax_err; } if (sscanf(devstr, "%u:%u:%u:%llu", &host, &channel, &id, &lun) != 4) goto out_syntax_err; TRACE_DBG("Dev %d:%d:%d:%lld", host, channel, id, lun); res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out; /* Check if devt not be already freed while we were coming here */ if (scst_check_devt_ptr(devt, &scst_dev_type_list) != 0) goto out_unlock; list_for_each_entry(d, &scst_dev_list, dev_list_entry) { if ((d->virt_id == 0) && d->scsi_dev->host->host_no == host && d->scsi_dev->channel == channel && d->scsi_dev->id == id && d->scsi_dev->lun == lun) { dev = d; TRACE_DBG("Dev %p (%d:%d:%d:%lld) found", dev, host, channel, id, lun); break; } } if (dev == NULL) { PRINT_ERROR("Device %d:%d:%d:%lld not found", host, channel, id, lun); res = -EINVAL; goto out_unlock; } if (dev->scsi_dev->type != devt->type) { PRINT_ERROR("Type %d of device %s differs from type " "%d of dev handler %s", dev->type, dev->virt_name, devt->type, devt->name); res = -EINVAL; goto out_unlock; } if (strcasecmp("add_device", action) == 0) { res = scst_assign_dev_handler(dev, devt); if (res == 0) PRINT_INFO("Device %s assigned to dev handler %s", dev->virt_name, devt->name); } else if (strcasecmp("del_device", action) == 0) { if (dev->handler != devt) { PRINT_ERROR("Device %s is not assigned to handler %s", dev->virt_name, devt->name); res = -EINVAL; goto out_unlock; } res = scst_assign_dev_handler(dev, &scst_null_devtype); if (res == 0) PRINT_INFO("Device %s unassigned from dev handler %s", dev->virt_name, devt->name); } else { PRINT_ERROR("Unknown action \"%s\"", action); res = -EINVAL; goto out_unlock; } out_unlock: mutex_unlock(&scst_mutex); out: TRACE_EXIT_RES(res); return res; out_syntax_err: PRINT_ERROR("Syntax error on \"%s\"", buffer); res = -EINVAL; goto out; } static int scst_devt_pass_through_mgmt_store_work_fn( struct scst_sysfs_work_item *work) { return scst_process_devt_pass_through_mgmt_store(work->buf, work->devt); } static ssize_t scst_devt_pass_through_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { return __scst_devt_mgmt_store(kobj, attr, buf, count, scst_devt_pass_through_mgmt_store_work_fn); } static struct kobj_attribute scst_devt_pass_through_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_devt_pass_through_mgmt_show, scst_devt_pass_through_mgmt_store); /* * Creates an attribute entry for dev handler. */ int scst_create_devt_attr(struct scst_dev_type *devt, struct kobj_attribute *attribute) { int res; res = sysfs_create_file(&devt->devt_kobj, &attribute->attr); if (res != 0) { PRINT_ERROR("Can't add attribute %s for dev handler %s", attribute->attr.name, devt->name); goto out; } out: return res; } EXPORT_SYMBOL(scst_create_devt_attr); int scst_devt_sysfs_create(struct scst_dev_type *devt) { int res; struct kobject *parent; TRACE_ENTRY(); if (devt->parent != NULL) parent = &devt->parent->devt_kobj; else parent = scst_handlers_kobj; res = kobject_init_and_add(&devt->devt_kobj, &scst_devt_ktype, parent, devt->name); if (res != 0) { PRINT_ERROR("Can't add devt %s to sysfs", devt->name); goto out; } if (devt->add_device != NULL) { res = sysfs_create_file(&devt->devt_kobj, &scst_devt_mgmt.attr); } else if (!devt->no_mgmt) { res = sysfs_create_file(&devt->devt_kobj, &scst_devt_pass_through_mgmt.attr); } if (res != 0) { PRINT_ERROR("Can't add mgmt attr for dev handler %s", devt->name); goto out_err; } if (devt->devt_attrs) { res = sysfs_create_files(&devt->devt_kobj, devt->devt_attrs); if (res != 0) { PRINT_ERROR("Can't add attributes for dev handler %s", devt->name); goto out_err; } } #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) if (devt->trace_flags != NULL) { res = sysfs_create_file(&devt->devt_kobj, &devt_trace_attr.attr); if (res != 0) { PRINT_ERROR("Can't add devt trace_flag for dev " "handler %s", devt->name); goto out_err; } } #endif out: TRACE_EXIT_RES(res); return res; out_err: scst_devt_sysfs_del(devt); goto out; } void scst_devt_sysfs_del(struct scst_dev_type *devt) { DECLARE_COMPLETION_ONSTACK(c); TRACE_ENTRY(); devt->devt_kobj_release_compl = &c; kobject_del(&devt->devt_kobj); SCST_KOBJECT_PUT_AND_WAIT(&devt->devt_kobj, "dev handler template", &c, &scst_devt_dep_map); TRACE_EXIT(); return; } /* ** SCST sysfs device_groups//devices/ implementation. **/ int scst_dg_dev_sysfs_add(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) { int res; TRACE_ENTRY(); res = sysfs_create_link(dg->dev_kobj, &dgdev->dev->dev_kobj, dgdev->dev->virt_name); TRACE_EXIT_RES(res); return res; } void scst_dg_dev_sysfs_del(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) { TRACE_ENTRY(); sysfs_remove_link(dg->dev_kobj, dgdev->dev->virt_name); TRACE_EXIT(); } /* ** SCST sysfs device_groups//devices directory implementation. **/ static ssize_t scst_dg_devs_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add device\" >mgmt\n" " echo \"del device\" >mgmt\n"; return scnprintf(buf, PAGE_SIZE, help); } static int scst_dg_devs_mgmt_store_work_fn(struct scst_sysfs_work_item *w) { struct scst_dev_group *dg; char *cmd, *p, *pp, *dev_name; int res; TRACE_ENTRY(); cmd = w->buf; dg = scst_lookup_dg_by_kobj(w->kobj); WARN_ON(!dg); p = strchr(cmd, '\n'); if (p) *p = '\0'; res = -EINVAL; pp = cmd; p = scst_get_next_lexem(&pp); if (strcasecmp(p, "add") == 0) { dev_name = scst_get_next_lexem(&pp); if (!*dev_name) goto out; res = scst_dg_dev_add(dg, dev_name); } else if (strcasecmp(p, "del") == 0) { dev_name = scst_get_next_lexem(&pp); if (!*dev_name) goto out; res = scst_dg_dev_remove_by_name(dg, dev_name); } out: kobject_put(w->kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_dg_devs_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char *cmd; struct scst_sysfs_work_item *work; int res; TRACE_ENTRY(); res = -ENOMEM; cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!cmd) goto out; res = scst_alloc_sysfs_work(scst_dg_devs_mgmt_store_work_fn, false, &work); if (res) goto out; swap(work->buf, cmd); work->kobj = kobj; SCST_SET_DEP_MAP(work, &scst_dg_dep_map); kobject_get(kobj); res = scst_sysfs_queue_wait_work(work); if (res) goto out; res = count; out: kfree(cmd); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_dg_devs_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_dg_devs_mgmt_show, scst_dg_devs_mgmt_store); static const struct attribute *scst_dg_devs_attrs[] = { &scst_dg_devs_mgmt.attr, NULL, }; /* ** SCST sysfs device_groups//target_groups// implementation. **/ static ssize_t scst_tg_tgt_rel_tgt_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tg_tgt *tg_tgt; tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); return scnprintf(buf, PAGE_SIZE, "%u\n" SCST_SYSFS_KEY_MARK "\n", tg_tgt->rel_tgt_id); } static ssize_t scst_tg_tgt_rel_tgt_id_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_tg_tgt *tg_tgt; unsigned long rel_tgt_id; char ch[8]; int res; TRACE_ENTRY(); tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); res = kstrtoul(ch, 0, &rel_tgt_id); if (res) goto out; res = -EINVAL; if (rel_tgt_id == 0 || rel_tgt_id > 0xffff) goto out; tg_tgt->rel_tgt_id = rel_tgt_id; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tg_tgt_rel_tgt_id = __ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_tg_tgt_rel_tgt_id_show, scst_tg_tgt_rel_tgt_id_store); static const struct attribute *scst_tg_tgt_attrs[] = { &scst_tg_tgt_rel_tgt_id.attr, NULL, }; int scst_tg_tgt_sysfs_add(struct scst_target_group *tg, struct scst_tg_tgt *tg_tgt) { int res; TRACE_ENTRY(); BUG_ON(!tg); BUG_ON(!tg_tgt); BUG_ON(!tg_tgt->name); if (tg_tgt->tgt) res = sysfs_create_link(&tg->kobj, &tg_tgt->tgt->tgt_kobj, tg_tgt->name); else { res = kobject_add(&tg_tgt->kobj, &tg->kobj, "%s", tg_tgt->name); if (res) goto err; res = sysfs_create_files(&tg_tgt->kobj, scst_tg_tgt_attrs); if (res) goto err; } out: TRACE_EXIT_RES(res); return res; err: scst_tg_tgt_sysfs_del(tg, tg_tgt); goto out; } void scst_tg_tgt_sysfs_del(struct scst_target_group *tg, struct scst_tg_tgt *tg_tgt) { TRACE_ENTRY(); if (tg_tgt->tgt) sysfs_remove_link(&tg->kobj, tg_tgt->name); else { sysfs_remove_files(&tg_tgt->kobj, scst_tg_tgt_attrs); kobject_del(&tg_tgt->kobj); } TRACE_EXIT(); } /* ** SCST sysfs device_groups//target_groups/ directory implementation. **/ static ssize_t scst_tg_group_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_target_group *tg; tg = container_of(kobj, struct scst_target_group, kobj); return scnprintf(buf, PAGE_SIZE, "%u\n" SCST_SYSFS_KEY_MARK "\n", tg->group_id); } static ssize_t scst_tg_group_id_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct scst_target_group *tg; unsigned long group_id; char ch[8]; int res; TRACE_ENTRY(); tg = container_of(kobj, struct scst_target_group, kobj); snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); res = kstrtoul(ch, 0, &group_id); if (res) goto out; res = -EINVAL; if (group_id == 0 || group_id > 0xffff) goto out; tg->group_id = group_id; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tg_group_id = __ATTR(group_id, S_IRUGO | S_IWUSR, scst_tg_group_id_show, scst_tg_group_id_store); static ssize_t scst_tg_preferred_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_target_group *tg; tg = container_of(kobj, struct scst_target_group, kobj); return scnprintf(buf, PAGE_SIZE, "%u\n%s", tg->preferred, tg->preferred ? SCST_SYSFS_KEY_MARK "\n" : ""); } static int scst_tg_preferred_store_work_fn(struct scst_sysfs_work_item *w) { struct scst_target_group *tg; unsigned long preferred; char *cmd; int res; TRACE_ENTRY(); cmd = w->buf; tg = container_of(w->kobj, struct scst_target_group, kobj); res = kstrtoul(cmd, 0, &preferred); if (res) goto out; res = -EINVAL; if (preferred != 0 && preferred != 1) goto out; res = scst_tg_set_preferred(tg, preferred); out: kobject_put(w->kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_tg_preferred_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char *cmd; struct scst_sysfs_work_item *work; int res; res = -ENOMEM; cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!cmd) goto out; res = scst_alloc_sysfs_work(scst_tg_preferred_store_work_fn, false, &work); if (res) goto out; swap(work->buf, cmd); work->kobj = kobj; SCST_SET_DEP_MAP(work, &scst_tg_dep_map); kobject_get(kobj); res = scst_sysfs_queue_wait_work(work); if (res) goto out; res = count; out: kfree(cmd); return res; } static struct kobj_attribute scst_tg_preferred = __ATTR(preferred, S_IRUGO | S_IWUSR, scst_tg_preferred_show, scst_tg_preferred_store); static ssize_t scst_tg_state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_target_group *tg; const char *n; tg = container_of(kobj, struct scst_target_group, kobj); n = scst_alua_state_name(tg->state); return scnprintf(buf, PAGE_SIZE, "%s\n" SCST_SYSFS_KEY_MARK "\n", n ? n : "???"); } static int scst_tg_state_store_work_fn(struct scst_sysfs_work_item *w) { struct scst_target_group *tg; char *cmd, *p; int res; enum scst_tg_state s; TRACE_ENTRY(); cmd = w->buf; tg = container_of(w->kobj, struct scst_target_group, kobj); p = strchr(cmd, '\n'); if (p) *p = '\0'; s = scst_alua_name_to_state(cmd); res = -EINVAL; if (s == SCST_TG_STATE_UNDEFINED) goto out; res = scst_tg_set_state(tg, s); out: kobject_put(w->kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_tg_state_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char *cmd; struct scst_sysfs_work_item *work; int res; TRACE_ENTRY(); res = -ENOMEM; cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!cmd) goto out; res = scst_alloc_sysfs_work(scst_tg_state_store_work_fn, false, &work); if (res) goto out; swap(work->buf, cmd); work->kobj = kobj; SCST_SET_DEP_MAP(work, &scst_tg_dep_map); kobject_get(kobj); res = scst_sysfs_queue_wait_work(work); if (res) goto out; res = count; out: kfree(cmd); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tg_state = __ATTR(state, S_IRUGO | S_IWUSR, scst_tg_state_show, scst_tg_state_store); static ssize_t scst_tg_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"add target\" >mgmt\n" " echo \"del target\" >mgmt\n"; return scnprintf(buf, PAGE_SIZE, help); } static int scst_tg_mgmt_store_work_fn(struct scst_sysfs_work_item *w) { struct scst_target_group *tg; char *cmd, *p, *pp, *target_name; int res; TRACE_ENTRY(); cmd = w->buf; tg = container_of(w->kobj, struct scst_target_group, kobj); p = strchr(cmd, '\n'); if (p) *p = '\0'; res = -EINVAL; pp = cmd; p = scst_get_next_lexem(&pp); if (strcasecmp(p, "add") == 0) { target_name = scst_get_next_lexem(&pp); if (!*target_name) goto out; res = scst_tg_tgt_add(tg, target_name); } else if (strcasecmp(p, "del") == 0) { target_name = scst_get_next_lexem(&pp); if (!*target_name) goto out; res = scst_tg_tgt_remove_by_name(tg, target_name); } out: kobject_put(w->kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_tg_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char *cmd; struct scst_sysfs_work_item *work; int res; TRACE_ENTRY(); res = -ENOMEM; cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!cmd) goto out; res = scst_alloc_sysfs_work(scst_tg_mgmt_store_work_fn, false, &work); if (res) goto out; swap(work->buf, cmd); work->kobj = kobj; SCST_SET_DEP_MAP(work, &scst_tg_dep_map); kobject_get(kobj); res = scst_sysfs_queue_wait_work(work); if (res) goto out; res = count; out: kfree(cmd); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_tg_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_tg_mgmt_show, scst_tg_mgmt_store); static const struct attribute *scst_tg_attrs[] = { &scst_tg_mgmt.attr, &scst_tg_group_id.attr, &scst_tg_preferred.attr, &scst_tg_state.attr, NULL, }; int scst_tg_sysfs_add(struct scst_dev_group *dg, struct scst_target_group *tg) { int res; TRACE_ENTRY(); res = kobject_add(&tg->kobj, dg->tg_kobj, "%s", tg->name); if (res) goto err; res = sysfs_create_files(&tg->kobj, scst_tg_attrs); if (res) goto err; out: TRACE_EXIT_RES(res); return res; err: scst_tg_sysfs_del(tg); goto out; } void scst_tg_sysfs_del(struct scst_target_group *tg) { TRACE_ENTRY(); sysfs_remove_files(&tg->kobj, scst_tg_attrs); kobject_del(&tg->kobj); TRACE_EXIT(); } /* ** SCST sysfs device_groups//target_groups directory implementation. **/ static ssize_t scst_dg_tgs_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"create group_name\" >mgmt\n" " echo \"del group_name\" >mgmt\n"; return scnprintf(buf, PAGE_SIZE, help); } static int scst_dg_tgs_mgmt_store_work_fn(struct scst_sysfs_work_item *w) { struct scst_dev_group *dg; char *cmd, *p, *pp, *dev_name; int res; TRACE_ENTRY(); cmd = w->buf; dg = scst_lookup_dg_by_kobj(w->kobj); WARN_ON(!dg); p = strchr(cmd, '\n'); if (p) *p = '\0'; res = -EINVAL; pp = cmd; p = scst_get_next_lexem(&pp); if (strcasecmp(p, "create") == 0 || strcasecmp(p, "add") == 0) { dev_name = scst_get_next_lexem(&pp); if (!*dev_name) goto out; res = scst_tg_add(dg, dev_name); } else if (strcasecmp(p, "del") == 0) { dev_name = scst_get_next_lexem(&pp); if (!*dev_name) goto out; res = scst_tg_remove_by_name(dg, dev_name); } out: kobject_put(w->kobj); TRACE_EXIT_RES(res); return res; } static ssize_t scst_dg_tgs_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char *cmd; struct scst_sysfs_work_item *work; int res; TRACE_ENTRY(); res = -ENOMEM; cmd = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); if (!cmd) goto out; res = scst_alloc_sysfs_work(scst_dg_tgs_mgmt_store_work_fn, false, &work); if (res) goto out; swap(work->buf, cmd); work->kobj = kobj; SCST_SET_DEP_MAP(work, &scst_dg_dep_map); kobject_get(kobj); res = scst_sysfs_queue_wait_work(work); if (res) goto out; res = count; out: kfree(cmd); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_dg_tgs_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_dg_tgs_mgmt_show, scst_dg_tgs_mgmt_store); static const struct attribute *scst_dg_tgs_attrs[] = { &scst_dg_tgs_mgmt.attr, NULL, }; /* ** SCST sysfs device_groups directory implementation. **/ int scst_dg_sysfs_add(struct kobject *parent, struct scst_dev_group *dg) { int res; dg->dev_kobj = NULL; dg->tg_kobj = NULL; res = kobject_add(&dg->kobj, parent, "%s", dg->name); if (res) goto err; res = -EEXIST; dg->dev_kobj = kobject_create_and_add("devices", &dg->kobj); if (!dg->dev_kobj) goto err; res = sysfs_create_files(dg->dev_kobj, scst_dg_devs_attrs); if (res) goto err; dg->tg_kobj = kobject_create_and_add("target_groups", &dg->kobj); if (!dg->tg_kobj) goto err; res = sysfs_create_files(dg->tg_kobj, scst_dg_tgs_attrs); if (res) goto err; out: return res; err: scst_dg_sysfs_del(dg); goto out; } void scst_dg_sysfs_del(struct scst_dev_group *dg) { if (dg->tg_kobj) { sysfs_remove_files(dg->tg_kobj, scst_dg_tgs_attrs); kobject_del(dg->tg_kobj); kobject_put(dg->tg_kobj); dg->tg_kobj = NULL; } if (dg->dev_kobj) { sysfs_remove_files(dg->dev_kobj, scst_dg_devs_attrs); kobject_del(dg->dev_kobj); kobject_put(dg->dev_kobj); dg->dev_kobj = NULL; } kobject_del(&dg->kobj); } static ssize_t scst_device_groups_mgmt_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { static const char help[] = "Usage: echo \"create group_name\" >mgmt\n" " echo \"del group_name\" >mgmt\n"; return scnprintf(buf, PAGE_SIZE, help); } static ssize_t scst_device_groups_mgmt_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; char *p, *pp, *input, *group_name; TRACE_ENTRY(); input = kasprintf(GFP_KERNEL, "%.*s", (int)count, buf); pp = input; p = strchr(input, '\n'); if (p) *p = '\0'; res = -EINVAL; p = scst_get_next_lexem(&pp); if (strcasecmp(p, "create") == 0 || strcasecmp(p, "add") == 0) { group_name = scst_get_next_lexem(&pp); if (!*group_name) goto out; res = scst_dg_add(scst_device_groups_kobj, group_name); } else if (strcasecmp(p, "del") == 0) { group_name = scst_get_next_lexem(&pp); if (!*group_name) goto out; res = scst_dg_remove(group_name); } out: kfree(input); if (res == 0) res = count; TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_device_groups_mgmt = __ATTR(mgmt, S_IRUGO | S_IWUSR, scst_device_groups_mgmt_show, scst_device_groups_mgmt_store); static const struct attribute *scst_device_groups_attrs[] = { &scst_device_groups_mgmt.attr, NULL, }; /* ** SCST sysfs root directory implementation **/ 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; res = kstrtol(buf, 0, &val); if (res < 0) goto out; val = !!val; res = scst_suspend_activity(10 * HZ); 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) { int count; TRACE_ENTRY(); count = sprintf(buf, "%d\n%s", scst_main_cmd_threads.nr_threads, (scst_main_cmd_threads.nr_threads != scst_threads) ? SCST_SYSFS_KEY_MARK "\n" : ""); TRACE_EXIT(); return count; } static int scst_process_threads_store(int newtn) { int res; long oldtn, delta; TRACE_ENTRY(); TRACE_DBG("newtn %d", newtn); /* * Some commands are taking scst_mutex on commands processing path, * so we need to drain them, because otherwise we can fall into a * deadlock with kthread_stop() in scst_del_threads() waiting for * those commands to finish. */ res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); if (res != 0) goto out; res = mutex_lock_interruptible(&scst_mutex); if (res != 0) goto out_resume; oldtn = scst_main_cmd_threads.nr_threads; delta = newtn - oldtn; if (delta < 0) scst_del_threads(&scst_main_cmd_threads, -delta); else { res = scst_add_threads(&scst_main_cmd_threads, NULL, NULL, delta); if (res != 0) goto out_up; } PRINT_INFO("Changed cmd threads num: old %ld, new %d", oldtn, newtn); out_up: mutex_unlock(&scst_mutex); out_resume: scst_resume_activity(); out: TRACE_EXIT_RES(res); return res; } static int scst_threads_store_work_fn(struct scst_sysfs_work_item *work) { return scst_process_threads_store(work->new_threads_num); } static ssize_t scst_threads_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; long newtn; struct scst_sysfs_work_item *work; TRACE_ENTRY(); res = kstrtol(buf, 0, &newtn); if (res != 0) { PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn <= 0) { PRINT_ERROR("Illegal threads num value %ld", newtn); res = -EINVAL; goto out; } res = scst_alloc_sysfs_work(scst_threads_store_work_fn, false, &work); if (res != 0) goto out; work->new_threads_num = newtn; res = scst_sysfs_queue_wait_work(work); if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_threads_attr = __ATTR(threads, S_IRUGO | S_IWUSR, scst_threads_show, scst_threads_store); static ssize_t scst_setup_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int count; TRACE_ENTRY(); count = sprintf(buf, "0x%x\n%s\n", scst_setup_id, (scst_setup_id == 0) ? "" : SCST_SYSFS_KEY_MARK); TRACE_EXIT(); return count; } static ssize_t scst_setup_id_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; unsigned long val; TRACE_ENTRY(); res = kstrtoul(buf, 0, &val); if (res != 0) { PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } scst_setup_id = val; PRINT_INFO("Changed scst_setup_id to %x", scst_setup_id); res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_setup_id_attr = __ATTR(setup_id, S_IRUGO | S_IWUSR, scst_setup_id_show, scst_setup_id_store); static ssize_t scst_max_tasklet_cmd_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int count; TRACE_ENTRY(); count = sprintf(buf, "%d\n%s\n", scst_max_tasklet_cmd, (scst_max_tasklet_cmd == SCST_DEF_MAX_TASKLET_CMD) ? "" : SCST_SYSFS_KEY_MARK); TRACE_EXIT(); return count; } static ssize_t scst_max_tasklet_cmd_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; unsigned long val; TRACE_ENTRY(); res = kstrtoul(buf, 0, &val); if (res != 0) { PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } scst_max_tasklet_cmd = val; PRINT_INFO("Changed scst_max_tasklet_cmd to %d", scst_max_tasklet_cmd); res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_max_tasklet_cmd_attr = __ATTR(max_tasklet_cmd, S_IRUGO | S_IWUSR, scst_max_tasklet_cmd_show, scst_max_tasklet_cmd_store); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) static ssize_t scst_poll_us_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int count; unsigned long t = scst_poll_ns; TRACE_ENTRY(); do_div(t, 1000); count = sprintf(buf, "%ld\n%s\n", t, (scst_poll_ns == SCST_DEF_POLL_NS) ? "" : SCST_SYSFS_KEY_MARK); TRACE_EXIT(); return count; } static ssize_t scst_poll_us_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; unsigned long val; TRACE_ENTRY(); res = kstrtoul(buf, 0, &val); if (res != 0) { PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } PRINT_INFO("Changed poll_us to %ld us", val); val *= 1000; scst_poll_ns = val; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_poll_us_attr = __ATTR(poll_us, S_IRUGO | S_IWUSR, scst_poll_us_show, scst_poll_us_store); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) */ static ssize_t scst_suspend_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int count; TRACE_ENTRY(); count = sprintf(buf, "%d\n", scst_get_suspend_count()); TRACE_EXIT(); return count; } static ssize_t scst_suspend_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; long val; TRACE_ENTRY(); res = kstrtol(buf, 0, &val); if (res != 0) { PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } if (val >= 0) { PRINT_INFO("SYSFS: suspending activities (timeout %ld)...", val); res = scst_suspend_activity(val*HZ); if (res == 0) PRINT_INFO("sysfs suspending done"); } else { PRINT_INFO("SYSFS: resuming activities"); scst_resume_activity(); } if (res == 0) res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_suspend_attr = __ATTR(suspend, S_IRUGO | S_IWUSR, scst_suspend_show, scst_suspend_store); #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) static ssize_t scst_main_trace_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return scst_trace_level_show(scst_local_trace_tbl, trace_flag, buf, NULL); } static ssize_t scst_main_trace_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; TRACE_ENTRY(); res = mutex_lock_interruptible(&scst_log_mutex); if (res != 0) goto out; res = scst_write_trace(buf, count, &trace_flag, SCST_DEFAULT_LOG_FLAGS, "scst", scst_local_trace_tbl); mutex_unlock(&scst_log_mutex); out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_main_trace_level_attr = __ATTR(trace_level, S_IRUGO | S_IWUSR, scst_main_trace_level_show, scst_main_trace_level_store); #endif /* defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) */ static ssize_t scst_force_global_sgv_pool_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n%s\n", scst_force_global_sgv_pool, scst_force_global_sgv_pool ? SCST_SYSFS_KEY_MARK "\n" : ""); } static ssize_t scst_force_global_sgv_pool_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int res; unsigned long v; TRACE_ENTRY(); res = kstrtoul(buf, 0, &v); if (res) goto out; scst_force_global_sgv_pool = v; res = count; out: TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_force_global_sgv_pool_attr = __ATTR(force_global_sgv_pool, S_IRUGO | S_IWUSR, scst_force_global_sgv_pool_show, scst_force_global_sgv_pool_store); static void __printf(2, 3) scst_append(void *arg, const char *fmt, ...) { char *buf = arg; int len = strlen(buf); va_list args; va_start(args, fmt); vscnprintf(buf + len, SCST_SYSFS_BLOCK_SIZE - len, fmt, args); va_end(args); } static int scst_process_show_trace_cmds(struct scst_sysfs_work_item *work) { int ret = -ENOMEM; work->res_buf = kmalloc(SCST_SYSFS_BLOCK_SIZE, GFP_KERNEL); if (!work->res_buf) goto put; work->res_buf[0] = '\0'; scst_trace_cmds(scst_append, work->res_buf); ret = 0; put: kobject_put(&scst_sysfs_root_kobj); return ret; } static ssize_t scst_show_trace_cmds(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_sysfs_work_item *work; int res; res = scst_alloc_sysfs_work(scst_process_show_trace_cmds, true, &work); if (res != 0) goto out; kobject_get(&scst_sysfs_root_kobj); scst_sysfs_work_get(work); res = scst_sysfs_queue_wait_work(work); if (res != 0) goto put; res = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s", work->res_buf); put: scst_sysfs_work_put(work); out: return res; } static struct kobj_attribute scst_trace_cmds_attr = __ATTR(trace_cmds, S_IRUGO, scst_show_trace_cmds, NULL); static int scst_process_show_trace_mcmds(struct scst_sysfs_work_item *work) { int ret = -ENOMEM; work->res_buf = kmalloc(SCST_SYSFS_BLOCK_SIZE, GFP_KERNEL); if (!work->res_buf) goto put; work->res_buf[0] = '\0'; scst_trace_mcmds(scst_append, work->res_buf); ret = 0; put: kobject_put(&scst_sysfs_root_kobj); return ret; } static ssize_t scst_show_trace_mcmds(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_sysfs_work_item *work; int res; res = scst_alloc_sysfs_work(scst_process_show_trace_mcmds, true, &work); if (res != 0) goto out; kobject_get(&scst_sysfs_root_kobj); scst_sysfs_work_get(work); res = scst_sysfs_queue_wait_work(work); if (res != 0) goto put; res = scnprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s", work->res_buf); put: scst_sysfs_work_put(work); out: return res; } static struct kobj_attribute scst_trace_mcmds_attr = __ATTR(trace_mcmds, S_IRUGO, scst_show_trace_mcmds, NULL); static ssize_t scst_version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { TRACE_ENTRY(); sprintf(buf, "%s\n", SCST_VERSION_STRING); #ifdef CONFIG_SCST_STRICT_SERIALIZING strcat(buf, "STRICT_SERIALIZING\n"); #endif #ifdef CONFIG_SCST_EXTRACHECKS strcat(buf, "EXTRACHECKS\n"); #endif #ifdef CONFIG_SCST_TRACING strcat(buf, "TRACING\n"); #endif #ifdef CONFIG_SCST_DEBUG strcat(buf, "DEBUG\n"); #endif #ifdef CONFIG_SCST_DEBUG_TM strcat(buf, "DEBUG_TM\n"); #endif #ifdef CONFIG_SCST_DEBUG_RETRY strcat(buf, "DEBUG_RETRY\n"); #endif #ifdef CONFIG_SCST_DEBUG_OOM strcat(buf, "DEBUG_OOM\n"); #endif #ifdef CONFIG_SCST_DEBUG_SN strcat(buf, "DEBUG_SN\n"); #endif #ifdef CONFIG_SCST_USE_EXPECTED_VALUES strcat(buf, "USE_EXPECTED_VALUES\n"); #endif #ifdef CONFIG_SCST_TEST_IO_IN_SIRQ strcat(buf, "TEST_IO_IN_SIRQ\n"); #endif #ifdef CONFIG_SCST_STRICT_SECURITY strcat(buf, "STRICT_SECURITY\n"); #endif TRACE_EXIT(); return strlen(buf); } static struct kobj_attribute scst_version_attr = __ATTR(version, S_IRUGO, scst_version_show, NULL); static ssize_t scst_last_sysfs_mgmt_res_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int res; TRACE_ENTRY(); spin_lock(&sysfs_work_lock); TRACE_DBG("active_sysfs_works %d", active_sysfs_works); if (active_sysfs_works > 0) res = -EAGAIN; else res = sprintf(buf, "%d\n", last_sysfs_work_res); spin_unlock(&sysfs_work_lock); TRACE_EXIT_RES(res); return res; } static struct kobj_attribute scst_last_sysfs_mgmt_res_attr = __ATTR(last_sysfs_mgmt_res, S_IRUGO, 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, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) &scst_poll_us_attr.attr, #endif &scst_suspend_attr.attr, #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) &scst_main_trace_level_attr.attr, #endif &scst_force_global_sgv_pool_attr.attr, &scst_trace_cmds_attr.attr, &scst_trace_mcmds_attr.attr, &scst_version_attr.attr, &scst_last_sysfs_mgmt_res_attr.attr, NULL, }; static void scst_sysfs_root_release(struct kobject *kobj) { complete_all(&scst_sysfs_root_release_completion); } static struct kobj_type scst_sysfs_root_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_sysfs_root_release, .default_attrs = scst_sysfs_root_default_attrs, }; /* ** Sysfs user info **/ static DEFINE_MUTEX(scst_sysfs_user_info_mutex); /* All protected by scst_sysfs_user_info_mutex */ static LIST_HEAD(scst_sysfs_user_info_list); static uint32_t scst_sysfs_info_cur_cookie; /* scst_sysfs_user_info_mutex supposed to be held */ static struct scst_sysfs_user_info *scst_sysfs_user_find_info(uint32_t cookie) { struct scst_sysfs_user_info *info, *res = NULL; TRACE_ENTRY(); list_for_each_entry(info, &scst_sysfs_user_info_list, info_list_entry) { if (info->info_cookie == cookie) { res = info; break; } } TRACE_EXIT_HRES(res); return res; } /* * scst_sysfs_user_get_info() - get user_info * * Finds the user_info based on cookie and mark it as received the reply by * setting for it flag info_being_executed. * * Returns found entry or NULL. */ struct scst_sysfs_user_info *scst_sysfs_user_get_info(uint32_t cookie) { struct scst_sysfs_user_info *res = NULL; TRACE_ENTRY(); mutex_lock(&scst_sysfs_user_info_mutex); res = scst_sysfs_user_find_info(cookie); if (res != NULL) { if (!res->info_being_executed) res->info_being_executed = 1; } mutex_unlock(&scst_sysfs_user_info_mutex); TRACE_EXIT_HRES(res); return res; } EXPORT_SYMBOL_GPL(scst_sysfs_user_get_info); /* ** Helper functionality to help target drivers and dev handlers support ** sending events to user space and wait for their completion in a safe ** manner. See samples how to use it in iscsi-scst or scst_user. **/ /* * scst_sysfs_user_add_info() - create and add user_info in the global list * * Creates an info structure and adds it in the info_list. * Returns 0 and out_info on success, error code otherwise. */ int scst_sysfs_user_add_info(struct scst_sysfs_user_info **out_info) { int res = 0; struct scst_sysfs_user_info *info; TRACE_ENTRY(); info = kzalloc(sizeof(*info), GFP_KERNEL); if (info == NULL) { PRINT_ERROR("Unable to allocate sysfs user info (size %zd)", sizeof(*info)); res = -ENOMEM; goto out; } mutex_lock(&scst_sysfs_user_info_mutex); while ((info->info_cookie == 0) || (scst_sysfs_user_find_info(info->info_cookie) != NULL)) info->info_cookie = scst_sysfs_info_cur_cookie++; init_completion(&info->info_completion); list_add_tail(&info->info_list_entry, &scst_sysfs_user_info_list); info->info_in_list = 1; *out_info = info; mutex_unlock(&scst_sysfs_user_info_mutex); out: TRACE_EXIT_RES(res); return res; } EXPORT_SYMBOL_GPL(scst_sysfs_user_add_info); /* * scst_sysfs_user_del_info - delete and frees user_info */ void scst_sysfs_user_del_info(struct scst_sysfs_user_info *info) { TRACE_ENTRY(); mutex_lock(&scst_sysfs_user_info_mutex); if (info->info_in_list) list_del(&info->info_list_entry); mutex_unlock(&scst_sysfs_user_info_mutex); kfree(info); TRACE_EXIT(); return; } EXPORT_SYMBOL_GPL(scst_sysfs_user_del_info); /* * Returns true if the reply received and being processed by another part of * the kernel, false otherwise. Also removes the user_info from the list to * fix for the user space that it missed the timeout. */ static bool scst_sysfs_user_info_executing(struct scst_sysfs_user_info *info) { bool res; TRACE_ENTRY(); mutex_lock(&scst_sysfs_user_info_mutex); res = info->info_being_executed; if (info->info_in_list) { list_del(&info->info_list_entry); info->info_in_list = 0; } mutex_unlock(&scst_sysfs_user_info_mutex); TRACE_EXIT_RES(res); return res; } /* * scst_wait_info_completion() - wait for a user space event's completion * * Waits for the info request been completed by user space at most timeout * jiffies. If the reply received before timeout and being processed by * another part of the kernel, i.e. scst_sysfs_user_info_executing() * returned true, waits for it to complete indefinitely. * * Returns status of the request completion. */ int scst_wait_info_completion(struct scst_sysfs_user_info *info, unsigned long timeout) { int res, rc; TRACE_ENTRY(); TRACE_DBG("Waiting for info %p completion", info); while (1) { rc = wait_for_completion_interruptible_timeout( &info->info_completion, timeout); if (rc > 0) { TRACE_DBG("Waiting for info %p finished with %d", info, rc); break; } else if (rc == 0) { if (!scst_sysfs_user_info_executing(info)) { PRINT_ERROR("Timeout waiting for user " "space event %p", info); res = -EBUSY; goto out; } else { /* Req is being executed in the kernel */ TRACE_DBG("Keep waiting for info %p completion", info); wait_for_completion(&info->info_completion); break; } } else if (rc != -ERESTARTSYS) { res = rc; PRINT_ERROR("wait_for_completion() failed: %d", res); goto out; } else { TRACE_DBG("Waiting for info %p finished with %d, " "retrying", info, rc); } } TRACE_DBG("info %p, status %d", info, info->info_status); res = info->info_status; out: TRACE_EXIT_RES(res); return res; } EXPORT_SYMBOL_GPL(scst_wait_info_completion); int __init scst_sysfs_init(void) { int res = 0; TRACE_ENTRY(); sysfs_work_thread = kthread_run(sysfs_work_thread_fn, NULL, "scst_uid"); if (IS_ERR(sysfs_work_thread)) { res = PTR_ERR(sysfs_work_thread); PRINT_ERROR("kthread_run() for user interface thread " "failed: %d", res); sysfs_work_thread = NULL; goto out; } res = kobject_init_and_add(&scst_sysfs_root_kobj, &scst_sysfs_root_ktype, kernel_kobj, "%s", "scst_tgt"); if (res != 0) goto sysfs_root_add_error; scst_targets_kobj = kobject_create_and_add("targets", &scst_sysfs_root_kobj); if (scst_targets_kobj == NULL) goto targets_kobj_error; scst_devices_kobj = kobject_create_and_add("devices", &scst_sysfs_root_kobj); if (scst_devices_kobj == NULL) goto devices_kobj_error; res = scst_add_sgv_kobj(&scst_sysfs_root_kobj, "sgv"); if (res != 0) goto sgv_kobj_error; scst_handlers_kobj = kobject_create_and_add("handlers", &scst_sysfs_root_kobj); if (scst_handlers_kobj == NULL) goto handlers_kobj_error; scst_device_groups_kobj = kobject_create_and_add("device_groups", &scst_sysfs_root_kobj); if (scst_device_groups_kobj == NULL) goto device_groups_kobj_error; if (sysfs_create_files(scst_device_groups_kobj, scst_device_groups_attrs)) goto device_groups_attrs_error; out: TRACE_EXIT_RES(res); return res; device_groups_attrs_error: kobject_del(scst_device_groups_kobj); kobject_put(scst_device_groups_kobj); device_groups_kobj_error: kobject_del(scst_handlers_kobj); kobject_put(scst_handlers_kobj); handlers_kobj_error: scst_del_put_sgv_kobj(); sgv_kobj_error: kobject_del(scst_devices_kobj); kobject_put(scst_devices_kobj); devices_kobj_error: kobject_del(scst_targets_kobj); kobject_put(scst_targets_kobj); targets_kobj_error: kobject_del(&scst_sysfs_root_kobj); sysfs_root_add_error: kobject_put(&scst_sysfs_root_kobj); kthread_stop(sysfs_work_thread); if (res == 0) res = -EINVAL; goto out; } void scst_sysfs_cleanup(void) { TRACE_ENTRY(); PRINT_INFO("%s", "Exiting SCST sysfs hierarchy..."); scst_del_put_sgv_kobj(); kobject_del(scst_devices_kobj); kobject_put(scst_devices_kobj); kobject_del(scst_targets_kobj); kobject_put(scst_targets_kobj); kobject_del(scst_handlers_kobj); kobject_put(scst_handlers_kobj); sysfs_remove_files(scst_device_groups_kobj, scst_device_groups_attrs); kobject_del(scst_device_groups_kobj); kobject_put(scst_device_groups_kobj); kobject_del(&scst_sysfs_root_kobj); kobject_put(&scst_sysfs_root_kobj); wait_for_completion(&scst_sysfs_root_release_completion); /* * There is a race, when in the release() schedule happens just after * calling complete(), so if we exit and unload scst module immediately, * there will be oops there. So let's give it a chance to quit * gracefully. Unfortunately, current kobjects implementation * doesn't allow better ways to handle it. */ msleep(3000); if (sysfs_work_thread) kthread_stop(sysfs_work_thread); PRINT_INFO("%s", "Exiting SCST sysfs hierarchy done"); TRACE_EXIT(); return; }