using SW timer for TDM vblank when DPMS off
[platform/core/uifw/libtdm.git] / src / tdm_server.c
1 /**************************************************************************
2
3 libtdm
4
5 Copyright 2015 Samsung Electronics co., Ltd. All Rights Reserved.
6
7 Contact: Eunchul Kim <chulspro.kim@samsung.com>,
8          JinYoung Jeon <jy0.jeon@samsung.com>,
9          Taeheon Kim <th908.kim@samsung.com>,
10          YoungJun Cho <yj44.cho@samsung.com>,
11          SooChan Lim <sc1.lim@samsung.com>,
12          Boram Park <sc1.lim@samsung.com>
13
14 Permission is hereby granted, free of charge, to any person obtaining a
15 copy of this software and associated documentation files (the
16 "Software"), to deal in the Software without restriction, including
17 without limitation the rights to use, copy, modify, merge, publish,
18 distribute, sub license, and/or sell copies of the Software, and to
19 permit persons to whom the Software is furnished to do so, subject to
20 the following conditions:
21
22 The above copyright notice and this permission notice (including the
23 next paragraph) shall be included in all copies or substantial portions
24 of the Software.
25
26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
27 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
29 IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
30 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
31 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
32 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
34 **************************************************************************/
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #include "tdm.h"
41 #include "tdm_private.h"
42 #include "tdm_list.h"
43 #include "tdm-server-protocol.h"
44
45 /* CAUTION:
46  * - tdm server doesn't care about thread things.
47  * - DO NOT use the TDM internal functions here.
48  *     However, the internal function which does lock/unlock the mutex of
49  *     private_display in itself can be called.
50  * - DO NOT use the tdm_private_display structure here.
51  */
52
53 struct _tdm_private_server {
54         tdm_private_loop *private_loop;
55         struct list_head client_list;
56         struct list_head vblank_list;
57 };
58
59 typedef struct _tdm_server_client_info {
60         struct list_head link;
61         tdm_private_server *private_server;
62         struct wl_resource *resource;
63
64         tdm_output *vblank_output;
65         double vblank_gap;
66         unsigned int last_tv_sec;
67         unsigned int last_tv_usec;
68 } tdm_server_client_info;
69
70 typedef struct _tdm_server_vblank_info {
71         struct list_head link;
72         tdm_server_client_info *client_info;
73         struct wl_resource *resource;
74
75         unsigned int req_sec;
76         unsigned int req_usec;
77
78         tdm_event_loop_source *timer_source;
79         unsigned int timer_target_sec;
80         unsigned int timer_target_usec;
81 } tdm_server_vblank_info;
82
83 static tdm_private_server *keep_private_server;
84 static int tdm_debug_server;
85
86 static void
87 _tdm_server_send_done(tdm_server_vblank_info *vblank_info, unsigned int sequence,
88                       unsigned int tv_sec, unsigned int tv_usec);
89
90 static tdm_error
91 _tdm_server_cb_timer(void *user_data)
92 {
93         tdm_server_vblank_info *vblank_info = (tdm_server_vblank_info*)user_data;
94
95         _tdm_server_send_done(vblank_info, 0,
96                               vblank_info->timer_target_sec,
97                               vblank_info->timer_target_usec);
98
99         return TDM_ERROR_NONE;
100 }
101
102 static tdm_error
103 _tdm_server_update_timer(tdm_server_vblank_info *vblank_info, int interval)
104 {
105         tdm_server_client_info *client_info = vblank_info->client_info;
106         tdm_private_server *private_server = client_info->private_server;
107         tdm_private_loop *private_loop = private_server->private_loop;
108         unsigned long last, prev_req, req, curr, next;
109         int ms_delay;
110         tdm_error ret;
111
112         ret = tdm_display_lock(private_loop->dpy);
113         TDM_RETURN_VAL_IF_FAIL(ret == TDM_ERROR_NONE, ret);
114
115         if (!vblank_info->timer_source) {
116                 vblank_info->timer_source =
117                         tdm_event_loop_add_timer_handler(private_loop->dpy,
118                                                          _tdm_server_cb_timer,
119                                                          vblank_info,
120                                                          NULL);
121                 if (!vblank_info->timer_source) {
122                         TDM_ERR("couldn't add timer");
123                         tdm_display_unlock(private_loop->dpy);
124                         return TDM_ERROR_OPERATION_FAILED;
125                 }
126         }
127
128         last = (unsigned long)client_info->last_tv_sec * 1000000 + client_info->last_tv_usec;
129         req = (unsigned long)vblank_info->req_sec * 1000000 + vblank_info->req_usec;
130         curr = tdm_helper_get_time_in_micros();
131
132         prev_req = last + (unsigned int)((req - last) / client_info->vblank_gap) * client_info->vblank_gap;
133         next = prev_req + (unsigned long)(client_info->vblank_gap * interval);
134
135         while (next < curr)
136                 next += (unsigned long)client_info->vblank_gap;
137
138         TDM_DBG("last(%.6lu) req(%.6lu) curr(%.6lu) prev_req(%.6lu) next(%.6lu)",
139                 last, req, curr, prev_req, next);
140
141         ms_delay = (int)ceil((double)(next - curr) / 1000);
142         if (ms_delay == 0)
143                 ms_delay = 1;
144
145         TDM_DBG("delay(%lu) ms_delay(%d)", next - curr, ms_delay);
146
147         ret = tdm_event_loop_source_timer_update(vblank_info->timer_source, ms_delay);
148         if (ret != TDM_ERROR_NONE) {
149                 tdm_event_loop_source_remove(vblank_info->timer_source);
150                 vblank_info->timer_source = NULL;
151                 tdm_display_unlock(private_loop->dpy);
152                 TDM_ERR("couldn't update timer");
153                 return TDM_ERROR_OPERATION_FAILED;
154         }
155
156         TDM_DBG("timer tick: %d us", (int)(next - last));
157
158         vblank_info->timer_target_sec = next / 1000000;
159         vblank_info->timer_target_usec = next % 1000000;
160
161         tdm_display_unlock(private_loop->dpy);
162
163         return TDM_ERROR_NONE;
164 }
165
166 static void
167 _tdm_server_send_done(tdm_server_vblank_info *vblank_info, unsigned int sequence,
168                       unsigned int tv_sec, unsigned int tv_usec)
169 {
170         tdm_server_vblank_info *found;
171         tdm_server_client_info *client_info;
172         unsigned long vtime = (tv_sec * 1000000) + tv_usec;
173         unsigned long curr = tdm_helper_get_time_in_micros();
174
175         if (!keep_private_server)
176                 return;
177
178         LIST_FIND_ITEM(vblank_info, &keep_private_server->vblank_list,
179                        tdm_server_vblank_info, link, found);
180         if (!found) {
181                 TDM_DBG("vblank_info(%p) is destroyed", vblank_info);
182                 return;
183         }
184
185         client_info = vblank_info->client_info;
186         client_info->last_tv_sec = tv_sec;
187         client_info->last_tv_usec = tv_usec;
188
189         TDM_DBG("wl_tdm_vblank@%d done. tv(%lu) curr(%lu)",
190                 wl_resource_get_id(vblank_info->resource), vtime, curr);
191
192         if (tdm_debug_server) {
193                 if (curr - vtime > 1000) /* 1ms */
194                         TDM_WRN("delay: %d us", (int)(curr - vtime));
195         }
196
197         wl_tdm_vblank_send_done(vblank_info->resource, sequence, tv_sec, tv_usec);
198         wl_resource_destroy(vblank_info->resource);
199 }
200
201 static void
202 _tdm_server_cb_output_vblank(tdm_output *output, unsigned int sequence,
203                              unsigned int tv_sec, unsigned int tv_usec,
204                              void *user_data)
205 {
206         tdm_server_vblank_info *vblank_info = (tdm_server_vblank_info*)user_data;
207
208         _tdm_server_send_done(vblank_info, sequence, tv_sec, tv_usec);
209 }
210
211 static void
212 destroy_vblank_callback(struct wl_resource *resource)
213 {
214         tdm_server_vblank_info *vblank_info = wl_resource_get_user_data(resource);
215
216         if (vblank_info->timer_source) {
217                 tdm_private_server *private_server = vblank_info->client_info->private_server;
218
219                 tdm_display_lock(private_server->private_loop->dpy);
220                 tdm_event_loop_source_remove(vblank_info->timer_source);
221                 tdm_display_unlock(private_server->private_loop->dpy);
222         }
223
224         LIST_DEL(&vblank_info->link);
225         free(vblank_info);
226 }
227
228 static void
229 _tdm_server_client_cb_destroy(struct wl_client *client, struct wl_resource *resource)
230 {
231         wl_resource_destroy(resource);
232 }
233
234 static void
235 _tdm_server_client_cb_wait_vblank(struct wl_client *client,
236                                   struct wl_resource *resource,
237                                   uint32_t id, const char *name,
238                                   int32_t sw_timer, int32_t interval,
239                                   uint32_t req_sec, uint32_t req_usec)
240 {
241         tdm_server_client_info *client_info = wl_resource_get_user_data(resource);
242         tdm_private_server *private_server = client_info->private_server;
243         tdm_private_loop *private_loop = private_server->private_loop;
244         tdm_server_vblank_info *vblank_info;
245         struct wl_resource *vblank_resource;
246         tdm_output *found = NULL;
247         tdm_output_dpms dpms_value = TDM_OUTPUT_DPMS_ON;
248         tdm_error ret;
249         const char *model;
250
251         TDM_DBG("The tdm client requests vblank");
252
253         if (tdm_debug_server) {
254                 unsigned long curr = tdm_helper_get_time_in_micros();
255                 unsigned long reqtime = (req_sec * 1000000) + req_usec;
256                 if (curr - reqtime > 1000) /* 1ms */
257                         TDM_WRN("delay(req): %d us", (int)(curr - reqtime));
258         }
259
260         if (client_info->vblank_output) {
261                 model = NULL;
262                 ret = tdm_output_get_model_info(client_info->vblank_output, NULL, &model, NULL);
263                 if (model && !strncmp(model, name, TDM_NAME_LEN))
264                         found = client_info->vblank_output;
265         }
266
267         if (!found) {
268                 int count = 0, i;
269
270                 tdm_display_get_output_count(private_loop->dpy, &count);
271
272                 for (i = 0; i < count; i++) {
273                         tdm_output *output= tdm_display_get_output(private_loop->dpy, i, NULL);
274                         tdm_output_conn_status status;
275
276                         ret = tdm_output_get_conn_status(output, &status);
277                         if (ret || status == TDM_OUTPUT_CONN_STATUS_DISCONNECTED)
278                                 continue;
279
280                         model = NULL;
281                         ret = tdm_output_get_model_info(output, NULL, &model, NULL);
282                         if (ret || !model)
283                                 continue;
284
285                         if (strncmp(model, name, TDM_NAME_LEN))
286                                 continue;
287
288                         found = output;
289                         break;
290                 }
291         }
292
293         if (!found) {
294                 wl_resource_post_error(resource, WL_TDM_CLIENT_ERROR_INVALID_NAME,
295                                        "There is no '%s' output", name);
296                 TDM_ERR("There is no '%s' output", name);
297                 return;
298         }
299
300         if (client_info->vblank_output != found) {
301                 const tdm_output_mode *mode = NULL;
302
303                 client_info->vblank_output = found;
304
305                 tdm_output_get_mode(client_info->vblank_output, &mode);
306                 if (!mode || mode->vrefresh <= 0) {
307                         wl_resource_post_error(resource, WL_TDM_CLIENT_ERROR_OPERATION_FAILED,
308                                                "couldn't get mode of %s", name);
309                         TDM_ERR("couldn't get mode of %s", name);
310                         return;
311                 }
312
313                 client_info->vblank_gap = (double)1000000 / mode->vrefresh;
314                 TDM_INFO("vblank_gap(%.6lf)", client_info->vblank_gap);
315         }
316
317         tdm_output_get_dpms(found, &dpms_value);
318
319         if (dpms_value != TDM_OUTPUT_DPMS_ON && !sw_timer) {
320                 wl_resource_post_error(resource, WL_TDM_CLIENT_ERROR_DPMS_OFF,
321                                        "dpms '%s'", tdm_get_dpms_str(dpms_value));
322                 TDM_ERR("dpms '%s'", tdm_get_dpms_str(dpms_value));
323                 return;
324         }
325
326         vblank_info = calloc(1, sizeof *vblank_info);
327         if (!vblank_info) {
328                 wl_resource_post_no_memory(resource);
329                 TDM_ERR("alloc failed");
330                 return;
331         }
332
333         TDM_DBG("wl_tdm_vblank@%d output(%s) interval(%d)", id, name, interval);
334
335         vblank_resource =
336                 wl_resource_create(client, &wl_tdm_vblank_interface,
337                                    wl_resource_get_version(resource), id);
338         if (!vblank_resource) {
339                 wl_resource_post_no_memory(resource);
340                 TDM_ERR("wl_resource_create failed");
341                 goto free_info;
342         }
343
344         vblank_info->resource = vblank_resource;
345         vblank_info->client_info = client_info;
346         vblank_info->req_sec = req_sec;
347         vblank_info->req_usec = req_usec;
348
349         if (dpms_value == TDM_OUTPUT_DPMS_ON) {
350                 ret = tdm_output_wait_vblank(found, interval, 0,
351                                              _tdm_server_cb_output_vblank, vblank_info);
352                 if (ret != TDM_ERROR_NONE) {
353                         wl_resource_post_error(resource, WL_TDM_CLIENT_ERROR_OPERATION_FAILED,
354                                                "couldn't wait vblank for %s", name);
355                         TDM_ERR("couldn't wait vblank for %s", name);
356                         goto destroy_resource;
357                 }
358         } else if (sw_timer) {
359                 ret = _tdm_server_update_timer(vblank_info, interval);
360                 if (ret != TDM_ERROR_NONE) {
361                         wl_resource_post_error(resource, WL_TDM_CLIENT_ERROR_OPERATION_FAILED,
362                                                "couldn't update timer for %s", name);
363                         TDM_ERR("couldn't update timer for %s", name);
364                         goto destroy_resource;
365                 }
366         } else {
367                 wl_resource_post_error(resource, WL_TDM_CLIENT_ERROR_OPERATION_FAILED,
368                                        "bad implementation");
369                 TDM_NEVER_GET_HERE();
370                 goto destroy_resource;
371         }
372
373         wl_resource_set_implementation(vblank_resource, NULL, vblank_info,
374                                        destroy_vblank_callback);
375
376         LIST_ADDTAIL(&vblank_info->link, &private_server->vblank_list);
377         return;
378 destroy_resource:
379         wl_resource_destroy(vblank_resource);
380 free_info:
381         free(vblank_info);
382 }
383
384 static const struct wl_tdm_client_interface tdm_client_implementation = {
385         _tdm_server_client_cb_destroy,
386         _tdm_server_client_cb_wait_vblank,
387 };
388
389 static void
390 destroy_client_callback(struct wl_resource *resource)
391 {
392         tdm_server_client_info *client_info = wl_resource_get_user_data(resource);
393         LIST_DEL(&client_info->link);
394         free(client_info);
395 }
396
397 static void
398 _tdm_server_cb_create_client(struct wl_client *client,
399                              struct wl_resource *resource, uint32_t id)
400 {
401         tdm_private_server *private_server = wl_resource_get_user_data(resource);
402         tdm_server_client_info *client_info;
403         struct wl_resource *client_resource;
404
405         client_info = calloc(1, sizeof *client_info);
406         if (!client_info) {
407                 wl_resource_post_no_memory(resource);
408                 TDM_ERR("alloc failed");
409                 return;
410         }
411
412         client_resource =
413                 wl_resource_create(client, &wl_tdm_client_interface,
414                                    wl_resource_get_version(resource), id);
415         if (!client_resource) {
416                 wl_resource_post_no_memory(resource);
417                 free(client_info);
418                 TDM_ERR("wl_resource_create failed");
419                 return;
420         }
421
422         client_info->private_server = private_server;
423         client_info->resource = client_resource;
424
425         wl_resource_set_implementation(client_resource, &tdm_client_implementation,
426                                        client_info, destroy_client_callback);
427
428         LIST_ADDTAIL(&client_info->link, &private_server->client_list);
429 }
430
431 static const struct wl_tdm_interface tdm_implementation = {
432         _tdm_server_cb_create_client,
433 };
434
435 static void
436 _tdm_server_bind(struct wl_client *client, void *data,
437                  uint32_t version, uint32_t id)
438 {
439         struct wl_resource *resource;
440
441         resource = wl_resource_create(client, &wl_tdm_interface, version, id);
442         if (!resource) {
443                 wl_client_post_no_memory(client);
444                 return;
445         }
446
447         TDM_DBG("tdm server binding");
448
449         wl_resource_set_implementation(resource, &tdm_implementation, data, NULL);
450 }
451
452 INTERN tdm_error
453 tdm_server_init(tdm_private_loop *private_loop)
454 {
455         tdm_private_server *private_server;
456         const char *debug;
457
458         debug = getenv("TDM_DEBUG_SERVER");
459         if (debug && (strstr(debug, "1")))
460                 tdm_debug_server = 1;
461
462         if (private_loop->private_server)
463                 return TDM_ERROR_NONE;
464
465         TDM_RETURN_VAL_IF_FAIL(private_loop, TDM_ERROR_OPERATION_FAILED);
466         TDM_RETURN_VAL_IF_FAIL(private_loop->wl_display, TDM_ERROR_OPERATION_FAILED);
467
468         if(wl_display_add_socket(private_loop->wl_display, "tdm-socket")) {
469                 TDM_ERR("createing a tdm-socket failed");
470                 return TDM_ERROR_OPERATION_FAILED;
471         }
472
473         private_server = calloc(1, sizeof *private_server);
474         if (!private_server) {
475                 TDM_ERR("alloc failed");
476                 return TDM_ERROR_OUT_OF_MEMORY;
477         }
478
479         LIST_INITHEAD(&private_server->client_list);
480         LIST_INITHEAD(&private_server->vblank_list);
481
482         if (!wl_global_create(private_loop->wl_display, &wl_tdm_interface, 1,
483                               private_server, _tdm_server_bind)) {
484                 TDM_ERR("creating a global resource failed");
485                 free(private_server);
486                 return TDM_ERROR_OUT_OF_MEMORY;
487         }
488
489         private_server->private_loop = private_loop;
490         private_loop->private_server = private_server;
491         keep_private_server = private_server;
492
493         return TDM_ERROR_NONE;
494 }
495
496 INTERN void
497 tdm_server_deinit(tdm_private_loop *private_loop)
498 {
499         tdm_server_vblank_info *v = NULL, *vv = NULL;
500         tdm_server_client_info *c = NULL, *cc = NULL;
501         tdm_private_server *private_server;
502
503         if (!private_loop->private_server)
504                 return;
505
506         private_server = private_loop->private_server;
507
508         LIST_FOR_EACH_ENTRY_SAFE(v, vv, &private_server->vblank_list, link) {
509                 wl_resource_destroy(v->resource);
510         }
511
512         LIST_FOR_EACH_ENTRY_SAFE(c, cc, &private_server->client_list, link) {
513                 wl_resource_destroy(c->resource);
514         }
515
516         free(private_server);
517         private_loop->private_server = NULL;
518         keep_private_server = NULL;
519 }