/* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2010-2018 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include "eal_private.h" #include "eal_alarm_private.h" #define MAX_INTR_EVENTS 16 /** * union buffer for reading on different devices */ union rte_intr_read_buffer { char charbuf[16]; /* for others */ }; TAILQ_HEAD(rte_intr_cb_list, rte_intr_callback); TAILQ_HEAD(rte_intr_source_list, rte_intr_source); struct rte_intr_callback { TAILQ_ENTRY(rte_intr_callback) next; rte_intr_callback_fn cb_fn; /**< callback address */ void *cb_arg; /**< parameter for callback */ uint8_t pending_delete; /**< delete after callback is called */ rte_intr_unregister_callback_fn ucb_fn; /**< fn to call before cb is deleted */ }; struct rte_intr_source { TAILQ_ENTRY(rte_intr_source) next; struct rte_intr_handle *intr_handle; /**< interrupt handle */ struct rte_intr_cb_list callbacks; /**< user callbacks */ uint32_t active; }; /* global spinlock for interrupt data operation */ static rte_spinlock_t intr_lock = RTE_SPINLOCK_INITIALIZER; /* interrupt sources list */ static struct rte_intr_source_list intr_sources; /* interrupt handling thread */ static pthread_t intr_thread; static volatile int kq = -1; static int intr_source_to_kevent(const struct rte_intr_handle *ih, struct kevent *ke) { /* alarm callbacks are special case */ if (rte_intr_type_get(ih) == RTE_INTR_HANDLE_ALARM) { uint64_t timeout_ns; /* get soonest alarm timeout */ if (eal_alarm_get_timeout_ns(&timeout_ns) < 0) return -1; ke->filter = EVFILT_TIMER; /* timers are one shot */ ke->flags |= EV_ONESHOT; ke->fflags = NOTE_NSECONDS; ke->data = timeout_ns; } else { ke->filter = EVFILT_READ; } ke->ident = rte_intr_fd_get(ih); return 0; } int rte_intr_callback_register(const struct rte_intr_handle *intr_handle, rte_intr_callback_fn cb, void *cb_arg) { struct rte_intr_callback *callback; struct rte_intr_source *src; int ret = 0, add_event = 0; /* first do parameter checking */ if (rte_intr_fd_get(intr_handle) < 0 || cb == NULL) { RTE_LOG(ERR, EAL, "Registering with invalid input parameter\n"); return -EINVAL; } if (kq < 0) { RTE_LOG(ERR, EAL, "Kqueue is not active: %d\n", kq); return -ENODEV; } rte_spinlock_lock(&intr_lock); /* find the source for this intr_handle */ TAILQ_FOREACH(src, &intr_sources, next) { if (rte_intr_fd_get(src->intr_handle) == rte_intr_fd_get(intr_handle)) break; } /* if this is an alarm interrupt and it already has a callback, * then we don't want to create a new callback because the only * thing on the list should be eal_alarm_callback() and we may * be called just to reset the timer. */ if (src != NULL && rte_intr_type_get(src->intr_handle) == RTE_INTR_HANDLE_ALARM && !TAILQ_EMPTY(&src->callbacks)) { callback = NULL; } else { /* allocate a new interrupt callback entity */ callback = calloc(1, sizeof(*callback)); if (callback == NULL) { RTE_LOG(ERR, EAL, "Can not allocate memory\n"); ret = -ENOMEM; goto fail; } callback->cb_fn = cb; callback->cb_arg = cb_arg; callback->pending_delete = 0; callback->ucb_fn = NULL; if (src == NULL) { src = calloc(1, sizeof(*src)); if (src == NULL) { RTE_LOG(ERR, EAL, "Can not allocate memory\n"); ret = -ENOMEM; goto fail; } else { src->intr_handle = rte_intr_instance_dup(intr_handle); if (src->intr_handle == NULL) { RTE_LOG(ERR, EAL, "Can not create intr instance\n"); ret = -ENOMEM; free(src); src = NULL; goto fail; } TAILQ_INIT(&src->callbacks); TAILQ_INSERT_TAIL(&intr_sources, src, next); } } /* we had no interrupts for this */ if (TAILQ_EMPTY(&src->callbacks)) add_event = 1; TAILQ_INSERT_TAIL(&(src->callbacks), callback, next); } /* add events to the queue. timer events are special as we need to * re-set the timer. */ if (add_event || rte_intr_type_get(src->intr_handle) == RTE_INTR_HANDLE_ALARM) { struct kevent ke; memset(&ke, 0, sizeof(ke)); ke.flags = EV_ADD; /* mark for addition to the queue */ if (intr_source_to_kevent(intr_handle, &ke) < 0) { RTE_LOG(ERR, EAL, "Cannot convert interrupt handle to kevent\n"); ret = -ENODEV; goto fail; } /** * add the intr file descriptor into wait list. */ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) { /* currently, nic_uio does not support interrupts, so * this error will always be triggered and output to the * user. so, don't output it unless debug log level set. */ if (errno == ENODEV) RTE_LOG(DEBUG, EAL, "Interrupt handle %d not supported\n", rte_intr_fd_get(src->intr_handle)); else RTE_LOG(ERR, EAL, "Error adding fd %d kevent, %s\n", rte_intr_fd_get(src->intr_handle), strerror(errno)); ret = -errno; goto fail; } } rte_eal_trace_intr_callback_register(intr_handle, cb, cb_arg, ret); rte_spinlock_unlock(&intr_lock); return 0; fail: /* clean up */ if (src != NULL) { if (callback != NULL) TAILQ_REMOVE(&(src->callbacks), callback, next); if (TAILQ_EMPTY(&(src->callbacks))) { TAILQ_REMOVE(&intr_sources, src, next); free(src); } } free(callback); rte_eal_trace_intr_callback_register(intr_handle, cb, cb_arg, ret); rte_spinlock_unlock(&intr_lock); return ret; } int rte_intr_callback_unregister_pending(const struct rte_intr_handle *intr_handle, rte_intr_callback_fn cb_fn, void *cb_arg, rte_intr_unregister_callback_fn ucb_fn) { int ret; struct rte_intr_source *src; struct rte_intr_callback *cb, *next; /* do parameter checking first */ if (rte_intr_fd_get(intr_handle) < 0) { RTE_LOG(ERR, EAL, "Unregistering with invalid input parameter\n"); return -EINVAL; } if (kq < 0) { RTE_LOG(ERR, EAL, "Kqueue is not active\n"); return -ENODEV; } rte_spinlock_lock(&intr_lock); /* check if the interrupt source for the fd is existent */ TAILQ_FOREACH(src, &intr_sources, next) if (rte_intr_fd_get(src->intr_handle) == rte_intr_fd_get(intr_handle)) break; /* No interrupt source registered for the fd */ if (src == NULL) { ret = -ENOENT; /* only usable if the source is active */ } else if (src->active == 0) { ret = -EAGAIN; } else { ret = 0; /* walk through the callbacks and mark all that match. */ for (cb = TAILQ_FIRST(&src->callbacks); cb != NULL; cb = next) { next = TAILQ_NEXT(cb, next); if (cb->cb_fn == cb_fn && (cb_arg == (void *)-1 || cb->cb_arg == cb_arg)) { cb->pending_delete = 1; cb->ucb_fn = ucb_fn; ret++; } } } rte_spinlock_unlock(&intr_lock); return ret; } int rte_intr_callback_unregister(const struct rte_intr_handle *intr_handle, rte_intr_callback_fn cb_fn, void *cb_arg) { int ret; struct rte_intr_source *src; struct rte_intr_callback *cb, *next; /* do parameter checking first */ if (rte_intr_fd_get(intr_handle) < 0) { RTE_LOG(ERR, EAL, "Unregistering with invalid input parameter\n"); return -EINVAL; } if (kq < 0) { RTE_LOG(ERR, EAL, "Kqueue is not active\n"); return -ENODEV; } rte_spinlock_lock(&intr_lock); /* check if the interrupt source for the fd is existent */ TAILQ_FOREACH(src, &intr_sources, next) if (rte_intr_fd_get(src->intr_handle) == rte_intr_fd_get(intr_handle)) break; /* No interrupt source registered for the fd */ if (src == NULL) { ret = -ENOENT; /* interrupt source has some active callbacks right now. */ } else if (src->active != 0) { ret = -EAGAIN; /* ok to remove. */ } else { struct kevent ke; ret = 0; /* remove it from the kqueue */ memset(&ke, 0, sizeof(ke)); ke.flags = EV_DELETE; /* mark for deletion from the queue */ if (intr_source_to_kevent(intr_handle, &ke) < 0) { RTE_LOG(ERR, EAL, "Cannot convert to kevent\n"); ret = -ENODEV; goto out; } /** * remove intr file descriptor from wait list. */ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) { RTE_LOG(ERR, EAL, "Error removing fd %d kevent, %s\n", rte_intr_fd_get(src->intr_handle), strerror(errno)); /* removing non-existent even is an expected condition * in some circumstances (e.g. oneshot events). */ } /*walk through the callbacks and remove all that match. */ for (cb = TAILQ_FIRST(&src->callbacks); cb != NULL; cb = next) { next = TAILQ_NEXT(cb, next); if (cb->cb_fn == cb_fn && (cb_arg == (void *)-1 || cb->cb_arg == cb_arg)) { TAILQ_REMOVE(&src->callbacks, cb, next); free(cb); ret++; } } /* all callbacks for that source are removed. */ if (TAILQ_EMPTY(&src->callbacks)) { TAILQ_REMOVE(&intr_sources, src, next); free(src); } } out: rte_eal_trace_intr_callback_unregister(intr_handle, cb_fn, cb_arg, ret); rte_spinlock_unlock(&intr_lock); return ret; } int rte_intr_callback_unregister_sync(const struct rte_intr_handle *intr_handle, rte_intr_callback_fn cb_fn, void *cb_arg) { int ret = 0; while ((ret = rte_intr_callback_unregister(intr_handle, cb_fn, cb_arg)) == -EAGAIN) rte_pause(); return ret; } int rte_intr_enable(const struct rte_intr_handle *intr_handle) { int rc = 0; if (intr_handle == NULL) return -1; if (rte_intr_type_get(intr_handle) == RTE_INTR_HANDLE_VDEV) { rc = 0; goto out; } if (rte_intr_fd_get(intr_handle) < 0 || rte_intr_dev_fd_get(intr_handle) < 0) { rc = -1; goto out; } switch (rte_intr_type_get(intr_handle)) { /* not used at this moment */ case RTE_INTR_HANDLE_ALARM: rc = -1; break; /* not used at this moment */ case RTE_INTR_HANDLE_DEV_EVENT: rc = -1; break; /* unknown handle type */ default: RTE_LOG(ERR, EAL, "Unknown handle type of fd %d\n", rte_intr_fd_get(intr_handle)); rc = -1; break; } out: rte_eal_trace_intr_enable(intr_handle, rc); return rc; } int rte_intr_disable(const struct rte_intr_handle *intr_handle) { int rc = 0; if (intr_handle == NULL) return -1; if (rte_intr_type_get(intr_handle) == RTE_INTR_HANDLE_VDEV) { rc = 0; goto out; } if (rte_intr_fd_get(intr_handle) < 0 || rte_intr_dev_fd_get(intr_handle) < 0) { rc = -1; goto out; } switch (rte_intr_type_get(intr_handle)) { /* not used at this moment */ case RTE_INTR_HANDLE_ALARM: rc = -1; break; /* not used at this moment */ case RTE_INTR_HANDLE_DEV_EVENT: rc = -1; break; /* unknown handle type */ default: RTE_LOG(ERR, EAL, "Unknown handle type of fd %d\n", rte_intr_fd_get(intr_handle)); rc = -1; break; } out: rte_eal_trace_intr_disable(intr_handle, rc); return rc; } int rte_intr_ack(const struct rte_intr_handle *intr_handle) { if (rte_intr_type_get(intr_handle) == RTE_INTR_HANDLE_VDEV) return 0; return -1; } static void eal_intr_process_interrupts(struct kevent *events, int nfds) { struct rte_intr_callback active_cb; union rte_intr_read_buffer buf; struct rte_intr_callback *cb, *next; struct rte_intr_source *src; bool call = false; int n, bytes_read; struct kevent ke; for (n = 0; n < nfds; n++) { int event_fd = events[n].ident; rte_spinlock_lock(&intr_lock); TAILQ_FOREACH(src, &intr_sources, next) if (rte_intr_fd_get(src->intr_handle) == event_fd) break; if (src == NULL) { rte_spinlock_unlock(&intr_lock); continue; } /* mark this interrupt source as active and release the lock. */ src->active = 1; rte_spinlock_unlock(&intr_lock); /* set the length to be read dor different handle type */ switch (rte_intr_type_get(src->intr_handle)) { case RTE_INTR_HANDLE_ALARM: bytes_read = 0; call = true; break; case RTE_INTR_HANDLE_VDEV: case RTE_INTR_HANDLE_EXT: bytes_read = 0; call = true; break; case RTE_INTR_HANDLE_DEV_EVENT: bytes_read = 0; call = true; break; default: bytes_read = 1; break; } if (bytes_read > 0) { /** * read out to clear the ready-to-be-read flag * for epoll_wait. */ bytes_read = read(event_fd, &buf, bytes_read); if (bytes_read < 0) { if (errno == EINTR || errno == EWOULDBLOCK) continue; RTE_LOG(ERR, EAL, "Error reading from file " "descriptor %d: %s\n", event_fd, strerror(errno)); } else if (bytes_read == 0) RTE_LOG(ERR, EAL, "Read nothing from file " "descriptor %d\n", event_fd); else call = true; } /* grab a lock, again to call callbacks and update status. */ rte_spinlock_lock(&intr_lock); if (call) { /* Finally, call all callbacks. */ TAILQ_FOREACH(cb, &src->callbacks, next) { /* make a copy and unlock. */ active_cb = *cb; rte_spinlock_unlock(&intr_lock); /* call the actual callback */ active_cb.cb_fn(active_cb.cb_arg); /*get the lock back. */ rte_spinlock_lock(&intr_lock); } } /* we done with that interrupt source, release it. */ src->active = 0; /* check if any callback are supposed to be removed */ for (cb = TAILQ_FIRST(&src->callbacks); cb != NULL; cb = next) { next = TAILQ_NEXT(cb, next); if (cb->pending_delete) { /* remove it from the kqueue */ memset(&ke, 0, sizeof(ke)); /* mark for deletion from the queue */ ke.flags = EV_DELETE; if (intr_source_to_kevent(src->intr_handle, &ke) < 0) { RTE_LOG(ERR, EAL, "Cannot convert to kevent\n"); rte_spinlock_unlock(&intr_lock); return; } /** * remove intr file descriptor from wait list. */ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) { RTE_LOG(ERR, EAL, "Error removing fd %d kevent, %s\n", rte_intr_fd_get(src->intr_handle), strerror(errno)); /* removing non-existent even is an expected * condition in some circumstances * (e.g. oneshot events). */ } TAILQ_REMOVE(&src->callbacks, cb, next); if (cb->ucb_fn) cb->ucb_fn(src->intr_handle, cb->cb_arg); free(cb); } } /* all callbacks for that source are removed. */ if (TAILQ_EMPTY(&src->callbacks)) { TAILQ_REMOVE(&intr_sources, src, next); free(src); } rte_spinlock_unlock(&intr_lock); } } static void * eal_intr_thread_main(void *arg __rte_unused) { struct kevent events[MAX_INTR_EVENTS]; int nfds; /* host thread, never break out */ for (;;) { /* do not change anything, just wait */ nfds = kevent(kq, NULL, 0, events, MAX_INTR_EVENTS, NULL); /* kevent fail */ if (nfds < 0) { if (errno == EINTR) continue; RTE_LOG(ERR, EAL, "kevent returns with fail\n"); break; } /* kevent timeout, will never happen here */ else if (nfds == 0) continue; /* kevent has at least one fd ready to read */ eal_intr_process_interrupts(events, nfds); } close(kq); kq = -1; return NULL; } int rte_eal_intr_init(void) { int ret = 0; /* init the global interrupt source head */ TAILQ_INIT(&intr_sources); kq = kqueue(); if (kq < 0) { RTE_LOG(ERR, EAL, "Cannot create kqueue instance\n"); return -1; } /* create the host thread to wait/handle the interrupt */ ret = rte_ctrl_thread_create(&intr_thread, "eal-intr-thread", NULL, eal_intr_thread_main, NULL); if (ret != 0) { rte_errno = -ret; RTE_LOG(ERR, EAL, "Failed to create thread for interrupt handling\n"); } return ret; } int rte_intr_rx_ctl(struct rte_intr_handle *intr_handle, int epfd, int op, unsigned int vec, void *data) { RTE_SET_USED(intr_handle); RTE_SET_USED(epfd); RTE_SET_USED(op); RTE_SET_USED(vec); RTE_SET_USED(data); return -ENOTSUP; } int rte_intr_efd_enable(struct rte_intr_handle *intr_handle, uint32_t nb_efd) { RTE_SET_USED(intr_handle); RTE_SET_USED(nb_efd); return 0; } void rte_intr_efd_disable(struct rte_intr_handle *intr_handle) { RTE_SET_USED(intr_handle); } int rte_intr_dp_is_en(struct rte_intr_handle *intr_handle) { RTE_SET_USED(intr_handle); return 0; } int rte_intr_allow_others(struct rte_intr_handle *intr_handle) { RTE_SET_USED(intr_handle); return 1; } int rte_intr_cap_multiple(struct rte_intr_handle *intr_handle) { RTE_SET_USED(intr_handle); return 0; } int rte_epoll_wait(int epfd, struct rte_epoll_event *events, int maxevents, int timeout) { RTE_SET_USED(epfd); RTE_SET_USED(events); RTE_SET_USED(maxevents); RTE_SET_USED(timeout); return -ENOTSUP; } int rte_epoll_wait_interruptible(int epfd, struct rte_epoll_event *events, int maxevents, int timeout) { RTE_SET_USED(epfd); RTE_SET_USED(events); RTE_SET_USED(maxevents); RTE_SET_USED(timeout); return -ENOTSUP; } int rte_epoll_ctl(int epfd, int op, int fd, struct rte_epoll_event *event) { RTE_SET_USED(epfd); RTE_SET_USED(op); RTE_SET_USED(fd); RTE_SET_USED(event); return -ENOTSUP; } int rte_intr_tls_epfd(void) { return -ENOTSUP; } void rte_intr_free_epoll_fd(struct rte_intr_handle *intr_handle) { RTE_SET_USED(intr_handle); } int rte_thread_is_intr(void) { return pthread_equal(intr_thread, pthread_self()); }