mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-08-19 08:42:22 +00:00
If a file descriptor being monitored by epoll_wait is closed in another thread, the result is unspecified. So add all mevents removed in other threads to delete list. And drain the list in the dispatch mevent thread. Tracked-On: #6877 Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@linux.intel.com> Acked-by: Wang, Yu1 <yu1.wang@intel.com>
451 lines
9.2 KiB
C
451 lines
9.2 KiB
C
/*-
|
|
* Copyright (c) 2011 NetApp, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
/*
|
|
* Micro event library for FreeBSD, designed for a single i/o thread
|
|
* using EPOLL, and having events be persistent by default.
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/queue.h>
|
|
#include <pthread.h>
|
|
|
|
#include "mevent.h"
|
|
#include "vmmapi.h"
|
|
#include "log.h"
|
|
|
|
#define MEVENT_MAX 64
|
|
|
|
#define MEV_ADD 1
|
|
#define MEV_ENABLE 2
|
|
#define MEV_DISABLE 3
|
|
#define MEV_DEL_PENDING 4
|
|
|
|
static int epoll_fd;
|
|
static pthread_t mevent_tid;
|
|
static int mevent_pipefd[2];
|
|
static pthread_mutex_t mevent_lmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
struct mevent {
|
|
void (*run)(int, enum ev_type, void *);
|
|
void *run_param;
|
|
void (*teardown)(void *);
|
|
void *teardown_param;
|
|
|
|
int me_fd;
|
|
enum ev_type me_type;
|
|
int me_cq;
|
|
int me_state;
|
|
|
|
int closefd;
|
|
LIST_ENTRY(mevent) me_list;
|
|
};
|
|
|
|
static LIST_HEAD(listhead, mevent) global_head;
|
|
/* List holds the mevent node which is requested to deleted */
|
|
static LIST_HEAD(del_listhead, mevent) del_head;
|
|
|
|
static void
|
|
mevent_qlock(void)
|
|
{
|
|
pthread_mutex_lock(&mevent_lmutex);
|
|
}
|
|
|
|
static void
|
|
mevent_qunlock(void)
|
|
{
|
|
pthread_mutex_unlock(&mevent_lmutex);
|
|
}
|
|
|
|
static bool
|
|
is_dispatch_thread(void)
|
|
{
|
|
return (pthread_self() == mevent_tid);
|
|
}
|
|
|
|
static void
|
|
mevent_pipe_read(int fd, enum ev_type type, void *param)
|
|
{
|
|
char buf[MEVENT_MAX];
|
|
ssize_t status;
|
|
|
|
/*
|
|
* Drain the pipe read side. The fd is non-blocking so this is
|
|
* safe to do.
|
|
*/
|
|
do {
|
|
status = read(fd, buf, sizeof(buf));
|
|
} while (status == MEVENT_MAX);
|
|
}
|
|
|
|
/* On error, -1 is returned, else return zero */
|
|
int
|
|
mevent_notify(void)
|
|
{
|
|
char c = 0;
|
|
|
|
/*
|
|
* If calling from outside the i/o thread, write a byte on the
|
|
* pipe to force the i/o thread to exit the blocking epoll call.
|
|
*/
|
|
if (mevent_pipefd[1] != 0 && !is_dispatch_thread())
|
|
if (write(mevent_pipefd[1], &c, 1) <= 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
mevent_kq_filter(struct mevent *mevp)
|
|
{
|
|
int retval;
|
|
|
|
retval = 0;
|
|
|
|
if (mevp->me_type == EVF_READ)
|
|
retval = EPOLLIN;
|
|
|
|
if (mevp->me_type == EVF_READ_ET)
|
|
retval = EPOLLIN | EPOLLET;
|
|
|
|
if (mevp->me_type == EVF_WRITE)
|
|
retval = EPOLLOUT;
|
|
|
|
if (mevp->me_type == EVF_WRITE_ET)
|
|
retval = EPOLLOUT | EPOLLET;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
mevent_destroy(void)
|
|
{
|
|
struct mevent *mevp, *tmpp;
|
|
|
|
mevent_qlock();
|
|
list_foreach_safe(mevp, &global_head, me_list, tmpp) {
|
|
LIST_REMOVE(mevp, me_list);
|
|
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, mevp->me_fd, NULL);
|
|
|
|
if ((mevp->me_type == EVF_READ ||
|
|
mevp->me_type == EVF_READ_ET ||
|
|
mevp->me_type == EVF_WRITE ||
|
|
mevp->me_type == EVF_WRITE_ET) &&
|
|
mevp->me_fd != STDIN_FILENO)
|
|
close(mevp->me_fd);
|
|
|
|
if (mevp->teardown)
|
|
mevp->teardown(mevp->teardown_param);
|
|
|
|
free(mevp);
|
|
}
|
|
|
|
/* the mevp in del_head was removed from epoll when add it
|
|
* to del_head already.
|
|
*/
|
|
list_foreach_safe(mevp, &del_head, me_list, tmpp) {
|
|
LIST_REMOVE(mevp, me_list);
|
|
|
|
if ((mevp->me_type == EVF_READ ||
|
|
mevp->me_type == EVF_READ_ET ||
|
|
mevp->me_type == EVF_WRITE ||
|
|
mevp->me_type == EVF_WRITE_ET) &&
|
|
mevp->me_fd != STDIN_FILENO)
|
|
close(mevp->me_fd);
|
|
|
|
if (mevp->teardown)
|
|
mevp->teardown(mevp->teardown_param);
|
|
|
|
free(mevp);
|
|
}
|
|
mevent_qunlock();
|
|
}
|
|
|
|
static void
|
|
mevent_handle(struct epoll_event *kev, int numev)
|
|
{
|
|
int i;
|
|
struct mevent *mevp;
|
|
|
|
for (i = 0; i < numev; i++) {
|
|
mevp = kev[i].data.ptr;
|
|
|
|
if (mevp->me_state)
|
|
(*mevp->run)(mevp->me_fd, mevp->me_type, mevp->run_param);
|
|
}
|
|
}
|
|
|
|
struct mevent *
|
|
mevent_add(int tfd, enum ev_type type,
|
|
void (*run)(int, enum ev_type, void *), void *run_param,
|
|
void (*teardown)(void *), void *teardown_param)
|
|
{
|
|
int ret;
|
|
struct epoll_event ee;
|
|
struct mevent *lp, *mevp;
|
|
|
|
if (tfd < 0 || run == NULL)
|
|
return NULL;
|
|
|
|
if (type == EVF_TIMER)
|
|
return NULL;
|
|
|
|
mevent_qlock();
|
|
/* Verify that the fd/type tuple is not present in the list */
|
|
LIST_FOREACH(lp, &global_head, me_list) {
|
|
if (lp->me_fd == tfd && lp->me_type == type) {
|
|
mevent_qunlock();
|
|
return lp;
|
|
}
|
|
}
|
|
mevent_qunlock();
|
|
|
|
/*
|
|
* Allocate an entry, populate it, and add it to the list.
|
|
*/
|
|
mevp = calloc(1, sizeof(struct mevent));
|
|
if (mevp == NULL)
|
|
return NULL;
|
|
|
|
mevp->me_fd = tfd;
|
|
mevp->me_type = type;
|
|
mevp->me_state = 1;
|
|
|
|
mevp->run = run;
|
|
mevp->run_param = run_param;
|
|
mevp->teardown = teardown;
|
|
mevp->teardown_param = teardown_param;
|
|
|
|
ee.events = mevent_kq_filter(mevp);
|
|
ee.data.ptr = mevp;
|
|
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mevp->me_fd, &ee);
|
|
|
|
if (ret == 0) {
|
|
mevent_qlock();
|
|
LIST_INSERT_HEAD(&global_head, mevp, me_list);
|
|
mevent_qunlock();
|
|
|
|
return mevp;
|
|
} else {
|
|
free(mevp);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int
|
|
mevent_enable(struct mevent *evp)
|
|
{
|
|
int ret;
|
|
struct epoll_event ee;
|
|
struct mevent *lp, *mevp = NULL;
|
|
|
|
mevent_qlock();
|
|
/* Verify that the fd/type tuple is not present in the list */
|
|
LIST_FOREACH(lp, &global_head, me_list) {
|
|
if (lp == evp) {
|
|
mevp = lp;
|
|
break;
|
|
}
|
|
}
|
|
mevent_qunlock();
|
|
|
|
if (!mevp)
|
|
return -1;
|
|
|
|
ee.events = mevent_kq_filter(mevp);
|
|
ee.data.ptr = mevp;
|
|
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mevp->me_fd, &ee);
|
|
if (ret < 0 && errno == EEXIST)
|
|
ret = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
mevent_disable(struct mevent *evp)
|
|
{
|
|
int ret;
|
|
|
|
ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evp->me_fd, NULL);
|
|
if (ret < 0 && errno == ENOENT)
|
|
ret = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
mevent_add_to_del_list(struct mevent *evp, int closefd)
|
|
{
|
|
mevent_qlock();
|
|
LIST_INSERT_HEAD(&del_head, evp, me_list);
|
|
mevent_qunlock();
|
|
|
|
mevent_notify();
|
|
}
|
|
|
|
static void
|
|
mevent_drain_del_list(void)
|
|
{
|
|
struct mevent *evp, *tmpp;
|
|
|
|
mevent_qlock();
|
|
list_foreach_safe(evp, &del_head, me_list, tmpp) {
|
|
LIST_REMOVE(evp, me_list);
|
|
if (evp->closefd) {
|
|
close(evp->me_fd);
|
|
}
|
|
|
|
if (evp->teardown)
|
|
evp->teardown(evp->teardown_param);
|
|
free(evp);
|
|
}
|
|
mevent_qunlock();
|
|
}
|
|
|
|
static int
|
|
mevent_delete_event(struct mevent *evp, int closefd)
|
|
{
|
|
mevent_qlock();
|
|
LIST_REMOVE(evp, me_list);
|
|
mevent_qunlock();
|
|
evp->me_state = 0;
|
|
evp->closefd = closefd;
|
|
|
|
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evp->me_fd, NULL);
|
|
if (!is_dispatch_thread()) {
|
|
mevent_add_to_del_list(evp, closefd);
|
|
} else {
|
|
if (evp->closefd) {
|
|
close(evp->me_fd);
|
|
}
|
|
|
|
if (evp->teardown)
|
|
evp->teardown(evp->teardown_param);
|
|
free(evp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
mevent_delete(struct mevent *evp)
|
|
{
|
|
return mevent_delete_event(evp, 0);
|
|
}
|
|
|
|
int
|
|
mevent_delete_close(struct mevent *evp)
|
|
{
|
|
return mevent_delete_event(evp, 1);
|
|
}
|
|
|
|
static void
|
|
mevent_set_name(void)
|
|
{
|
|
pthread_setname_np(mevent_tid, "mevent");
|
|
}
|
|
|
|
int
|
|
mevent_init(void)
|
|
{
|
|
epoll_fd = epoll_create1(0);
|
|
|
|
if (epoll_fd >= 0)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
mevent_deinit(void)
|
|
{
|
|
mevent_destroy();
|
|
close(epoll_fd);
|
|
if (mevent_pipefd[1] != 0)
|
|
close(mevent_pipefd[1]);
|
|
}
|
|
|
|
void
|
|
mevent_dispatch(void)
|
|
{
|
|
struct epoll_event eventlist[MEVENT_MAX];
|
|
|
|
struct mevent *pipev;
|
|
int ret;
|
|
|
|
mevent_tid = pthread_self();
|
|
mevent_set_name();
|
|
|
|
/*
|
|
* Open the pipe that will be used for other threads to force
|
|
* the blocking kqueue call to exit by writing to it. Set the
|
|
* descriptor to non-blocking.
|
|
*/
|
|
ret = pipe2(mevent_pipefd, O_NONBLOCK);
|
|
if (ret < 0) {
|
|
pr_err("pipe");
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* Add internal event handler for the pipe write fd
|
|
*/
|
|
pipev = mevent_add(mevent_pipefd[0], EVF_READ, mevent_pipe_read, NULL, NULL, NULL);
|
|
if (!pipev) {
|
|
pr_err("pipefd mevent_add failed\n");
|
|
exit(0);
|
|
}
|
|
|
|
for (;;) {
|
|
int suspend_mode;
|
|
|
|
/*
|
|
* Block awaiting events
|
|
*/
|
|
ret = epoll_wait(epoll_fd, eventlist, MEVENT_MAX, -1);
|
|
|
|
if (ret == -1 && errno != EINTR)
|
|
pr_err("Error return from epoll_wait");
|
|
|
|
/*
|
|
* Handle reported events
|
|
*/
|
|
mevent_handle(eventlist, ret);
|
|
mevent_drain_del_list();
|
|
|
|
suspend_mode = vm_get_suspend_mode();
|
|
if ((suspend_mode != VM_SUSPEND_NONE) &&
|
|
(suspend_mode != VM_SUSPEND_SYSTEM_RESET) &&
|
|
(suspend_mode != VM_SUSPEND_SUSPEND))
|
|
break;
|
|
}
|
|
}
|