/*
 * Copyright (c) 2010 Cisco Systems, Inc.
 *
 * This program is free software; you may redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#ifndef __SCSI_FCST_H__
#define __SCSI_FCST_H__

#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#else
#include <linux/version.h>
#include "scst.h"
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) || \
	defined(CONFIG_SUSE_KERNEL) && \
	LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
/*
 * See also commit 768c72cc34a2 ("scsi: libfc: Replace ->exch_done callback
 * with function call") # v4.10
 * and commit 9625cc483b8c ("scsi: libfc: Replace ->seq_release callback with
 * function call") # v4.10
 * and commit c6865b30be7e ("scsi: libfc: Replace ->seq_start_next callback
 * with function call") # v4.10.
 */
#define NEW_LIBFC_API
#endif

#define FT_VERSION	"3.7.0-pre"
#define FT_MODULE	"fcst"

#define FT_MAX_HW_PENDING_TIME	20	/* max I/O time in seconds */

/*
 * Debug options.
 */
#define FT_DEBUG_CONF	0x01	/* configuration messages */
#define FT_DEBUG_SESS	0x02	/* session messages */
#define FT_DEBUG_IO	0x04	/* I/O operations */

extern unsigned int ft_debug_logging;	/* debug options */

#define FT_ERR(fmt, args...) pr_err("%s: " fmt, __func__, ##args)

#define FT_DEBUG(mask, fmt, args...)					\
	do {								\
		if (ft_debug_logging & (mask))				\
			pr_info("%s: " fmt, __func__, ##args);		\
	} while (0)

#define FT_CONF_DBG(fmt, args...)	FT_DEBUG(FT_DEBUG_CONF, fmt, ##args)
#define FT_SESS_DBG(fmt, args...)	FT_DEBUG(FT_DEBUG_SESS, fmt, ##args)
#define FT_IO_DBG(fmt, args...)		FT_DEBUG(FT_DEBUG_IO, fmt, ##args)

#define FT_NAMELEN	32		/* length of ASCI WWPNs including pad */

/*
 * Session (remote port).
 */
struct ft_sess {
	u32 port_id;			/* for hash lookup use only */
	u32 params;
	u16 max_payload;		/* max transmitted payload size */
	u32 max_lso_payload;		/* max offloaded payload size */
	u64 port_name;			/* port name for transport ID */
	struct ft_tport *tport;
	struct scst_session *scst_sess;
	struct hlist_node hash;		/* linkage in ft_sess_hash table */
	struct rcu_head rcu;
	struct kref kref;		/* ref for hash and outstanding I/Os */
};

/*
 * Hash table of sessions per local port.
 * Hash lookup by remote port FC_ID.
 */
#define FT_SESS_HASH_BITS	6
#define FT_SESS_HASH_SIZE	(1 << FT_SESS_HASH_BITS)

/*
 * Per local port data.
 * This is created when the first session logs into the local port.
 * Deleted when tpg is deleted or last session is logged off.
 */
struct ft_tport {
	u32	sess_count;		/* number of sessions in hash */
	u8	enabled:1;
	struct rcu_head rcu;
	struct hlist_head hash[FT_SESS_HASH_SIZE];	/* list of sessions */
	struct fc_lport *lport;
	struct scst_tgt *tgt;
};

/**
 * enum ft_cmd_state - SCSI command state managed by fcst
 * @FT_STATE_NEW:           New command arrived and is being processed.
 * @FT_STATE_NEED_DATA:     Processing a write or bidir command and waiting
 *                          for data arrival.
 * @FT_STATE_DATA_IN:       Data for the write or bidir command arrived and is
 *                          being processed.
 * @FT_STATE_CMD_RSP_SENT:  Response with SCSI status has been sent.
 * @FT_STATE_MGMT:          Processing a SCSI task management function.
 * @FT_STATE_MGMT_RSP_SENT: Response for task management function has been sent.
 * @FT_STATE_DONE:          Command processing finished successfully, command
 *                          processing has been aborted or command processing
 *                          failed.
 */
enum ft_cmd_state {
	FT_STATE_NEW		= 0,
	FT_STATE_NEED_DATA	= 1,
	FT_STATE_DATA_IN	= 2,
	FT_STATE_CMD_RSP_SENT	= 3,
	FT_STATE_MGMT		= 4,
	FT_STATE_MGMT_RSP_SENT	= 5,
	FT_STATE_DONE		= 6,
};

/*
 * Commands
 */
struct ft_cmd {
	struct fc_seq *seq;		/* sequence in exchange mgr */
	struct fc_frame *req_frame;	/* original request frame */
	u32 write_data_len;		/* data received from initiator */
	u32 read_data_len;		/* data sent to initiator */
	u32 max_lso_payload;		/* max offloaded (LSO) data payload */
	u16 max_payload;		/* max transmitted data payload */
	struct scst_cmd *scst_cmd;
	spinlock_t lock;		/* protects state */
	enum ft_cmd_state state;
};

extern struct list_head ft_lport_list;
extern struct mutex ft_lport_lock;
extern struct scst_tgt_template ft_scst_template;

/*
 * libfc interface.
 */
extern struct fc4_prov ft_prov;

/*
 * SCST interface.
 */
int ft_send_response(struct scst_cmd *cmd);
int ft_send_xfer_rdy(struct scst_cmd *cmd);
void ft_cmd_timeout(struct scst_cmd *cmd);
void ft_cmd_free(struct scst_cmd *cmd);
void ft_cmd_tm_done(struct scst_mgmt_cmd *mcmd);
int ft_tgt_release(struct scst_tgt *tgt);
int ft_tgt_enable(struct scst_tgt *tgt, bool enable);
bool ft_tgt_enabled(struct scst_tgt *tgt);
int ft_report_aen(struct scst_aen *aen);
int ft_get_transport_id(struct scst_tgt *tgt, struct scst_session *scst_sess,
			uint8_t **result);

/*
 * Session interface.
 */
int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg);
void ft_lport_add(struct fc_lport *lport, void *arg);
void ft_lport_del(struct fc_lport *lport, void *arg);

/*
 * other internal functions.
 */
bool ft_test_and_set_cmd_state(struct ft_cmd *fcmd, enum ft_cmd_state old,
			       enum ft_cmd_state new);
void ft_recv_req(struct ft_sess *sess, struct fc_frame *fp);
void ft_recv_write_data(struct scst_cmd *cmd, struct fc_frame *fp);
int ft_send_read_data(struct scst_cmd *cmd);

/* #define FCST_INJECT_SEND_ERRORS 2 */

#ifdef FCST_INJECT_SEND_ERRORS
#define FCST_INJ_SEND_ERR(e)						\
({									\
	int _error = 0;							\
									\
	if (scst_random() % 62929 == 0)					\
		_error = -ENOMEM;					\
	if (FCST_INJECT_SEND_ERRORS >= 2 && scst_random() % 69491 == 0)	\
		_error = -ENXIO;					\
	if (_error)							\
		pr_warn("%s: injected seq_send() error %d\n", __func__,	\
			_error);					\
	else								\
		_error = (e);						\
	_error;								\
})
#else
#define FCST_INJ_SEND_ERR(e) (e)
#endif

#endif /* __SCSI_FCST_H__ */
