| #include <inttypes.h>
|
| #include <dirent.h>
|
| #include <fcntl.h>
|
| #include <iostream>
|
| #include <sstream>
|
| #include <cstring>
|
| #include <unistd.h>
|
| #include <sys/ioctl.h>
|
| #include <cerrno>
|
| #include <xf86drm.h>
|
| #include <memory>
|
| #include <cassert>
|
| #include <unordered_map>
|
| #include <map>
|
| #include <vector>
|
| #include <new>
|
| #include <fstream>
|
| #include <poll.h>
|
| #include <cmath>
|
| #include <climits>
|
| #include <cstdint>
|
| #include <set>
|
| #include <deque>
|
| #include <chrono>
|
| #include <array>
|
| #include <thread>
|
| #include <atomic>
|
| #include <csignal>
|
| #include <mutex>
|
| #include <shared_mutex>
|
| #include <condition_variable>
|
|
|
| #include <systemd/sd-daemon.h>
|
|
|
| #include <hybris/hwc2/hwc2_compatibility_layer.h>
|
| #include <hybris/gralloc/gralloc.h>
|
| #include <hybris/platforms/common/windowbuffer.h>
|
|
|
| struct drm_evdi_vsync {
|
| uint32_t display_id;
|
| };
|
|
|
| #define DRM_EVDI_CONNECT 0x00
|
| #define DRM_EVDI_REQUEST_UPDATE 0x01
|
| #define DRM_EVDI_GRABPIX 0x02
|
| #define DRM_EVDI_ENABLE_CURSOR_EVENTS 0x03
|
| #define DRM_EVDI_POLL 0x04
|
| #define DRM_EVDI_GBM_ADD_BUFF 0x05
|
| #define DRM_EVDI_GBM_GET_BUFF 0x06
|
| #define DRM_EVDI_ADD_BUFF_CALLBACK 0x07
|
| #define DRM_EVDI_GET_BUFF_CALLBACK 0x08
|
| #define DRM_EVDI_DESTROY_BUFF_CALLBACK 0x09
|
| #define DRM_EVDI_GBM_DEL_BUFF 0x0B
|
| #define DRM_EVDI_GBM_CREATE_BUFF 0x0C
|
| #define DRM_EVDI_GBM_CREATE_BUFF_CALLBACK 0x0D
|
| #define DRM_EVDI_VSYNC 0x0E
|
|
|
| #define DRM_IOCTL_EVDI_CONNECT DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_CONNECT, struct drm_evdi_connect)
|
| #define DRM_IOCTL_EVDI_REQUEST_UPDATE DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_REQUEST_UPDATE, struct drm_evdi_request_update)
|
| #define DRM_IOCTL_EVDI_GRABPIX DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_GRABPIX, struct drm_evdi_grabpix)
|
| #define DRM_IOCTL_EVDI_ENABLE_CURSOR_EVENTS DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_ENABLE_CURSOR_EVENTS, struct drm_evdi_enable_cursor_events)
|
| #define DRM_IOCTL_EVDI_POLL DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_POLL, struct drm_evdi_poll)
|
| #define DRM_IOCTL_EVDI_GBM_ADD_BUFF DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_GBM_ADD_BUFF, struct drm_evdi_gbm_add_buf)
|
| #define DRM_IOCTL_EVDI_GBM_GET_BUFF DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_GBM_GET_BUFF, struct drm_evdi_gbm_get_buff)
|
| #define DRM_IOCTL_EVDI_ADD_BUFF_CALLBACK DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_ADD_BUFF_CALLBACK, struct drm_evdi_add_buff_callabck)
|
| #define DRM_IOCTL_EVDI_GET_BUFF_CALLBACK DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_GET_BUFF_CALLBACK, struct drm_evdi_get_buff_callabck)
|
| #define DRM_IOCTL_EVDI_DESTROY_BUFF_CALLBACK DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_DESTROY_BUFF_CALLBACK, struct drm_evdi_destroy_buff_callback)
|
| #define DRM_IOCTL_EVDI_GBM_CREATE_BUFF_CALLBACK DRM_IOWR(DRM_COMMAND_BASE + \
|
| DRM_EVDI_GBM_CREATE_BUFF_CALLBACK, struct drm_evdi_create_buff_callabck)
|
| #define DRM_IOCTL_EVDI_VSYNC DRM_IOW(DRM_COMMAND_BASE + \
|
| DRM_EVDI_VSYNC, struct drm_evdi_vsync)
|
|
|
| static const int kMaxDriverDisplays = 5;
|
| static std::unordered_map<long long, int> g_hwc_to_drv;
|
| static std::unordered_map<int, long long> g_drv_to_hwc;
|
| static std::vector<int> g_free_drv_ids;
|
| static std::atomic<bool> drm_ready{false};
|
|
|
| static constexpr uint32_t kHwcMaxSlots = 64;
|
|
|
| static constexpr uint32_t kSlotsPerDisplay = kHwcMaxSlots / uint32_t(kMaxDriverDisplays);
|
| static_assert(kSlotsPerDisplay >= 4, "Need at least 4 slots per display");
|
| static_assert(kSlotsPerDisplay * uint32_t(kMaxDriverDisplays) <= kHwcMaxSlots, "Slot partition overflow");
|
|
|
| static std::mutex g_state_mutex;
|
| static std::array<std::mutex, kMaxDriverDisplays> g_hwc_mutex;
|
|
|
| struct ScopedHwcLock {
|
| std::unique_lock<std::mutex> lk;
|
| explicit ScopedHwcLock(int display_id) {
|
| if (display_id >= 0 && display_id < kMaxDriverDisplays)
|
| lk = std::unique_lock<std::mutex>(g_hwc_mutex[display_id]);
|
| }
|
| };
|
|
|
| static std::shared_mutex g_drm_mutex;
|
|
|
| static std::mutex g_update_mutex;
|
| static std::condition_variable g_update_cv;
|
| static std::deque<int> g_work_queue;
|
| static bool g_pending_update[kMaxDriverDisplays] = {};
|
| static bool g_pending_disconnect[kMaxDriverDisplays] = {};
|
| static bool g_enqueued[kMaxDriverDisplays] = {};
|
| static std::thread g_update_thread;
|
|
|
| static std::atomic<bool> g_reopen_requested{false};
|
|
|
| static std::mutex g_present_mutex;
|
| static std::condition_variable g_present_cv;
|
| static std::deque<struct PresentJob> g_present_q;
|
| static std::thread g_present_thread;
|
|
|
| static std::mutex g_vsync_mutex;
|
| static std::condition_variable g_vsync_cv;
|
| static bool g_vsync_fired[kMaxDriverDisplays] = {false};
|
|
|
| static constexpr int kShutdownKickSignal = SIGUSR1;
|
|
|
| int drm_fd;
|
|
|
| static inline void request_reopen()
|
| {
|
| g_reopen_requested.store(true, std::memory_order_release);
|
| }
|
|
|
| static inline int ioctl_retry(int fd, unsigned long req, void *arg)
|
| {
|
| int rc;
|
| do {
|
| rc = ::ioctl(fd, req, arg);
|
| } while (rc < 0 && errno == EINTR);
|
| return rc;
|
| }
|
|
|
| static inline int drm_get_fd()
|
| {
|
| std::shared_lock<std::shared_mutex> lk(g_drm_mutex);
|
| return drm_fd;
|
| }
|
|
|
| static inline void drm_shutdown_close_fd()
|
| {
|
| std::unique_lock<std::shared_mutex> lk(g_drm_mutex);
|
| if (drm_fd >= 0) {
|
| ::close(drm_fd);
|
| drm_fd = -1;
|
| }
|
| drm_ready.store(false, std::memory_order_release);
|
| }
|
|
|
| static inline int drm_ioctl(unsigned long req, void *arg)
|
| {
|
| int fd = drm_get_fd();
|
| if (fd < 0) {
|
| errno = EBADF;
|
| return -1;
|
| }
|
| return ioctl_retry(fd, req, arg);
|
| }
|
|
|
| static inline void schedule_update(int drv_display_id)
|
| {
|
| {
|
| std::lock_guard<std::mutex> lk(g_update_mutex);
|
| if (drv_display_id >= 0 && drv_display_id < kMaxDriverDisplays) {
|
| g_pending_update[drv_display_id] = true;
|
| if (!g_enqueued[drv_display_id]) {
|
| g_work_queue.push_back(drv_display_id);
|
| g_enqueued[drv_display_id] = true;
|
| }
|
| }
|
| }
|
| g_update_cv.notify_one();
|
| }
|
|
|
| static inline void schedule_disconnect(int drv_display_id)
|
| {
|
| {
|
| std::lock_guard<std::mutex> lk(g_update_mutex);
|
| if (drv_display_id >= 0 && drv_display_id < kMaxDriverDisplays) {
|
| g_pending_disconnect[drv_display_id] = true;
|
| g_pending_update[drv_display_id] = false;
|
| if (!g_enqueued[drv_display_id]) {
|
| g_work_queue.push_back(drv_display_id);
|
| g_enqueued[drv_display_id] = true;
|
| }
|
| }
|
| }
|
| g_update_cv.notify_one();
|
| }
|
|
|
| namespace {
|
| struct RwbPool {
|
| std::vector<void*> free_blocks;
|
| void* acquire() {
|
| if (!free_blocks.empty()) {
|
| void* p = free_blocks.back();
|
| free_blocks.pop_back();
|
| return p;
|
| }
|
| return ::operator new(sizeof(RemoteWindowBuffer));
|
| }
|
| void release(void* p) { if (p) free_blocks.push_back(p); }
|
| ~RwbPool() {
|
| for (void* p : free_blocks) ::operator delete(p);
|
| free_blocks.clear();
|
| }
|
| };
|
| static RwbPool g_rwb_pool;
|
| struct RwbDeleter { void operator()(RemoteWindowBuffer* p) const { if (!p) return; p->~RemoteWindowBuffer(); g_rwb_pool.release(static_cast<void*>(p)); } };
|
| using SharedRwb = std::shared_ptr<RemoteWindowBuffer>;
|
|
|
| static inline SharedRwb make_rwb(int w, int h, uint32_t stride,
|
| int format, int usage,
|
| buffer_handle_t handle)
|
| {
|
| void* mem = g_rwb_pool.acquire();
|
| RemoteWindowBuffer* rb = new (mem) RemoteWindowBuffer(w, h, stride, format, usage, handle);
|
| return SharedRwb(rb, RwbDeleter{});
|
| }
|
| } // namespace
|
|
|
| struct BufferEntry;
|
|
|
| struct Display {
|
| int display_id = -1;
|
| long long hwc_id = 0;
|
| int width = 0;
|
| int height = 0;
|
| uint32_t stride = 0;
|
| bool connected = false;
|
| hwc2_compat_display_t* hwcDisplay = nullptr;
|
| hwc2_compat_layer_t* layer = nullptr;
|
| SharedRwb active_rwb;
|
| uint32_t next_slot_index = 0;
|
|
|
| Display() = default;
|
| Display(const Display& other) {
|
| *this = other;
|
| }
|
| Display& operator=(const Display& other) {
|
| display_id = other.display_id;
|
| hwc_id = other.hwc_id;
|
| width = other.width;
|
| height = other.height;
|
| stride = other.stride;
|
| connected = other.connected;
|
| hwcDisplay = other.hwcDisplay;
|
| layer = other.layer;
|
| active_rwb = other.active_rwb;
|
| next_slot_index = other.next_slot_index;
|
| return *this;
|
| }
|
| };
|
|
|
| static std::unordered_map<int, Display> g_displays;
|
|
|
| // Poll thread
|
| static std::thread g_poll_thread;
|
| static std::atomic<bool> g_running{true};
|
|
|
| #ifndef likely
|
| #define likely(x) __builtin_expect(!!(x), 1)
|
| #define unlikely(x) __builtin_expect(!!(x), 0)
|
| #endif
|
|
|
| static constexpr int kRwbUsage =
|
| GRALLOC_USAGE_HW_TEXTURE |
|
| GRALLOC_USAGE_HW_RENDER |
|
| GRALLOC_USAGE_HW_COMPOSER;
|
|
|
| static inline Display& get_or_create_display(int display_id) {
|
| auto it = g_displays.find(display_id);
|
| if (it != g_displays.end()) return it->second;
|
| Display d;
|
| d.display_id = display_id;
|
| g_displays.emplace(display_id, d);
|
| return g_displays[display_id];
|
| }
|
|
|
| hwc2_compat_device_t* hwcDevice;
|
|
|
| enum class BufferOrigin : uint8_t {
|
| Imported = 0,
|
| Allocated = 1,
|
| };
|
|
|
| struct BufferEntry {
|
| BufferOrigin origin = BufferOrigin::Imported;
|
| native_handle_t* handle = nullptr;
|
| // created first swap_to:
|
| SharedRwb rwb;
|
| // Track geometry used to build rwb so we can detect mismatch
|
| int rwb_w = 0;
|
| int rwb_h = 0;
|
| uint32_t rwb_stride = 0;
|
| uint32_t stride_px = 0;
|
| uint32_t assigned_slot = std::numeric_limits<uint32_t>::max();
|
| ~BufferEntry() {
|
| rwb.reset();
|
| if (origin == BufferOrigin::Imported && handle) {
|
| for (int i = 0; i < handle->numFds; i++)
|
| close(handle->data[i]);
|
| free(handle);
|
| }
|
| }
|
| };
|
|
|
| static std::unordered_map<int, std::shared_ptr<BufferEntry>> g_buffers;
|
|
|
| static constexpr size_t kExpectedHandles = 4096;
|
| /* buffer ids must be > 0 to not break PRIME export */
|
| int next_id = 1;
|
| enum poll_event_type {
|
| none,
|
| add_buf,
|
| get_buf,
|
| destroy_buf,
|
| swap_to,
|
| create_buf
|
| };
|
|
|
| struct drm_evdi_request_update {
|
| int32_t reserved;
|
| };
|
|
|
| struct drm_evdi_connect {
|
| int32_t connected;
|
| int32_t dev_index;
|
| uint32_t width;
|
| uint32_t height;
|
| uint32_t refresh_rate;
|
| uint32_t display_id;
|
| };
|
|
|
| struct drm_evdi_poll {
|
| poll_event_type event;
|
| int poll_id;
|
| void *data;
|
| };
|
|
|
| struct drm_evdi_add_buff_callabck {
|
| int poll_id;
|
| int buff_id;
|
| };
|
|
|
| struct drm_evdi_get_buff_callabck {
|
| int poll_id;
|
| int version;
|
| int numFds;
|
| int numInts;
|
| int *fd_ints;
|
| int *data_ints;
|
| };
|
|
|
| struct drm_evdi_destroy_buff_callback {
|
| int poll_id;
|
| };
|
|
|
| struct drm_evdi_gbm_get_buff {
|
| int id;
|
| void *native_handle;
|
| };
|
|
|
| struct drm_evdi_gbm_create_buff {
|
| int *id;
|
| uint32_t *stride;
|
| uint32_t format;
|
| uint32_t width;
|
| uint32_t height;
|
| };
|
|
|
| struct drm_evdi_create_buff_callabck {
|
| int poll_id;
|
| int id;
|
| uint32_t stride;
|
| };
|
|
|
| static inline int evdi_vsync(int drv_display_id)
|
| {
|
| struct drm_evdi_vsync cmd = {};
|
| cmd.display_id = (uint32_t)drv_display_id;
|
|
|
| int fd = drm_get_fd();
|
| if (fd < 0)
|
| return -EBADF;
|
|
|
| return ioctl_retry(fd, DRM_IOCTL_EVDI_VSYNC, &cmd);
|
| }
|
|
|
| int add_handle(native_handle_t* handle, BufferOrigin origin)
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
|
|
| if (next_id <= 0)
|
| next_id = 1;
|
|
|
| int id = next_id++;
|
| auto e = std::make_shared<BufferEntry>();
|
| e->origin = origin;
|
| e->handle = handle;
|
|
|
| g_buffers[id] = std::move(e);
|
| return id;
|
| }
|
|
|
| static inline std::shared_ptr<BufferEntry> get_entry_locked_nolock(int id)
|
| {
|
| auto it = g_buffers.find(id);
|
| return (it != g_buffers.end()) ? it->second : nullptr;
|
| }
|
|
|
| struct PresentJob {
|
| int drv_display_id = 0;
|
| int buf_id = -1;
|
| uint32_t slot = 0;
|
| std::shared_ptr<BufferEntry> entry;
|
| SharedRwb rwb;
|
| };
|
|
|
| static inline void enqueue_present_job(PresentJob&& j)
|
| {
|
| {
|
| std::lock_guard<std::mutex> lk(g_present_mutex);
|
| for (auto it = g_present_q.begin(); it != g_present_q.end(); ) {
|
| if (it->drv_display_id == j.drv_display_id)
|
| it = g_present_q.erase(it);
|
| else
|
| ++it;
|
| }
|
| g_present_q.push_back(std::move(j));
|
| }
|
| g_present_cv.notify_one();
|
| }
|
|
|
| static void present_thread_main()
|
| {
|
| while (g_running.load(std::memory_order_acquire)) {
|
| PresentJob j;
|
| {
|
| std::unique_lock<std::mutex> lk(g_present_mutex);
|
| g_present_cv.wait(lk, []{
|
| return !g_running.load(std::memory_order_acquire) || !g_present_q.empty();
|
| });
|
| if (!g_running.load(std::memory_order_acquire))
|
| break;
|
| j = std::move(g_present_q.front());
|
| g_present_q.pop_front();
|
| }
|
|
|
| hwc2_compat_display_t* hwcDisp = nullptr;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| Display& D = get_or_create_display(j.drv_display_id);
|
| hwcDisp = D.hwcDisplay;
|
| }
|
| if (!hwcDisp || !j.rwb)
|
| continue;
|
|
|
| if (j.drv_display_id < 0 || j.drv_display_id >= kMaxDriverDisplays)
|
| continue;
|
|
|
| uint32_t numTypes = 0, numRequests = 0;
|
| hwc2_error_t err = HWC2_ERROR_NONE;
|
|
|
| ScopedHwcLock hwclk(j.drv_display_id);
|
|
|
| err = hwc2_compat_display_set_client_target(hwcDisp, j.slot, j.rwb.get(),
|
| -1, HAL_DATASPACE_UNKNOWN);
|
| if (err != HWC2_ERROR_NONE)
|
| fprintf(stderr, "set_client_target failed: %d\n", (int)err);
|
|
|
| err = hwc2_compat_display_validate(hwcDisp, &numTypes, &numRequests);
|
| if (err == HWC2_ERROR_HAS_CHANGES && (numTypes || numRequests))
|
| (void)hwc2_compat_display_accept_changes(hwcDisp);
|
|
|
| {
|
| std::unique_lock<std::mutex> vlk(g_vsync_mutex);
|
| g_vsync_fired[j.drv_display_id] = false;
|
|
|
| g_vsync_cv.wait_for(vlk, std::chrono::milliseconds(100), [&]{
|
| return g_vsync_fired[j.drv_display_id] || !g_running.load(std::memory_order_acquire);
|
| });
|
| }
|
|
|
| if (!g_running.load(std::memory_order_acquire)) break;
|
|
|
| int presentFence = -1;
|
| err = hwc2_compat_display_present(hwcDisp, &presentFence);
|
| if (err != HWC2_ERROR_NONE) {
|
| fprintf(stderr, "present failed: %d\n", (int)err);
|
| } else {
|
| std::lock_guard<std::mutex> state_lk(g_state_mutex);
|
| Display& D = get_or_create_display(j.drv_display_id);
|
| D.active_rwb = j.rwb;
|
| }
|
| }
|
| }
|
|
|
| static inline void init_free_driver_slots_once() {
|
| if (!g_free_drv_ids.empty()) return;
|
| g_free_drv_ids.reserve(kMaxDriverDisplays);
|
| for (int i = kMaxDriverDisplays - 1; i >= 0; --i) {
|
| g_free_drv_ids.push_back(i);
|
| }
|
| }
|
|
|
| bool is_evdi_lindroid(int fd) {
|
| drmVersionPtr version = drmGetVersion(fd);
|
| if (version) {
|
| std::string driver_name(version->name, version->name_len);
|
| drmFreeVersion(version);
|
| return (driver_name == "evdi-lindroid");
|
| }
|
| return false;
|
| }
|
|
|
| int find_evdi_lindroid_device() {
|
| const std::string dri_path = "/dev/dri/";
|
| std::vector<std::string> candidates;
|
|
|
| if (DIR* dir = opendir(dri_path.c_str())) {
|
| struct dirent* entry;
|
| while ((entry = readdir(dir)) != nullptr) {
|
| if (strncmp(entry->d_name, "card", 4) == 0) {
|
| candidates.emplace_back(dri_path + entry->d_name);
|
| }
|
| }
|
| closedir(dir);
|
| }
|
|
|
| for (const auto& path : candidates) {
|
| int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
|
| if (fd < 0) continue;
|
|
|
| if (is_evdi_lindroid(fd)) {
|
| std::cout << "Found evdi-lindroid at " << path << std::endl;
|
|
|
| if (drmIsMaster(fd)) {
|
| if (ioctl(fd, DRM_IOCTL_DROP_MASTER, nullptr) < 0) {
|
| std::cerr << "Failed to drop master on " << path << ": " << strerror(errno) << std::endl;
|
| close(fd);
|
| return -1;
|
| }
|
| }
|
|
|
| return fd;
|
| }
|
|
|
| close(fd);
|
| }
|
|
|
| return -1;
|
| }
|
|
|
| int open_evdi_lindroid_or_create() {
|
| int fd = find_evdi_lindroid_device();
|
| if (fd >= 0) {
|
| return fd;
|
| }
|
|
|
| //try to create device
|
| std::cout << "evdi-lindroid not found. Attempting to create..." << std::endl;
|
| std::ofstream evdi_add("/sys/devices/evdi-lindroid/add");
|
| if (!evdi_add) {
|
| std::cerr << "Failed to write to /sys/devices/evdi-lindroid/add: " << strerror(errno) << std::endl;
|
| return -1;
|
| }
|
|
|
| evdi_add << "1";
|
| evdi_add.close();
|
|
|
| int wait_interval = 1; // interval between evdi device check
|
| int total_wait_limit = 30; // total wait time limit for evdi device check
|
| for (int wait_time = 0; wait_time < total_wait_limit; wait_time += wait_interval) {
|
| fd = find_evdi_lindroid_device();
|
| if (fd >= 0) {
|
| return fd;
|
| }
|
| sleep(wait_interval);
|
| }
|
|
|
| std::cerr << "evdi-lindroid still not available after add attempt." << std::endl;
|
| return -1;
|
| }
|
|
|
| static inline int evdi_connect(int fd, int device_index,
|
| uint32_t width, uint32_t height,
|
| uint32_t refresh_rate, uint32_t display_id,
|
| int connected) {
|
| drm_evdi_connect cmd = {
|
| .connected = connected,
|
| .dev_index = device_index,
|
| .width = width,
|
| .height = height,
|
| .refresh_rate = refresh_rate,
|
| .display_id = display_id,
|
| };
|
|
|
| if (ioctl(fd, DRM_IOCTL_EVDI_CONNECT, &cmd) < 0) {
|
| perror("DRM_IOCTL_EVDI_CONNECT failed");
|
| return -1;
|
| }
|
|
|
| return 0;
|
| }
|
|
|
| int update_display(int display_id);
|
|
|
| static inline int drv_id_for_hwc(long long hwc_id) {
|
| auto it = g_hwc_to_drv.find(hwc_id);
|
| return it == g_hwc_to_drv.end() ? -1 : it->second;
|
| }
|
|
|
| void onVsyncReceived(HWC2EventListener* listener, int32_t sequenceId,
|
| hwc2_display_t display, int64_t timestamp)
|
| {
|
| const long long hwc_id = (long long)display;
|
| int drv_id = -1;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| drv_id = drv_id_for_hwc(hwc_id);
|
| }
|
|
|
| if (drv_id >= 0) {
|
| int vsync_ret = evdi_vsync(drv_id);
|
| if (vsync_ret < 0 && errno != ETIMEDOUT && errno != ENODEV && errno != EBADF) {
|
| fprintf(stderr, "vsync failed for display %d: %d (%s)\n",
|
| drv_id, errno, strerror(errno));
|
| }
|
| {
|
| std::lock_guard<std::mutex> lk(g_vsync_mutex);
|
| g_vsync_fired[drv_id] = true;
|
| }
|
| g_vsync_cv.notify_all();
|
| }
|
| }
|
|
|
| static inline int alloc_driver_slot_for_hwc(long long hwc_id) {
|
| int drv = drv_id_for_hwc(hwc_id);
|
| if (drv >= 0) return drv;
|
| if (g_free_drv_ids.empty()) return -1;
|
| drv = g_free_drv_ids.back();
|
| g_free_drv_ids.pop_back();
|
| g_hwc_to_drv[hwc_id] = drv;
|
| g_drv_to_hwc[drv] = hwc_id;
|
| return drv;
|
| }
|
|
|
| static inline void release_driver_slot_for_hwc(long long hwc_id) {
|
| auto it = g_hwc_to_drv.find(hwc_id);
|
| if (it == g_hwc_to_drv.end()) return;
|
| int drv = it->second;
|
| g_hwc_to_drv.erase(it);
|
| g_drv_to_hwc.erase(drv);
|
| g_free_drv_ids.push_back(drv);
|
| }
|
|
|
| void onHotplugReceived(HWC2EventListener* listener, int32_t sequenceId,
|
| hwc2_display_t display, bool connected,
|
| bool primaryDisplay)
|
| {
|
| printf("onHotplugReceived(%d, %" PRIu64 ", %s, %s)\n",
|
| sequenceId, display,
|
| connected ? "connected" : "disconnected",
|
| primaryDisplay ? "primary" : "external");
|
|
|
| hwc2_compat_device_on_hotplug(hwcDevice, display, connected);
|
| init_free_driver_slots_once();
|
|
|
| const long long hwc_id = (long long)display;
|
| int drv_id = -1;
|
|
|
| if (connected) {
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| drv_id = drv_id_for_hwc(hwc_id);
|
| if (drv_id < 0) {
|
| drv_id = alloc_driver_slot_for_hwc(hwc_id);
|
| if (drv_id < 0) {
|
| std::cerr << "No free driver display slots; ignoring hotplug for HWC id "
|
| << hwc_id << std::endl;
|
| return;
|
| }
|
| }
|
| Display& D = get_or_create_display(drv_id);
|
| D.display_id = drv_id;
|
| D.hwc_id = hwc_id;
|
| D.hwcDisplay = hwc2_compat_device_get_display_by_id(hwcDevice, display);
|
| D.connected = true;
|
| }
|
| schedule_update(drv_id);
|
| hwc2_compat_display_set_vsync_enabled(hwc2_compat_device_get_display_by_id(
|
| hwcDevice, display), HWC2_VSYNC_ENABLE);
|
| } else {
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| drv_id = drv_id_for_hwc(hwc_id);
|
| if (drv_id < 0) return;
|
| Display& D = get_or_create_display(drv_id);
|
| D.connected = false;
|
| }
|
| schedule_disconnect(drv_id);
|
| }
|
| }
|
|
|
| void onRefreshReceived(HWC2EventListener* listener,
|
| int32_t sequenceId, hwc2_display_t display)
|
| {
|
| const long long hwc_id = (long long)display;
|
| int drv_id = -1;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| drv_id = drv_id_for_hwc(hwc_id);
|
| }
|
| if (drv_id < 0)
|
| return;
|
| printf("onRefreshReceived (HWC %" PRIu64 ") -> driver slot %d\n", (uint64_t)hwc_id, drv_id);
|
| schedule_update(drv_id);
|
| }
|
|
|
| HWC2EventListener eventListener = {
|
| &onVsyncReceived,
|
| &onHotplugReceived,
|
| &onRefreshReceived
|
| };
|
|
|
| void get_buf_from_map(void *data, int poll_id, int drm_fd) {
|
| int id;
|
| struct drm_evdi_get_buff_callabck cmd;
|
| memcpy(&id, data, sizeof(int));
|
|
|
| std::memset(&cmd, 0, sizeof(cmd));
|
| cmd.poll_id = poll_id;
|
|
|
| native_handle_t* handle = nullptr;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| std::shared_ptr<BufferEntry> E = get_entry_locked_nolock(id);
|
| handle = (E && E->handle) ? E->handle : nullptr;
|
| }
|
|
|
| if (!handle) {
|
| cmd.version = -1;
|
| cmd.numFds = -1;
|
| cmd.numInts = -1;
|
| cmd.fd_ints = nullptr;
|
| cmd.data_ints = nullptr;
|
| } else {
|
| cmd.version = handle->version;
|
| cmd.numFds = handle->numFds;
|
| cmd.numInts = handle->numInts;
|
| cmd.fd_ints = const_cast<int *>(&handle->data[0]);
|
| cmd.data_ints = const_cast<int *>(&handle->data[handle->numFds]);
|
| }
|
| // printf("get_buf_from_map id: %d, version: %d\n", id, handle->version);
|
| ioctl(drm_fd, DRM_IOCTL_EVDI_GET_BUFF_CALLBACK, &cmd);
|
| }
|
|
|
| void swap_to_buff(void *data, int poll_id, int drm_fd) {
|
| struct { int id; int display_id; } ex = { -1, 0 };
|
| std::memset(&ex, 0, sizeof(ex));
|
| ex.id = -1;
|
| ex.display_id = 0;
|
| uint32_t numTypes = 0;
|
| uint32_t numRequests = 0;
|
| hwc2_error_t error = HWC2_ERROR_NONE;
|
| memcpy(&ex, data, sizeof(ex));
|
| const int id = ex.id;
|
| const int drv_display_id = ex.display_id;
|
|
|
| hwc2_compat_display_t* hwcDisp = nullptr;
|
| Display Dsnap;
|
| std::shared_ptr<BufferEntry> entry;
|
| SharedRwb rwb;
|
| uint32_t slot = 0;
|
|
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| entry = get_entry_locked_nolock(id);
|
| if (!entry || !entry->handle) {
|
| printf("Failed to find buf: %d\n", id);
|
| return;
|
| }
|
| Display& D = get_or_create_display(drv_display_id);
|
| Dsnap = D;
|
| hwcDisp = D.hwcDisplay;
|
| }
|
|
|
| if (unlikely(!hwcDisp || Dsnap.width == 0 || Dsnap.height == 0 || Dsnap.stride == 0)) {
|
| printf("Display %d not ready (no HWC or size)\n", drv_display_id);
|
| return;
|
| }
|
|
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| if (entry->assigned_slot == std::numeric_limits<uint32_t>::max()) {
|
| Display& D = get_or_create_display(drv_display_id);
|
| const uint32_t base_slot = (uint32_t)drv_display_id * kSlotsPerDisplay;
|
| entry->assigned_slot = base_slot + (D.next_slot_index % kSlotsPerDisplay);
|
| D.next_slot_index++;
|
| }
|
| slot = entry->assigned_slot;
|
| }
|
|
|
| if (slot >= kHwcMaxSlots) {
|
| fprintf(stderr, "Invalid HWC slot %u (max %u) for display %d\n",
|
| slot, kHwcMaxSlots, drv_display_id);
|
| return;
|
| }
|
|
|
| // Check RWB matches display geometry
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| const uint32_t buf_stride = (entry->stride_px != 0) ? entry->stride_px : Dsnap.stride;
|
| if (!entry->rwb ||
|
| entry->rwb_w != Dsnap.width || entry->rwb_h != Dsnap.height || entry->rwb_stride != buf_stride) {
|
| entry->rwb = make_rwb(Dsnap.width, Dsnap.height, buf_stride,
|
| HAL_PIXEL_FORMAT_RGBA_8888, kRwbUsage,
|
| entry->handle);
|
| entry->rwb_w = Dsnap.width;
|
| entry->rwb_h = Dsnap.height;
|
| entry->rwb_stride = buf_stride;
|
| }
|
| rwb = entry->rwb;
|
| }
|
|
|
| if (unlikely(!rwb)) {
|
| printf("Failed to allocate RemoteWindowBuffer for id=%d\n", id);
|
| return;
|
| }
|
|
|
| // Offload blocking to present thread
|
| PresentJob j;
|
| j.drv_display_id = drv_display_id;
|
| j.buf_id = id;
|
| j.slot = slot;
|
| j.entry = std::move(entry);
|
| j.rwb = std::move(rwb);
|
| enqueue_present_job(std::move(j));
|
| return;
|
| }
|
|
|
| void destroy_buff(void *data, int poll_id, int drm_fd) {
|
| int id = *(int *)data;
|
| int ret;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| g_buffers.erase(id);
|
| }
|
|
|
| struct drm_evdi_destroy_buff_callback cmd = {.poll_id=poll_id};
|
| ret = drm_ioctl(DRM_IOCTL_EVDI_DESTROY_BUFF_CALLBACK, &cmd);
|
| if (ret < 0 && (errno == ENODEV || errno == EBADF))
|
| request_reopen();
|
| }
|
|
|
|
|
| void create_buff(void *data, int poll_id, int drm_fd) {
|
| //printf("Hi from create_buff\n");
|
| struct drm_evdi_gbm_create_buff buff_params;
|
| struct drm_evdi_create_buff_callabck cmd;
|
| memcpy(&buff_params, data, sizeof(struct drm_evdi_gbm_create_buff));
|
| const native_handle_t *full_handle;
|
| int ret = hybris_gralloc_allocate(buff_params.width, buff_params.height, HAL_PIXEL_FORMAT_RGBA_8888,
|
| kRwbUsage, (buffer_handle_t*)&full_handle, &cmd.stride);
|
| if (ret != 0) {
|
| fprintf(stderr, "[libgbm-hybris] hybris_gralloc_allocate failed: %d\n", ret);
|
| cmd.id = -1;
|
| cmd.poll_id = poll_id;
|
| cmd.stride = 0;
|
| (void)drm_ioctl(DRM_IOCTL_EVDI_GBM_CREATE_BUFF_CALLBACK, &cmd);
|
| return;
|
| }
|
| cmd.id = add_handle(const_cast<native_handle_t*>(full_handle), BufferOrigin::Allocated);
|
| cmd.poll_id = poll_id;
|
| drm_ioctl(DRM_IOCTL_EVDI_GBM_CREATE_BUFF_CALLBACK, &cmd);
|
|
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| auto it = g_buffers.find(cmd.id);
|
| if (it != g_buffers.end() && it->second) {
|
| it->second->stride_px = cmd.stride;
|
| }
|
| }
|
| }
|
|
|
| static inline int hz_from_period_ns(int32_t ns)
|
| {
|
| if (ns <= 0) return 60;
|
| const double hz_f = 1e9 / static_cast<double>(ns);
|
| int hz = static_cast<int>(std::lround(hz_f));
|
| return hz;
|
| }
|
|
|
| static inline int get_refresh_hz_from_active_config(const HWC2DisplayConfig* cfg)
|
| {
|
| return hz_from_period_ns(cfg->vsyncPeriod);
|
| }
|
| int update_display(int display_id) {
|
| Display Dsnap;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| Dsnap = get_or_create_display(display_id);
|
| }
|
| if (!Dsnap.hwcDisplay) return -1;
|
| HWC2DisplayConfig* config = hwc2_compat_display_get_active_config(Dsnap.hwcDisplay);
|
| if (!config) {
|
| fprintf(stderr, "update_display(%d): no active HWC config yet, will retry on next refresh\n",
|
| display_id);
|
| return -1;
|
| }
|
|
|
| if (config->width <= 0 || config->height <= 0) {
|
| fprintf(stderr, "update_display(%d): invalid geometry %dx%d, deferring\n",
|
| display_id, config->width, config->height);
|
| return -1;
|
| }
|
|
|
| if (!drm_ready.load(std::memory_order_acquire)) {
|
| fprintf(stderr, "update_display(%d): DRM not ready, deferring CONNECT\n", display_id);
|
| return -1;
|
| }
|
|
|
| printf("display %d width: %i height: %i\n", display_id, config->width, config->height);
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| ScopedHwcLock hwclk(display_id);
|
| Display& D = get_or_create_display(display_id);
|
| if (D.width == (int)config->width && D.height == (int)config->height && D.stride != 0)
|
| return 0;
|
|
|
| for (auto &kv : g_buffers) {
|
| if (kv.second)
|
| kv.second->rwb.reset();
|
| }
|
| D.width = config->width;
|
| D.height = config->height;
|
| D.stride = 0;
|
| if (D.layer && D.hwcDisplay) {
|
| hwc2_compat_display_destroy_layer(D.hwcDisplay, D.layer);
|
| D.layer = nullptr;
|
| D.active_rwb.reset();
|
| }
|
| if (D.hwcDisplay) {
|
| D.layer = hwc2_compat_display_create_layer(D.hwcDisplay);
|
| if (D.layer) {
|
| hwc2_compat_layer_set_composition_type(D.layer, HWC2_COMPOSITION_CLIENT);
|
| hwc2_compat_layer_set_blend_mode(D.layer, HWC2_BLEND_MODE_NONE);
|
| hwc2_compat_layer_set_source_crop(D.layer, 0.0f, 0.0f, config->width, config->height);
|
| hwc2_compat_layer_set_display_frame(D.layer, 0, 0, config->width, config->height);
|
| hwc2_compat_layer_set_visible_region(D.layer, 0, 0, config->width, config->height);
|
| }
|
| }
|
| }
|
| buffer_handle_t handle = NULL;
|
| int r = hybris_gralloc_allocate(config->width, config->height, HAL_PIXEL_FORMAT_RGBA_8888,
|
| kRwbUsage, &handle, &Dsnap.stride);
|
| if (r == 0 && handle) {
|
| // Free immediately since this was only used to determine stride
|
| (void)hybris_gralloc_release(handle, /*was_allocated=*/1);
|
| }
|
|
|
| int refresh_hz = get_refresh_hz_from_active_config(config);
|
|
|
| std::ostringstream oss;
|
| oss << "EDID for " << config->width << "x" << config->height
|
| << "@" << refresh_hz << "Hz 'Lindroid display " << display_id << "'";
|
| std::cout << oss.str() << std::endl;
|
|
|
| if (evdi_connect(drm_fd, 0,
|
| (uint32_t)config->width, (uint32_t)config->height,
|
| (uint32_t)refresh_hz, (uint32_t)display_id, 1) < 0) {
|
| return EXIT_FAILURE;
|
| }
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| Display& D = get_or_create_display(display_id);
|
| D.stride = Dsnap.stride;
|
| }
|
| return 0;
|
| }
|
|
|
| static void disconnect_display(int drv_id)
|
| {
|
| Display snap;
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| snap = get_or_create_display(drv_id);
|
| }
|
|
|
| if (drm_ready.load(std::memory_order_acquire)) {
|
| (void)evdi_connect(drm_fd, 0, 0, 0, 0, (uint32_t)drv_id, 0);
|
| }
|
|
|
| {
|
| std::lock_guard<std::mutex> lk(g_state_mutex);
|
| ScopedHwcLock hwclk(drv_id);
|
| Display& D = get_or_create_display(drv_id);
|
| const long long hwc_id = D.hwc_id;
|
| if (D.layer && D.hwcDisplay)
|
| hwc2_compat_display_destroy_layer(D.hwcDisplay, D.layer);
|
| D.layer = nullptr;
|
| D.hwcDisplay = nullptr;
|
| D.width = D.height = 0;
|
| D.stride = 0;
|
| D.connected = false;
|
| D.hwc_id = 0;
|
| D.active_rwb.reset();
|
| if (hwc_id != 0)
|
| release_driver_slot_for_hwc(hwc_id);
|
| }
|
| }
|
|
|
| static void update_thread_main()
|
| {
|
| for (;;) {
|
| int disp = -1;
|
|
|
| {
|
| std::unique_lock<std::mutex> lk(g_update_mutex);
|
| g_update_cv.wait(lk, []{
|
| extern std::atomic<bool> g_running;
|
| return !g_running.load(std::memory_order_acquire) ||
|
| !g_work_queue.empty();
|
| });
|
|
|
| extern std::atomic<bool> g_running;
|
| if (!g_running.load(std::memory_order_acquire))
|
| break;
|
|
|
| if (g_work_queue.empty())
|
| continue;
|
| disp = g_work_queue.front();
|
| g_work_queue.pop_front();
|
| if (disp >= 0 && disp < kMaxDriverDisplays)
|
| g_enqueued[disp] = false;
|
| }
|
|
|
| if (disp < 0)
|
| continue;
|
|
|
| bool do_disconnect = false;
|
| bool do_update = false;
|
| {
|
| std::lock_guard<std::mutex> lk(g_update_mutex);
|
| if (disp >= 0 && disp < kMaxDriverDisplays) {
|
| if (g_pending_disconnect[disp]) {
|
| g_pending_disconnect[disp] = false;
|
| do_disconnect = true;
|
| } else if (g_pending_update[disp]) {
|
| g_pending_update[disp] = false;
|
| do_update = true;
|
| }
|
| }
|
| }
|
|
|
| if (do_disconnect)
|
| disconnect_display(disp);
|
| else if (do_update)
|
| (void)update_display(disp);
|
| }
|
| }
|
|
|
| static void poll_thread_main()
|
| {
|
| for (;;) {
|
| if (!g_running.load(std::memory_order_acquire))
|
| break;
|
|
|
| if (g_reopen_requested.exchange(false, std::memory_order_acq_rel)) {
|
| std::unique_lock<std::shared_mutex> lk(g_drm_mutex);
|
|
|
| if (drm_fd >= 0) {
|
| ::close(drm_fd);
|
| drm_fd = -1;
|
| }
|
| drm_ready.store(false, std::memory_order_release);
|
|
|
| for (int tries = 0; tries < 30; ++tries) {
|
| int fd = open_evdi_lindroid_or_create();
|
| if (fd >= 0) {
|
| drm_fd = fd;
|
| drm_ready.store(true, std::memory_order_release);
|
| fprintf(stderr, "Reopened evdi-lindroid fd=%d\n", drm_fd);
|
| break;
|
| }
|
| std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
| }
|
|
|
| if (drm_ready.load(std::memory_order_acquire)) {
|
| std::lock_guard<std::mutex> slk(g_state_mutex);
|
| for (auto &kv : g_displays) {
|
| if (kv.second.connected && kv.second.hwcDisplay) {
|
| kv.second.width = 0;
|
| kv.second.height = 0;
|
| kv.second.stride = 0;
|
| schedule_update(kv.first);
|
| }
|
| }
|
| }
|
| }
|
|
|
| drm_evdi_poll poll_cmd;
|
| // Match EVDI_EVENT_PAYLOAD_MAX
|
| uint8_t poll_payload[32];
|
| poll_cmd.data = poll_payload;
|
|
|
| int ret = drm_ioctl(DRM_IOCTL_EVDI_POLL, &poll_cmd);
|
| if (ret < 0) {
|
| if (errno == EINTR || errno == ERESTART) {
|
| if (!g_running.load(std::memory_order_acquire))
|
| break;
|
| continue;
|
| }
|
| if (errno == ENODEV || errno == EBADF) {
|
| request_reopen();
|
| std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
| }
|
| continue;
|
| }
|
|
|
| switch (poll_cmd.event) {
|
| case get_buf:
|
| get_buf_from_map(poll_cmd.data, poll_cmd.poll_id, drm_fd);
|
| break;
|
| case swap_to:
|
| swap_to_buff(poll_cmd.data, poll_cmd.poll_id, drm_fd);
|
| break;
|
| case destroy_buf:
|
| destroy_buff(poll_cmd.data, poll_cmd.poll_id, drm_fd);
|
| break;
|
| case create_buf:
|
| create_buff(poll_cmd.data, poll_cmd.poll_id, drm_fd);
|
| break;
|
| default:
|
| break;
|
| }
|
| }
|
| }
|
|
|
| static void handle_signal(int signo)
|
| {
|
| (void)signo;
|
| g_running.store(false, std::memory_order_release);
|
| g_update_cv.notify_all();
|
| g_present_cv.notify_all();
|
| }
|
|
|
| static void kick_thread_out_of_ioctl(std::thread& t)
|
| {
|
| if (!t.joinable())
|
| return;
|
| (void)pthread_kill(t.native_handle(), kShutdownKickSignal);
|
| }
|
|
|
| static void install_signal_handlers()
|
| {
|
| struct sigaction sa;
|
| std::memset(&sa, 0, sizeof(sa));
|
| sa.sa_handler = handle_signal;
|
| sigemptyset(&sa.sa_mask);
|
| sigaction(SIGINT, &sa, nullptr);
|
| sigaction(SIGTERM, &sa, nullptr);
|
|
|
| struct sigaction sb;
|
| std::memset(&sb, 0, sizeof(sb));
|
| sb.sa_handler = [](int) {};
|
| sigemptyset(&sb.sa_mask);
|
| sigaction(kShutdownKickSignal, &sb, nullptr);
|
| }
|
|
|
| int main() {
|
| int device_index = 0;
|
| int composerSequenceId = 0;
|
|
|
| sd_notifyf(0, "MAINPID=%lu", (unsigned long)getpid());
|
| sd_notify(0, "STATUS=Initializing create-disp…");
|
|
|
| init_free_driver_slots_once();
|
|
|
| g_buffers.reserve(kExpectedHandles);
|
| g_displays.reserve(kMaxDriverDisplays);
|
| g_hwc_to_drv.reserve(kMaxDriverDisplays);
|
| g_drv_to_hwc.reserve(kMaxDriverDisplays);
|
|
|
| // Wait up to 5s for evdi; then open
|
| drm_fd = -1;
|
| for (int i = 0; i < 5 * 1000; ++i) {
|
| drm_fd = find_evdi_lindroid_device();
|
| if (drm_fd >= 0)
|
| break;
|
| usleep(1000);
|
| }
|
| if (drm_fd < 0) drm_fd = open_evdi_lindroid_or_create();
|
| if (drm_fd < 0) return EXIT_FAILURE;
|
| drm_ready.store(true, std::memory_order_release);
|
|
|
| hwcDevice = hwc2_compat_device_new(false);
|
| if (!hwcDevice)
|
| return EXIT_FAILURE;
|
| assert(hwcDevice);
|
| hwc2_compat_device_register_callback(hwcDevice, &eventListener,
|
| composerSequenceId);
|
|
|
| try {
|
| g_update_thread = std::thread(update_thread_main);
|
| } catch (...) {
|
| fprintf(stderr, "Failed to create update thread\n");
|
| close(drm_fd);
|
| return EXIT_FAILURE;
|
| }
|
|
|
| for (const auto& kv : g_hwc_to_drv) {
|
| int drv_id = kv.second;
|
| auto it = g_displays.find(drv_id);
|
| if (it == g_displays.end())
|
| continue;
|
| Display& D = it->second;
|
| if (!D.hwcDisplay) {
|
| long long hwc_id = g_drv_to_hwc[drv_id];
|
| D.hwcDisplay = hwc2_compat_device_get_display_by_id(hwcDevice, (hwc2_display_t)hwc_id);
|
| }
|
| if (D.hwcDisplay)
|
| (void)update_display(drv_id);
|
| }
|
|
|
| install_signal_handlers();
|
|
|
| sd_notify(0, "READY=1");
|
| sd_notify(0, "STATUS=create-disp ready.");
|
|
|
| // Start present thread
|
| try {
|
| g_present_thread = std::thread(present_thread_main);
|
| } catch (...) {
|
| fprintf(stderr, "Failed to create present thread\n");
|
| g_running.store(false, std::memory_order_release);
|
| g_update_cv.notify_all();
|
| close(drm_fd);
|
| return EXIT_FAILURE;
|
| }
|
|
|
| // Start poll thread
|
| try {
|
| g_poll_thread = std::thread(poll_thread_main);
|
| } catch (...) {
|
| fprintf(stderr, "Failed to create poll thread\n");
|
| close(drm_fd);
|
| return EXIT_FAILURE;
|
| }
|
|
|
| // Main thread loop.
|
| while (g_running.load(std::memory_order_acquire)) {
|
| pause();
|
| }
|
|
|
| g_update_cv.notify_all();
|
| g_present_cv.notify_all();
|
| g_vsync_cv.notify_all();
|
|
|
| if (g_poll_thread.joinable())
|
| kick_thread_out_of_ioctl(g_poll_thread);
|
|
|
| // Shutdown
|
| sd_notify(0, "STATUS=Stopping poll thread…");
|
| if (g_poll_thread.joinable())
|
| g_poll_thread.join();
|
|
|
| sd_notify(0, "STATUS=Stopping present thread…");
|
| g_present_cv.notify_all();
|
| if (g_present_thread.joinable())
|
| kick_thread_out_of_ioctl(g_present_thread);
|
|
|
| drm_shutdown_close_fd();
|
|
|
| if (g_present_thread.joinable())
|
| g_present_thread.join();
|
|
|
| sd_notify(0, "STATUS=Stopping update thread…");
|
| g_update_cv.notify_all();
|
| if (g_update_thread.joinable())
|
| g_update_thread.join();
|
|
|
| sd_notify(0, "STATUS=Shutting down…");
|
| return EXIT_SUCCESS;
|
| }
|