/*
 * isert_buf.c
 *
 * 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.
 */

#include <linux/kernel.h>

#include "isert_dbg.h"
#include "iser.h"

static int isert_buf_alloc_pg(struct ib_device *ib_dev,
			      struct isert_buf *isert_buf, size_t size,
			      enum dma_data_direction dma_dir)
{
	int res = 0;
	int i;
	struct page *page;

	isert_buf->sg_cnt = DIV_ROUND_UP(size, PAGE_SIZE);
	isert_buf->sg = kmalloc_array(isert_buf->sg_cnt, sizeof(*isert_buf->sg),
				      GFP_KERNEL);
	if (unlikely(!isert_buf->sg)) {
		PRINT_ERROR("Failed to allocate buffer SG");
		res = -ENOMEM;
		goto out;
	}

	sg_init_table(isert_buf->sg, isert_buf->sg_cnt);
	for (i = 0; i < isert_buf->sg_cnt; ++i) {
		size_t page_len = min_t(size_t, size, PAGE_SIZE);

		page = alloc_page(GFP_KERNEL);
		if (unlikely(!page)) {
			PRINT_ERROR("Failed to allocate page");
			res = -ENOMEM;
			goto out_map_failed;
		}
		sg_set_page(&isert_buf->sg[i], page, page_len, 0);
		size -= page_len;
	}

	res = ib_dma_map_sg(ib_dev, isert_buf->sg, isert_buf->sg_cnt, dma_dir);
	if (unlikely(!res)) {
		--i; /* do not overrun isert_buf->sg */
		PRINT_ERROR("Failed to DMA map iser sg:%p len:%d",
			    isert_buf->sg, isert_buf->sg_cnt);
		res = -ENOMEM;
		goto out_map_failed;
	}

	isert_buf->addr = sg_virt(&isert_buf->sg[0]);

	res = 0;
	goto out;

out_map_failed:
	for (; i >= 0; --i)
		__free_page(sg_page(&isert_buf->sg[i]));
	kfree(isert_buf->sg);
	isert_buf->sg = NULL;
out:
	return res;
}

static void isert_buf_release_pg(struct isert_buf *isert_buf)
{
	int i;

	for (i = 0; i < isert_buf->sg_cnt; ++i)
		__free_page(sg_page(&isert_buf->sg[i]));
}

static int isert_buf_malloc(struct ib_device *ib_dev,
			    struct isert_buf *isert_buf, size_t size,
			    enum dma_data_direction dma_dir)
{
	int res = 0;

	isert_buf->sg_cnt = 1;
	isert_buf->sg = kmalloc(sizeof(isert_buf->sg[0]), GFP_KERNEL);
	if (unlikely(!isert_buf->sg)) {
		PRINT_ERROR("Failed to allocate buffer SG");
		res = -ENOMEM;
		goto out;
	}

	isert_buf->addr = kmalloc(size, GFP_KERNEL);
	if (unlikely(!isert_buf->addr)) {
		PRINT_ERROR("Failed to allocate data buffer");
		res = -ENOMEM;
		goto data_malloc_failed;
	}

	sg_init_one(&isert_buf->sg[0], isert_buf->addr, size);

	res = ib_dma_map_sg(ib_dev, isert_buf->sg, isert_buf->sg_cnt, dma_dir);
	if (unlikely(!res)) {
		PRINT_ERROR("Failed to DMA map iser sg:%p len:%d",
			    isert_buf->sg, isert_buf->sg_cnt);
		res = -ENOMEM;
		goto out_map_failed;
	}

	res = 0;
	goto out;

out_map_failed:
	kfree(isert_buf->addr);
	isert_buf->addr = NULL;
data_malloc_failed:
	kfree(isert_buf->addr);
	isert_buf->addr = NULL;
out:
	return res;
}

static void isert_buf_release_kmalloc(struct isert_buf *isert_buf)
{
	kfree(isert_buf->addr);
	isert_buf->addr = NULL;
}

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)
{
	int res = 0;

	isert_buf->is_alloced = 0;
	if (size >= PAGE_SIZE) {
		res = isert_buf_alloc_pg(ib_dev, isert_buf, size, dma_dir);
		if (unlikely(res))
			goto out;
		isert_buf->is_pgalloced = 1;
		isert_buf->is_malloced = 0;
		isert_buf->is_alloced = 1;
	} else if (size) {
		res = isert_buf_malloc(ib_dev, isert_buf, size, dma_dir);
		if (unlikely(res))
			goto out;
		isert_buf->is_pgalloced = 0;
		isert_buf->is_malloced = 1;
		isert_buf->is_alloced = 1;
	}

	isert_buf->size = size;
	isert_buf->dma_dir = dma_dir;
out:
	return res;
}

void isert_buf_release(struct isert_buf *isert_buf)
{
	if (isert_buf->is_alloced) {
		if (isert_buf->is_pgalloced)
			isert_buf_release_pg(isert_buf);

		if (isert_buf->is_malloced)
			isert_buf_release_kmalloc(isert_buf);

		isert_buf->is_alloced = 0;
		kfree(isert_buf->sg);
		isert_buf->sg = NULL;
	}
}

