gst-indent run on core
[platform/upstream/gstreamer.git] / gst / cothreads.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *                    2000 Wim Taymans <wtay@chello.be>
4  *
5  * cothreads.c: Cothreading routines
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <glib.h>
24
25 #include <sys/time.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <signal.h>
29 #include <setjmp.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <sys/mman.h>
33
34 #include "gst_private.h"
35
36 #include "cothreads.h"
37 #include "gstarch.h"
38 #include "gstinfo.h"
39 #include "gstutils.h"
40
41 #ifdef HAVE_UCONTEXT_H
42 #include <ucontext.h>
43 #endif
44
45 #ifndef MAP_ANONYMOUS
46 #ifdef MAP_ANON
47 /* older glibc's have MAP_ANON instead of MAP_ANONYMOUS */
48 #define MAP_ANONYMOUS MAP_ANON
49 #else
50 /* make due without.  If this fails, we need to open and map /dev/zero */
51 #define MAP_ANONYMOUS 0
52 #endif
53 #endif
54
55 #define STACK_SIZE 0x200000
56
57 #define COTHREAD_MAGIC_NUMBER 0xabcdef
58
59 #define COTHREAD_MAXTHREADS 16
60 #define COTHREAD_STACKSIZE (STACK_SIZE/COTHREAD_MAXTHREADS)
61
62 static void cothread_destroy (cothread_state * cothread);
63
64 struct _cothread_context
65 {
66   cothread_state *cothreads[COTHREAD_MAXTHREADS];       /* array of cothread states */
67   int ncothreads;
68   int current;
69   unsigned long stack_top;
70   GHashTable *data;
71   GThread *thread;
72 };
73
74 /* Disabling this define allows you to shut off a few checks in
75  * cothread_switch.  This likely will speed things up fractionally */
76 #define COTHREAD_PARANOID
77
78
79 /* this _cothread_ctx_key is used as a GThread key to the thread's context
80  * a GThread key is a "pointer" to memory space that is/can be different
81  * (ie. private) for each thread.  The key itself is shared among threads,
82  * so it only needs to be initialized once.
83  */
84 static GStaticPrivate _cothread_ctx_key = G_STATIC_PRIVATE_INIT;
85
86 /*
87  * This should only after context init, since we do checking.
88  */
89 static cothread_context *
90 cothread_get_current_context (void)
91 {
92   cothread_context *ctx;
93
94   ctx = g_static_private_get (&_cothread_ctx_key);
95   g_assert (ctx);
96
97 #ifdef COTHREAD_PARANOID
98   g_assert (ctx->thread == g_thread_self ());
99 #endif
100
101   return ctx;
102 }
103
104 /**
105  * cothread_context_init:
106  *
107  * Create and initialize a new cothread context 
108  *
109  * Returns: the new cothread context
110  */
111 cothread_context *
112 cothread_context_init (void)
113 {
114   char __csf;
115   void *current_stack_frame = &__csf;   /* Get pointer inside current stack frame */
116   cothread_context *ctx;
117
118   /* if there already is a cotread context for this thread,
119    * just return it */
120   ctx = g_static_private_get (&_cothread_ctx_key);
121   if (ctx) {
122     GST_CAT_INFO (GST_CAT_COTHREADS,
123         "returning private _cothread_ctx_key %p", ctx);
124     return ctx;
125   }
126
127   /*
128    * initalize the whole of the cothreads context 
129    */
130   ctx = (cothread_context *) g_malloc (sizeof (cothread_context));
131
132   /* we consider the initiating process to be cothread 0 */
133   ctx->ncothreads = 1;
134   ctx->current = 0;
135   ctx->data = g_hash_table_new (g_str_hash, g_str_equal);
136   ctx->thread = g_thread_self ();
137
138   GST_CAT_INFO (GST_CAT_COTHREADS, "initializing cothreads");
139
140   /* set this thread's context pointer */
141   GST_CAT_INFO (GST_CAT_COTHREADS,
142       "setting private _cothread_ctx_key to %p in thread %p", ctx,
143       g_thread_self ());
144   g_static_private_set (&_cothread_ctx_key, ctx, NULL);
145
146   g_assert (ctx == cothread_get_current_context ());
147
148   /* clear the cothread data */
149   memset (ctx->cothreads, 0, sizeof (ctx->cothreads));
150
151   /* FIXME this may not be 64bit clean
152    *       could use casts to uintptr_t from inttypes.h
153    *       if only all platforms had inttypes.h
154    */
155   /* stack_top is the address of the first byte past our stack segment. */
156   /* FIXME: an assumption is made that the stack segment is STACK_SIZE
157    * aligned. */
158   ctx->stack_top = ((gulong) current_stack_frame | (STACK_SIZE - 1)) + 1;
159   GST_CAT_DEBUG (GST_CAT_COTHREADS, "stack top is 0x%08lx", ctx->stack_top);
160
161   /*
162    * initialize the 0th cothread
163    */
164   ctx->cothreads[0] = (cothread_state *) g_malloc0 (sizeof (cothread_state));
165   ctx->cothreads[0]->ctx = ctx;
166   ctx->cothreads[0]->cothreadnum = 0;
167   ctx->cothreads[0]->func = NULL;
168   ctx->cothreads[0]->argc = 0;
169   ctx->cothreads[0]->argv = NULL;
170   ctx->cothreads[0]->priv = NULL;
171   ctx->cothreads[0]->flags = COTHREAD_STARTED;
172   ctx->cothreads[0]->sp = (void *) current_stack_frame;
173
174   GST_CAT_INFO (GST_CAT_COTHREADS, "0th cothread is %p at sp:%p",
175       ctx->cothreads[0], ctx->cothreads[0]->sp);
176
177   return ctx;
178 }
179
180 /**
181  * cothread_context_free:
182  * @ctx: the cothread context to free
183  *
184  * Free the cothread context.
185  */
186 void
187 cothread_context_free (cothread_context * ctx)
188 {
189   gint i;
190
191   g_return_if_fail (ctx != NULL);
192   g_assert (ctx->thread == g_thread_self ());
193   g_assert (ctx->current == 0);
194
195   GST_CAT_INFO (GST_CAT_COTHREADS, "free cothread context");
196
197   for (i = 1; i < COTHREAD_MAXTHREADS; i++) {
198     if (ctx->cothreads[i]) {
199       cothread_destroy (ctx->cothreads[i]);
200     }
201   }
202   if (ctx->cothreads[0]) {
203     g_free (ctx->cothreads[0]);
204     ctx->cothreads[0] = NULL;
205   }
206   g_hash_table_destroy (ctx->data);
207   /* make sure we free the private key for cothread context */
208   GST_CAT_INFO (GST_CAT_COTHREADS,
209       "setting private _cothread_ctx_key to NULL in thread %p",
210       g_thread_self ());
211   g_static_private_set (&_cothread_ctx_key, NULL, NULL);
212   g_free (ctx);
213 }
214
215 /**
216  * cothread_create:
217  * @ctx: the cothread context
218  *
219  * Create a new cothread state in the given context
220  *
221  * Returns: the new cothread state or NULL on error
222  */
223 cothread_state *
224 cothread_create (cothread_context * ctx)
225 {
226   cothread_state *cothread;
227   void *mmaped = 0;
228   gint slot = 0;
229   unsigned long page_size;
230
231   g_return_val_if_fail (ctx != NULL, NULL);
232
233   GST_CAT_DEBUG (GST_CAT_COTHREADS, "manager sef %p, cothread self %p",
234       ctx->thread, g_thread_self ());
235
236   if (ctx->ncothreads == COTHREAD_MAXTHREADS) {
237     /* this is pretty fatal */
238     g_warning ("cothread_create: attempt to create > COTHREAD_MAXTHREADS");
239     return NULL;
240   }
241   /* find a free spot in the stack, note slot 0 has the main thread */
242   for (slot = 1; slot < ctx->ncothreads; slot++) {
243     if (ctx->cothreads[slot] == NULL)
244       break;
245     else if (ctx->cothreads[slot]->flags & COTHREAD_DESTROYED &&
246         slot != ctx->current) {
247       cothread_destroy (ctx->cothreads[slot]);
248       break;
249     }
250   }
251
252   GST_CAT_DEBUG (GST_CAT_COTHREADS, "Found free cothread slot %d", slot);
253
254   /* cothread stack space of the thread is mapped in reverse, with cothread 0
255    * stack space at the top */
256   cothread =
257       (cothread_state *) (ctx->stack_top - (slot + 1) * COTHREAD_STACKSIZE);
258   GST_CAT_DEBUG (GST_CAT_COTHREADS, "cothread pointer is %p", cothread);
259
260 #if 0
261   /* This tests to see whether or not we can grow down the stack */
262   {
263     unsigned long ptr;
264
265     for (ptr = ctx->stack_top - 4096; ptr > (unsigned long) cothread;
266         ptr -= 4096) {
267       GST_CAT_DEBUG (GST_CAT_COTHREADS, "touching location 0x%08lx", ptr);
268       *(volatile unsigned int *) ptr = *(volatile unsigned int *) ptr;
269       GST_CAT_DEBUG (GST_CAT_COTHREADS, "ok (0x%08x)", *(unsigned int *) ptr);
270     }
271   }
272 #endif
273
274 #ifdef _SC_PAGESIZE
275   page_size = sysconf (_SC_PAGESIZE);
276 #else
277   page_size = getpagesize ();
278 #endif
279
280   /* The mmap is necessary on Linux/i386, and possibly others, since the
281    * kernel is picky about when we can expand our stack. */
282   GST_CAT_DEBUG (GST_CAT_COTHREADS, "mmaping %p, size 0x%08x", cothread,
283       COTHREAD_STACKSIZE);
284   /* Remap with a guard page. This decreases our stack size by 8 kB (for
285    * 4 kB pages) and also wastes almost 4 kB for the cothreads
286    * structure */
287   munmap ((void *) cothread, COTHREAD_STACKSIZE);
288   mmaped = mmap ((void *) cothread, page_size,
289       PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
290   mmaped = mmap (((void *) cothread) + page_size * 2,
291       COTHREAD_STACKSIZE - page_size * 2,
292       PROT_READ | PROT_WRITE | PROT_EXEC,
293       MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
294   GST_CAT_DEBUG (GST_CAT_COTHREADS, "coming out of mmap");
295   if (mmaped == MAP_FAILED) {
296     perror ("mmap'ing cothread stack space");
297     return NULL;
298   }
299   if (mmaped != (void *) cothread + page_size * 2) {
300     g_warning ("could not mmap requested memory for cothread");
301     return NULL;
302   }
303
304   cothread->magic_number = COTHREAD_MAGIC_NUMBER;
305   GST_CAT_DEBUG (GST_CAT_COTHREADS,
306       "create  cothread %d with magic number 0x%x", slot,
307       cothread->magic_number);
308   cothread->ctx = ctx;
309   cothread->cothreadnum = slot;
310   cothread->flags = 0;
311   cothread->priv = NULL;
312   cothread->sp = ((guchar *) cothread + COTHREAD_STACKSIZE);
313   cothread->stack_size = COTHREAD_STACKSIZE - page_size * 2;
314   cothread->stack_base = (void *) cothread + 2 * page_size;
315
316   GST_CAT_INFO (GST_CAT_COTHREADS,
317       "created cothread #%d in slot %d: %p at sp:%p",
318       ctx->ncothreads, slot, cothread, cothread->sp);
319
320   ctx->cothreads[slot] = cothread;
321   ctx->ncothreads++;
322
323   return cothread;
324 }
325
326 /**
327  * cothread_free:
328  * @cothread: the cothread state
329  *
330  * Free the given cothread state
331  */
332 void
333 cothread_free (cothread_state * cothread)
334 {
335   g_return_if_fail (cothread != NULL);
336
337   GST_CAT_INFO (GST_CAT_COTHREADS, "flag cothread %d for destruction",
338       cothread->cothreadnum);
339
340   /* we simply flag the cothread for destruction here */
341   if (cothread)
342     cothread->flags |= COTHREAD_DESTROYED;
343   else
344     g_warning ("somebody set up us the bomb");
345 }
346
347 static void
348 cothread_destroy (cothread_state * cothread)
349 {
350   cothread_context *ctx;
351   gint cothreadnum;
352
353   g_return_if_fail (cothread != NULL);
354
355   cothreadnum = cothread->cothreadnum;
356   ctx = cothread->ctx;
357   g_assert (ctx->thread == g_thread_self ());
358   g_assert (ctx == cothread_get_current_context ());
359
360   GST_CAT_INFO (GST_CAT_COTHREADS, "destroy cothread %d %p %d",
361       cothreadnum, cothread, ctx->current);
362
363   /* cothread 0 needs to be destroyed specially */
364   g_assert (cothreadnum != 0);
365
366   /* we have to unlock here because we might be switched out 
367    * with the lock held */
368   cothread_unlock (cothread);
369
370   /* doing cleanups of the cothread create */
371   GST_CAT_DEBUG (GST_CAT_COTHREADS,
372       "destroy cothread %d with magic number 0x%x", cothreadnum,
373       cothread->magic_number);
374   g_assert (cothread->magic_number == COTHREAD_MAGIC_NUMBER);
375
376   g_assert (cothread->priv == NULL);
377
378   memset (cothread, 0, sizeof (*cothread));
379
380   ctx->cothreads[cothreadnum] = NULL;
381   ctx->ncothreads--;
382 }
383
384 /**
385  * cothread_setfunc:
386  * @cothread: the cothread state
387  * @func: the function to call
388  * @argc: argument count for the cothread function
389  * @argv: arguments for the cothread function
390  *
391  * Set the cothread function
392  */
393 void
394 cothread_setfunc (cothread_state * cothread, cothread_func func, int argc,
395     char **argv)
396 {
397   cothread->func = func;
398   cothread->argc = argc;
399   cothread->argv = argv;
400 }
401
402 /**
403  * cothread_stop:
404  * @cothread: the cothread to stop
405  *
406  * Stop the cothread and reset the stack and program counter.
407  */
408 void
409 cothread_stop (cothread_state * cothread)
410 {
411   cothread->flags &= ~COTHREAD_STARTED;
412 }
413
414 /**
415  * cothread_main:
416  * @ctx: cothread context to find main cothread of.
417  *
418  * Gets the main thread.
419  *
420  * Returns: the #cothread_state of the main (0th) cothread.
421  */
422 cothread_state *
423 cothread_main (cothread_context * ctx)
424 {
425   g_assert (ctx->thread == g_thread_self ());
426
427   GST_CAT_DEBUG (GST_CAT_COTHREADS, "returning %p, the 0th cothread",
428       ctx->cothreads[0]);
429   return ctx->cothreads[0];
430 }
431
432 /**
433  * cothread_current_main:
434  *
435  * Get the main thread in the current GThread.
436  *
437  * Returns: the #cothread_state of the main (0th) thread in the current GThread
438  */
439 cothread_state *
440 cothread_current_main (void)
441 {
442   cothread_context *ctx = cothread_get_current_context ();
443
444   return ctx->cothreads[0];
445 }
446
447 /**
448  * cothread_current:
449  *
450  * Get the currenttly executing cothread
451  *
452  * Returns: the #cothread_state of the current cothread
453  */
454 cothread_state *
455 cothread_current (void)
456 {
457   cothread_context *ctx = cothread_get_current_context ();
458
459   return ctx->cothreads[ctx->current];
460 }
461
462 static void
463 cothread_stub (void)
464 {
465   cothread_context *ctx = cothread_get_current_context ();
466   cothread_state *cothread = ctx->cothreads[ctx->current];
467
468 #ifndef GST_DISABLE_GST_DEBUG
469   char __csf;
470   void *current_stack_frame = &__csf;
471 #endif
472
473   GST_CAT_DEBUG (GST_CAT_COTHREADS, "stack addr %p", &ctx);
474
475   cothread->flags |= COTHREAD_STARTED;
476
477   while (TRUE) {
478     cothread->func (cothread->argc, cothread->argv);
479
480     GST_CAT_DEBUG (GST_CAT_COTHREADS, "cothread[%d] thread->func exited",
481         ctx->current);
482
483     GST_CAT_DEBUG (GST_CAT_COTHREADS, "sp=%p", current_stack_frame);
484     GST_CAT_DEBUG (GST_CAT_COTHREADS, "ctx=%p current=%p", ctx,
485         cothread_get_current_context ());
486     g_assert (ctx == cothread_get_current_context ());
487
488     g_assert (ctx->current != 0);
489
490     /* we do this to avoid ever returning, we just switch to 0th thread */
491     cothread_switch (cothread_main (ctx));
492   }
493 }
494
495 /**
496  * cothread_getcurrent:
497  *
498  * Get the current cothread id
499  *
500  * Returns: the current cothread id
501  */
502 int
503 cothread_getcurrent (void)
504     G_GNUC_NO_INSTRUMENT;
505
506      int cothread_getcurrent (void)
507 {
508   cothread_context *ctx = cothread_get_current_context ();
509
510   if (!ctx)
511     return -1;
512
513   return ctx->current;
514 }
515
516 /**
517  * cothread_set_private:
518  * @cothread: the cothread state
519  * @data: the data
520  *
521  * set private data for the cothread.
522  */
523 void
524 cothread_set_private (cothread_state * cothread, gpointer data)
525 {
526   cothread->priv = data;
527 }
528
529 /**
530  * cothread_context_set_data:
531  * @cothread: the cothread state
532  * @key: a key for the data
533  * @data: the data
534  *
535  * adds data to a cothread
536  */
537 void
538 cothread_context_set_data (cothread_state * cothread, gchar * key,
539     gpointer data)
540 {
541   cothread_context *ctx = cothread_get_current_context ();
542
543   g_hash_table_insert (ctx->data, key, data);
544 }
545
546 /**
547  * cothread_get_private:
548  * @cothread: the cothread state
549  *
550  * get the private data from the cothread
551  *
552  * Returns: the private data of the cothread
553  */
554 gpointer
555 cothread_get_private (cothread_state * cothread)
556 {
557   return cothread->priv;
558 }
559
560 /**
561  * cothread_context_get_data:
562  * @cothread: the cothread state
563  * @key: a key for the data
564  *
565  * get data from the cothread
566  *
567  * Returns: the data associated with the key
568  */
569 gpointer
570 cothread_context_get_data (cothread_state * cothread, gchar * key)
571 {
572   cothread_context *ctx = cothread_get_current_context ();
573
574   return g_hash_table_lookup (ctx->data, key);
575 }
576
577 /**
578  * cothread_switch:
579  * @cothread: cothread state to switch to
580  *
581  * Switches to the given cothread state
582  */
583 void
584 cothread_switch (cothread_state * cothread)
585 {
586   cothread_context *ctx;
587   cothread_state *current;
588   int enter;
589
590 #ifdef COTHREAD_PARANOID
591   if (cothread == NULL)
592     goto nothread;
593 #endif
594   ctx = cothread->ctx;
595
596   /* paranoia check to make sure we're in the right thread */
597   g_assert (ctx->thread == g_thread_self ());
598
599 #ifdef COTHREAD_PARANOID
600   if (ctx == NULL)
601     goto nocontext;
602 #endif
603
604   current = ctx->cothreads[ctx->current];
605 #ifdef COTHREAD_PARANOID
606   if (current == NULL)
607     goto nocurrent;
608 #endif
609   if (current == cothread)
610     goto selfswitch;
611
612
613   /* find the number of the thread to switch to */
614   GST_CAT_INFO (GST_CAT_COTHREAD_SWITCH,
615       "switching from cothread #%d to cothread #%d",
616       ctx->current, cothread->cothreadnum);
617   ctx->current = cothread->cothreadnum;
618
619   /* save the current stack pointer, frame pointer, and pc */
620 #ifdef GST_ARCH_PRESETJMP
621   GST_ARCH_PRESETJMP ();
622 #endif
623   enter = setjmp (current->jmp);
624   if (enter != 0) {
625     GST_CAT_DEBUG (GST_CAT_COTHREADS,
626         "enter cothread #%d %d sp=%p jmpbuf=%p",
627         current->cothreadnum, enter, current->sp, current->jmp);
628     return;
629   }
630   GST_CAT_DEBUG (GST_CAT_COTHREADS, "exit cothread #%d %d sp=%p jmpbuf=%p",
631       current->cothreadnum, enter, current->sp, current->jmp);
632   enter = 1;
633
634   if (current->flags & COTHREAD_DESTROYED) {
635     cothread_destroy (current);
636   }
637
638   GST_CAT_DEBUG (GST_CAT_COTHREADS, "set stack to %p", cothread->sp);
639   /* restore stack pointer and other stuff of new cothread */
640   if (cothread->flags & COTHREAD_STARTED) {
641     GST_CAT_DEBUG (GST_CAT_COTHREADS, "via longjmp() jmpbuf %p", cothread->jmp);
642     /* switch to it */
643     longjmp (cothread->jmp, 1);
644   } else {
645 #ifdef HAVE_MAKECONTEXT
646     ucontext_t ucp;
647
648     GST_CAT_DEBUG (GST_CAT_COTHREADS, "making context");
649
650     g_assert (cothread != cothread_main (ctx));
651
652     getcontext (&ucp);
653     ucp.uc_stack.ss_sp = (void *) cothread->stack_base;
654     ucp.uc_stack.ss_size = cothread->stack_size;
655     makecontext (&ucp, cothread_stub, 0);
656     setcontext (&ucp);
657 #else
658     GST_ARCH_SETUP_STACK ((char *) cothread->sp);
659     GST_ARCH_SET_SP (cothread->sp);
660     /* start it */
661     GST_ARCH_CALL (cothread_stub);
662 #endif
663
664     GST_CAT_DEBUG (GST_CAT_COTHREADS, "exit thread ");
665     ctx->current = 0;
666   }
667
668   return;
669
670 #ifdef COTHREAD_PARANOID
671 nothread:
672   g_warning ("cothread: can't switch to NULL cothread!");
673   return;
674 nocontext:
675   g_warning ("cothread: there's no context, help!");
676   exit (2);
677 nocurrent:
678   g_warning ("cothread: there's no current thread, help!");
679   exit (2);
680 #endif /* COTHREAD_PARANOID */
681 selfswitch:
682   g_warning
683       ("cothread: trying to switch to same thread, legal but not necessary");
684   return;
685 }
686
687 /**
688  * cothread_lock:
689  * @cothread: cothread state to lock
690  *
691  * Locks the cothread state.
692  */
693 void
694 cothread_lock (cothread_state * cothread)
695 {
696 }
697
698 /**
699  * cothread_trylock:
700  * @cothread: cothread state to try to lock
701  *
702  * Try to lock the cothread state
703  *
704  * Returns: TRUE if the cothread could be locked.
705  */
706 gboolean
707 cothread_trylock (cothread_state * cothread)
708 {
709   return TRUE;
710 }
711
712 /**
713  * cothread_unlock:
714  * @cothread: cothread state to unlock
715  *
716  * Unlock the cothread state.
717  */
718 void
719 cothread_unlock (cothread_state * cothread)
720 {
721 }