/*
 * This file is part of iser target kernel module.
 *
 * Copyright (c) 2013 - 2014 Mellanox Technologies. All rights reserved.
 * Copyright (c) 2013 - 2014 Yan Burman (yanb@mellanox.com)
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *            - Redistributions of source code must retain the above
 *              copyright notice, this list of conditions and the following
 *              disclaimer.
 *
 *            - Redistributions in binary form must reproduce the above
 *              copyright notice, this list of conditions and the following
 *              disclaimer in the documentation and/or other materials
 *              provided with the distribution.
 *
 * 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 __ISER_H__
#define __ISER_H__

#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <rdma/ib_verbs.h>
#ifndef INSIDE_KERNEL_TREE
#include <linux/version.h>
#endif

#if defined(RHEL_MAJOR) && RHEL_MAJOR -0 == 5
static inline u16 vlan_dev_vlan_id(const void *dev)
{
	BUG();
	return 0;
}
#endif
#include <rdma/rdma_cm.h>

#include "iser_hdr.h"

enum isert_portal_state {
	ISERT_PORTAL_ACTIVE,
	ISERT_PORTAL_INACTIVE
};

struct isert_portal {
	struct rdma_cm_id	*cm_id;
	struct sockaddr_storage	addr;
	struct list_head	list_node; /* in portals list */
	/* protected by dev_list_mutex */
	struct list_head	conn_list; /* head of conns list */
	enum isert_portal_state	state;
	int			refcnt;
};

struct isert_buf {
	int			sg_cnt ____cacheline_aligned;
	struct scatterlist	*sg;
	u8			*addr;
	size_t			size;
	enum dma_data_direction	dma_dir;
	unsigned int		is_alloced:1;
	unsigned int		is_pgalloced:1;
	unsigned int		is_malloced:1;
};

enum isert_wr_op {
	ISER_WR_RECV,
	ISER_WR_SEND,
	ISER_WR_RDMA_WRITE,
	ISER_WR_RDMA_READ,
};

struct isert_device;
struct isert_connection;

struct isert_wr {
	enum isert_wr_op	wr_op;
	struct isert_buf	*buf;

	struct isert_connection	*conn;
	struct isert_cmnd	*pdu;

	struct isert_device	*isert_dev;

	struct ib_sge		*sge_list;
	union {
		struct ib_recv_wr recv_wr;
#ifdef USE_PRE_440_WR_STRUCTURE
		struct ib_send_wr send_wr;
#else
		struct ib_rdma_wr send_wr;
#endif
	};
} ____cacheline_aligned;

#define ISER_SQ_SIZE		128
#define ISER_MAX_WCE		2048

#define ISER_MIN_SQ_SIZE	16

struct isert_cmnd {
	struct iscsi_cmnd	iscsi ____cacheline_aligned;

	struct isert_buf	buf;
	struct isert_buf	rdma_buf;
	struct isert_wr		*wr;
	struct ib_sge		*sg_pool;
	int			n_wr;
	int			n_sge;

	struct isert_hdr	*isert_hdr ____cacheline_aligned;
	struct iscsi_hdr	*bhs;
	void			*ahs;
	void			*data;

	u8			isert_opcode;
	u8			iscsi_opcode;
	u8			is_rstag_valid;
	u8			is_wstag_valid;

	u32			rem_write_stag; /* write rkey */
	u64			rem_write_va;
	u32			rem_read_stag;  /* read rkey */
	u64			rem_read_va;

	int			is_fake_rx;
	struct list_head	pool_node; /* pool list */
};

enum isert_conn_state {
	ISER_CONN_INIT = 0,
	ISER_CONN_HANDSHAKE,
	ISER_CONN_ACTIVE,
	ISER_CONN_CLOSING,
};

struct isert_cq {
	struct ib_cq		*cq ____cacheline_aligned;
	struct ib_wc		wc[ISER_SQ_SIZE];
	struct isert_device	*dev;
	struct workqueue_struct	*cq_workqueue;
	struct work_struct	cq_comp_work;
	int			idx;
};

