mirror of
https://github.com/OpenAMP/libmetal.git
synced 2026-09-21 08:53:47 +08:00
linux: irq: implement Linux IRQ controller
Implement Linux IRQ controller with updated libmetal IRQ abstraction. Signed-off-by: Wendy Liang <wendy.liang@xilinx.com>
This commit is contained in:
+96
-154
@@ -13,6 +13,7 @@
|
||||
#include <sched.h>
|
||||
#include <metal/device.h>
|
||||
#include <metal/irq.h>
|
||||
#include <metal/irq_controller.h>
|
||||
#include <metal/sys.h>
|
||||
#include <metal/mutex.h>
|
||||
#include <metal/list.h>
|
||||
@@ -27,153 +28,85 @@
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAX_IRQS FD_SETSIZE /**< maximum number of irqs */
|
||||
#define METAL_IRQ_STOP 0xFFFFFFFF /**< stop interrupts handling thread */
|
||||
#define MAX_IRQS (FD_SETSIZE - 1) /**< maximum number of irqs */
|
||||
|
||||
#define METAL_LINUX_IRQ_DISABLED 0 /**< IRQ is disabled */
|
||||
#define METAL_LINUX_IRQ_ENABLED 1 /**< IRQ is enabled */
|
||||
static struct metal_device *irqs_devs[MAX_IRQS]; /**< Linux devices for IRQs */
|
||||
static int irq_notify_fd; /**< irq handling state change notification file
|
||||
descriptor */
|
||||
static metal_mutex_t irq_lock; /**< irq handling lock */
|
||||
|
||||
/** IRQ descriptor structure */
|
||||
struct metal_irq_desc {
|
||||
metal_irq_handler hd; /**< irq handler */
|
||||
struct metal_device *dev; /**< metal device */
|
||||
void *drv_id; /**< id to identify the driver
|
||||
of the irq handler*/
|
||||
bool state; /**< IRQ enabling state */
|
||||
};
|
||||
static bool irq_handling_stop; /**< stop interrupts handling */
|
||||
|
||||
struct metal_irqs_state {
|
||||
struct metal_irq_desc hds[MAX_IRQS]; /**< irqs handlers descriptor */
|
||||
int irq_reg_fd; /**< irqs registration notification file
|
||||
descriptor */
|
||||
static pthread_t irq_pthread; /**< irq handling thread id */
|
||||
|
||||
metal_mutex_t irq_lock; /**< irq handling lock */
|
||||
/**< Indicate which IRQ is enabled */
|
||||
static unsigned long
|
||||
irqs_enabled[metal_div_round_up(MAX_IRQS, METAL_BITS_PER_ULONG)];
|
||||
|
||||
unsigned int irq_state; /**< global irq handling state */
|
||||
static struct metal_irq irqs[MAX_IRQS]; /**< Linux IRQs array */
|
||||
|
||||
pthread_t irq_pthread; /**< irq handling thread id */
|
||||
};
|
||||
/* Static functions */
|
||||
static void metal_linux_irq_set_enable(struct metal_irq_controller *irq_cntr,
|
||||
int irq, unsigned int state);
|
||||
|
||||
struct metal_irqs_state _irqs;
|
||||
|
||||
int metal_irq_register(int irq,
|
||||
metal_irq_handler hd,
|
||||
struct metal_device *dev,
|
||||
void *drv_id)
|
||||
{
|
||||
uint64_t val = 1;
|
||||
int ret;
|
||||
|
||||
if ((irq < 0) || (irq >= MAX_IRQS)) {
|
||||
metal_log(METAL_LOG_ERROR,
|
||||
"%s: irq %d is larger than the max supported %d.\n",
|
||||
__func__, irq, MAX_IRQS - 1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
if (_irqs.irq_state == METAL_IRQ_STOP) {
|
||||
metal_log(METAL_LOG_ERROR,
|
||||
"%s: failed. metal IRQ handling has stopped.\n",
|
||||
__func__);
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (_irqs.hds[irq].hd != NULL && hd != NULL &&
|
||||
_irqs.hds[irq].hd != hd) {
|
||||
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;
|
||||
}
|
||||
|
||||
_irqs.hds[irq].hd = hd;
|
||||
_irqs.hds[irq].dev = dev;
|
||||
_irqs.hds[irq].drv_id = drv_id;
|
||||
if (hd != NULL)
|
||||
_irqs.hds[irq].state = METAL_LINUX_IRQ_ENABLED;
|
||||
else
|
||||
_irqs.hds[irq].state = METAL_LINUX_IRQ_DISABLED;
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
|
||||
ret = write(_irqs.irq_reg_fd, &val, sizeof(val));
|
||||
if (ret < 0) {
|
||||
metal_log(METAL_LOG_DEBUG, "%s: write failed IRQ %d\n",
|
||||
__func__, irq);
|
||||
}
|
||||
|
||||
metal_log(METAL_LOG_DEBUG, "%s: registered IRQ %d\n", __func__, irq);
|
||||
return 0;
|
||||
}
|
||||
/**< Linux IRQ controller */
|
||||
static METAL_IRQ_CONTROLLER_DECLARE(linux_irq_cntr,
|
||||
0, MAX_IRQS,
|
||||
NULL,
|
||||
metal_linux_irq_set_enable, NULL,
|
||||
irqs)
|
||||
|
||||
unsigned int metal_irq_save_disable()
|
||||
{
|
||||
/* This is to avoid deadlock if it is called in ISR */
|
||||
if (pthread_self() == _irqs.irq_pthread)
|
||||
if (pthread_self() == irq_pthread)
|
||||
return 0;
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
metal_mutex_acquire(&irq_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void metal_irq_restore_enable(unsigned flags)
|
||||
{
|
||||
(void)flags;
|
||||
if (pthread_self() != _irqs.irq_pthread)
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
if (pthread_self() != irq_pthread)
|
||||
metal_mutex_release(&irq_lock);
|
||||
}
|
||||
|
||||
void metal_irq_enable(unsigned int vector)
|
||||
static int metal_linux_irq_notify()
|
||||
{
|
||||
uint64_t val = 1;
|
||||
int ret;
|
||||
|
||||
if (vector >= MAX_IRQS) {
|
||||
metal_log(METAL_LOG_ERROR,
|
||||
"%s: irq %d is larger than the max supported %d.\n",
|
||||
__func__, vector, MAX_IRQS - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
if (_irqs.irq_state == METAL_IRQ_STOP) {
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
return;
|
||||
}
|
||||
_irqs.hds[vector].state = METAL_LINUX_IRQ_ENABLED;
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
|
||||
ret = write(_irqs.irq_reg_fd, &val, sizeof(val));
|
||||
ret = write(irq_notify_fd, &val, sizeof(val));
|
||||
if (ret < 0) {
|
||||
metal_log(METAL_LOG_DEBUG, "%s: write failed IRQ %d\n",
|
||||
__func__, vector);
|
||||
metal_log(METAL_LOG_ERROR, "%s failed\n", __func__);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void metal_irq_disable(unsigned int vector)
|
||||
static void metal_linux_irq_set_enable(struct metal_irq_controller *irq_cntr,
|
||||
int irq, unsigned int state)
|
||||
{
|
||||
uint64_t val = 1;
|
||||
int ret;
|
||||
int offset, ret;
|
||||
|
||||
if (vector >= MAX_IRQS) {
|
||||
metal_log(METAL_LOG_ERROR,
|
||||
"%s: irq %d is larger than the max supported %d.\n",
|
||||
__func__, vector, MAX_IRQS - 1);
|
||||
if (irq < irq_cntr->irq_base ||
|
||||
irq >= irq_cntr->irq_base + irq_cntr->irq_num) {
|
||||
metal_log(METAL_LOG_ERROR, "%s: invalid irq %d\n",
|
||||
__func__, irq);
|
||||
return;
|
||||
}
|
||||
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
if (_irqs.irq_state == METAL_IRQ_STOP) {
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
return;
|
||||
}
|
||||
_irqs.hds[vector].state = METAL_LINUX_IRQ_DISABLED;
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
|
||||
ret = write(_irqs.irq_reg_fd, &val, sizeof(val));
|
||||
offset = irq - linux_irq_cntr.irq_base;
|
||||
metal_mutex_acquire(&irq_lock);
|
||||
if (state == METAL_IRQ_ENABLE)
|
||||
metal_bitmap_set_bit(irqs_enabled, offset);
|
||||
else
|
||||
metal_bitmap_clear_bit(irqs_enabled, offset);
|
||||
metal_mutex_release(&irq_lock);
|
||||
/* Notify IRQ thread that IRQ state has changed */
|
||||
ret = metal_linux_irq_notify();
|
||||
if (ret < 0) {
|
||||
metal_log(METAL_LOG_DEBUG, "%s: write failed IRQ %d\n",
|
||||
__func__, vector);
|
||||
metal_log(METAL_LOG_ERROR, "%s: failed to notify set %d enable\n",
|
||||
__func__, irq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +124,7 @@ static void *metal_linux_irq_handling(void *args)
|
||||
|
||||
(void) args;
|
||||
|
||||
pfds = (struct pollfd *)malloc(MAX_IRQS * sizeof(struct pollfd));
|
||||
pfds = (struct pollfd *)malloc(FD_SETSIZE * sizeof(struct pollfd));
|
||||
if (!pfds) {
|
||||
metal_log(METAL_LOG_ERROR, "%s: failed to allocate irq fds mem.\n",
|
||||
__func__);
|
||||
@@ -202,31 +135,30 @@ static void *metal_linux_irq_handling(void *args)
|
||||
/* Ignore the set scheduler error */
|
||||
ret = sched_setscheduler(0, SCHED_FIFO, ¶m);
|
||||
if (ret) {
|
||||
metal_log(METAL_LOG_WARNING, "%s: Failed to set scheduler: %d.\n",
|
||||
__func__, ret);
|
||||
metal_log(METAL_LOG_WARNING, "%s: Failed to set scheduler: %s.\n",
|
||||
__func__, strerror(ret));
|
||||
}
|
||||
|
||||
while (1) {
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
if (_irqs.irq_state == METAL_IRQ_STOP) {
|
||||
metal_mutex_acquire(&irq_lock);
|
||||
if (irq_handling_stop == true) {
|
||||
/* Killing this IRQ handling thread */
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
metal_mutex_release(&irq_lock);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Get the fdset */
|
||||
memset(pfds, 0, MAX_IRQS * sizeof(struct pollfd));
|
||||
pfds[0].fd = _irqs.irq_reg_fd;
|
||||
pfds[0].fd = irq_notify_fd;
|
||||
pfds[0].events = POLLIN;
|
||||
for(i = 0, j = 1; i < MAX_IRQS && j < MAX_IRQS; i++) {
|
||||
if (_irqs.hds[i].hd != NULL &&
|
||||
_irqs.hds[i].state == METAL_LINUX_IRQ_ENABLED) {
|
||||
pfds[j].fd = i;
|
||||
pfds[j].events = POLLIN;
|
||||
j++;
|
||||
}
|
||||
j = 1;
|
||||
metal_bitmap_for_each_set_bit(irqs_enabled, i,
|
||||
linux_irq_cntr.irq_num) {
|
||||
pfds[j].fd = i;
|
||||
pfds[j].events = POLLIN;
|
||||
j++;
|
||||
}
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
metal_mutex_release(&irq_lock);
|
||||
/* Wait for interrupt */
|
||||
ret = poll(pfds, j, -1);
|
||||
if (ret < 0) {
|
||||
@@ -237,7 +169,7 @@ static void *metal_linux_irq_handling(void *args)
|
||||
/* Waken up from interrupt */
|
||||
pfds_total = j;
|
||||
for (i = 0; i < pfds_total; i++) {
|
||||
if ( (pfds[i].fd == _irqs.irq_reg_fd) &&
|
||||
if ( (pfds[i].fd == irq_notify_fd) &&
|
||||
(pfds[i].revents & (POLLIN | POLLRDNORM))) {
|
||||
/* IRQ registration change notification */
|
||||
if (read(pfds[i].fd, (void*)&val, sizeof(uint64_t)) < 0)
|
||||
@@ -245,22 +177,21 @@ static void *metal_linux_irq_handling(void *args)
|
||||
"%s, read irq fd %d failed.\n",
|
||||
__func__, pfds[i].fd);
|
||||
} else if ((pfds[i].revents & (POLLIN | POLLRDNORM))) {
|
||||
struct metal_irq_desc *desc;
|
||||
struct metal_device *dev = NULL;
|
||||
int irq_handled = 0;
|
||||
int fd;
|
||||
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
desc = &_irqs.hds[pfds[i].fd];
|
||||
dev = desc->dev;
|
||||
|
||||
if (desc->hd(pfds[i].fd, desc->drv_id)
|
||||
fd = pfds[i].fd;
|
||||
dev = irqs_devs[fd];
|
||||
metal_mutex_acquire(&irq_lock);
|
||||
if (metal_irq_handle(&irqs[fd], fd)
|
||||
== METAL_IRQ_HANDLED)
|
||||
irq_handled = 1;
|
||||
if (irq_handled) {
|
||||
if (dev && dev->bus->ops.dev_irq_ack)
|
||||
dev->bus->ops.dev_irq_ack(dev->bus, dev, i);
|
||||
dev->bus->ops.dev_irq_ack(dev->bus, dev, fd);
|
||||
}
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
metal_mutex_release(&irq_lock);
|
||||
} else if (pfds[i].revents) {
|
||||
metal_log(METAL_LOG_DEBUG,
|
||||
"%s: poll unexpected. fd %d: %d\n",
|
||||
@@ -280,16 +211,23 @@ int metal_linux_irq_init()
|
||||
{
|
||||
int ret;
|
||||
|
||||
memset(&_irqs, 0, sizeof(_irqs));
|
||||
memset(&irqs, 0, sizeof(irqs));
|
||||
|
||||
_irqs.irq_reg_fd = eventfd(0,0);
|
||||
if (_irqs.irq_reg_fd < 0) {
|
||||
irq_notify_fd = eventfd(0,0);
|
||||
if (irq_notify_fd < 0) {
|
||||
metal_log(METAL_LOG_ERROR, "Failed to create eventfd for IRQ handling.\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
metal_mutex_init(&_irqs.irq_lock);
|
||||
ret = pthread_create(&_irqs.irq_pthread, NULL,
|
||||
metal_mutex_init(&irq_lock);
|
||||
irq_handling_stop = false;
|
||||
ret = metal_irq_register_controller(&linux_irq_cntr);
|
||||
if (ret < 0) {
|
||||
metal_log(METAL_LOG_ERROR,
|
||||
"Linux IRQ controller failed to register.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = pthread_create(&irq_pthread, NULL,
|
||||
metal_linux_irq_handling, NULL);
|
||||
if (ret != 0) {
|
||||
metal_log(METAL_LOG_ERROR, "Failed to create IRQ thread: %d.\n", ret);
|
||||
@@ -305,20 +243,24 @@ int metal_linux_irq_init()
|
||||
void metal_linux_irq_shutdown()
|
||||
{
|
||||
int ret;
|
||||
uint64_t val = 1;
|
||||
|
||||
metal_log(METAL_LOG_DEBUG, "%s\n", __func__);
|
||||
metal_mutex_acquire(&_irqs.irq_lock);
|
||||
_irqs.irq_state = METAL_IRQ_STOP;
|
||||
metal_mutex_release(&_irqs.irq_lock);
|
||||
ret = write (_irqs.irq_reg_fd, &val, sizeof(val));
|
||||
if (ret < 0) {
|
||||
metal_log(METAL_LOG_ERROR, "Failed to write.\n");
|
||||
}
|
||||
ret = pthread_join(_irqs.irq_pthread, NULL);
|
||||
irq_handling_stop = true;
|
||||
metal_linux_irq_notify();
|
||||
ret = pthread_join(irq_pthread, NULL);
|
||||
if (ret) {
|
||||
metal_log(METAL_LOG_ERROR, "Failed to join IRQ thread: %d.\n", ret);
|
||||
}
|
||||
close(_irqs.irq_reg_fd);
|
||||
metal_mutex_deinit(&_irqs.irq_lock);
|
||||
close(irq_notify_fd);
|
||||
metal_mutex_deinit(&irq_lock);
|
||||
}
|
||||
|
||||
void metal_linux_irq_register_dev(struct metal_device *dev, int irq)
|
||||
{
|
||||
if (irq > MAX_IRQS) {
|
||||
metal_log(METAL_LOG_ERROR, "Failed to register device to irq %d\n",
|
||||
irq);
|
||||
return;
|
||||
}
|
||||
irqs_devs[irq] = dev;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
#endif
|
||||
|
||||
#ifndef __METAL_LINUX_IRQ__H__
|
||||
#ifdef METAL_INTERNAL
|
||||
|
||||
#include <metal/device.h>
|
||||
|
||||
/**
|
||||
* @brief metal_linux_register_dev
|
||||
*
|
||||
* Metal Linux internal function to register metal device to a IRQ
|
||||
* which is generated from the device.
|
||||
*
|
||||
* @param[in] dev pointer to metal device
|
||||
* @param[in] irq interrupt id
|
||||
*/
|
||||
void metal_linux_irq_register_dev(struct metal_device *dev, int irq);
|
||||
|
||||
#endif /* METAL_INTERNAL */
|
||||
#define __METAL_LINUX_IRQ__H__
|
||||
|
||||
#endif /* __METAL_LINUX_IRQ__H__ */
|
||||
|
||||
Reference in New Issue
Block a user