/* * scst_tg.c * * SCSI target group related code. * * Copyright (C) 2011 - 2018 Bart Van Assche . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * 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 #ifdef INSIDE_KERNEL_TREE #include #include #else #include "scst.h" #include "scst_event.h" #endif #include "scst_priv.h" #include "scst_pres.h" struct alua_state_and_name { enum scst_tg_state s; char *n; }; static const struct alua_state_and_name scst_tg_state_names[] = { { SCST_TG_STATE_OPTIMIZED, "active" }, { SCST_TG_STATE_NONOPTIMIZED, "nonoptimized" }, { SCST_TG_STATE_STANDBY, "standby" }, { SCST_TG_STATE_UNAVAILABLE, "unavailable" }, { SCST_TG_STATE_OFFLINE, "offline" }, { SCST_TG_STATE_TRANSITIONING, "transitioning" }, }; /* * Protects scst_dev_group_list and also dev_list and tg_list in struct * scst_dev_group. */ static DEFINE_MUTEX(scst_dg_mutex); static LIST_HEAD(scst_dev_group_list); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) || \ defined(RHEL_MAJOR) && RHEL_MAJOR -0 <= 5 static int alua_invariant_check; #else static bool alua_invariant_check; #endif module_param(alua_invariant_check, bool, 0644); MODULE_PARM_DESC(alua_invariant_check, "Enables a run-time ALUA state invariant check."); /* Global SCST ALUA lock/unlock functions (scst_dg_mutex) */ void scst_alua_lock(void) { mutex_lock(&scst_dg_mutex); } EXPORT_SYMBOL(scst_alua_lock); void scst_alua_unlock(void) { mutex_unlock(&scst_dg_mutex); } EXPORT_SYMBOL(scst_alua_unlock); void lockdep_assert_alua_lock_held(void) { lockdep_assert_held(&scst_dg_mutex); } EXPORT_SYMBOL(lockdep_assert_alua_lock_held); const char *scst_alua_state_name(enum scst_tg_state s) { int i; for (i = 0; i < ARRAY_SIZE(scst_tg_state_names); i++) if (scst_tg_state_names[i].s == s) return scst_tg_state_names[i].n; return NULL; } EXPORT_SYMBOL(scst_alua_state_name); enum scst_tg_state scst_alua_name_to_state(const char *n) { int i; for (i = 0; i < ARRAY_SIZE(scst_tg_state_names); i++) if (strcmp(scst_tg_state_names[i].n, n) == 0) return scst_tg_state_names[i].s; return SCST_TG_STATE_UNDEFINED; } /* Look up a device by name. */ static struct scst_device *__lookup_dev(const char *name) { struct scst_device *dev; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dev, &scst_dev_list, dev_list_entry) if (strcmp(dev->virt_name, name) == 0) return dev; return NULL; } /* Look up a target by name. */ static struct scst_tgt *__lookup_tgt(const char *name) { struct scst_tgt_template *t; struct scst_tgt *tgt; lockdep_assert_held(&scst_mutex); list_for_each_entry(t, &scst_template_list, scst_template_list_entry) list_for_each_entry(tgt, &t->tgt_list, tgt_list_entry) if (strcmp(tgt->tgt_name, name) == 0) return tgt; return NULL; } /* Look up a target by name in the given device group. */ static struct scst_tg_tgt *__lookup_dg_tgt(struct scst_dev_group *dg, const char *tgt_name) { struct scst_target_group *tg; struct scst_tg_tgt *tg_tgt; lockdep_assert_held(&scst_dg_mutex); BUG_ON(!dg); BUG_ON(!tgt_name); list_for_each_entry(tg, &dg->tg_list, entry) list_for_each_entry(tg_tgt, &tg->tgt_list, entry) if (strcmp(tg_tgt->name, tgt_name) == 0) return tg_tgt; return NULL; } /* Look up a target group by name in the given device group. */ static struct scst_target_group *__lookup_tg_by_name(struct scst_dev_group *dg, const char *name) { struct scst_target_group *tg; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(tg, &dg->tg_list, entry) if (strcmp(tg->name, name) == 0) return tg; return NULL; } /* Look up a target group by group ID. */ static struct scst_target_group *__lookup_tg_by_group_id(struct scst_dev_group *dg, uint16_t group_id) { struct scst_target_group *tg; lockdep_assert_held(&scst_mutex); list_for_each_entry(tg, &dg->tg_list, entry) if (tg->group_id == group_id) return tg; return NULL; } /* Look up a target group by target port. */ static struct scst_target_group *__lookup_tg_by_tgt(struct scst_dev_group *dg, const struct scst_tgt *tgt) { struct scst_target_group *tg; struct scst_tg_tgt *tg_tgt; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(tg, &dg->tg_list, entry) list_for_each_entry(tg_tgt, &tg->tgt_list, entry) if (tg_tgt->tgt == tgt) return tg; return NULL; } /* Look up a device node by device pointer in the given device group. */ static struct scst_dg_dev *__lookup_dg_dev_by_dev(struct scst_dev_group *dg, struct scst_device *dev) { struct scst_dg_dev *dgd; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dgd, &dg->dev_list, entry) if (dgd->dev == dev) return dgd; return NULL; } /* Look up a device node by name in the given device group. */ static struct scst_dg_dev *__lookup_dg_dev_by_name(struct scst_dev_group *dg, const char *name) { struct scst_dg_dev *dgd; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dgd, &dg->dev_list, entry) if (strcmp(dgd->dev->virt_name, name) == 0) return dgd; return NULL; } /* Look up a device node by name in any device group. */ static struct scst_dg_dev *__global_lookup_dg_dev_by_name(const char *name) { struct scst_dev_group *dg; struct scst_dg_dev *dgd; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dg, &scst_dev_group_list, entry) { dgd = __lookup_dg_dev_by_name(dg, name); if (dgd) return dgd; } return NULL; } /* Look up a device group by name. */ static struct scst_dev_group *__lookup_dg_by_name(const char *name) { struct scst_dev_group *dg; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dg, &scst_dev_group_list, entry) if (strcmp(dg->name, name) == 0) return dg; return NULL; } /* Look up a device group by device pointer. */ static struct scst_dev_group *__lookup_dg_by_dev(struct scst_device *dev) { struct scst_dev_group *dg; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dg, &scst_dev_group_list, entry) if (__lookup_dg_dev_by_dev(dg, dev)) return dg; return NULL; } /* * Target group contents management. */ static void scst_release_tg_tgt(struct kobject *kobj) { struct scst_tg_tgt *tg_tgt; tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); kfree(tg_tgt->name); kfree(tg_tgt); } static struct kobj_type scst_tg_tgt_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_release_tg_tgt, }; /* * Whether or not to accept a command in the ALUA standby state. */ static int scst_tg_accept_standby(struct scst_cmd *cmd) { int res; TRACE_ENTRY(); switch (cmd->cdb[0]) { case INQUIRY: case MODE_SENSE: case MODE_SENSE_10: case READ_CAPACITY: case REPORT_LUNS: case REQUEST_SENSE: case RELEASE: case RELEASE_10: case RESERVE: case RESERVE_10: case READ_BUFFER: case WRITE_BUFFER: case MODE_SELECT: case MODE_SELECT_10: case LOG_SELECT: case LOG_SENSE: case RECEIVE_DIAGNOSTIC: case SEND_DIAGNOSTIC: case PERSISTENT_RESERVE_IN: case PERSISTENT_RESERVE_OUT: res = SCST_ALUA_CHECK_OK; goto out; case SERVICE_ACTION_IN_16: switch (cmd->cdb[1] & 0x1f) { case SAI_READ_CAPACITY_16: res = SCST_ALUA_CHECK_OK; goto out; } break; case MAINTENANCE_IN: switch (cmd->cdb[1] & 0x1f) { case MI_REPORT_TARGET_PGS: res = SCST_ALUA_CHECK_OK; goto out; } break; case MAINTENANCE_OUT: switch (cmd->cdb[1] & 0x1f) { case MO_SET_TARGET_PGS: res = SCST_ALUA_CHECK_OK; goto out; } break; } scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_standby)); res = SCST_ALUA_CHECK_ERROR; out: TRACE_EXIT_RES(res); return res; } /* * Whether or not to accept a command in the ALUA unavailable state. */ static int scst_tg_accept_unav(struct scst_cmd *cmd) { int res; TRACE_ENTRY(); switch (cmd->cdb[0]) { case INQUIRY: case READ_CAPACITY: case REPORT_LUNS: case REQUEST_SENSE: case READ_BUFFER: case WRITE_BUFFER: res = SCST_ALUA_CHECK_OK; goto out; case SERVICE_ACTION_IN_16: switch (cmd->cdb[1] & 0x1f) { case SAI_READ_CAPACITY_16: res = SCST_ALUA_CHECK_OK; goto out; } break; case MAINTENANCE_IN: switch (cmd->cdb[1] & 0x1f) { case MI_REPORT_TARGET_PGS: res = SCST_ALUA_CHECK_OK; goto out; } break; case MAINTENANCE_OUT: switch (cmd->cdb[1] & 0x1f) { case MO_SET_TARGET_PGS: res = SCST_ALUA_CHECK_OK; goto out; } break; } scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_unav)); res = SCST_ALUA_CHECK_ERROR; out: TRACE_EXIT_RES(res); return res; } struct scst_alua_retry { struct scst_cmd *alua_retry_cmd; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) struct work_struct alua_retry_work; #else struct delayed_work alua_retry_work; #endif }; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) static void scst_alua_transitioning_work_fn(void *p) { struct scst_alua_retry *retry = p; #else static void scst_alua_transitioning_work_fn(struct work_struct *work) { struct scst_alua_retry *retry = container_of(work, struct scst_alua_retry, alua_retry_work.work); #endif struct scst_cmd *cmd = retry->alua_retry_cmd; TRACE_ENTRY(); TRACE_DBG("Retrying transitioning cmd %p", cmd); spin_lock_irq(&cmd->cmd_threads->cmd_list_lock); list_add(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); wake_up(&cmd->cmd_threads->cmd_list_waitQ); spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock); kfree(retry); TRACE_EXIT(); return; } /* * Whether or not to accept a command in the ALUA transitioning state. */ static int scst_tg_accept_transitioning(struct scst_cmd *cmd) { int res; TRACE_ENTRY(); switch (cmd->cdb[0]) { case INQUIRY: case READ_CAPACITY: case REPORT_LUNS: case REQUEST_SENSE: case READ_BUFFER: case WRITE_BUFFER: res = SCST_ALUA_CHECK_OK; goto out; case SERVICE_ACTION_IN_16: switch (cmd->cdb[1] & 0x1f) { case SAI_READ_CAPACITY_16: res = SCST_ALUA_CHECK_OK; goto out; } break; case MAINTENANCE_IN: switch (cmd->cdb[1] & 0x1f) { case MI_REPORT_TARGET_PGS: res = SCST_ALUA_CHECK_OK; goto out; } break; } if (cmd->already_transitioning) TRACE_DBG("cmd %p already transitioned checked, failing", cmd); else { struct scst_alua_retry *retry; TRACE_DBG("ALUA transitioning: delaying cmd %p", cmd); retry = kzalloc(sizeof(*retry), GFP_KERNEL); if (retry == NULL) { TRACE_DBG("Unable to allocate ALUA retry " "struct, failing cmd %p", cmd); scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_transitioning)); res = SCST_ALUA_CHECK_ERROR; goto out; } /* No get is needed, because cmd is sync here */ retry->alua_retry_cmd = cmd; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) INIT_WORK(&retry->alua_retry_work, scst_alua_transitioning_work_fn, retry); #else INIT_DELAYED_WORK(&retry->alua_retry_work, scst_alua_transitioning_work_fn); #endif cmd->already_transitioning = 1; schedule_delayed_work(&retry->alua_retry_work, HZ/2); res = SCST_ALUA_CHECK_DELAYED; goto out; } scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_transitioning)); res = SCST_ALUA_CHECK_ERROR; out: TRACE_EXIT_RES(res); return res; } static int (*scst_alua_filter[])(struct scst_cmd *cmd) = { [SCST_TG_STATE_OPTIMIZED] = NULL, [SCST_TG_STATE_NONOPTIMIZED] = NULL, [SCST_TG_STATE_STANDBY] = scst_tg_accept_standby, [SCST_TG_STATE_UNAVAILABLE] = scst_tg_accept_unav, [SCST_TG_STATE_LBA_DEPENDENT] = NULL, [SCST_TG_STATE_OFFLINE] = scst_tg_accept_unav, [SCST_TG_STATE_TRANSITIONING] = scst_tg_accept_transitioning, }; /* * Check whether the tgt_dev ALUA filter is consistent with the target group * ALUA state. */ static void scst_check_alua_invariant(void) { struct scst_device *dev; struct scst_tgt_dev *tgt_dev; struct scst_dev_group *dg; struct scst_target_group *tg; enum scst_tg_state expected_state; #if 0 lockdep_assert_held(&scst_mutex); /* scst_dev_list, dev_tgt_dev_list */ #endif lockdep_assert_held(&scst_dg_mutex); if (!alua_invariant_check) return; list_for_each_entry(dev, &scst_dev_list, dev_list_entry) { dg = __lookup_dg_by_dev(dev); list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { tg = dg ? __lookup_tg_by_tgt(dg, tgt_dev->acg_dev->acg->tgt) : NULL; expected_state = tg ? tg->state : SCST_TG_STATE_OPTIMIZED; if (tgt_dev->alua_filter != scst_alua_filter[expected_state]) { PRINT_ERROR("LUN %s/%s/%s/%lld/%s: ALUA filter" " %p <> %p", tgt_dev->acg_dev->acg->tgt->tgt_name, tgt_dev->acg_dev->acg->acg_name ? : "(default)", tgt_dev->sess->initiator_name, tgt_dev->lun, tgt_dev->dev->virt_name ? : "(null)", tgt_dev->alua_filter, scst_alua_filter[expected_state]); } } } } /* Update the ALUA filter of a tgt_dev */ static void scst_update_tgt_dev_alua_filter(struct scst_tgt_dev *tgt_dev, enum scst_tg_state state) { lockdep_assert_held(&scst_dg_mutex); tgt_dev->alua_filter = scst_alua_filter[state]; } /* Update the ALUA filter after an ALUA state change and generate UA */ static void scst_tg_change_tgt_dev_state(struct scst_tgt_dev *tgt_dev, enum scst_tg_state state, bool gen_ua) { lockdep_assert_held(&scst_dg_mutex); TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed (gen_ua %d)", tgt_dev, gen_ua); scst_update_tgt_dev_alua_filter(tgt_dev, state); if (gen_ua) scst_gen_aen_or_ua(tgt_dev, SCST_LOAD_SENSE(scst_sense_asym_access_state_changed)); } /* Initialize ALUA state of LUN tgt_dev */ void scst_tg_init_tgt_dev(struct scst_tgt_dev *tgt_dev) { struct scst_dev_group *dg; struct scst_target_group *tg; mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(tgt_dev->dev); if (dg) { tg = __lookup_tg_by_tgt(dg, tgt_dev->acg_dev->acg->tgt); if (tg) { scst_update_tgt_dev_alua_filter(tgt_dev, tg->state); scst_check_alua_invariant(); } } mutex_unlock(&scst_dg_mutex); } /* * Update the ALUA filter of all tgt_devs associated with target group @tg * and target @tgt. */ static void scst_update_tgt_alua_filter(struct scst_target_group *tg, struct scst_tgt *tgt) { struct scst_dg_dev *dgd; struct scst_tgt_dev *tgt_dev; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dgd, &tg->dg->dev_list, entry) { list_for_each_entry(tgt_dev, &dgd->dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { if (tgt_dev->acg_dev->acg->tgt == tgt) scst_update_tgt_dev_alua_filter(tgt_dev, tg->state); } } scst_check_alua_invariant(); } /* * Reset the ALUA filter of all tgt_devs associated with target group @tg * and target @tgt. */ static void scst_reset_tgt_alua_filter(struct scst_target_group *tg, struct scst_tgt *tgt) { struct scst_dg_dev *dgd; struct scst_tgt_dev *tgt_dev; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dgd, &tg->dg->dev_list, entry) { list_for_each_entry(tgt_dev, &dgd->dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { if (tgt_dev->acg_dev->acg->tgt == tgt) scst_update_tgt_dev_alua_filter(tgt_dev, SCST_TG_STATE_OPTIMIZED); } } scst_check_alua_invariant(); } /* * scst_tg_tgt_add() - Add a target to a target group. */ int scst_tg_tgt_add(struct scst_target_group *tg, const char *name) { struct scst_tg_tgt *tg_tgt; struct scst_tgt *tgt; int res; TRACE_ENTRY(); BUG_ON(!tg); BUG_ON(!name); res = -ENOMEM; tg_tgt = kzalloc(sizeof(*tg_tgt), GFP_KERNEL); if (!tg_tgt) goto out; tg_tgt->tg = tg; #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) kobject_init(&tg_tgt->kobj, &scst_tg_tgt_ktype); #else kobject_init(&tg_tgt->kobj); tg_tgt->kobj.ktype = &scst_tg_tgt_ktype; #endif tg_tgt->name = kstrdup(name, GFP_KERNEL); if (!tg_tgt->name) goto out_put; res = mutex_lock_interruptible(&scst_mutex); if (res) goto out_put; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out_unlock_scst; res = -EEXIST; tgt = __lookup_tgt(name); if (__lookup_dg_tgt(tg->dg, name)) goto out_unlock_dg; tg_tgt->tgt = tgt; res = scst_tg_tgt_sysfs_add(tg, tg_tgt); if (res) goto out_unlock_dg; list_add_tail(&tg_tgt->entry, &tg->tgt_list); scst_update_tgt_alua_filter(tg, tgt); res = 0; mutex_unlock(&scst_dg_mutex); mutex_unlock(&scst_mutex); out: TRACE_EXIT_RES(res); return res; out_unlock_dg: mutex_unlock(&scst_dg_mutex); out_unlock_scst: mutex_unlock(&scst_mutex); out_put: kobject_put(&tg_tgt->kobj); goto out; } static void __scst_tg_tgt_remove(struct scst_target_group *tg, struct scst_tg_tgt *tg_tgt) { TRACE_ENTRY(); list_del(&tg_tgt->entry); scst_tg_tgt_sysfs_del(tg, tg_tgt); scst_reset_tgt_alua_filter(tg, tg_tgt->tgt); kobject_put(&tg_tgt->kobj); TRACE_EXIT(); } /* * scst_tg_tgt_remove_by_name() - Remove a target from a target group. */ int scst_tg_tgt_remove_by_name(struct scst_target_group *tg, const char *name) { struct scst_tg_tgt *tg_tgt; int res; TRACE_ENTRY(); BUG_ON(!tg); BUG_ON(!name); res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; res = -EINVAL; tg_tgt = __lookup_dg_tgt(tg->dg, name); if (!tg_tgt) goto out_unlock; __scst_tg_tgt_remove(tg, tg_tgt); res = 0; out_unlock: mutex_unlock(&scst_dg_mutex); out: TRACE_EXIT_RES(res); return res; } /* Called from the target removal code. */ void scst_tg_tgt_remove_by_tgt(struct scst_tgt *tgt) { struct scst_dev_group *dg; struct scst_target_group *tg; struct scst_tg_tgt *t, *t2; mutex_lock(&scst_dg_mutex); list_for_each_entry(dg, &scst_dev_group_list, entry) list_for_each_entry(tg, &dg->tg_list, entry) list_for_each_entry_safe(t, t2, &tg->tgt_list, entry) if (t->tgt == tgt) __scst_tg_tgt_remove(tg, t); mutex_unlock(&scst_dg_mutex); } /* * Target group management. */ static void scst_release_tg(struct kobject *kobj) { struct scst_target_group *tg; tg = container_of(kobj, struct scst_target_group, kobj); kfree(tg->name); kfree(tg); } static struct kobj_type scst_tg_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_release_tg, }; /* * scst_tg_add() - Add a target group. */ int scst_tg_add(struct scst_dev_group *dg, const char *name) { struct scst_target_group *tg; int res; TRACE_ENTRY(); res = -ENOMEM; tg = kzalloc(sizeof(*tg), GFP_KERNEL); if (!tg) goto out; #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) kobject_init(&tg->kobj, &scst_tg_ktype); #else kobject_init(&tg->kobj); tg->kobj.ktype = &scst_tg_ktype; #endif tg->name = kstrdup(name, GFP_KERNEL); if (!tg->name) goto out_put; tg->dg = dg; tg->state = SCST_TG_STATE_OPTIMIZED; INIT_LIST_HEAD(&tg->tgt_list); res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out_put; res = -EEXIST; if (__lookup_tg_by_name(dg, name)) goto out_unlock; res = scst_tg_sysfs_add(dg, tg); if (res) goto out_unlock; list_add_tail(&tg->entry, &dg->tg_list); mutex_unlock(&scst_dg_mutex); out: TRACE_EXIT_RES(res); return res; out_unlock: mutex_unlock(&scst_dg_mutex); out_put: kobject_put(&tg->kobj); goto out; } static void __scst_tg_remove(struct scst_dev_group *dg, struct scst_target_group *tg) { struct scst_tg_tgt *tg_tgt; TRACE_ENTRY(); BUG_ON(!dg); BUG_ON(!tg); while (!list_empty(&tg->tgt_list)) { tg_tgt = list_first_entry(&tg->tgt_list, struct scst_tg_tgt, entry); __scst_tg_tgt_remove(tg, tg_tgt); } list_del(&tg->entry); scst_tg_sysfs_del(tg); kobject_put(&tg->kobj); TRACE_EXIT(); } /* * scst_tg_remove_by_name() - Remove a target group. */ int scst_tg_remove_by_name(struct scst_dev_group *dg, const char *name) { struct scst_target_group *tg; int res; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; res = -EINVAL; tg = __lookup_tg_by_name(dg, name); if (!tg) goto out_unlock; __scst_tg_remove(dg, tg); res = 0; out_unlock: mutex_unlock(&scst_dg_mutex); out: return res; } static void scst_event_stpg_notify_fn(struct scst_event *event, void *priv, int status) { struct scst_dev_group *dg; struct scst_cmd *cmd = (struct scst_cmd *)priv; struct scst_event_stpg_payload *p = (struct scst_event_stpg_payload *)event->payload; struct scst_event_stpg_descr *d; struct scst_dg_dev *dgd; int i; TRACE_ENTRY(); PRINT_INFO("Notification for event %u (id %d) received " "with status %d (priv %p)", event->event_code, event->event_id, status, priv); mutex_lock(&scst_mutex); mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(cmd->dev); if (!dg) { PRINT_ERROR("STPG: unable to find DG for device %s", cmd->dev->virt_name); goto out_fail; } list_for_each_entry(dgd, &dg->dev_list, entry) { if (dgd->dev->stpg_ext_blocked) { TRACE_DBG("STPG: ext unblocking dev %s", dgd->dev->virt_name); scst_ext_unblock_dev(dgd->dev, true); dgd->dev->stpg_ext_blocked = 0; } } kfree(dg->stpg_transport_id); dg->stpg_transport_id = NULL; if (status != 0) { PRINT_ERROR("on_stpg script for device group %s failed with status %d", dg->name, status); goto out_fail; } for (i = 0, d = &p->stpg_descriptors[0]; i < p->stpg_descriptors_cnt; i++, d++) { struct scst_target_group *tg = __lookup_tg_by_group_id(dg, d->group_id); if (!tg) { PRINT_ERROR("STPG: unable to find TG %d", d->group_id); goto out_fail; } else if (tg->state == scst_alua_name_to_state(d->prev_state)) { PRINT_ERROR("on_stpg script did not change ALUA state" " for device group %s / target group %s", dg->name, tg->name); goto out_fail; } } out_unlock: mutex_unlock(&scst_dg_mutex); mutex_unlock(&scst_mutex); scst_stpg_del_unblock_next(cmd); cmd->completed = 1; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); TRACE_EXIT(); return; out_fail: scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); goto out_unlock; } /* * Update the ALUA filter of those LUNs (tgt_dev) whose target port is a member * of target group @tg and that export a device that is a member of the device * group @tg->dg. */ static void __scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state) { struct scst_dg_dev *dg_dev; struct scst_device *dev; struct scst_tgt_dev *tgt_dev; struct scst_tg_tgt *tg_tgt; struct scst_tgt *tgt; enum scst_tg_state old_state = tg->state; bool dev_changed; sBUG_ON(state >= ARRAY_SIZE(scst_alua_filter)); lockdep_assert_held(&scst_dg_mutex); if (tg->state == state) return; tg->state = state; list_for_each_entry(dg_dev, &tg->dg->dev_list, entry) { dev = dg_dev->dev; dev_changed = false; if ((dev->handler->on_alua_state_change_start != NULL) && !dev_changed) { dev->handler->on_alua_state_change_start(dev, old_state, state); dev_changed = true; } list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { tgt = tgt_dev->sess->tgt; list_for_each_entry(tg_tgt, &tg->tgt_list, entry) { if (tg_tgt->tgt == tgt) { bool gen_ua = (state != SCST_TG_STATE_TRANSITIONING); if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) && tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id)) gen_ua = false; scst_tg_change_tgt_dev_state(tgt_dev, state, gen_ua); break; } } } if ((dev->handler->on_alua_state_change_finish != NULL) && dev_changed) dev->handler->on_alua_state_change_finish(dev, old_state, state); } scst_check_alua_invariant(); PRINT_INFO("Changed ALUA state of %s/%s into %s", tg->dg->name, tg->name, scst_alua_state_name(state)); } int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state) { int res; res = -EINVAL; if (state >= ARRAY_SIZE(scst_alua_filter)) goto out; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; __scst_tg_set_state(tg, state); mutex_unlock(&scst_dg_mutex); out: return res; } /* * Generate an ASYMMETRIC ACCESS STATE CHANGED check condition after the value * of the "preferred" state of a target port group has been changed. Although * not required by SPC-4, generating this check condition terminates an * initiator-side STPG loop. An initiator typically retries an STPG after * having received a LOGICAL UNIT NOT ACCESSIBLE, ASYMMETRIC ACCESS STATE * TRANSITION but stops resending an STPG when it receives an ASYMMETRIC * ACCESS STATE CHANGED check condition. */ static void __scst_gen_alua_state_changed_ua(struct scst_target_group *tg) { struct scst_dg_dev *dg_dev; struct scst_device *dev; struct scst_tgt_dev *tgt_dev; struct scst_tg_tgt *tg_tgt; struct scst_tgt *tgt; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(dg_dev, &tg->dg->dev_list, entry) { dev = dg_dev->dev; list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { tgt = tgt_dev->sess->tgt; list_for_each_entry(tg_tgt, &tg->tgt_list, entry) { if (tg_tgt->tgt == tgt) { scst_gen_aen_or_ua(tgt_dev, SCST_LOAD_SENSE(scst_sense_asym_access_state_changed)); break; } } } } } static void __scst_tg_set_preferred(struct scst_target_group *tg, bool preferred) { bool prev_preferred; lockdep_assert_held(&scst_dg_mutex); if (tg->preferred == preferred) return; prev_preferred = tg->preferred; tg->preferred = preferred; PRINT_INFO("Changed preferred state of %s/%s from %d into %d", tg->dg->name, tg->name, prev_preferred, preferred); __scst_gen_alua_state_changed_ua(tg); } int scst_tg_set_preferred(struct scst_target_group *tg, bool preferred) { int res; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; __scst_tg_set_preferred(tg, preferred); mutex_unlock(&scst_dg_mutex); out: return res; } /* * Device group contents manipulation. */ /* * Update the ALUA filter of all tgt_devs associated with device group @dg * and device @dev. */ static void scst_update_dev_alua_filter(struct scst_dev_group *dg, struct scst_device *dev) { struct scst_tgt_dev *tgt_dev; struct scst_target_group *tg; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { tg = __lookup_tg_by_tgt(dg, tgt_dev->acg_dev->acg->tgt); if (tg) scst_update_tgt_dev_alua_filter(tgt_dev, tg->state); } scst_check_alua_invariant(); } /* * Reset the ALUA filter of all tgt_devs associated with device @dev. Note: * each device is member of at most one device group. */ static void scst_reset_dev_alua_filter(struct scst_device *dev) { struct scst_tgt_dev *tgt_dev; lockdep_assert_held(&scst_dg_mutex); list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) scst_update_tgt_dev_alua_filter(tgt_dev, SCST_TG_STATE_OPTIMIZED); scst_check_alua_invariant(); } /* * scst_dg_dev_add() - Add a device to a device group. * * It is verified whether 'name' refers to an existing device and whether that * device has not yet been added to any other device group. */ int scst_dg_dev_add(struct scst_dev_group *dg, const char *name) { struct scst_dg_dev *dgdev; struct scst_device *dev; int res; res = -ENOMEM; dgdev = kzalloc(sizeof(*dgdev), GFP_KERNEL); if (!dgdev) goto out; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out_free; res = -EEXIST; if (__global_lookup_dg_dev_by_name(name)) goto out_unlock; res = -EINVAL; dev = __lookup_dev(name); if (!dev) goto out_unlock; dgdev->dev = dev; res = scst_dg_dev_sysfs_add(dg, dgdev); if (res) goto out_unlock; list_add_tail(&dgdev->entry, &dg->dev_list); scst_update_dev_alua_filter(dg, dev); mutex_unlock(&scst_dg_mutex); out: return res; out_unlock: mutex_unlock(&scst_dg_mutex); out_free: kfree(dgdev); goto out; } /* scst_dg_mutex supposed to be locked */ static void __scst_dg_dev_remove(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) { if (dgdev->dev->stpg_ext_blocked) { TRACE_DBG("DG %s remove: unblocking STPG ext blocked " "dev %s", dg->name, dgdev->dev->virt_name); scst_ext_unblock_dev(dgdev->dev, true); dgdev->dev->stpg_ext_blocked = 0; } list_del(&dgdev->entry); scst_dg_dev_sysfs_del(dg, dgdev); scst_reset_dev_alua_filter(dgdev->dev); kfree(dgdev); } /* * scst_dg_dev_remove_by_name() - Remove a device from a device group. */ int scst_dg_dev_remove_by_name(struct scst_dev_group *dg, const char *name) { struct scst_dg_dev *dgdev; int res; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; res = -EINVAL; dgdev = __lookup_dg_dev_by_name(dg, name); if (!dgdev) goto out_unlock; __scst_dg_dev_remove(dg, dgdev); res = 0; out_unlock: mutex_unlock(&scst_dg_mutex); out: return res; } /* Called from the device removal code. */ int scst_dg_dev_remove_by_dev(struct scst_device *dev) { struct scst_dev_group *dg; struct scst_dg_dev *dgdev; int res; res = -EINVAL; mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(dev); if (!dg) goto out; dgdev = __lookup_dg_dev_by_dev(dg, dev); BUG_ON(!dgdev); __scst_dg_dev_remove(dg, dgdev); res = 0; out: mutex_unlock(&scst_dg_mutex); return res; } /* * Device group management. */ static void scst_release_dg(struct kobject *kobj) { struct scst_dev_group *dg; dg = container_of(kobj, struct scst_dev_group, kobj); kfree(dg->name); kfree(dg); } static struct kobj_type scst_dg_ktype = { .sysfs_ops = &scst_sysfs_ops, .release = scst_release_dg, }; /* * scst_dg_add() - Add a new device group object and make it visible in sysfs. */ int scst_dg_add(struct kobject *parent, const char *name) { struct scst_dev_group *dg; int res; TRACE_ENTRY(); res = -ENOMEM; dg = kzalloc(sizeof(*dg), GFP_KERNEL); if (!dg) goto out; #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) kobject_init(&dg->kobj, &scst_dg_ktype); #else kobject_init(&dg->kobj); dg->kobj.ktype = &scst_dg_ktype; #endif dg->name = kstrdup(name, GFP_KERNEL); if (!dg->name) goto out_put; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out_put; res = -EEXIST; if (__lookup_dg_by_name(name)) goto out_unlock; INIT_LIST_HEAD(&dg->dev_list); INIT_LIST_HEAD(&dg->tg_list); res = scst_dg_sysfs_add(parent, dg); if (res) goto out_unlock; list_add_tail(&dg->entry, &scst_dev_group_list); mutex_unlock(&scst_dg_mutex); out: TRACE_EXIT_RES(res); return res; out_unlock: mutex_unlock(&scst_dg_mutex); out_put: kobject_put(&dg->kobj); goto out; } static void __scst_dg_remove(struct scst_dev_group *dg) { struct scst_dg_dev *dgdev; struct scst_target_group *tg; lockdep_assert_held(&scst_dg_mutex); list_del(&dg->entry); scst_dg_sysfs_del(dg); list_for_each_entry(tg, &dg->tg_list, entry) __scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED); while (!list_empty(&dg->dev_list)) { dgdev = list_first_entry(&dg->dev_list, struct scst_dg_dev, entry); __scst_dg_dev_remove(dg, dgdev); } while (!list_empty(&dg->tg_list)) { tg = list_first_entry(&dg->tg_list, struct scst_target_group, entry); __scst_tg_remove(dg, tg); } kobject_put(&dg->kobj); } int scst_dg_remove(const char *name) { struct scst_dev_group *dg; int res; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; res = -EINVAL; dg = __lookup_dg_by_name(name); if (!dg) goto out_unlock; __scst_dg_remove(dg); res = 0; out_unlock: mutex_unlock(&scst_dg_mutex); out: return res; } /* * Given a pointer to a device_groups//devices or * device_groups//target_groups kobject, return the pointer to the * corresponding device group. * * Note: The caller must hold a reference on the kobject to avoid that the * object disappears before the caller stops using the device group pointer. */ struct scst_dev_group *scst_lookup_dg_by_kobj(struct kobject *kobj) { int res; struct scst_dev_group *dg; dg = NULL; res = mutex_lock_interruptible(&scst_dg_mutex); if (res) goto out; list_for_each_entry(dg, &scst_dev_group_list, entry) if (dg->dev_kobj == kobj || dg->tg_kobj == kobj) goto out_unlock; dg = NULL; out_unlock: mutex_unlock(&scst_dg_mutex); out: return dg; } struct scst_dev_group *scst_lookup_dg_by_dev(struct scst_device *dev) { return __lookup_dg_by_dev(dev); } /* * Target group module management. */ void scst_tg_init(void) { } void scst_tg_cleanup(void) { struct scst_dev_group *tg; mutex_lock(&scst_dg_mutex); while (!list_empty(&scst_dev_group_list)) { tg = list_first_entry(&scst_dev_group_list, struct scst_dev_group, entry); __scst_dg_remove(tg); } mutex_unlock(&scst_dg_mutex); } /* * Functions for target group related SCSI command support. */ /** * scst_lookup_tg_id() - Look up a target port group identifier. * @dev: SCST device. * @tgt: SCST target. * * Returns a non-zero number if the lookup was successful and zero if not. */ uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt) { struct scst_dev_group *dg; struct scst_target_group *tg; struct scst_tg_tgt *tg_tgt; uint16_t tg_id = 0; TRACE_ENTRY(); mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(dev); if (!dg) goto out_unlock; tg_tgt = __lookup_dg_tgt(dg, tgt->tgt_name); if (!tg_tgt) goto out_unlock; tg = tg_tgt->tg; BUG_ON(!tg); tg_id = tg->group_id; out_unlock: mutex_unlock(&scst_dg_mutex); TRACE_EXIT_RES(tg_id); return tg_id; } EXPORT_SYMBOL_GPL(scst_lookup_tg_id); /** * scst_get_alua_state() - returns ALUA state the target port group. * @dev: SCST device. * @tgt: SCST target. * * Returns a valid ALUA state (SCST_TG_STATE_OPTIMIZED is no ALUA configured) */ enum scst_tg_state scst_get_alua_state(struct scst_device *dev, struct scst_tgt *tgt) { struct scst_dev_group *dg; struct scst_target_group *tg; struct scst_tg_tgt *tg_tgt; enum scst_tg_state res = SCST_TG_STATE_OPTIMIZED; TRACE_ENTRY(); mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(dev); if (!dg) goto out_unlock; tg_tgt = __lookup_dg_tgt(dg, tgt->tgt_name); if (!tg_tgt) goto out_unlock; tg = tg_tgt->tg; BUG_ON(!tg); res = tg->state; out_unlock: mutex_unlock(&scst_dg_mutex); TRACE_EXIT_RES(res); return res; } EXPORT_SYMBOL_GPL(scst_get_alua_state); /** * scst_alua_configured() - Whether implicit ALUA has been configured. * @dev: Pointer to the SCST device to verify. */ bool scst_alua_configured(struct scst_device *dev) { struct scst_dev_group *dg; mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(dev); mutex_unlock(&scst_dg_mutex); return dg != NULL; } EXPORT_SYMBOL_GPL(scst_alua_configured); /** * scst_tg_get_group_info() - Build REPORT TARGET GROUPS response. * @buf: Pointer to a pointer to which the result buffer pointer will be set. * @length: Response length, including the "RETURN DATA LENGTH" field. * @dev: Pointer to the SCST device for which to obtain group information. * @data_format: Three-bit response data format specification. */ int scst_tg_get_group_info(void **buf, uint32_t *length, struct scst_device *dev, uint8_t data_format) { struct scst_dev_group *dg; struct scst_target_group *tg; struct scst_tg_tgt *tgtgt; struct scst_tgt *tgt; uint8_t *p; uint32_t ret_data_len; uint16_t rel_tgt_id; int res; TRACE_ENTRY(); BUG_ON(!buf); BUG_ON(!length); ret_data_len = 0; res = -EINVAL; switch (data_format) { case 0: break; case 1: /* Extended header */ ret_data_len += 4; break; default: goto out; } *length = 4; mutex_lock(&scst_dg_mutex); dg = __lookup_dg_by_dev(dev); if (dg) { list_for_each_entry(tg, &dg->tg_list, entry) { /* Target port group descriptor header. */ ret_data_len += 8; list_for_each_entry(tgtgt, &tg->tgt_list, entry) { /* Target port descriptor. */ ret_data_len += 4; } } } *length += ret_data_len; res = -ENOMEM; *buf = kzalloc(*length, GFP_KERNEL); if (!*buf) goto out_unlock; p = *buf; /* Return data length. */ put_unaligned_be32(ret_data_len, p); p += 4; if (data_format == 1) { /* Extended header */ *p++ = 0x10; /* format = 1 */ *p++ = 0x00; /* implicit transition time = 0 */ p += 2; /* reserved */ } if (!dg) goto done; list_for_each_entry(tg, &dg->tg_list, entry) { /* Target port group descriptor header. */ *p++ = (tg->preferred ? SCST_TG_PREFERRED : 0) | tg->state; *p++ = SCST_TG_SUP_OPTIMIZED | SCST_TG_SUP_NONOPTIMIZED | SCST_TG_SUP_STANDBY | SCST_TG_SUP_UNAVAILABLE | SCST_TG_SUP_OFFLINE | SCST_TG_SUP_TRANSITION; put_unaligned_be16(tg->group_id, p); p += 2; p++; /* reserved */ *p++ = 2; /* status code: implicit transition */ p++; /* vendor specific */ list_for_each_entry(tgtgt, &tg->tgt_list, entry) (*p)++; /* target port count */ p++; list_for_each_entry(tgtgt, &tg->tgt_list, entry) { tgt = tgtgt->tgt; rel_tgt_id = tgt ? tgt->rel_tgt_id : tgtgt->rel_tgt_id; /* Target port descriptor. */ p += 2; /* reserved */ /* Relative target port identifier. */ put_unaligned_be16(rel_tgt_id, p); p += 2; } } done: WARN_ON(p - (uint8_t *)*buf != *length); res = 0; out_unlock: mutex_unlock(&scst_dg_mutex); out: TRACE_EXIT_RES(res); return res; } EXPORT_SYMBOL_GPL(scst_tg_get_group_info); struct scst_stpg_wait { atomic_t stpg_wait_left; int status; struct scst_dev_group *dg; struct scst_event_entry *event_entry; }; /* No locks */ static void scst_stpg_check_blocking_done(struct scst_stpg_wait *wait) { TRACE_ENTRY(); TRACE_DBG("wait %p, left %d", wait, atomic_read(&wait->stpg_wait_left)); if (atomic_dec_and_test(&wait->stpg_wait_left)) { if (wait->status == 0) scst_event_queue(SCST_EVENT_STPG_USER_INVOKE, SCST_EVENT_SCST_CORE_ISSUER, wait->event_entry); else { wait->event_entry->event_notify_fn(&wait->event_entry->event, wait->event_entry->notify_fn_priv, wait->status); } kfree(wait); } TRACE_EXIT(); return; } /* No locks */ static void scst_stpg_ext_blocking_done(struct scst_device *dev, uint8_t *data, int len) { sBUG_ON(len != sizeof(data)); scst_stpg_check_blocking_done(*((struct scst_stpg_wait **)data)); } /* * scst_tg_set_group_info - SET TARGET PORT GROUPS implementation. * * Returns >=0 upon success or negative error code otherwise, for instance, * if either an invalid group ID has been specified or the group ID * of a target group with one, or more non-local target ports has been * specified. In the error case the cmd has its sense set. * * In case of returned 0 the command completed asynchronously, i.e. upon * return might be already dead!! */ int scst_tg_set_group_info(struct scst_cmd *cmd) { struct scst_device *dev = cmd->dev; uint8_t *buf; int len; int i, j, res = 1, tpg_desc_count, valid_desc_count; struct scst_dev_group *dg; struct osi { uint16_t group_id; struct scst_target_group *tg; enum scst_tg_state prev_state; enum scst_tg_state new_state; } *osi = NULL; int event_entry_len, payload_len; struct scst_event_entry *event_entry; struct scst_event *event; struct scst_event_stpg_payload *payload; struct scst_event_stpg_descr *descr; TRACE_ENTRY(); len = scst_get_buf_full(cmd, &buf); if (len < 0) { PRINT_ERROR("scst_get_buf_full() failed: %d", len); res = len; if (len == -ENOMEM) scst_set_busy(cmd); else scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); goto out; } /* * From SPC-4: "A parameter list length of zero specifies that no data * shall be transferred, and that no change shall be made in the * target port asymmetric access state of any target port groups or * target ports". */ if (len == 0) goto out_put; tpg_desc_count = (len - 4) / 4; /* Check for some reasonable limit */ if (tpg_desc_count > 64) { PRINT_ERROR("Too many STPG descriptors (%d) for dev %s", tpg_desc_count, dev->virt_name); res = -EINVAL; scst_set_invalid_field_in_cdb(cmd, 6, 0); goto out_put; } TRACE_DBG("tpg_desc_count %d", tpg_desc_count); osi = kcalloc(tpg_desc_count, sizeof(*osi), GFP_KERNEL); if (!osi) { res = -ENOMEM; scst_set_busy(cmd); goto out_put; } res = mutex_lock_interruptible(&scst_mutex); if (res) { PRINT_INFO("mutex_lock_interruptible() returned %d, finishing " "cmd %p", res, cmd); scst_set_busy(cmd); goto out_put; } res = mutex_lock_interruptible(&scst_dg_mutex); if (res) { PRINT_INFO("mutex_lock_interruptible() returned %d, finishing " "cmd %p", res, cmd); scst_set_busy(cmd); goto out_unlock_sm_fail; } dg = __lookup_dg_by_dev(dev); if (!dg) { res = -EINVAL; goto out_unlock_fail; } TRACE_DBG("dg %s (%p) found, dev %s", dg->name, dg, dev->virt_name); for (i = 4, j = 0; i + 4 <= len; i += 4, j++) { #ifndef __CHECKER__ /* * Hide the statement below for smatch because otherwise it * triggers a false positive. */ WARN_ON_ONCE(j >= tpg_desc_count); #endif osi[j].new_state = buf[i] & 0x1f; switch (osi[j].new_state) { case SCST_TG_STATE_OPTIMIZED: case SCST_TG_STATE_NONOPTIMIZED: case SCST_TG_STATE_STANDBY: case SCST_TG_STATE_UNAVAILABLE: case SCST_TG_STATE_OFFLINE: break; case SCST_TG_STATE_LBA_DEPENDENT: case SCST_TG_STATE_TRANSITIONING: case SCST_TG_STATE_UNDEFINED: default: TRACE_MGMT_DBG("Incorrect new state %d", osi[j].new_state); res = -EINVAL; goto out_unlock_fail; } osi[j].group_id = get_unaligned_be16(&buf[i + 2]); if (!osi[j].group_id) { TRACE_MGMT_DBG("Invalid group_id %d", osi[j].group_id); res = -EINVAL; goto out_unlock_fail; } osi[j].tg = __lookup_tg_by_group_id(dg, osi[j].group_id); if (!osi[j].tg) { TRACE_MGMT_DBG("No TG for group_id %d", osi[j].group_id); res = -ESRCH; goto out_unlock_fail; } if (osi[j].tg->state == SCST_TG_STATE_TRANSITIONING) { TRACE_MGMT_DBG("TG %p is transitioning", osi[j].tg); res = -EBUSY; scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_transitioning)); /* second sense will not override the set one */ goto out_unlock_fail; } osi[j].prev_state = osi[j].tg->state; TRACE_DBG("j %d, group_id %u, tg %s (%p), state %d", j, osi[j].group_id, osi[j].tg->name, osi[j].tg, osi[j].tg->state); } mutex_unlock(&scst_dg_mutex); mutex_unlock(&scst_mutex); scst_put_buf_full(cmd, buf); payload_len = sizeof(*payload) + sizeof(*descr) * tpg_desc_count; event_entry_len = sizeof(*event_entry) + payload_len; event_entry = kzalloc(event_entry_len, GFP_KERNEL); if (event_entry == NULL) { PRINT_ERROR("Unable to allocate event (size %d)", event_entry_len); res = -ENOMEM; scst_set_busy(cmd); goto out_free; } TRACE_MEM("event_entry %p (len %d) allocated", event_entry, event_entry_len); event = &event_entry->event; event->payload_len = payload_len; payload = (struct scst_event_stpg_payload *)event->payload; payload->stpg_cmd_tag = cmd->tag; res = 1; if (strlen(dev->virt_name) >= sizeof(payload->device_name)) { PRINT_ERROR("Device name %s too long", dev->virt_name); goto out_too_long; } strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name)); valid_desc_count = 0; for (j = 0, descr = &payload->stpg_descriptors[0]; j < tpg_desc_count; j++) { if (osi[j].prev_state == osi[j].new_state) continue; if (strlen(scst_alua_state_name(osi[j].prev_state)) >= sizeof(descr->prev_state)) { PRINT_ERROR("prev state too long (%d)", osi[j].prev_state); goto out_too_long; } strlcpy(descr->prev_state, scst_alua_state_name(osi[j].prev_state), sizeof(descr->prev_state)); if (strlen(scst_alua_state_name(osi[j].new_state)) >= sizeof(descr->new_state)) { PRINT_ERROR("new state too long (%d)", osi[j].new_state); goto out_too_long; } strlcpy(descr->new_state, scst_alua_state_name(osi[j].new_state), sizeof(descr->new_state)); if (strlen(dg->name) >= sizeof(descr->dg_name)) { PRINT_ERROR("dg_name too long (%s)", dg->name); goto out_too_long; } strlcpy(descr->dg_name, dg->name, sizeof(descr->dg_name)); if (strlen(osi[j].tg->name) >= sizeof(descr->tg_name)) { PRINT_ERROR("tg_name too long (%s)", osi[j].tg->name); goto out_too_long; } strlcpy(descr->tg_name, osi[j].tg->name, sizeof(descr->tg_name)); descr->group_id = osi[j].group_id; TRACE_DBG("group_id %u, prev_state %s, new_state %s, dg_name %s, " "tg_name %s", descr->group_id, descr->prev_state, descr->new_state, descr->dg_name, descr->tg_name); valid_desc_count++; descr++; } payload->stpg_descriptors_cnt = valid_desc_count; if (valid_desc_count > 0) { struct scst_dg_dev *dgd; struct scst_stpg_wait *wait; int rc; dg->stpg_rel_tgt_id = cmd->tgt->rel_tgt_id; dg->stpg_transport_id = kmemdup(cmd->sess->transport_id, scst_tid_size(cmd->sess->transport_id), GFP_KERNEL); if (dg->stpg_transport_id == NULL) { PRINT_ERROR("Unable to duplicate stpg_transport_id"); goto out_free_event; } wait = kzalloc(sizeof(*wait), GFP_KERNEL); if (wait == NULL) { PRINT_ERROR("Unable to allocate STPG wait struct " "(size %zd)", sizeof(*wait)); scst_set_busy(cmd); res = -ENOMEM; goto out_free_tr_id; } atomic_set(&wait->stpg_wait_left, 1); wait->event_entry = event_entry; event_entry->event_notify_fn = scst_event_stpg_notify_fn; event_entry->notify_fn_priv = cmd; mutex_lock(&scst_dg_mutex); list_for_each_entry(dgd, &dg->dev_list, entry) { if (dgd->dev == dev) continue; TRACE_DBG("STPG: ext blocking dev %s", dgd->dev->virt_name); atomic_inc(&wait->stpg_wait_left); rc = scst_ext_block_dev(dgd->dev, scst_stpg_ext_blocking_done, (uint8_t *)&wait, sizeof(wait), SCST_EXT_BLOCK_STPG); if (rc != 0) { TRACE_DBG("scst_ext_block_dev() failed " "with %d, reverting (cmd %p)", rc, cmd); wait->status = rc; wait->dg = dg; atomic_dec(&wait->stpg_wait_left); spin_lock_bh(&dev->dev_lock); WARN_ON(dgd->dev->stpg_ext_blocked); dgd->dev->stpg_ext_blocked = 0; spin_unlock_bh(&dev->dev_lock); break; } } mutex_unlock(&scst_dg_mutex); scst_stpg_check_blocking_done(wait); /* !! cmd can be already dead here !! */ } else { TRACE_DBG("Nothing to do"); goto out_free_event; } res = 0; out_free: kfree(osi); out: TRACE_EXIT_RES(res); return res; out_unlock_fail: mutex_unlock(&scst_dg_mutex); out_unlock_sm_fail: mutex_unlock(&scst_mutex); scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); out_put: scst_put_buf_full(cmd, buf); goto out_free; out_too_long: scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); res = -EOVERFLOW; out_free_tr_id: kfree(dg->stpg_transport_id); out_free_event: kfree(event_entry); goto out_free; } EXPORT_SYMBOL_GPL(scst_tg_set_group_info);