/**
 * \file
 *
 * \brief Asynchronous Advanced Encryption Standard (AES) driver
 *
 * This is a fully asynchronous driver for the AES module present on
 * AT32UC3A3S. Data must be provided as a single linked list of buffers,
 * and as long as no reconfiguration of the AES module is required, request
 * queuing is supported.
 *
 * - 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 <assert.h>
#include <debug.h>
#include <malloc.h>
#include <status-codes.h>
#include <string.h>
#include <interrupt.h>
#include <chip/memory-map.h>
#include <aes.h>

#include "aes_regs.h"

/**
 * \brief Initialize AES module.
 *
 * This function will initialize the AES hardware module and create the DMA
 * channels needed for it to operate. The operation mode can be set by the
 * mode bit field.
 *
 * \param  module      Pointer to AES module struct
 * \param  mode        AES mode bit field
 */
void aes_init(struct aes_module *module, uint32_t mode)
{
	assert(module);

	module->port = (void *)AES_BASE;
	module->mode = mode | AES_MR_START_MODE_DMA;

	aes_write_reg(module->port, CR, AES_CR_SWRST);
	aes_write_reg(module->port, MR, module->mode);

	module->dma_rx_channel = dmac_aes_alloc_rx_channel();
	module->dma_tx_channel = dmac_aes_alloc_tx_channel();

	module->counter = 0;
}

/**
 * \brief Free AES module.
 *
 * This function will free the DMA channels allocated by aes_init(), which
 * afterwards will have to be run again in order to use the AES module.
 *
 * \param  module      Pointer to AES module struct
 */
void aes_free(struct aes_module *module)
{
	assert(module);

	module->port = NULL;
	dmac_aes_free_channel(module->dma_rx_channel);
	dmac_aes_free_channel(module->dma_tx_channel);
}

/**
 * \brief Change AES cipher mode.
 *
 * This function will change the current AES cipher mode to encrypt or
 * decrypt, depending on the mode paramater.
 *
 * \param  module      Pointer to AES module struct
 * \param  mode        AES cipher mode mask
 */
void aes_set_cipher(struct aes_module *module, uint32_t mode)
{
	assert(module);

	module->mode = (module->mode & ~AES_MR_CIPHER_MASK) |
			(mode & AES_MR_CIPHER_MASK);

	aes_write_reg(module->port, CR, AES_CR_SWRST);
	aes_write_reg(module->port, MR, module->mode);
}

/**
 * \brief Get AES cipher mode.
 *
 * This function will return the current AES cipher mode. It can be used to
 * make sure the correct mode is enabled before submitting requests.
 *
 * \param  module       Pointer to AES module struct
 */
uint32_t aes_get_cipher(struct aes_module *module)
{
	assert(module);

	return (module->mode & AES_MR_CIPHER_MASK);
}

/**
 * \brief Load initialization vector.
 *
 * This function will load an initialization vector to the AES module. This
 * vector is needed for when performing block chained encryption.
 *
 * \param  module      Pointer to AES module struct
 * \param  key         Initialization vector
 */
void aes_load_init_vector(struct aes_module *module, const uint32_t *vector)
{
	int i;

	assert(module);
	assert(vector);

	for (i = 0; i < 4; i++)
		aes_write_init_reg(module->port, i, *vector++);
}

/**
 * \brief Load AES key.
 *
 * This function will load the key the AES module will use for encryption
 * and decryption into the hardware. aes_init() must be run first and
 * the key provided here must correspond to the keysize selected when the
 * module was initialized.
 *
 * \param  module      Pointer to AES module struct
 * \param  key         AES key
 */
void aes_load_key(struct aes_module *module, const uint32_t *key)
{
	int i;
	int key_length = 0;

	assert(module);
	assert(key);

	switch (module->mode & AES_KEYSIZE_MASK) {
	case AES_KEYSIZE_128:
		key_length = 4;
		break;
	case AES_KEYSIZE_192:
		key_length = 6;
		break;
	case AES_KEYSIZE_256:
		key_length = 8;
		break;
	default:
		unhandled_case(module->mode);
	}

	for (i = 0; i < key_length; i++)
		aes_write_key_reg(module->port, i, *key++);
}

/**
 * \brief Free a duplicated buffer list.
 *
 * This function will free all the buffers allocated by
 * aes_duplicate_buffer_refs().
 *
 * \param buf_list	The buffer list to free
 */
void aes_free_duplicate_buffers(struct slist *buf_list)
{
	struct buffer *buf;

	assert(buf_list);

	while (!slist_is_empty(buf_list)) {
		buf = slist_pop_head(buf_list, struct buffer, node);
		buffer_free(buf);
	}
}

/**
 * \brief Create duplicate references to the data in a buffer list.
 *
 * This function will create a duplicate buffer list, mirroring the
 * buffers in the original buffer list. The new buffers will refer to the
 * data stored in the original buffers. If the two buffer lists are
 * submitted as tx and rx buffers to the AES driver, the encryption will
 * be performed in-place.
 *
 * \param copy		Duplicated buffer list
 * \param original	The original buffer list
 *
 * \return 0		Successfull duplication
 * \return -1		Duplication failed, no new buffers allocated
 */
