diff options
Diffstat (limited to 'src/display.c')
-rw-r--r-- | src/display.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/src/display.c b/src/display.c new file mode 100644 index 0000000..69c7bb2 --- /dev/null +++ b/src/display.c @@ -0,0 +1,623 @@ +#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" + + +#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; + + struct drm_mode_get_connector connector; + __u64 connProps[ 16 ]; + __u64 connPropVals[ 16 ]; + __u64 connEncs[ 16 ]; + struct drm_mode_modeinfo connModes[ 32 ]; + struct drm_mode_modeinfo *mode; + + 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 Size vtPipe[2] = { -1, -1 }; + +static struct termios termOldConfig; + + +static Size _get_drm_resources( Size fd ); +static Size _get_drm_connector( Size fd ); +static Size _get_drm_crtc( Size fd ); +static Size _get_drm_framebuffer( Size fd ); + +static int _is_card( const struct dirent *f ); +static Size _open_graphics_device( Size *fd ); + +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)); + + +Size display_init( void ) +{ + Size i, fd; + + 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; + } + else { + 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( &fd ) ) { + logf( "Failed to find a suitable graphics device" ); + return E33_EXIT_FAILURE; + } + + + if( e33_ioctl( fd, DRM_IOCTL_SET_MASTER, 0 ) == -1 ) + { + logf( "Failed to set DRM master" ); + return E33_EXIT_FAILURE; + } + + + if( _get_drm_resources( fd ) ) { + logf( "Failed to get resources" ); + return E33_EXIT_FAILURE; + } + if( _get_drm_connector( fd ) ) { + logf( "Failed to set connector" ); + return E33_EXIT_FAILURE; + } + if( _get_drm_framebuffer( fd ) ) { + logf( "Failed to set framebuffer" ); + return E33_EXIT_FAILURE; + } + if( _get_drm_crtc( fd ) ) { + logf( "Failed to set crtc" ); + return E33_EXIT_FAILURE; + } + + + __display.devFd = fd; + __display.surface.w = drmData.mode->hdisplay; + __display.surface.h = drmData.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 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; + + + /* Init VT pipes */ + if (pipe(vtPipe) != 0) { + 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 | O_CLOEXEC ) < 0 ) { + loge( "Could not open TTY for VT control" ); + return E33_EXIT_FAILURE; + } + + + /* Setup signals */ + 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; +} + + +static Size _get_drm_resources( Size fd ) +{ + if( e33_ioctl(fd, (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(__u64) * 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(__u64) * 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( ioctl( fd, (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( Size fd ) +{ + struct drm_mode_get_connector *connector = &__display.connector; + u16 i, j; + + for( i = 0; i < drmData.drmRes.count_connectors; ++i ) + { + connector->connector_id = drmData.drmRes_conns[i]; + + if( connector->connector_id <= 0 ) { + continue; + } + + if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_GETCONNECTOR, connector ) == -1 ) { + continue; + } + + if( connector->connection && connector->count_modes > 0 && + connector->encoder_id && 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( fd, (int)DRM_IOCTL_MODE_GETCONNECTOR, connector) == -1 ) { + continue; + } + + goto success; + } + } + + loge( "Failed to find suitable connector"); + return E33_EXIT_FAILURE; + +success: + drmData.mode = &drmData.connModes[0]; + + logi( "Using mode '%s'", drmData.mode->name ); + + /* TODO there has to be a better way */ + if( _strcomp( drmData.mode->name, "1366x768" ) ) { + drmData.mode->hdisplay += 10; + } + return E33_EXIT_SUCCESS; +} + +static Size _get_drm_crtc( Size fd ) +{ + struct drm_mode_get_encoder *encoder = &__display.encoder; + struct drm_mode_crtc *crtc = &__display.crtc; + + encoder->encoder_id = __display.connector.encoder_id; + + if( e33_ioctl( fd, (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( fd, (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 = *drmData.mode; + crtc->mode_valid = 1; + + if( e33_ioctl( fd, (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( Size fd ) +{ + 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 = drmData.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( fd, (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( fd, (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( fd, (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, fd, 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 int _is_card( const struct dirent *f ) +{ + return _strcomp(f->d_name, "card"); +} + + +static Size _open_graphics_device( Size *fd ) +{ + Size i, num, _fd; + struct dirent **evfs; + char path[512]; + + num = scandir( "/dev/dri", &evfs, _is_card, 0 ); + + if( num <= 0 ) { + loge( "No graphics devices found" ); + goto failure; + } + + for( i = 0; i < num; ++i ) + { + struct drm_get_cap cap = {0,}; + + snprintf( path, 512, "%s%s", "/dev/dri/", evfs[i]->d_name ); + + logi( "Trying '%s'", path ); + + if( (_fd = open(path, O_RDWR )) == -1 ) { + logw( "Failed to open graphics device '%s'", path ); + continue; + } + + cap.capability = DRM_CAP_DUMB_BUFFER; + if( e33_ioctl( _fd, DRM_IOCTL_GET_CAP, &cap ) == -1 ) { + logw( "Failed to get device capabilities for '%s'", path ); + continue; + } + + if( cap.value ) + { + logi("Using device: %s", path); + *fd = _fd; + free( evfs ); + return E33_EXIT_SUCCESS; + } + + close( _fd ); + } + +failure: + free( evfs ); + return E33_EXIT_FAILURE; +} + |