power: refactor power operations
[platform/core/system/deviced.git] / plugins / iot-headless / power / power-dbus.c
1 /*
2  * deviced
3  *
4  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
5  *
6  * Licensed under the Apache License, Version 2.0 (the License);
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdint.h>
22 #include <glib.h>
23 #include <time.h>
24 #include <libsyscommon/libgdbus.h>
25 #include <libsyscommon/list.h>
26 #include <libsyscommon/file.h>
27 #include <device/power-internal.h>
28
29 #include "shared/devices.h"
30 #include "shared/log.h"
31 #include "shared/device-notifier.h"
32
33 #include "power/power-control.h"
34 #include "power-state-manager.h"
35 #include "power-state-wait.h"
36
37 #ifndef PROCESS_CHECK_TIMEOUT
38 #define PROCESS_CHECK_TIMEOUT    600000 /* milisecond, 10 minute */
39 #endif
40
41 static GList *lock_list;
42
43 struct lock_node {
44         pid_t pid;
45         char comm[64];
46         int timer_id;
47         int timeout;
48         char locktime[64];
49 };
50
51 static void print_lock_node(void)
52 {
53         GList *elem;
54         struct lock_node *node;
55
56         _D("List of remaining CPU locks");
57         SYS_G_LIST_FOREACH(lock_list, elem, node)
58                 _D(" pid=%d(%s), locktime=%s, timeout=%dms",
59                         node->pid, node->comm, node->locktime, node->timeout);
60 }
61
62 static void remove_lock_node(struct lock_node *node)
63 {
64         if (!node)
65                 return;
66
67         if (node->timer_id) {
68                 g_source_remove(node->timer_id);
69                 node->timer_id = 0;
70         }
71
72         SYS_G_LIST_REMOVE(lock_list, node);
73         free(node);
74
75         /* Check the lock_list is empty after removing the node.
76          * If it is, then request wake unlock */
77         if (SYS_G_LIST_LENGTH(lock_list) == 0)
78                 power_release_wakelock();
79         else
80                 print_lock_node();
81 }
82
83 static gboolean lock_expired_cb(void *data)
84 {
85         struct lock_node *this = (struct lock_node *) data;
86
87         _D("Powerlock of pid=%d(%s) for %dms is expired", this->pid, this->comm, this->timeout);
88         remove_lock_node(this);
89
90         return G_SOURCE_REMOVE;
91 }
92
93 /* check existance of process for every PROCESS_CHECK_TIMEOUT
94  * if the process has requested infinite CPU lock */
95 static gboolean process_check_cb(void *data)
96 {
97         struct lock_node *this = (struct lock_node *) data;
98
99         if (kill(this->pid, 0) != 0) {
100                 _D("pid=%d(%s) is not found, deviced release the CPU lock", this->pid, this->comm);
101                 remove_lock_node(this);
102                 return G_SOURCE_REMOVE;
103         }
104
105         return G_SOURCE_CONTINUE;
106 }
107
108 static GVariant *dbus_power_lock_cpu(GDBusConnection *conn,
109         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
110         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
111 {
112         int timeout;
113         int ret = 0;
114         pid_t pid;
115         GList *elem;
116         struct lock_node *node;
117
118         time_t current;
119         struct tm current_tm;
120         struct tm *rettm;
121
122         /* lock timeout in milisecond */
123         g_variant_get(param, "(i)", &timeout);
124         if (timeout < 0) {
125                 ret = -EINVAL;
126                 goto out;
127         }
128
129         pid = gdbus_connection_get_sender_pid(conn, sender);
130         if (pid == -1 || kill(pid, 0) == -1) {
131                 _E("%d process does not exist, dbus ignored.", pid);
132                 ret = -ESRCH;
133                 goto out;
134         }
135
136         SYS_G_LIST_FOREACH(lock_list, elem, node) {
137                 if (node->pid == pid)
138                         break;
139         }
140
141         if (!node) {
142                 int retval;
143                 char commpath[128];
144                 char *sep;
145
146                 node = calloc(1, sizeof(struct lock_node));
147                 if (!node) {
148                         ret = -ENOMEM;
149                         goto out;
150                 }
151
152                 node->pid = pid;
153                 snprintf(commpath, sizeof(commpath), "/proc/%d/comm", pid);
154                 retval = sys_get_str(commpath, node->comm, sizeof(node->comm));
155                 if (retval != 0)
156                         snprintf(node->comm, sizeof(node->comm), "Unknown");
157
158                 /* remove whitesapce */
159                 sep = strpbrk(node->comm, " \n\t\r");
160                 if (sep)
161                         *sep = '\0';
162
163                 SYS_G_LIST_APPEND(lock_list, node);
164         }
165
166         /* set current time for logging */
167         current = time(NULL);
168         rettm = localtime_r(&current, &current_tm);
169         if (!rettm)
170                 snprintf(node->locktime, sizeof(node->locktime), "Unknown");
171         else
172                 strftime(node->locktime, sizeof(node->locktime), "%Y-%m-%d %H:%M:%S", &current_tm);
173
174         node->timeout = timeout;
175
176         if (node->timer_id) {
177                 g_source_remove(node->timer_id);
178                 node->timer_id = 0;
179         }
180
181         _D("pid=%d(%s) request CPU lock for %dms", pid, node->comm, timeout);
182         power_acquire_wakelock();
183
184         if (timeout > 0)
185                 node->timer_id = g_timeout_add(timeout, lock_expired_cb, node);
186         else if (timeout == 0) /* endless CPU lock: add checker that monitors the process*/
187                 node->timer_id = g_timeout_add(PROCESS_CHECK_TIMEOUT, process_check_cb, node);
188
189 out:
190         return g_variant_new("(i)", ret);
191 }
192
193 static GVariant *dbus_power_unlock_cpu(GDBusConnection *conn,
194         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
195         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
196 {
197         int ret = 0;
198         pid_t pid;
199         GList *elem;
200         struct lock_node *node;
201
202         pid = gdbus_connection_get_sender_pid(conn, sender);
203         if (pid == -1 || kill(pid, 0) == -1) {
204                 _E("%d process does not exist, dbus ignored.", pid);
205                 ret = -ESRCH;
206                 goto out;
207         }
208
209         SYS_G_LIST_FOREACH(lock_list, elem, node) {
210                 if (node->pid == pid)
211                         break;
212         }
213
214         if (node) {
215                 _D("pid=%d(%s) release CPU lock", node->pid, node->comm);
216                 remove_lock_node(node);
217         }
218
219 out:
220         return g_variant_new("(i)", ret);
221 }
222
223 static GVariant *dbus_power_add_change_state_wait(GDBusConnection *conn,
224         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
225         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
226 {
227         int ret = 0;
228         pid_t pid;
229         guint64 state;
230
231         pid = gdbus_connection_get_sender_pid(conn, sender);
232         if (pid == -1 || kill(pid, 0) == -1) {
233                 _E("%d process does not exist, dbus ignored.", pid);
234                 ret = -ESRCH;
235                 goto out;
236         }
237
238         g_variant_get(param, "(t)", &state);
239
240         ret = add_change_state_wait(pid, state);
241
242 out:
243         return g_variant_new("(i)", ret);
244 }
245
246 static GVariant *dbus_power_remove_change_state_wait(GDBusConnection *conn,
247         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
248         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
249 {
250         pid_t pid;
251         guint64 state;
252
253         pid = gdbus_connection_get_sender_pid(conn, sender);
254         if (pid == -1 || kill(pid, 0) == -1) {
255                 _E("%d process does not exist, dbus ignored.", pid);
256                 goto out;
257         }
258
259         g_variant_get(param, "(t)", &state);
260
261         remove_change_state_wait(pid, state);
262
263 out:
264         return gdbus_new_g_variant_tuple();
265 }
266
267 static GVariant *dbus_power_confirm_change_state_wait(GDBusConnection *conn,
268         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
269         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
270 {
271         int ret = 0;
272         pid_t pid;
273         guint64 sleep_id;
274
275         pid = gdbus_connection_get_sender_pid(conn, sender);
276         if (pid == -1 || kill(pid, 0) == -1) {
277                 _E("%d process does not exist, dbus ignored.", pid);
278                 ret = -ESRCH;
279                 goto out;
280         }
281
282         g_variant_get(param, "(t)", &sleep_id);
283
284         ret = confirm_change_state_wait(pid, sleep_id);
285
286 out:
287         return g_variant_new("(i)", ret);
288 }
289
290 static const dbus_method_s dbus_methods[] = {
291         { "LockCpu",                "i",   "i",  dbus_power_lock_cpu },
292         { "UnlockCpu",              NULL,  "i",  dbus_power_unlock_cpu },
293         { "AddChangeStateWait",     "t",   "i",  dbus_power_add_change_state_wait },
294         { "RemoveChangeStateWait",  "t",   NULL, dbus_power_remove_change_state_wait },
295         { "ConfirmChangeStateWait", "t",   "i",  dbus_power_confirm_change_state_wait },
296         /* Add methods here */
297 };
298
299 static const dbus_interface_u dbus_interface = {
300         .oh = NULL,
301         .name = DEVICED_INTERFACE_POWER,
302         .methods = dbus_methods,
303         .nr_methods = ARRAY_SIZE(dbus_methods),
304 };
305
306 void power_plugin_dbus_init(void *data)
307 {
308         int retval;
309
310         retval = gdbus_add_object(NULL, DEVICED_PATH_POWER, &dbus_interface);
311         if (retval < 0)
312                 _E("Failed to init dbus method.");
313 }