int aes_duplicate_buffer_refs(struct slist *copy, struct slist *original)
{
	struct buffer *buf;
	struct buffer *temp;

	assert(copy);
	assert(original);

	slist_for_each(original, buf, node) {
		temp = buffer_alloc();
		if (unlikely(!temp))
			goto ret_error;
		temp->addr.ptr = buf->addr.ptr;
		temp->addr.phys = buf->addr.phys;
		temp->len = buf->len;
		slist_insert_tail(copy, &temp->node);
	}
	return 0;

ret_error:
	aes_free_duplicate_buffers(copy);
	return -1;
}

static void aes_dma_tx_done(struct dmac_channel *chan,
		struct dmac_request *req)
{
	struct aes_request *areq = req->context;

	aes_free_duplicate_buffers(&req->buf_list);
	free(req);

	if (++areq->done_counter >= 2 && areq->req_done)
		areq->req_done(areq, &areq->rx_buf_list, areq->context);
}

static void aes_dma_rx_done(struct dmac_channel *chan,
		struct dmac_request *req)
{
	struct aes_request *areq = req->context;
	unsigned int iflags;

	assert(req);
	assert(areq);

	iflags = cpu_irq_save();
	if (!slist_is_empty(&req->buf_list))
		slist_move_to_tail(&areq->rx_buf_list, &req->buf_list);
	cpu_irq_restore(iflags);

	free(req);

	if (++areq->done_counter >= 2 && areq->req_done)
		areq->req_done(areq, &areq->rx_buf_list, areq->context);
}

static void aes_dma_req_init(struct dmac_request *req,
		struct aes_request *areq, enum dma_direction dir)
{
	memset(req, 0, sizeof(struct dmac_request));
	dmac_req_init(req);
	req->direction = dir;

	switch (dir) {
	case DMA_TO_DEVICE:
		req->req_done = aes_dma_tx_done;
		break;

	case DMA_FROM_DEVICE:
		req->req_done = aes_dma_rx_done;
		break;

	default:
		unhandled_case(dir);
	}

	req->reg_width = DMAC_REG_WIDTH_32BIT;
	req->burst_length = DMAC_BURST_LENGTH_4;
	req->context = areq;
}

/**
 * \brief Submit AES request.
 *
 * This function will submit one AES request for encryption/decryption
 * by the AES hardware module. It will allocate two DMA requests for transfer
 * to and from the AES module and submit these to their repsective DMA
 * channels. The memory allocated for DMA requests will automatically be
 * freed prior to calling the callback.
 *
 * \param  req      Pointer to AES request struct
 */
void aes_submit_request(struct aes_request *req)
{
	struct dmac_request *tx_req = &req->tx_req;
	struct dmac_request *rx_req = &req->rx_req;

	assert(req);

	aes_dma_req_init(tx_req, req, DMA_TO_DEVICE);
	aes_dma_req_init(rx_req, req, DMA_FROM_DEVICE);
	req->done_counter = 0;

	if (!slist_is_empty(&req->tx_buf_list)) {
		/* Move buffers to DMA requests. */
		slist_move_to_tail(&tx_req->buf_list, &req->tx_buf_list);
		slist_move_to_tail(&rx_req->buf_list, &req->rx_buf_list);

		/* Submit DMA requests to the DMA controller. */
		dmac_chan_submit_request(req->module->dma_rx_channel, rx_req);
		dmac_chan_submit_request(req->module->dma_tx_channel, tx_req);
	} else {
		/* Empty request; skip actual encryption, clean up and
		 * run callback. */
		aes_dma_tx_done(req->module->dma_tx_channel, tx_req);
		aes_dma_rx_done(req->module->dma_rx_channel, rx_req);
	}
}

/**
 * \brief Allocate AES request.
 *
 * This function will allocate memory for one AES request.
 *
 * \param  module      Pointer to AES module struct
 *
 * \return Pointer to newly allocated AES request struct
 */
struct aes_request *aes_alloc_request(struct aes_module *module)
{
	struct aes_request *req;

	assert(module);
	/* Try to catch AES usage without running init first. */
	assert(module->port);

	req = malloc(sizeof(struct aes_request));
	assert(req);
	req->module = module;

	return req;
}

/**
 * \brief Free AES request.
 *
 * This function will free the memory allocate by an AES request.
 *
 * \param  req         Pointer to AES request struct
 */
void aes_free_request(struct aes_request *req)
{
	assert(slist_is_empty(&req->tx_buf_list));
	assert(slist_is_empty(&req->rx_buf_list));

	free(req);
}

/**
 * \brief Prepare an AES request.
 *
 * This function will prepare an AES request for submittion. The buffers in
 * buf_list is copied to an inernal list in the request. A new bufer list
 * mirroring the input buffer list is created and will be used to hold the
 * AES output (encrypted data when encrypting). When the operation is done,
 * the input buffers are thrown away and a pointer to the output buffer list
 * is returned to the callback function.
 *
 * \param  req         Pointer to AES request struct
 * \param  buf_list    Buffer list containing input data.
 */
void aes_prepare_request(struct aes_request *req, struct slist *buf_list)
{
	slist_init(&req->tx_buf_list);
	slist_init(&req->rx_buf_list);

	if (!slist_is_empty(buf_list)) {
		assert(!aes_duplicate_buffer_refs(
				&req->rx_buf_list, buf_list));
		slist_move_to_tail(&req->tx_buf_list, buf_list);
	}
}
