/** * @file usbmode.c * * USB mode tracking module 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.h" #include "../mce-log.h" #include "../mce-dbus.h" #include #include #include #include /* ========================================================================= * * CABLE_STATE * ========================================================================= */ static usb_cable_state_t usbmode_cable_state_lookup (const char *name); static void usbmode_cable_state_update (const char *mode); /* ========================================================================= * * DBUS_IPC * ========================================================================= */ static void usbmode_dbus_query_cb (DBusPendingCall *pc, void *aptr); static void usbmode_dbus_query_cancel (void); static void usbmode_dbus_query_start (void); /* ========================================================================= * * DBUS_HANDLERS * ========================================================================= */ static gboolean usbmode_dbus_mode_changed_cb (DBusMessage *const msg); static void usbmode_dbus_init (void); static void usbmode_dbus_quit (void); /* ========================================================================= * * DATAPIPE_HANDLERS * ========================================================================= */ static void usbmode_datapipe_usbmoded_service_state_cb (gconstpointer data); static void usbmode_datapipe_init (void); static void usbmode_datapipe_quit (void); /* ========================================================================= * * MODULE_LOAD_UNLOAD * ========================================================================= */ G_MODULE_EXPORT const gchar *g_module_check_init(GModule *module); G_MODULE_EXPORT void g_module_unload(GModule *module); /* ========================================================================= * * CABLE_STATE * ========================================================================= */ /** Lookup table for mode strings usb_moded can be expected to emit * * The set of available modes is not static. New modes can be added * via usb-moded configuration files, but basically * * - "undefined" means cable is not connected * - any other name means cable is connected (=charging should be possible) * - some special cases signify that fs is mounted or otherwise directly * accessed via usb (mass storage and mtp modes) */ static const struct { const char *mode; usb_cable_state_t state; } mode_lut[] = { /* No cable attached */ { MODE_UNDEFINED, USB_CABLE_DISCONNECTED }, /* Attach / detach dedicated charger */ { CHARGER_CONNECTED, USB_CABLE_CONNECTED }, { MODE_CHARGER, USB_CABLE_CONNECTED }, { CHARGER_DISCONNECTED, USB_CABLE_DISCONNECTED }, /* Attach / detach pc cable */ { USB_CONNECTED, USB_CABLE_CONNECTED }, { MODE_CHARGING_FALLBACK, USB_CABLE_CONNECTED }, { USB_CONNECTED_DIALOG_SHOW, USB_CABLE_ASK_USER }, { MODE_ASK, USB_CABLE_ASK_USER }, { MODE_MASS_STORAGE, USB_CABLE_CONNECTED }, { MODE_MTP, USB_CABLE_CONNECTED }, { MODE_PC_SUITE, USB_CABLE_CONNECTED }, { MODE_DEVELOPER, USB_CABLE_CONNECTED }, { MODE_CHARGING, USB_CABLE_CONNECTED }, { MODE_HOST, USB_CABLE_CONNECTED }, { MODE_CONNECTION_SHARING, USB_CABLE_CONNECTED }, { MODE_DIAG, USB_CABLE_CONNECTED }, { MODE_ADB, USB_CABLE_CONNECTED }, { USB_DISCONNECTED, USB_CABLE_DISCONNECTED }, /* Busy can occur both on connect / after disconnect */ { MODE_BUSY, USB_CABLE_UNDEF }, /* Events ignored while evaluating cable state */ { DATA_IN_USE, USB_CABLE_UNDEF }, { USB_REALLY_DISCONNECT, USB_CABLE_UNDEF }, { USB_PRE_UNMOUNT, USB_CABLE_UNDEF }, { RE_MOUNT_FAILED, USB_CABLE_UNDEF }, { MODE_SETTING_FAILED, USB_CABLE_UNDEF }, { UMOUNT_ERROR, USB_CABLE_UNDEF }, }; /** Map reported usb mode to usb_cable_state_t used within mce * * @param mode Name of USB mode as reported by usb_moded * * @return usb_cable_state_t enumeration value */ static usb_cable_state_t usbmode_cable_state_lookup(const char *mode) { usb_cable_state_t state = USB_CABLE_DISCONNECTED; /* Getting a null/empty string here means that for one or another * reason we were not able to get the current mode from usb_moded. */ if( !mode || !*mode ) goto cleanup; /* Try to lookup from known set of modes */ for( size_t i = 0; i < G_N_ELEMENTS(mode_lut); ++i ) { if( strcmp(mode_lut[i].mode, mode) ) continue; state = mode_lut[i].state; goto cleanup; } /* The "undefined" that usb_moded uses to signal no usb cable connected * is included in the lookup table -> any unknown mode name is assumed * to mean that cable is connected & charging should be possible */ mce_log(LL_INFO, "unknown usb mode '%s'; assuming connected", mode); state = USB_CABLE_CONNECTED; cleanup: return state; } /** Update usb_cable_state_pipe according tomatch USB mode reported by usb_moded * * @param mode Name of USB mode as reported by usb_moded */ static void usbmode_cable_state_update(const char *mode) { mce_log(LL_NOTICE, "usb mode: %s", mode); usb_cable_state_t prev = datapipe_get_gint(usb_cable_state_pipe); usb_cable_state_t curr = usbmode_cable_state_lookup(mode); if( curr == USB_CABLE_UNDEF ) goto EXIT; if( prev == curr ) goto EXIT; mce_log(LL_DEVEL, "usb cable state: %s -> %s", usb_cable_state_repr(prev), usb_cable_state_repr(curr)); datapipe_exec_full(&usb_cable_state_pipe, GINT_TO_POINTER(curr)); EXIT: return; } /* ========================================================================= * * DBUS_IPC * ========================================================================= */ static DBusPendingCall *usbmode_dbus_query_pc = 0; /** Handle reply to async query made from usbmode_dbus_query_async() */ static void usbmode_dbus_query_cb(DBusPendingCall *pc, void *aptr) { (void)aptr; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; const char *mode = 0; if( pc != usbmode_dbus_query_pc ) goto EXIT; usbmode_dbus_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) || !dbus_message_get_args(rsp, &err, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID) ) { mce_log(LL_WARN, "error: %s: %s", err.name, err.message); goto EXIT; } usbmode_cable_state_update(mode); EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); dbus_pending_call_unref(pc); return; } /** Cancel pending async usb mode query */ static void usbmode_dbus_query_cancel(void) { if( usbmode_dbus_query_pc ) { dbus_pending_call_cancel(usbmode_dbus_query_pc); dbus_pending_call_unref(usbmode_dbus_query_pc); usbmode_dbus_query_pc = 0; } } /** Initiate async query to find out current usb mode */ static void usbmode_dbus_query_start(void) { usbmode_dbus_query_cancel(); dbus_send_ex(USB_MODED_DBUS_SERVICE, USB_MODED_DBUS_OBJECT, USB_MODED_DBUS_INTERFACE, USB_MODED_QUERY_MODE_REQ, usbmode_dbus_query_cb, 0, 0, &usbmode_dbus_query_pc, DBUS_TYPE_INVALID); } /* ========================================================================= * * DBUS_HANDLERS * ========================================================================= */ /** Handle usb mode change signals * * @param msg The D-Bus message * * @return TRUE */ static gboolean usbmode_dbus_mode_changed_cb(DBusMessage *const msg) { const char *mode = 0; DBusError err = DBUS_ERROR_INIT; if( !dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID) ) { mce_log(LL_WARN, "parse error: %s: %s", err.name, err.message); goto EXIT; } usbmode_cable_state_update(mode); EXIT: dbus_error_free(&err); return TRUE; } /** Array of dbus message handlers */ static mce_dbus_handler_t usbmode_dbus_handlers[] = { /* signals */ { .interface = USB_MODED_DBUS_INTERFACE, .name = USB_MODED_MODE_CHANGED_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .callback = usbmode_dbus_mode_changed_cb, }, /* sentinel */ { .interface = 0 } }; /** Install dbus message handlers */ static void usbmode_dbus_init(void) { mce_dbus_handler_register_array(usbmode_dbus_handlers); } /** Remove dbus message handlers */ static void usbmode_dbus_quit(void) { mce_dbus_handler_unregister_array(usbmode_dbus_handlers); } /* ========================================================================= * * DATAPIPE_HANDLERS * ========================================================================= */ static service_state_t usbmoded_service_state = SERVICE_STATE_UNDEF; /** Handle display_state_request_pipe notifications * * This is where display state transition starts * * @param data Requested display_state_t (as void pointer) */ static void usbmode_datapipe_usbmoded_service_state_cb(gconstpointer data) { service_state_t prev = usbmoded_service_state; usbmoded_service_state = GPOINTER_TO_INT(data); if( usbmoded_service_state == prev ) goto EXIT; mce_log(LL_NOTICE, "usbmoded_service_state = %s -> %s", service_state_repr(prev), service_state_repr(usbmoded_service_state)); if( usbmoded_service_state == SERVICE_STATE_RUNNING ) usbmode_dbus_query_start(); else usbmode_dbus_query_cancel(); EXIT: return; } /** Array of datapipe handlers */ static datapipe_handler_t usbmode_datapipe_handlers[] = { // output triggers { .datapipe = &usbmoded_service_state_pipe, .output_cb = usbmode_datapipe_usbmoded_service_state_cb, }, // sentinel { .datapipe = 0, } }; static datapipe_bindings_t usbmode_datapipe_bindings = { .module = "usbmode", .handlers = usbmode_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void usbmode_datapipe_init(void) { // triggers mce_datapipe_init_bindings(&usbmode_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void usbmode_datapipe_quit(void) { // triggers mce_datapipe_quit_bindings(&usbmode_datapipe_bindings); } /* ========================================================================= * * MODULE_LOAD_UNLOAD * ========================================================================= */ /** Init function for the usb mode tracking module * * @param module (not used) * * @return NULL on success, a string with an error message on failure */ const gchar *g_module_check_init(GModule *module) { (void)module; /* Append triggers/filters to datapipes */ usbmode_datapipe_init(); /* Install dbus message handlers */ usbmode_dbus_init(); return NULL; } /** Exit function for the usb mode tracking module * * @param module (not used) */ void g_module_unload(GModule *module) { (void)module; /* Remove dbus message handlers */ usbmode_dbus_quit(); /* Remove triggers/filters from datapipes */ usbmode_datapipe_quit(); /* Stop timers, async calls etc */ usbmode_dbus_query_cancel(); return; }