/* Copyright (C) 2008 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include <sysdep.h>
#include <tls.h>
#ifndef __ASSEMBLER__
# include <nptl/pthreadP.h>
#endif

#if !defined NOT_IN_libc || defined IS_IN_libpthread || defined IS_IN_librt

# define STKSPACE 24

# undef PSEUDO
# define PSEUDO(name, syscall_name, args)				      \
  .text;								      \
  ENTRY (name)								      \
    cfi_startproc;							      \
    SINGLE_THREAD_P (%a0);						      \
    jne .Lpseudo_cancel;						      \
  .type __##syscall_name##_nocancel,@function;			              \
  .globl __##syscall_name##_nocancel;				 	      \
  __##syscall_name##_nocancel:					              \
    DO_CALL (syscall_name, args);					      \
    cmp.l &-4095, %d0;							      \
    jcc SYSCALL_ERROR_LABEL;						      \
    rts;								      \
  .size __##syscall_name##_nocancel,.-__##syscall_name##_nocancel;	      \
  .Lpseudo_cancel:							      \
    sub.l STKSPACE, %sp;						      \
    cfi_adjust_cfa_offset (STKSPACE);					      \
    CENABLE;								      \
    move.l %d0, STKSPACE - 24(%sp);					      \
    PUSHARGS_##args;							      \
    SAVESTK_##args;							      \
    move.l &SYS_ify (syscall_name), %d0;				      \
    trap &0;								      \
    move.l %d0, %d2;							      \
    /* ??? sysdep-cancel.h in linuxthreads never used saved D0,               \
       is this a bug?  */                                                     \
    move.l STKSPACE - 24(%sp), %d0;					      \
    CDISABLE;								      \
    move.l %d2, %d0;		                                              \
    POPARGS_##args;			                                      \
    cmp.l &-4095, %d0;							      \
    jcc SYSCALL_ERROR_LABEL;		                                      \
    add.l STKSPACE, %sp;			                              \
    cfi_adjust_cfa_offset (-STKSPACE);	                                      \
    cfi_endproc

# undef PSEUDO_END
# define PSEUDO_END(name)						      \
  SYSCALL_ERROR_HANDLER;						      \
  END (name)

# define PUSHARGS_0 move.l %d2, STKSPACE - 20(%sp); \
                    cfi_rel_offset (%d2, STKSPACE - 20)
# define PUSHARGS_1 PUSHARGS_0
# define PUSHARGS_2 PUSHARGS_1
# define PUSHARGS_3 PUSHARGS_2; move.l %d3, STKSPACE - 16(%sp);		\
                                cfi_rel_offset (%d3, STKSPACE - 16)
# define PUSHARGS_4 PUSHARGS_3; move.l %d4, STKSPACE - 12(%sp);		\
                                cfi_rel_offset (%d4, STKSPACE - 12)
# define PUSHARGS_5 PUSHARGS_4; move.l %d5, STKSPACE - 8(%sp);		\
                                cfi_rel_offset (%d5, STKSPACE - 8)
# define PUSHARGS_6 PUSHARGS_5; move.l %d6, STKSPACE - 4(%sp);		\
                                cfi_rel_offset (%d6, STKSPACE - 4)

# define POPARGS_0  move.l STKSPACE - 20(%sp), %d2
# define POPARGS_1  POPARGS_0
# define POPARGS_2  POPARGS_1
# define POPARGS_3  POPARGS_2; move.l STKSPACE - 16(%sp), %d3
# define POPARGS_4  POPARGS_3; move.l STKSPACE - 12(%sp), %d4
# define POPARGS_5  POPARGS_4; move.l STKSPACE - 8(%sp), %d5
# define POPARGS_6  POPARGS_5; move.l STKSPACE - 4(%sp), %d6

# define SAVESTK_0
# define SAVESTK_1  move.l STKSPACE + 4(%sp), %d1
# define SAVESTK_2  SAVESTK_1; move.l STKSPACE + 8(%sp), %d2
# define SAVESTK_3  SAVESTK_2; move.l STKSPACE + 12(%sp), %d3
# define SAVESTK_4  SAVESTK_3; move.l STKSPACE + 16(%sp), %d4
# define SAVESTK_5  SAVESTK_4; move.l STKSPACE + 20(%sp), %d5
# define SAVESTK_6  SAVESTK_5; move.l STKSPACE + 24(%sp), %d6

# ifdef PIC
#  define PSEUDO_JMP(sym) jbsr sym ## @PLTPC
# else
#  define PSEUDO_JMP(sym) jbsr sym
# endif

# ifdef IS_IN_libpthread
#  define CENABLE	PSEUDO_JMP (__pthread_enable_asynccancel)
#  define CDISABLE	PSEUDO_JMP (__pthread_disable_asynccancel)
# elif !defined NOT_IN_libc
#  define CENABLE	PSEUDO_JMP (__libc_enable_asynccancel)
#  define CDISABLE	PSEUDO_JMP (__libc_disable_asynccancel)
# elif defined IS_IN_librt
#  define CENABLE	PSEUDO_JMP (__librt_enable_asynccancel)
#  define CDISABLE	PSEUDO_JMP (__librt_disable_asynccancel)
# else
#  error Unsupported library
# endif

# ifndef __ASSEMBLER__
#  define SINGLE_THREAD_P						\
  __builtin_expect (THREAD_GETMEM (THREAD_SELF,				\
				   header.multiple_threads) == 0, 1)
# else
#  define SINGLE_THREAD_P(ATMP)			\
  PSEUDO_JMP (__m68k_read_tp);		        \
  move.l MULTIPLE_THREADS_OFFSET(%a0), ATMP
# endif

#elif !defined __ASSEMBLER__

# define SINGLE_THREAD_P (1)
# define NO_CANCELLATION (1)

#endif

#ifndef __ASSEMBLER__
# define RTLD_SINGLE_THREAD_P \
  __builtin_expect (THREAD_GETMEM (THREAD_SELF, \
				   header.multiple_threads) == 0, 1)
#endif
