--- /dev/null
+/*
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <unistd.h>
+#include <glib.h>
+#include <sys/epoll.h>
+#include <pthread.h>
+
+#include "resource-manager.h"
+#include "resource-listener.h"
+
+/* listener common */
+struct listener_handle {
+ int id;
+ int resource_id;
+ const struct syscommon_resman_resource_attribute *attr;
+ void *action_param;
+ unsigned long priv;
+};
+
+static GHashTable *g_listener_hash_table;
+static pthread_mutex_t g_listener_lock = PTHREAD_MUTEX_INITIALIZER;
+static int g_listener_handle_id;
+
+/* epoll listener */
+
+#define EPOLL_LISTENER_DEFAULT_POLLING_RATE 3 /* seconds */
+#define EPOLL_LISTENER_DEFAULT_MAX_EVENTS 100
+
+static int g_epoll_fd = -1;
+static pthread_mutex_t g_epoll_lock = PTHREAD_MUTEX_INITIALIZER;
+static unsigned int g_epoll_worker_refcount;
+
+static int add_listener(struct listener_handle *handle)
+{
+ if (!handle)
+ return -EINVAL;
+
+ if (!g_listener_hash_table) {
+ g_listener_hash_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free);
+ if (!g_listener_hash_table)
+ return -ENOMEM;
+ }
+
+ g_hash_table_insert(g_listener_hash_table, GINT_TO_POINTER(handle->id), handle);
+
+ return 0;
+}
+
+static struct listener_handle *create_listener_handle(void)
+{
+ struct listener_handle *handle;
+
+ handle = malloc(sizeof(struct listener_handle));
+ if (!handle)
+ return NULL;
+
+ pthread_mutex_lock(&g_listener_lock);
+ handle->id = ++g_listener_handle_id;
+ pthread_mutex_unlock(&g_listener_lock);
+
+ return handle;
+}
+
+static struct listener_handle *find_listener(int id)
+{
+ if (!g_listener_hash_table)
+ return NULL;
+
+ return g_hash_table_lookup(g_listener_hash_table, GINT_TO_POINTER(id));
+}
+
+
+static gboolean handle_epoll_event(gpointer user_data)
+{
+ struct listener_handle *handle = user_data;
+ const struct syscommon_resman_resource_attribute *attr;
+
+ if (!handle)
+ return G_SOURCE_REMOVE;
+
+ attr = handle->attr;
+ attr->listener_ops.action(handle->resource_id, attr, handle->action_param, SYSCOMMON_RESMAN_LISTENER_TYPE_EPOLL);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void epoll_worker_get(void)
+{
+ pthread_mutex_lock(&g_epoll_lock);
+ g_epoll_worker_refcount++;
+ pthread_mutex_unlock(&g_epoll_lock);
+}
+
+static void epoll_worker_put(void)
+{
+ pthread_mutex_lock(&g_epoll_lock);
+ g_epoll_worker_refcount--;
+ pthread_mutex_unlock(&g_epoll_lock);
+}
+
+static int epoll_worker(void *arg)
+{
+ struct epoll_event *event;
+
+ while (1) {
+ struct epoll_event epoll_events[EPOLL_LISTENER_DEFAULT_MAX_EVENTS];
+ int event_count, i;
+
+ event_count = epoll_wait(g_epoll_fd, epoll_events, EPOLL_LISTENER_DEFAULT_MAX_EVENTS, 0);
+
+ pthread_mutex_lock(&g_epoll_lock);
+ if (g_epoll_worker_refcount == 0) {
+ /* there is no one using epoll worker */
+ pthread_mutex_unlock(&g_epoll_lock);
+ break;
+ }
+ pthread_mutex_unlock(&g_epoll_lock);
+
+ for (i = 0, event = &epoll_events[0]; i < event_count; i++, event++) {
+ if (event->events & EPOLLERR || event->events & EPOLLHUP)
+ continue;
+
+ g_idle_add(handle_epoll_event, event->data.ptr);
+ }
+ sleep(EPOLL_LISTENER_DEFAULT_POLLING_RATE);
+ }
+
+ close(g_epoll_fd);
+ g_epoll_fd = -1;
+
+ return 0;
+}
+
+int
+syscommon_resman_register_epoll_listener(int resource_id,
+ const struct syscommon_resman_resource_attribute *attr,
+ int fd, void *action_param)
+{
+ struct listener_handle *handle;
+ struct epoll_event epoll_event;
+ int ret;
+
+ epoll_worker_get();
+
+ if (g_epoll_fd < 0) {
+ pthread_t th;
+
+ g_epoll_fd = epoll_create(1);
+ if (g_epoll_fd < 0) {
+ ret = -errno;
+ goto err_worker_put;
+ }
+
+ ret = pthread_create(&th, NULL, (void *)epoll_worker, NULL);
+ if (ret < 0) {
+ ret = -errno;
+ goto err_epoll;
+ }
+
+ pthread_detach(th);
+ }
+
+ handle = create_listener_handle();
+ if (!handle) {
+ ret = -ENOMEM;
+ goto err_epoll;
+ }
+
+ handle->resource_id = resource_id;
+ handle->attr = attr;
+ handle->action_param = action_param;
+ handle->priv = fd;
+
+ epoll_event.events = EPOLLPRI;
+ epoll_event.data.ptr = handle;
+
+ ret = epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, fd, &epoll_event);
+ if (ret < 0) {
+ ret = -errno;
+ goto err_create;
+ }
+
+ add_listener(handle);
+
+ return handle->id;
+
+err_create:
+ free(handle);
+err_epoll:
+ if (g_epoll_fd > 0) {
+ close(g_epoll_fd);
+ g_epoll_fd = -1;
+ }
+err_worker_put:
+ epoll_worker_put();
+ return ret;
+}
+
+void
+syscommon_resman_unregister_epoll_listener(int handle_id)
+{
+ struct listener_handle *handle = find_listener(handle_id);
+ int fd;
+
+ if (!handle)
+ return;
+
+ fd = handle->priv;
+
+ g_hash_table_remove(g_listener_hash_table, GINT_TO_POINTER(handle_id));
+ epoll_ctl(g_epoll_fd, EPOLL_CTL_DEL, fd, NULL);
+ epoll_worker_put();
+}