source: trunk/libs/newlib/src/libgloss/aarch64/syscalls.c @ 573

Last change on this file since 573 was 444, checked in by satin@…, 7 years ago

add newlib,libalmos-mkh, restructure shared_syscalls.h and mini-libc

File size: 18.7 KB
RevLine 
[444]1/* Copyright (c) 2009, 2010, 2011, 2012 ARM Ltd.  All rights reserved.
2
3 Redistribution and use in source and binary forms, with or without
4 modification, are permitted provided that the following conditions
5 are met:
6 1. Redistributions of source code must retain the above copyright
7    notice, this list of conditions and the following disclaimer.
8 2. Redistributions in binary form must reproduce the above copyright
9    notice, this list of conditions and the following disclaimer in the
10    documentation and/or other materials provided with the distribution.
11 3. The name of the company may not be used to endorse or promote
12    products derived from this software without specific prior written
13    permission.
14
15 THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25
26/* Support files for GNU libc.  Files in the system namespace go here.
27   Files in the C namespace (ie those that do not start with an
28   underscore) go in .c.  */
29
30#include <_ansi.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <sys/fcntl.h>
34#include <stdio.h>
35#include <string.h>
36#include <time.h>
37#include <sys/time.h>
38#include <sys/times.h>
39#include <errno.h>
40#include <reent.h>
41#include <unistd.h>
42#include <sys/wait.h>
43#include "svc.h"
44
45/* Safe casting in both LP64 and ILP32.  */
46#define POINTER_TO_PARAM_BLOCK_T(PTR)           \
47  (param_block_t)(unsigned long) (PTR)
48
49/* Forward prototypes.  */
50int _system (const char *);
51int _rename (const char *, const char *);
52int _isatty (int);
53clock_t _times (struct tms *);
54int _gettimeofday (struct timeval *, void *);
55int _unlink (const char *);
56int _link (void);
57int _stat (const char *, struct stat *);
58int _fstat (int, struct stat *);
59int _swistat (int fd, struct stat * st);
60caddr_t _sbrk (int);
61int _getpid (int);
62int _close (int);
63clock_t _clock (void);
64int _swiclose (int);
65int _open (const char *, int, ...);
66int _swiopen (const char *, int);
67int _write (int, char *, int);
68int _swiwrite (int, char *, int);
69int _lseek (int, int, int);
70int _swilseek (int, int, int);
71int _read (int, char *, int);
72int _swiread (int, char *, int);
73void initialise_monitor_handles (void);
74
75static int checkerror (int);
76static int error (int);
77static int get_errno (void);
78
79/* Semihosting utilities.  */
80static void initialise_semihosting_exts (void);
81
82/* Struct used to keep track of the file position, just so we
83   can implement fseek(fh,x,SEEK_CUR).  */
84struct fdent
85{
86  int handle;
87  int flags;
88  ino_t ino;
89  int pos;
90};
91
92#define MAX_OPEN_FILES 20
93
94/* User file descriptors (fd) are integer indexes into
95   the openfiles[] array. Error checking is done by using
96   findslot().
97
98   This openfiles array is manipulated directly by only
99   these 5 functions:
100
101        findslot() - Translate entry.
102        newslot() - Find empty entry.
103        initilise_monitor_handles() - Initialize entries.
104        _swiopen() - Initialize entry.
105        _close() - Handle stdout == stderr case.
106
107   Every other function must use findslot().  */
108
109static struct fdent openfiles[MAX_OPEN_FILES];
110
111static struct fdent *findslot (int);
112static int newslot (void);
113
114/* Register name faking - works in collusion with the linker.  */
115#ifdef __ILP32__
116register char * stack_ptr asm ("wsp");
117#else
118register char * stack_ptr asm ("sp");
119#endif
120
121
122/* following is copied from libc/stdio/local.h to check std streams */
123extern void __sinit (struct _reent *);
124#define CHECK_INIT(ptr) \
125  do                                            \
126    {                                           \
127      if ((ptr) && !(ptr)->__sdidinit)          \
128        __sinit (ptr);                          \
129    }                                           \
130  while (0)
131
132static int monitor_stdin;
133static int monitor_stdout;
134static int monitor_stderr;
135
136static int supports_ext_exit_extended = -1;
137static int supports_ext_stdout_stderr = -1;
138
139/* Return a pointer to the structure associated with
140   the user file descriptor fd. */
141static struct fdent *
142findslot (int fd)
143{
144  CHECK_INIT (_REENT);
145
146  /* User file descriptor is out of range. */
147  if ((unsigned int) fd >= MAX_OPEN_FILES)
148    return NULL;
149
150  /* User file descriptor is open? */
151  if (openfiles[fd].handle == -1)
152    return NULL;
153
154  /* Valid. */
155  return &openfiles[fd];
156}
157
158/* Return the next lowest numbered free file
159   structure, or -1 if we can't find one. */
160static int
161newslot (void)
162{
163  int i;
164
165  for (i = 0; i < MAX_OPEN_FILES; i++)
166    if (openfiles[i].handle == -1)
167      break;
168
169  if (i == MAX_OPEN_FILES)
170    return -1;
171
172  return i;
173}
174
175void
176initialise_monitor_handles (void)
177{
178  int i;
179
180  /* Open the standard file descriptors by opening the special
181   * teletype device, ":tt", read-only to obtain a descritpor for
182   * standard input and write-only to obtain a descriptor for standard
183   * output. Finally, open ":tt" in append mode to obtain a descriptor
184   * for standard error. Since this is a write mode, most kernels will
185   * probably return the same value as for standard output, but the
186   * kernel can differentiate the two using the mode flag and return a
187   * different descriptor for standard error.
188   */
189
190  param_block_t block[3];
191
192  block[0] = POINTER_TO_PARAM_BLOCK_T (":tt");
193  block[2] = 3;                 /* length of filename */
194  block[1] = 0;                 /* mode "r" */
195  monitor_stdin = do_AngelSVC (AngelSVC_Reason_Open, block);
196
197  for (i = 0; i < MAX_OPEN_FILES; i++)
198    openfiles[i].handle = -1;;
199
200  if (_has_ext_stdout_stderr ())
201  {
202    block[0] = POINTER_TO_PARAM_BLOCK_T (":tt");
203    block[2] = 3;                       /* length of filename */
204    block[1] = 4;                       /* mode "w" */
205    monitor_stdout = do_AngelSVC (AngelSVC_Reason_Open, block);
206
207    block[0] = POINTER_TO_PARAM_BLOCK_T (":tt");
208    block[2] = 3;                       /* length of filename */
209    block[1] = 8;                       /* mode "a" */
210    monitor_stderr = do_AngelSVC (AngelSVC_Reason_Open, block);
211  }
212
213  /* If we failed to open stderr, redirect to stdout. */
214  if (monitor_stderr == -1)
215    monitor_stderr = monitor_stdout;
216
217  openfiles[0].handle = monitor_stdin;
218  openfiles[0].flags = _FREAD;
219  openfiles[0].pos = 0;
220
221  if (_has_ext_stdout_stderr ())
222  {
223    openfiles[1].handle = monitor_stdout;
224    openfiles[0].flags = _FWRITE;
225    openfiles[1].pos = 0;
226    openfiles[2].handle = monitor_stderr;
227    openfiles[0].flags = _FWRITE;
228    openfiles[2].pos = 0;
229  }
230}
231
232int
233_has_ext_exit_extended (void)
234{
235  if (supports_ext_exit_extended < 0)
236  {
237    initialise_semihosting_exts ();
238  }
239
240  return supports_ext_exit_extended;
241}
242
243int
244_has_ext_stdout_stderr (void)
245{
246  if (supports_ext_stdout_stderr < 0)
247  {
248    initialise_semihosting_exts ();
249  }
250
251  return supports_ext_stdout_stderr;
252}
253
254static void
255initialise_semihosting_exts (void)
256{
257  supports_ext_exit_extended = 0;
258  supports_ext_stdout_stderr = 1;
259
260#if SEMIHOST_V2
261  char features[1];
262  if (_get_semihosting_exts (features, 0, 1) > 0)
263  {
264     supports_ext_exit_extended
265       = features[0] & (1 << SH_EXT_EXIT_EXTENDED_BITNUM);
266     supports_ext_stdout_stderr
267       = features[0] & (1 << SH_EXT_STDOUT_STDERR_BITNUM);
268  }
269#endif
270}
271
272int
273_get_semihosting_exts (char* features, int offset, int num)
274{
275  int fd = _open (":semihosting-features", O_RDONLY);
276  memset (features, 0, num);
277
278  if (fd == -1)
279  {
280    return -1;
281  }
282
283  struct fdent *pfd;
284  pfd = findslot (fd);
285
286  param_block_t block[1];
287  block[0] = pfd->handle;
288
289  int len = do_AngelSVC (AngelSVC_Reason_FLen, block);
290
291  if (len < NUM_SHFB_MAGIC
292      || num > (len - NUM_SHFB_MAGIC))
293  {
294     _close (fd);
295     return -1;
296  }
297
298  char buffer[NUM_SHFB_MAGIC];
299  int n_read = _read (fd, buffer, NUM_SHFB_MAGIC);
300
301  if (n_read < NUM_SHFB_MAGIC
302      || buffer[0] != SHFB_MAGIC_0
303      || buffer[1] != SHFB_MAGIC_1
304      || buffer[2] != SHFB_MAGIC_2
305      || buffer[3] != SHFB_MAGIC_3)
306  {
307     _close (fd);
308     return -1;
309  }
310
311  if (_lseek (fd, offset, SEEK_CUR) < 0)
312  {
313     _close (fd);
314     return -1;
315  }
316
317  n_read = _read (fd, features, num);
318
319  _close (fd);
320
321  return checkerror (n_read);
322}
323
324static int
325get_errno (void)
326{
327  return do_AngelSVC (AngelSVC_Reason_Errno, NULL);
328}
329
330/* Set errno and return result. */
331static int
332error (int result)
333{
334  errno = get_errno ();
335  return result;
336}
337
338/* Check the return and set errno appropriately. */
339static int
340checkerror (int result)
341{
342  if (result == -1)
343    return error (-1);
344  return result;
345}
346
347/* fh, is a valid internal file handle.
348   ptr, is a null terminated string.
349   len, is the length in bytes to read.
350   Returns the number of bytes *not* written. */
351int
352_swiread (int fh, char *ptr, int len)
353{
354  param_block_t block[3];
355
356  block[0] = fh;
357  block[1] = POINTER_TO_PARAM_BLOCK_T (ptr);
358  block[2] = len;
359
360  return checkerror (do_AngelSVC (AngelSVC_Reason_Read, block));
361}
362
363/* fd, is a valid user file handle.
364   Translates the return of _swiread into
365   bytes read. */
366int
367_read (int fd, char *ptr, int len)
368{
369  int res;
370  struct fdent *pfd;
371
372  pfd = findslot (fd);
373  if (pfd == NULL)
374    {
375      errno = EBADF;
376      return -1;
377    }
378
379  res = _swiread (pfd->handle, ptr, len);
380
381  if (res == -1)
382    return res;
383
384  pfd->pos += len - res;
385
386  /* res == len is not an error,
387     at least if we want feof() to work.  */
388  return len - res;
389}
390
391/* fd, is a user file descriptor. */
392int
393_swilseek (int fd, int ptr, int dir)
394{
395  int res;
396  struct fdent *pfd;
397
398  /* Valid file descriptor? */
399  pfd = findslot (fd);
400  if (pfd == NULL)
401    {
402      errno = EBADF;
403      return -1;
404    }
405
406  /* Valid whence? */
407  if ((dir != SEEK_CUR) && (dir != SEEK_SET) && (dir != SEEK_END))
408    {
409      errno = EINVAL;
410      return -1;
411    }
412
413  /* Convert SEEK_CUR to SEEK_SET */
414  if (dir == SEEK_CUR)
415    {
416      ptr = pfd->pos + ptr;
417      /* The resulting file offset would be negative. */
418      if (ptr < 0)
419        {
420          errno = EINVAL;
421          if ((pfd->pos > 0) && (ptr > 0))
422            errno = EOVERFLOW;
423          return -1;
424        }
425      dir = SEEK_SET;
426    }
427
428  param_block_t block[2];
429  if (dir == SEEK_END)
430    {
431      block[0] = pfd->handle;
432      res = checkerror (do_AngelSVC (AngelSVC_Reason_FLen, block));
433      if (res == -1)
434        return -1;
435      ptr += res;
436    }
437
438  /* This code only does absolute seeks.  */
439  block[0] = pfd->handle;
440  block[1] = ptr;
441  res = checkerror (do_AngelSVC (AngelSVC_Reason_Seek, block));
442  /* At this point ptr is the current file position. */
443  if (res >= 0)
444    {
445      pfd->pos = ptr;
446      return ptr;
447    }
448  else
449    return -1;
450}
451
452_lseek (int fd, int ptr, int dir)
453{
454  return _swilseek (fd, ptr, dir);
455}
456
457/* fh, is a valid internal file handle.
458   Returns the number of bytes *not* written. */
459int
460_swiwrite (int fh, char *ptr, int len)
461{
462  param_block_t block[3];
463
464  block[0] = fh;
465  block[1] = POINTER_TO_PARAM_BLOCK_T (ptr);
466  block[2] = len;
467
468  return checkerror (do_AngelSVC (AngelSVC_Reason_Write, block));
469}
470
471/* fd, is a user file descriptor. */
472int
473_write (int fd, char *ptr, int len)
474{
475  int res;
476  struct fdent *pfd;
477
478  pfd = findslot (fd);
479  if (pfd == NULL)
480    {
481      errno = EBADF;
482      return -1;
483    }
484
485  res = _swiwrite (pfd->handle, ptr, len);
486
487  /* Clearly an error. */
488  if (res < 0)
489    return -1;
490
491  pfd->pos += len - res;
492
493  /* We wrote 0 bytes?
494     Retrieve errno just in case. */
495  if ((len - res) == 0)
496    return error (0);
497
498  return (len - res);
499}
500
501int
502_swiopen (const char *path, int flags)
503{
504  int aflags = 0, fh;
505  param_block_t block[3];
506  static ino_t ino = 1;
507  int fd;
508
509  if (path == NULL)
510    {
511      errno = ENOENT;
512      return -1;
513    }
514
515  fd = newslot ();
516
517  if (fd == -1)
518    {
519      errno = EMFILE;
520      return -1;
521    }
522
523  /* It is an error to open a file that already exists. */
524  if ((flags & O_CREAT) && (flags & O_EXCL))
525    {
526      struct stat st;
527      int res;
528      res = _stat (path, &st);
529      if (res != -1)
530        {
531          errno = EEXIST;
532          return -1;
533        }
534    }
535
536  /* The flags are Unix-style, so we need to convert them. */
537#ifdef O_BINARY
538  if (flags & O_BINARY)
539    aflags |= 1;
540#endif
541
542  /* In O_RDONLY we expect aflags == 0. */
543
544  if (flags & O_RDWR)
545    aflags |= 2;
546
547  if ((flags & O_CREAT) || (flags & O_TRUNC) || (flags & O_WRONLY))
548    aflags |= 4;
549
550  if (flags & O_APPEND)
551    {
552      /* Can't ask for w AND a; means just 'a'.  */
553      aflags &= ~4;
554      aflags |= 8;
555    }
556
557  block[0] = POINTER_TO_PARAM_BLOCK_T (path);
558  block[2] = strlen (path);
559  block[1] = aflags;
560
561  fh = do_AngelSVC (AngelSVC_Reason_Open, block);
562
563  /* Return a user file descriptor or an error. */
564  if (fh >= 0)
565    {
566      openfiles[fd].handle = fh;
567      openfiles[fd].flags = flags + 1;
568      openfiles[fd].ino = ino++;
569      openfiles[fd].pos = 0;
570      return fd;
571    }
572  else
573    return error (fh);
574}
575
576int
577_open (const char *path, int flags, ...)
578{
579  return _swiopen (path, flags);
580}
581
582/* fh, is a valid internal file handle. */
583int
584_swiclose (int fh)
585{
586  param_block_t param[1];
587  param[0] = fh;
588  return checkerror (do_AngelSVC (AngelSVC_Reason_Close, param));
589}
590
591/* fd, is a user file descriptor. */
592int
593_close (int fd)
594{
595  int res;
596  struct fdent *pfd;
597
598  pfd = findslot (fd);
599  if (pfd == NULL)
600    {
601      errno = EBADF;
602      return -1;
603    }
604
605  /* Handle stderr == stdout. */
606  if ((fd == 1 || fd == 2) && (openfiles[1].handle == openfiles[2].handle))
607    {
608      pfd->handle = -1;
609      return 0;
610    }
611
612  /* Attempt to close the handle. */
613  res = _swiclose (pfd->handle);
614
615  /* Reclaim handle? */
616  if (res == 0)
617    pfd->handle = -1;
618
619  return res;
620}
621
622int __attribute__((weak))
623_getpid (int n __attribute__ ((unused)))
624{
625  return 1;
626}
627
628/* Heap limit returned from SYS_HEAPINFO Angel semihost call.  */
629ulong __heap_limit __attribute__ ((aligned (8))) = 0xcafedead;
630
631caddr_t
632_sbrk (int incr)
633{
634  extern char end asm ("end");  /* Defined by the linker.  */
635  static char *heap_end;
636  char *prev_heap_end;
637
638  if (heap_end == NULL)
639    heap_end = &end;
640
641  prev_heap_end = heap_end;
642
643  if ((heap_end + incr > stack_ptr)
644      /* Honour heap limit if it's valid.  */
645      || ((__heap_limit != 0xcafedead) && (heap_end + incr > __heap_limit)))
646    {
647      /* Some of the libstdc++-v3 tests rely upon detecting
648         out of memory errors, so do not abort here.  */
649      errno = ENOMEM;
650      return (caddr_t) - 1;
651    }
652
653  heap_end += incr;
654
655  return (caddr_t) prev_heap_end;
656}
657
658int
659_swistat (int fd, struct stat *st)
660{
661  struct fdent *pfd;
662  param_block_t param[1];
663  int res;
664
665  pfd = findslot (fd);
666  if (pfd == NULL)
667    {
668      errno = EBADF;
669      return -1;
670    }
671
672  param[0] = pfd->handle;
673  res = do_AngelSVC (AngelSVC_Reason_IsTTY, param);
674  if (res != 0 && res != 1)
675    return error (-1);
676
677  memset (st, 0, sizeof (*st));
678
679  if (res)
680    {
681      /* This is a tty. */
682      st->st_mode |= S_IFCHR;
683    }
684  else
685    {
686      /* This is a file, return the file length.  */
687      st->st_mode |= S_IFREG;
688      res = checkerror (do_AngelSVC (AngelSVC_Reason_FLen, param));
689      if (res == -1)
690        return -1;
691      st->st_size = res;
692      st->st_blksize = 1024;
693      st->st_blocks = (res + 1023) / 1024;
694    }
695
696  /* Deduce permissions based on mode in which file opened.  */
697  st->st_mode |= S_IRUSR | S_IRGRP | S_IROTH;
698  if (pfd->flags & _FWRITE)
699    st->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
700
701  st->st_ino = pfd->ino;
702  st->st_nlink = 1;
703  return 0;
704}
705
706int __attribute__((weak))
707_fstat (int fd, struct stat * st)
708{
709  return _swistat (fd, st);
710}
711
712int __attribute__((weak))
713_stat (const char *fname, struct stat *st)
714{
715  int fd, res;
716  /* The best we can do is try to open the file readonly.
717     If it exists, then we can guess a few things about it. */
718  if ((fd = _open (fname, O_RDONLY)) == -1)
719    return -1;
720  res = _swistat (fd, st);
721  /* Not interested in the error. */
722  _close (fd);
723  return res;
724}
725
726int __attribute__((weak))
727_link (void)
728{
729  errno = ENOSYS;
730  return -1;
731}
732
733int
734_unlink (const char *path)
735{
736  int res;
737  param_block_t block[2];
738  block[0] = POINTER_TO_PARAM_BLOCK_T (path);
739  block[1] = strlen (path);
740  res = do_AngelSVC (AngelSVC_Reason_Remove, block);
741  if (res == -1)
742    return error (res);
743  return 0;
744}
745
746int
747_gettimeofday (struct timeval *tp, void *tzvp)
748{
749  struct timezone *tzp = tzvp;
750  if (tp)
751    {
752      /* Ask the host for the seconds since the Unix epoch.  */
753      tp->tv_sec = do_AngelSVC (AngelSVC_Reason_Time, NULL);
754      tp->tv_usec = 0;
755    }
756
757  /* Return fixed data for the timezone.  */
758  if (tzp)
759    {
760      tzp->tz_minuteswest = 0;
761      tzp->tz_dsttime = 0;
762    }
763
764  return 0;
765}
766
767/* Return a clock that ticks at 100Hz.  */
768clock_t
769_clock (void)
770{
771  clock_t timeval;
772
773  timeval = do_AngelSVC (AngelSVC_Reason_Clock, NULL);
774  return timeval;
775}
776
777/* Return a clock that ticks at 100Hz.  */
778clock_t
779_times (struct tms * tp)
780{
781  clock_t timeval = _clock ();
782
783  if (tp)
784    {
785      tp->tms_utime = timeval;  /* user time */
786      tp->tms_stime = 0;        /* system time */
787      tp->tms_cutime = 0;       /* user time, children */
788      tp->tms_cstime = 0;       /* system time, children */
789    }
790
791  return timeval;
792};
793
794
795int
796_isatty (int fd)
797{
798  struct fdent *pfd;
799  param_block_t param[1];
800  int res;
801
802  /* Return 1 if fd is an open file descriptor referring to a terminal;
803     otherwise 0 is returned, and errno is set to indicate the error.  */
804
805  pfd = findslot (fd);
806  if (pfd == NULL)
807    {
808      errno = EBADF;
809      return 0;
810    }
811
812  param[0] = pfd->handle;
813  res = do_AngelSVC (AngelSVC_Reason_IsTTY, param);
814
815  if (res != 1)
816    return error (0);
817  return res;
818}
819
820int
821_system (const char *s)
822{
823  param_block_t block[2];
824  int e;
825
826  /* Hmmm.  The ARM debug interface specification doesn't say whether
827     SYS_SYSTEM does the right thing with a null argument, or assign any
828     meaning to its return value.  Try to do something reasonable....  */
829  if (!s)
830    return 1;                   /* maybe there is a shell available? we can hope. :-P */
831  block[0] = POINTER_TO_PARAM_BLOCK_T (s);
832  block[1] = strlen (s);
833  e = checkerror (do_AngelSVC (AngelSVC_Reason_System, block));
834  if ((e >= 0) && (e < 256))
835    {
836      /* We have to convert e, an exit status to the encoded status of
837         the command.  To avoid hard coding the exit status, we simply
838         loop until we find the right position.  */
839      int exit_code;
840
841      for (exit_code = e; e && WEXITSTATUS (e) != exit_code; e <<= 1)
842        continue;
843    }
844  return e;
845}
846
847int
848_rename (const char *oldpath, const char *newpath)
849{
850  param_block_t block[4];
851  block[0] = POINTER_TO_PARAM_BLOCK_T (oldpath);
852  block[1] = strlen (oldpath);
853  block[2] = POINTER_TO_PARAM_BLOCK_T (newpath);
854  block[3] = strlen (newpath);
855  return checkerror (do_AngelSVC (AngelSVC_Reason_Rename, block)) ? -1 : 0;
856}
857
858/* Returns the number of elapsed target ticks since the support code
859   started execution. Returns -1 and sets errno on error.  */
860long
861__aarch64_angel_elapsed (void)
862{
863  int result;
864  param_block_t block[2];
865  result = checkerror (do_AngelSVC (AngelSVC_Reason_Elapsed, block));
866  if (result == -1)
867    return result;
868  return block[0];
869}
Note: See TracBrowser for help on using the repository browser.