df86bb375410fc30680e40c7b1453598c9bef8df
[platform/core/system/deviced.git] / src / usb / usb-state.c
1 /*
2  * deviced
3  *
4  * Copyright (c) 2016 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 <vconf.h>
21 #include <bundle.h>
22 #include <eventsystem.h>
23 #include <hal/device/hal-usb_gadget-interface.h>
24
25 #include "core/log.h"
26 #include "apps/apps.h"
27 #include "extcon/extcon.h"
28
29 #include "usb.h"
30 #include "usb-debug.h"
31
32 static int noti_id = -1;
33
34 static unsigned int usb_current_mode = USB_FUNCTION_NONE;
35 static unsigned int usb_selected_mode = USB_FUNCTION_NONE;
36
37 static extcon_usb_state_e usb_connection = USB_DISCONNECTED;
38
39 /**
40  * Important
41  * You must keep the order for the specific items.
42  *
43  * There may be several mode_v for a mode.
44  * In this case, the representative value should be placed on top.
45  *
46  * In case of SET_USB_SDB_DIAG and SET_USB_DIAG_SDB, the SET_USB_SDB_DIAG should be above.
47  * As another example, SET_USB_RNDIS_SDB should be at the top of them.
48  */
49 static const struct _usb_mode_mapping_table {
50         int          mode_v; /* Integer defined by vconf */
51         unsigned int mode;   /* Bitmap of usb function combination */
52 } usb_mode_mapping_table[] = {
53         /* Hack for performance. In most cases this is the value. */
54         {SET_USB_DEFAULT,         USB_FUNCTION_MTP | USB_FUNCTION_ACM},
55         {SET_USB_SDB,             USB_FUNCTION_MTP | USB_FUNCTION_ACM | USB_FUNCTION_SDB},
56
57         {SET_USB_NONE,            USB_FUNCTION_NONE},
58         {SET_USB_RNDIS,           USB_FUNCTION_RNDIS},
59         {SET_USB_RNDIS_DIAG,      USB_FUNCTION_RNDIS | USB_FUNCTION_DIAG},
60         {SET_USB_DIAG_RMNET,      USB_FUNCTION_DIAG | USB_FUNCTION_RMNET},
61         {SET_USB_ACM_SDB_DM,      USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_DM},
62
63         {SET_USB_SDB_DIAG,        USB_FUNCTION_MTP | USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_DIAG},
64         {SET_USB_DIAG_SDB,        USB_FUNCTION_MTP | USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_DIAG},
65
66         {SET_USB_RNDIS_SDB,       USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_RNDIS},
67         {SET_USB_RNDIS_TETHERING, USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_RNDIS},
68         {SET_USB_RNDIS_SDB_ACM,   USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_RNDIS},
69 };
70
71 unsigned int get_mode_bitmap_from_vconf(int mode_v)
72 {
73         int i;
74         int array_len = sizeof(usb_mode_mapping_table)/sizeof(usb_mode_mapping_table[0]);
75
76         for (i = 0; i < array_len; i++) {
77                 if (usb_mode_mapping_table[i].mode_v == mode_v)
78                         return usb_mode_mapping_table[i].mode;
79         }
80
81         return USB_FUNCTION_INVALID;
82 }
83
84 static int get_mode_vconf_from_bitmap(unsigned int mode)
85 {
86         int i;
87         int array_len = sizeof(usb_mode_mapping_table)/sizeof(usb_mode_mapping_table[0]);
88
89         /* Caution: The order to search the usb_mode_mapping_table must start with a low index. */
90         for (i = 0; i < array_len; i++) {
91                 if (usb_mode_mapping_table[i].mode == mode)
92                         return usb_mode_mapping_table[i].mode_v;
93         }
94
95         return SET_USB_INVALID;
96 }
97
98 static void usb_state_send_system_event(int status)
99 {
100         bundle *b;
101         const char *str;
102
103         switch (status) {
104         case VCONFKEY_SYSMAN_USB_DISCONNECTED:
105                 str = EVT_VAL_USB_DISCONNECTED;
106                 break;
107
108         case VCONFKEY_SYSMAN_USB_CONNECTED:
109                 str = EVT_VAL_USB_CONNECTED;
110                 break;
111
112         case VCONFKEY_SYSMAN_USB_AVAILABLE:
113                 str = EVT_VAL_USB_AVAILABLE;
114                 break;
115
116         default:
117                 return;
118         }
119
120         _I("USB system_event (%s)", str);
121
122         b = bundle_create();
123         bundle_add_str(b, EVT_KEY_USB_STATUS, str);
124         eventsystem_send_system_event(SYS_EVENT_USB_STATUS, b);
125         bundle_free(b);
126 }
127
128 void usb_state_retrieve_selected_mode(void)
129 {
130         int mode_v;
131         unsigned int mode;
132         const unsigned int default_mode = USB_FUNCTION_MTP | USB_FUNCTION_ACM;
133
134         /* Never return here because deviced must never run in USB_FUNCTION_NONE mode */
135         if (vconf_get_int(VCONFKEY_USB_SEL_MODE, &mode_v) != VCONF_OK) {
136                 mode_v = SET_USB_INVALID;
137                 _E("Failed to get vconf value for USB sel mode: %d", vconf_get_ext_errno());
138         }
139
140         mode = get_mode_bitmap_from_vconf(mode_v);
141
142         /*
143          * Deviced must never run in USB_FUNCTION_NONE mode.
144          * So if vconf value is invalid, deviced uses the default usb mode internally.
145          * Keep the problematic vconf values in order to define the problem correctly.
146          */
147         switch (mode) {
148         case USB_FUNCTION_INVALID: /* Invalid vconf */
149                 usb_selected_mode = default_mode;
150                 _E("Failed to convert vconf to USB mode. There is no mode matches up with vconf %d. Use default mode=%#x.", mode_v, default_mode);
151                 break;
152
153         case USB_FUNCTION_NONE: /* Invalid vconf */
154                 usb_selected_mode = default_mode;
155                 _E("There is USB_FUNCTION_NONE USB mode matches up with vconf %d. Use default mode=%#x.", mode_v, default_mode);
156                 break;
157
158         default: /* Success */
159                 usb_selected_mode = mode;
160                 _I("Succeeded to convert vconf to USB mode. vconf=%d, mode=%#x.", mode_v, mode);
161                 break;
162         }
163
164         /*
165          * Tizen has no way of having different vconf value in engineer mode and user mode.
166          * So, alternatively, always enable SDB in engineer mode.
167          *
168          * Expected default sel_mode value in vconf in user     mode: 1 (MTP + ACM)
169          * Expected default sel_mode value in vconf in engineer mode: 2 (MTP + ACM + SDB)
170          */
171 #ifdef ENGINEER_MODE
172         _I("Engineer mode. USB mode %#x, debug state %d", usb_selected_mode, get_usb_debug_state());
173
174         if (!(usb_selected_mode & USB_FUNCTION_SDB)) {
175                 usb_selected_mode = USB_FUNCTION_MTP | USB_FUNCTION_ACM | USB_FUNCTION_SDB;
176                 (void)usb_state_set_selected_mode(usb_selected_mode);
177         }
178 #endif
179
180         /* To sync with vconf for debug mode */
181         set_usb_debug_state((bool)(usb_selected_mode & USB_FUNCTION_SDB));
182 }
183
184 /* Cache of vconf_get_int(VCONFKEY_USB_SEL_MODE) */
185 unsigned int usb_state_get_selected_mode(void)
186 {
187         return usb_selected_mode;
188 }
189
190 /* Since it is changed externally by dbus and vconf, it should be processed even if it has the same value as before. */
191 int usb_state_set_selected_mode(unsigned int mode)
192 {
193         int ret;
194         int mode_v;
195
196         usb_selected_mode = mode;
197
198         mode_v = get_mode_vconf_from_bitmap(mode);
199         if (mode_v == SET_USB_INVALID) {
200                 _E("Failed to convert USB selected_mode to vconf. There is no vconf matches up with USB mode %#x", mode);
201                 return -EINVAL;
202         }
203
204         ret = vconf_set_int(VCONFKEY_USB_SEL_MODE, mode_v);
205         if (ret != VCONF_OK)
206                 _E("Failed to set vconf value for USB selected mode: %d", vconf_get_ext_errno());
207
208         return ret;
209 }
210
211 /* Cache of vconf_get_int(VCONFKEY_USB_CUR_MODE) */
212 unsigned int usb_state_get_current_mode(void)
213 {
214         return usb_current_mode;
215 }
216
217 /* Because it is only changed by deviced, it is only processed when the vconf actually changes. */
218 int usb_state_set_current_mode(unsigned int mode)
219 {
220         int ret = 0;
221         int mode_v;
222         static int old_mode_v = -1;
223
224         usb_current_mode = mode;
225
226         mode_v = get_mode_vconf_from_bitmap(mode);
227         if (mode_v == SET_USB_INVALID) {
228                 _E("Failed to convert USB current_mode to vconf. There is no vconf matches up with mode %#x", mode);
229                 return -EINVAL;
230         }
231
232         if (old_mode_v != mode_v) {
233                 old_mode_v = mode_v;
234                 ret = vconf_set_int(VCONFKEY_USB_CUR_MODE, mode_v);
235                 if (ret < 0)
236                         _E("Failed to set vconf value for USB current mode: %d", vconf_get_ext_errno());
237         }
238
239         return ret;
240 }
241
242 extcon_usb_state_e usb_state_get_connection(void)
243 {
244         return usb_connection;
245 }
246
247 void usb_state_set_connection(extcon_usb_state_e conn)
248 {
249         usb_connection = conn;
250 }
251
252 static void media_noti_cb(GVariant *var, void *user_data, GError *err)
253 {
254         int id = 0;
255
256         if (!var) {
257                 if (err)
258                         _E("USB media notification error: %s", err->message);
259                 return;
260         }
261
262         if (!g_variant_get_safe(var, "(i)", &id)) {
263                 _E("Failed to get variant(%s): no USB notification message", g_variant_get_type_string(var));
264                 goto out;
265         }
266
267         noti_id = id;
268         _D("USB media notification message(%d)", noti_id);
269
270 out:
271         g_variant_unref(var);
272 }
273
274 static void add_notification_handler(void)
275 {
276         int ret;
277
278         if (noti_id < 0) {
279                 ret = add_async_notification("MediaDeviceNotiOn", media_noti_cb, NULL);
280                 if (ret < 0)
281                         _E("Failed to add USB notification for usb connection: %d", ret);
282         }
283 }
284
285 static void remove_notification_handler(void)
286 {
287         int ret;
288
289         if (noti_id >= 0) {
290                 ret = remove_notification("MediaDeviceNotiOff", noti_id);
291                 if (ret < 0)
292                         _E("Failed to remove USB notification for usb connection: %d", ret);
293                 else
294                         noti_id = -1;
295         }
296 }
297
298 void change_usb_state_notification_handler(unsigned int mode)
299 {
300         if (mode & USB_FUNCTION_MTP)
301                 add_notification_handler();
302         else if (mode == USB_FUNCTION_NONE)
303                 remove_notification_handler();
304 }
305
306 /*
307  * USB_DISCONNECTED                  : VCONFKEY_SYSMAN_USB_DISCONNECTED
308  * USB_CONNECTED + USB_FUNCTION_NONE : VCONFKEY_SYSMAN_USB_CONNECTED
309  * USB_CONNECTED + USB_FUNCTION_OOOO : VCONFKEY_SYSMAN_USB_AVAILABLE
310  *
311  * When connecting the usb cable     : "disconnected"(previous value) -> "connected" -> "available"
312  * When disconnecting the usb cable  : "available"(previous value) -> "dosconnected"
313  * When changing usb mode            : "available"(previous value) -> "connected" -> "available"
314  *
315  * When USB cable is connected but USB initialization fails : It stays "connected" without going to "available"
316  *
317  */
318 void send_usb_state_changed_event(int status)
319 {
320         static int old_status = -1;
321
322         if ((status != VCONFKEY_SYSMAN_USB_CONNECTED) &&
323                 (status != VCONFKEY_SYSMAN_USB_AVAILABLE) &&
324                 (status != VCONFKEY_SYSMAN_USB_DISCONNECTED)) {
325                 _E("Invalid USB state changed event (%d)", status);
326                 return;
327         }
328
329         if (old_status == status)
330                 return;
331
332         old_status = status;
333
334         usb_state_send_system_event(status);
335
336         if (vconf_set_int(VCONFKEY_SYSMAN_USB_STATUS, status) != VCONF_OK)
337                 _E("Failed to set vconf value for USB status: %d", vconf_get_ext_errno());
338
339         /* Caution: calls after vconf_set_int(VCONFKEY_SYSMAN_USB_STATUS) */
340         broadcast_usb_state_changed();
341 }
342