From 9613b6f659ea0c4cc05e075fff9849ecb933adaa Mon Sep 17 00:00:00 2001 From: Wendy Liang Date: Wed, 2 Jan 2019 15:11:53 -0800 Subject: [PATCH] linux: irq: implement Linux IRQ controller Implement Linux IRQ controller with updated libmetal IRQ abstraction. Signed-off-by: Wendy Liang --- lib/system/linux/irq.c | 250 ++++++++++++++++------------------------- lib/system/linux/irq.h | 16 +++ 2 files changed, 112 insertions(+), 154 deletions(-) diff --git a/lib/system/linux/irq.c b/lib/system/linux/irq.c index 8955181..0151bc4 100644 --- a/lib/system/linux/irq.c +++ b/lib/system/linux/irq.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -27,153 +28,85 @@ #include #include -#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; } diff --git a/lib/system/linux/irq.h b/lib/system/linux/irq.h index f0cd27b..ebb87cd 100644 --- a/lib/system/linux/irq.h +++ b/lib/system/linux/irq.h @@ -14,6 +14,22 @@ #endif #ifndef __METAL_LINUX_IRQ__H__ +#ifdef METAL_INTERNAL + +#include + +/** + * @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__ */