/* * Copyright (C) 2008 - 2010 Richard Sharpe * Copyright (C) 1992 Eric Youngdale * Copyright (C) 2008 - 2018 Vladislav Bolkhovitin * * Simulate a host adapter and an SCST target adapter back to back * * Based on the scsi_debug.c driver originally by Eric Youngdale and * others, including D Gilbert et al * */ #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) #include #else #include static inline u32 blk_mq_unique_tag(struct request *rq) { return rq->tag; } #endif #include #include #include #include #define LOG_PREFIX "scst_local" /* SCST includes ... */ #ifdef INSIDE_KERNEL_TREE #include #include #else #include #include #endif #ifndef INSIDE_KERNEL_TREE #if defined(CONFIG_HIGHMEM4G) || defined(CONFIG_HIGHMEM64G) #warning HIGHMEM kernel configurations are not supported by this module, \ because nowadays it is not worth the effort. Consider changing \ VMSPLIT option or use a 64-bit configuration instead. See SCST core \ README file for details. #endif #endif #ifdef CONFIG_SCST_DEBUG #define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_PID | \ TRACE_LINE | TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_DEBUG | \ TRACE_MINOR | TRACE_SPECIAL) #else # ifdef CONFIG_SCST_TRACING #define SCST_LOCAL_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_PID | \ TRACE_SPECIAL) # endif #endif #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) #define trace_flag scst_local_trace_flag static unsigned long scst_local_trace_flag = SCST_LOCAL_DEFAULT_LOG_FLAGS; #endif #define SCST_LOCAL_VERSION "3.7.0-pre" static const char *scst_local_version_date = "20110901"; /* Some statistics */ static atomic_t num_aborts = ATOMIC_INIT(0); static atomic_t num_dev_resets = ATOMIC_INIT(0); static atomic_t num_target_resets = ATOMIC_INIT(0); static bool scst_local_add_default_tgt = true; module_param_named(add_default_tgt, scst_local_add_default_tgt, bool, S_IRUGO); MODULE_PARM_DESC(add_default_tgt, "add (default) or not on start default " "target scst_local_tgt with default session scst_local_host"); static struct workqueue_struct *aen_workqueue; struct scst_aen_work_item { struct list_head work_list_entry; struct scst_aen *aen; }; struct scst_local_tgt { struct scst_tgt *scst_tgt; struct list_head sessions_list; /* protected by scst_local_mutex */ struct list_head tgts_list_entry; /* SCSI version descriptors */ uint16_t scsi_transport_version; uint16_t phys_transport_version; }; struct scst_local_sess { struct scst_session *scst_sess; unsigned int unregistering:1; struct device dev; struct Scsi_Host *shost; struct scst_local_tgt *tgt; int number; struct mutex tr_id_mutex; uint8_t *transport_id; int transport_id_len; struct work_struct aen_work; spinlock_t aen_lock; struct list_head aen_work_list; /* protected by aen_lock */ struct work_struct remove_work; struct list_head sessions_list_entry; }; #define to_scst_lcl_sess(d) \ container_of(d, struct scst_local_sess, dev) static int __scst_local_add_adapter(struct scst_local_tgt *tgt, const char *initiator_name, bool locked); static int scst_local_add_adapter(struct scst_local_tgt *tgt, const char *initiator_name); static void scst_local_close_session_impl(struct scst_local_sess *sess, bool async); static void scst_local_remove_adapter(struct scst_local_sess *sess); static int scst_local_add_target(const char *target_name, struct scst_local_tgt **out_tgt); static void __scst_local_remove_target(struct scst_local_tgt *tgt); static void scst_local_remove_target(struct scst_local_tgt *tgt); static atomic_t scst_local_sess_num = ATOMIC_INIT(0); static LIST_HEAD(scst_local_tgts_list); static DEFINE_MUTEX(scst_local_mutex); static DECLARE_RWSEM(scst_local_exit_rwsem); MODULE_AUTHOR("Richard Sharpe, Vladislav Bolkhovitin + ideas from SCSI_DEBUG"); MODULE_DESCRIPTION("SCSI+SCST local adapter driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(SCST_LOCAL_VERSION); MODULE_IMPORT_NS(SCST); static int scst_local_get_sas_transport_id(struct scst_local_sess *sess, uint8_t **transport_id, int *len) { int res = 0; int tr_id_size = 0; uint8_t *tr_id = NULL; TRACE_ENTRY(); tr_id_size = 24; /* A SAS TransportID */ tr_id = kzalloc(tr_id_size, GFP_KERNEL); if (tr_id == NULL) { PRINT_ERROR("Allocation of TransportID (size %d) failed", tr_id_size); res = -ENOMEM; goto out; } tr_id[0] = 0x00 | SCSI_TRANSPORTID_PROTOCOLID_SAS; /* * Assemble a valid SAS address = 0x5OOUUIIR12345678 ... Does SCST * have one? */ tr_id[4] = 0x5F; tr_id[5] = 0xEE; tr_id[6] = 0xDE; tr_id[7] = 0x40 | ((sess->number >> 4) & 0x0F); tr_id[8] = 0x0F | ((sess->number & 0x0F) << 4); tr_id[9] = 0xAD; tr_id[10] = 0xE0; tr_id[11] = 0x50; *transport_id = tr_id; if (len) *len = tr_id_size; TRACE_DBG("Created tid '%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'", tr_id[4], tr_id[5], tr_id[6], tr_id[7], tr_id[8], tr_id[9], tr_id[10], tr_id[11]); out: TRACE_EXIT_RES(res); return res; } static int scst_local_get_initiator_port_transport_id( struct scst_tgt *tgt, struct scst_session *scst_sess, uint8_t **transport_id) { int res = 0; struct scst_local_sess *sess; TRACE_ENTRY(); if (scst_sess == NULL) { res = SCSI_TRANSPORTID_PROTOCOLID_SAS; goto out; } sess = scst_sess_get_tgt_priv(scst_sess); mutex_lock(&sess->tr_id_mutex); if (sess->transport_id == NULL) res = scst_local_get_sas_transport_id(sess, transport_id, NULL); else { *transport_id = kmemdup(sess->transport_id, sess->transport_id_len, GFP_KERNEL); if (*transport_id == NULL) { PRINT_ERROR("Allocation of TransportID (size %d) failed", sess->transport_id_len); res = -ENOMEM; } } mutex_unlock(&sess->tr_id_mutex); out: TRACE_EXIT_RES(res); return res; } /* ** Tgtt attributes **/ static ssize_t scst_local_version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { sprintf(buf, "%s/%s\n", SCST_LOCAL_VERSION, scst_local_version_date); #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 TRACE_EXIT(); return strlen(buf); } static struct kobj_attribute scst_local_version_attr = __ATTR(version, S_IRUGO, scst_local_version_show, NULL); static ssize_t scst_local_stats_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "Aborts: %d, Device Resets: %d, Target Resets: %d\n", atomic_read(&num_aborts), atomic_read(&num_dev_resets), atomic_read(&num_target_resets)); } static struct kobj_attribute scst_local_stats_attr = __ATTR(stats, S_IRUGO, scst_local_stats_show, NULL); static const struct attribute *scst_local_tgtt_attrs[] = { &scst_local_version_attr.attr, &scst_local_stats_attr.attr, NULL, }; /* ** Tgt attributes **/ static ssize_t scst_local_scsi_transport_version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt *scst_tgt; struct scst_local_tgt *tgt; ssize_t res = -ENOENT; if (down_read_trylock(&scst_local_exit_rwsem) == 0) goto out; res = -E_TGT_PRIV_NOT_YET_SET; scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj); tgt = scst_tgt_get_tgt_priv(scst_tgt); if (!tgt) goto out_up; if (tgt->scsi_transport_version != 0) res = sprintf(buf, "0x%x\n%s", tgt->scsi_transport_version, SCST_SYSFS_KEY_MARK "\n"); else res = sprintf(buf, "0x%x\n", 0x0BE0); /* SAS */ out_up: up_read(&scst_local_exit_rwsem); out: return res; } static ssize_t scst_local_scsi_transport_version_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buffer, size_t size) { ssize_t res = -ENOENT; struct scst_tgt *scst_tgt; struct scst_local_tgt *tgt; unsigned long val; if (down_read_trylock(&scst_local_exit_rwsem) == 0) goto out; res = -E_TGT_PRIV_NOT_YET_SET; scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj); tgt = scst_tgt_get_tgt_priv(scst_tgt); if (!tgt) goto out_up; res = kstrtoul(buffer, 0, &val); if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out_up; } tgt->scsi_transport_version = val; res = size; out_up: up_read(&scst_local_exit_rwsem); out: return res; } static struct kobj_attribute scst_local_scsi_transport_version_attr = __ATTR(scsi_transport_version, S_IRUGO | S_IWUSR, scst_local_scsi_transport_version_show, scst_local_scsi_transport_version_store); static ssize_t scst_local_phys_transport_version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_tgt *scst_tgt; struct scst_local_tgt *tgt; ssize_t res = -ENOENT; if (down_read_trylock(&scst_local_exit_rwsem) == 0) goto out; res = -E_TGT_PRIV_NOT_YET_SET; scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj); tgt = scst_tgt_get_tgt_priv(scst_tgt); if (!tgt) goto out_up; res = sprintf(buf, "0x%x\n%s", tgt->phys_transport_version, (tgt->phys_transport_version != 0) ? SCST_SYSFS_KEY_MARK "\n" : ""); out_up: up_read(&scst_local_exit_rwsem); out: return res; } static ssize_t scst_local_phys_transport_version_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buffer, size_t size) { ssize_t res = -ENOENT; struct scst_tgt *scst_tgt; struct scst_local_tgt *tgt; unsigned long val; if (down_read_trylock(&scst_local_exit_rwsem) == 0) goto out; res = -E_TGT_PRIV_NOT_YET_SET; scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj); tgt = scst_tgt_get_tgt_priv(scst_tgt); if (!tgt) goto out_up; res = kstrtoul(buffer, 0, &val); if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out_up; } tgt->phys_transport_version = val; res = size; out_up: up_read(&scst_local_exit_rwsem); out: return res; } static struct kobj_attribute scst_local_phys_transport_version_attr = __ATTR(phys_transport_version, S_IRUGO | S_IWUSR, scst_local_phys_transport_version_show, scst_local_phys_transport_version_store); static const struct attribute *scst_local_tgt_attrs[] = { &scst_local_scsi_transport_version_attr.attr, &scst_local_phys_transport_version_attr.attr, NULL, }; /* ** Session attributes **/ static ssize_t host_no_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct scst_session *scst_sess = container_of(kobj, struct scst_session, sess_kobj); struct scst_local_sess *sess = scst_sess_get_tgt_priv(scst_sess); struct Scsi_Host *host = sess->shost; return host ? snprintf(buf, PAGE_SIZE, "%u\n", host->host_no) : -EINVAL; } static struct kobj_attribute scst_local_host_no_attr = __ATTR_RO(host_no); static ssize_t scst_local_transport_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { ssize_t res; struct scst_session *scst_sess; struct scst_local_sess *sess; uint8_t *tr_id; int tr_id_len, i; if (down_read_trylock(&scst_local_exit_rwsem) == 0) return -ENOENT; scst_sess = container_of(kobj, struct scst_session, sess_kobj); sess = scst_sess_get_tgt_priv(scst_sess); mutex_lock(&sess->tr_id_mutex); if (sess->transport_id != NULL) { tr_id = sess->transport_id; tr_id_len = sess->transport_id_len; } else { res = scst_local_get_sas_transport_id(sess, &tr_id, &tr_id_len); if (res != 0) goto out_unlock; } res = 0; for (i = 0; i < tr_id_len; i++) res += sprintf(&buf[res], "%c", tr_id[i]); if (sess->transport_id == NULL) kfree(tr_id); out_unlock: mutex_unlock(&sess->tr_id_mutex); up_read(&scst_local_exit_rwsem); return res; } static ssize_t scst_local_transport_id_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buffer, size_t size) { ssize_t res; struct scst_session *scst_sess; struct scst_local_sess *sess; if (down_read_trylock(&scst_local_exit_rwsem) == 0) return -ENOENT; scst_sess = container_of(kobj, struct scst_session, sess_kobj); sess = scst_sess_get_tgt_priv(scst_sess); mutex_lock(&sess->tr_id_mutex); kfree(sess->transport_id); sess->transport_id = NULL; sess->transport_id_len = 0; if (size == 0) goto out_res; sess->transport_id = kzalloc(size, GFP_KERNEL); if (sess->transport_id == NULL) { PRINT_ERROR("Allocation of transport_id (size %zd) failed", size); res = -ENOMEM; goto out_unlock; } sess->transport_id_len = size; memcpy(sess->transport_id, buffer, sess->transport_id_len); out_res: res = size; out_unlock: mutex_unlock(&sess->tr_id_mutex); up_read(&scst_local_exit_rwsem); return res; } static struct kobj_attribute scst_local_transport_id_attr = __ATTR(transport_id, S_IRUGO | S_IWUSR, scst_local_transport_id_show, scst_local_transport_id_store); static const struct attribute *scst_local_sess_attrs[] = { &scst_local_host_no_attr.attr, &scst_local_transport_id_attr.attr, NULL, }; static ssize_t scst_local_sysfs_add_target(const char *target_name, char *params) { int res; struct scst_local_tgt *tgt; char *param, *p; TRACE_ENTRY(); if (down_read_trylock(&scst_local_exit_rwsem) == 0) return -ENOENT; res = scst_local_add_target(target_name, &tgt); if (res != 0) goto out_up; while (1) { param = scst_get_next_token_str(¶ms); if (param == NULL) break; p = scst_get_next_lexem(¶m); if (*p == '\0') break; if (strcasecmp("session_name", p) != 0) { PRINT_ERROR("Unknown parameter %s", p); res = -EINVAL; goto out_remove; } p = scst_get_next_lexem(¶m); if (*p == '\0') { PRINT_ERROR("Wrong session name %s", p); res = -EINVAL; goto out_remove; } res = scst_local_add_adapter(tgt, p); if (res != 0) goto out_remove; } out_up: up_read(&scst_local_exit_rwsem); TRACE_EXIT_RES(res); return res; out_remove: scst_local_remove_target(tgt); goto out_up; } static ssize_t scst_local_sysfs_del_target(const char *target_name) { int res; struct scst_local_tgt *tgt; bool deleted = false; TRACE_ENTRY(); if (down_read_trylock(&scst_local_exit_rwsem) == 0) return -ENOENT; mutex_lock(&scst_local_mutex); list_for_each_entry(tgt, &scst_local_tgts_list, tgts_list_entry) { if (strcmp(target_name, tgt->scst_tgt->tgt_name) == 0) { __scst_local_remove_target(tgt); deleted = true; break; } } mutex_unlock(&scst_local_mutex); if (!deleted) { PRINT_ERROR("Target %s not found", target_name); res = -ENOENT; goto out_up; } res = 0; out_up: up_read(&scst_local_exit_rwsem); TRACE_EXIT_RES(res); return res; } static ssize_t scst_local_sysfs_mgmt_cmd(char *buf) { ssize_t res = 0; char *command, *target_name, *session_name; struct scst_local_tgt *t, *tgt; TRACE_ENTRY(); if (down_read_trylock(&scst_local_exit_rwsem) == 0) return -ENOENT; command = scst_get_next_lexem(&buf); target_name = scst_get_next_lexem(&buf); if (*target_name == '\0') { PRINT_ERROR("%s", "Target name required"); res = -EINVAL; goto out_up; } mutex_lock(&scst_local_mutex); tgt = NULL; list_for_each_entry(t, &scst_local_tgts_list, tgts_list_entry) { if (strcmp(t->scst_tgt->tgt_name, target_name) == 0) { tgt = t; break; } } if (tgt == NULL) { PRINT_ERROR("Target %s not found", target_name); res = -EINVAL; goto out_unlock; } session_name = scst_get_next_lexem(&buf); if (*session_name == '\0') { PRINT_ERROR("%s", "Session name required"); res = -EINVAL; goto out_unlock; } if (strcasecmp("add_session", command) == 0) { res = __scst_local_add_adapter(tgt, session_name, true); } else if (strcasecmp("del_session", command) == 0) { struct scst_local_sess *s, *sess = NULL; list_for_each_entry(s, &tgt->sessions_list, sessions_list_entry) { if (strcmp(s->scst_sess->initiator_name, session_name) == 0) { sess = s; break; } } if (sess == NULL) { PRINT_ERROR("Session %s not found (target %s)", session_name, target_name); res = -EINVAL; goto out_unlock; } scst_local_close_session_impl(sess, false); } out_unlock: mutex_unlock(&scst_local_mutex); out_up: up_read(&scst_local_exit_rwsem); TRACE_EXIT_RES(res); return res; } static int scst_local_abort(struct scsi_cmnd *scmd) { struct scst_local_sess *sess; int ret; DECLARE_COMPLETION_ONSTACK(dev_reset_completion); TRACE_ENTRY(); sess = to_scst_lcl_sess(scsi_get_device(scmd->device->host)); ret = scst_rx_mgmt_fn_tag(sess->scst_sess, SCST_ABORT_TASK, blk_mq_unique_tag(scsi_cmd_to_rq(scmd)), false, &dev_reset_completion); /* Now wait for the completion ... */ wait_for_completion_interruptible(&dev_reset_completion); atomic_inc(&num_aborts); if (ret == 0) ret = SUCCESS; TRACE_EXIT_RES(ret); return ret; } static int scst_local_device_reset(struct scsi_cmnd *scmd) { struct scst_local_sess *sess; struct scsi_lun lun; int ret; DECLARE_COMPLETION_ONSTACK(dev_reset_completion); TRACE_ENTRY(); sess = to_scst_lcl_sess(scsi_get_device(scmd->device->host)); int_to_scsilun(scmd->device->lun, &lun); ret = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_LUN_RESET, lun.scsi_lun, sizeof(lun), false, &dev_reset_completion); /* Now wait for the completion ... */ wait_for_completion_interruptible(&dev_reset_completion); atomic_inc(&num_dev_resets); if (ret == 0) ret = SUCCESS; TRACE_EXIT_RES(ret); return ret; } static int scst_local_target_reset(struct scsi_cmnd *scmd) { struct scst_local_sess *sess; struct scsi_lun lun; int ret; DECLARE_COMPLETION_ONSTACK(dev_reset_completion); TRACE_ENTRY(); sess = to_scst_lcl_sess(scsi_get_device(scmd->device->host)); int_to_scsilun(scmd->device->lun, &lun); ret = scst_rx_mgmt_fn_lun(sess->scst_sess, SCST_TARGET_RESET, lun.scsi_lun, sizeof(lun), false, &dev_reset_completion); /* Now wait for the completion ... */ wait_for_completion_interruptible(&dev_reset_completion); atomic_inc(&num_target_resets); if (ret == 0) ret = SUCCESS; TRACE_EXIT_RES(ret); return ret; } static void scst_local_copy_sense(struct scsi_cmnd *cmnd, struct scst_cmd *scst_cmnd) { int scst_cmnd_sense_len = scst_cmd_get_sense_buffer_len(scst_cmnd); TRACE_ENTRY(); scst_cmnd_sense_len = min(scst_cmnd_sense_len, SCSI_SENSE_BUFFERSIZE); memcpy(cmnd->sense_buffer, scst_cmd_get_sense_buffer(scst_cmnd), scst_cmnd_sense_len); TRACE_BUFFER("Sense set", cmnd->sense_buffer, scst_cmnd_sense_len); TRACE_EXIT(); return; } /* * Utility function to handle processing of done and allow * easy insertion of error injection if desired */ static int scst_local_send_resp(struct scsi_cmnd *cmnd, struct scst_cmd *scst_cmnd, void (*done)(struct scsi_cmnd *), int scsi_result) { int ret = 0; TRACE_ENTRY(); if (scst_cmnd) { /* The buffer isn't ours, so let's be safe and restore it */ scst_check_restore_sg_buff(scst_cmnd); /* Simulate autosense by this driver */ if (unlikely(scst_sense_valid(scst_cmnd->sense))) scst_local_copy_sense(cmnd, scst_cmnd); } cmnd->result = scsi_result; done(cmnd); TRACE_EXIT_RES(ret); return ret; } /* * This does the heavy lifting ... we pass all the commands on to the * target driver and have it do its magic ... */ static int scst_local_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scmd) { struct scst_local_sess *sess; struct scatterlist *sgl = NULL; int sgl_count = 0; struct scsi_lun lun; struct scst_cmd *scst_cmd = NULL; scst_data_direction dir; TRACE_ENTRY(); TRACE_DBG("lun %lld, cmd: 0x%02X", (u64)scmd->device->lun, scmd->cmnd[0]); sess = to_scst_lcl_sess(scsi_get_device(scmd->device->host)); if (sess->unregistering) { scmd->result = DID_BAD_TARGET << 16; scsi_done(scmd); return 0; } scsi_set_resid(scmd, 0); /* * Tell the target that we have a command ... but first we need * to get the LUN into a format that SCST understand * * NOTE! We need to call it with atomic parameter true to not * get into mem alloc deadlock when mounting file systems over * our devices. */ int_to_scsilun(scmd->device->lun, &lun); scst_cmd = scst_rx_cmd(sess->scst_sess, lun.scsi_lun, sizeof(lun), scmd->cmnd, scmd->cmd_len, true); if (!scst_cmd) { PRINT_ERROR("%s", "scst_rx_cmd() failed"); return SCSI_MLQUEUE_HOST_BUSY; } scst_cmd_set_tag(scst_cmd, blk_mq_unique_tag(scsi_cmd_to_rq(scmd))); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) if (scmd->device->tagged_supported && scmd->device->simple_tags) scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE); else scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED); #else switch (scsi_get_tag_type(scmd->device)) { case MSG_SIMPLE_TAG: scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_SIMPLE); break; case MSG_HEAD_TAG: scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE); break; case MSG_ORDERED_TAG: scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_ORDERED); break; case SCSI_NO_TAG: default: scst_cmd_set_queue_type(scst_cmd, SCST_CMD_QUEUE_UNTAGGED); break; } #endif sgl = scsi_sglist(scmd); sgl_count = scsi_sg_count(scmd); if (scsi_bidi_cmnd(scmd)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0) && \ (!defined(RHEL_RELEASE_CODE) || \ RHEL_RELEASE_CODE -0 < RHEL_RELEASE_VERSION(8, 3)) /* Some of these symbols are only defined after 2.6.24 */ dir = SCST_DATA_BIDI; scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(scmd)); scst_cmd_set_expected_out_transfer_len(scst_cmd, scsi_in(scmd)->length); scst_cmd_set_noio_mem_alloc(scst_cmd); scst_cmd_set_tgt_sg(scst_cmd, scsi_in(scmd)->table.sgl, scsi_in(scmd)->table.nents); scst_cmd_set_tgt_out_sg(scst_cmd, sgl, sgl_count); #endif } else if (scmd->sc_data_direction == DMA_TO_DEVICE) { dir = SCST_DATA_WRITE; scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(scmd)); scst_cmd_set_noio_mem_alloc(scst_cmd); scst_cmd_set_tgt_sg(scst_cmd, sgl, sgl_count); } else if (scmd->sc_data_direction == DMA_FROM_DEVICE) { dir = SCST_DATA_READ; scst_cmd_set_expected(scst_cmd, dir, scsi_bufflen(scmd)); scst_cmd_set_noio_mem_alloc(scst_cmd); scst_cmd_set_tgt_sg(scst_cmd, sgl, sgl_count); } else { dir = SCST_DATA_NONE; scst_cmd_set_expected(scst_cmd, dir, 0); } /* Save the correct thing below depending on version */ scst_cmd_set_tgt_priv(scst_cmd, scmd); scst_cmd_init_done(scst_cmd, SCST_CONTEXT_THREAD); TRACE_EXIT(); return 0; } static int scst_local_targ_pre_exec(struct scst_cmd *scst_cmd) { int res = SCST_PREPROCESS_STATUS_SUCCESS; TRACE_ENTRY(); if (scst_cmd_get_dh_data_buff_alloced(scst_cmd) && (scst_cmd_get_data_direction(scst_cmd) & SCST_DATA_WRITE)) scst_copy_sg(scst_cmd, SCST_SG_COPY_FROM_TARGET); TRACE_EXIT_RES(res); return res; } static int scst_local_get_max_queue_depth(struct scsi_device *sdev) { int res; struct scst_local_sess *sess; struct scsi_lun lun; TRACE_ENTRY(); sess = to_scst_lcl_sess(scsi_get_device(sdev->host)); int_to_scsilun(sdev->lun, &lun); res = scst_get_max_lun_commands(sess->scst_sess, scst_unpack_lun(lun.scsi_lun, sizeof(lun))); TRACE_EXIT_RES(res); return res; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) static int scst_local_change_queue_depth(struct scsi_device *sdev, int depth) { return scsi_change_queue_depth(sdev, depth); } #else static int scst_local_change_queue_depth(struct scsi_device *sdev, int depth, int reason) { int res, mqd; TRACE_ENTRY(); switch (reason) { case SCSI_QDEPTH_DEFAULT: mqd = scst_local_get_max_queue_depth(sdev); if (mqd < depth) { PRINT_INFO("Requested queue depth %d is too big " "(possible max %d (sdev %p)", depth, mqd, sdev); res = -EINVAL; goto out; } PRINT_INFO("Setting queue depth %d as default (sdev %p, " "current %d)", depth, sdev, sdev->queue_depth); scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth); break; case SCSI_QDEPTH_QFULL: TRACE(TRACE_FLOW_CONTROL, "QUEUE FULL on sdev %p, setting " "qdepth %d (cur %d)", sdev, depth, sdev->queue_depth); scsi_track_queue_full(sdev, depth); break; case SCSI_QDEPTH_RAMP_UP: TRACE(TRACE_FLOW_CONTROL, "Ramping up qdepth on sdev %p to %d " "(cur %d)", sdev, depth, sdev->queue_depth); scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), depth); break; default: res = -EOPNOTSUPP; goto out; } res = sdev->queue_depth; out: TRACE_EXIT_RES(res); return res; } #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) */ static int scst_local_slave_alloc(struct scsi_device *sdev) { struct request_queue *q = sdev->request_queue; #ifdef QUEUE_FLAG_BIDI #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0) && \ !defined(CONFIG_SUSE_KERNEL) #if !defined(RHEL_MAJOR) || RHEL_MAJOR -0 >= 6 queue_flag_set_unlocked(QUEUE_FLAG_BIDI, q); #endif #elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0) blk_queue_flag_set(QUEUE_FLAG_BIDI, q); #endif #endif /* * vdisk_blockio requires that data buffers have block_size alignment * and supports block sizes from 512 up to 4096. See also * https://github.com/sahlberg/libiscsi/issues/302. */ blk_queue_dma_alignment(q, 4095); return 0; } static int scst_local_slave_configure(struct scsi_device *sdev) { int mqd; TRACE_ENTRY(); mqd = scst_local_get_max_queue_depth(sdev); PRINT_INFO("Configuring queue depth %d on sdev %p (tagged supported %d)", mqd, sdev, sdev->tagged_supported); #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0) if (sdev->tagged_supported) scsi_activate_tcq(sdev, mqd); else scsi_deactivate_tcq(sdev, mqd); #endif TRACE_EXIT(); return 0; } /* Must be called under sess->aen_lock. Drops then reacquires it inside. */ static void scst_process_aens(struct scst_local_sess *sess, bool cleanup_only) __releases(&sess->aen_lock) __acquires(&sess->aen_lock) { struct scst_aen_work_item *work_item = NULL; struct Scsi_Host *shost; TRACE_ENTRY(); TRACE_DBG("Target work sess %p", sess); while (!list_empty(&sess->aen_work_list)) { work_item = list_first_entry(&sess->aen_work_list, struct scst_aen_work_item, work_list_entry); list_del(&work_item->work_list_entry); shost = sess->shost; if (shost && !scsi_host_get(shost)) shost = NULL; spin_unlock(&sess->aen_lock); if (cleanup_only) goto done; sBUG_ON(work_item->aen->event_fn != SCST_AEN_SCSI); /* Let's always rescan */ if (shost) scsi_scan_target(&shost->shost_gendev, 0, 0, SCAN_WILD_CARD, 1); done: scst_aen_done(work_item->aen); kfree(work_item); if (shost) scsi_host_put(shost); spin_lock(&sess->aen_lock); } TRACE_EXIT(); return; } static void scst_aen_work_fn(struct work_struct *work) { struct scst_local_sess *sess = container_of(work, struct scst_local_sess, aen_work); TRACE_ENTRY(); TRACE_MGMT_DBG("Target work %p)", sess); spin_lock(&sess->aen_lock); scst_process_aens(sess, false); spin_unlock(&sess->aen_lock); TRACE_EXIT(); return; } static int scst_local_report_aen(struct scst_aen *aen) { int res = 0; int event_fn = scst_aen_get_event_fn(aen); struct scst_local_sess *sess; struct scst_aen_work_item *work_item = NULL; TRACE_ENTRY(); sess = scst_sess_get_tgt_priv(scst_aen_get_sess(aen)); switch (event_fn) { case SCST_AEN_SCSI: /* * Allocate a work item and place it on the queue */ work_item = kzalloc(sizeof(*work_item), GFP_KERNEL); if (!work_item) { PRINT_ERROR("%s", "Unable to allocate work item " "to handle AEN!"); return -ENOMEM; } spin_lock(&sess->aen_lock); if (unlikely(sess->unregistering)) { spin_unlock(&sess->aen_lock); kfree(work_item); res = SCST_AEN_RES_NOT_SUPPORTED; goto out; } list_add_tail(&work_item->work_list_entry, &sess->aen_work_list); work_item->aen = aen; spin_unlock(&sess->aen_lock); /* * We might queue the same item over and over, but that is OK * It will be ignored by queue_work if it is already queued. */ queue_work(aen_workqueue, &sess->aen_work); break; default: TRACE_MGMT_DBG("Unsupported AEN %d", event_fn); res = SCST_AEN_RES_NOT_SUPPORTED; break; } out: TRACE_EXIT_RES(res); return res; } static int scst_local_targ_release(struct scst_tgt *tgt) { TRACE_ENTRY(); TRACE_EXIT(); return 0; } static void scst_remove_work_fn(struct work_struct *work) { struct scst_local_sess *sess = container_of(work, struct scst_local_sess, remove_work); scst_local_remove_adapter(sess); } static void scst_local_close_session_impl(struct scst_local_sess *sess, bool async) { bool unregistering; spin_lock(&sess->aen_lock); unregistering = sess->unregistering; sess->unregistering = 1; spin_unlock(&sess->aen_lock); if (!unregistering) { if (async) schedule_work(&sess->remove_work); else scst_local_remove_adapter(sess); } } /* * Perform removal from the context of another thread since the caller may * already hold an SCST mutex, since scst_local_remove_adapter() triggers a * call of device_unregister(), since device_unregister() invokes * device_del(), since device_del() locks the same mutex that is held while * invoking scst_add() from class_interface_register() and since scst_add() * also may lock an SCST mutex. */ static int scst_local_close_session(struct scst_session *scst_sess) { struct scst_local_sess *sess = scst_sess_get_tgt_priv(scst_sess); scst_local_close_session_impl(sess, true); return 0; } static int scst_local_targ_xmit_response(struct scst_cmd *scst_cmd) { struct scsi_cmnd *scmd = NULL; void (*done)(struct scsi_cmnd *); TRACE_ENTRY(); if (unlikely(scst_cmd_aborted_on_xmit(scst_cmd))) { scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_ABORTED); scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME); return SCST_TGT_RES_SUCCESS; } if (scst_cmd_get_dh_data_buff_alloced(scst_cmd) && (scst_cmd_get_data_direction(scst_cmd) & SCST_DATA_READ)) scst_copy_sg(scst_cmd, SCST_SG_COPY_TO_TARGET); scmd = scst_cmd_get_tgt_priv(scst_cmd); #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) done = scmd->scsi_done; #else done = scsi_done; #endif /* * This might have to change to use the two status flags */ if (scst_cmd_get_is_send_status(scst_cmd)) { int resid = 0, out_resid = 0; /* Calculate the residual ... */ if (likely(!scst_get_resid(scst_cmd, &resid, &out_resid))) { TRACE_DBG("No residuals for request %p", scmd); } else { if (out_resid != 0) PRINT_ERROR("Unable to return OUT residual %d " "(op %02x)", out_resid, scmd->cmnd[0]); } scsi_set_resid(scmd, resid); /* * It seems like there is no way to set out_resid ... */ (void)scst_local_send_resp(scmd, scst_cmd, done, scst_cmd_get_status(scst_cmd)); } /* Now tell SCST that the command is done ... */ scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_SAME); TRACE_EXIT(); return SCST_TGT_RES_SUCCESS; } static void scst_local_targ_task_mgmt_done(struct scst_mgmt_cmd *mgmt_cmd) { struct completion *compl; TRACE_ENTRY(); compl = scst_mgmt_cmd_get_tgt_priv(mgmt_cmd); if (compl) complete(compl); TRACE_EXIT(); return; } static uint16_t scst_local_get_scsi_transport_version(struct scst_tgt *scst_tgt) { struct scst_local_tgt *tgt = scst_tgt_get_tgt_priv(scst_tgt); /* * It's OK to not check tgt != NULL here, because new sessions * can't create before its' set. */ if (tgt->scsi_transport_version == 0) return 0x0BE0; /* SAS */ else return tgt->scsi_transport_version; } static uint16_t scst_local_get_phys_transport_version(struct scst_tgt *scst_tgt) { struct scst_local_tgt *tgt = scst_tgt_get_tgt_priv(scst_tgt); /* * It's OK to not check tgt != NULL here, because new sessions * can't create before its' set. */ return tgt->phys_transport_version; } static struct scst_tgt_template scst_local_targ_tmpl = { .name = "scst_local", .sg_tablesize = 0xffff, .xmit_response_atomic = 1, .multithreaded_init_done = 1, .enabled_attr_not_needed = 1, .tgtt_attrs = scst_local_tgtt_attrs, .tgt_attrs = scst_local_tgt_attrs, .sess_attrs = scst_local_sess_attrs, .add_target = scst_local_sysfs_add_target, .del_target = scst_local_sysfs_del_target, .mgmt_cmd = scst_local_sysfs_mgmt_cmd, .add_target_parameters = "session_name", .mgmt_cmd_help = " echo \"add_session target_name session_name\" >mgmt\n" " echo \"del_session target_name session_name\" >mgmt\n", .release = scst_local_targ_release, .close_session = scst_local_close_session, .pre_exec = scst_local_targ_pre_exec, .xmit_response = scst_local_targ_xmit_response, .task_mgmt_fn_done = scst_local_targ_task_mgmt_done, .report_aen = scst_local_report_aen, .get_initiator_port_transport_id = scst_local_get_initiator_port_transport_id, .get_scsi_transport_version = scst_local_get_scsi_transport_version, .get_phys_transport_version = scst_local_get_phys_transport_version, #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) .default_trace_flags = SCST_LOCAL_DEFAULT_LOG_FLAGS, .trace_flags = &trace_flag, #endif }; static struct scsi_host_template scst_lcl_ini_driver_template = { .name = SCST_LOCAL_NAME, .queuecommand = scst_local_queuecommand, .change_queue_depth = scst_local_change_queue_depth, .slave_alloc = scst_local_slave_alloc, .slave_configure = scst_local_slave_configure, .eh_abort_handler = scst_local_abort, .eh_device_reset_handler = scst_local_device_reset, .eh_target_reset_handler = scst_local_target_reset, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) && \ LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) .use_blk_tags = true, #endif .can_queue = 2048, /* * Set it low for the "Drop back to untagged" case in * scsi_track_queue_full(). We are adjusting it to a better * default in slave_configure() */ .cmd_per_lun = 3, .this_id = -1, .sg_tablesize = 0xFFFF, .max_sectors = 0xffff, /* Possible pass-through backend device may not support clustering */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 21, 0) .use_clustering = DISABLE_CLUSTERING, #else .dma_boundary = PAGE_SIZE - 1, .max_segment_size = PAGE_SIZE, #endif .skip_settle_delay = 1, .module = THIS_MODULE, }; /* * LLD Bus and functions */ static int scst_local_driver_probe(struct device *dev) { int ret; struct scst_local_sess *sess; struct Scsi_Host *hpnt; TRACE_ENTRY(); sess = to_scst_lcl_sess(dev); TRACE_DBG("sess %p", sess); hpnt = scsi_host_alloc(&scst_lcl_ini_driver_template, sizeof(*sess)); if (hpnt == NULL) { PRINT_ERROR("%s", "scsi_register() failed"); ret = -ENODEV; goto out; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) hpnt->nr_hw_queues = num_possible_cpus(); #endif sess->shost = hpnt; hpnt->max_id = 1; /* Don't want more than one id */ hpnt->max_lun = SCST_MAX_LUN + 1; /* * Because of a change in the size of this field at 2.6.26 * we use this check ... it allows us to work on earlier * kernels. If we don't, max_cmd_size gets set to 4 (and we get * a compiler warning) so a scan never occurs. */ hpnt->max_cmd_len = 260; ret = scsi_add_host(hpnt, &sess->dev); if (ret) { PRINT_ERROR("%s", "scsi_add_host() failed"); ret = -ENODEV; scsi_host_put(hpnt); goto out; } out: TRACE_EXIT_RES(ret); return ret; } #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) /* See also commit fc7a6209d571 ("bus: Make remove callback return void") */ #define DRIVER_REMOVE_RET int #else #define DRIVER_REMOVE_RET void #endif static DRIVER_REMOVE_RET scst_local_driver_remove(struct device *dev) { struct scst_local_sess *sess; struct Scsi_Host *shost = NULL; TRACE_ENTRY(); sess = to_scst_lcl_sess(dev); spin_lock(&sess->aen_lock); swap(sess->shost, shost); spin_unlock(&sess->aen_lock); scsi_remove_host(shost); scsi_host_put(shost); TRACE_EXIT(); return (DRIVER_REMOVE_RET)0; } static int scst_local_bus_match(struct device *dev, struct device_driver *dev_driver) { TRACE_ENTRY(); TRACE_EXIT(); return 1; } static struct bus_type scst_local_lld_bus = { .name = "scst_local_bus", .match = scst_local_bus_match, .probe = scst_local_driver_probe, .remove = scst_local_driver_remove, }; static struct device_driver scst_local_driver = { .name = SCST_LOCAL_NAME, .bus = &scst_local_lld_bus, }; static struct device *scst_local_root; static void scst_local_free_sess(struct scst_session *scst_sess) { struct scst_local_sess *sess = scst_sess_get_tgt_priv(scst_sess); kfree(sess); return; } static void scst_local_release_adapter(struct device *dev) { struct scst_local_sess *sess; TRACE_ENTRY(); sess = to_scst_lcl_sess(dev); /* * At this point the SCSI device is almost gone because the SCSI * Mid Layer calls us when the device is being unregistered. However, * SCST might have queued some AENs to us that have not yet been * processed when unregister_device started working. * * To prevent a race between us and AEN handling we must flush the * workqueue before we clean up the AEN list (calling scst_process_aens * with cleanup_only set to true) and then unregister the session. * * For kernels after 2.6.22 it is sufficient to cancel any outstanding * work. */ cancel_work_sync(&sess->aen_work); spin_lock(&sess->aen_lock); WARN_ON_ONCE(!sess->unregistering); scst_process_aens(sess, true); spin_unlock(&sess->aen_lock); scst_unregister_session(sess->scst_sess, false, scst_local_free_sess); TRACE_EXIT(); return; } static int __scst_local_add_adapter(struct scst_local_tgt *tgt, const char *initiator_name, bool locked) { int res; struct scst_local_sess *sess; TRACE_ENTRY(); /* It's read-mostly, so cache alignment isn't needed */ sess = kzalloc(sizeof(*sess), GFP_KERNEL); if (sess == NULL) { PRINT_ERROR("Unable to alloc scst_lcl_host (size %zu)", sizeof(*sess)); res = -ENOMEM; goto out; } sess->tgt = tgt; sess->number = atomic_inc_return(&scst_local_sess_num); mutex_init(&sess->tr_id_mutex); /* * Init this stuff we need for scheduling AEN work */ INIT_WORK(&sess->aen_work, scst_aen_work_fn); INIT_WORK(&sess->remove_work, scst_remove_work_fn); spin_lock_init(&sess->aen_lock); INIT_LIST_HEAD(&sess->aen_work_list); sess->scst_sess = scst_register_session(tgt->scst_tgt, 0, initiator_name, sess, NULL, NULL); if (sess->scst_sess == NULL) { PRINT_ERROR("%s", "scst_register_session failed"); res = -EFAULT; goto out_free; } sess->dev.bus = &scst_local_lld_bus; sess->dev.parent = scst_local_root; sess->dev.release = &scst_local_release_adapter; sess->dev.init_name = kobject_name(&sess->scst_sess->sess_kobj); res = device_register(&sess->dev); if (res != 0) goto unregister_session; res = sysfs_create_link(scst_sysfs_get_sess_kobj(sess->scst_sess), &sess->shost->shost_dev.kobj, "host"); if (res != 0) { PRINT_ERROR("Unable to create \"host\" link for target " "%s", scst_get_tgt_name(tgt->scst_tgt)); goto unregister_dev; } if (!locked) mutex_lock(&scst_local_mutex); list_add_tail(&sess->sessions_list_entry, &tgt->sessions_list); if (!locked) mutex_unlock(&scst_local_mutex); if (scst_initiator_has_luns(tgt->scst_tgt, initiator_name)) scsi_scan_target(&sess->shost->shost_gendev, 0, 0, SCAN_WILD_CARD, 1); out: TRACE_EXIT_RES(res); return res; unregister_dev: device_unregister(&sess->dev); goto out; unregister_session: scst_unregister_session(sess->scst_sess, true, NULL); out_free: kfree(sess); goto out; } static int scst_local_add_adapter(struct scst_local_tgt *tgt, const char *initiator_name) { return __scst_local_add_adapter(tgt, initiator_name, false); } /* Must be called under scst_local_mutex */ static void scst_local_remove_adapter(struct scst_local_sess *sess) { TRACE_ENTRY(); list_del(&sess->sessions_list_entry); device_unregister(&sess->dev); TRACE_EXIT(); return; } static int scst_local_add_target(const char *target_name, struct scst_local_tgt **out_tgt) { int res; struct scst_local_tgt *tgt; TRACE_ENTRY(); tgt = kzalloc(sizeof(*tgt), GFP_KERNEL); if (tgt == NULL) { PRINT_ERROR("Unable to alloc tgt (size %zu)", sizeof(*tgt)); res = -ENOMEM; goto out; } INIT_LIST_HEAD(&tgt->sessions_list); tgt->scst_tgt = scst_register_target(&scst_local_targ_tmpl, target_name); if (tgt->scst_tgt == NULL) { res = -EFAULT; goto out_free; } scst_tgt_set_tgt_priv(tgt->scst_tgt, tgt); mutex_lock(&scst_local_mutex); list_add_tail(&tgt->tgts_list_entry, &scst_local_tgts_list); mutex_unlock(&scst_local_mutex); res = 0; if (out_tgt != NULL) *out_tgt = tgt; out: TRACE_EXIT_RES(res); return res; out_free: kfree(tgt); goto out; } /* Must be called under scst_local_mutex */ static void __scst_local_remove_target(struct scst_local_tgt *tgt) { struct scst_local_sess *sess, *ts; TRACE_ENTRY(); list_for_each_entry_safe(sess, ts, &tgt->sessions_list, sessions_list_entry) { scst_local_close_session_impl(sess, false); } list_del(&tgt->tgts_list_entry); scst_unregister_target(tgt->scst_tgt); kfree(tgt); TRACE_EXIT(); return; } static void scst_local_remove_target(struct scst_local_tgt *tgt) { TRACE_ENTRY(); mutex_lock(&scst_local_mutex); __scst_local_remove_target(tgt); mutex_unlock(&scst_local_mutex); TRACE_EXIT(); return; } static int __init scst_local_init(void) { int ret; struct scst_local_tgt *tgt; TRACE_ENTRY(); #ifndef INSIDE_KERNEL_TREE #if defined(CONFIG_HIGHMEM4G) || defined(CONFIG_HIGHMEM64G) PRINT_ERROR("%s", "HIGHMEM kernel configurations are not supported. " "Consider changing VMSPLIT option or use a 64-bit " "configuration instead. See SCST core README file for " "details."); ret = -EINVAL; goto out; #endif #endif scst_local_root = root_device_register(SCST_LOCAL_NAME); if (IS_ERR(scst_local_root)) { ret = PTR_ERR(scst_local_root); goto out; } ret = bus_register(&scst_local_lld_bus); if (ret < 0) { PRINT_ERROR("bus_register() error: %d", ret); goto dev_unreg; } ret = driver_register(&scst_local_driver); if (ret < 0) { PRINT_ERROR("driver_register() error: %d", ret); goto bus_unreg; } ret = scst_register_target_template(&scst_local_targ_tmpl); if (ret != 0) { PRINT_ERROR("Unable to register target template: %d", ret); goto driver_unreg; } /* * We don't expect much work on this queue, so only create a * single thread workqueue rather than one on each core. */ aen_workqueue = create_singlethread_workqueue("scstlclaen"); if (!aen_workqueue) { PRINT_ERROR("%s", "Unable to create scst_local workqueue"); goto tgt_templ_unreg; } /* Don't add a default target unless we are told to do so. */ if (!scst_local_add_default_tgt) goto out; ret = scst_local_add_target("scst_local_tgt", &tgt); if (ret != 0) goto workqueue_unreg; ret = scst_local_add_adapter(tgt, "scst_local_host"); if (ret != 0) goto tgt_unreg; out: TRACE_EXIT_RES(ret); return ret; tgt_unreg: scst_local_remove_target(tgt); workqueue_unreg: destroy_workqueue(aen_workqueue); tgt_templ_unreg: scst_unregister_target_template(&scst_local_targ_tmpl); driver_unreg: driver_unregister(&scst_local_driver); bus_unreg: bus_unregister(&scst_local_lld_bus); dev_unreg: root_device_unregister(scst_local_root); goto out; } static void __exit scst_local_exit(void) { struct scst_local_tgt *tgt, *tt; TRACE_ENTRY(); down_write(&scst_local_exit_rwsem); mutex_lock(&scst_local_mutex); list_for_each_entry_safe(tgt, tt, &scst_local_tgts_list, tgts_list_entry) { __scst_local_remove_target(tgt); } mutex_unlock(&scst_local_mutex); destroy_workqueue(aen_workqueue); driver_unregister(&scst_local_driver); bus_unregister(&scst_local_lld_bus); root_device_unregister(scst_local_root); /* Now unregister the target template */ scst_unregister_target_template(&scst_local_targ_tmpl); /* To make lockdep happy */ up_write(&scst_local_exit_rwsem); TRACE_EXIT(); return; } device_initcall(scst_local_init); module_exit(scst_local_exit);