4 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 #include <hal/device/hal-external_connection.h>
22 #include <libsyscommon/libgdbus.h>
23 #include <libsyscommon/ini-parser.h>
26 #include "shared/common.h"
27 #include "shared/devices.h"
28 #include "shared/device-notifier.h"
29 #include "core/udev.h"
32 #define EXTCON_PATH "/sys/class/extcon"
33 #define STATE_NAME "STATE"
34 #define DEVICE_TYPE "DEVTYPE"
36 #define METHOD_SYSPOPUP_SHOW "show"
40 static GList *extcon_list;
41 static bool extcon_dev_available = false;
43 static void extcon_deferred_init(void);
44 static int delayed_init_done(void *data);
46 void add_extcon(struct extcon_ops *dev)
48 SYS_G_LIST_APPEND(extcon_list, dev);
51 void remove_extcon(struct extcon_ops *dev)
53 SYS_G_LIST_REMOVE(extcon_list, dev);
56 static struct extcon_ops *find_extcon(const char *name)
59 struct extcon_ops *dev;
64 SYS_G_LIST_FOREACH(extcon_list, l, dev) {
65 if (!strcasecmp(dev->name, name))
72 int extcon_get_status(const char *name)
74 struct extcon_ops *dev;
79 dev = find_extcon(name);
83 if (!dev->initialized)
89 static int extcon_update(const char *name, const char *index, const char *value)
91 struct extcon_ops *dev;
100 dev = find_extcon(name);
104 status = atoi(value);
106 /* Do not invoke update func. if it's the same value */
107 if (dev->status == status && !index)
110 _I("Changed %s device: (%d) to (%d).", name, dev->status, status);
112 dev->status = status;
114 if (dev->initialized == false && strncmp(name, "USB", strlen("USB")) == 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),
124 _E("Failed to launch USB restricted popup: %d", ret_dbus);
133 dev->update(index, status);
138 int extcon_enable_device(const char *name)
140 struct extcon_ops *dev;
143 if (!delayed_init_done(NULL))
144 extcon_deferred_init();
146 dev = find_extcon(name);
150 if (dev->initialized) {
151 _I("Extcon(%s) already initialized.", name);
156 dev->initialized = true;
158 if (strncmp(name, "USB", strlen("USB")) == 0) {
159 ret = extcon_update("USB", NULL, "0");
161 _E("Failed to disconnect USB.");
163 ret = extcon_update("USB", NULL, "1");
165 _E("Failed to connect USB.");
167 _I("Extcon(%s) initialized.", name);
172 int extcon_disable_device(const char *name)
174 struct extcon_ops *dev;
178 dev = find_extcon(name);
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);
189 _E("Failed to launch USB restricted popup: %d", ret_dbus);
191 if (!dev->initialized) {
192 _I("Extcon(%s) already deinitialized.", name);
197 dev->initialized = false;
199 _I("Extcon(%s) deinitialized.", name);
205 static int extcon_parsing_value(const char *index, const char *value)
210 if (!value || !index)
214 while (s && *s != '\0') {
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);
231 static void uevent_extcon_handler(struct udev_device *dev)
233 const char *env_value;
234 const char *dev_index;
237 env_value = udev_device_get_property_value(dev, STATE_NAME);
238 dev_index = udev_device_get_property_value(dev, DEVICE_TYPE);
240 ret_val = extcon_parsing_value(dev_index, env_value);
242 _E("Failed to parse extcon value: %d", ret_val);
245 static int extcon_load_uevent(struct parse_result *result, void *user_data)
247 char *index = user_data;
252 if (!result->name || !result->value)
255 extcon_update(result->name, index, result->value);
260 static int get_extcon_init_state(void)
263 struct dirent *result;
267 dir = opendir(EXTCON_PATH);
270 _E("Cannot open dir(%s): %d", EXTCON_PATH, ret);
275 while ((result = readdir(dir))) {
276 if (result->d_name[0] == '.')
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)
284 ret = config_parse(node, extcon_load_uevent, result->d_name);
286 _E("Failed to parse %s data: %d", node, ret);
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)
299 struct extcon_ops *dev;
303 if (!delayed_init_done(NULL))
304 extcon_deferred_init();
306 g_variant_get(param, "(s)", &str);
308 dev = find_extcon(str);
310 _E("Failed to matched extcon(%s) device.", str);
316 _D("Extcon(%s) device status=%d", dev->name, dev->status);
320 return g_variant_new("(i)", ret);
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)
330 if (!delayed_init_done(NULL))
331 extcon_deferred_init();
333 g_variant_get(param, "(s)", &device);
335 ret = extcon_update(device, NULL, "1");
338 return g_variant_new("(i)", ret);
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)
348 if (!delayed_init_done(NULL))
349 extcon_deferred_init();
351 g_variant_get(param, "(s)", &device);
353 ret = extcon_update(device, NULL, "0");
356 return g_variant_new("(i)", ret);
359 static struct uevent_handler uh = {
360 .subsystem = EXTCON_SUBSYSTEM,
361 .uevent_func = uevent_extcon_handler,
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 */
371 static const dbus_interface_u dbus_interface = {
373 .name = DEVICED_INTERFACE_EXTCON,
374 .methods = dbus_methods,
375 .nr_methods = ARRAY_SIZE(dbus_methods),
380 static void extcon_changed(struct connection_info *info, void *data)
385 if (!info->name || !info->state)
389 extcon_update(info->name, NULL, info->state);
392 static int extcon_probe(void *data)
396 if (extcon_dev_available)
399 ret_val = hal_device_external_connection_get_backend();
401 extcon_dev_available = true;
402 _I("Extcon device structure load success.");
407 _E("There is no HAL for extcon.");
408 extcon_dev_available = false;
412 * if there is no extcon class,
413 * deviced does not control extcon devices.
415 if (access(EXTCON_PATH, R_OK) != 0) {
416 _E("There is no extcon class.");
423 static void add_extcon_event_handler(void)
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);
431 /* register extcon uevent */
432 ret_val = register_kernel_uevent_control(&uh);
434 _E("Failed to register extcon uevent: %d", ret_val);
436 /* load the initialize value by accessing the node directly */
437 ret_val = get_extcon_init_state();
439 _E("Failed to init extcon nodes: %d", ret_val);
443 static void remove_extcon_event_handler(void)
447 if (extcon_dev_available)
448 hal_device_external_connection_unregister_changed_event(extcon_changed);
450 /* unreigster extcon uevent */
451 ret_val = unregister_kernel_uevent_control(&uh);
453 _E("Failed to unregister extcon uevent: %d", ret_val);
457 static int event_handler_state_changed(void *data)
459 static device_notifier_state_e old = DEVICED_NOTIFIER_STATE_STOP;
460 device_notifier_state_e state = *(device_notifier_state_e *)data;
467 if (state == DEVICED_NOTIFIER_STATE_START)
468 add_extcon_event_handler();
469 else if (state == DEVICED_NOTIFIER_STATE_STOP)
470 remove_extcon_event_handler();
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)
482 struct extcon_ops *dev;
483 device_notifier_state_e state = DEVICED_NOTIFIER_STATE_START;
484 static int initialized = false;
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);
495 _I("Extcon(%s) init.", dev->name);
499 dev->initialized = true;
502 event_handler_state_changed((void *)&state);
503 syscommon_notifier_subscribe_notify(DEVICED_NOTIFIER_EVENT_HANDLER, event_handler_state_changed);
508 static int delayed_init_done(void *data)
514 done = *(int *) data;
521 extcon_deferred_init();
526 static void extcon_init(void *data)
530 retval = gdbus_add_object(NULL, DEVICED_PATH_EXTCON, &dbus_interface);
532 _E("Failed to init dbus method: %d", retval);
534 syscommon_notifier_subscribe_notify(DEVICED_NOTIFIER_DELAYED_INIT, delayed_init_done);
537 static void extcon_exit(void *data)
540 struct extcon_ops *dev;
541 device_notifier_state_e state = DEVICED_NOTIFIER_STATE_STOP;
543 syscommon_notifier_unsubscribe_notify(DEVICED_NOTIFIER_EVENT_HANDLER, event_handler_state_changed);
545 event_handler_state_changed((void *)&state);
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);
553 _I("Extcon(%s) deinit.", dev->name);
557 dev->initialized = false;
559 extcon_dev_available = false;
562 static const struct device_ops extcon_device_ops = {
563 DECLARE_NAME_LEN("extcon"),
564 .probe = extcon_probe,
569 DEVICE_OPS_REGISTER(&extcon_device_ops)