/**
* @file mce-dsme.c
* Interface code and logic between
* DSME (the Device State Management Entity)
* and MCE (the Mode Control Entity)
*
* Copyright © 2004-2011 Nokia Corporation and/or its subsidiary(-ies).
* Copyright © 2012-2019 Jolla Ltd.
*
* @author David Weinehall
* @author Ismo Laitinen
* @author Tapio Rantala
* @author Santtu Lakkala
* @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-dsme.h"
#include "mce.h"
#include "mce-log.h"
#include "mce-dbus.h"
#include "mce-worker.h"
#include
#include
#include
#include
#include
#include
/* ========================================================================= *
* MODULE DATA
* ========================================================================= */
/** Pointer to the dsmesock connection */
static dsmesock_connection_t *mce_dsme_socket_connection = NULL;
/** I/O watch for mce_dsme_socket_connection */
static guint mce_dsme_socket_recv_id = 0;
/** ID for delayed state transition reporting timer */
static guint mce_dsme_transition_id = 0;
/** Availability of dsme; from dsme_service_state_pipe */
static service_state_t dsme_service_state = SERVICE_STATE_UNDEF;
/** Availability of thermalmanager; from thermalmanager_service_state_pipe */
static service_state_t thermalmanager_service_state = SERVICE_STATE_UNDEF;
/** System state from dsme; fed to system_state_pipe */
static system_state_t system_state = MCE_SYSTEM_STATE_UNDEF;
/** Thermal state from thermalmanager (dsme); fed to thermal_state_pipe */
static thermal_state_t thermal_state = THERMAL_STATE_UNDEF;
/** Shutdown warning from dsme; fed to shutting_down_pipe */
static bool mce_dsme_shutting_down_flag = false;
/** Cached init_done state; assume unknown */
static tristate_t init_done = TRISTATE_UNKNOWN;
/* ========================================================================= *
* MODULE FUNCTIONS
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* UTILITY_FUNCTIONS
* ------------------------------------------------------------------------- */
static thermal_state_t mce_dsme_parse_thermal_state (const char *name);
static system_state_t mce_dsme_normalise_system_state (dsme_state_t dsmestate);
/* ------------------------------------------------------------------------- *
* WORKER_WATCHDOG
* ------------------------------------------------------------------------- */
static void mce_dsme_worker_done_cb (void *aptr, void *reply);
static void *mce_dsme_worker_pong_cb (void *aptr);
static void mce_dsme_worker_ping (void);;
/* ------------------------------------------------------------------------- *
* PROCESS_WATCHDOG
* ------------------------------------------------------------------------- */
static void mce_dsme_processwd_pong (void);
static void mce_dsme_processwd_init (void);
static void mce_dsme_processwd_quit (void);
/* ------------------------------------------------------------------------- *
* SYSTEM_STATE
* ------------------------------------------------------------------------- */
static void mce_dsme_query_system_state (void);
void mce_dsme_request_powerup (void);
void mce_dsme_request_reboot (void);
void mce_dsme_request_normal_shutdown (void);
/* ------------------------------------------------------------------------- *
* TRANSITION_SUBMODE
* ------------------------------------------------------------------------- */
static gboolean mce_dsme_transition_cb (gpointer data);
static void mce_dsme_transition_cancel (void);
static void mce_dsme_transition_schedule (void);
/* ------------------------------------------------------------------------- *
* SHUTTING_DOWN
* ------------------------------------------------------------------------- */
static bool mce_dsme_is_shutting_down (void);
static void mce_dsme_set_shutting_down (bool shutting_down);
/* ------------------------------------------------------------------------- *
* SOCKET_CONNECTION
* ------------------------------------------------------------------------- */
static bool mce_dsme_socket_send (void *msg);
static gboolean mce_dsme_socket_recv_cb (GIOChannel *source, GIOCondition condition, gpointer data);
static bool mce_dsme_socket_is_connected (void);
static bool mce_dsme_socket_connect (void);
static void mce_dsme_socket_disconnect (void);
/* ------------------------------------------------------------------------- *
* DBUS_HANDLERS
* ------------------------------------------------------------------------- */
static gboolean mce_dsme_dbus_thermal_state_change_cb (DBusMessage *const msg);
static void mce_dsme_dbus_thermal_state_reply_cb (DBusPendingCall *pc, void *aptr);
static void mce_dsme_dbus_cancel_thermal_state_query(void);
static void mce_dsme_dbus_start_thermal_state_query (void);
static gboolean mce_dsme_dbus_init_done_cb (DBusMessage *const msg);
static gboolean mce_dsme_dbus_shutdown_cb (DBusMessage *const msg);
static gboolean mce_dsme_dbus_thermal_shutdown_cb (DBusMessage *const msg);
static gboolean mce_dsme_dbus_battery_empty_shutdown_cb (DBusMessage *const msg);
static void mce_dsme_dbus_init(void);
static void mce_dsme_dbus_quit(void);
/* ------------------------------------------------------------------------- *
* DATAPIPE_TRACKING
* ------------------------------------------------------------------------- */
static void mce_dsme_datapipe_set_thermal_state (thermal_state_t state);
static void mce_dsme_datapipe_dsme_service_state_cb (gconstpointer data);
static void mce_dsme_datapipe_thermalmanager_service_state_cb(gconstpointer const data);
static void mce_dsme_datapipe_init_done_cb (gconstpointer data);
static void mce_dsme_datapipe_system_state_cb (gconstpointer data);
static void mce_dsme_datapipe_init(void);
static void mce_dsme_datapipe_quit(void);
/* ------------------------------------------------------------------------- *
* MODULE_INIT_EXIT
* ------------------------------------------------------------------------- */
gboolean mce_dsme_init(void);
void mce_dsme_exit(void);
/* ========================================================================= *
* UTILITY_FUNCTIONS
* ========================================================================= */
/** Convert system states used by dsme to the ones used in mce datapipes
*
* @param dsmestate System state constant used by dsme
*
* @return System state constant used within mce
*/
static system_state_t mce_dsme_normalise_system_state(dsme_state_t dsmestate)
{
system_state_t state = MCE_SYSTEM_STATE_UNDEF;
switch (dsmestate) {
case DSME_STATE_SHUTDOWN:
state = MCE_SYSTEM_STATE_SHUTDOWN;
break;
case DSME_STATE_USER:
state = MCE_SYSTEM_STATE_USER;
break;
case DSME_STATE_ACTDEAD:
state = MCE_SYSTEM_STATE_ACTDEAD;
break;
case DSME_STATE_REBOOT:
state = MCE_SYSTEM_STATE_REBOOT;
break;
case DSME_STATE_BOOT:
state = MCE_SYSTEM_STATE_BOOT;
break;
case DSME_STATE_NOT_SET:
break;
case DSME_STATE_TEST:
mce_log(LL_WARN,
"Received DSME_STATE_TEST; treating as undefined");
break;
case DSME_STATE_MALF:
mce_log(LL_WARN,
"Received DSME_STATE_MALF; treating as undefined");
break;
case DSME_STATE_LOCAL:
mce_log(LL_WARN,
"Received DSME_STATE_LOCAL; treating as undefined");
break;
default:
mce_log(LL_ERR,
"Received an unknown state from DSME; "
"treating as undefined");
break;
}
return state;
}
/** Convert thermal manager thermal state name to mce thermal_state_t
*
* @param name Thermal state name as received over D-Bus
*
* @return name mapped to thermal_state_t enumeration value
*/
static thermal_state_t mce_dsme_parse_thermal_state(const char *name)
{
thermal_state_t state = THERMAL_STATE_UNDEF;
if( name == 0 )
;
else if( !strcmp(name, thermalmanager_thermal_status_low) )
state = THERMAL_STATE_OK;
else if( !strcmp(name, thermalmanager_thermal_status_normal) )
state = THERMAL_STATE_OK;
else if( !strcmp(name, thermalmanager_thermal_status_warning) )
state = THERMAL_STATE_OK;
else if( !strcmp(name, thermalmanager_thermal_status_alert) )
state = THERMAL_STATE_OVERHEATED;
else if( !strcmp(name, thermalmanager_thermal_status_fatal) )
state = THERMAL_STATE_OVERHEATED;
return state;
}
/* ========================================================================= *
* WORKER_WATCHDOG
* ========================================================================= */
/** Validation context for the jobs passed from this module */
#define MCE_DSME_WORKERWD_JOB_CONTEXT "mce-dsme"
/** Descriptive name for the dummy sanity check worker thread jobs */
#define MCE_DSME_WORKERWD_JOB_NAME "ping"
/** Number of worker jobs scheduled */
static guint mce_dsme_worker_ping_cnt = 0;
/** Number of worker jobs executed */
static guint mce_dsme_worker_pong_cnt = 0;
/** Number of worker jobs notified */
static guint mce_dsme_worker_done_cnt = 0;
/** Flag for: worker thread issues noticed */
static bool mce_dsme_worker_misbehaving = false;
/** Handle dummy job finished notification
*
* @param aptr Ping count passed to the worker thread (as void pointer)
* @param reply Ping count returned from the worker thread (as void pointer)
*/
static void mce_dsme_worker_done_cb(void *aptr, void *reply)
{
(void)reply;
mce_dsme_worker_done_cnt = GPOINTER_TO_INT(aptr);
/* Check if the last job scheduled matches what got executed
* and notified as finished */
if( mce_dsme_worker_ping_cnt != mce_dsme_worker_pong_cnt ||
mce_dsme_worker_ping_cnt != mce_dsme_worker_done_cnt ) {
mce_log(LL_CRIT, "worker thread is misbehaving");
mce_dsme_worker_misbehaving = true;
}
}
/** Dummy job to be executed by the worker thread
*
* @param aptr Ping count (as void pointer)
*
* @return Ping count (as void pointer)
*/
static void *mce_dsme_worker_pong_cb(void *aptr)
{
/* Note: This is executed in the worker thread context */
mce_dsme_worker_pong_cnt = GPOINTER_TO_INT(aptr);
/* Check if the job we got to execute is the latest one
* scheduled from the main thread */
if( mce_dsme_worker_ping_cnt != mce_dsme_worker_pong_cnt ) {
mce_log(LL_CRIT, "worker thread is misbehaving");
mce_dsme_worker_misbehaving = true;
}
return aptr;
}
/** Run a dummy job through worker thread to make sure it is still functioning
*/
static void mce_dsme_worker_ping(void)
{
/* Check if previous job got executed as expected */
if( mce_dsme_worker_ping_cnt != mce_dsme_worker_pong_cnt ||
mce_dsme_worker_ping_cnt != mce_dsme_worker_done_cnt ) {
mce_log(LL_CRIT, "worker thread is possibly stuck");
mce_dsme_worker_misbehaving = true;
}
else if( mce_dsme_worker_misbehaving ) {
mce_dsme_worker_misbehaving = false;
mce_log(LL_CRIT, "worker thread is working again");
}
mce_dsme_worker_ping_cnt += 1;
mce_worker_add_job(MCE_DSME_WORKERWD_JOB_CONTEXT,
MCE_DSME_WORKERWD_JOB_NAME,
mce_dsme_worker_pong_cb,
mce_dsme_worker_done_cb,
GINT_TO_POINTER(mce_dsme_worker_ping_cnt));
}
/* ========================================================================= *
* PROCESS_WATCHDOG
* ========================================================================= */
/**
* Send pong message to the DSME process watchdog
*/
static void mce_dsme_processwd_pong(void)
{
/* Set up the message */
DSM_MSGTYPE_PROCESSWD_PONG msg =
DSME_MSG_INIT(DSM_MSGTYPE_PROCESSWD_PONG);
msg.pid = getpid();
/* Send the message */
mce_dsme_socket_send(&msg);
/* Run worker thread sanity check */
mce_dsme_worker_ping();
/* Execute hearbeat actions even if ping-pong ipc failed */
datapipe_exec_full(&heartbeat_event_pipe, GINT_TO_POINTER(0));
}
/**
* Register to DSME process watchdog
*/
static void mce_dsme_processwd_init(void)
{
/* Set up the message */
DSM_MSGTYPE_PROCESSWD_CREATE msg =
DSME_MSG_INIT(DSM_MSGTYPE_PROCESSWD_CREATE);
msg.pid = getpid();
/* Send the message */
mce_dsme_socket_send(&msg);
}
/**
* Unregister from DSME process watchdog
*/
static void mce_dsme_processwd_quit(void)
{
mce_log(LL_DEBUG, "Disabling DSME process watchdog");
/* Set up the message */
DSM_MSGTYPE_PROCESSWD_DELETE msg =
DSME_MSG_INIT(DSM_MSGTYPE_PROCESSWD_DELETE);
msg.pid = getpid();
/* Send the message */
mce_dsme_socket_send(&msg);
}
/* ========================================================================= *
* SYSTEM_STATE
* ========================================================================= */
/** Send system state inquiry
*/
static void mce_dsme_query_system_state(void)
{
/* Set up the message */
DSM_MSGTYPE_STATE_QUERY msg = DSME_MSG_INIT(DSM_MSGTYPE_STATE_QUERY);
/* Send the message */
mce_dsme_socket_send(&msg);
}
/** Request powerup
*/
void mce_dsme_request_powerup(void)
{
/* Set up the message */
DSM_MSGTYPE_POWERUP_REQ msg = DSME_MSG_INIT(DSM_MSGTYPE_POWERUP_REQ);
/* Send the message */
mce_dsme_socket_send(&msg);
}
/** Request reboot
*/
void mce_dsme_request_reboot(void)
{
if( datapipe_get_gint(osupdate_running_pipe) ) {
mce_log(LL_WARN, "reboot blocked; os update in progress");
goto EXIT;
}
/* Set up the message */
DSM_MSGTYPE_REBOOT_REQ msg = DSME_MSG_INIT(DSM_MSGTYPE_REBOOT_REQ);
/* Send the message */
mce_dsme_socket_send(&msg);
EXIT:
return;
}
/** Request normal shutdown
*/
void mce_dsme_request_normal_shutdown(void)
{
if( datapipe_get_gint(osupdate_running_pipe) ) {
mce_log(LL_WARN, "shutdown blocked; os update in progress");
goto EXIT;
}
/* Set up the message */
DSM_MSGTYPE_SHUTDOWN_REQ msg = DSME_MSG_INIT(DSM_MSGTYPE_SHUTDOWN_REQ);
/* Send the message */
mce_dsme_socket_send(&msg);
EXIT:
return;
}
/* ========================================================================= *
* TRANSITION_SUBMODE
* ========================================================================= */
/** Timer callback for ending transition submode
*
* @param data (not used)
*
* @return Always returns FALSE, to disable the timeout
*/
static gboolean mce_dsme_transition_cb(gpointer data)
{
(void)data;
mce_dsme_transition_id = 0;
mce_rem_submode_int32(MCE_SUBMODE_TRANSITION);
return FALSE;
}
/** Cancel delayed end of transition submode
*/
static void mce_dsme_transition_cancel(void)
{
if( mce_dsme_transition_id ) {
g_source_remove(mce_dsme_transition_id),
mce_dsme_transition_id = 0;
}
}
/** Schedule delayed end of transition submode
*/
static void mce_dsme_transition_schedule(void)
{
/* Remove existing timeout */
mce_dsme_transition_cancel();
/* Check if we have transition to end */
if( !(mce_get_submode_int32() & MCE_SUBMODE_TRANSITION) )
goto EXIT;
#if TRANSITION_DELAY > 0
/* Setup new timeout */
mce_dsme_transition_id =
g_timeout_add(TRANSITION_DELAY, mce_dsme_transition_cb, NULL);
#elif TRANSITION_DELAY == 0
/* Set up idle callback */
mce_dsme_transition_id =
g_idle_add(mce_dsme_transition_cb, NULL);
#else
/* Trigger immediately */
mce_dsme_transition_cb(0);
#endif
EXIT:
return;
}
/* ========================================================================= *
* SHUTTING_DOWN
* ========================================================================= */
/** Predicate for: device is shutting down
*
* @return true if shutdown is in progress, false otherwise
*/
static bool mce_dsme_is_shutting_down(void)
{
return mce_dsme_shutting_down_flag;
}
/** Update device is shutting down state
*
* @param shutting_down true if shutdown has started, false otherwise
*/
static void mce_dsme_set_shutting_down(bool shutting_down)
{
if( mce_dsme_shutting_down_flag == shutting_down )
goto EXIT;
mce_dsme_shutting_down_flag = shutting_down;
mce_log(LL_DEVEL, "Shutdown %s",
mce_dsme_shutting_down_flag ? "started" : "canceled");
/* Re-evaluate dsmesock connection */
if( !mce_dsme_shutting_down_flag )
mce_dsme_socket_connect();
datapipe_exec_full(&shutting_down_pipe,
GINT_TO_POINTER(mce_dsme_shutting_down_flag));
EXIT:
return;
}
/* ========================================================================= *
* SOCKET_CONNECTION
* ========================================================================= */
/**
* Generic send function for dsmesock messages
*
* @param msg A pointer to the message to send
*/
static bool mce_dsme_socket_send(void *msg)
{
bool res = false;
if( !mce_dsme_socket_connection ) {
mce_log(LL_WARN, "failed to send %s to dsme; %s",
dsmemsg_name(msg), "not connected");
goto EXIT;
}
if( dsmesock_send(mce_dsme_socket_connection, msg) == -1) {
mce_log(LL_ERR, "failed to send %s to dsme; %m",
dsmemsg_name(msg));
}
mce_log(LL_DEBUG, "%s sent to DSME", dsmemsg_name(msg));
res = true;
EXIT:
return res;
}
/** Callback for pending I/O from dsmesock
*
* @param source (not used)
* @param condition I/O condition that caused the callback to be called
* @param data (not used)
*
* @return TRUE if iowatch is to be kept, or FALSE if it should be removed
*/
static gboolean mce_dsme_socket_recv_cb(GIOChannel *source,
GIOCondition condition,
gpointer data)
{
gboolean keep_going = TRUE;
dsmemsg_generic_t *msg = 0;
DSM_MSGTYPE_STATE_CHANGE_IND *msg2;
(void)source;
(void)data;
if( condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) ) {
if( !mce_dsme_is_shutting_down() )
mce_log(LL_CRIT, "DSME socket hangup/error");
keep_going = FALSE;
goto EXIT;
}
if( !(msg = dsmesock_receive(mce_dsme_socket_connection)) )
goto EXIT;
if( DSMEMSG_CAST(DSM_MSGTYPE_CLOSE, msg) ) {
if( !mce_dsme_is_shutting_down() )
mce_log(LL_WARN, "DSME socket closed");
keep_going = FALSE;
}
else if( DSMEMSG_CAST(DSM_MSGTYPE_PROCESSWD_PING, msg) ) {
mce_dsme_processwd_pong();
}
else if( (msg2 = DSMEMSG_CAST(DSM_MSGTYPE_STATE_CHANGE_IND, msg)) ) {
system_state_t state = mce_dsme_normalise_system_state(msg2->state);
datapipe_exec_full(&system_state_pipe,
GINT_TO_POINTER(state));
}
else {
mce_log(LL_DEBUG, "Unhandled %s message received from DSME",
dsmemsg_name(msg));
}
EXIT:
free(msg);
if( !keep_going ) {
if( !mce_dsme_is_shutting_down() ) {
mce_log(LL_WARN, "DSME i/o notifier disabled;"
" assuming dsme was stopped");
}
/* mark notifier as removed */
mce_dsme_socket_recv_id = 0;
/* close and wait for possible dsme restart */
mce_dsme_socket_disconnect();
}
return keep_going;
}
/** Predicate for: socket connection to dsme exists
*
* @return true if connected, false otherwise
*/
static bool mce_dsme_socket_is_connected(void)
{
return mce_dsme_socket_connection && mce_dsme_socket_recv_id;
}
/** Initialise dsmesock connection
*
* @return true on success, false on failure
*/
static bool mce_dsme_socket_connect(void)
{
GIOChannel *iochan = NULL;
/* No new connections during shutdown */
if( mce_dsme_is_shutting_down() )
goto EXIT;
/* No new connections unless dsme dbus service is up */
if( dsme_service_state != SERVICE_STATE_RUNNING )
goto EXIT;
/* Already connected ? */
if( mce_dsme_socket_recv_id )
goto EXIT;
mce_log(LL_DEBUG, "Opening DSME socket");
if( !(mce_dsme_socket_connection = dsmesock_connect()) ) {
mce_log(LL_ERR, "Failed to open DSME socket");
goto EXIT;
}
mce_log(LL_DEBUG, "Adding DSME socket notifier");
if( !(iochan = g_io_channel_unix_new(mce_dsme_socket_connection->fd)) ) {
mce_log(LL_ERR,"Failed to set up I/O channel for DSME socket");
goto EXIT;
}
mce_dsme_socket_recv_id =
g_io_add_watch(iochan,
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
mce_dsme_socket_recv_cb, NULL);
/* Query the current system state; if the mainloop isn't running,
* this will trigger an update when the mainloop starts
*/
mce_dsme_query_system_state();
/* Register with DSME's process watchdog */
mce_dsme_processwd_init();
EXIT:
if( iochan ) g_io_channel_unref(iochan);
if( !mce_dsme_socket_recv_id )
mce_dsme_socket_disconnect();
return mce_dsme_socket_is_connected();
}
/** Close dsmesock connection
*/
static void mce_dsme_socket_disconnect(void)
{
if( mce_dsme_socket_recv_id ) {
mce_log(LL_DEBUG, "Removing DSME socket notifier");
g_source_remove(mce_dsme_socket_recv_id);
mce_dsme_socket_recv_id = 0;
/* Still having had a live socket notifier means: We have
* initiated the dsmesock disconnect and need to deactivate
* the process watchdog before actually disconnecting. */
mce_dsme_processwd_quit();
}
if( mce_dsme_socket_connection ) {
mce_log(LL_DEBUG, "Closing DSME socket");
dsmesock_close(mce_dsme_socket_connection);
mce_dsme_socket_connection = 0;
}
}
/* ========================================================================= *
* DBUS_HANDLERS
* ========================================================================= */
/** Pending thermal state query */
static DBusPendingCall *mce_dsme_thermal_state_query_pc = 0;
/** D-Bus callback for the thermal state change notification signal
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean
mce_dsme_dbus_thermal_state_change_cb(DBusMessage *const msg)
{
DBusError error = DBUS_ERROR_INIT;
const char *name = 0;
if( !dbus_message_get_args(msg, &error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
mce_log(LL_ERR, "Failed to parse %s.%s; %s: %s",
dbus_message_get_interface(msg),
dbus_message_get_member(msg),
error.name, error.message);
goto EXIT;
}
mce_log(LL_WARN, "thermal state signal: %s", name);
thermal_state_t state = mce_dsme_parse_thermal_state(name);
mce_dsme_datapipe_set_thermal_state(state);
EXIT:
dbus_error_free(&error);
return TRUE;
}
/** Handle reply to async query made from mce_dsme_thermal_state_query_async()
*/
static void
mce_dsme_dbus_thermal_state_reply_cb(DBusPendingCall *pc, void *aptr)
{
(void)aptr;
DBusMessage *rsp = 0;
DBusError err = DBUS_ERROR_INIT;
const char *name = 0;
if( pc != mce_dsme_thermal_state_query_pc )
goto EXIT;
dbus_pending_call_unref(mce_dsme_thermal_state_query_pc),
mce_dsme_thermal_state_query_pc = 0;
if( !(rsp = dbus_pending_call_steal_reply(pc)) ) {
mce_log(LL_WARN, "no reply");
goto EXIT;
}
if( dbus_set_error_from_message(&err, rsp) ) {
mce_log(LL_WARN, "error reply: %s: %s", err.name, err.message);
goto EXIT;
}
if( !dbus_message_get_args(rsp, &err,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID) ) {
mce_log(LL_WARN, "parse error: %s: %s", err.name, err.message);
goto EXIT;
}
mce_log(LL_DEBUG, "thermal state reply: %s", name);
thermal_state_t state = mce_dsme_parse_thermal_state(name);
mce_dsme_datapipe_set_thermal_state(state);
EXIT:
if( rsp ) dbus_message_unref(rsp);
dbus_error_free(&err);
return;
}
/** Cancel pending async thermal state query
*/
static void
mce_dsme_dbus_cancel_thermal_state_query(void)
{
if( mce_dsme_thermal_state_query_pc ) {
mce_log(LL_DEBUG, "cancel thermal state query");
dbus_pending_call_cancel(mce_dsme_thermal_state_query_pc);
dbus_pending_call_unref(mce_dsme_thermal_state_query_pc);
mce_dsme_thermal_state_query_pc = 0;
}
}
/** Initiate async query to find out current thermal state
*/
static void
mce_dsme_dbus_start_thermal_state_query(void)
{
mce_dsme_dbus_cancel_thermal_state_query();
mce_log(LL_DEBUG, "start thermal state query");
dbus_send_ex(thermalmanager_service,
thermalmanager_path,
thermalmanager_interface,
thermalmanager_get_thermal_state,
mce_dsme_dbus_thermal_state_reply_cb, 0, 0,
&mce_dsme_thermal_state_query_pc,
DBUS_TYPE_INVALID);
}
/** D-Bus callback for the init done notification signal
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean mce_dsme_dbus_init_done_cb(DBusMessage *const msg)
{
(void)msg;
mce_log(LL_DEVEL, "Received init done notification");
/* Remove transition submode after brief delay */
mce_dsme_transition_schedule();
return TRUE;
}
/** D-Bus callback for the shutdown notification signal
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean mce_dsme_dbus_shutdown_cb(DBusMessage *const msg)
{
(void)msg;
mce_log(LL_WARN, "Received shutdown notification");
mce_dsme_set_shutting_down(true);
return TRUE;
}
/** D-Bus callback for the thermal shutdown notification signal
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean
mce_dsme_dbus_thermal_shutdown_cb(DBusMessage *const msg)
{
(void)msg;
mce_log(LL_WARN, "Received thermal shutdown notification");
mce_datapipe_request_display_state(MCE_DISPLAY_ON);
return TRUE;
}
/** D-Bus callback for the battery empty shutdown notification signal
*
* @param msg The D-Bus message
*
* @return TRUE
*/
static gboolean
mce_dsme_dbus_battery_empty_shutdown_cb(DBusMessage *const msg)
{
(void)msg;
mce_log(LL_WARN, "Received battery empty shutdown notification");
mce_datapipe_request_display_state(MCE_DISPLAY_ON);
return TRUE;
}
/** Array of dbus message handlers */
static mce_dbus_handler_t mce_dsme_dbus_handlers[] =
{
/* signals */
{
.interface = "com.nokia.startup.signal",
.name = "init_done",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = mce_dsme_dbus_init_done_cb,
},
{
.interface = "com.nokia.dsme.signal",
.name = "shutdown_ind",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = mce_dsme_dbus_shutdown_cb,
},
{
.interface = "com.nokia.dsme.signal",
.name = "thermal_shutdown_ind",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = mce_dsme_dbus_thermal_shutdown_cb,
},
{
.interface = "com.nokia.dsme.signal",
.name = "battery_empty_ind",
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = mce_dsme_dbus_battery_empty_shutdown_cb,
},
{
.interface = thermalmanager_interface,
.name = thermalmanager_state_change_ind,
.type = DBUS_MESSAGE_TYPE_SIGNAL,
.callback = mce_dsme_dbus_thermal_state_change_cb,
},
/* sentinel */
{
.interface = 0
}
};
/** Add dbus handlers
*/
static void mce_dsme_dbus_init(void)
{
mce_dbus_handler_register_array(mce_dsme_dbus_handlers);
}
/** Remove dbus handlers
*/
static void mce_dsme_dbus_quit(void)
{
mce_dbus_handler_unregister_array(mce_dsme_dbus_handlers);
}
/* ========================================================================= *
* DATAPIPE_TRACKING
* ========================================================================= */
static void mce_dsme_datapipe_set_thermal_state(thermal_state_t state)
{
if( thermal_state != state ) {
/* All thermal state transitions are of interest - except for the
* UNDEF -> OK that is expected to happen during mce startup. */
int level = LL_WARN;
if( thermal_state == THERMAL_STATE_UNDEF && state == THERMAL_STATE_OK )
level = LL_DEBUG;
mce_log(level, "thermal state changed: %s -> %s",
thermal_state_repr(thermal_state),
thermal_state_repr(state));
thermal_state = state;
datapipe_exec_full(&thermal_state_pipe, GINT_TO_POINTER(thermal_state));
}
}
/** Datapipe trigger for dsme availability
*
* @param data DSME D-Bus service availability (as a void pointer)
*/
static void mce_dsme_datapipe_dsme_service_state_cb(gconstpointer const data)
{
service_state_t prev = dsme_service_state;
dsme_service_state = GPOINTER_TO_INT(data);
if( dsme_service_state == prev )
goto EXIT;
mce_log(LL_DEVEL, "DSME dbus service: %s -> %s",
service_state_repr(prev),
service_state_repr(dsme_service_state));
/* Re-evaluate dsmesock connection */
if( dsme_service_state == SERVICE_STATE_RUNNING )
mce_dsme_socket_connect();
EXIT:
return;
}
/** Datapipe trigger for thermalmanager availability
*
* @param data thermalmanager D-Bus service availability (as a void pointer)
*/
static void mce_dsme_datapipe_thermalmanager_service_state_cb(gconstpointer const data)
{
service_state_t prev = thermalmanager_service_state;
thermalmanager_service_state = GPOINTER_TO_INT(data);
if( thermalmanager_service_state == prev )
goto EXIT;
mce_log(LL_DEBUG, "thermalmanager dbus service: %s -> %s",
service_state_repr(prev),
service_state_repr(thermalmanager_service_state));
if( thermalmanager_service_state == SERVICE_STATE_RUNNING )
mce_dsme_dbus_start_thermal_state_query();
else
mce_dsme_dbus_cancel_thermal_state_query();
EXIT:
return;
}
/** Change notifications for init_done
*/
static void mce_dsme_datapipe_init_done_cb(gconstpointer data)
{
tristate_t prev = init_done;
init_done = GPOINTER_TO_INT(data);
if( init_done == prev )
goto EXIT;
mce_log(LL_DEBUG, "init_done = %s -> %s",
tristate_repr(prev),
tristate_repr(init_done));
if( init_done == TRISTATE_TRUE ) {
/* Remove transition submode after brief delay */
mce_dsme_transition_schedule();
}
EXIT:
return;
}
/** Handle system_state_pipe notifications
*
* Implemented as an input trigger to ensure this function gets
* executed before output triggers from other modules/plugins.
*
* @param data The system state (as a void pointer)
*/
static void mce_dsme_datapipe_system_state_cb(gconstpointer data)
{
system_state_t prev = system_state;
system_state = GPOINTER_TO_INT(data);
if( system_state == prev )
goto EXIT;
mce_log(LL_DEVEL, "system_state: %s -> %s",
system_state_repr(prev),
system_state_repr(system_state));
/* Set transition submode unless coming from MCE_SYSTEM_STATE_UNDEF */
if( prev != MCE_SYSTEM_STATE_UNDEF )
mce_add_submode_int32(MCE_SUBMODE_TRANSITION);
/* Handle LED patterns */
switch( system_state ) {
case MCE_SYSTEM_STATE_USER:
datapipe_exec_full(&led_pattern_activate_pipe,
MCE_LED_PATTERN_DEVICE_ON);
break;
case MCE_SYSTEM_STATE_SHUTDOWN:
case MCE_SYSTEM_STATE_REBOOT:
datapipe_exec_full(&led_pattern_deactivate_pipe,
MCE_LED_PATTERN_DEVICE_ON);
break;
default:
break;
}
/* Handle shutdown flag */
switch( system_state ) {
case MCE_SYSTEM_STATE_ACTDEAD:
case MCE_SYSTEM_STATE_USER:
/* Re-entry to actdead/user also means shutdown
* has been cancelled */
mce_dsme_set_shutting_down(false);
break;
case MCE_SYSTEM_STATE_SHUTDOWN:
case MCE_SYSTEM_STATE_REBOOT:
mce_dsme_set_shutting_down(true);
break;
default:
break;
}
EXIT:
return;
}
/** Array of datapipe handlers */
static datapipe_handler_t mce_dsme_datapipe_handlers[] =
{
// input triggers
{
.datapipe = &system_state_pipe,
.input_cb = mce_dsme_datapipe_system_state_cb,
},
// output triggers
{
.datapipe = &dsme_service_state_pipe,
.output_cb = mce_dsme_datapipe_dsme_service_state_cb,
},
{
.datapipe = &thermalmanager_service_state_pipe,
.output_cb = mce_dsme_datapipe_thermalmanager_service_state_cb,
},
{
.datapipe = &init_done_pipe,
.output_cb = mce_dsme_datapipe_init_done_cb,
},
// sentinel
{
.datapipe = 0,
}
};
static datapipe_bindings_t mce_dsme_datapipe_bindings =
{
.module = "mce-dsme",
.handlers = mce_dsme_datapipe_handlers,
};
/** Append triggers/filters to datapipes
*/
static void mce_dsme_datapipe_init(void)
{
mce_datapipe_init_bindings(&mce_dsme_datapipe_bindings);
}
/** Remove triggers/filters from datapipes
*/
static void mce_dsme_datapipe_quit(void)
{
mce_datapipe_quit_bindings(&mce_dsme_datapipe_bindings);
}
/* ========================================================================= *
* MODULE_INIT_EXIT
* ========================================================================= */
/** Init function for the mce-dsme component
*
* @return TRUE on success, FALSE on failure
*/
gboolean mce_dsme_init(void)
{
mce_worker_add_context(MCE_DSME_WORKERWD_JOB_CONTEXT);
mce_dsme_datapipe_init();
mce_dsme_dbus_init();
return TRUE;
}
/** Exit function for the mce-dsme component
*/
void mce_dsme_exit(void)
{
mce_worker_rem_context(MCE_DSME_WORKERWD_JOB_CONTEXT);
mce_dsme_dbus_quit();
mce_dsme_socket_disconnect();
mce_dsme_datapipe_quit();
/* Remove all timer sources & pending calls before returning */
mce_dsme_transition_cancel();
mce_dsme_dbus_cancel_thermal_state_query();
return;
}