power: revamp power module 71/285071/4
authorYoungjae Cho <y0.cho@samsung.com>
Tue, 22 Nov 2022 08:27:36 +0000 (17:27 +0900)
committerYoungjae Cho <y0.cho@samsung.com>
Tue, 6 Dec 2022 06:07:37 +0000 (15:07 +0900)
- Integrate power-state-wait.c into power.c

- Transition is not preemptive anymore
  : Previosuly, transitions can cancel and intercept ongoing one.
    But it has changed to be enqueued if there is ongoing transition.
    It will be handled after handling all the other pending transitions.

Change-Id: Ifbe92098ea4c69f09f744609405dfb1e5e67f75f
Signed-off-by: Youngjae Cho <y0.cho@samsung.com>
src/power/power-dbus.c
src/power/power-event-lock.c
src/power/power-event-lock.h
src/power/power-off.c
src/power/power-off.h
src/power/power-state-wait.c [deleted file]
src/power/power-state-wait.h [deleted file]
src/power/power.c
src/power/power.h

index a0c6cfa..89162cc 100644 (file)
@@ -32,7 +32,6 @@
 
 #include "power-suspend.h"
 #include "power.h"
-#include "power-state-wait.h"
 
 #ifndef PROCESS_CHECK_TIMEOUT
 #define PROCESS_CHECK_TIMEOUT    600000 /* milisecond, 10 minute */
index 70bb44c..cca7921 100644 (file)
  * of an event from going to sleep regardless of the power state. */
 #define EVENT_LOCK           "eventlock"
 
-enum eventlock_type {
-       EL_MIN,
-       /* On receiving key event, expands EL_KEY to EL_KEY_* according to keycode */
-       EL_KEY,
-       EL_KEY_POWER = EL_KEY,
-       EL_KEY_BLUETOOTH,
-
-       EL_CHARGER,
-       EL_EVENT_ACTION,
-       /* add eventlock type here */
-       EL_MAX,
-};
-
 static struct dd_bitmap *eventlock;
 static int notifier_id[EL_MAX][2];
 
-static void event_wake_lock(enum eventlock_type type)
+void event_wake_lock(enum eventlock_type type)
 {
        int setcount;
 
@@ -69,7 +56,7 @@ static void event_wake_lock(enum eventlock_type type)
        }
 }
 
-static void event_wake_unlock(enum eventlock_type type)
+void event_wake_unlock(enum eventlock_type type)
 {
        int setcount;
 
index e2943dd..e054ec1 100644 (file)
 #ifndef __POWER_EVENT_LOCK_H__
 #define __POWER_EVENT_LOCK_H__
 
+enum eventlock_type {
+       EL_MIN,
+       /* On receiving key event, expands EL_KEY to EL_KEY_* according to keycode */
+       EL_KEY,
+       EL_KEY_POWER = EL_KEY,
+       EL_KEY_BLUETOOTH,
+
+       EL_CHARGER,
+       EL_EVENT_ACTION,
+       EL_POWER_TRANSITION_STATE,
+       /* add eventlock type here */
+       EL_MAX,
+};
+
 void power_event_lock_init(void);
+void event_wake_lock(enum eventlock_type type);
+void event_wake_unlock(enum eventlock_type type);
 
 #endif //__POWER_EVENT_LOCK_H__
 
index 91c8e52..2cf17c6 100644 (file)
@@ -205,24 +205,21 @@ int poweroff_check_revived(void)
        return POWER_STATE_NORMAL;
 }
 
