/************************************************************************** libtdm_drm Copyright 2015 Samsung Electronics co., Ltd. All Rights Reserved. Contact: SooChan Lim 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, sub license, 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 (including the next paragraph) shall be included in all copies or substantial portions 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 NON-INFRINGEMENT. IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS 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. **************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "tdm_backend_drm.h" #if HAVE_UDEV #include #endif #define ENABLE_PP #define TDM_DRM_NAME "vigs" #ifdef HAVE_UDEV static int _tdm_drm_is_kms(struct udev_device *device) { drmModeRes *res; int fd = -1, id = -1; const char *file_name; const char *sys_num; file_name = udev_device_get_devnode(device); if (!file_name) return 0; TDM_BACKEND_INFO("check kms device:%s", file_name); sys_num = udev_device_get_sysnum(device); if (!sys_num) return 0; id = atoi(sys_num); if (id < 0) return 0; fd = open(file_name, O_RDWR | O_CLOEXEC); if (fd < 0) { TDM_BACKEND_ERR("fail to open drm device(%s)", file_name); return 0; } res = drmModeGetResources(fd); if (!res) goto fail; if ((res->count_crtcs <= 0) || (res->count_connectors <= 0) || (res->count_encoders <= 0)) goto fail; close(fd); drmModeFreeResources(res); return 1; fail: if (fd >= 0) close(fd); if (res) drmModeFreeResources(res); return 0; } static struct udev_device * _tdm_drm_find_primary_gpu(void) { struct udev *udev; struct udev_enumerate *e; struct udev_list_entry *entry; const char *path, *id; struct udev_device *device, *drm_device, *pci; udev = udev_new(); if (!udev) { TDM_BACKEND_ERR("fail to initialize udev context\n"); return NULL; } e = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(e, "drm"); udev_enumerate_add_match_sysname(e, "card[0-9]*"); udev_enumerate_scan_devices(e); drm_device = NULL; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { int is_boot_vga = 0; path = udev_list_entry_get_name(entry); device = udev_device_new_from_syspath(udev, path); if (!device) continue; pci = udev_device_get_parent_with_subsystem_devtype(device, "pci", NULL); if (pci) { id = udev_device_get_sysattr_value(pci, "boot_vga"); if (id && !strcmp(id, "1")) is_boot_vga = 1; } if (!is_boot_vga && drm_device) { udev_device_unref(device); continue; } if (!_tdm_drm_is_kms(device)) { udev_device_unref(device); continue; } if (is_boot_vga) { if (drm_device) udev_device_unref(drm_device); drm_device = device; break; } drm_device = device; } udev_enumerate_unref(e); return drm_device; } static hal_tdm_error _tdm_drm_udev_fd_handler(int fd, hal_tdm_event_loop_mask mask, void *user_data) { tdm_drm_display *display_data = (tdm_drm_display*)user_data; struct udev_device *dev; const char *hotplug; struct stat s; dev_t udev_devnum; int ret; dev = udev_monitor_receive_device(display_data->uevent_monitor); if (!dev) { TDM_BACKEND_ERR("couldn't receive device"); return HAL_TDM_ERROR_OPERATION_FAILED; } udev_devnum = udev_device_get_devnum(dev); ret = fstat(display_data->drm_fd, &s); if (ret == -1) { TDM_BACKEND_ERR("fstat failed"); udev_device_unref(dev); return HAL_TDM_ERROR_OPERATION_FAILED; } hotplug = udev_device_get_property_value(dev, "HOTPLUG"); if (memcmp(&s.st_rdev, &udev_devnum, sizeof(dev_t)) == 0 && hotplug && atoi(hotplug) == 1) { TDM_BACKEND_INFO("HotPlug"); tdm_drm_display_update_output_status(display_data); } udev_device_unref(dev); return HAL_TDM_ERROR_NONE; } static struct udev_monitor * _tdm_drm_create_udev_monitor() { struct udev *u = NULL; struct udev_monitor *mon = NULL; u = udev_new(); if (!u) { TDM_BACKEND_ERR("couldn't create udev"); goto failed; } mon = udev_monitor_new_from_netlink(u, "udev"); if (!mon) { TDM_BACKEND_ERR("couldn't create udev monitor"); goto failed; } if (udev_monitor_filter_add_match_subsystem_devtype(mon, "drm", "drm_minor") > 0 || udev_monitor_enable_receiving(mon) < 0) { TDM_BACKEND_ERR("add match subsystem failed"); goto failed; } TDM_BACKEND_INFO("hotplug monitor created"); return mon; failed: if (mon) udev_monitor_unref(mon); if (u) udev_unref(u); return NULL; } static void _tdm_drm_destroy_udev_monitor(struct udev_monitor *mon) { struct udev *u; if (!mon) return; u = udev_monitor_get_udev(mon); udev_monitor_unref(mon); udev_unref(u); TDM_BACKEND_INFO("hotplug monitor destroyed"); } #endif static int _tdm_drm_open_drm(void) { int fd = -1; fd = drmOpen(TDM_DRM_NAME, NULL); if (fd < 0) TDM_BACKEND_WRN("fail to open '%s' drm", TDM_DRM_NAME); #ifdef HAVE_UDEV struct udev_device *drm_device = NULL; const char *file_name; if (fd < 0) { TDM_BACKEND_WRN("fail to open drm device.. search by udev"); drm_device = _tdm_drm_find_primary_gpu(); if (!drm_device) { TDM_BACKEND_ERR("fail to find drm device"); goto ret; } file_name = udev_device_get_devnode(drm_device); if (!file_name) { TDM_BACKEND_ERR("fail to get devnode"); goto ret; } fd = open(file_name, O_RDWR | O_CLOEXEC); if (fd < 0) { TDM_BACKEND_ERR("fail to open drm device(%s)", file_name); goto ret; } TDM_BACKEND_INFO("open drm device (name:%s fd:%d)", file_name, fd); } ret: if (drm_device) udev_device_unref(drm_device); #endif return fd; } static void _tdm_drm_display_deinitialize(tdm_drm_display *display_data) { tdm_drm_display_destroy_output_list(display_data); if (display_data->plane_res) drmModeFreePlaneResources(display_data->plane_res); if (display_data->mode_res) drmModeFreeResources(display_data->mode_res); } static hal_tdm_error _tdm_drm_display_initialize(tdm_drm_display *display_data) { char errno_buf[256] = {0,}; char *errno_msg; hal_tdm_error ret; drmVersion *version = drmGetVersion(display_data->drm_fd); if (strcmp(version->name, "virtio_gpu") == 0) display_data->periodic_vblank_broken = true; drmFreeVersion(version); #if LIBDRM_MAJOR_VERSION >= 2 && LIBDRM_MINOR_VERSION >= 4 && LIBDRM_MICRO_VERSION >= 47 if (drmSetClientCap(display_data->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) < 0) { TDM_BACKEND_WRN("Set DRM_CLIENT_CAP_UNIVERSAL_PLANES failed"); } else { TDM_BACKEND_INFO("has universal planes"); display_data->has_universal_plane = 1; } #endif display_data->mode_res = drmModeGetResources(display_data->drm_fd); if (!display_data->mode_res) { errno_msg = strerror_r(errno, errno_buf, sizeof(errno_buf)); TDM_BACKEND_ERR("no drm resource: %s", errno_msg); ret = HAL_TDM_ERROR_OPERATION_FAILED; goto failed; } display_data->plane_res = drmModeGetPlaneResources(display_data->drm_fd); if (!display_data->plane_res) { errno_msg = strerror_r(errno, errno_buf, sizeof(errno_buf)); TDM_BACKEND_ERR("no drm plane resource: %s", errno_msg); ret = HAL_TDM_ERROR_OPERATION_FAILED; goto failed; } if (display_data->plane_res->count_planes <= 0) { TDM_BACKEND_ERR("no drm plane resource"); ret = HAL_TDM_ERROR_OPERATION_FAILED; goto failed; } ret = tdm_drm_display_create_output_list(display_data); if (ret != HAL_TDM_ERROR_NONE) goto failed; ret = tdm_drm_display_create_layer_list(display_data); if (ret != HAL_TDM_ERROR_NONE) goto failed; return ret; failed: _tdm_drm_display_deinitialize(display_data); return ret; } static hal_tdm_error _tdm_drm_master_drm_fd_handler(hal_tdm_fd master_drm_fd, void *user_data) { tdm_drm_display *display_data = (tdm_drm_display *) user_data; hal_tdm_error ret; TDM_BACKEND_RETURN_VAL_IF_FAIL(display_data != NULL, HAL_TDM_ERROR_INVALID_PARAMETER); display_data->drm_fd = master_drm_fd; TDM_BACKEND_INFO("Get the master drm_fd(%d)!\n", display_data->drm_fd); // initialize display with a master drm_fd ret = _tdm_drm_display_initialize(display_data); if (ret != HAL_TDM_ERROR_NONE) { TDM_BACKEND_ERR("fail to _tdm_drm_display_initialize!\n"); return HAL_TDM_ERROR_OPERATION_FAILED; } return HAL_TDM_ERROR_NONE; } int hal_backend_tdm_drm_exit(void *data) { hal_tdm_backend_data *backend_data = (hal_tdm_backend_data *)data; tdm_drm_display *display_data; TDM_BACKEND_INFO("deinit"); TDM_BACKEND_RETURN_VAL_IF_FAIL(backend_data != NULL, -1); display_data = (tdm_drm_display *)backend_data->display; TDM_BACKEND_RETURN_VAL_IF_FAIL(display_data != NULL, -1); if (backend_data->hwc_window_funcs) { free(backend_data->hwc_window_funcs); backend_data->hwc_window_funcs = NULL; } if (backend_data->hwc_funcs) { free(backend_data->hwc_funcs); backend_data->hwc_funcs = NULL; } if (backend_data->output_funcs) { free(backend_data->output_funcs); backend_data->output_funcs = NULL; } if (backend_data->display_funcs) { free(backend_data->display_funcs); backend_data->display_funcs = NULL; } _tdm_drm_display_deinitialize(display_data); #ifdef HAVE_UDEV if (display_data->uevent_monitor) _tdm_drm_destroy_udev_monitor(display_data->uevent_monitor); #endif if (display_data->drm_fd >= 0) close(display_data->drm_fd); free(display_data); display_data = NULL; free(backend_data); return HAL_TDM_ERROR_NONE; } static int hal_backend_tdm_drm_init(void **data) { hal_tdm_backend_data *backend_data = NULL; hal_tdm_display_funcs *display_funcs = NULL; hal_tdm_output_funcs *output_funcs = NULL; hal_tdm_hwc_funcs *hwc_funcs = NULL; hal_tdm_hwc_window_funcs *hwc_window_funcs = NULL; hal_tdm_pp_funcs *pp_funcs = NULL; tdm_drm_display *display_data = NULL; hal_tdm_error ret; int drm_fd; #ifdef HAVE_UDEV static struct udev_monitor *mon; hal_tdm_event_source *udev_event_source; #endif /* allocate a hal_tdm_backend_data */ backend_data = calloc(1, sizeof(struct _hal_tdm_backend_data)); if (!backend_data) { TDM_BACKEND_ERR("fail to alloc backend_data!\n"); *data = NULL; return -1; } *data = backend_data; /* allocate a hal_tdm_display */ display_data = calloc(1, sizeof(struct _tdm_drm_display)); if (!display_data) { TDM_BACKEND_ERR("fail to alloc display_data!\n"); goto failed; } backend_data->display = (hal_tdm_display *)display_data; LIST_INITHEAD(&display_data->output_list); LIST_INITHEAD(&display_data->buffer_list); // check if drm_fd is master fd. drm_fd = _tdm_drm_open_drm(); if (drm_fd < 0) { ret = HAL_TDM_ERROR_OPERATION_FAILED; goto failed; } // set true when backend has a drm_device. backend_data->has_drm_device = 1; if (drmIsMaster(drm_fd)) { // drm_fd is a master drm_fd. backend_data->drm_info.drm_fd = drm_fd; backend_data->drm_info.is_master = 1; display_data->drm_fd = drm_fd; TDM_BACKEND_INFO("Get the master drm_fd(%d)!\n", display_data->drm_fd); // initialize display with a master drm_fd ret = _tdm_drm_display_initialize(display_data); if (ret != HAL_TDM_ERROR_NONE) { TDM_BACKEND_ERR("fail to _tdm_drm_display_initialize!\n"); goto failed; } } else { // drm_fd is not a master drm_fd. // request a master drm_fd close(drm_fd); backend_data->drm_info.drm_fd = -1; backend_data->drm_info.is_master = 0; backend_data->drm_info.master_drm_fd_func = _tdm_drm_master_drm_fd_handler; backend_data->drm_info.user_data = display_data; TDM_BACKEND_INFO("A backend requests an master drm_fd.\n"); } #ifdef HAVE_UDEV mon = _tdm_drm_create_udev_monitor(); if (!mon) { ret = HAL_TDM_ERROR_OPERATION_FAILED; goto failed; } display_data->uevent_monitor = mon; /* alloc and register udev_event_source */ udev_event_source = calloc(1, sizeof(struct _hal_tdm_event_source)); if (!udev_event_source) { TDM_BACKEND_ERR("fail to alloc udev_event_source!\n"); goto failed; } udev_event_source->event_fd = udev_monitor_get_fd(mon); udev_event_source->func = _tdm_drm_udev_fd_handler; udev_event_source->user_data = display_data; backend_data->event_sources[0] = udev_event_source; backend_data->num_event_sources++; #endif /* alloc and register display_funcs */ display_funcs = calloc(1, sizeof(struct _hal_tdm_display_funcs)); if (!display_funcs) { TDM_BACKEND_ERR("fail to alloc display_funcs!\n"); goto failed; } backend_data->display_funcs = display_funcs; display_funcs->display_get_capability = drm_display_get_capability; display_funcs->display_get_pp_capability = drm_display_get_pp_capability; display_funcs->display_get_outputs = drm_display_get_outputs; display_funcs->display_get_fd = drm_display_get_fd; display_funcs->display_handle_events = drm_display_handle_events; display_funcs->display_create_pp = drm_display_create_pp; /* alloc and register output_funcs */ output_funcs = calloc(1, sizeof(struct _hal_tdm_output_funcs)); if (!output_funcs) { TDM_BACKEND_ERR("fail to alloc output_funcs!\n"); goto failed; } backend_data->output_funcs = output_funcs; output_funcs->output_get_capability = drm_output_get_capability; output_funcs->output_set_property = drm_output_set_property; output_funcs->output_get_property = drm_output_get_property; output_funcs->output_wait_vblank = drm_output_wait_vblank; output_funcs->output_set_vblank_handler = drm_output_set_vblank_handler; output_funcs->output_set_dpms = drm_output_set_dpms; output_funcs->output_get_dpms = drm_output_get_dpms; output_funcs->output_set_mode = drm_output_set_mode; output_funcs->output_get_mode = drm_output_get_mode; #ifdef HAVE_UDEV output_funcs->output_set_status_handler = drm_output_set_status_handler; #endif output_funcs->output_get_hwc = drm_output_get_hwc; /* alloc and register hwc_funcs */ hwc_funcs = calloc(1, sizeof(struct _hal_tdm_hwc_funcs)); if (!hwc_funcs) { TDM_BACKEND_ERR("fail to alloc hwc_funcs!\n"); goto failed; } backend_data->hwc_funcs = hwc_funcs; hwc_funcs->hwc_create_window = drm_hwc_create_window; hwc_funcs->hwc_get_video_supported_formats = drm_hwc_get_video_supported_formats; hwc_funcs->hwc_get_video_available_properties = NULL; hwc_funcs->hwc_get_capabilities = drm_hwc_get_capabilities; hwc_funcs->hwc_get_available_properties = drm_hwc_get_available_properties; hwc_funcs->hwc_get_client_target_buffer_queue = drm_hwc_get_client_target_buffer_queue; hwc_funcs->hwc_set_client_target_buffer = drm_hwc_set_client_target_buffer; hwc_funcs->hwc_validate = drm_hwc_validate; hwc_funcs->hwc_get_changed_composition_types = drm_hwc_get_changed_composition_types; hwc_funcs->hwc_accept_validation = drm_hwc_accept_validation; hwc_funcs->hwc_commit = drm_hwc_commit; hwc_funcs->hwc_set_commit_handler = drm_hwc_set_commit_handler; /* alloc and register hwc_window_funcs */ hwc_window_funcs = calloc(1, sizeof(struct _hal_tdm_hwc_window_funcs)); if (!hwc_window_funcs) { TDM_BACKEND_ERR("fail to alloc hwc_window_funcs!\n"); goto failed; } backend_data->hwc_window_funcs = hwc_window_funcs; hwc_window_funcs->hwc_window_destroy = drm_hwc_window_destroy; hwc_window_funcs->hwc_window_acquire_buffer_queue = NULL; // no need hwc_window_funcs->hwc_window_release_buffer_queue = NULL; // no need hwc_window_funcs->hwc_window_set_composition_type = drm_hwc_window_set_composition_type; hwc_window_funcs->hwc_window_set_buffer_damage = drm_hwc_window_set_buffer_damage; hwc_window_funcs->hwc_window_set_info = drm_hwc_window_set_info; hwc_window_funcs->hwc_window_set_buffer = drm_hwc_window_set_buffer; hwc_window_funcs->hwc_window_set_property = drm_hwc_window_set_property; hwc_window_funcs->hwc_window_get_property = drm_hwc_window_get_property; hwc_window_funcs->hwc_window_get_constraints = drm_hwc_window_get_constraints; hwc_window_funcs->hwc_window_set_name = drm_hwc_window_set_name; hwc_window_funcs->hwc_window_set_cursor_image = drm_hwc_window_set_cursor_image; #ifdef ENABLE_PP /* alloc and register pp_funcs */ pp_funcs = calloc(1, sizeof(struct _hal_tdm_pp_funcs)); if (!pp_funcs) { TDM_BACKEND_ERR("fail to alloc pp_funcs!\n"); goto failed; } backend_data->pp_funcs = pp_funcs; pp_funcs->pp_destroy = drm_pp_destroy; pp_funcs->pp_set_info = drm_pp_set_info; pp_funcs->pp_attach = drm_pp_attach; pp_funcs->pp_commit = drm_pp_commit; pp_funcs->pp_set_done_handler = drm_pp_set_done_handler; #endif TDM_BACKEND_INFO("init success!"); return HAL_TDM_ERROR_NONE; failed: TDM_BACKEND_ERR("init failed!"); hal_backend_tdm_drm_exit((void *)backend_data); return -1; } hal_backend hal_backend_tdm_data = { "drm", "Samsung", HAL_ABI_VERSION_TIZEN_6_5, hal_backend_tdm_drm_init, hal_backend_tdm_drm_exit };