/**
 * \file
 *
 * \brief Architecture-specific soft interrupt implementation
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All AVR32 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 <compiler.h>
#include <interrupt.h>
#include <softirq.h>
#include <util.h>

#include <app/softirq.h>

struct softirq_desc {
	softirq_handler_t	handler;
	void			*data;
};

/**
 * \internal
 * \brief Bitmask indicating the active status of each soft interrupt.
 */
unsigned long softirq_priv_status;

static struct softirq_desc softirq_table[SOFTIRQ_NR_IDS];

/**
 * \brief Install a soft interrupt handler.
 *
 * \param handler Function to be called when soft interrupt is active
 * \param data Arbitrary data to be passed to \a handler
 */
void softirq_set_handler(unsigned int id, softirq_handler_t handler, void *data)
{
	struct softirq_desc	*desc;

	build_assert(ARRAY_LEN(softirq_table)
			<= 8 * sizeof(softirq_priv_status));
	assert(id < ARRAY_LEN(softirq_table));
	assert(handler);

	desc = &softirq_table[id];
	desc->handler = handler;
	desc->data = data;
}

/**
 * \internal
 * \brief Execute handlers for all pending softirqs.
 *
 * This function is called with interrupts disabled and returns with
 * interrupts disabled, but interrupts are enabled most of the time
 * while the function is running.
 *
 * \pre Interrupts are disabled
 * \pre All interrupt levels are enabled
 * \pre At least one softirq is pending
 * \post Interrupts are disabled
 */
void softirq_priv_do_pending(void)
{
	struct softirq_desc	*desc;
	unsigned int		id;

	assert(!cpu_irq_is_enabled());
	assert(!(sysreg_read(SR) & (SYSREG_SR_IM(0) | SYSREG_SR_IM(1)
					| SYSREG_SR_IM(2) | SYSREG_SR_IM(3))));
	assert(softirq_priv_status);

	do {
		id = __ffs(softirq_priv_status);
		assert(id < ARRAY_LEN(softirq_table));
		clear_bit(id, &softirq_priv_status);

		cpu_irq_enable();

		desc = &softirq_table[id];
		assert(desc->handler);
		desc->handler(desc->data);

		cpu_irq_disable();
	} while (softirq_priv_status);

	assert(!cpu_irq_is_enabled());
}

/**
 * \brief Enable softirq processing
 *
 * This function will also process any softirqs raised while softirq
 * processing was disabled.
 *
 * \pre Interrupts are not masked
 */
void softirq_enable(void)
{
	assert(cpu_irq_is_enabled());

	cpu_irq_disable();
	if (softirq_priv_status)
		softirq_priv_do_pending();
	avr32_clear_sr_bit(SYSREG_SR_T_BIT);
	cpu_irq_enable();
}
