/** * @file mce-hybris.c * Mode Control Entity - android hal access *

* Copyright (C) 2013-2019 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 . */ /* ========================================================================= * * Most of the functions in this module are just thunks that load and call * the real functionality from hybris-plugin on demand. If the hybris plugin * is not installed or underlying android code does not support some hw * elements these functions turn in to "NOP and return failure". * * In addition to the above this module also: * - moves sensor input data via pipe from worker thread context to the * thread that is running the glib mainloop. * - proxies diagnostic output from hybris-plugin to mce_log() * ========================================================================= */ #define MCE_HYBRIS_INTERNAL 1 #include "mce-hybris.h" #include "mce.h" #include "mce-log.h" #include "mce-conf.h" #include "mce-modules.h" #include #include #include #include #include /* ========================================================================= * * On some devices using in theory supported hybris functionality can lead * to problems. As a solution mce side configuration files can be used to * disable individual features. * ========================================================================= */ #define MCE_CONF_FEATURE_HYBRIS_GROUP "FeatureHybris" #define MCE_CONF_FEATURE_HYBRIS_FRAMEBUFFER "FrameBuffer" #define MCE_CONF_FEATURE_HYBRIS_BACKLIGHT "BackLight" #define MCE_CONF_FEATURE_HYBRIS_KEYPAD "KeyPad" #define MCE_CONF_FEATURE_HYBRIS_INDICATOR_LED "IndicatorLed" #define MCE_CONF_FEATURE_HYBRIS_PROXIMITY_SENSOR "ProximitySensor" #define MCE_CONF_FEATURE_HYBRIS_LIGHT_SENSOR "LightSensor" static bool mce_hybris_feature_supported(const char *key) { bool res = mce_conf_get_bool(MCE_CONF_FEATURE_HYBRIS_GROUP, key, true); mce_log(LL_WARN, "hybris feature %s is %s", key, res ? "allowed" : "denied"); return res; } static void mce_hybris_ps_set_hook(mce_hybris_ps_fn cb); static void mce_hybris_als_set_hook(mce_hybris_als_fn cb); /* ------------------------------------------------------------------------- * * Feeding sensor data via pipe to glib mainloop goes roughly as follows * * --- mce-libhybris-plugin worker thread -- * 1) uses blocking poll_dev->poll() function to read sensor data * 2) uses a set of callbacks to write the data to a pipe * --- mce-libhybris-module -- * 3) iowatch reads the data from pipe * 4) and passes the data to mce via another set of callbacks * --- mce sensor handling code -- * 5) can act on the data in the context that runs gmainloop * ------------------------------------------------------------------------- */ /** Sensor enumeration for mux @ worker thread -> pipe -> demux @ mainloop */ enum { EVEPIPE_ALS, EVEPIPE_PS, }; /** Sensor data passed over pipe */ typedef struct { int64_t time; // time stamp from android side int32_t type; // EVEPIPE_ALS or EVEPIPE_PS float value; // sensor data from android side } evepipe_t; /** Initialize once flag for sensor data pipe */ static bool evepipe_done = false; /** Callback for handling proximity data */ static mce_hybris_ps_fn evepipe_ps_cb = 0; /** Callback for handling ambient light data */ static mce_hybris_als_fn evepipe_als_cb = 0; /** The sensor data pipe */ static int evepipe_fd[2] = { -1, -1 }; /** I/O watch id for the sensor data pipe */ static guint evepipe_id = 0; /** I/O watch callback for handling pipe input * * @param channel (not used) * @param condition (not used) * @param data (not used) * * @return TRUE to keep the iowatch alive, or FALSE to remove it */ static gboolean evepipe_recv_cb(GIOChannel *channel, GIOCondition condition, gpointer data) { /* we just want the cb ... */ (void)channel; (void)condition; (void)data; gboolean keep_going = TRUE; evepipe_t eve[64]; if( condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) ) { keep_going = FALSE; } int rc = read(evepipe_fd[0], eve, sizeof eve); if( rc < 0 ) { switch( errno ) { case EINTR: case EAGAIN: break; default: mce_log(LL_ERR, "failed to read sensor events: %m"); keep_going = FALSE; break; } goto cleanup; } rc /= sizeof *eve; for( int i = 0; i < rc; ++i ) { switch( eve[i].type ) { case EVEPIPE_PS: if( evepipe_ps_cb ) { evepipe_ps_cb(eve[i].time, eve[i].value); } break; case EVEPIPE_ALS: if( evepipe_als_cb ) { evepipe_als_cb(eve[i].time, eve[i].value); } break; default: break; } } cleanup: if( !keep_going ) { mce_log(LL_CRIT, "disabling sensor event pipe iowatch"); evepipe_id = 0; } return keep_going; } /** Write sensor data to the pipe * * @param timestamp nanoseconds * @param type EVEPIPE_ALS or EVEPIPE_PS * @param data sensor data */ static void evepipe_send(int64_t timestamp, int32_t type, float data) { evepipe_t eve = { .time = timestamp, .type = type, .value = data, }; int rc = TEMP_FAILURE_RETRY(write(evepipe_fd[1], &eve, sizeof eve)); if( rc != sizeof eve ) { // TODO: since this happens from separate thread, we might want // to do something bit more clever in case the sensor data // overflows the pipe ... mce_abort(); } } /** Write PS data to the sensor data pipe * * @param timestamp nanoseconds * @param distance centimeters */ static void evepipe_send_ps(int64_t timestamp, float distance) { evepipe_send(timestamp, EVEPIPE_PS, distance); } /** Write ALS data to the sensor data pipe * @param timestamp nanoseconds * @param ligt lux */ static void evepipe_send_als(int64_t timestamp, float light) { evepipe_send(timestamp, EVEPIPE_ALS, light); } /** Close sensor data pipe * * @param reset_done true if we wish to return to uninitialized * state, or false to preserve "already tried * but failed" state */ static void evepipe_quit(bool reset_done) { /* remove io watch */ if( evepipe_id ) g_source_remove(evepipe_id), evepipe_id = 0; /* close pipe file descriptors */ if( evepipe_fd[1] != -1 ) close(evepipe_fd[1]), evepipe_fd[1] = -1; if( evepipe_fd[0] != -1 ) close(evepipe_fd[0]), evepipe_fd[0] = -1; if( reset_done ) evepipe_done = false; } /** Initialize sensor data pipe * * @return true on success, or false in case of errors */ static bool evepipe_init(void) { GIOChannel *chn = 0; if( evepipe_done ) { goto EXIT; } evepipe_done = true; if( pipe(evepipe_fd) == -1 ) { goto EXIT; } if( !(chn = g_io_channel_unix_new(evepipe_fd[0])) ) { goto EXIT; } evepipe_id = g_io_add_watch(chn, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, evepipe_recv_cb, 0); if( !evepipe_id ) { goto EXIT; } EXIT: if( chn != 0 ) g_io_channel_unref(chn); if( !evepipe_id ) { evepipe_quit(false); } return evepipe_id != 0; } /** Callback for forwarding logging from hybris-plugin to mce_log() * * @param lev syslog priority (=mce_log level) i.e. LOG_ERR etc * @param file source code path * @param func name of function within file * @param text diagnostic message to output */ static void log_cb(int lev, const char *file, const char *func, const char *text) { mce_log_file(lev, file, func, "%s", text); } /** INTERNAL Set up hybris-plugin -> mce_log() proxy * * @param base handle for hybris-plugin */ static void mce_hybris_set_logging_proxy(void *base) { static const char name[] = "mce_hybris_set_log_hook"; void (*func)(mce_hybris_log_fn cb) = 0; if( (func = dlsym(base, name)) ) { func(log_cb); } } /** INTERNAL Lookup path to hybris plugin DSO * * @return path to DSO, or NULL in case of errors */ static char *mce_hybris_module_path(void) { static const char module_name[] = "hybris.so"; gchar *module_dir = 0; char *module_path = 0; module_dir = mce_conf_get_string(MCE_CONF_MODULES_GROUP, MCE_CONF_MODULES_PATH, DEFAULT_MCE_MODULE_PATH); if( !module_dir ) { goto EXIT; } if( asprintf(&module_path, "%s/%s", module_dir, module_name) < 0 ) { module_path = 0; } EXIT: g_free(module_dir); return module_path; } /** Lookup function address from hybris plugin * * @name function name * * @return function address, or NULL in case of errors */ static void *mce_hybris_lookup_function(const char *name) { static void *base = 0; static bool done = false; void *addr = 0; if( !done ) { char *path = 0; done = true; if( !(path = mce_hybris_module_path()) ) { mce_log(LL_WARN, "could not locate hybris plugin"); } else if( access(path, F_OK) == -1 && errno == ENOENT ) { mce_log(LL_NOTICE, "%s: not installed", path); } else if( !(base = dlopen(path, RTLD_NOW|RTLD_LOCAL|RTLD_DEEPBIND)) ) { mce_log(LL_WARN, "%s: failed to load: %s", path, dlerror()); } else { mce_log(LL_NOTICE, "loaded hybris plugin"); mce_hybris_set_logging_proxy(base); } free(path); } if( base ) { if( !(addr = dlsym(base, name)) ) { mce_log(LL_ERR, "%s: failed to lookup: %s", name, dlerror()); } } return addr; } /** Glue macro to perform function address lookup once * * Assumes a local function pointer variable 'real' exists, * and the local function name is the same as the function * we want to lookup from the plugin.so */ #define RESOLVE do {\ static bool done = false; \ if( !done ) { \ done = true;\ real = mce_hybris_lookup_function(__FUNCTION__);\ }\ } while(0); /* Thunk functions that will either call the real functionality * from the hybris plugin, or fall back to NOP with appropriate * return value to signal failure. */ /** Release all resources allocated by this module */ void mce_hybris_quit(void) { static void (*real)(void) = 0; RESOLVE; evepipe_quit(true); if( real ) real(); } /* ------------------------------------------------------------------------- * * framebuffer device * ------------------------------------------------------------------------- */ /** Start using libhybris for frame buffer power control * * @return true if functionality supported, or false if not */ bool mce_hybris_framebuffer_init(void) { static bool (*real)(void) = 0; if( mce_hybris_feature_supported(MCE_CONF_FEATURE_HYBRIS_FRAMEBUFFER) ) RESOLVE; return !real ? false : real(); } /** Stop using libhybris for frame buffer power control */ void mce_hybris_framebuffer_quit(void) { static void (*real)(void) = 0; RESOLVE; if( real ) real(); } /** Turn frame buffer power on/off via libhybris * * @param state true for power on, false for power off * * @return true on success, or false on failure */ bool mce_hybris_framebuffer_set_power(bool state) { static bool (*real)(bool) = 0; RESOLVE; return !real ? false : real(state); } /* ------------------------------------------------------------------------- * * display backlight device * ------------------------------------------------------------------------- */ /** Start using libhybris for display backlight brightness control * * @return true if functionality supported, or false if not */ bool mce_hybris_backlight_init(void) { static bool (*real)(void) = 0; if( mce_hybris_feature_supported(MCE_CONF_FEATURE_HYBRIS_BACKLIGHT) ) RESOLVE; return !real ? false : real(); } /** Stop using libhybris for display backlight brightness control */ void mce_hybris_backlight_quit(void) { static void (*real)(void) = 0; RESOLVE; if( real ) real(); } /** Set display backlight brightness via libhybris * * @param level 0 for off, ..., 255 for maximum brightness * * @return true on success, or false on failure */ bool mce_hybris_backlight_set_brightness(int level) { static bool (*real)(int) = 0; RESOLVE; return !real ? false : real(level); } /* ------------------------------------------------------------------------- * * keypad backlight device * ------------------------------------------------------------------------- */ /** Start using libhybris for keypad backlight brightness control * * @return true if functionality supported, or false if not */ bool mce_hybris_keypad_init(void) { static bool (*real)(void) = 0; if( mce_hybris_feature_supported(MCE_CONF_FEATURE_HYBRIS_KEYPAD) ) RESOLVE; return !real ? false : real(); } /** Stop using libhybris for keypad backlight brightness control */ void mce_hybris_keypad_quit(void) { static void (*real)(void) = 0; RESOLVE; if( real ) real(); } /** Set keypad backlight brightness via libhybris * * @param level 0 for off, ..., 255 for maximum brightness * * @return true on success, or false on failure */ bool mce_hybris_keypad_set_brightness(int level) { static bool (*real)(int) = 0; RESOLVE; return !real ? false : real(level); } /* ------------------------------------------------------------------------- * * indicator led device * ------------------------------------------------------------------------- */ /** Start using libhybris for indicator led control * * @return true if functionality supported, or false if not */ bool mce_hybris_indicator_init(void) { static bool (*real)(void) = 0; if( mce_hybris_feature_supported(MCE_CONF_FEATURE_HYBRIS_INDICATOR_LED) ) RESOLVE; return !real ? false : real(); } /** Stop using libhybris for indicator led control */ void mce_hybris_indicator_quit(void) { static void (*real)(void) = 0; RESOLVE; if( real ) real(); } /** Set indicator led pattern via libhybris * * @param r red intensity 0 ... 255 * @param g green intensity 0 ... 255 * @param b blue intensity 0 ... 255 * @param ms_on milliseconds to keep the led on, or 0 for no flashing * @param ms_on milliseconds to keep the led off, or 0 for no flashing * * @return true on success, or false on failure */ bool mce_hybris_indicator_set_pattern(int r, int g, int b, int ms_on, int ms_off) { static bool (*real)(int,int,int,int,int) = 0; RESOLVE; return !real ? false : real(r,g,b, ms_on, ms_off); } /** Query if currently active led backend can support breathing * * @return true if breathing can be requested, false otherwise */ bool mce_hybris_indicator_can_breathe(void) { static bool (*real)(void) = 0; RESOLVE; /* If the plugin does not have this method, err on the safe side * and assume that breathing is not ok */ return !real ? false : real(); } /** Enable/disable timer based led breathing * * @param enable true for smooth sw transitions, false for hw blinking only */ void mce_hybris_indicator_enable_breathing(bool enable) { static void (*real)(bool) = 0; RESOLVE; if( real ) real(enable); } /** Set indicator led brightness * * @param level 1=minimum, 255=maximum * * @return true on success, or false on failure */ bool mce_hybris_indicator_set_brightness(int level) { static bool (*real)(int) = 0; RESOLVE; return !real ? false : real(level); } /* ------------------------------------------------------------------------- * * proximity sensor * ------------------------------------------------------------------------- */ /** Start using libhybris for proximity sensor input * * @return true if functionality supported, or false if not */ bool mce_hybris_ps_init(void) { bool (*real)(void) = 0; if( mce_hybris_feature_supported(MCE_CONF_FEATURE_HYBRIS_PROXIMITY_SENSOR) ) RESOLVE; return !real ? false : real(); } /** Stop using libhybris for proximity sensor input */ void mce_hybris_ps_quit(void) { static void (*real)(void) = 0; RESOLVE; evepipe_ps_cb = 0; if( real ) real(); } /** Enable/disable proximity sensor events via libhybris * * @param state true for enabling events, false for disabling * * @return true on success, or false on failure */ bool mce_hybris_ps_set_active(bool state) { static bool (*real)(bool) = 0; RESOLVE; return !real ? false : real(state); } /** INTERNAL Set hybris-plugin -> hybris-module PS event callback * * Note: the callback will be called from worker thread context * * @param cb callback plugin should use to send events to module side */ static void mce_hybris_ps_set_hook(mce_hybris_ps_fn cb) { static void (*real)(mce_hybris_ps_fn) = 0; RESOLVE; if( real ) real(cb); } /** Set proximity sensor event reporting callback * * Note: the callback will be called from the same context where * glib mainloop is running * * @param cb callback plugin should use to send events to application code * * @return true on success, or false on failure */ bool mce_hybris_ps_set_callback(mce_hybris_ps_fn cb) { bool res = true; if( (evepipe_ps_cb = cb) ) { mce_hybris_ps_set_hook(evepipe_send_ps); res = evepipe_init(); } else { mce_hybris_ps_set_hook(0); } return res; } /* ------------------------------------------------------------------------- * * ambient light sensor * ------------------------------------------------------------------------- */ /** Start using libhybris for ambient light sensor input * * @return true if functionality supported, or false if not */ bool mce_hybris_als_init(void) { bool (*real)(void) = 0; if( mce_hybris_feature_supported(MCE_CONF_FEATURE_HYBRIS_LIGHT_SENSOR) ) RESOLVE; return !real ? false : real(); } /** Stop using libhybris for ambient light sensor input */ void mce_hybris_als_quit(void) { static void (*real)(void) = 0; RESOLVE; evepipe_als_cb = 0; if( real ) real(); } /** Enable/disable ambient light sensor events via libhybris * * @param state true for enabling events, false for disabling * * @return true on success, or false on failure */ bool mce_hybris_als_set_active(bool state) { static bool (*real)(bool) = 0; RESOLVE; return !real ? false : real(state); } /** INTERNAL Set hybris-plugin -> hybris-module PS event callback * * Note: the callback will be called from worker thread context * * @param cb callback plugin should use to send events to module side */ static void mce_hybris_als_set_hook(mce_hybris_als_fn cb) { static void (*real)(mce_hybris_als_fn) = 0; RESOLVE; if( real ) real(cb); } /** Set ambient light sensor event reporting callback * * Note: the callback will be called from the same context where * glib mainloop is running * * @param cb callback plugin should use to send events to application code * * @return true on success, or false on failure */ bool mce_hybris_als_set_callback(mce_hybris_als_fn cb) { bool res = true; if( (evepipe_als_cb = cb) ) { mce_hybris_als_set_hook(evepipe_send_als); res = evepipe_init(); } else { mce_hybris_als_set_hook(0); } return res; }