-static void mark_poweroff_option(const struct trans_info *ti)
+static void mark_poweroff_option(uint64_t state, const char *option)
 {
        int fd;
        ssize_t len;
        const char *optpath;
-       const char *option;
 
-       if (ti->next == POWER_STATE_POWEROFF)
+       if (state == POWER_STATE_POWEROFF)
                optpath = POWEROFF_OPTPATH_POWEROFF;
-       else if (ti->next == POWER_STATE_REBOOT)
+       else if (state == POWER_STATE_REBOOT)
                optpath = POWEROFF_OPTPATH_REBOOT;
-       else if (ti->next == POWER_STATE_EXIT)
+       else if (state == POWER_STATE_EXIT)
                optpath = POWEROFF_OPTPATH_EXIT;
        else
                return;
 
-       option = (char *) ti->data;
-
        fd = open(optpath, O_RDWR|O_CREAT, S_IRUSR | S_IWUSR);
        if (fd < 0) {
                _E("Failed to create '%s'.", optpath);
@@ -294,18 +291,18 @@ static void system_shutdown_send_system_event(void)
        bundle_free(b);
 }
 
-void poweroff_prepare(const struct trans_info *ti)
+void poweroff_prepare(uint64_t state, void *option)
 {
        int vconf = VCONFKEY_SYSMAN_POWER_OFF_NONE;
 
-       if (!is_poweroff_state(ti->next))
+       if (!is_poweroff_state(state))
                return;
 
-       mark_poweroff_option(ti);
+       mark_poweroff_option(state, option);
 
-       if (ti->next == POWER_STATE_POWEROFF)
+       if (state == POWER_STATE_POWEROFF)
                vconf = VCONFKEY_SYSMAN_POWER_OFF_DIRECT;
-       else if (ti->next == POWER_STATE_REBOOT)
+       else if (state == POWER_STATE_REBOOT)
                vconf = VCONFKEY_SYSMAN_POWER_OFF_RESTART;
        vconf_set_int(VCONFKEY_SYSMAN_POWER_OFF_STATUS, vconf);
 
@@ -316,7 +313,7 @@ void poweroff_prepare(const struct trans_info *ti)
        system_shutdown_send_system_event();
 
        /* Skip running animation if it is silent reboot */
-       if (ti->data && strncmp(ti->data, "silent", sizeof("silent")) == 0)
+       if (option && strncmp(option, "silent", sizeof("silent")) == 0)
                return;
 
        poweroff_start_animation();
index a3c3466..7e961f7 100644 (file)
@@ -34,7 +34,7 @@
 void power_off_init(void);
 int poweroff_check_revived(void);
 void poweroff_request_shutdown(int state);
-void poweroff_prepare(const struct trans_info *ti);
+void poweroff_prepare(uint64_t state, void *option);
 void poweroff_main(void *udata);
 
 #endif /* __DEVICED_POWER_OFF_H__ */
diff --git a/src/power/power-state-wait.c b/src/power/power-state-wait.c
deleted file mode 100644 (file)
index a1c3f65..0000000
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * deviced
- *
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the License);
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <unistd.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <libsyscommon/list.h>
-#include <device/power-internal.h>
-
-#include "shared/log.h"
-#include "shared/device-notifier.h"
-#include "shared/common.h"
-
-#include "power.h"
-#include "power-state-wait.h"
-
-#define POWER_CONF_FILE    "/etc/deviced/power.conf"
-#define MAX_WAIT_SECOND    5 /* second */
-
-static int max_wait_timeout = 5; /* second */
-
-struct proc_info {
-       pid_t pid;
-       char comm[128];
-       guint64 state_bitmap;
-       int killed;
-};
-
-struct change_state_wait {
-       struct proc_info *pi;
-       guint64 id;
-       int state;
-};
-
-static GList *proc_list;
-static GList *waiting_list;
-static int max_wait_timer;
-static void (*__change_state_wait_done) (void *);
-static void *udata;
-static int waiting_state = POWER_STATE_START;
-
-static void change_state_wait_done(void)
-{
-       _D("%s wait done", state_name(waiting_state));
-
-       if (max_wait_timer) {
-               g_source_remove(max_wait_timer);
-               max_wait_timer = 0;
-       }
-
-       if (__change_state_wait_done) {
-               __change_state_wait_done(udata);
-       }
-}
-
-static gboolean max_wait_expired_cb(void *data)
-{
-       struct change_state_wait *csw;
-       struct proc_info *pi;
-       GList *elem, *elem_next;
-
-       max_wait_timer = 0;
-
-       SYS_G_LIST_FOREACH_SAFE(waiting_list, elem, elem_next, csw) {
-               if (kill(csw->pi->pid, 0) != 0)
-                       csw->pi->killed = 1;
-               else
-                       _E("pid=%d(%s) hasn't confirmed id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->id, state_name(csw->state));
-               waiting_list = g_list_remove_link(waiting_list, elem);
-               g_list_free(elem);
-               free(csw);
-       }
-
-       SYS_G_LIST_FOREACH_SAFE(proc_list, elem, elem_next, pi) {
-               if (pi->killed) {
-                       _D("Clean up not existing pid=%d(%s)", pi->pid, pi->comm);
-                       proc_list = g_list_remove_link(proc_list, elem);
-                       g_list_free(elem);
-                       free(pi);
-               }
-       }
-
-       change_state_wait_done();
-
-       return G_SOURCE_REMOVE;
-}
-
-int add_change_state_wait(pid_t pid, guint64 state)
-{
-       struct proc_info *pi;
-       GList *elem;
-
-       SYS_G_LIST_FOREACH(proc_list, elem, pi) {
-               if (pi->pid == pid) {
-                       pi->state_bitmap |= state;
-                       _D("pid=%d(%s) updated csw for %#"PRIx64, pid, pi->comm, state);
-                       return 0;
-               }
-       }
-
-       pi = calloc(1, sizeof(struct proc_info));
-       if (!pi)
-               return -ENOMEM;
-
-       pi->pid = pid;
-       pi->state_bitmap = state;
-       get_command(pid, pi->comm, sizeof(pi->comm));
-       SYS_G_LIST_APPEND(proc_list, pi);
-
-       _D("pid=%d(%s) added csw for %#"PRIx64, pid, pi->comm, state);
-
-       return 0;
-}
-
-void remove_change_state_wait(pid_t pid, guint64 state)
-{
-       struct proc_info *pi;
-       GList *elem, *elem_next;
-
-       _D("pid=%d removed csw for %#"PRIx64, pid, state);
-
-       SYS_G_LIST_FOREACH_SAFE(proc_list, elem, elem_next, pi) {
-               if (pi->pid == pid) {
-                       pi->state_bitmap &= ~state;
-                       if (pi->state_bitmap == 0) {
-                               proc_list = g_list_remove_link(proc_list, elem);
-                               g_list_free(elem);
-                               free(pi);
-                       }
-               }
-       }
-}
-
-int confirm_change_state_wait(pid_t pid, guint64 id)
-{
-       struct change_state_wait *csw;
-       GList *elem, *elem_next;
-
-       SYS_G_LIST_FOREACH_SAFE(waiting_list, elem, elem_next, csw) {
-               if (csw->pi->pid == pid && csw->id == id) {
-                       _D("pid=%d(%s) confirmed id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->id, state_name(csw->state));
-                       waiting_list = g_list_remove_link(waiting_list, elem);
-                       g_list_free(elem);
-                       free(csw);
-
-                       if (SYS_G_LIST_LENGTH(waiting_list) == 0) {
-                               change_state_wait_done();
-                               return 0;
-                       }
-               }
-       }
-
-       return 0;
-}
-
-int update_change_state_wait(guint64 id, const struct trans_info *ti, change_state_wait_done_cb callback, void *user_data)
-{
-       struct proc_info *pi;
-       struct change_state_wait *csw;
-       GList *elem, *elem_next;
-       static int n_waiting = 0;
-
-       // initialize timer
-       if (max_wait_timer) {
-               g_source_remove(max_wait_timer);
-               max_wait_timer = 0;
-       }
-
-       if (is_poweroff_state(waiting_state)) {
-               _W("No need to wait if poweroff had been triggered.");
-               return n_waiting;
-       }
-
-       // we are waiting for confirm of transition to the next state
-       waiting_state = ti->next;
-
-       _D("%s wait is triggered, id=%"PRIu64, state_name(waiting_state), id);
-
-       // cancel all ongoing csw that is not a waiting for the next state
-       SYS_G_LIST_FOREACH_SAFE(waiting_list, elem, elem_next, csw) {
-               if ((csw->state & waiting_state) == 0) {
-                       waiting_list = g_list_remove_link(waiting_list, elem);
-                       g_list_free(elem);
-                       _D("Cancel waiting: pid=%d(%s) for id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->id, state_name(csw->state));
-                       free(csw);
-               }
-       }
-
-       // add wait list
-       SYS_G_LIST_FOREACH(proc_list, elem, pi) {
-               if (pi->state_bitmap & waiting_state) {
-                       csw = calloc(1, sizeof(struct change_state_wait));
-                               if (!csw)
-                                       continue;
-
-                       csw->pi = pi;
-                       csw->id = id;
-                       csw->state = waiting_state;
-                       SYS_G_LIST_APPEND(waiting_list, csw);
-               }
-       }
-
-       n_waiting = SYS_G_LIST_LENGTH(waiting_list);
-       if (n_waiting == 0) {
-               _D("There were no csw requests for %s, skip waiting", state_name(waiting_state));
-               __change_state_wait_done = NULL;
-               return 0;
-       }
-
-       __change_state_wait_done = callback;
-       udata = user_data;
-
-       _D("The number of pending wait confirm=%d", n_waiting);
-       max_wait_timer = g_timeout_add_seconds(max_wait_timeout, max_wait_expired_cb, NULL);
-
-       return n_waiting;
-}
-
-static int load_max_wait_timeout(struct parse_result *result, void *user_data)
-{
-       if (MATCH(result->section, "PowerState")
-               && MATCH(result->name, "ChangeStateMaxWaitSecond"))
-               max_wait_timeout = atoi(result->value);
-
-       return 0;
-}
-
-void power_state_wait_init(void)
-{
-       config_parse(POWER_CONF_FILE, load_max_wait_timeout, NULL);
-
-       CRITICAL_LOG("Change state max wait timeout=%ds", max_wait_timeout);
-}
diff --git a/src/power/power-state-wait.h b/src/power/power-state-wait.h
deleted file mode 100644 (file)
index 3a2660f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * deviced
- *
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the License);
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __POWER_STATE_WAIT_H__
-#define __POWER_STATE_WAIT_H__
-
-#include <unistd.h>
-#include "power.h"
-
-typedef void (*change_state_wait_done_cb) (void *);
-
-int add_change_state_wait(pid_t pid, guint64 state);
-void remove_change_state_wait(pid_t pid, guint64 state);
-int confirm_change_state_wait(pid_t pid, guint64 id);
-int update_change_state_wait(guint64 id, const struct trans_info *ti, change_state_wait_done_cb cb, void *user_data);
-void power_state_wait_init(void);
-
-#endif //__POWER_STATE_WAIT_H__
index 519a126..0ae9be6 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <stdio.h>
 #include <stdint.h>
+#include <assert.h>
 #include <inttypes.h>
 #include <linux/input.h>
 #include <libsyscommon/libgdbus.h>
 #include "shared/devices.h"
 #include "shared/device-notifier.h"
 #include "shared/log.h"
+#include "shared/common.h"
 #include "power.h"
 #include "power-suspend.h"
 #include "power-dbus.h"
 #include "power-boot.h"
 #include "power-off.h"
-#include "power-state-wait.h"
 #include "power-event-lock.h"
 
-#define EVENT_TYPE_SLEEP    0
-#define EVENT_TYPE_WAKEUP   1
+#define POWER_CONF_FILE              "/etc/deviced/power.conf"
+#define POWER_STATE_INDEX(state)     ((state) ? 63 - __builtin_clzll(state) : 0)
+#define DEFAULT_MAX_WAIT_SECOND      5 /* second */
+#define MAX_DESC_LEN                 256
 
 static int delayed_init_done = 0;
-static uint64_t state_transition_counter = 0;
-static uint64_t current = POWER_STATE_START;
+static uint64_t current = POWER_STATE_START; /* current power state */
+static int max_wait_timeout = DEFAULT_MAX_WAIT_SECOND;
+static GQueue *transition_queue;
+static struct {
+       int id;
+       int ongoing;
+       struct trans_info ti;
+       GList *waitings;
+       int max_wait_timer;
+} transition_context;
+
+struct proc_info {
+       pid_t pid;
+       char comm[128];
+       guint64 state_bitmap;
+};
+static GList *proc_list;
 
-/* hold trans_info until delayed_init_done */
-static GList *deferred_transition_list;
+struct change_state_wait {
+       struct proc_info *pi;
+       guint64 id;
+       int state;
+};
 
-static int power_transition_state(void *data);
+static int transition_request_callback(void *data);
+static void prepare_transition(const struct trans_info *ti);
+static void action_transition(void);
+
+static void power_action_normal(void *data);
+static void power_action_sleep(void *data);
+static void power_action_poweroff(void *data);
+static void (*const action_on_state[POWER_STATE_MAX_INDEX]) (void *) = {
+       [POWER_STATE_NORMAL_INDEX]   = power_action_normal,
+       [POWER_STATE_SLEEP_INDEX]    = power_action_sleep,
+       [POWER_STATE_POWEROFF_INDEX] = power_action_poweroff,
+       [POWER_STATE_REBOOT_INDEX]   = power_action_poweroff,
+       [POWER_STATE_EXIT_INDEX]     = power_action_poweroff,
+};
 
 uint64_t power_get_state(void)
 {
        return current;
 }
 
-static void power_wake_unlock(void *udata)
+static void cleanup_waiting_list(gpointer data)
+{
+       struct change_state_wait *csw = (struct change_state_wait *) data;
+
+       _E("%s(pid=%d) hasn't confirmed id=%"PRIu64"(%s)", csw->pi->comm, csw->pi->pid, csw->id, state_name(csw->state));
+
+       if (kill(csw->pi->pid, 0) != 0) {
+               _E("cleanup not existing process: %s(pid=%d)", csw->pi->comm, csw->pi->pid);
+               proc_list = g_list_remove(proc_list, csw->pi);
+               free(csw->pi);
+       }
+
+       free(csw);
+}
+
+static gboolean max_wait_expired_cb(void *data)
+{
+       transition_context.max_wait_timer = 0;
+
+       g_list_free_full(g_steal_pointer(&transition_context.waitings), cleanup_waiting_list);
+
+       action_transition();
+
+       return G_SOURCE_REMOVE;
+}
+
+static gint find_pi_by_pid(gconstpointer data, gconstpointer udata)
+{
+       const struct proc_info *pi = (const struct proc_info *) data;
+       const pid_t pid = *(const pid_t *) udata;
+
+       if (pi->pid == pid)
+               return 0;
+
+       return -1;
+}
+
+int add_change_state_wait(pid_t pid, guint64 state)
+{
+       struct proc_info *pi = NULL;
+       GList *l = NULL;
+
+       l = g_list_find_custom(proc_list, &pid, find_pi_by_pid);
+       if (l && l->data) {
+               pi = l->data;
+               pi->state_bitmap |= state;
+               _D("pid=%d(%s) updated csw for %#"PRIx64, pi->pid, pi->comm, state);
+               return 0;
+       }
+
+       pi = calloc(1, sizeof(struct proc_info));
+       if (!pi)
+               return -ENOMEM;
+
+       pi->pid = pid;
+       pi->state_bitmap = state;
+       get_command(pid, pi->comm, sizeof(pi->comm));
+       proc_list = g_list_append(proc_list, pi);
+
+       _D("pid=%d(%s) added csw for %#"PRIx64, pid, pi->comm, state);
+
+       return 0;
+}
+
+void remove_change_state_wait(pid_t pid, guint64 state)
+{
+       struct proc_info *pi = NULL;
+       GList *l = NULL;
+
+       l = g_list_find_custom(proc_list, &pid, find_pi_by_pid);
+       if (!l)
+               return;
+
+       if (!l->data)
+               return;
+
+       pi = l->data;
+
+       _D("pid=%d(%s) removed csw for %#"PRIx64, pi->pid, pi->comm, state);
+
+       pi->state_bitmap &= ~state;
+       if (pi->state_bitmap == 0) {
+               proc_list = g_list_remove_link(proc_list, l);
+               free(g_steal_pointer(&l->data));
+               g_list_free(l);
+       }
+}
+
+static gint find_csw_by_pid_id(gconstpointer data, gconstpointer udata)
+{
+       const struct change_state_wait *csw = (const struct change_state_wait *) data;
+       const struct change_state_wait *target = (const struct change_state_wait *) udata;
+
+       if (csw->pi->pid == target->pi->pid && csw->id == target->id)
+               return 0;
+
+       return -1;
+}
+
+int confirm_change_state_wait(pid_t pid, guint64 id)
+{
+       struct proc_info target_pi = { .pid = pid };
+       struct change_state_wait target_csw = { .pi = &target_pi, .id = id };
+       struct change_state_wait *csw = NULL;
+       GList *l = NULL;
+
+       l = g_list_find_custom(transition_context.waitings, &target_csw, find_csw_by_pid_id);
+       if (!l)
+               return 0;
+
+       if (!l->data)
+               return 0;
+
+       csw = l->data;
+       _D("pid=%d(%s) confirmed id=%"PRIu64"(%s)", csw->pi->pid, csw->pi->comm, csw->id, state_name(csw->state));
+
+       transition_context.waitings = g_list_remove_link(transition_context.waitings, l);
+       free(g_steal_pointer(&l->data));
+       g_list_free(l);
+
+       // continue waiting
+       if (g_list_length(transition_context.waitings) != 0)
+               return 0;
+
+       // the last csw is cleared
+       g_source_remove(transition_context.max_wait_timer);
+       transition_context.max_wait_timer = 0;
+
+       action_transition();
+
+       return 0;
+}
+
+static int alloc_unique_id(void)
+{
+       static int id = 0;
+
+       return ++id;
+}
+
+static void power_action_normal(void *udata)
+{
+       power_acquire_wakelock();
+}
+
+static void power_action_sleep(void *udata)
 {
        /* for POWER_STATE_NORMAL, POWER_STATE_POWEROFF, do not wake unlock */
        if (current != POWER_STATE_SLEEP) {
@@ -63,254 +242,378 @@ static void power_wake_unlock(void *udata)
        power_release_wakelock();
 }
 
-static void broadcast_transition_info(const struct trans_info *ti)
+static void power_action_poweroff(void *data)
+{
+       // do not transition anymore after poweroff
+       unregister_notifier(DEVICE_NOTIFIER_REQUEST_TRANSITION_STATE, transition_request_callback);
+
+       poweroff_prepare(current, data);
+       poweroff_main((void *)(intptr_t) current);
+}
+
+static void broadcast_transition_info(void)
 {
        const char *signame;
 
-       if (ti->next == POWER_STATE_START)
-               signame = SIGNAME_CHANGE_STATE_TO_START;
-       else if (ti->next == POWER_STATE_NORMAL)
-               signame = SIGNAME_CHANGE_STATE_TO_NORMAL;
-       else if (ti->next == POWER_STATE_SLEEP)
-               signame = SIGNAME_CHANGE_STATE_TO_SLEEP;
-       else if (ti->next == POWER_STATE_POWEROFF)
-               signame = SIGNAME_CHANGE_STATE_TO_POWEROFF;
-       else if (ti->next == POWER_STATE_REBOOT)
-               signame = SIGNAME_CHANGE_STATE_TO_REBOOT;
-       else if (ti->next == POWER_STATE_EXIT)
-               signame = SIGNAME_CHANGE_STATE_TO_EXIT;
+       if (transition_context.ti.next == POWER_STATE_START)
+               signame = DEVICED_SIGNAL_POWER_CHANGE_STATE_TO_START;
+       else if (transition_context.ti.next == POWER_STATE_NORMAL)
+               signame = DEVICED_SIGNAL_POWER_CHANGE_STATE_TO_NORMAL;
+       else if (transition_context.ti.next == POWER_STATE_SLEEP)
+               signame = DEVICED_SIGNAL_POWER_CHANGE_STATE_TO_SLEEP;
+       else if (transition_context.ti.next == POWER_STATE_POWEROFF)
+               signame = DEVICED_SIGNAL_POWER_CHANGE_STATE_TO_POWEROFF;
+       else if (transition_context.ti.next == POWER_STATE_REBOOT)
+               signame = DEVICED_SIGNAL_POWER_CHANGE_STATE_TO_REBOOT;
+       else if (transition_context.ti.next == POWER_STATE_EXIT)
+               signame = DEVICED_SIGNAL_POWER_CHANGE_STATE_TO_EXIT;
        else
                return;
 
        gdbus_signal_emit(NULL, DEVICED_PATH_POWER, DEVICED_INTERFACE_POWER, signame,
-               g_variant_new("(ttti)", ti->curr, ti->next, state_transition_counter, ti->reason));
+               g_variant_new("(ttti)",
+                       transition_context.ti.curr,
+                       transition_context.ti.next,
+                       transition_context.id,
+                       transition_context.ti.reason));
 }
 
-static void power_transition_start_to_normal(const struct trans_info *ti)
+static uint64_t available_starting_state(void)
 {
-       current = POWER_STATE_NORMAL;
+       struct trans_info *ti = NULL;
 
-       power_acquire_wakelock();
-       broadcast_transition_info(ti);
+       // If there is no ongoing transition, the available state is the current state.
+       if (!transition_context.ongoing)
+               return current;
+
+       // If there is ongoing transition and there are also some pending transitions,
+       // the available state is the last state of pending transitions.
+       // Otherwise, if there is ongoing transition but there are no pending transitions,
+       // the available state is the destination of ongoing transition.
+       return (ti = g_queue_peek_head(transition_queue)) ? ti->next : transition_context.ti.next;
 }
 
-static void power_transition_start_to_sleep(const struct trans_info *ti)
+static int dequeue_transition(struct trans_info *ti)
 {
-       int waiting;
+       struct trans_info *tail = NULL;
 
-       current = POWER_STATE_SLEEP;
+       if (!ti)
+               return -1;
 
-       power_acquire_wakelock();
-       broadcast_transition_info(ti);
-       waiting = update_change_state_wait(state_transition_counter, ti, power_wake_unlock, NULL);
-       if (waiting > 0) {
-               _D("Defer wake unlock.");
-               return;
-       }
+       tail = g_queue_pop_tail(transition_queue);
+       if (!tail)
+               return -1;
+
+       *ti = (struct trans_info) {
+               .curr = tail->curr,
+               .next = tail->next,
+               .reason = tail->reason,
+               .data = tail->data,
+       };
+
+       free(tail);
 
-       power_wake_unlock(NULL);
+       return 0;
 }
 
-static void power_transition_normal_to_normal(const struct trans_info *ti)
+static void enqueue_transition(const struct trans_info *ti)
 {
-       broadcast_transition_info(ti);
-       update_change_state_wait(state_transition_counter, ti, NULL, NULL);
+       struct trans_info *ti_new = NULL;
+
+       if (!ti)
+               return;
+
+       ti_new = calloc(1, sizeof(struct trans_info));
+       if (!ti_new)
+               return;
+
+       g_queue_push_head(transition_queue, memcpy(ti_new, ti, sizeof(struct trans_info)));
 }
 
-static void power_transition_normal_to_sleep(const struct trans_info *ti)
+static void init_waiting_list(gpointer data, gpointer udata)
 {
-       int waiting;
+       uint64_t waiting_state;
+       struct proc_info *pi = (struct proc_info *) data;
+       struct change_state_wait *csw = NULL;
 
-       current = POWER_STATE_SLEEP;
+       waiting_state = transition_context.ti.next;
 
-       broadcast_transition_info(ti);
-       waiting = update_change_state_wait(state_transition_counter, ti, power_wake_unlock, NULL);
-       if (waiting > 0) {
-               _D("Defer wake unlock.");
+       if ((pi->state_bitmap & waiting_state) == 0)
                return;
-       }
 
-       power_wake_unlock(NULL);
+       csw = calloc(1, sizeof(struct change_state_wait));
+       if (!csw)
+               return;
+
+       *csw = (struct change_state_wait) {
+               .pi = pi,
+               .id = transition_context.id,
+               .state = waiting_state,
+       };
+
+       *(int *) udata += 1;
+
+       transition_context.waitings = g_list_append(transition_context.waitings, csw);
 }
 
-static void power_transition_sleep_to_normal(const struct trans_info *ti)
+static char *state_abbr_name(uint64_t state)
 {
-       current = POWER_STATE_NORMAL;
+       if (state == POWER_STATE_START)
+               return "START";
+       if (state == POWER_STATE_NORMAL)
+               return "NORMAL";
+       if (state == POWER_STATE_SLEEP)
+               return "SLEEP";
+       if (state == POWER_STATE_POWEROFF)
+               return "POWEROFF";
+       if (state == POWER_STATE_REBOOT)
+               return "REBOOT";
+       if (state == POWER_STATE_EXIT)
+               return "EXIT";
+
+       return "INVALID";
+}
 
-       power_acquire_wakelock();
-       broadcast_transition_info(ti);
-       update_change_state_wait(state_transition_counter, ti, NULL, NULL);
+static void reload_transition_sequence(GList **sequence)
+{
+       GList *new_sequence = NULL;
+
+       g_list_free(g_steal_pointer(sequence));
+
+       new_sequence = g_list_append(new_sequence, state_abbr_name(transition_context.ti.curr));
+       new_sequence = g_list_append(new_sequence, state_abbr_name(transition_context.ti.next));
+
+       *sequence = new_sequence;
 }
 
-static void power_transition_sleep_to_sleep(const struct trans_info *ti)
+static const char* build_description(GList *sequence, const int seqnum)
 {
-       int waiting;
+       static char buffer[MAX_DESC_LEN] = { 0, };
+       char *head = buffer;
+       // leave 1 byte for terminating null character
+       char *const tail = buffer + sizeof(buffer) - 1;
 
-       power_acquire_wakelock();
-       broadcast_transition_info(ti);
-       waiting = update_change_state_wait(state_transition_counter, ti, power_wake_unlock, NULL);
-       if (waiting > 0) {
-               _D("Defer wake unlock.");
-               return;
+       // additional (length-1) placeholders for transitioning arrow between each state
+       int max_sequence = g_list_length(sequence) * 2 - 1;
+
+       GList *iter = sequence;
+       int n = 0;
+
+       memset(buffer, 0, sizeof(buffer));
+
+       while (iter) {
+               GList *next = iter->next;
+
+               if (n == seqnum)
+                       head += snprintf(head, tail - head, "[%s] ", (const char *) iter->data);
+               else
+                       head += snprintf(head, tail - head, " %s  ", (const char *) iter->data);
+
+               if (head >= tail)
+                       break;
+
+               ++n;
+               if (n >= max_sequence)
+                       break;
+
+               if (n == seqnum)
+                       head += snprintf(head, tail - head, "[=>] ");
+               else
+                       head += snprintf(head, tail - head, " =>  ");
+
+               if (head >= tail)
+                       break;
+
+               ++n;
+               iter = next;
        }
 
-       power_wake_unlock(NULL);
+       return buffer;
 }
 
-static void power_transition_to_poweroff(const struct trans_info *ti)
+static void transition_description(int reload)
 {
-       int waiting;
+       static GList *transition_sequence = NULL;
+       static int seqnum = 0;
 
-       _D("Transition power state: %s -> %s, option=%s, reason=%d",
-               state_name(ti->curr), state_name(ti->next), (char *) ti->data, ti->reason);
+       if (reload) {
+               seqnum = 0;
+               reload_transition_sequence(&transition_sequence);
+       }
 
-       current = ti->next;
+       _D("%s", build_description(transition_sequence, seqnum++));
+}
 
-       // do not transition anymore after poweroff
-       unregister_notifier(DEVICE_NOTIFIER_REQUEST_TRANSITION_STATE, power_transition_state);
+static void trigger_transition(void)
+{
+       int waiting = 0;
 
-       poweroff_prepare(ti);
-       broadcast_transition_info(ti);
-       waiting = update_change_state_wait(state_transition_counter, ti, poweroff_main, (void *)(intptr_t) ti->next);
-       if (waiting > 0) {
-               _D("Defer poweroff.");
-               return;
+       assert(transition_context.max_wait_timer == 0);
+       assert(is_poweroff_state(current) == 0);
+
+       transition_context.id = alloc_unique_id();
+
+       if (!transition_context.ongoing) {
+               // hold secondary wakelock not to fall asleep during transition
+               event_wake_lock(EL_POWER_TRANSITION_STATE);
+               transition_context.ongoing = 1;
        }
 
-       poweroff_main((void *)(intptr_t) ti->next);
+       transition_description(0);
+
+       broadcast_transition_info();
+       g_list_foreach(proc_list, init_waiting_list, &waiting);
+       if (waiting > 0)
+               transition_context.max_wait_timer = g_timeout_add_seconds(max_wait_timeout, max_wait_expired_cb, NULL);
+       else
+               action_transition();
 }
 
-static void transition_state(const struct trans_info *ti)
+static void action_transition(void)
 {
-       int next;
-       struct trans_info __ti = { 0, };
-
-       if (ti->curr == POWER_STATE_ALL) {
-               /* replace POWER_STATE_ALL to the current state */
-               memcpy(&__ti, ti, sizeof(struct trans_info));
-               __ti.curr = current;
-               ti = &__ti;
+       struct trans_info ti = { 0 , };
+       int retval;
+
+       transition_description(0);
+
+       current = transition_context.ti.next;
+
+       if (action_on_state[POWER_STATE_INDEX(current)])
+               action_on_state[POWER_STATE_INDEX(current)](transition_context.ti.data);
+
+       // transition end
+       transition_context.ongoing = 0;
+       event_wake_unlock(EL_POWER_TRANSITION_STATE);
+
+       // trigger next transition if exist
+       retval = dequeue_transition(&ti);
+       if (retval == 0) {
+               prepare_transition(&ti);
+               trigger_transition();
        }
+}
 
-       _D("Transition power state: %s -> %s, reason=%d",
-               state_name(ti->curr), state_name(ti->next), ti->reason);
-
-       next = ti->next;
-
-       /* transition */
-       ++state_transition_counter;
-       if (current == POWER_STATE_START) {
-               if (next == POWER_STATE_NORMAL)
-                       power_transition_start_to_normal(ti);
-               else if (next == POWER_STATE_SLEEP)
-                       power_transition_start_to_sleep(ti);
-               else if (is_poweroff_state(next))
-                       power_transition_to_poweroff(ti);
-       } else if (current == POWER_STATE_NORMAL) {
-               if (next == POWER_STATE_NORMAL)
-                       power_transition_normal_to_normal(ti);
-               else if (next == POWER_STATE_SLEEP)
-                       power_transition_normal_to_sleep(ti);
-               else if (is_poweroff_state(next))
-                       power_transition_to_poweroff(ti);
-       } else if (current == POWER_STATE_SLEEP) {
-               if (next == POWER_STATE_NORMAL)
-                       power_transition_sleep_to_normal(ti);
-               else if (next == POWER_STATE_SLEEP)
-                       power_transition_sleep_to_sleep(ti);
-               else if (is_poweroff_state(next))
-                       power_transition_to_poweroff(ti);
+static void prepare_transition(const struct trans_info *ti)
+{
+       assert(transition_context.waitings == NULL);
+       assert(transition_context.max_wait_timer == 0);
+
+       if (ti) {
+               transition_context.ti = (struct trans_info) {
+                       .curr = ti->curr,
+                       .next = ti->next,
+                       .reason = ti->reason,
+                       .data = ti->data,
+               };
        }
 
-       if (current != ti->next)
-               _W("Not defined transition. The current still stays at %s", state_name(current));
+       transition_context.ongoing = 0;
+
+       transition_description(1);
 }
 
-static void deferred_transition_state(gpointer data)
+static gint find_ti_by_state(gconstpointer data, gconstpointer udata)
 {
-       transition_state(data);
-       free(data);
+       const struct trans_info *ti = (const struct trans_info *) data;
+       const uint64_t state = *(const uint64_t *) udata;
+
+       if (ti->curr & state)
+               return 0;
+
+       return -1;
 }
 
-static int power_transition_state(void *data)
+static int transition_request_callback(void *data)
 {
-       GList *ti_list, *elem;
-       const struct trans_info *ti = NULL;
+       GList *ti_list, *l;
+       const struct trans_info *t = NULL;
+       struct trans_info ti = { 0 , };
+       uint64_t available;
 
        if (!data)
                return 0;
 
+       available = available_starting_state();
+
        ti_list = (GList *) data;
 
-       // look for trans_info defined on the current state
-       SYS_G_LIST_FOREACH(ti_list, elem, ti) {
-               if (ti->curr & current)
-                       break;
-       }
+       l = g_list_find_custom(ti_list, &available, find_ti_by_state);
+       if (!l)
+               return 0;
 
-       if (!ti)
+       if (!l->data)
                return 0;
 
+       t = l->data;
+
        // check invalid next state
-       if (__builtin_popcountll(ti->next & POWER_STATE_ALL) != 1) {
-               _E("Invalid next state, curr=%"PRIx64", next=%"PRIx64", reason=%d", ti->curr, ti->next, ti->reason);
+       if (__builtin_popcountll(t->next & POWER_STATE_ALL) != 1) {
+               _E("Invalid next state, curr=%"PRIx64", next=%"PRIx64", reason=%d", t->curr, t->next, t->reason);
                return 0;
        }
 
-       // defer state transition until delayed_init_done unless it is transition to poweroff.
-       // poweroff shall be handled immediately regardless of the delayed_init_done.
-       if (!delayed_init_done && !is_poweroff_state(ti->next)) {
-               struct trans_info *deferred_ti = calloc(1, sizeof(struct trans_info));
-               if (!deferred_ti) {
-                       CRITICAL_LOG_E("Failed to defer transition.");
-                       return 0;
-               }
-
-               // Pseudo state transition.
-               current = ti->next;
+       ti = (struct trans_info) {
+               .curr = available,
+               .next = t->next,
+               .reason = t->reason,
+               .data = t->data,
+       };
 
-               // Reserve the trans_info.
-               // Those are used on receiving delayed_init_done for real transitioning state.
-               memcpy(deferred_ti, ti, sizeof(struct trans_info));
-               deferred_transition_list = g_list_append(deferred_transition_list, deferred_ti);
-               _D("Defer state transition %s->%s until delayed init done.", state_name(ti->curr), state_name(ti->next));
+       // TODO: immediate handling of poweroff?
 
+       if (!delayed_init_done || transition_context.ongoing) {
+               enqueue_transition(&ti);
                return 0;
        }
 
-       transition_state(ti);
+       prepare_transition(&ti);
+       trigger_transition();
 
        return 0;
 }
 
-static int delayed_init_cb(void *data)
+static int delayed_init_callback(void *data)
 {
+       int retval;
+       struct trans_info ti = { 0 , };
+
        delayed_init_done = 1;
 
        _D("Start deferred state transition.");
 
-        /* rewind current state to initial state and do the deferred transition */
-       current = POWER_STATE_START;
-       g_list_free_full(g_steal_pointer(&deferred_transition_list), deferred_transition_state);
+       retval = dequeue_transition(&ti);
+       if (retval == 0) {
+               prepare_transition(&ti);
+               trigger_transition();
+       }
 
-       /* Enable autosleep at this point.
-        * This prevents system go suspend(autosleep) before booting done */
-       _D("Finished deferred state transition. Enable autosleep.");
        power_enable_autosleep();
 
        return 0;
 }
 
+static int load_max_wait_timeout(struct parse_result *result, void *user_data)
+{
+       if (MATCH(result->section, "PowerState")
+               && MATCH(result->name, "ChangeStateMaxWaitSecond"))
+               max_wait_timeout = atoi(result->value);
+
+       return 0;
+}
+
 void power_state_init(void *data)
 {
-       register_notifier(DEVICE_NOTIFIER_DELAYED_INIT, delayed_init_cb);
-       register_notifier(DEVICE_NOTIFIER_REQUEST_TRANSITION_STATE, power_transition_state);
+       transition_queue = g_queue_new();
+
+       config_parse(POWER_CONF_FILE, load_max_wait_timeout, NULL);
+
+       register_notifier(DEVICE_NOTIFIER_DELAYED_INIT, delayed_init_callback);
+       register_notifier(DEVICE_NOTIFIER_REQUEST_TRANSITION_STATE, transition_request_callback);
 
        power_dbus_init();
        power_off_init();
        power_suspend_init();
        power_event_lock_init();
-       power_state_wait_init();
 
        /* Take the first transition.
         *
index 7ce29b2..3f469f8 100644 (file)
@@ -99,5 +99,8 @@ static inline void power_request_change_state(uint64_t next, int reason)
 }
 
 uint64_t power_get_state(void);
+int add_change_state_wait(pid_t pid, guint64 state);
+void remove_change_state_wait(pid_t pid, guint64 state);
+int confirm_change_state_wait(pid_t pid, guint64 id);
 
 #endif //__POWER_STATE_MANAGER_H__