#define ISERT_CONNECTION_ABORTED	0
#define ISERT_DRAIN_POSTED		1
#define ISERT_DISCON_CALLED		2
#define ISERT_DRAINED_RQ		3
#define ISERT_DRAINED_SQ		4
#define ISERT_CONNECTION_CLOSE		5
#define ISERT_IN_PORTAL_LIST		6

struct isert_connection {
	struct iscsi_conn	iscsi ____cacheline_aligned;

	int			repost_threshold ____cacheline_aligned;
	/* access to the following 3 fields is guarded by post_recv_lock */
	int			to_post_recv;
	struct isert_wr		*post_recv_first;
	struct isert_wr		*post_recv_curr;

	spinlock_t		post_recv_lock;


	spinlock_t		tx_lock ____cacheline_aligned;

	/* Following two protected by tx_lock */
	struct list_head	tx_free_list;
	struct list_head	tx_busy_list;

	struct rdma_cm_id	*cm_id;
	struct isert_device	*isert_dev;
	struct ib_qp		*qp;
	struct isert_cq		*cq_desc;

	enum isert_conn_state	state;
	struct mutex		state_mutex;

	u32			responder_resources;
	u32			initiator_depth;
	u32			max_sge;

	/*
	 * Unprotected. Accessed only before login response is sent and when
	 * freeing connection
	 */
	struct list_head	rx_buf_list;

	struct isert_cmnd	*login_req_pdu;
	struct isert_cmnd	*login_rsp_pdu;
	struct isert_wr		*saved_wr;

	int			queue_depth;
	int			immediate_data;
	unsigned int		target_recv_data_length;
	int			initiator_recv_data_length;
	int			initial_r2t;
	unsigned int		first_burst_length;
	struct sockaddr_storage	peer_addr;
	size_t			peer_addrsz;
	struct sockaddr_storage	self_addr;

	struct list_head	portal_node;

	unsigned long		flags;
	struct work_struct	close_work;
	struct work_struct	drain_work;
	struct work_struct	discon_work;
	struct work_struct	free_work;
	struct isert_wr		drain_wr_sq;
	struct isert_wr		drain_wr_rq;
	struct kref		kref;

	struct isert_portal	*portal;
	void			*priv_data; /* for connection tracking */
};

struct isert_device {
	struct ib_device	*ib_dev;
	struct ib_pd		*pd;
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
	struct ib_mr		*mr;
#endif
	u32			lkey;

	struct list_head	devs_node;
	/* conn_list and refcnt protected by dev_list_mutex */
	struct list_head	conn_list;
	int			refcnt;
	struct ib_device_attr	device_attr;

	int			num_cqs;
	int			*cq_qps;
	struct isert_cq		*cq_desc;
};

struct isert_global {
	spinlock_t		portal_lock;
	/* protected by portal_lock */
	struct list_head	portal_list;
	/* Number of live portal objects. Protected by portal_lock. */
	int			portal_cnt;
	wait_queue_head_t	portal_wq;
	/* protected by dev_list_mutex */
	struct list_head	dev_list;
	struct workqueue_struct	*conn_wq;
};

#define _ptr_to_u64(p)		(u64)(unsigned long)(p)
#define _u64_to_ptr(v)		(void *)(unsigned long)(v)

/* global iser scope */
int isert_global_init(void);
int isert_datamover_cleanup(void);

void isert_portal_list_add(struct isert_portal *portal);
void isert_portal_list_remove(struct isert_portal *portal);
void isert_decrease_portal_cnt(void);
void isert_wait_for_portal_release(void);

void isert_dev_list_add(struct isert_device *isert_dev);
void isert_dev_list_remove(struct isert_device *isert_dev);
struct isert_device *isert_device_find(struct ib_device *ib_dev);

void isert_conn_queue_work(struct work_struct *w);

extern struct kmem_cache *isert_cmnd_cache;
extern struct kmem_cache *isert_conn_cache;

/* iser portal */
struct isert_portal *isert_portal_create(void);
int isert_portal_listen(struct isert_portal *portal,
			struct sockaddr *sa,
			size_t addr_len);
