e3e8aac35a8ef0e29de5c676c9cd0ae31857882e
[platform/core/system/deviced.git] / src / extcon / extcon.c
1 /*
2  * deviced
3  *
4  * Copyright (c) 2015 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
20 #include <stdio.h>
21 #include <hal/device/hal-external_connection.h>
22 #include <libsyscommon/libgdbus.h>
23 #include <libsyscommon/ini-parser.h>
24
25 #include "core/log.h"
26 #include "shared/common.h"
27 #include "shared/devices.h"
28 #include "shared/device-notifier.h"
29 #include "core/udev.h"
30 #include "extcon.h"
31
32 #define EXTCON_PATH "/sys/class/extcon"
33 #define STATE_NAME  "STATE"
34 #define DEVICE_TYPE  "DEVTYPE"
35
36 #define METHOD_SYSPOPUP_SHOW            "show"
37
38 #define BUF_MAX 512
39
40 static GList *extcon_list;
41 static bool extcon_dev_available = false;
42
43 static void extcon_deferred_init(void);
44 static int delayed_init_done(void *data);
45
46 void add_extcon(struct extcon_ops *dev)
47 {
48         SYS_G_LIST_APPEND(extcon_list, dev);
49 }
50
51 void remove_extcon(struct extcon_ops *dev)
52 {
53         SYS_G_LIST_REMOVE(extcon_list, dev);
54 }
55
56 static struct extcon_ops *find_extcon(const char *name)
57 {
58         GList *l;
59         struct extcon_ops *dev;
60
61         if (!name)
62                 return NULL;
63
64         SYS_G_LIST_FOREACH(extcon_list, l, dev) {
65                 if (!strcasecmp(dev->name, name))
66                         return dev;
67         }
68
69         return NULL;
70 }
71
72 int extcon_get_status(const char *name)
73 {
74         struct extcon_ops *dev;
75
76         if (!name)
77                 return -EINVAL;
78
79         dev = find_extcon(name);
80         if (!dev)
81                 return -ENOENT;
82
83         if (!dev->initialized)
84                 return -ENODEV;
85
86         return dev->status;
87 }
88
89 static int extcon_update(const char *name, const char *index, const char *value)
90 {
91         struct extcon_ops *dev;
92         char buf[BUF_MAX];
93         int status;
94         int ret_dbus = 0;
95         int reply;
96
97         if (!name || !value)
98                 return -EINVAL;
99
100         dev = find_extcon(name);
101         if (!dev)
102                 return -EINVAL;
103
104         status = atoi(value);
105
106         /* Do not invoke update func. if it's the same value */
107         if (dev->status == status && !index)
108                 return 0;
109
110         _I("Changed %s device: (%d) to (%d).", name, dev->status, status);
111
112         dev->status = status;
113
114         if (dev->initialized == false && strncmp(name, "USB", strlen("USB")) == 0) {
115                 if (status > 0) {
116                         snprintf(buf, BUF_MAX, "usb-client");
117                         ret_dbus = gdbus_call_sync_with_reply_int(DEVICEMANAGER_BUS_NAME,
118                                                         DEVICEMANAGER_PATH_POPUP,
119                                                         DEVICEMANAGER_INTERFACE_POPUP,
120                                                         METHOD_SYSPOPUP_SHOW,
121                                                         g_variant_new("(s)", buf),
122                                                         &reply);
123                         if (ret_dbus < 0){
124                                 _E("Failed to launch USB restricted popup: %d", ret_dbus);
125                                 return ret_dbus;
126                         }
127                         return reply;
128                 } else
129                         return 0;
130         }
131
132         if (dev->update)
133                 dev->update(index, status);
134
135         return 0;
136 }
137
138 int extcon_enable_device(const char *name)
139 {
140         struct extcon_ops *dev;
141         int ret;
142
143         if (!delayed_init_done(NULL))
144                 extcon_deferred_init();
145
146         dev = find_extcon(name);
147         if (!dev)
148                 return -ENODEV;
149
150         if (dev->initialized) {
151                 _I("Extcon(%s) already initialized.", name);
152                 return 0;
153         }
154
155         dev->init(NULL);
156         dev->initialized = true;
157
158         if (strncmp(name, "USB", strlen("USB")) == 0) {
159                 ret = extcon_update("USB", NULL, "0");
160                 if (ret != 0)
161                         _E("Failed to disconnect USB.");
162
163                 ret = extcon_update("USB", NULL, "1");
164                 if (ret != 0)
165                         _E("Failed to connect USB.");
166         }
167         _I("Extcon(%s) initialized.", name);
168
169         return 0;
170 }
171
172 int extcon_disable_device(const char *name)
173 {
174         struct extcon_ops *dev;
175         char buf[BUF_MAX];
176         int ret_dbus;
177
178         dev = find_extcon(name);
179         if (!dev)
180                 return -ENODEV;
181
182         snprintf(buf, BUF_MAX, "usb-client");
183         ret_dbus = gdbus_call_sync_with_reply_int(DEVICEMANAGER_BUS_NAME,
184                                         DEVICEMANAGER_PATH_POPUP,
185                                         DEVICEMANAGER_INTERFACE_POPUP,
186                                         METHOD_SYSPOPUP_SHOW,
187                                         g_variant_new("(s)", buf), NULL);
188         if (ret_dbus < 0)
189                 _E("Failed to launch USB restricted popup: %d", ret_dbus);
190
191         if (!dev->initialized) {
192                 _I("Extcon(%s) already deinitialized.", name);
193                 return 0;
194         }
195
196         dev->exit(NULL);
197         dev->initialized = false;
198
199         _I("Extcon(%s) deinitialized.", name);
200
201         return 0;
202 }
203
204
205 static int extcon_parsing_value(const char *index, const char *value)
206 {
207         char *s, *p;
208         char name[NAME_MAX];
209
210         if (!value || !index)
211                 return -EINVAL;
212
213         s = (char*)value;
214         while (s && *s != '\0') {
215                 p = strchr(s, '=');
216                 if (!p)
217                         break;
218                 memset(name, 0, sizeof(name));
219                 memcpy(name, s, p-s);
220                 /* name is env_name and p+1 is env_value */
221                 extcon_update(name, index, p+1);
222                 s = strchr(p, '\n');
223                 if (!s)
224                         break;
225                 s += 1;
226         }
227
228         return 0;
229 }
230
231 static void uevent_extcon_handler(struct udev_device *dev)
232 {
233         const char *env_value;
234         const char *dev_index;
235         int ret_val;
236
237         env_value = udev_device_get_property_value(dev, STATE_NAME);
238         dev_index = udev_device_get_property_value(dev, DEVICE_TYPE);
239
240         ret_val = extcon_parsing_value(dev_index, env_value);
241         if (ret_val < 0)
242                 _E("Failed to parse extcon value: %d", ret_val);
243 }
244
245 static int extcon_load_uevent(struct parse_result *result, void *user_data)
246 {
247         char *index = user_data;
248
249         if (!result)
250                 return 0;
251
252         if (!result->name || !result->value)
253                 return 0;
254
255         extcon_update(result->name, index, result->value);
256
257         return 0;
258 }
259
260 static int get_extcon_init_state(void)
261 {
262         DIR *dir;
263         struct dirent *result;
264         char node[BUF_MAX];
265         int ret;
266
267         dir = opendir(EXTCON_PATH);
268         if (!dir) {
269                 ret = -errno;
270                 _E("Cannot open dir(%s): %d", EXTCON_PATH, ret);
271                 return ret;
272         }
273
274         ret = -ENOENT;
275         while ((result = readdir(dir))) {
276                 if (result->d_name[0] == '.')
277                         continue;
278                 snprintf(node, sizeof(node), "%s/%s/state",
279                                 EXTCON_PATH, result->d_name);
280                 _I("Checking node(%s).", node);
281                 if (access(node, F_OK) != 0)
282                         continue;
283
284                 ret = config_parse(node, extcon_load_uevent, result->d_name);
285                 if (ret < 0)
286                         _E("Failed to parse %s data: %d", node, ret);
287         }
288
289         if (dir)
290                 closedir(dir);
291
292         return 0;
293 }
294
295 static GVariant * dbus_get_extcon_status(GDBusConnection *conn,
296         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
297         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
298 {
299         struct extcon_ops *dev;
300         char *str;
301         int ret;
302
303         if (!delayed_init_done(NULL))
304                 extcon_deferred_init();
305
306         g_variant_get(param, "(s)", &str);
307
308         dev = find_extcon(str);
309         if (!dev) {
310                 _E("Failed to matched extcon(%s) device.", str);
311                 ret = -ENOENT;
312                 goto error;
313         }
314
315         ret = dev->status;
316         _D("Extcon(%s) device status=%d", dev->name, dev->status);
317
318 error:
319         g_free(str);
320         return g_variant_new("(i)", ret);
321 }
322
323 static GVariant *dbus_enable_device(GDBusConnection *conn,
324         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
325         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
326 {
327         char *device;
328         int ret;
329
330         if (!delayed_init_done(NULL))
331                 extcon_deferred_init();
332
333         g_variant_get(param, "(s)", &device);
334
335         ret = extcon_update(device, NULL, "1");
336
337         g_free(device);
338         return g_variant_new("(i)", ret);
339 }
340
341 static GVariant *dbus_disable_device(GDBusConnection *conn,
342         const gchar *sender, const gchar *path, const gchar *iface, const gchar *name,
343         GVariant *param, GDBusMethodInvocation *invocation, gpointer user_data)
344 {
345         char *device;
346         int ret;
347
348         if (!delayed_init_done(NULL))
349                 extcon_deferred_init();
350
351         g_variant_get(param, "(s)", &device);
352
353         ret = extcon_update(device, NULL, "0");
354
355         g_free(device);
356         return g_variant_new("(i)", ret);
357 }
358
359 static struct uevent_handler uh = {
360         .subsystem = EXTCON_SUBSYSTEM,
361         .uevent_func = uevent_extcon_handler,
362 };
363
364 static const dbus_method_s dbus_methods[] = {
365         { "GetStatus", "s",  "i", dbus_get_extcon_status },
366         { "enable",    "s", "i", dbus_enable_device },  /* for devicectl */
367         { "disable",   "s", "i", dbus_disable_device }, /* for devicectl */
368         /* Add methods here */
369 };
370
371 static const dbus_interface_u dbus_interface = {
372         .oh = NULL,
373         .name = DEVICED_INTERFACE_EXTCON,
374         .methods = dbus_methods,
375         .nr_methods = ARRAY_SIZE(dbus_methods),
376 };
377
378
379 /* used by HAL */
380 static void extcon_changed(struct connection_info *info, void *data)
381 {
382         if (!info)
383                 return;
384
385         if (!info->name || !info->state)
386                 return;
387
388         /* call to update */
389         extcon_update(info->name, NULL, info->state);
390 }
391
392 static int extcon_probe(void *data)
393 {
394         int ret_val;
395
396         if (extcon_dev_available)
397                 return 0;
398
399         ret_val = hal_device_external_connection_get_backend();
400         if (ret_val == 0) {
401                 extcon_dev_available = true;
402                 _I("Extcon device structure load success.");
403
404                 return 0;
405         }
406
407         _E("There is no HAL for extcon.");
408         extcon_dev_available = false;
409
410         /**
411          * find extcon class.
412          * if there is no extcon class,
413          * deviced does not control extcon devices.
414          */
415         if (access(EXTCON_PATH, R_OK) != 0) {
416                 _E("There is no extcon class.");
417                 return ret_val;
418         }
419
420         return 0;
421 }
422
423 static void add_extcon_event_handler(void)
424 {
425         int ret_val;
426
427         if (extcon_dev_available) { /* HAL is used */
428                 hal_device_external_connection_register_changed_event(extcon_changed, NULL);
429                 hal_device_external_connection_get_current_state(extcon_changed, NULL);
430         } else {
431                 /* register extcon uevent */
432                 ret_val = register_kernel_uevent_control(&uh);
433                 if (ret_val < 0)
434                         _E("Failed to register extcon uevent: %d", ret_val);
435
436                 /* load the initialize value by accessing the node directly */
437                 ret_val = get_extcon_init_state();
438                 if (ret_val < 0)
439                         _E("Failed to init extcon nodes: %d", ret_val);
440         }
441 }
442
443 static void remove_extcon_event_handler(void)
444 {
445         int ret_val;
446
447         if (extcon_dev_available)
448                 hal_device_external_connection_unregister_changed_event(extcon_changed);
449         else {
450                 /* unreigster extcon uevent */
451                 ret_val = unregister_kernel_uevent_control(&uh);
452                 if (ret_val < 0)
453                         _E("Failed to unregister extcon uevent: %d", ret_val);
454         }
455 }
456
457 static int event_handler_state_changed(void *data)
458 {
459         static device_notifier_state_e old = DEVICE_NOTIFIER_STATE_STOP;
460         device_notifier_state_e state = *(device_notifier_state_e *)data;
461
462         if (old == state)
463                 return 0;
464
465         old = state;
466
467         if (state == DEVICE_NOTIFIER_STATE_START)
468                 add_extcon_event_handler();
469         else if (state == DEVICE_NOTIFIER_STATE_STOP)
470                 remove_extcon_event_handler();
471
472         return 0;
473 }
474
475 /* defer extcon init until booting done as it takes long time.
476  * if dbus request is arrived before the booting done,
477  * initialize extcon on arriving that request even though
478  * system booting has not been finished */
479 static void extcon_deferred_init(void)
480 {
481         GList *l;
482         struct extcon_ops *dev;
483         device_notifier_state_e state = DEVICE_NOTIFIER_STATE_START;
484         static int initialized = false;
485
486         if (initialized)
487                 return;
488
489         /* initialize extcon devices */
490         SYS_G_LIST_FOREACH(extcon_list, l, dev) {
491                 if (dev->initialized) {
492                         _I("Extcon(%s) is already initialized.", dev->name);
493                         continue;
494                 } else
495                         _I("Extcon(%s) init.", dev->name);
496
497                 if (dev->init)
498                         dev->init(NULL);
499                 dev->initialized = true;
500         }
501
502         event_handler_state_changed((void *)&state);
503         syscommon_notifier_subscribe_notify(DEVICE_NOTIFIER_EVENT_HANDLER, event_handler_state_changed);
504
505         initialized = true;
506 }
507
508 static int delayed_init_done(void *data)
509 {
510         static int done;
511
512         if (!data)
513                 return done;
514         done = *(int *) data;
515         if (!done)
516                 return done;
517
518         if (!extcon_list)
519                 return done;
520
521         extcon_deferred_init();
522
523         return done;
524 }
525
526 static void extcon_init(void *data)
527 {
528         int retval;
529
530         retval = gdbus_add_object(NULL, DEVICED_PATH_EXTCON, &dbus_interface);
531         if (retval < 0)
532                 _E("Failed to init dbus method: %d", retval);
533
534         syscommon_notifier_subscribe_notify(DEVICE_NOTIFIER_DELAYED_INIT, delayed_init_done);
535 }
536
537 static void extcon_exit(void *data)
538 {
539         GList *l;
540         struct extcon_ops *dev;
541         device_notifier_state_e state = DEVICE_NOTIFIER_STATE_STOP;
542
543         syscommon_notifier_unsubscribe_notify(DEVICE_NOTIFIER_EVENT_HANDLER, event_handler_state_changed);
544
545         event_handler_state_changed((void *)&state);
546
547         /* deinitialize extcon devices */
548         SYS_G_LIST_FOREACH(extcon_list, l, dev) {
549                 if (!dev->initialized) {
550                         _I("Extcon(%s) is already deinitialized.", dev->name);
551                         continue;
552                 } else
553                         _I("Extcon(%s) deinit.", dev->name);
554
555                 if (dev->exit)
556                         dev->exit(data);
557                 dev->initialized = false;
558         }
559         extcon_dev_available = false;
560 }
561
562 static const struct device_ops extcon_device_ops = {
563         DECLARE_NAME_LEN("extcon"),
564         .probe  = extcon_probe,
565         .init   = extcon_init,
566         .exit   = extcon_exit,
567 };
568
569 DEVICE_OPS_REGISTER(&extcon_device_ops)