/** * @file mce-fbdev.c * Frame buffer device handling code for the Mode Control Entity *

* Copyright 2015 Jolla Ltd. *

* @author Simo Piiroinen * * mce is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * mce is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mce. If not, see . */ #include "mce-fbdev.h" #include "mce-log.h" #include "mce.h" #ifdef ENABLE_HYBRIS # include "mce-hybris.h" #endif #include #include #include #include #include #include /* ========================================================================= * * CONSTANTS * ========================================================================= */ /** Path to the framebuffer device */ #define FB_DEVICE "/dev/fb0" /* ========================================================================= * * STATE_DATA * ========================================================================= */ /** File descriptor for frame buffer device */ static int mce_fbdev_handle = -1; /** Flag for: use hybris for fb power control */ #ifdef ENABLE_HYBRIS static bool fbdev_use_hybris = false; #endif /** Flag for: Opening frame buffer device is allowed */ static bool mce_fbdev_allow_open = false; /* ========================================================================= * * FBDEV_FILE_DESCRIPTOR * ========================================================================= */ /** Frame buffer is open predicate * * @return true if frame buffer device is currently opened, false otherwise */ bool mce_fbdev_is_open(void) { return mce_fbdev_handle != -1; } /** Open frame buffer device unless denied * * @return true on success, or false on failure */ bool mce_fbdev_open(void) { #ifdef ENABLE_HYBRIS if( fbdev_use_hybris ) goto EXIT; #endif if( mce_fbdev_handle != -1 ) goto EXIT; if( !mce_fbdev_allow_open ) goto EXIT; mce_log(LL_NOTICE, "open frame buffer device"); if( (mce_fbdev_handle = open(FB_DEVICE, O_RDWR)) == -1 ) { if( errno != ENOENT ) mce_log(LL_WARN, "failed to open frame buffer device: %m"); goto EXIT; } mce_log(LL_DEBUG, "frame buffer device opened"); EXIT: return mce_fbdev_handle != -1; } /** Close frame buffer device */ void mce_fbdev_close(void) { if( mce_fbdev_handle == -1 ) goto EXIT; mce_log(LL_NOTICE, "closing frame buffer device"); close(mce_fbdev_handle), mce_fbdev_handle = -1; mce_log(LL_DEBUG, "closed frame buffer device"); EXIT: return; } /** Reopen frame buffer device unless denied */ void mce_fbdev_reopen(void) { if( mce_fbdev_allow_open ) mce_fbdev_close(); mce_fbdev_open(); } /* ========================================================================= * * POST_EXIT_LINGER * ========================================================================= */ /** Signal handler that just exits * * @param sig signal number (unused) */ static void mce_fbdev_linger_signal_hander(int sig) { (void) sig; _exit(EXIT_SUCCESS); } /** Create a child process to keep frame buffer device open after mce exits * * The frame buffer device powers off automatically when the last open * file descriptor gets closed. * * To allow the shutdown logo to stay on screen after lipstick and mce * have been terminated, we create a detached child process that hangs * on to frame buffer device. * * @param delay_ms how long the child process should linger */ void mce_fbdev_linger_after_exit(int delay_ms) { static const char msg[] = "closing frame buffer device after delay\n"; /* Fork a child process */ int child_pid = fork(); /* Deal with parent side and return to caller */ if( child_pid != 0 ) { if( child_pid < 0 ) mce_log(LL_ERR, "forking fbdev linger child failed: %m"); else mce_log(LL_DEBUG, "fbdev linger child: pid %d", child_pid); return; } /* Detach from parent so that we will not get killed with it */ setsid(); /* The parent process needs to pay special attention to signals * in order not to leave wakelocks active on exit. Also some * signals are transferred to mainloop via a pipe. None of this * should happen if the child process gets signals -> remove * all signal handlers the parent process has installed. */ mce_signal_handlers_remove(); /* Since the intent is that the child process exits very close to * power off, it is unlikely that core can be succesfully dumped * and processed -> trap all signals that do core dump by default * and make an _exit() instead. */ static const int trap[] = { SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGBUS, SIGSYS, SIGTRAP, SIGXCPU, SIGXFSZ, -1 }; for( size_t i = 0; trap[i] != -1; ++i ) signal(trap[i], mce_fbdev_linger_signal_hander); /* Close all files, except fbdev & stderr */ int nfd = getdtablesize(); for( int fd = 0; fd < nfd; ++fd ) { if( fd != mce_fbdev_handle && fd != STDERR_FILENO ) close(fd); } if( delay_ms < 500 ) delay_ms = 500; /* Wait ... */ struct timespec ts = { .tv_sec = (time_t)(delay_ms / 1000), .tv_nsec = (long)(delay_ms % 1000) * 1000000, }; while( nanosleep(&ts, &ts) == -1 && errno == EINTR ) { /* nop */ } /* If journald is still up, the end-of-linger message written to stderr * ends up in journal and attributed to parent mce process. * * ... and in case journald has already made an exit, we do not want * to die by SIGPIPE, so it needs to be ignored. */ signal(SIGPIPE, SIG_IGN); if( write(STDERR_FILENO, msg, sizeof msg - 1) < 0 ) { /* dontcare */ } /* Exit - the frame buffer device will power off if we * were the last process to have an open file descriptor */ _exit(EXIT_SUCCESS); } /* ========================================================================= * * FRAMEBUFFER_POWER * ========================================================================= */ /** Set the frame buffer power state * * MCE uses this function for display power control only if autosuspend * control sysfs files are not present. * * If there is a hw composer that is also doing fbdev ioctl() calls, * there is some change that kernel side troubles can be caused by * having two entities trying to control the same resource. * * @param power_in true to power up, false to power down */ void mce_fbdev_set_power(bool power_on) { mce_log(LL_DEBUG, "fbdev power %s", power_on ? "up" : "down"); if( mce_fbdev_handle != -1 ) { int value = power_on ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; if( ioctl(mce_fbdev_handle, FBIOBLANK, value) == -1 ) mce_log(LL_ERR, "%s: ioctl(FBIOBLANK,%d): %m", FB_DEVICE, value); else mce_log(LL_DEBUG, "success"); } #ifdef ENABLE_HYBRIS else if( fbdev_use_hybris ) { mce_hybris_framebuffer_set_power(power_on); } #endif return; } /* ========================================================================= * * MODULE_INIT * ========================================================================= */ /** Initialize frame buffer module */ void mce_fbdev_init(void) { /* allow opening frame buffer device */ mce_fbdev_allow_open = true; if( mce_fbdev_open() ) { mce_log(LL_NOTICE, "using ioctl for fb power control"); } #ifdef ENABLE_HYBRIS else if( mce_hybris_framebuffer_init() ) { mce_log(LL_NOTICE, "using libhybris for fb power control"); fbdev_use_hybris = true; } #endif else { mce_log(LL_WARN, "no fb power control available");; } } /** De-initialize frame buffer module */ void mce_fbdev_quit(void) { /* deny opening frame buffer device */ mce_fbdev_allow_open = false; mce_fbdev_close(); #ifdef ENABLE_HYBRIS if( fbdev_use_hybris ) { fbdev_use_hybris = false; mce_hybris_framebuffer_quit(); } #endif }