void isert_portal_release(struct isert_portal *portal);
void isert_portal_list_release_all(void);
struct isert_portal *isert_portal_start(struct sockaddr *sa, size_t addr_len);

/* iser connection */
int isert_post_recv(struct isert_connection *isert_conn,
		    struct isert_wr *first_wr, int num_wr);
int isert_post_send(struct isert_connection *isert_conn,
		    struct isert_wr *first_wr, int num_wr);

int isert_alloc_conn_resources(struct isert_connection *isert_conn);
void isert_free_conn_resources(struct isert_connection *isert_conn);
void isert_conn_free(struct isert_connection *isert_conn);
void isert_conn_disconnect(struct isert_connection *isert_conn);
void isert_post_drain(struct isert_connection *isert_conn);
void isert_sched_conn_free(struct isert_connection *isert_conn);

static inline struct isert_connection *isert_conn_zalloc(void)
{
	return kmem_cache_zalloc(isert_conn_cache, GFP_KERNEL);
}

static inline void isert_conn_kfree(struct isert_connection *isert_conn)
{
	kmem_cache_free(isert_conn_cache, isert_conn);
}

/* iser buf */
int isert_buf_alloc_data_buf(struct ib_device *ib_dev,
			     struct isert_buf *isert_buf, size_t size,
			     enum dma_data_direction dma_dir);
void isert_wr_set_fields(struct isert_wr *wr,
			 struct isert_connection *isert_conn,
			 struct isert_cmnd *pdu);
int isert_wr_init(struct isert_wr *wr,
		  enum isert_wr_op wr_op,
		  struct isert_buf *isert_buf,
		  struct isert_connection *isert_conn,
		  struct isert_cmnd *pdu,
		  struct ib_sge *sge,
		  int sg_offset,
		  int sg_cnt,
		  int buff_offset);
void isert_wr_release(struct isert_wr *wr);

void isert_buf_release(struct isert_buf *isert_buf);

static inline void isert_buf_init_sg(struct isert_buf *isert_buf,
				     struct scatterlist *sg,
				     int sg_cnt, size_t size)
{
	isert_buf->sg_cnt = sg_cnt;
	isert_buf->sg = sg;
	isert_buf->size = size;
}

/* iser pdu */
static inline struct isert_cmnd *isert_pdu_alloc(void)
{
	return kmem_cache_zalloc(isert_cmnd_cache, GFP_KERNEL);
}

static inline void isert_pdu_kfree(struct isert_cmnd *cmnd)
{
	kmem_cache_free(isert_cmnd_cache, cmnd);
}

struct isert_cmnd *isert_rx_pdu_alloc(struct isert_connection *isert_conn,
				      size_t size);
struct isert_cmnd *isert_tx_pdu_alloc(struct isert_connection *isert_conn,
				      size_t size);
void isert_tx_pdu_init(struct isert_cmnd *isert_pdu,
		       struct isert_connection *isert_conn);
int isert_pdu_send(struct isert_connection *isert_conn,
		   struct isert_cmnd *tx_pdu);

int isert_prepare_rdma(struct isert_cmnd *isert_pdu,
		       struct isert_connection *isert_conn,
		       enum isert_wr_op op);
int isert_pdu_post_rdma_write(struct isert_connection *isert_conn,
			      struct isert_cmnd *isert_cmd,
			      struct isert_cmnd *isert_rsp,
			      int wr_cnt);
int isert_pdu_post_rdma_read(struct isert_connection *isert_conn,
			     struct isert_cmnd *isert_cmd,
			     int wr_cnt);

void isert_pdu_free(struct isert_cmnd *pdu);
int isert_rx_pdu_done(struct isert_cmnd *pdu);

void isert_tx_pdu_convert_from_iscsi(struct isert_cmnd *isert_cmnd,
				     struct iscsi_cmnd *iscsi_cmnd);

void isert_tx_pdu_init_iscsi(struct isert_cmnd *isert_pdu);

/* global */
void isert_global_cleanup(void);
int isert_get_addr_size(struct sockaddr *sa, size_t *size);

#endif