void isert_wr_set_fields(struct isert_wr *wr,
			 struct isert_conn *isert_conn,
			 struct isert_cmnd *pdu)
{
	struct isert_device *isert_dev = isert_conn->isert_dev;

	wr->conn = isert_conn;
	wr->pdu = pdu;
	wr->isert_dev = isert_dev;
}

int isert_wr_init(struct isert_wr *wr,
		  enum isert_wr_op wr_op,
		  struct isert_buf *isert_buf,
		  struct isert_conn *isert_conn,
		  struct isert_cmnd *pdu,
		  struct ib_sge *sge,
		  int sg_offset,
		  int sg_cnt,
		  int buff_offset)
{
	enum ib_wr_opcode send_wr_op = IB_WR_SEND;
	struct scatterlist *sg_tmp;
	int i;
	u32 send_flags = 0;

	TRACE_ENTRY();

	switch (wr_op) {
	case ISER_WR_RECV:
		break;
	case ISER_WR_SEND:
		send_flags = IB_SEND_SIGNALED;
		break;
	case ISER_WR_RDMA_READ:
		send_wr_op = IB_WR_RDMA_READ;
		if (unlikely(!pdu->is_wstag_valid)) {
			PRINT_ERROR("No write tag/va specified for RDMA op");
			isert_buf_release(isert_buf);
			buff_offset = -EFAULT;
			goto out;
		}
#ifdef USE_PRE_440_WR_STRUCTURE
		wr->send_wr.wr.rdma.remote_addr = pdu->rem_write_va +
						  buff_offset;
		wr->send_wr.wr.rdma.rkey = pdu->rem_write_stag;
#else
		wr->send_wr.remote_addr = pdu->rem_write_va + buff_offset;
		wr->send_wr.rkey = pdu->rem_write_stag;
#endif
		break;
	case ISER_WR_RDMA_WRITE:
		send_wr_op = IB_WR_RDMA_WRITE;
		if (unlikely(!pdu->is_rstag_valid)) {
			PRINT_ERROR("No read tag/va specified for RDMA op");
			isert_buf_release(isert_buf);
			buff_offset = -EFAULT;
			goto out;
		}
#ifdef USE_PRE_440_WR_STRUCTURE
		wr->send_wr.wr.rdma.remote_addr = pdu->rem_read_va +
						  buff_offset;
		wr->send_wr.wr.rdma.rkey = pdu->rem_read_stag;
#else
		wr->send_wr.remote_addr = pdu->rem_read_va + buff_offset;
		wr->send_wr.rkey = pdu->rem_read_stag;
#endif
		break;
	default:
		BUG();
	}

	EXTRACHECKS_BUG_ON(isert_buf->sg_cnt == 0);

	wr->wr_op = wr_op;
	wr->buf = isert_buf;

	wr->sge_list = sge + sg_offset;

	sg_tmp = &isert_buf->sg[sg_offset];
	for (i = 0; i < sg_cnt; i++, sg_tmp++) {
		wr->sge_list[i].addr = sg_dma_address(sg_tmp);
		wr->sge_list[i].length = sg_dma_len(sg_tmp);
		buff_offset += wr->sge_list[i].length;
	}

	if (wr_op == ISER_WR_RECV) {
		wr->recv_wr.next = NULL;
		wr->recv_wr.wr_id = _ptr_to_u64(wr);
		wr->recv_wr.sg_list = wr->sge_list;
		wr->recv_wr.num_sge = sg_cnt;
	} else {
#ifdef USE_PRE_440_WR_STRUCTURE
		wr->send_wr.next = NULL;
		wr->send_wr.wr_id = _ptr_to_u64(wr);
		wr->send_wr.sg_list = wr->sge_list;
		wr->send_wr.num_sge = sg_cnt;
		wr->send_wr.opcode = send_wr_op;
		wr->send_wr.send_flags = send_flags;
#else
		wr->send_wr.wr.next = NULL;
		wr->send_wr.wr.wr_id = _ptr_to_u64(wr);
		wr->send_wr.wr.sg_list = wr->sge_list;
		wr->send_wr.wr.num_sge = sg_cnt;
		wr->send_wr.wr.opcode = send_wr_op;
		wr->send_wr.wr.send_flags = send_flags;
#endif
	}

out:
	TRACE_EXIT_RES(buff_offset);
	return buff_offset;
}

void isert_wr_release(struct isert_wr *wr)
{
	struct isert_buf *isert_buf = wr->buf;

	if (isert_buf && isert_buf->is_alloced) {
		struct isert_device *isert_dev = wr->isert_dev;
		struct ib_device *ib_dev;

		if (isert_buf->sg_cnt) {
			ib_dev = isert_dev->ib_dev;
			ib_dma_unmap_sg(ib_dev, isert_buf->sg,
					isert_buf->sg_cnt, isert_buf->dma_dir);
		}
		isert_buf_release(isert_buf);
	}
	memset(wr, 0, sizeof(*wr));
}

