diff options
Diffstat (limited to 'src/display33.c')
-rw-r--r-- | src/display33.c | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/src/display33.c b/src/display33.c new file mode 100644 index 0000000..928d4e0 --- /dev/null +++ b/src/display33.c @@ -0,0 +1,575 @@ +#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.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 = E33_TRUE; + + free( drmData.drmRes_fbs ); + free( drmData.drmRes_encs ); + free( drmData.drmRes_conns ); + + 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_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.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; +} + + + + + |