#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 "display33.h" #include "system33.h" #include "logger33.h" #include "options33.h" #define REL_SIGNAL SIGUSR1 #define ACQ_SIGNAL SIGUSR2 #define REL_EVENT 1 #define ACQ_EVENT 2 Display33 __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_t; static DRMData_t drmData = {0,}; static int vtPipe[2] = { -1, -1 }; static struct termios termOldConfig; static struct vt_mode vtModeOld = {0,}; static Error _open_graphics_device( void ); static Error _get_drm_resources( void ); static Error _get_drm_connector( void ); static Error _get_drm_crtc( void ); static Error _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); Error display33_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.fb.size = (Size)drmData.drmCreateDumb[0].size; __display.surface.data = __display.fb.map[0]; __display.active = E33_TRUE; return E33_EXIT_SUCCESS; } void display33_flip( void ) { static u8 i_s = 1; struct drm_mode_crtc_page_flip flip = { 0, }; __display.surface.data = __display.fb.map[i_s]; i_s ^= 1; __display.crtc.fb_id = __display.fb.id[i_s]; if( e33_ioctl( __display.devFd, (int)DRM_IOCTL_MODE_SETCRTC, &__display.crtc ) == -1 ) { LOGW( "Failed to set CRTC for page flip prep. Skipping." ); return; } flip.fb_id = __display.fb.id[i_s]; flip.crtc_id = __display.crtc.crtc_id; flip.user_data = ((__u64)(&__display.crtc.crtc_id)); flip.flags = DRM_MODE_PAGE_FLIP_EVENT; e33_ioctl( __display.devFd, (int)DRM_IOCTL_MODE_PAGE_FLIP, &flip ); /* TODO remove later */ memset( __display.surface.data, 0, (uSize)__display.fb.size ); } void display33_term( void ) { if( e33_ioctl( __display.devFd, DRM_IOCTL_DROP_MASTER, 0 ) == -1 ) { LOGW( "Failed to drop drm master." ); } if( munmap( __display.fb.map[0], (uSize)__display.fb.size ) == -1 ) { LOGW( "Failed to unmap framebuffer[0]." ); } if( munmap( __display.fb.map[1], (uSize)__display.fb.size ) == -1 ) { LOGW( "Failed to unmap framebuffer[1]." ); } if( close( __display.devFd ) ) { LOGW( "Failed to close graphics device." ); } free( drmData.drmRes_fbs ); free( drmData.drmRes_encs ); free( drmData.drmRes_conns ); free( drmData.drmRes_crtcs ); if( ioctl(__display.ttyFd, VT_SETMODE, &vtModeOld) < 0 ) { LOGW( "Could not reset VT mode." ); } if( tcsetattr(STDIN_FILENO, TCSAFLUSH, &termOldConfig) == -1 ) { LOGW( "Failed to set stdin attributes." ); } if( close( __display.ttyFd ) == -1 ) { LOGW( "Failed to close TTY." ); } } void display33_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 Error _open_graphics_device( void ) { struct drm_get_cap cap = {0,}; if( (__display.devFd = open( E33_DRM_DEVICE, O_RDWR )) == -1 ) { LOGE( "Failed to open DRM device '%s'.", E33_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 ) { LOGE( "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 ) { LOGE( "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( e33_strcmp( mode->name, E33_DRM_MODE ) && (mode->vrefresh == E33_DRM_RATE) ) { /* TODO there has to be a better way */ if( mode->hdisplay == 1366 ) { mode->hdisplay += 10; } __display.surface.w = mode->hdisplay; __display.surface.h = mode->vdisplay; __display.mode = *mode; return E33_EXIT_SUCCESS; } } } } LOGE( "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." ); 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." ); 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." ); 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." ); 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 ) ) { LOGW( "Could not drop DRM master." ); } if( ioctl(__display.ttyFd, VT_RELDISP, 1) < 0 ) { LOGW( "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 ) { LOGW( "(vt_acquire) Failed to acquire VT."); } if( ioctl( __display.devFd, (int)DRM_IOCTL_MODE_SETCRTC, &__display.crtc ) == -1 ) { LOGW( "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 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 vtMode = {0,}; sigset_t set; __display.ttyFd = -1; if( pipe2(vtPipe, 0) == -1 ) { vtPipe[0] = vtPipe[1] = -1; LOGW( "Could not set VT pipes."); return E33_EXIT_FAILURE; } if( _set_pipe(vtPipe[0]) ) { LOGW( "Could not set VT read pipe." ); return E33_EXIT_FAILURE; } if( _set_pipe(vtPipe[1]) ) { LOGW( "Could not set VT write pipe." ); return E33_EXIT_FAILURE; } if( __display.ttyFd = open("/dev/tty", O_RDWR ) < 0 ) { LOGW( "Could not open TTY for VT control." ); return E33_EXIT_FAILURE; } if( e33_has_signal(REL_SIGNAL) ) { LOGW( "VT release signal is already in use."); return E33_EXIT_FAILURE; } if( e33_has_signal(ACQ_SIGNAL) ) { LOGW( "VT acquire signal is already in use."); return E33_EXIT_FAILURE; } if( e33_set_signal( REL_SIGNAL, _vt_switch_sighandler ) ) { LOGW( "Could not set relese signal handler."); return E33_EXIT_FAILURE; } if( e33_set_signal( ACQ_SIGNAL, _vt_switch_sighandler ) ) { LOGW( "Could not set acquire signal handler."); return E33_EXIT_FAILURE; } if( ioctl(__display.ttyFd, VT_GETMODE, &vtModeOld) < 0 ) { LOGW( "Could not get VT mode." ); return E33_EXIT_FAILURE; } vtMode = vtModeOld; vtMode.mode = VT_PROCESS; vtMode.relsig = REL_SIGNAL; vtMode.acqsig = ACQ_SIGNAL; vtMode.frsig = SIGIO; if( ioctl(__display.ttyFd, VT_SETMODE, &vtMode) < 0 ) { LOGW( "Could not set VT mode." ); return E33_EXIT_FAILURE; } return E33_EXIT_SUCCESS; }