280 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2016 - 2017, Xilinx Inc. and Contributors. All rights reserved.
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * @file	freertos/irq.c
 | 
						|
 * @brief	FreeRTOS libmetal irq definitions.
 | 
						|
 */
 | 
						|
 | 
						|
#include <metal/errno.h>
 | 
						|
#include <metal/irq.h>
 | 
						|
#include <metal/sys.h>
 | 
						|
#include <metal/log.h>
 | 
						|
#include <metal/mutex.h>
 | 
						|
#include <metal/list.h>
 | 
						|
#include <metal/utilities.h>
 | 
						|
#include <metal/alloc.h>
 | 
						|
 | 
						|
/** IRQ handlers descriptor structure */
 | 
						|
struct metal_irq_hddesc {
 | 
						|
	metal_irq_handler hd;     /**< irq handler */
 | 
						|
	void *drv_id;             /**< id to identify the driver
 | 
						|
	                               of the irq handler */
 | 
						|
	struct metal_device *dev; /**< device identifier */
 | 
						|
	struct metal_list node;   /**< node on irq handlers list */
 | 
						|
};
 | 
						|
 | 
						|
/** IRQ descriptor structure */
 | 
						|
struct metal_irq_desc {
 | 
						|
	int irq;                  /**< interrupt number */
 | 
						|
	struct metal_list hdls;   /**< interrupt handlers */
 | 
						|
	struct metal_list node;   /**< node on irqs list */
 | 
						|
};
 | 
						|
 | 
						|
/** IRQ state structure */
 | 
						|
struct metal_irqs_state {
 | 
						|
	struct metal_list irqs;    /**< interrupt descriptors */
 | 
						|
	metal_mutex_t irq_lock;    /**< access lock */
 | 
						|
};
 | 
						|
 | 
						|
static struct metal_irqs_state _irqs = {
 | 
						|
	.irqs = METAL_INIT_LIST(_irqs.irqs),
 | 
						|
	.irq_lock = METAL_MUTEX_INIT(_irqs.irq_lock),
 | 
						|
};
 | 
						|
 | 
						|
