diff options
Diffstat (limited to 'drivers/target/sbp/sbp_target.c')
| -rw-r--r-- | drivers/target/sbp/sbp_target.c | 2621 | 
1 files changed, 2621 insertions, 0 deletions
diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c new file mode 100644 index 00000000000..37c609898f8 --- /dev/null +++ b/drivers/target/sbp/sbp_target.c @@ -0,0 +1,2621 @@ +/* + * SBP2 target driver (SCSI over IEEE1394 in target mode) + * + * Copyright (C) 2011  Chris Boot <bootc@bootc.net> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define KMSG_COMPONENT "sbp_target" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> +#include <asm/unaligned.h> + +#include "sbp_target.h" + +/* Local pointer to allocated TCM configfs fabric module */ +static struct target_fabric_configfs *sbp_fabric_configfs; + +/* FireWire address region for management and command block address handlers */ +static const struct fw_address_region sbp_register_region = { +	.start	= CSR_REGISTER_BASE + 0x10000, +	.end	= 0x1000000000000ULL, +}; + +static const u32 sbp_unit_directory_template[] = { +	0x1200609e, /* unit_specifier_id: NCITS/T10 */ +	0x13010483, /* unit_sw_version: 1155D Rev 4 */ +	0x3800609e, /* command_set_specifier_id: NCITS/T10 */ +	0x390104d8, /* command_set: SPC-2 */ +	0x3b000000, /* command_set_revision: 0 */ +	0x3c000001, /* firmware_revision: 1 */ +}; + +#define SESSION_MAINTENANCE_INTERVAL HZ + +static atomic_t login_id = ATOMIC_INIT(0); + +static void session_maintenance_work(struct work_struct *); +static int sbp_run_transaction(struct fw_card *, int, int, int, int, +		unsigned long long, void *, size_t); + +static int read_peer_guid(u64 *guid, const struct sbp_management_request *req) +{ +	int ret; +	__be32 high, low; + +	ret = sbp_run_transaction(req->card, TCODE_READ_QUADLET_REQUEST, +			req->node_addr, req->generation, req->speed, +			(CSR_REGISTER_BASE | CSR_CONFIG_ROM) + 3 * 4, +			&high, sizeof(high)); +	if (ret != RCODE_COMPLETE) +		return ret; + +	ret = sbp_run_transaction(req->card, TCODE_READ_QUADLET_REQUEST, +			req->node_addr, req->generation, req->speed, +			(CSR_REGISTER_BASE | CSR_CONFIG_ROM) + 4 * 4, +			&low, sizeof(low)); +	if (ret != RCODE_COMPLETE) +		return ret; + +	*guid = (u64)be32_to_cpu(high) << 32 | be32_to_cpu(low); + +	return RCODE_COMPLETE; +} + +static struct sbp_session *sbp_session_find_by_guid( +	struct sbp_tpg *tpg, u64 guid) +{ +	struct se_session *se_sess; +	struct sbp_session *sess, *found = NULL; + +	spin_lock_bh(&tpg->se_tpg.session_lock); +	list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { +		sess = se_sess->fabric_sess_ptr; +		if (sess->guid == guid) +			found = sess; +	} +	spin_unlock_bh(&tpg->se_tpg.session_lock); + +	return found; +} + +static struct sbp_login_descriptor *sbp_login_find_by_lun( +		struct sbp_session *session, struct se_lun *lun) +{ +	struct sbp_login_descriptor *login, *found = NULL; + +	spin_lock_bh(&session->lock); +	list_for_each_entry(login, &session->login_list, link) { +		if (login->lun == lun) +			found = login; +	} +	spin_unlock_bh(&session->lock); + +	return found; +} + +static int sbp_login_count_all_by_lun( +		struct sbp_tpg *tpg, +		struct se_lun *lun, +		int exclusive) +{ +	struct se_session *se_sess; +	struct sbp_session *sess; +	struct sbp_login_descriptor *login; +	int count = 0; + +	spin_lock_bh(&tpg->se_tpg.session_lock); +	list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { +		sess = se_sess->fabric_sess_ptr; + +		spin_lock_bh(&sess->lock); +		list_for_each_entry(login, &sess->login_list, link) { +			if (login->lun != lun) +				continue; + +			if (!exclusive || login->exclusive) +				count++; +		} +		spin_unlock_bh(&sess->lock); +	} +	spin_unlock_bh(&tpg->se_tpg.session_lock); + +	return count; +} + +static struct sbp_login_descriptor *sbp_login_find_by_id( +	struct sbp_tpg *tpg, int login_id) +{ +	struct se_session *se_sess; +	struct sbp_session *sess; +	struct sbp_login_descriptor *login, *found = NULL; + +	spin_lock_bh(&tpg->se_tpg.session_lock); +	list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { +		sess = se_sess->fabric_sess_ptr; + +		spin_lock_bh(&sess->lock); +		list_for_each_entry(login, &sess->login_list, link) { +			if (login->login_id == login_id) +				found = login; +		} +		spin_unlock_bh(&sess->lock); +	} +	spin_unlock_bh(&tpg->se_tpg.session_lock); + +	return found; +} + +static struct se_lun *sbp_get_lun_from_tpg(struct sbp_tpg *tpg, int lun) +{ +	struct se_portal_group *se_tpg = &tpg->se_tpg; +	struct se_lun *se_lun; + +	if (lun >= TRANSPORT_MAX_LUNS_PER_TPG) +		return ERR_PTR(-EINVAL); + +	spin_lock(&se_tpg->tpg_lun_lock); +	se_lun = se_tpg->tpg_lun_list[lun]; + +	if (se_lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) +		se_lun = ERR_PTR(-ENODEV); + +	spin_unlock(&se_tpg->tpg_lun_lock); + +	return se_lun; +} + +static struct sbp_session *sbp_session_create( +		struct sbp_tpg *tpg, +		u64 guid) +{ +	struct sbp_session *sess; +	int ret; +	char guid_str[17]; +	struct se_node_acl *se_nacl; + +	sess = kmalloc(sizeof(*sess), GFP_KERNEL); +	if (!sess) { +		pr_err("failed to allocate session descriptor\n"); +		return ERR_PTR(-ENOMEM); +	} + +	sess->se_sess = transport_init_session(); +	if (IS_ERR(sess->se_sess)) { +		pr_err("failed to init se_session\n"); + +		ret = PTR_ERR(sess->se_sess); +		kfree(sess); +		return ERR_PTR(ret); +	} + +	snprintf(guid_str, sizeof(guid_str), "%016llx", guid); + +	se_nacl = core_tpg_check_initiator_node_acl(&tpg->se_tpg, guid_str); +	if (!se_nacl) { +		pr_warn("Node ACL not found for %s\n", guid_str); + +		transport_free_session(sess->se_sess); +		kfree(sess); + +		return ERR_PTR(-EPERM); +	} + +	sess->se_sess->se_node_acl = se_nacl; + +	spin_lock_init(&sess->lock); +	INIT_LIST_HEAD(&sess->login_list); +	INIT_DELAYED_WORK(&sess->maint_work, session_maintenance_work); + +	sess->guid = guid; + +	transport_register_session(&tpg->se_tpg, se_nacl, sess->se_sess, sess); + +	return sess; +} + +static void sbp_session_release(struct sbp_session *sess, bool cancel_work) +{ +	spin_lock_bh(&sess->lock); +	if (!list_empty(&sess->login_list)) { +		spin_unlock_bh(&sess->lock); +		return; +	} +	spin_unlock_bh(&sess->lock); + +	if (cancel_work) +		cancel_delayed_work_sync(&sess->maint_work); + +	transport_deregister_session_configfs(sess->se_sess); +	transport_deregister_session(sess->se_sess); + +	if (sess->card) +		fw_card_put(sess->card); + +	kfree(sess); +} + +static void sbp_target_agent_unregister(struct sbp_target_agent *); + +static void sbp_login_release(struct sbp_login_descriptor *login, +	bool cancel_work) +{ +	struct sbp_session *sess = login->sess; + +	/* FIXME: abort/wait on tasks */ + +	sbp_target_agent_unregister(login->tgt_agt); + +	if (sess) { +		spin_lock_bh(&sess->lock); +		list_del(&login->link); +		spin_unlock_bh(&sess->lock); + +		sbp_session_release(sess, cancel_work); +	} + +	kfree(login); +} + +static struct sbp_target_agent *sbp_target_agent_register( +	struct sbp_login_descriptor *); + +static void sbp_management_request_login( +	struct sbp_management_agent *agent, struct sbp_management_request *req, +	int *status_data_size) +{ +	struct sbp_tport *tport = agent->tport; +	struct sbp_tpg *tpg = tport->tpg; +	struct se_lun *se_lun; +	int ret; +	u64 guid; +	struct sbp_session *sess; +	struct sbp_login_descriptor *login; +	struct sbp_login_response_block *response; +	int login_response_len; + +	se_lun = sbp_get_lun_from_tpg(tpg, +			LOGIN_ORB_LUN(be32_to_cpu(req->orb.misc))); +	if (IS_ERR(se_lun)) { +		pr_notice("login to unknown LUN: %d\n", +			LOGIN_ORB_LUN(be32_to_cpu(req->orb.misc))); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_LUN_NOTSUPP)); +		return; +	} + +	ret = read_peer_guid(&guid, req); +	if (ret != RCODE_COMPLETE) { +		pr_warn("failed to read peer GUID: %d\n", ret); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); +		return; +	} + +	pr_notice("mgt_agent LOGIN to LUN %d from %016llx\n", +		se_lun->unpacked_lun, guid); + +	sess = sbp_session_find_by_guid(tpg, guid); +	if (sess) { +		login = sbp_login_find_by_lun(sess, se_lun); +		if (login) { +			pr_notice("initiator already logged-in\n"); + +			/* +			 * SBP-2 R4 says we should return access denied, but +			 * that can confuse initiators. Instead we need to +			 * treat this like a reconnect, but send the login +			 * response block like a fresh login. +			 * +			 * This is required particularly in the case of Apple +			 * devices booting off the FireWire target, where +			 * the firmware has an active login to the target. When +			 * the OS takes control of the session it issues its own +			 * LOGIN rather than a RECONNECT. To avoid the machine +			 * waiting until the reconnect_hold expires, we can skip +			 * the ACCESS_DENIED errors to speed things up. +			 */ + +			goto already_logged_in; +		} +	} + +	/* +	 * check exclusive bit in login request +	 * reject with access_denied if any logins present +	 */ +	if (LOGIN_ORB_EXCLUSIVE(be32_to_cpu(req->orb.misc)) && +			sbp_login_count_all_by_lun(tpg, se_lun, 0)) { +		pr_warn("refusing exclusive login with other active logins\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); +		return; +	} + +	/* +	 * check exclusive bit in any existing login descriptor +	 * reject with access_denied if any exclusive logins present +	 */ +	if (sbp_login_count_all_by_lun(tpg, se_lun, 1)) { +		pr_warn("refusing login while another exclusive login present\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); +		return; +	} + +	/* +	 * check we haven't exceeded the number of allowed logins +	 * reject with resources_unavailable if we have +	 */ +	if (sbp_login_count_all_by_lun(tpg, se_lun, 0) >= +			tport->max_logins_per_lun) { +		pr_warn("max number of logins reached\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); +		return; +	} + +	if (!sess) { +		sess = sbp_session_create(tpg, guid); +		if (IS_ERR(sess)) { +			switch (PTR_ERR(sess)) { +			case -EPERM: +				ret = SBP_STATUS_ACCESS_DENIED; +				break; +			default: +				ret = SBP_STATUS_RESOURCES_UNAVAIL; +				break; +			} + +			req->status.status = cpu_to_be32( +				STATUS_BLOCK_RESP( +					STATUS_RESP_REQUEST_COMPLETE) | +				STATUS_BLOCK_SBP_STATUS(ret)); +			return; +		} + +		sess->node_id = req->node_addr; +		sess->card = fw_card_get(req->card); +		sess->generation = req->generation; +		sess->speed = req->speed; + +		schedule_delayed_work(&sess->maint_work, +				SESSION_MAINTENANCE_INTERVAL); +	} + +	/* only take the latest reconnect_hold into account */ +	sess->reconnect_hold = min( +		1 << LOGIN_ORB_RECONNECT(be32_to_cpu(req->orb.misc)), +		tport->max_reconnect_timeout) - 1; + +	login = kmalloc(sizeof(*login), GFP_KERNEL); +	if (!login) { +		pr_err("failed to allocate login descriptor\n"); + +		sbp_session_release(sess, true); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); +		return; +	} + +	login->sess = sess; +	login->lun = se_lun; +	login->status_fifo_addr = sbp2_pointer_to_addr(&req->orb.status_fifo); +	login->exclusive = LOGIN_ORB_EXCLUSIVE(be32_to_cpu(req->orb.misc)); +	login->login_id = atomic_inc_return(&login_id); + +	login->tgt_agt = sbp_target_agent_register(login); +	if (IS_ERR(login->tgt_agt)) { +		ret = PTR_ERR(login->tgt_agt); +		pr_err("failed to map command block handler: %d\n", ret); + +		sbp_session_release(sess, true); +		kfree(login); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); +		return; +	} + +	spin_lock_bh(&sess->lock); +	list_add_tail(&login->link, &sess->login_list); +	spin_unlock_bh(&sess->lock); + +already_logged_in: +	response = kzalloc(sizeof(*response), GFP_KERNEL); +	if (!response) { +		pr_err("failed to allocate login response block\n"); + +		sbp_login_release(login, true); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); +		return; +	} + +	login_response_len = clamp_val( +			LOGIN_ORB_RESPONSE_LENGTH(be32_to_cpu(req->orb.length)), +			12, sizeof(*response)); +	response->misc = cpu_to_be32( +		((login_response_len & 0xffff) << 16) | +		(login->login_id & 0xffff)); +	response->reconnect_hold = cpu_to_be32(sess->reconnect_hold & 0xffff); +	addr_to_sbp2_pointer(login->tgt_agt->handler.offset, +		&response->command_block_agent); + +	ret = sbp_run_transaction(sess->card, TCODE_WRITE_BLOCK_REQUEST, +		sess->node_id, sess->generation, sess->speed, +		sbp2_pointer_to_addr(&req->orb.ptr2), response, +		login_response_len); +	if (ret != RCODE_COMPLETE) { +		pr_debug("failed to write login response block: %x\n", ret); + +		kfree(response); +		sbp_login_release(login, true); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); +		return; +	} + +	kfree(response); + +	req->status.status = cpu_to_be32( +		STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +		STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void sbp_management_request_query_logins( +	struct sbp_management_agent *agent, struct sbp_management_request *req, +	int *status_data_size) +{ +	pr_notice("QUERY LOGINS not implemented\n"); +	/* FIXME: implement */ + +	req->status.status = cpu_to_be32( +		STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +		STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); +} + +static void sbp_management_request_reconnect( +	struct sbp_management_agent *agent, struct sbp_management_request *req, +	int *status_data_size) +{ +	struct sbp_tport *tport = agent->tport; +	struct sbp_tpg *tpg = tport->tpg; +	int ret; +	u64 guid; +	struct sbp_login_descriptor *login; + +	ret = read_peer_guid(&guid, req); +	if (ret != RCODE_COMPLETE) { +		pr_warn("failed to read peer GUID: %d\n", ret); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); +		return; +	} + +	pr_notice("mgt_agent RECONNECT from %016llx\n", guid); + +	login = sbp_login_find_by_id(tpg, +		RECONNECT_ORB_LOGIN_ID(be32_to_cpu(req->orb.misc))); + +	if (!login) { +		pr_err("mgt_agent RECONNECT unknown login ID\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); +		return; +	} + +	if (login->sess->guid != guid) { +		pr_err("mgt_agent RECONNECT login GUID doesn't match\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); +		return; +	} + +	spin_lock_bh(&login->sess->lock); +	if (login->sess->card) +		fw_card_put(login->sess->card); + +	/* update the node details */ +	login->sess->generation = req->generation; +	login->sess->node_id = req->node_addr; +	login->sess->card = fw_card_get(req->card); +	login->sess->speed = req->speed; +	spin_unlock_bh(&login->sess->lock); + +	req->status.status = cpu_to_be32( +		STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +		STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void sbp_management_request_logout( +	struct sbp_management_agent *agent, struct sbp_management_request *req, +	int *status_data_size) +{ +	struct sbp_tport *tport = agent->tport; +	struct sbp_tpg *tpg = tport->tpg; +	int login_id; +	struct sbp_login_descriptor *login; + +	login_id = LOGOUT_ORB_LOGIN_ID(be32_to_cpu(req->orb.misc)); + +	login = sbp_login_find_by_id(tpg, login_id); +	if (!login) { +		pr_warn("cannot find login: %d\n", login_id); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_LOGIN_ID_UNKNOWN)); +		return; +	} + +	pr_info("mgt_agent LOGOUT from LUN %d session %d\n", +		login->lun->unpacked_lun, login->login_id); + +	if (req->node_addr != login->sess->node_id) { +		pr_warn("logout from different node ID\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); +		return; +	} + +	sbp_login_release(login, true); + +	req->status.status = cpu_to_be32( +		STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +		STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void session_check_for_reset(struct sbp_session *sess) +{ +	bool card_valid = false; + +	spin_lock_bh(&sess->lock); + +	if (sess->card) { +		spin_lock_irq(&sess->card->lock); +		card_valid = (sess->card->local_node != NULL); +		spin_unlock_irq(&sess->card->lock); + +		if (!card_valid) { +			fw_card_put(sess->card); +			sess->card = NULL; +		} +	} + +	if (!card_valid || (sess->generation != sess->card->generation)) { +		pr_info("Waiting for reconnect from node: %016llx\n", +				sess->guid); + +		sess->node_id = -1; +		sess->reconnect_expires = get_jiffies_64() + +			((sess->reconnect_hold + 1) * HZ); +	} + +	spin_unlock_bh(&sess->lock); +} + +static void session_reconnect_expired(struct sbp_session *sess) +{ +	struct sbp_login_descriptor *login, *temp; +	LIST_HEAD(login_list); + +	pr_info("Reconnect timer expired for node: %016llx\n", sess->guid); + +	spin_lock_bh(&sess->lock); +	list_for_each_entry_safe(login, temp, &sess->login_list, link) { +		login->sess = NULL; +		list_del(&login->link); +		list_add_tail(&login->link, &login_list); +	} +	spin_unlock_bh(&sess->lock); + +	list_for_each_entry_safe(login, temp, &login_list, link) { +		list_del(&login->link); +		sbp_login_release(login, false); +	} + +	sbp_session_release(sess, false); +} + +static void session_maintenance_work(struct work_struct *work) +{ +	struct sbp_session *sess = container_of(work, struct sbp_session, +			maint_work.work); + +	/* could be called while tearing down the session */ +	spin_lock_bh(&sess->lock); +	if (list_empty(&sess->login_list)) { +		spin_unlock_bh(&sess->lock); +		return; +	} +	spin_unlock_bh(&sess->lock); + +	if (sess->node_id != -1) { +		/* check for bus reset and make node_id invalid */ +		session_check_for_reset(sess); + +		schedule_delayed_work(&sess->maint_work, +				SESSION_MAINTENANCE_INTERVAL); +	} else if (!time_after64(get_jiffies_64(), sess->reconnect_expires)) { +		/* still waiting for reconnect */ +		schedule_delayed_work(&sess->maint_work, +				SESSION_MAINTENANCE_INTERVAL); +	} else { +		/* reconnect timeout has expired */ +		session_reconnect_expired(sess); +	} +} + +static int tgt_agent_rw_agent_state(struct fw_card *card, int tcode, void *data, +		struct sbp_target_agent *agent) +{ +	__be32 state; + +	switch (tcode) { +	case TCODE_READ_QUADLET_REQUEST: +		pr_debug("tgt_agent AGENT_STATE READ\n"); + +		spin_lock_bh(&agent->lock); +		state = cpu_to_be32(agent->state); +		spin_unlock_bh(&agent->lock); +		memcpy(data, &state, sizeof(state)); + +		return RCODE_COMPLETE; + +	case TCODE_WRITE_QUADLET_REQUEST: +		/* ignored */ +		return RCODE_COMPLETE; + +	default: +		return RCODE_TYPE_ERROR; +	} +} + +static int tgt_agent_rw_agent_reset(struct fw_card *card, int tcode, void *data, +		struct sbp_target_agent *agent) +{ +	switch (tcode) { +	case TCODE_WRITE_QUADLET_REQUEST: +		pr_debug("tgt_agent AGENT_RESET\n"); +		spin_lock_bh(&agent->lock); +		agent->state = AGENT_STATE_RESET; +		spin_unlock_bh(&agent->lock); +		return RCODE_COMPLETE; + +	default: +		return RCODE_TYPE_ERROR; +	} +} + +static int tgt_agent_rw_orb_pointer(struct fw_card *card, int tcode, void *data, +		struct sbp_target_agent *agent) +{ +	struct sbp2_pointer *ptr = data; + +	switch (tcode) { +	case TCODE_WRITE_BLOCK_REQUEST: +		spin_lock_bh(&agent->lock); +		if (agent->state != AGENT_STATE_SUSPENDED && +				agent->state != AGENT_STATE_RESET) { +			spin_unlock_bh(&agent->lock); +			pr_notice("Ignoring ORB_POINTER write while active.\n"); +			return RCODE_CONFLICT_ERROR; +		} +		agent->state = AGENT_STATE_ACTIVE; +		spin_unlock_bh(&agent->lock); + +		agent->orb_pointer = sbp2_pointer_to_addr(ptr); +		agent->doorbell = false; + +		pr_debug("tgt_agent ORB_POINTER write: 0x%llx\n", +				agent->orb_pointer); + +		queue_work(system_unbound_wq, &agent->work); + +		return RCODE_COMPLETE; + +	case TCODE_READ_BLOCK_REQUEST: +		pr_debug("tgt_agent ORB_POINTER READ\n"); +		spin_lock_bh(&agent->lock); +		addr_to_sbp2_pointer(agent->orb_pointer, ptr); +		spin_unlock_bh(&agent->lock); +		return RCODE_COMPLETE; + +	default: +		return RCODE_TYPE_ERROR; +	} +} + +static int tgt_agent_rw_doorbell(struct fw_card *card, int tcode, void *data, +		struct sbp_target_agent *agent) +{ +	switch (tcode) { +	case TCODE_WRITE_QUADLET_REQUEST: +		spin_lock_bh(&agent->lock); +		if (agent->state != AGENT_STATE_SUSPENDED) { +			spin_unlock_bh(&agent->lock); +			pr_debug("Ignoring DOORBELL while active.\n"); +			return RCODE_CONFLICT_ERROR; +		} +		agent->state = AGENT_STATE_ACTIVE; +		spin_unlock_bh(&agent->lock); + +		agent->doorbell = true; + +		pr_debug("tgt_agent DOORBELL\n"); + +		queue_work(system_unbound_wq, &agent->work); + +		return RCODE_COMPLETE; + +	case TCODE_READ_QUADLET_REQUEST: +		return RCODE_COMPLETE; + +	default: +		return RCODE_TYPE_ERROR; +	} +} + +static int tgt_agent_rw_unsolicited_status_enable(struct fw_card *card, +		int tcode, void *data, struct sbp_target_agent *agent) +{ +	switch (tcode) { +	case TCODE_WRITE_QUADLET_REQUEST: +		pr_debug("tgt_agent UNSOLICITED_STATUS_ENABLE\n"); +		/* ignored as we don't send unsolicited status */ +		return RCODE_COMPLETE; + +	case TCODE_READ_QUADLET_REQUEST: +		return RCODE_COMPLETE; + +	default: +		return RCODE_TYPE_ERROR; +	} +} + +static void tgt_agent_rw(struct fw_card *card, struct fw_request *request, +		int tcode, int destination, int source, int generation, +		unsigned long long offset, void *data, size_t length, +		void *callback_data) +{ +	struct sbp_target_agent *agent = callback_data; +	struct sbp_session *sess = agent->login->sess; +	int sess_gen, sess_node, rcode; + +	spin_lock_bh(&sess->lock); +	sess_gen = sess->generation; +	sess_node = sess->node_id; +	spin_unlock_bh(&sess->lock); + +	if (generation != sess_gen) { +		pr_notice("ignoring request with wrong generation\n"); +		rcode = RCODE_TYPE_ERROR; +		goto out; +	} + +	if (source != sess_node) { +		pr_notice("ignoring request from foreign node (%x != %x)\n", +				source, sess_node); +		rcode = RCODE_TYPE_ERROR; +		goto out; +	} + +	/* turn offset into the offset from the start of the block */ +	offset -= agent->handler.offset; + +	if (offset == 0x00 && length == 4) { +		/* AGENT_STATE */ +		rcode = tgt_agent_rw_agent_state(card, tcode, data, agent); +	} else if (offset == 0x04 && length == 4) { +		/* AGENT_RESET */ +		rcode = tgt_agent_rw_agent_reset(card, tcode, data, agent); +	} else if (offset == 0x08 && length == 8) { +		/* ORB_POINTER */ +		rcode = tgt_agent_rw_orb_pointer(card, tcode, data, agent); +	} else if (offset == 0x10 && length == 4) { +		/* DOORBELL */ +		rcode = tgt_agent_rw_doorbell(card, tcode, data, agent); +	} else if (offset == 0x14 && length == 4) { +		/* UNSOLICITED_STATUS_ENABLE */ +		rcode = tgt_agent_rw_unsolicited_status_enable(card, tcode, +				data, agent); +	} else { +		rcode = RCODE_ADDRESS_ERROR; +	} + +out: +	fw_send_response(card, request, rcode); +} + +static void sbp_handle_command(struct sbp_target_request *); +static int sbp_send_status(struct sbp_target_request *); +static void sbp_free_request(struct sbp_target_request *); + +static void tgt_agent_process_work(struct work_struct *work) +{ +	struct sbp_target_request *req = +		container_of(work, struct sbp_target_request, work); + +	pr_debug("tgt_orb ptr:0x%llx next_ORB:0x%llx data_descriptor:0x%llx misc:0x%x\n", +			req->orb_pointer, +			sbp2_pointer_to_addr(&req->orb.next_orb), +			sbp2_pointer_to_addr(&req->orb.data_descriptor), +			be32_to_cpu(req->orb.misc)); + +	if (req->orb_pointer >> 32) +		pr_debug("ORB with high bits set\n"); + +	switch (ORB_REQUEST_FORMAT(be32_to_cpu(req->orb.misc))) { +		case 0:/* Format specified by this standard */ +			sbp_handle_command(req); +			return; +		case 1: /* Reserved for future standardization */ +		case 2: /* Vendor-dependent */ +			req->status.status |= cpu_to_be32( +					STATUS_BLOCK_RESP( +						STATUS_RESP_REQUEST_COMPLETE) | +					STATUS_BLOCK_DEAD(0) | +					STATUS_BLOCK_LEN(1) | +					STATUS_BLOCK_SBP_STATUS( +						SBP_STATUS_REQ_TYPE_NOTSUPP)); +			sbp_send_status(req); +			sbp_free_request(req); +			return; +		case 3: /* Dummy ORB */ +			req->status.status |= cpu_to_be32( +					STATUS_BLOCK_RESP( +						STATUS_RESP_REQUEST_COMPLETE) | +					STATUS_BLOCK_DEAD(0) | +					STATUS_BLOCK_LEN(1) | +					STATUS_BLOCK_SBP_STATUS( +						SBP_STATUS_DUMMY_ORB_COMPLETE)); +			sbp_send_status(req); +			sbp_free_request(req); +			return; +		default: +			BUG(); +	} +} + +/* used to double-check we haven't been issued an AGENT_RESET */ +static inline bool tgt_agent_check_active(struct sbp_target_agent *agent) +{ +	bool active; + +	spin_lock_bh(&agent->lock); +	active = (agent->state == AGENT_STATE_ACTIVE); +	spin_unlock_bh(&agent->lock); + +	return active; +} + +static void tgt_agent_fetch_work(struct work_struct *work) +{ +	struct sbp_target_agent *agent = +		container_of(work, struct sbp_target_agent, work); +	struct sbp_session *sess = agent->login->sess; +	struct sbp_target_request *req; +	int ret; +	bool doorbell = agent->doorbell; +	u64 next_orb = agent->orb_pointer; + +	while (next_orb && tgt_agent_check_active(agent)) { +		req = kzalloc(sizeof(*req), GFP_KERNEL); +		if (!req) { +			spin_lock_bh(&agent->lock); +			agent->state = AGENT_STATE_DEAD; +			spin_unlock_bh(&agent->lock); +			return; +		} + +		req->login = agent->login; +		req->orb_pointer = next_orb; + +		req->status.status = cpu_to_be32(STATUS_BLOCK_ORB_OFFSET_HIGH( +					req->orb_pointer >> 32)); +		req->status.orb_low = cpu_to_be32( +				req->orb_pointer & 0xfffffffc); + +		/* read in the ORB */ +		ret = sbp_run_transaction(sess->card, TCODE_READ_BLOCK_REQUEST, +				sess->node_id, sess->generation, sess->speed, +				req->orb_pointer, &req->orb, sizeof(req->orb)); +		if (ret != RCODE_COMPLETE) { +			pr_debug("tgt_orb fetch failed: %x\n", ret); +			req->status.status |= cpu_to_be32( +					STATUS_BLOCK_SRC( +						STATUS_SRC_ORB_FINISHED) | +					STATUS_BLOCK_RESP( +						STATUS_RESP_TRANSPORT_FAILURE) | +					STATUS_BLOCK_DEAD(1) | +					STATUS_BLOCK_LEN(1) | +					STATUS_BLOCK_SBP_STATUS( +						SBP_STATUS_UNSPECIFIED_ERROR)); +			spin_lock_bh(&agent->lock); +			agent->state = AGENT_STATE_DEAD; +			spin_unlock_bh(&agent->lock); + +			sbp_send_status(req); +			sbp_free_request(req); +			return; +		} + +		/* check the next_ORB field */ +		if (be32_to_cpu(req->orb.next_orb.high) & 0x80000000) { +			next_orb = 0; +			req->status.status |= cpu_to_be32(STATUS_BLOCK_SRC( +						STATUS_SRC_ORB_FINISHED)); +		} else { +			next_orb = sbp2_pointer_to_addr(&req->orb.next_orb); +			req->status.status |= cpu_to_be32(STATUS_BLOCK_SRC( +						STATUS_SRC_ORB_CONTINUING)); +		} + +		if (tgt_agent_check_active(agent) && !doorbell) { +			INIT_WORK(&req->work, tgt_agent_process_work); +			queue_work(system_unbound_wq, &req->work); +		} else { +			/* don't process this request, just check next_ORB */ +			sbp_free_request(req); +		} + +		spin_lock_bh(&agent->lock); +		doorbell = agent->doorbell = false; + +		/* check if we should carry on processing */ +		if (next_orb) +			agent->orb_pointer = next_orb; +		else +			agent->state = AGENT_STATE_SUSPENDED; + +		spin_unlock_bh(&agent->lock); +	}; +} + +static struct sbp_target_agent *sbp_target_agent_register( +		struct sbp_login_descriptor *login) +{ +	struct sbp_target_agent *agent; +	int ret; + +	agent = kmalloc(sizeof(*agent), GFP_KERNEL); +	if (!agent) +		return ERR_PTR(-ENOMEM); + +	spin_lock_init(&agent->lock); + +	agent->handler.length = 0x20; +	agent->handler.address_callback = tgt_agent_rw; +	agent->handler.callback_data = agent; + +	agent->login = login; +	agent->state = AGENT_STATE_RESET; +	INIT_WORK(&agent->work, tgt_agent_fetch_work); +	agent->orb_pointer = 0; +	agent->doorbell = false; + +	ret = fw_core_add_address_handler(&agent->handler, +			&sbp_register_region); +	if (ret < 0) { +		kfree(agent); +		return ERR_PTR(ret); +	} + +	return agent; +} + +static void sbp_target_agent_unregister(struct sbp_target_agent *agent) +{ +	fw_core_remove_address_handler(&agent->handler); +	cancel_work_sync(&agent->work); +	kfree(agent); +} + +/* + * Simple wrapper around fw_run_transaction that retries the transaction several + * times in case of failure, with an exponential backoff. + */ +static int sbp_run_transaction(struct fw_card *card, int tcode, int destination_id, +		int generation, int speed, unsigned long long offset, +		void *payload, size_t length) +{ +	int attempt, ret, delay; + +	for (attempt = 1; attempt <= 5; attempt++) { +		ret = fw_run_transaction(card, tcode, destination_id, +				generation, speed, offset, payload, length); + +		switch (ret) { +		case RCODE_COMPLETE: +		case RCODE_TYPE_ERROR: +		case RCODE_ADDRESS_ERROR: +		case RCODE_GENERATION: +			return ret; + +		default: +			delay = 5 * attempt * attempt; +			usleep_range(delay, delay * 2); +		} +	} + +	return ret; +} + +/* + * Wrapper around sbp_run_transaction that gets the card, destination, + * generation and speed out of the request's session. + */ +static int sbp_run_request_transaction(struct sbp_target_request *req, +		int tcode, unsigned long long offset, void *payload, +		size_t length) +{ +	struct sbp_login_descriptor *login = req->login; +	struct sbp_session *sess = login->sess; +	struct fw_card *card; +	int node_id, generation, speed, ret; + +	spin_lock_bh(&sess->lock); +	card = fw_card_get(sess->card); +	node_id = sess->node_id; +	generation = sess->generation; +	speed = sess->speed; +	spin_unlock_bh(&sess->lock); + +	ret = sbp_run_transaction(card, tcode, node_id, generation, speed, +			offset, payload, length); + +	fw_card_put(card); + +	return ret; +} + +static int sbp_fetch_command(struct sbp_target_request *req) +{ +	int ret, cmd_len, copy_len; + +	cmd_len = scsi_command_size(req->orb.command_block); + +	req->cmd_buf = kmalloc(cmd_len, GFP_KERNEL); +	if (!req->cmd_buf) +		return -ENOMEM; + +	memcpy(req->cmd_buf, req->orb.command_block, +		min_t(int, cmd_len, sizeof(req->orb.command_block))); + +	if (cmd_len > sizeof(req->orb.command_block)) { +		pr_debug("sbp_fetch_command: filling in long command\n"); +		copy_len = cmd_len - sizeof(req->orb.command_block); + +		ret = sbp_run_request_transaction(req, +				TCODE_READ_BLOCK_REQUEST, +				req->orb_pointer + sizeof(req->orb), +				req->cmd_buf + sizeof(req->orb.command_block), +				copy_len); +		if (ret != RCODE_COMPLETE) +			return -EIO; +	} + +	return 0; +} + +static int sbp_fetch_page_table(struct sbp_target_request *req) +{ +	int pg_tbl_sz, ret; +	struct sbp_page_table_entry *pg_tbl; + +	if (!CMDBLK_ORB_PG_TBL_PRESENT(be32_to_cpu(req->orb.misc))) +		return 0; + +	pg_tbl_sz = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)) * +		sizeof(struct sbp_page_table_entry); + +	pg_tbl = kmalloc(pg_tbl_sz, GFP_KERNEL); +	if (!pg_tbl) +		return -ENOMEM; + +	ret = sbp_run_request_transaction(req, TCODE_READ_BLOCK_REQUEST, +			sbp2_pointer_to_addr(&req->orb.data_descriptor), +			pg_tbl, pg_tbl_sz); +	if (ret != RCODE_COMPLETE) { +		kfree(pg_tbl); +		return -EIO; +	} + +	req->pg_tbl = pg_tbl; +	return 0; +} + +static void sbp_calc_data_length_direction(struct sbp_target_request *req, +	u32 *data_len, enum dma_data_direction *data_dir) +{ +	int data_size, direction, idx; + +	data_size = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)); +	direction = CMDBLK_ORB_DIRECTION(be32_to_cpu(req->orb.misc)); + +	if (!data_size) { +		*data_len = 0; +		*data_dir = DMA_NONE; +		return; +	} + +	*data_dir = direction ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + +	if (req->pg_tbl) { +		*data_len = 0; +		for (idx = 0; idx < data_size; idx++) { +			*data_len += be16_to_cpu( +					req->pg_tbl[idx].segment_length); +		} +	} else { +		*data_len = data_size; +	} +} + +static void sbp_handle_command(struct sbp_target_request *req) +{ +	struct sbp_login_descriptor *login = req->login; +	struct sbp_session *sess = login->sess; +	int ret, unpacked_lun; +	u32 data_length; +	enum dma_data_direction data_dir; + +	ret = sbp_fetch_command(req); +	if (ret) { +		pr_debug("sbp_handle_command: fetch command failed: %d\n", ret); +		req->status.status |= cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_DEAD(0) | +			STATUS_BLOCK_LEN(1) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); +		sbp_send_status(req); +		sbp_free_request(req); +		return; +	} + +	ret = sbp_fetch_page_table(req); +	if (ret) { +		pr_debug("sbp_handle_command: fetch page table failed: %d\n", +			ret); +		req->status.status |= cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_DEAD(0) | +			STATUS_BLOCK_LEN(1) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); +		sbp_send_status(req); +		sbp_free_request(req); +		return; +	} + +	unpacked_lun = req->login->lun->unpacked_lun; +	sbp_calc_data_length_direction(req, &data_length, &data_dir); + +	pr_debug("sbp_handle_command ORB:0x%llx unpacked_lun:%d data_len:%d data_dir:%d\n", +			req->orb_pointer, unpacked_lun, data_length, data_dir); + +	target_submit_cmd(&req->se_cmd, sess->se_sess, req->cmd_buf, +			req->sense_buf, unpacked_lun, data_length, +			MSG_SIMPLE_TAG, data_dir, 0); +} + +/* + * DMA_TO_DEVICE = read from initiator (SCSI WRITE) + * DMA_FROM_DEVICE = write to initiator (SCSI READ) + */ +static int sbp_rw_data(struct sbp_target_request *req) +{ +	struct sbp_session *sess = req->login->sess; +	int tcode, sg_miter_flags, max_payload, pg_size, speed, node_id, +		generation, num_pte, length, tfr_length, +		rcode = RCODE_COMPLETE; +	struct sbp_page_table_entry *pte; +	unsigned long long offset; +	struct fw_card *card; +	struct sg_mapping_iter iter; + +	if (req->se_cmd.data_direction == DMA_FROM_DEVICE) { +		tcode = TCODE_WRITE_BLOCK_REQUEST; +		sg_miter_flags = SG_MITER_FROM_SG; +	} else { +		tcode = TCODE_READ_BLOCK_REQUEST; +		sg_miter_flags = SG_MITER_TO_SG; +	} + +	max_payload = 4 << CMDBLK_ORB_MAX_PAYLOAD(be32_to_cpu(req->orb.misc)); +	speed = CMDBLK_ORB_SPEED(be32_to_cpu(req->orb.misc)); + +	pg_size = CMDBLK_ORB_PG_SIZE(be32_to_cpu(req->orb.misc)); +	if (pg_size) { +		pr_err("sbp_run_transaction: page size ignored\n"); +		pg_size = 0x100 << pg_size; +	} + +	spin_lock_bh(&sess->lock); +	card = fw_card_get(sess->card); +	node_id = sess->node_id; +	generation = sess->generation; +	spin_unlock_bh(&sess->lock); + +	if (req->pg_tbl) { +		pte = req->pg_tbl; +		num_pte = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)); + +		offset = 0; +		length = 0; +	} else { +		pte = NULL; +		num_pte = 0; + +		offset = sbp2_pointer_to_addr(&req->orb.data_descriptor); +		length = req->se_cmd.data_length; +	} + +	sg_miter_start(&iter, req->se_cmd.t_data_sg, req->se_cmd.t_data_nents, +		sg_miter_flags); + +	while (length || num_pte) { +		if (!length) { +			offset = (u64)be16_to_cpu(pte->segment_base_hi) << 32 | +				be32_to_cpu(pte->segment_base_lo); +			length = be16_to_cpu(pte->segment_length); + +			pte++; +			num_pte--; +		} + +		sg_miter_next(&iter); + +		tfr_length = min3(length, max_payload, (int)iter.length); + +		/* FIXME: take page_size into account */ + +		rcode = sbp_run_transaction(card, tcode, node_id, +				generation, speed, +				offset, iter.addr, tfr_length); + +		if (rcode != RCODE_COMPLETE) +			break; + +		length -= tfr_length; +		offset += tfr_length; +		iter.consumed = tfr_length; +	} + +	sg_miter_stop(&iter); +	fw_card_put(card); + +	if (rcode == RCODE_COMPLETE) { +		WARN_ON(length != 0); +		return 0; +	} else { +		return -EIO; +	} +} + +static int sbp_send_status(struct sbp_target_request *req) +{ +	int ret, length; +	struct sbp_login_descriptor *login = req->login; + +	length = (((be32_to_cpu(req->status.status) >> 24) & 0x07) + 1) * 4; + +	ret = sbp_run_request_transaction(req, TCODE_WRITE_BLOCK_REQUEST, +			login->status_fifo_addr, &req->status, length); +	if (ret != RCODE_COMPLETE) { +		pr_debug("sbp_send_status: write failed: 0x%x\n", ret); +		return -EIO; +	} + +	pr_debug("sbp_send_status: status write complete for ORB: 0x%llx\n", +			req->orb_pointer); + +	return 0; +} + +static void sbp_sense_mangle(struct sbp_target_request *req) +{ +	struct se_cmd *se_cmd = &req->se_cmd; +	u8 *sense = req->sense_buf; +	u8 *status = req->status.data; + +	WARN_ON(se_cmd->scsi_sense_length < 18); + +	switch (sense[0] & 0x7f) { 		/* sfmt */ +	case 0x70: /* current, fixed */ +		status[0] = 0 << 6; +		break; +	case 0x71: /* deferred, fixed */ +		status[0] = 1 << 6; +		break; +	case 0x72: /* current, descriptor */ +	case 0x73: /* deferred, descriptor */ +	default: +		/* +		 * TODO: SBP-3 specifies what we should do with descriptor +		 * format sense data +		 */ +		pr_err("sbp_send_sense: unknown sense format: 0x%x\n", +			sense[0]); +		req->status.status |= cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_DEAD(0) | +			STATUS_BLOCK_LEN(1) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQUEST_ABORTED)); +		return; +	} + +	status[0] |= se_cmd->scsi_status & 0x3f;/* status */ +	status[1] = +		(sense[0] & 0x80) |		/* valid */ +		((sense[2] & 0xe0) >> 1) |	/* mark, eom, ili */ +		(sense[2] & 0x0f);		/* sense_key */ +	status[2] = se_cmd->scsi_asc;		/* sense_code */ +	status[3] = se_cmd->scsi_ascq;		/* sense_qualifier */ + +	/* information */ +	status[4] = sense[3]; +	status[5] = sense[4]; +	status[6] = sense[5]; +	status[7] = sense[6]; + +	/* CDB-dependent */ +	status[8] = sense[8]; +	status[9] = sense[9]; +	status[10] = sense[10]; +	status[11] = sense[11]; + +	/* fru */ +	status[12] = sense[14]; + +	/* sense_key-dependent */ +	status[13] = sense[15]; +	status[14] = sense[16]; +	status[15] = sense[17]; + +	req->status.status |= cpu_to_be32( +		STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +		STATUS_BLOCK_DEAD(0) | +		STATUS_BLOCK_LEN(5) | +		STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static int sbp_send_sense(struct sbp_target_request *req) +{ +	struct se_cmd *se_cmd = &req->se_cmd; + +	if (se_cmd->scsi_sense_length) { +		sbp_sense_mangle(req); +	} else { +		req->status.status |= cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_DEAD(0) | +			STATUS_BLOCK_LEN(1) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +	} + +	return sbp_send_status(req); +} + +static void sbp_free_request(struct sbp_target_request *req) +{ +	kfree(req->pg_tbl); +	kfree(req->cmd_buf); +	kfree(req); +} + +static void sbp_mgt_agent_process(struct work_struct *work) +{ +	struct sbp_management_agent *agent = +		container_of(work, struct sbp_management_agent, work); +	struct sbp_management_request *req = agent->request; +	int ret; +	int status_data_len = 0; + +	/* fetch the ORB from the initiator */ +	ret = sbp_run_transaction(req->card, TCODE_READ_BLOCK_REQUEST, +		req->node_addr, req->generation, req->speed, +		agent->orb_offset, &req->orb, sizeof(req->orb)); +	if (ret != RCODE_COMPLETE) { +		pr_debug("mgt_orb fetch failed: %x\n", ret); +		goto out; +	} + +	pr_debug("mgt_orb ptr1:0x%llx ptr2:0x%llx misc:0x%x len:0x%x status_fifo:0x%llx\n", +		sbp2_pointer_to_addr(&req->orb.ptr1), +		sbp2_pointer_to_addr(&req->orb.ptr2), +		be32_to_cpu(req->orb.misc), be32_to_cpu(req->orb.length), +		sbp2_pointer_to_addr(&req->orb.status_fifo)); + +	if (!ORB_NOTIFY(be32_to_cpu(req->orb.misc)) || +		ORB_REQUEST_FORMAT(be32_to_cpu(req->orb.misc)) != 0) { +		pr_err("mgt_orb bad request\n"); +		goto out; +	} + +	switch (MANAGEMENT_ORB_FUNCTION(be32_to_cpu(req->orb.misc))) { +	case MANAGEMENT_ORB_FUNCTION_LOGIN: +		sbp_management_request_login(agent, req, &status_data_len); +		break; + +	case MANAGEMENT_ORB_FUNCTION_QUERY_LOGINS: +		sbp_management_request_query_logins(agent, req, +				&status_data_len); +		break; + +	case MANAGEMENT_ORB_FUNCTION_RECONNECT: +		sbp_management_request_reconnect(agent, req, &status_data_len); +		break; + +	case MANAGEMENT_ORB_FUNCTION_SET_PASSWORD: +		pr_notice("SET PASSWORD not implemented\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + +		break; + +	case MANAGEMENT_ORB_FUNCTION_LOGOUT: +		sbp_management_request_logout(agent, req, &status_data_len); +		break; + +	case MANAGEMENT_ORB_FUNCTION_ABORT_TASK: +		pr_notice("ABORT TASK not implemented\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + +		break; + +	case MANAGEMENT_ORB_FUNCTION_ABORT_TASK_SET: +		pr_notice("ABORT TASK SET not implemented\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + +		break; + +	case MANAGEMENT_ORB_FUNCTION_LOGICAL_UNIT_RESET: +		pr_notice("LOGICAL UNIT RESET not implemented\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + +		break; + +	case MANAGEMENT_ORB_FUNCTION_TARGET_RESET: +		pr_notice("TARGET RESET not implemented\n"); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + +		break; + +	default: +		pr_notice("unknown management function 0x%x\n", +			MANAGEMENT_ORB_FUNCTION(be32_to_cpu(req->orb.misc))); + +		req->status.status = cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + +		break; +	} + +	req->status.status |= cpu_to_be32( +		STATUS_BLOCK_SRC(1) | /* Response to ORB, next_ORB absent */ +		STATUS_BLOCK_LEN(DIV_ROUND_UP(status_data_len, 4) + 1) | +		STATUS_BLOCK_ORB_OFFSET_HIGH(agent->orb_offset >> 32)); +	req->status.orb_low = cpu_to_be32(agent->orb_offset); + +	/* write the status block back to the initiator */ +	ret = sbp_run_transaction(req->card, TCODE_WRITE_BLOCK_REQUEST, +		req->node_addr, req->generation, req->speed, +		sbp2_pointer_to_addr(&req->orb.status_fifo), +		&req->status, 8 + status_data_len); +	if (ret != RCODE_COMPLETE) { +		pr_debug("mgt_orb status write failed: %x\n", ret); +		goto out; +	} + +out: +	fw_card_put(req->card); +	kfree(req); + +	spin_lock_bh(&agent->lock); +	agent->state = MANAGEMENT_AGENT_STATE_IDLE; +	spin_unlock_bh(&agent->lock); +} + +static void sbp_mgt_agent_rw(struct fw_card *card, +	struct fw_request *request, int tcode, int destination, int source, +	int generation, unsigned long long offset, void *data, size_t length, +	void *callback_data) +{ +	struct sbp_management_agent *agent = callback_data; +	struct sbp2_pointer *ptr = data; +	int rcode = RCODE_ADDRESS_ERROR; + +	if (!agent->tport->enable) +		goto out; + +	if ((offset != agent->handler.offset) || (length != 8)) +		goto out; + +	if (tcode == TCODE_WRITE_BLOCK_REQUEST) { +		struct sbp_management_request *req; +		int prev_state; + +		spin_lock_bh(&agent->lock); +		prev_state = agent->state; +		agent->state = MANAGEMENT_AGENT_STATE_BUSY; +		spin_unlock_bh(&agent->lock); + +		if (prev_state == MANAGEMENT_AGENT_STATE_BUSY) { +			pr_notice("ignoring management request while busy\n"); +			rcode = RCODE_CONFLICT_ERROR; +			goto out; +		} + +		req = kzalloc(sizeof(*req), GFP_ATOMIC); +		if (!req) { +			rcode = RCODE_CONFLICT_ERROR; +			goto out; +		} + +		req->card = fw_card_get(card); +		req->generation = generation; +		req->node_addr = source; +		req->speed = fw_get_request_speed(request); + +		agent->orb_offset = sbp2_pointer_to_addr(ptr); +		agent->request = req; + +		queue_work(system_unbound_wq, &agent->work); +		rcode = RCODE_COMPLETE; +	} else if (tcode == TCODE_READ_BLOCK_REQUEST) { +		addr_to_sbp2_pointer(agent->orb_offset, ptr); +		rcode = RCODE_COMPLETE; +	} else { +		rcode = RCODE_TYPE_ERROR; +	} + +out: +	fw_send_response(card, request, rcode); +} + +static struct sbp_management_agent *sbp_management_agent_register( +		struct sbp_tport *tport) +{ +	int ret; +	struct sbp_management_agent *agent; + +	agent = kmalloc(sizeof(*agent), GFP_KERNEL); +	if (!agent) +		return ERR_PTR(-ENOMEM); + +	spin_lock_init(&agent->lock); +	agent->tport = tport; +	agent->handler.length = 0x08; +	agent->handler.address_callback = sbp_mgt_agent_rw; +	agent->handler.callback_data = agent; +	agent->state = MANAGEMENT_AGENT_STATE_IDLE; +	INIT_WORK(&agent->work, sbp_mgt_agent_process); +	agent->orb_offset = 0; +	agent->request = NULL; + +	ret = fw_core_add_address_handler(&agent->handler, +			&sbp_register_region); +	if (ret < 0) { +		kfree(agent); +		return ERR_PTR(ret); +	} + +	return agent; +} + +static void sbp_management_agent_unregister(struct sbp_management_agent *agent) +{ +	fw_core_remove_address_handler(&agent->handler); +	cancel_work_sync(&agent->work); +	kfree(agent); +} + +static int sbp_check_true(struct se_portal_group *se_tpg) +{ +	return 1; +} + +static int sbp_check_false(struct se_portal_group *se_tpg) +{ +	return 0; +} + +static char *sbp_get_fabric_name(void) +{ +	return "sbp"; +} + +static char *sbp_get_fabric_wwn(struct se_portal_group *se_tpg) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; + +	return &tport->tport_name[0]; +} + +static u16 sbp_get_tag(struct se_portal_group *se_tpg) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	return tpg->tport_tpgt; +} + +static u32 sbp_get_default_depth(struct se_portal_group *se_tpg) +{ +	return 1; +} + +static struct se_node_acl *sbp_alloc_fabric_acl(struct se_portal_group *se_tpg) +{ +	struct sbp_nacl *nacl; + +	nacl = kzalloc(sizeof(struct sbp_nacl), GFP_KERNEL); +	if (!nacl) { +		pr_err("Unable to alocate struct sbp_nacl\n"); +		return NULL; +	} + +	return &nacl->se_node_acl; +} + +static void sbp_release_fabric_acl( +	struct se_portal_group *se_tpg, +	struct se_node_acl *se_nacl) +{ +	struct sbp_nacl *nacl = +		container_of(se_nacl, struct sbp_nacl, se_node_acl); +	kfree(nacl); +} + +static u32 sbp_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ +	return 1; +} + +static void sbp_release_cmd(struct se_cmd *se_cmd) +{ +	struct sbp_target_request *req = container_of(se_cmd, +			struct sbp_target_request, se_cmd); + +	sbp_free_request(req); +} + +static int sbp_shutdown_session(struct se_session *se_sess) +{ +	return 0; +} + +static void sbp_close_session(struct se_session *se_sess) +{ +	return; +} + +static u32 sbp_sess_get_index(struct se_session *se_sess) +{ +	return 0; +} + +static int sbp_write_pending(struct se_cmd *se_cmd) +{ +	struct sbp_target_request *req = container_of(se_cmd, +			struct sbp_target_request, se_cmd); +	int ret; + +	ret = sbp_rw_data(req); +	if (ret) { +		req->status.status |= cpu_to_be32( +			STATUS_BLOCK_RESP( +				STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_DEAD(0) | +			STATUS_BLOCK_LEN(1) | +			STATUS_BLOCK_SBP_STATUS( +				SBP_STATUS_UNSPECIFIED_ERROR)); +		sbp_send_status(req); +		return ret; +	} + +	transport_generic_process_write(se_cmd); + +	return 0; +} + +static int sbp_write_pending_status(struct se_cmd *se_cmd) +{ +	return 0; +} + +static void sbp_set_default_node_attrs(struct se_node_acl *nacl) +{ +	return; +} + +static u32 sbp_get_task_tag(struct se_cmd *se_cmd) +{ +	struct sbp_target_request *req = container_of(se_cmd, +			struct sbp_target_request, se_cmd); + +	/* only used for printk until we do TMRs */ +	return (u32)req->orb_pointer; +} + +static int sbp_get_cmd_state(struct se_cmd *se_cmd) +{ +	return 0; +} + +static int sbp_queue_data_in(struct se_cmd *se_cmd) +{ +	struct sbp_target_request *req = container_of(se_cmd, +			struct sbp_target_request, se_cmd); +	int ret; + +	ret = sbp_rw_data(req); +	if (ret) { +		req->status.status |= cpu_to_be32( +			STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | +			STATUS_BLOCK_DEAD(0) | +			STATUS_BLOCK_LEN(1) | +			STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); +		sbp_send_status(req); +		return ret; +	} + +	return sbp_send_sense(req); +} + +/* + * Called after command (no data transfer) or after the write (to device) + * operation is completed + */ +static int sbp_queue_status(struct se_cmd *se_cmd) +{ +	struct sbp_target_request *req = container_of(se_cmd, +			struct sbp_target_request, se_cmd); + +	return sbp_send_sense(req); +} + +static int sbp_queue_tm_rsp(struct se_cmd *se_cmd) +{ +	return 0; +} + +static u16 sbp_set_fabric_sense_len(struct se_cmd *se_cmd, u32 sense_length) +{ +	return 0; +} + +static u16 sbp_get_fabric_sense_len(void) +{ +	return 0; +} + +static int sbp_check_stop_free(struct se_cmd *se_cmd) +{ +	struct sbp_target_request *req = container_of(se_cmd, +			struct sbp_target_request, se_cmd); + +	transport_generic_free_cmd(&req->se_cmd, 0); +	return 1; +} + +/* + * Handlers for Serial Bus Protocol 2/3 (SBP-2 / SBP-3) + */ +static u8 sbp_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ +	/* +	 * Return a IEEE 1394 SCSI Protocol identifier for loopback operations +	 * This is defined in section 7.5.1 Table 362 in spc4r17 +	 */ +	return SCSI_PROTOCOL_SBP; +} + +static u32 sbp_get_pr_transport_id( +	struct se_portal_group *se_tpg, +	struct se_node_acl *se_nacl, +	struct t10_pr_registration *pr_reg, +	int *format_code, +	unsigned char *buf) +{ +	int ret; + +	/* +	 * Set PROTOCOL IDENTIFIER to 3h for SBP +	 */ +	buf[0] = SCSI_PROTOCOL_SBP; +	/* +	 * From spc4r17, 7.5.4.4 TransportID for initiator ports using SCSI +	 * over IEEE 1394 +	 */ +	ret = hex2bin(&buf[8], se_nacl->initiatorname, 8); +	if (ret < 0) +		pr_debug("sbp transport_id: invalid hex string\n"); + +	/* +	 * The IEEE 1394 Transport ID is a hardcoded 24-byte length +	 */ +	return 24; +} + +static u32 sbp_get_pr_transport_id_len( +	struct se_portal_group *se_tpg, +	struct se_node_acl *se_nacl, +	struct t10_pr_registration *pr_reg, +	int *format_code) +{ +	*format_code = 0; +	/* +	 * From spc4r17, 7.5.4.4 TransportID for initiator ports using SCSI +	 * over IEEE 1394 +	 * +	 * The SBP Transport ID is a hardcoded 24-byte length +	 */ +	return 24; +} + +/* + * Used for handling SCSI fabric dependent TransportIDs in SPC-3 and above + * Persistent Reservation SPEC_I_PT=1 and PROUT REGISTER_AND_MOVE operations. + */ +static char *sbp_parse_pr_out_transport_id( +	struct se_portal_group *se_tpg, +	const char *buf, +	u32 *out_tid_len, +	char **port_nexus_ptr) +{ +	/* +	 * Assume the FORMAT CODE 00b from spc4r17, 7.5.4.4 TransportID +	 * for initiator ports using SCSI over SBP Serial SCSI Protocol +	 * +	 * The TransportID for a IEEE 1394 Initiator Port is of fixed size of +	 * 24 bytes, and IEEE 1394 does not contain a I_T nexus identifier, +	 * so we return the **port_nexus_ptr set to NULL. +	 */ +	*port_nexus_ptr = NULL; +	*out_tid_len = 24; + +	return (char *)&buf[8]; +} + +static int sbp_count_se_tpg_luns(struct se_portal_group *tpg) +{ +	int i, count = 0; + +	spin_lock(&tpg->tpg_lun_lock); +	for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { +		struct se_lun *se_lun = tpg->tpg_lun_list[i]; + +		if (se_lun->lun_status == TRANSPORT_LUN_STATUS_FREE) +			continue; + +		count++; +	} +	spin_unlock(&tpg->tpg_lun_lock); + +	return count; +} + +static int sbp_update_unit_directory(struct sbp_tport *tport) +{ +	int num_luns, num_entries, idx = 0, mgt_agt_addr, ret, i; +	u32 *data; + +	if (tport->unit_directory.data) { +		fw_core_remove_descriptor(&tport->unit_directory); +		kfree(tport->unit_directory.data); +		tport->unit_directory.data = NULL; +	} + +	if (!tport->enable || !tport->tpg) +		return 0; + +	num_luns = sbp_count_se_tpg_luns(&tport->tpg->se_tpg); + +	/* +	 * Number of entries in the final unit directory: +	 *  - all of those in the template +	 *  - management_agent +	 *  - unit_characteristics +	 *  - reconnect_timeout +	 *  - unit unique ID +	 *  - one for each LUN +	 * +	 *  MUST NOT include leaf or sub-directory entries +	 */ +	num_entries = ARRAY_SIZE(sbp_unit_directory_template) + 4 + num_luns; + +	if (tport->directory_id != -1) +		num_entries++; + +	/* allocate num_entries + 4 for the header and unique ID leaf */ +	data = kcalloc((num_entries + 4), sizeof(u32), GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	/* directory_length */ +	data[idx++] = num_entries << 16; + +	/* directory_id */ +	if (tport->directory_id != -1) +		data[idx++] = (CSR_DIRECTORY_ID << 24) | tport->directory_id; + +	/* unit directory template */ +	memcpy(&data[idx], sbp_unit_directory_template, +			sizeof(sbp_unit_directory_template)); +	idx += ARRAY_SIZE(sbp_unit_directory_template); + +	/* management_agent */ +	mgt_agt_addr = (tport->mgt_agt->handler.offset - CSR_REGISTER_BASE) / 4; +	data[idx++] = 0x54000000 | (mgt_agt_addr & 0x00ffffff); + +	/* unit_characteristics */ +	data[idx++] = 0x3a000000 | +		(((tport->mgt_orb_timeout * 2) << 8) & 0xff00) | +		SBP_ORB_FETCH_SIZE; + +	/* reconnect_timeout */ +	data[idx++] = 0x3d000000 | (tport->max_reconnect_timeout & 0xffff); + +	/* unit unique ID (leaf is just after LUNs) */ +	data[idx++] = 0x8d000000 | (num_luns + 1); + +	spin_lock(&tport->tpg->se_tpg.tpg_lun_lock); +	for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { +		struct se_lun *se_lun = tport->tpg->se_tpg.tpg_lun_list[i]; +		struct se_device *dev; +		int type; + +		if (se_lun->lun_status == TRANSPORT_LUN_STATUS_FREE) +			continue; + +		spin_unlock(&tport->tpg->se_tpg.tpg_lun_lock); + +		dev = se_lun->lun_se_dev; +		type = dev->transport->get_device_type(dev); + +		/* logical_unit_number */ +		data[idx++] = 0x14000000 | +			((type << 16) & 0x1f0000) | +			(se_lun->unpacked_lun & 0xffff); + +		spin_lock(&tport->tpg->se_tpg.tpg_lun_lock); +	} +	spin_unlock(&tport->tpg->se_tpg.tpg_lun_lock); + +	/* unit unique ID leaf */ +	data[idx++] = 2 << 16; +	data[idx++] = tport->guid >> 32; +	data[idx++] = tport->guid; + +	tport->unit_directory.length = idx; +	tport->unit_directory.key = (CSR_DIRECTORY | CSR_UNIT) << 24; +	tport->unit_directory.data = data; + +	ret = fw_core_add_descriptor(&tport->unit_directory); +	if (ret < 0) { +		kfree(tport->unit_directory.data); +		tport->unit_directory.data = NULL; +	} + +	return ret; +} + +static ssize_t sbp_parse_wwn(const char *name, u64 *wwn, int strict) +{ +	const char *cp; +	char c, nibble; +	int pos = 0, err; + +	*wwn = 0; +	for (cp = name; cp < &name[SBP_NAMELEN - 1]; cp++) { +		c = *cp; +		if (c == '\n' && cp[1] == '\0') +			continue; +		if (c == '\0') { +			err = 2; +			if (pos != 16) +				goto fail; +			return cp - name; +		} +		err = 3; +		if (isdigit(c)) +			nibble = c - '0'; +		else if (isxdigit(c) && (islower(c) || !strict)) +			nibble = tolower(c) - 'a' + 10; +		else +			goto fail; +		*wwn = (*wwn << 4) | nibble; +		pos++; +	} +	err = 4; +fail: +	printk(KERN_INFO "err %u len %zu pos %u\n", +			err, cp - name, pos); +	return -1; +} + +static ssize_t sbp_format_wwn(char *buf, size_t len, u64 wwn) +{ +	return snprintf(buf, len, "%016llx", wwn); +} + +static struct se_node_acl *sbp_make_nodeacl( +		struct se_portal_group *se_tpg, +		struct config_group *group, +		const char *name) +{ +	struct se_node_acl *se_nacl, *se_nacl_new; +	struct sbp_nacl *nacl; +	u64 guid = 0; +	u32 nexus_depth = 1; + +	if (sbp_parse_wwn(name, &guid, 1) < 0) +		return ERR_PTR(-EINVAL); + +	se_nacl_new = sbp_alloc_fabric_acl(se_tpg); +	if (!se_nacl_new) +		return ERR_PTR(-ENOMEM); + +	/* +	 * se_nacl_new may be released by core_tpg_add_initiator_node_acl() +	 * when converting a NodeACL from demo mode -> explict +	 */ +	se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new, +			name, nexus_depth); +	if (IS_ERR(se_nacl)) { +		sbp_release_fabric_acl(se_tpg, se_nacl_new); +		return se_nacl; +	} + +	nacl = container_of(se_nacl, struct sbp_nacl, se_node_acl); +	nacl->guid = guid; +	sbp_format_wwn(nacl->iport_name, SBP_NAMELEN, guid); + +	return se_nacl; +} + +static void sbp_drop_nodeacl(struct se_node_acl *se_acl) +{ +	struct sbp_nacl *nacl = +		container_of(se_acl, struct sbp_nacl, se_node_acl); + +	core_tpg_del_initiator_node_acl(se_acl->se_tpg, se_acl, 1); +	kfree(nacl); +} + +static int sbp_post_link_lun( +		struct se_portal_group *se_tpg, +		struct se_lun *se_lun) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + +	return sbp_update_unit_directory(tpg->tport); +} + +static void sbp_pre_unlink_lun( +		struct se_portal_group *se_tpg, +		struct se_lun *se_lun) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	int ret; + +	if (sbp_count_se_tpg_luns(&tpg->se_tpg) == 0) +		tport->enable = 0; + +	ret = sbp_update_unit_directory(tport); +	if (ret < 0) +		pr_err("unlink LUN: failed to update unit directory\n"); +} + +static struct se_portal_group *sbp_make_tpg( +		struct se_wwn *wwn, +		struct config_group *group, +		const char *name) +{ +	struct sbp_tport *tport = +		container_of(wwn, struct sbp_tport, tport_wwn); + +	struct sbp_tpg *tpg; +	unsigned long tpgt; +	int ret; + +	if (strstr(name, "tpgt_") != name) +		return ERR_PTR(-EINVAL); +	if (kstrtoul(name + 5, 10, &tpgt) || tpgt > UINT_MAX) +		return ERR_PTR(-EINVAL); + +	if (tport->tpg) { +		pr_err("Only one TPG per Unit is possible.\n"); +		return ERR_PTR(-EBUSY); +	} + +	tpg = kzalloc(sizeof(*tpg), GFP_KERNEL); +	if (!tpg) { +		pr_err("Unable to allocate struct sbp_tpg\n"); +		return ERR_PTR(-ENOMEM); +	} + +	tpg->tport = tport; +	tpg->tport_tpgt = tpgt; +	tport->tpg = tpg; + +	/* default attribute values */ +	tport->enable = 0; +	tport->directory_id = -1; +	tport->mgt_orb_timeout = 15; +	tport->max_reconnect_timeout = 5; +	tport->max_logins_per_lun = 1; + +	tport->mgt_agt = sbp_management_agent_register(tport); +	if (IS_ERR(tport->mgt_agt)) { +		ret = PTR_ERR(tport->mgt_agt); +		kfree(tpg); +		return ERR_PTR(ret); +	} + +	ret = core_tpg_register(&sbp_fabric_configfs->tf_ops, wwn, +			&tpg->se_tpg, (void *)tpg, +			TRANSPORT_TPG_TYPE_NORMAL); +	if (ret < 0) { +		sbp_management_agent_unregister(tport->mgt_agt); +		kfree(tpg); +		return ERR_PTR(ret); +	} + +	return &tpg->se_tpg; +} + +static void sbp_drop_tpg(struct se_portal_group *se_tpg) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; + +	core_tpg_deregister(se_tpg); +	sbp_management_agent_unregister(tport->mgt_agt); +	tport->tpg = NULL; +	kfree(tpg); +} + +static struct se_wwn *sbp_make_tport( +		struct target_fabric_configfs *tf, +		struct config_group *group, +		const char *name) +{ +	struct sbp_tport *tport; +	u64 guid = 0; + +	if (sbp_parse_wwn(name, &guid, 1) < 0) +		return ERR_PTR(-EINVAL); + +	tport = kzalloc(sizeof(*tport), GFP_KERNEL); +	if (!tport) { +		pr_err("Unable to allocate struct sbp_tport\n"); +		return ERR_PTR(-ENOMEM); +	} + +	tport->guid = guid; +	sbp_format_wwn(tport->tport_name, SBP_NAMELEN, guid); + +	return &tport->tport_wwn; +} + +static void sbp_drop_tport(struct se_wwn *wwn) +{ +	struct sbp_tport *tport = +		container_of(wwn, struct sbp_tport, tport_wwn); + +	kfree(tport); +} + +static ssize_t sbp_wwn_show_attr_version( +		struct target_fabric_configfs *tf, +		char *page) +{ +	return sprintf(page, "FireWire SBP fabric module %s\n", SBP_VERSION); +} + +TF_WWN_ATTR_RO(sbp, version); + +static struct configfs_attribute *sbp_wwn_attrs[] = { +	&sbp_wwn_version.attr, +	NULL, +}; + +static ssize_t sbp_tpg_show_directory_id( +		struct se_portal_group *se_tpg, +		char *page) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; + +	if (tport->directory_id == -1) +		return sprintf(page, "implicit\n"); +	else +		return sprintf(page, "%06x\n", tport->directory_id); +} + +static ssize_t sbp_tpg_store_directory_id( +		struct se_portal_group *se_tpg, +		const char *page, +		size_t count) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	unsigned long val; + +	if (tport->enable) { +		pr_err("Cannot change the directory_id on an active target.\n"); +		return -EBUSY; +	} + +	if (strstr(page, "implicit") == page) { +		tport->directory_id = -1; +	} else { +		if (kstrtoul(page, 16, &val) < 0) +			return -EINVAL; +		if (val > 0xffffff) +			return -EINVAL; + +		tport->directory_id = val; +	} + +	return count; +} + +static ssize_t sbp_tpg_show_enable( +		struct se_portal_group *se_tpg, +		char *page) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	return sprintf(page, "%d\n", tport->enable); +} + +static ssize_t sbp_tpg_store_enable( +		struct se_portal_group *se_tpg, +		const char *page, +		size_t count) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	unsigned long val; +	int ret; + +	if (kstrtoul(page, 0, &val) < 0) +		return -EINVAL; +	if ((val != 0) && (val != 1)) +		return -EINVAL; + +	if (tport->enable == val) +		return count; + +	if (val) { +		if (sbp_count_se_tpg_luns(&tpg->se_tpg) == 0) { +			pr_err("Cannot enable a target with no LUNs!\n"); +			return -EINVAL; +		} +	} else { +		/* XXX: force-shutdown sessions instead? */ +		spin_lock_bh(&se_tpg->session_lock); +		if (!list_empty(&se_tpg->tpg_sess_list)) { +			spin_unlock_bh(&se_tpg->session_lock); +			return -EBUSY; +		} +		spin_unlock_bh(&se_tpg->session_lock); +	} + +	tport->enable = val; + +	ret = sbp_update_unit_directory(tport); +	if (ret < 0) { +		pr_err("Could not update Config ROM\n"); +		return ret; +	} + +	return count; +} + +TF_TPG_BASE_ATTR(sbp, directory_id, S_IRUGO | S_IWUSR); +TF_TPG_BASE_ATTR(sbp, enable, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *sbp_tpg_base_attrs[] = { +	&sbp_tpg_directory_id.attr, +	&sbp_tpg_enable.attr, +	NULL, +}; + +static ssize_t sbp_tpg_attrib_show_mgt_orb_timeout( +		struct se_portal_group *se_tpg, +		char *page) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	return sprintf(page, "%d\n", tport->mgt_orb_timeout); +} + +static ssize_t sbp_tpg_attrib_store_mgt_orb_timeout( +		struct se_portal_group *se_tpg, +		const char *page, +		size_t count) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	unsigned long val; +	int ret; + +	if (kstrtoul(page, 0, &val) < 0) +		return -EINVAL; +	if ((val < 1) || (val > 127)) +		return -EINVAL; + +	if (tport->mgt_orb_timeout == val) +		return count; + +	tport->mgt_orb_timeout = val; + +	ret = sbp_update_unit_directory(tport); +	if (ret < 0) +		return ret; + +	return count; +} + +static ssize_t sbp_tpg_attrib_show_max_reconnect_timeout( +		struct se_portal_group *se_tpg, +		char *page) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	return sprintf(page, "%d\n", tport->max_reconnect_timeout); +} + +static ssize_t sbp_tpg_attrib_store_max_reconnect_timeout( +		struct se_portal_group *se_tpg, +		const char *page, +		size_t count) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	unsigned long val; +	int ret; + +	if (kstrtoul(page, 0, &val) < 0) +		return -EINVAL; +	if ((val < 1) || (val > 32767)) +		return -EINVAL; + +	if (tport->max_reconnect_timeout == val) +		return count; + +	tport->max_reconnect_timeout = val; + +	ret = sbp_update_unit_directory(tport); +	if (ret < 0) +		return ret; + +	return count; +} + +static ssize_t sbp_tpg_attrib_show_max_logins_per_lun( +		struct se_portal_group *se_tpg, +		char *page) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	return sprintf(page, "%d\n", tport->max_logins_per_lun); +} + +static ssize_t sbp_tpg_attrib_store_max_logins_per_lun( +		struct se_portal_group *se_tpg, +		const char *page, +		size_t count) +{ +	struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); +	struct sbp_tport *tport = tpg->tport; +	unsigned long val; + +	if (kstrtoul(page, 0, &val) < 0) +		return -EINVAL; +	if ((val < 1) || (val > 127)) +		return -EINVAL; + +	/* XXX: also check against current count? */ + +	tport->max_logins_per_lun = val; + +	return count; +} + +TF_TPG_ATTRIB_ATTR(sbp, mgt_orb_timeout, S_IRUGO | S_IWUSR); +TF_TPG_ATTRIB_ATTR(sbp, max_reconnect_timeout, S_IRUGO | S_IWUSR); +TF_TPG_ATTRIB_ATTR(sbp, max_logins_per_lun, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *sbp_tpg_attrib_attrs[] = { +	&sbp_tpg_attrib_mgt_orb_timeout.attr, +	&sbp_tpg_attrib_max_reconnect_timeout.attr, +	&sbp_tpg_attrib_max_logins_per_lun.attr, +	NULL, +}; + +static struct target_core_fabric_ops sbp_ops = { +	.get_fabric_name		= sbp_get_fabric_name, +	.get_fabric_proto_ident		= sbp_get_fabric_proto_ident, +	.tpg_get_wwn			= sbp_get_fabric_wwn, +	.tpg_get_tag			= sbp_get_tag, +	.tpg_get_default_depth		= sbp_get_default_depth, +	.tpg_get_pr_transport_id	= sbp_get_pr_transport_id, +	.tpg_get_pr_transport_id_len	= sbp_get_pr_transport_id_len, +	.tpg_parse_pr_out_transport_id	= sbp_parse_pr_out_transport_id, +	.tpg_check_demo_mode		= sbp_check_true, +	.tpg_check_demo_mode_cache	= sbp_check_true, +	.tpg_check_demo_mode_write_protect = sbp_check_false, +	.tpg_check_prod_mode_write_protect = sbp_check_false, +	.tpg_alloc_fabric_acl		= sbp_alloc_fabric_acl, +	.tpg_release_fabric_acl		= sbp_release_fabric_acl, +	.tpg_get_inst_index		= sbp_tpg_get_inst_index, +	.release_cmd			= sbp_release_cmd, +	.shutdown_session		= sbp_shutdown_session, +	.close_session			= sbp_close_session, +	.sess_get_index			= sbp_sess_get_index, +	.write_pending			= sbp_write_pending, +	.write_pending_status		= sbp_write_pending_status, +	.set_default_node_attributes	= sbp_set_default_node_attrs, +	.get_task_tag			= sbp_get_task_tag, +	.get_cmd_state			= sbp_get_cmd_state, +	.queue_data_in			= sbp_queue_data_in, +	.queue_status			= sbp_queue_status, +	.queue_tm_rsp			= sbp_queue_tm_rsp, +	.get_fabric_sense_len		= sbp_get_fabric_sense_len, +	.set_fabric_sense_len		= sbp_set_fabric_sense_len, +	.check_stop_free		= sbp_check_stop_free, + +	.fabric_make_wwn		= sbp_make_tport, +	.fabric_drop_wwn		= sbp_drop_tport, +	.fabric_make_tpg		= sbp_make_tpg, +	.fabric_drop_tpg		= sbp_drop_tpg, +	.fabric_post_link		= sbp_post_link_lun, +	.fabric_pre_unlink		= sbp_pre_unlink_lun, +	.fabric_make_np			= NULL, +	.fabric_drop_np			= NULL, +	.fabric_make_nodeacl		= sbp_make_nodeacl, +	.fabric_drop_nodeacl		= sbp_drop_nodeacl, +}; + +static int sbp_register_configfs(void) +{ +	struct target_fabric_configfs *fabric; +	int ret; + +	fabric = target_fabric_configfs_init(THIS_MODULE, "sbp"); +	if (!fabric) { +		pr_err("target_fabric_configfs_init() failed\n"); +		return -ENOMEM; +	} + +	fabric->tf_ops = sbp_ops; + +	/* +	 * Setup default attribute lists for various fabric->tf_cit_tmpl +	 */ +	TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = sbp_wwn_attrs; +	TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = sbp_tpg_base_attrs; +	TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = sbp_tpg_attrib_attrs; +	TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL; +	TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; +	TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL; +	TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; +	TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; +	TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; + +	ret = target_fabric_configfs_register(fabric); +	if (ret < 0) { +		pr_err("target_fabric_configfs_register() failed for SBP\n"); +		return ret; +	} + +	sbp_fabric_configfs = fabric; + +	return 0; +}; + +static void sbp_deregister_configfs(void) +{ +	if (!sbp_fabric_configfs) +		return; + +	target_fabric_configfs_deregister(sbp_fabric_configfs); +	sbp_fabric_configfs = NULL; +}; + +static int __init sbp_init(void) +{ +	int ret; + +	ret = sbp_register_configfs(); +	if (ret < 0) +		return ret; + +	return 0; +}; + +static void sbp_exit(void) +{ +	sbp_deregister_configfs(); +}; + +MODULE_DESCRIPTION("FireWire SBP fabric driver"); +MODULE_LICENSE("GPL"); +module_init(sbp_init); +module_exit(sbp_exit);  |