/* * Copyright (c) 2011 Benjamin Franzke * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial eventions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "wflist.h" #include "wfhandle.h" #include "wfdregistry.h" #include "wfdevent.h" #include "wfdport.h" #include "wfdpipeline.h" #include #include #include #include #include #include #include struct bind_event { WFDint pipeline_id; WFDSource source; WFDint time; struct wf_list link; }; #define EGL_EGLEXT_PROTOTYPES #include #include #include struct wfd_event { uint32_t start_time_msec; int epoll_fd; uint32_t filter; struct wfd_device *device; /* source bind complete */ int drm_fd; struct bind_event bind; /* port attach */ struct udev_monitor *udev_monitor; int udev_monitor_fd; WFDint attach_port_id; WFDboolean attach_state; int source_bind_event_queue_fd; struct wf_list bind_event_queue; WFDEventType current_event_type; EGLDisplay egl_display; EGLSyncKHR egl_sync; pthread_t thread; int thread_destroy_fd; }; static int create_port_attach_event_fd(struct wfd_event *event) { struct wfd_registry *registry = wfd_get_registry(); event->udev_monitor = udev_monitor_new_from_netlink(registry->udev, "udev"); if (event->udev_monitor == NULL) { fprintf(stderr, "failed to create udev monitor\n"); return -1; } udev_monitor_filter_add_match_subsystem_devtype(event->udev_monitor, "drm", NULL); if (udev_monitor_enable_receiving(event->udev_monitor) < 0) { fprintf(stderr, "failed to enable udev-monitor receiving\n"); udev_monitor_unref(event->udev_monitor); return -1; } return udev_monitor_get_fd(event->udev_monitor); } enum wfd_event_type_mask { WFD_EVENT_NONE_MASK = (1 << 0), WFD_EVENT_DESTROYED_MASK = (1 << 1), WFD_EVENT_PORT_ATTACH_DETACH_MASK = (1 << 2), WFD_EVENT_PORT_PROTECTION_FAILURE_MASK = (1 << 3), WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE_MASK = (1 << 4), WFD_EVENT_PIPELINE_BIND_MASK_COMPLETE_MASK = (1 << 5), WFD_EVENT_ALL_MASK = 0x3f, }; static int wfd_event_update(struct wfd_device *device, struct wfd_event *event, uint32_t filter) { struct epoll_event ep; uint32_t additions, removals; int ret; additions = filter & ~event->filter; removals = ~filter & event->filter; if (additions & WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE_MASK) { memset(&ep, 0, sizeof ep); ep.events = EPOLLIN; event->drm_fd = fcntl(wfd_device_get_fd(device), F_DUPFD_CLOEXEC, 0); if (event->drm_fd < 0) { fprintf(stderr, "failed to dup drm fd: %m\n"); return -1; } ep.data.fd = event->drm_fd; ret = epoll_ctl(event->epoll_fd, EPOLL_CTL_ADD, ep.data.fd, &ep); if (ret == -1) { fprintf(stderr, "failed to add fd to epoll: %m\n"); close(event->drm_fd); return -1; } memset(&ep, 0, sizeof ep); event->source_bind_event_queue_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE); ep.events = EPOLLIN; ep.data.fd = event->source_bind_event_queue_fd; ret = epoll_ctl(event->epoll_fd, EPOLL_CTL_ADD, ep.data.fd, &ep); if (ret == -1) { fprintf(stderr, "failed to add fd to epoll: %m\n"); return -1; } wf_list_init(&event->bind_event_queue); event->filter |= WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE_MASK; } else if (removals & WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE_MASK) { ret = epoll_ctl(event->epoll_fd, EPOLL_CTL_DEL, event->drm_fd, NULL); if (ret) { fprintf(stderr, "failed to del fd form epoll: %m\n"); return -1; } close (event->drm_fd); ret = epoll_ctl(event->epoll_fd, EPOLL_CTL_DEL, event->source_bind_event_queue_fd, NULL); if (ret) { fprintf(stderr, "failed to del fd form epoll: %m\n"); return -1; } close(event->source_bind_event_queue_fd); struct bind_event *entry, *next; wf_list_for_each_safe(entry, next, &event->bind_event_queue, link) { wf_list_remove(&entry->link); free(entry); } event->filter &= ~WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE_MASK; } if (additions & WFD_EVENT_PORT_ATTACH_DETACH) { memset(&ep, 0, sizeof ep); ep.events = EPOLLIN; event->udev_monitor_fd = create_port_attach_event_fd(event); if (event->udev_monitor_fd < 0) { fprintf(stderr, "failed to create udev monitor fd\n"); return -1; } ep.data.fd = event->udev_monitor_fd; if (ep.data.fd >= 0) { ret = epoll_ctl(event->epoll_fd, EPOLL_CTL_ADD, ep.data.fd, &ep); if (ret == -1) { fprintf(stderr, "failed to add fd to epoll: %m\n"); return -1; } } event->filter |= WFD_EVENT_PORT_ATTACH_DETACH; } else if (removals & WFD_EVENT_PORT_ATTACH_DETACH) { ret = epoll_ctl(event->epoll_fd, EPOLL_CTL_DEL, event->udev_monitor_fd, NULL); if (ret) { fprintf(stderr, "failed to del fd form epoll: %m\n"); return -1; } udev_monitor_unref(event->udev_monitor); event->udev_monitor_fd = -1; event->filter &= ~WFD_EVENT_PORT_ATTACH_DETACH; } return 0; } void wfd_event_set_filter(struct wfd_device *device, struct wfd_event *event, const WFDEventType *filter_list) { uint32_t filter; int i; /* These events cant be disabled */ filter = WFD_EVENT_NONE_MASK | WFD_EVENT_DESTROYED_MASK; if (filter_list == NULL) filter |= WFD_EVENT_ALL_MASK; for (i = 0; filter_list != NULL && filter_list[i] != WFD_NONE; ++i) { switch (filter_list[i]) { case WFD_EVENT_NONE: case WFD_EVENT_DESTROYED: case WFD_EVENT_INVALID: break; case WFD_EVENT_PORT_ATTACH_DETACH: filter |= WFD_EVENT_PORT_ATTACH_DETACH_MASK; break; case WFD_EVENT_PORT_PROTECTION_FAILURE: filter |= WFD_EVENT_PORT_PROTECTION_FAILURE_MASK; break; case WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE: filter |= WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE_MASK; break; case WFD_EVENT_PIPELINE_BIND_MASK_COMPLETE: filter |= WFD_EVENT_PIPELINE_BIND_MASK_COMPLETE_MASK; break; default: wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return; } } wfd_event_update(device, event, filter); } void wfd_event_destroy(struct wfd_device *device, struct wfd_event *event) { if (event->thread_destroy_fd >= 0) { int64_t value = 1; write(event->thread_destroy_fd, &value, sizeof value); pthread_join(event->thread, NULL); close(event->thread_destroy_fd); event->thread_destroy_fd = -1; } wfd_event_update(device, event, 0); close(event->epoll_fd); free(event); } struct wfd_event * wfd_create_event(struct wfd_device *device, const WFDint *attrib_list) { struct wfd_event *event; struct timeval tv; event = calloc(1, sizeof *event); if (event == NULL) return NULL; gettimeofday(&tv, NULL); event->start_time_msec = tv.tv_sec * 1e3 + tv.tv_usec / 1e3; event->epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (event->epoll_fd == -1) { fprintf(stderr, "failed to crate epoll instance: %m\n"); free(event); return NULL; } event->device = device; event->current_event_type = WFD_EVENT_NONE; event->filter = 0; event->thread_destroy_fd = -1; if (wfd_event_update(device, event, WFD_EVENT_ALL_MASK) < 0) { wfd_event_destroy(device, event); return NULL; } return event; } WFDint wfd_event_get_attribi(struct wfd_device *device, struct wfd_event *event, WFDEventAttrib attribute) { switch (attribute) { case WFD_EVENT_TYPE: return event->current_event_type; case WFD_EVENT_PIPELINE_BIND_QUEUE_SIZE: case WFD_EVENT_PORT_ATTACH_PORT_ID: case WFD_EVENT_PORT_ATTACH_STATE: case WFD_EVENT_PORT_PROTECTION_PORT_ID: case WFD_EVENT_PIPELINE_BIND_PIPELINE_ID: case WFD_EVENT_PIPELINE_BIND_SOURCE: case WFD_EVENT_PIPELINE_BIND_MASK: case WFD_EVENT_PIPELINE_BIND_QUEUE_OVERFLOW: case WFD_EVENT_PIPELINE_BIND_TIME_EXT: break; default: wfd_device_set_error(device, WFD_ERROR_BAD_ATTRIBUTE); return 0; } switch (event->current_event_type) { case WFD_EVENT_PORT_ATTACH_DETACH: switch (attribute) { case WFD_EVENT_PORT_ATTACH_PORT_ID: return event->attach_port_id; case WFD_EVENT_PORT_ATTACH_STATE: return event->attach_state; default: wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return 0; } case WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE: switch (attribute) { case WFD_EVENT_PIPELINE_BIND_PIPELINE_ID: return event->bind.pipeline_id; case WFD_EVENT_PIPELINE_BIND_TIME_EXT: return event->bind.time; case WFD_EVENT_PIPELINE_BIND_SOURCE: return event->bind.source; default: wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return 0; } default: wfd_device_set_error(device, WFD_ERROR_ILLEGAL_ARGUMENT); return 0; } return 0; } static struct bind_event global_bind_event; struct wfd_event *global_event; static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct wfd_pipeline *pipeline = data; struct bind_event bind; wf_list_init(&bind.link); bind.source = wf_handle_get(wfd_pipeline_get_previous_source(pipeline), SOURCE_HANDLE); bind.pipeline_id = wfd_pipeline_get_id(pipeline); bind.time = (sec * 1000 + usec / 1000) - global_event->start_time_msec; wfd_pipeline_destroy_pending_fb(pipeline); if (global_bind_event.pipeline_id == WFD_INVALID_PIPELINE_ID) { global_bind_event = bind; } else { /* queue up */ int64_t add = 1; struct bind_event *queue_entry; queue_entry = malloc(sizeof *queue_entry); if (queue_entry) { *queue_entry = bind; wf_list_push_tail(&global_event->bind_event_queue, queue_entry, link); if (write(global_event->source_bind_event_queue_fd, &add, sizeof add) < 0) { printf("overflow\n"); } } } } static int udev_event_is_hotplug_for_device(struct udev_device *u_event, struct wfd_device *device) { struct udev_list_entry *list, *hotplug_entry; list = udev_device_get_properties_list_entry(u_event); hotplug_entry = udev_list_entry_get_by_name(list, "HOTPLUG"); if (hotplug_entry == NULL) return 0; if (strcmp(udev_list_entry_get_value(hotplug_entry), "1") != 0) return 0; if (udev_device_get_devnum(u_event) != udev_device_get_devnum(wfd_device_get_udev_device(device))) return 0; return 1; } WFDEventType wfd_event_wait(struct wfd_device *device, struct wfd_event *event, WFDtime time) { struct epoll_event ep; WFDEventType event_type = WFD_EVENT_NONE; int num; int timeout; timeout = time / 1e6; num = epoll_wait(event->epoll_fd, &ep, 1, timeout); if (num == -1) { fprintf(stderr, "epoll_wait failed: %m\n"); return WFD_EVENT_NONE; } else if (num == 0) { return WFD_EVENT_NONE; } global_event = event; if (ep.data.fd == event->source_bind_event_queue_fd) { int64_t id; struct bind_event *queue_entry; if (read(event->source_bind_event_queue_fd, &id, sizeof id) < 0) { fprintf(stderr, "error reading from event queue\n"); return WFD_EVENT_NONE; } wf_list_pop_head(&event->bind_event_queue, queue_entry, link); event->bind = *queue_entry; free(queue_entry); event_type = WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE; } else if (ep.data.fd == event->drm_fd) { drmEventContext drm_evctx; memset(&drm_evctx, 0, sizeof drm_evctx); drm_evctx.version = DRM_EVENT_CONTEXT_VERSION; drm_evctx.page_flip_handler = page_flip_handler; global_bind_event.pipeline_id = WFD_INVALID_PIPELINE_ID; drmHandleEvent(event->drm_fd, &drm_evctx); if (global_bind_event.pipeline_id != WFD_INVALID_PIPELINE_ID) { event->bind = global_bind_event; event_type = WFD_EVENT_PIPELINE_BIND_SOURCE_COMPLETE; } } else if (ep.data.fd == event->udev_monitor_fd) { struct udev_device *u_event; u_event = udev_monitor_receive_device(event->udev_monitor); if (udev_event_is_hotplug_for_device(u_event, device)) { event_type = wfd_device_find_attach_detach(device, &event->attach_port_id, &event->attach_state); } udev_device_unref(u_event); } event->current_event_type = event_type; return event_type; } int wfd_event_get_fd(struct wfd_device *device, struct wfd_event *event) { return event->epoll_fd; } static void * signalling_thread(void *_event) { struct wfd_event *event = _event; struct epoll_event ep[16]; int i, num; while (1) { num = epoll_wait(event->epoll_fd, ep, 16, -1); if (num < 0) continue; for (i = 0; i < num; ++i) { if (ep[i].data.fd == event->thread_destroy_fd) return NULL; else eglSignalSyncKHR(event->egl_display, event->egl_sync, EGL_SIGNALED_KHR); } } return NULL; } int wfd_event_async(struct wfd_device *device, struct wfd_event *event, WFDEGLDisplay *egl_display, WFDEGLSync *egl_sync) { struct epoll_event ep; if (event->egl_sync != NULL) return -1; event->egl_display = (EGLDisplay) egl_display; event->egl_sync = (EGLSyncKHR) egl_sync; event->thread_destroy_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE); memset(&ep, 0, sizeof ep); ep.events = EPOLLIN; ep.data.fd = event->thread_destroy_fd; epoll_ctl(event->epoll_fd, EPOLL_CTL_ADD, ep.data.fd, &ep); pthread_create(&event->thread, NULL, signalling_thread, event); return 0; }