int metal_irq_register(int irq,
 | 
						|
                       metal_irq_handler hd,
 | 
						|
                       struct metal_device *dev,
 | 
						|
                       void *drv_id)
 | 
						|
{
 | 
						|
	struct metal_irq_desc *irq_p = NULL;
 | 
						|
	struct metal_irq_hddesc *hdl_p;
 | 
						|
	struct metal_list *node;
 | 
						|
	unsigned int irq_flags_save;
 | 
						|
 | 
						|
	if (irq < 0) {
 | 
						|
		metal_log(METAL_LOG_ERROR,
 | 
						|
			  "%s: irq %d need to be a positive number\n",
 | 
						|
		          __func__, irq);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((drv_id == NULL) || (hd == NULL)) {
 | 
						|
		metal_log(METAL_LOG_ERROR, "%s: irq %d need drv_id and hd.\n",
 | 
						|
		          __func__, irq);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Search for irq in list */
 | 
						|
	metal_mutex_acquire(&_irqs.irq_lock);
 | 
						|
	metal_list_for_each(&_irqs.irqs, node) {
 | 
						|
		irq_p = metal_container_of(node, struct metal_irq_desc, node);
 | 
						|
 | 
						|
		if (irq_p->irq == irq) {
 | 
						|
			struct metal_list *h_node;
 | 
						|
 | 
						|
			/* Check if drv_id already exist */
 | 
						|
			metal_list_for_each(&irq_p->hdls, h_node) {
 | 
						|
				hdl_p = metal_container_of(h_node,
 | 
						|
							   struct metal_irq_hddesc,
 | 
						|
							   node);
 | 
						|
 | 
						|
				/* if drv_id already exist reject */
 | 
						|
				if ((hdl_p->drv_id == drv_id) &&
 | 
						|
					((dev == NULL) || (hdl_p->dev == dev))) {
 | 
						|
					metal_log(METAL_LOG_ERROR,
 | 
						|
						  "%s: irq %d already registered."
 | 
						|
							"Will not register again.\n",
 | 
						|
							__func__, irq);
 | 
						|
					metal_mutex_release(&_irqs.irq_lock);
 | 
						|
					return -EINVAL;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			/* irq found and drv_id not used, get out of metal_list_for_each */
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Either need to add handler to an existing list or to a new one */
 | 
						|
	hdl_p = metal_allocate_memory(sizeof(struct metal_irq_hddesc));
 | 
						|
	if (hdl_p == NULL) {
 | 
						|
		metal_log(METAL_LOG_ERROR,
 | 
						|
		          "%s: irq %d cannot allocate mem for drv_id %d.\n",
 | 
						|
		          __func__, irq, drv_id);
 | 
						|
		metal_mutex_release(&_irqs.irq_lock);
 | 
						|
		return -ENOMEM;
 | 
						|
	}
 | 
						|
	hdl_p->hd = hd;
 | 
						|
	hdl_p->drv_id = drv_id;
 | 
						|
	hdl_p->dev = dev;
 | 
						|
 | 
						|
	/* interrupt already registered, add handler to existing list*/
 | 
						|
	if ((irq_p != NULL) && (irq_p->irq == irq)) {
 | 
						|
		irq_flags_save = metal_irq_save_disable();
 | 
						|
		metal_list_add_tail(&irq_p->hdls, &hdl_p->node);
 | 
						|
		metal_irq_restore_enable(irq_flags_save);
 | 
						|
 | 
						|
		metal_log(METAL_LOG_DEBUG, "%s: success, irq %d add drv_id %p \n",
 | 
						|
		          __func__, irq, drv_id);
 | 
						|
		metal_mutex_release(&_irqs.irq_lock);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	/* interrupt was not already registered, add */
 | 
						|
	irq_p = metal_allocate_memory(sizeof(struct metal_irq_desc));
 | 
						|
	if (irq_p == NULL) {
 | 
						|
		metal_log(METAL_LOG_ERROR, "%s: irq %d cannot allocate mem.\n",
 | 
						|
		          __func__, irq);
 | 
						|
		metal_mutex_release(&_irqs.irq_lock);
 | 
						|
		return -ENOMEM;
 | 
						|
	}
 | 
						|
	irq_p->irq = irq;
 | 
						|
	metal_list_init(&irq_p->hdls);
 | 
						|
	metal_list_add_tail(&irq_p->hdls, &hdl_p->node);
 | 
						|
 | 
						|
	irq_flags_save = metal_irq_save_disable();
 | 
						|
	metal_list_add_tail(&_irqs.irqs, &irq_p->node);
 | 
						|
	metal_irq_restore_enable(irq_flags_save);
 | 
						|
 | 
						|
	metal_log(METAL_LOG_DEBUG, "%s: success, added irq %d\n", __func__, irq);
 | 
						|
	metal_mutex_release(&_irqs.irq_lock);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* helper function for metal_irq_unregister() */
 | 
						|
static void metal_irq_delete_node(struct metal_list *node, void *p_to_free)
 | 
						|
{
 | 
						|
	unsigned int irq_flags_save;
 | 
						|
 | 
						|
	irq_flags_save=metal_irq_save_disable();
 | 
						|
	metal_list_del(node);
 | 
						|
	metal_irq_restore_enable(irq_flags_save);
 | 
						|
	metal_free_memory(p_to_free);
 | 
						|
}
 | 
						|
 | 
						|
int metal_irq_unregister(int irq,
 | 
						|
                         metal_irq_handler hd,
 | 
						|
                         struct metal_device *dev,
 | 
						|
                         void *drv_id)
 | 
						|
{
 | 
						|
	struct metal_irq_desc *irq_p;
 | 
						|
	struct metal_list *node;
 | 
						|
 | 
						|
	if (irq < 0) {
 | 
						|
		metal_log(METAL_LOG_ERROR, "%s: irq %d need to be a positive number\n",
 | 
						|
		          __func__, irq);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Search for irq in list */
 | 
						|
	metal_mutex_acquire(&_irqs.irq_lock);
 | 
						|
	metal_list_for_each(&_irqs.irqs, node) {
 | 
						|
 | 
						|
		irq_p = metal_container_of(node, struct metal_irq_desc, node);
 | 
						|
 | 
						|
		if (irq_p->irq == irq) {
 | 
						|
			struct metal_list *h_node, *h_prenode;
 | 
						|
			struct metal_irq_hddesc *hdl_p;
 | 
						|
			unsigned int delete_count = 0;
 | 
						|
 | 
						|
			metal_log(METAL_LOG_DEBUG, "%s: found irq %d\n",
 | 
						|
				  __func__, irq);
 | 
						|
 | 
						|
			/* Search through handlers */
 | 
						|
			metal_list_for_each(&irq_p->hdls, h_node) {
 | 
						|
				hdl_p = metal_container_of(h_node,
 | 
						|
							   struct metal_irq_hddesc,
 | 
						|
							   node);
 | 
						|
 | 
						|
				if (((hd == NULL) || (hdl_p->hd == hd)) &&
 | 
						|
				    ((drv_id == NULL) || (hdl_p->drv_id == drv_id)) &&
 | 
						|
				    ((dev    == NULL) || (hdl_p->dev == dev))) {
 | 
						|
					metal_log(METAL_LOG_DEBUG,
 | 
						|
					          "%s: unregister hd=%p drv_id=%p dev=%p\n",
 | 
						|
						  __func__, hdl_p->hd, hdl_p->drv_id, hdl_p->dev);
 | 
						|
					h_prenode = h_node->prev;
 | 
						|
					metal_irq_delete_node(h_node, hdl_p);
 | 
						|
					delete_count++;
 | 
						|
					h_node = h_prenode;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			/* we did not find any handler to delete */
 | 
						|
			if (!delete_count) {
 | 
						|
				metal_log(METAL_LOG_DEBUG, "%s: No matching entry\n",
 | 
						|
				          __func__);
 | 
						|
				metal_mutex_release(&_irqs.irq_lock);
 | 
						|
				return -ENOENT;
 | 
						|
 | 
						|
			}
 | 
						|
 | 
						|
			/* if interrupt handlers list is empty, unregister interrupt */
 | 
						|
			if (metal_list_is_empty(&irq_p->hdls)) {
 | 
						|
				metal_log(METAL_LOG_DEBUG,
 | 
						|
				          "%s: handlers list empty, unregister interrupt\n",
 | 
						|
					  __func__);
 | 
						|
				metal_irq_delete_node(node, irq_p);
 | 
						|
			}
 | 
						|
 | 
						|
			metal_log(METAL_LOG_DEBUG, "%s: success\n", __func__);
 | 
						|
 | 
						|
			metal_mutex_release(&_irqs.irq_lock);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	metal_log(METAL_LOG_DEBUG, "%s: No matching IRQ entry\n", __func__);
 | 
						|
 | 
						|
	metal_mutex_release(&_irqs.irq_lock);
 | 
						|
	return -ENOENT;
 | 
						|
}
 | 
						|
 | 
						|
unsigned int metal_irq_save_disable(void)
 | 
						|
{
 | 
						|
	return sys_irq_save_disable();
 | 
						|
}
 | 
						|
 | 
						|
void metal_irq_restore_enable(unsigned int flags)
 | 
						|
{
 | 
						|
	sys_irq_restore_enable(flags);
 | 
						|
}
 | 
						|
 | 
						|
void metal_irq_enable(unsigned int vector)
 | 
						|
{
 | 
						|
	sys_irq_enable(vector);
 | 
						|
}
 | 
						|
 | 
						|
void metal_irq_disable(unsigned int vector)
 | 
						|
{
 | 
						|
	sys_irq_disable(vector);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief default handler
 | 
						|
 */
 | 
						|
void metal_irq_isr(unsigned int vector)
 | 
						|
{
 | 
						|
	struct metal_list *node;
 | 
						|
	struct metal_irq_desc *irq_p;
 | 
						|
 | 
						|
	metal_list_for_each(&_irqs.irqs, node) {
 | 
						|
		irq_p = metal_container_of(node, struct metal_irq_desc, node);
 | 
						|
 | 
						|
		if ((unsigned int)irq_p->irq == vector) {
 | 
						|
			struct metal_list *h_node;
 | 
						|
			struct metal_irq_hddesc *hdl_p;
 | 
						|
 | 
						|
			metal_list_for_each(&irq_p->hdls, h_node) {
 | 
						|
				hdl_p = metal_container_of(h_node,
 | 
						|
							   struct metal_irq_hddesc,
 | 
						|
							   node);
 | 
						|
 | 
						|
				(hdl_p->hd)(vector, hdl_p->drv_id);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |