/**
 * \file
 *
 * \brief Block device medium initialization
 *
 * This file contains the functionality needed to write a certain
 * pattern to a range of blocks on a given block device.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
#include <buffer.h>
#include <debug.h>
#include <malloc.h>
#include <string.h>
#include <types.h>
#include <workqueue.h>
#include <app/config_dmapool.h>
#include <app/workqueue.h>
#include <block/device.h>
#include <block/fill.h>

#define BLOCK_FILL_BUF_SIZE	CONFIG_DMAPOOL_LARGE_OBJ_SIZE
#define BLOCK_FILL_MAX_BUFFERS	(CONFIG_DMAPOOL_NR_LARGE_OBJS / 2)

struct block_fill_request {
	uint32_t		blocks_remaining;
	uint32_t		blocks_allocated;
	uint32_t		nr_blocks;
	block_addr_t		lba;
	struct block_device	*bdev;
	struct block_request	*breq;
	struct workqueue_item	work;
	block_fill_callback_t	completion_callback;
	void			*context;
};

static void block_fill_complete(struct block_fill_request *req, int status)
{
	if (req->completion_callback)
		req->completion_callback(req, status,
				req->nr_blocks - req->blocks_remaining,
				req->context);
}

static void block_fill_error(void *data)
{
	struct block_fill_request	*req = data;
	struct block_request		*breq = req->breq;

	req->blocks_remaining -= breq->bytes_xfered / req->bdev->block_size;
	block_fill_complete(req, breq->status);
}

static void block_fill_worker(void *data)
{
	struct block_fill_request	*req = data;
	struct block_request		*breq = req->breq;
	uint32_t			blocks_remaining;
	uint32_t			blocks_allocated;
	struct slist			buf_list;

	blocks_remaining = req->blocks_remaining;
	blocks_allocated = req->blocks_allocated;
	assert(blocks_remaining >= blocks_allocated);
	blocks_remaining -= blocks_allocated;
	req->blocks_remaining = blocks_remaining;

	dbg_verbose("block_fill: %u / %u blocks done\n",
			req->nr_blocks - blocks_remaining, req->nr_blocks);

	if (blocks_remaining == 0) {
		block_fill_complete(req, 0);
		return;
	}

	req->lba += blocks_allocated;

	slist_init(&buf_list);
	slist_move_to_tail(&buf_list, &breq->buf_list);
	if (blocks_remaining < blocks_allocated) {
		unsigned int	blocks_per_buf;
		unsigned int	block_size;

		block_size = req->bdev->block_size;
		blocks_per_buf = BLOCK_FILL_BUF_SIZE / block_size;

		/* Free excess buffers */
		while (blocks_allocated - blocks_per_buf >= blocks_remaining) {
			struct buffer	*buf;

			buf = slist_pop_head(&buf_list, struct buffer, node);
			buffer_dma_free(buf, BLOCK_FILL_BUF_SIZE);
			blocks_allocated -= blocks_per_buf;
		}

		/* Resize the last buffer if necessary */
		if (blocks_allocated > blocks_remaining) {
			struct buffer	*buf;

			buf = slist_peek_tail(&buf_list, struct buffer, node);
			buffer_resize(buf, BLOCK_FILL_BUF_SIZE
					- block_size * (blocks_allocated
						- blocks_remaining));
		}

		req->blocks_allocated = blocks_allocated;
	}

	block_prepare_req(req->bdev, req->breq, req->lba, blocks_allocated,
			BLK_OP_WRITE);
	slist_move_to_tail(&req->breq->buf_list, &buf_list);
	block_submit_req(req->bdev, req->breq);
}

static void block_fill_write_done(struct block_device *bdev,
		struct block_request *breq)
{
	struct block_fill_request	*req = breq->context;

	assert(bdev == req->bdev);
	assert(breq == req->breq);

	/* Run error handler instead of normal worker on error */
	if (breq->status)
		workqueue_init_item(&req->work, block_fill_error, req);

	workqueue_add_item(&block_workqueue, &req->work);
}

struct block_fill_request *block_fill_alloc(struct block_device *bdev,
		block_addr_t first_lba, uint32_t nr_blocks, void *context)
{
	struct block_fill_request	*req;
	struct block_request		*breq;
	unsigned int			block_size;
	uint32_t			blocks_allocated;
	uint32_t			blocks_remaining;
	struct slist			buf_list;
	unsigned int			i;

	block_size = bdev->block_size;

	/*
	 * We must be able to hold an integral number of blocks in each
	 * buffer.
	 */
	if (BLOCK_FILL_BUF_SIZE < block_size)
		return NULL;
	if (BLOCK_FILL_BUF_SIZE % block_size)
		return NULL;

	req = malloc(sizeof(struct block_fill_request));
	if (unlikely(!req))
		return NULL;
	memset(req, 0, sizeof(struct block_fill_request));

	breq = block_alloc_request(bdev);
	if (unlikely(!breq))
		goto err_alloc_breq;

	/*
	 * Allocate as many buffers as we can up to what's required to
	 * hold the specified number of blocks.
	 */
	slist_init(&buf_list);
	blocks_remaining = nr_blocks;
	for (i = 0; i < BLOCK_FILL_MAX_BUFFERS; i++) {
		struct buffer	*buf;
		uint32_t	buf_blocks;

		buf = buffer_dma_alloc(BLOCK_FILL_BUF_SIZE);
		if (!buf)
			break;

		buf_blocks = BLOCK_FILL_BUF_SIZE / block_size;
		slist_insert_tail(&buf_list, &buf->node);
		if (buf_blocks >= blocks_remaining) {
			if (buf_blocks > blocks_remaining) {
				buf_blocks = blocks_remaining;
				buffer_resize(buf, buf_blocks * block_size);
			}
			blocks_remaining = 0;
			break;
		}

		blocks_remaining -= buf_blocks;
	}

	blocks_allocated = nr_blocks - blocks_remaining;
	if (unlikely(!blocks_allocated))
		/* Could not allocate anything -- abort the operation */
		goto err_alloc_buf;

	/*
	 * The block request will be reused several times until all the
	 * blocks have been written.
	 */
	block_prepare_req(bdev, breq, first_lba, blocks_allocated,
			BLK_OP_WRITE);
	breq->req_done = block_fill_write_done;
	breq->context = req;
	slist_move_to_tail(&breq->buf_list, &buf_list);

	req->bdev = bdev;
	req->breq = breq;
	req->lba = first_lba;
	req->nr_blocks = nr_blocks;
	req->blocks_remaining = nr_blocks;
	req->blocks_allocated = blocks_allocated;
	req->context = context;

	return req;

err_alloc_buf:
	block_free_request(bdev, breq);
err_alloc_breq:
	free(req);
	return NULL;
}

void block_fill_free(struct block_fill_request *req)
{
	while (!slist_is_empty(&req->breq->buf_list)) {
		struct buffer	*buf;

		buf = slist_pop_head(&req->breq->buf_list, struct buffer, node);

		/* All buffers have the same size initially */
		buffer_dma_free(buf, BLOCK_FILL_BUF_SIZE);
	}

	block_free_request(req->bdev, req->breq);
	free(req);
}

void block_fill_set_byte_pattern(struct block_fill_request *req,
		uint8_t pattern)
{
	struct buffer	*buf;

	assert(req);
	assert(!slist_is_empty(&req->breq->buf_list));

	blk_req_for_each_buffer(req->breq, buf)
		memset(buf->addr.ptr, pattern, buf->len);
}

void block_fill_set_completion_callback(struct block_fill_request *req,
		block_fill_callback_t callback)
{
	assert(req);

	req->completion_callback = callback;
}

void block_fill_submit(struct block_fill_request *req)
{
	assert(req);
	assert(req->bdev);
	assert(req->breq);
	assert(!slist_is_empty(&req->breq->buf_list));

	workqueue_init_item(&req->work, block_fill_worker, req);

	block_submit_req(req->bdev, req->breq);
}
