0a0ac50818e69b689f24cae4d15ea303d51c7020
[platform/core/system/deviced.git] / plugins / iot-headless / power / power-dbus.c
1 /*
2  * deviced
3  *
4  * Copyright (c) 2021 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-state-manager.h"
34 #include "sleep-wait.h"
35
36 #ifndef PROCESS_CHECK_TIMEOUT
37 #define PROCESS_CHECK_TIMEOUT    600000 /* milisecond, 10 minute */
38 #endif
39
40 static GList *lock_list;
41
42 struct lock_node {
43         pid_t pid;
44         char comm[64];
45         int timer_id;
46         int timeout;
47         char locktime[64];
48 };
49
50 static void print_lock_node(void)
51 {
52         GList *elem;
53         struct lock_node *node;
54
55         _D("List of remaining CPU locks");
56         SYS_G_LIST_FOREACH(lock_list, elem, node)
57                 _D(" pid=%d(%s), locktime=%s, timeout=%dms",
58                         node->pid, node->comm, node->locktime, node->timeout);
59 }
60
61 /* check the lock_list is empty.
62  * if it is, then request wake unlock */
63 static void try_wake_unlock(void)
64 {
65         if (SYS_G_LIST_LENGTH(lock_list) == 0)
66                 device_notify(DEVICE_NOTIFIER_REQUEST_WAKE_UNLOCK, NULL);
67         else
68                 print_lock_node();
69 }
70
71 static void try_wake_lock(void)
72 {
73         device_notify(DEVICE_NOTIFIER_REQUEST_WAKE_LOCK, NULL);
74 }
75
76 /* remove given node from lock list.
77  * after removing the node, request wake unlock if possible */
78 static void remove_lock_node(struct lock_node *node)
79 {
80         if (!node)
81                 return;
82
83         if (node->timer_id) {
84                 g_source_remove(node->timer_id);
85                 node->timer_id = 0;
86         }
87
88         SYS_G_LIST_REMOVE(lock_list, node);
89         free(node);
90
91         try_wake_unlock();
92 }
93
94 static gboolean lock_expired_cb(void *data)
95 {
96         struct lock_node *this = (struct lock_node *) data;
97
98         _D("Powerlock of pid=%d(%s) for %dms is expired", this->pid, this->comm, this->timeout);
99         remove_lock_node(this);
100
101         return G_SOURCE_REMOVE;
102 }
103
104 /* check existance of process for every PROCESS_CHECK_TIMEOUT
105  * if the process has requested infinite CPU lock */
106 static gboolean process_check_cb(void *data)
107 {
108         struct lock_node *this = (struct lock_node *) data;
109
110         if (kill(this->pid, 0) != 0) {
111                 _D("pid=%d(%s) is not found, deviced release the CPU lock", this->pid, this->comm);
112                 remove_lock_node(this);
113                 return G_SOURCE_REMOVE;
114         }
115
116         return G_SOURCE_CONTINUE;
117 }
118
119 static GVariant *dbus_power_lock_cpu(GDBusConnection *conn,
120         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
121         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
122 {
123         int timeout;
124         int ret = 0;
125         pid_t pid;
126         GList *elem;
127         struct lock_node *node;
128
129         time_t current;
130         struct tm current_tm;
131         struct tm *rettm;
132
133         /* lock timeout in milisecond */
134         g_variant_get(param, "(i)", &timeout);
135         if (timeout < 0) {
136                 ret = -EINVAL;
137                 goto out;
138         }
139
140         pid = gdbus_connection_get_sender_pid(conn, sender);
141         if (pid == -1 || kill(pid, 0) == -1) {
142                 _E("%d process does not exist, dbus ignored.", pid);
143                 ret = -ESRCH;
144                 goto out;
145         }
146
147         SYS_G_LIST_FOREACH(lock_list, elem, node) {
148                 if (node->pid == pid)
149                         break;
150         }
151
152         if (!node) {
153                 int retval;
154                 char commpath[128];
155                 char *sep;
156
157                 node = calloc(1, sizeof(struct lock_node));
158                 if (!node) {
159                         ret = -ENOMEM;
160                         goto out;
161                 }
162
163                 node->pid = pid;
164                 snprintf(commpath, sizeof(commpath), "/proc/%d/comm", pid);
165                 retval = sys_get_str(commpath, node->comm, sizeof(node->comm));
166                 if (retval != 0)
167                         snprintf(node->comm, sizeof(node->comm), "Unknown");
168
169                 /* remove whitesapce */
170                 sep = strpbrk(node->comm, " \n\t\r");
171                 if (sep)
172                         *sep = '\0';
173
174                 SYS_G_LIST_APPEND(lock_list, node);
175         }
176
177         /* set current time for logging */
178         current = time(NULL);
179         rettm = localtime_r(&current, &current_tm);
180         if (!rettm)
181                 snprintf(node->locktime, sizeof(node->locktime), "Unknown");
182         else
183                 strftime(node->locktime, sizeof(node->locktime), "%Y-%m-%d %H:%M:%S", &current_tm);
184
185         node->timeout = timeout;
186
187         if (node->timer_id) {
188                 g_source_remove(node->timer_id);
189                 node->timer_id = 0;
190         }
191
192         _D("pid=%d(%s) request CPU lock for %dms", pid, node->comm, timeout);
193         try_wake_lock();
194
195         if (timeout > 0)
196                 node->timer_id = g_timeout_add(timeout, lock_expired_cb, node);
197         else if (timeout == 0) /* endless CPU lock: add checker that monitors the process*/
198                 node->timer_id = g_timeout_add(PROCESS_CHECK_TIMEOUT, process_check_cb, node);
199
200 out:
201         return g_variant_new("(i)", ret);
202 }
203
204 static GVariant *dbus_power_unlock_cpu(GDBusConnection *conn,
205         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
206         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
207 {
208         int ret = 0;
209         pid_t pid;
210         GList *elem;
211         struct lock_node *node;
212
213         pid = gdbus_connection_get_sender_pid(conn, sender);
214         if (pid == -1 || kill(pid, 0) == -1) {
215                 _E("%d process does not exist, dbus ignored.", pid);
216                 ret = -ESRCH;
217                 goto out;
218         }
219
220         SYS_G_LIST_FOREACH(lock_list, elem, node) {
221                 if (node->pid == pid)
222                         break;
223         }
224
225         if (node) {
226                 _D("pid=%d(%s) release CPU lock", node->pid, node->comm);
227                 remove_lock_node(node);
228         }
229
230 out:
231         return g_variant_new("(i)", ret);
232 }
233
234 static GVariant *dbus_power_add_sleep_wait(GDBusConnection *conn,
235         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
236         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
237 {
238         int ret = 0;
239         pid_t pid;
240
241         pid = gdbus_connection_get_sender_pid(conn, sender);
242         if (pid == -1 || kill(pid, 0) == -1) {
243                 _E("%d process does not exist, dbus ignored.", pid);
244                 ret = -ESRCH;
245                 goto out;
246         }
247
248         ret = add_sleep_wait(pid);
249
250 out:
251         return g_variant_new("(i)", ret);
252 }
253
254 static GVariant *dbus_power_remove_sleep_wait(GDBusConnection *conn,
255         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
256         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
257 {
258         pid_t pid;
259
260         pid = gdbus_connection_get_sender_pid(conn, sender);
261         if (pid == -1 || kill(pid, 0) == -1) {
262                 _E("%d process does not exist, dbus ignored.", pid);
263                 goto out;
264         }
265
266         remove_sleep_wait(pid);
267
268 out:
269         return gdbus_new_g_variant_tuple();
270 }
271
272 static GVariant *dbus_power_confirm_sleep_wait(GDBusConnection *conn,
273         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
274         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
275 {
276         int ret = 0;
277         pid_t pid;
278         int sleep_id;
279
280         pid = gdbus_connection_get_sender_pid(conn, sender);
281         if (pid == -1 || kill(pid, 0) == -1) {
282                 _E("%d process does not exist, dbus ignored.", pid);
283                 ret = -ESRCH;
284                 goto out;
285         }
286
287         g_variant_get(param, "(i)", &sleep_id);
288
289         ret = confirm_sleep_wait(pid, sleep_id);
290
291 out:
292         return g_variant_new("(i)", ret);
293 }
294
295 static const dbus_method_s dbus_methods[] = {
296         { "LockCpu",          "i",   "i",  dbus_power_lock_cpu },
297         { "UnlockCpu",        NULL,  "i",  dbus_power_unlock_cpu },
298         { "AddSleepWait",     NULL,  "i",  dbus_power_add_sleep_wait },
299         { "RemoveSleepWait",  NULL,  NULL, dbus_power_remove_sleep_wait },
300         { "ConfirmSleepWait", "i",   "i",  dbus_power_confirm_sleep_wait },
301         /* Add methods here */
302 };
303
304 static const dbus_interface_u dbus_interface = {
305         .oh = NULL,
306         .name = DEVICED_INTERFACE_POWER,
307         .methods = dbus_methods,
308         .nr_methods = ARRAY_SIZE(dbus_methods),
309 };
310
311 void power_plugin_dbus_init(void *data)
312 {
313         int retval;
314
315         retval = gdbus_add_object(NULL, DEVICED_PATH_POWER, &dbus_interface);
316         if (retval < 0)
317                 _E("Failed to init dbus method.");
318 }