#include "string.h" #include "stdlib.h" #include "signal.h" #include "fcntl.h" #include "errno.h" #include "termios.h" #include "unistd.h" #include "poll.h" #include "dirent.h" #include "termio.h" #include "stdio.h" #include "libdrm/drm_mode.h" #include "sys/vt.h" #include "sys/kd.h" #include "sys/ioctl.h" #include "sys/mman.h" #include "display.h" #include "system.h" #include "logger.h" #include "options.h" #define REL_SIGNAL SIGUSR1 #define ACQ_SIGNAL SIGUSR2 #define REL_EVENT 1 #define ACQ_EVENT 2 Display __display = {0}; typedef struct { struct drm_mode_card_res drmRes; __u64 *drmRes_fbs; __u32 *drmRes_conns; __u64 *drmRes_encs; __u64 *drmRes_crtcs; __u64 connProps[ 16 ]; __u64 connPropVals[ 16 ]; __u64 connEncs[ 16 ]; struct drm_mode_modeinfo connModes[ 32 ]; struct drm_mode_get_encoder *encoder; struct drm_mode_create_dumb drmCreateDumb[2]; struct drm_mode_map_dumb drmMapDumb[2]; struct drm_mode_fb_cmd drmFBCmd[2]; } DRMData_d; static DRMData_d drmData = {0,}; static int vtPipe[2] = { -1, -1 }; static struct termios termOldConfig; static err _open_graphics_device( void ); static err _get_drm_resources( void ); static err _get_drm_connector( void ); static err _get_drm_crtc( void ); static err _get_drm_framebuffer(void ); static Size _init_vt_switch( void ); static void _vt_release( void ); static void _vt_acquire( void ); static void _vt_switch_sighandler(int sig); static Size _has_signal(Size signo); static Size _set_signal(Size signo, void(*sig_handler)(int)); err display_init( void ) { struct termios termConfig; if(!isatty(STDIN_FILENO)) { logf( "stdin is not a terminal" ); return E33_EXIT_FAILURE; } if( tcgetattr(STDIN_FILENO, &termOldConfig) == -1 ) { logf( "Could not get terminal attributes" ); return E33_EXIT_FAILURE; } termConfig = termOldConfig; termConfig.c_iflag &= ~(IGNBRK | BRKINT | PARMRK); termConfig.c_lflag &= ~(ICANON | ECHO | IEXTEN | TOSTOP); termConfig.c_lflag |= ISIG; termConfig.c_cc[VTIME] = 0; termConfig.c_cc[VMIN] = 0; termConfig.c_cc[VSTART] = 0; termConfig.c_cc[VSTOP] = 0; if( tcsetattr(STDIN_FILENO, TCSANOW, &termConfig) == -1 ) { logf( "Could not set terminal attributes" ); return E33_EXIT_FAILURE; } if( _init_vt_switch() ) { logw( "Failed to initialize VT switcher. VT switching will not be available" ); } if( _open_graphics_device() ) { logf( "Failed to find a suitable graphics device" ); return E33_EXIT_FAILURE; } if( e33_ioctl( __display.devfd, DRM_IOCTL_SET_MASTER, 0 ) == -1 ) { logf( "Failed to set DRM master" ); return E33_EXIT_FAILURE; } if( _get_drm_resources() ) { logf( "Failed to get resources" ); return E33_EXIT_FAILURE; } if( _get_drm_connector() ) { logf( "Failed to set connector" ); return E33_EXIT_FAILURE; } if( _get_drm_framebuffer() ) { logf( "Failed to set framebuffer" ); return E33_EXIT_FAILURE; } if( _get_drm_crtc() ) { logf( "Failed to set crtc" ); return E33_EXIT_FAILURE; } __display.surface.w = __display.mode.hdisplay; __display.surface.h = __display.mode.vdisplay; __display.fb.size = (Size)drmData.drmCreateDumb[0].size; __display.surface.data = __display.fb.map[0]; __display.active = 1; return E33_EXIT_SUCCESS; } void display_flip( void ) { static u8 i = 1; struct drm_mode_crtc_page_flip flip = { 0, }; __display.surface.data = __display.fb.map[i]; i ^= 1; __display.crtc.fb_id = __display.fb.id[i]; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_SETCRTC, &__display.crtc ) == -1 ) { return; } flip.fb_id = __display.fb.id[i]; flip.crtc_id = __display.crtc.crtc_id; flip.user_data = ((__u64)(&__display.crtc.crtc_id)); flip.flags = DRM_MODE_PAGE_FLIP_EVENT; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_PAGE_FLIP, &flip ) == -1 ) { return; } /* TODO remove later */ memset( __display.surface.data, 0, (uSize)__display.fb.size ); } void display_term( void ) { if( e33_ioctl( __display.devfd, DRM_IOCTL_DROP_MASTER, 0 ) == -1 ) { logw( "Failed to drop drm master. Oh well." ); } if( ioctl(__display.ttyfd, VT_RELDISP, 1) == -1 ) { logw( "Failed to drop release vt. Oh well." ); } if( munmap( __display.fb.map[0], (uSize)__display.fb.size ) == -1 ) { logw( "Failed to unmap framebuffer[0]. Oh well." ); } if( munmap( __display.fb.map[1], (uSize)__display.fb.size ) == -1 ) { logw( "Failed to unmap framebuffer[1]. Oh well." ); } free( drmData.drmRes_fbs ); free( drmData.drmRes_crtcs ); free( drmData.drmRes_encs ); free( drmData.drmRes_conns ); if( close( __display.ttyfd ) == -1 ) { logw( "Failed to close TTY. Oh well." ); } if( close( __display.devfd ) ) { logw( "Failed to close graphics device. Oh well." ); } if( tcsetattr(STDIN_FILENO, TCSAFLUSH, &termOldConfig) == -1 ) { logw( "Failed to set stdin attributes. Oh well." ); } } void display_vtswitcher_poll( int timeout ) { struct pollfd fds[1]; unsigned char event; fds[0].fd = vtPipe[0]; fds[0].events = POLLIN; fds[0].revents = 0; poll( fds, 1, timeout ); if( !fds[0].revents ) { return; } if( read(fds[0].fd, &event, sizeof(event)) != sizeof(event) ) { logw( "Invalid VT switch events read"); return; } switch(event) { case REL_EVENT: _vt_release(); break; case ACQ_EVENT: _vt_acquire(); break; } } static err _open_graphics_device( void ) { struct drm_get_cap cap = {0,}; if( (__display.devfd = open(DRM_DEVICE, O_RDWR )) == -1 ) { logw( "Failed to open DRM device" ); return E33_EXIT_FAILURE; } cap.capability = DRM_CAP_DUMB_BUFFER; if( e33_ioctl( __display.devfd, DRM_IOCTL_GET_CAP, &cap ) == -1 ) { logw( "Failed to get DRM device capabilities" ); return E33_EXIT_FAILURE; } if( cap.value ) { return E33_EXIT_SUCCESS; } return E33_EXIT_FAILURE; } static Size _get_drm_resources( void ) { if( e33_ioctl(__display.devfd, (int)DRM_IOCTL_MODE_GETRESOURCES, &drmData.drmRes) == -1 ) { logw( "Failed to init drm resources" ); return E33_EXIT_FAILURE; } drmData.drmRes_fbs = malloc( sizeof(__u64) * drmData.drmRes.count_fbs ); drmData.drmRes_crtcs = malloc( sizeof(__u64) * drmData.drmRes.count_crtcs ); drmData.drmRes_encs = malloc( sizeof(__u64) * drmData.drmRes.count_encoders ); drmData.drmRes_conns = malloc( sizeof(__u32) * drmData.drmRes.count_connectors ); /* DRM resource objects must also be zero'd before second call */ memset( drmData.drmRes_fbs, 0, sizeof(__u64) * drmData.drmRes.count_fbs ); memset( drmData.drmRes_crtcs, 0, sizeof(__u64) * drmData.drmRes.count_crtcs ); memset( drmData.drmRes_encs, 0, sizeof(__u64) * drmData.drmRes.count_encoders ); memset( drmData.drmRes_conns, 0, sizeof(__u32) * drmData.drmRes.count_connectors ); drmData.drmRes.fb_id_ptr = (__u64)drmData.drmRes_fbs; drmData.drmRes.crtc_id_ptr = (__u64)drmData.drmRes_crtcs; drmData.drmRes.connector_id_ptr = (__u64)drmData.drmRes_conns; drmData.drmRes.encoder_id_ptr = (__u64)drmData.drmRes_encs; /* Second call writes drm resource data using the given pointers */ if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_GETRESOURCES, &drmData.drmRes ) == -1 ) { logw( "Failed to get drm resources" ); return E33_EXIT_FAILURE; } return E33_EXIT_SUCCESS; } static Size _get_drm_connector( void ) { u16 i, j; for( i = 0; i < drmData.drmRes.count_connectors; ++i ) { struct drm_mode_get_connector *connector = &__display.connector; connector->connector_id = drmData.drmRes_conns[i]; if( connector->connector_id <= 0 ) { continue; } if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_GETCONNECTOR, connector ) == -1 ) { continue; } if( connector->connection == 1 && connector->count_modes > 0 && connector->count_encoders > 0 ) { connector->modes_ptr = (__u64)drmData.connModes; connector->props_ptr = (__u64)drmData.connProps; connector->prop_values_ptr = (__u64)drmData.connPropVals; connector->encoders_ptr = (__u64)drmData.connEncs; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_GETCONNECTOR, connector) == -1 ) { continue; } for( j = 0; j < connector->count_modes; ++j ) { struct drm_mode_modeinfo *mode = &drmData.connModes[j]; if( strcomp( mode->name, DRM_MODE ) && (mode->vrefresh == DRM_RATE) ) { /* TODO there has to be a better way */ if( mode->hdisplay % 8 ) { mode->hdisplay += (mode->hdisplay % 8); } __display.mode = *mode; return E33_EXIT_SUCCESS; } } } } logw( "Failed to find a valid connector" ); return E33_EXIT_FAILURE; } static Size _get_drm_crtc( void ) { struct drm_mode_get_encoder encoder = {0,}; struct drm_mode_crtc *crtc = &__display.crtc; encoder.encoder_id = __display.connector.encoder_id; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_GETENCODER, &encoder ) == -1 ) { loge( "Failed to get encoder" ); return E33_EXIT_FAILURE; } crtc->crtc_id = encoder.crtc_id; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_GETCRTC, crtc ) == -1 ) { loge( "Failed to get CRTC" ); return E33_EXIT_FAILURE; } crtc->fb_id = drmData.drmFBCmd[0].fb_id; crtc->set_connectors_ptr = (__u64)drmData.drmRes_conns; crtc->count_connectors = 1; crtc->mode = __display.mode; crtc->mode_valid = 1; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_SETCRTC, crtc ) == -1 ) { loge( "Could not set CRTC" ); return E33_EXIT_FAILURE; } return E33_EXIT_SUCCESS; } static Size _get_drm_framebuffer( void ) { struct drm_mode_create_dumb *drmCreateDumb = drmData.drmCreateDumb; struct drm_mode_map_dumb *drmMapDumb = drmData.drmMapDumb; struct drm_mode_fb_cmd *drmFBCmd = drmData.drmFBCmd; struct drm_mode_modeinfo *mode = &__display.mode; u8 i; for( i = 0; i < 2; ++i ) { drmCreateDumb[i].width = mode->hdisplay; drmCreateDumb[i].height = mode->vdisplay; drmCreateDumb[i].bpp = 32; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_CREATE_DUMB, drmCreateDumb + i ) == -1 ) { loge( "Failed to create dumb buffer #%d", i ); return E33_EXIT_FAILURE; } drmFBCmd[i].width = drmCreateDumb[i].width; drmFBCmd[i].height = drmCreateDumb[i].height; drmFBCmd[i].bpp = drmCreateDumb[i].bpp; drmFBCmd[i].pitch = drmCreateDumb[i].pitch; drmFBCmd[i].depth = 24; drmFBCmd[i].handle = drmCreateDumb[i].handle; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_ADDFB, drmFBCmd + i ) == -1 ) { loge( "Failed to add framebuffer #%d", i ); return E33_EXIT_FAILURE; } __display.fb.id[i] = drmFBCmd[i].fb_id; drmMapDumb[i].handle = drmCreateDumb[i].handle; if( e33_ioctl( __display.devfd, (int)DRM_IOCTL_MODE_MAP_DUMB, drmMapDumb + i ) == -1 ) { loge( "Failed to map dumb buffer #%d", i ); return E33_EXIT_FAILURE; } __display.fb.map[i] = mmap( 0, drmCreateDumb[i].size, PROT_READ | PROT_WRITE, MAP_SHARED, __display.devfd, drmMapDumb[i].offset ); if( __display.fb.map[i] == MAP_FAILED ) { loge( "Failed to map framebuffer #%d", i ); return E33_EXIT_FAILURE; } } return E33_EXIT_SUCCESS; } static void _vt_release( void ) { logi( "VT release signal"); if( ioctl( __display.devfd, DRM_IOCTL_DROP_MASTER, 0 ) ) { loge( "Could not drop DRM master" ); } if( ioctl(__display.ttyfd, VT_RELDISP, 1) < 0 ) { loge( "Failed to release VT"); } __display.active = 0; } static void _vt_acquire( void ) { logi( "VT acquire signal"); if( ioctl( __display.devfd, DRM_IOCTL_SET_MASTER, 0 ) == -1 ) { logw( "Could not become DRM master." ); } if( ioctl(__display.ttyfd, VT_RELDISP, VT_ACKACQ) < 0 ) { loge( "(vt_acquire) Failed to acquire VT."); } if( ioctl( __display.devfd, (int)DRM_IOCTL_MODE_SETCRTC, &__display.crtc ) == -1 ) { loge( "Failed to set crtc on VT switch." ); } __display.active = 1; } static int _set_pipe(int fd) { if (fd >= 0) { int flags = fcntl(fd, F_GETFD); if (flags == -1) { logw( "Could not get flags for VT switcher pipe"); return E33_EXIT_FAILURE; } if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { logw( "Could not set flags for VT switcher pipe"); return E33_EXIT_FAILURE; } } return E33_EXIT_SUCCESS; } static Size _has_signal(Size signo) { struct sigaction sact = {0}; sigaction(signo, 0, &sact); return sact.sa_handler != 0; } static Size _set_signal(Size signo, void(*sig_handler)(int)) { struct sigaction sact = {0}; sact.sa_handler = sig_handler; sigemptyset(&sact.sa_mask); sact.sa_flags = SA_RESTART; return sigaction(signo, &sact, NULL); } static void _vt_switch_sighandler(int sig) { unsigned char event = REL_EVENT; if( sig == ACQ_SIGNAL ) { event = ACQ_EVENT; } (void)write( vtPipe[1], &event, sizeof(event) ); } static Size _init_vt_switch( void ) { struct vt_mode vt_mode = {0,}; sigset_t set; __display.ttyfd = -1; if( pipe(vtPipe) == -1 ) { vtPipe[0] = vtPipe[1] = -1; loge( "Could not set VT pipes"); return E33_EXIT_FAILURE; } if( _set_pipe(vtPipe[0]) ) { loge( "Could not set VT read pipe" ); return E33_EXIT_FAILURE; } if( _set_pipe(vtPipe[1]) ) { loge( "Could not set VT write pipe" ); return E33_EXIT_FAILURE; } if( __display.ttyfd = open("/dev/tty", O_RDWR ) < 0 ) { loge( "Could not open TTY for VT control" ); return E33_EXIT_FAILURE; } if( _has_signal(REL_SIGNAL) ) { loge( "VT release signal is already in use"); return E33_EXIT_FAILURE; } if( _has_signal(ACQ_SIGNAL) ) { loge( "VT acquire signal is already in use"); return E33_EXIT_FAILURE; } if( _set_signal( REL_SIGNAL, _vt_switch_sighandler ) ) { loge( "Could not set relese signal handler"); return E33_EXIT_FAILURE; } if( _set_signal( ACQ_SIGNAL, _vt_switch_sighandler ) ) { loge( "Could not set acquire signal handler"); return E33_EXIT_FAILURE; } if( ioctl(__display.ttyfd, VT_GETMODE, &vt_mode) < 0 ) { logw( "Could not get VT mode" ); return E33_EXIT_FAILURE; } vt_mode.mode = VT_PROCESS; vt_mode.relsig = REL_SIGNAL; vt_mode.acqsig = ACQ_SIGNAL; vt_mode.frsig = SIGIO; if( ioctl(__display.ttyfd, VT_SETMODE, &vt_mode) < 0 ) { logw( "Could not set VT mode" ); return E33_EXIT_FAILURE; } return E33_EXIT_SUCCESS; }