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:
Wendy Liang
2019-01-31 09:57:34 -08:00
parent d433de24e0
commit 9613b6f659
2 changed files with 112 additions and 154 deletions
+96 -154
View File
@@ -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, &param);
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;
}
+16
View File
@@ -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__ */