xref: /illumos-kvm-cmd/arm-semi.c (revision 68396ea9)
1 /*
2  *  Arm "Angel" semihosting syscalls
3  *
4  *  Copyright (c) 2005, 2007 CodeSourcery.
5  *  Written by Paul Brook.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program 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
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <time.h>
28 
29 #include "cpu.h"
30 #ifdef CONFIG_USER_ONLY
31 #include "qemu.h"
32 
33 #define ARM_ANGEL_HEAP_SIZE (128 * 1024 * 1024)
34 #else
35 #include "qemu-common.h"
36 #include "sysemu.h"
37 #include "gdbstub.h"
38 #endif
39 
40 #define SYS_OPEN        0x01
41 #define SYS_CLOSE       0x02
42 #define SYS_WRITEC      0x03
43 #define SYS_WRITE0      0x04
44 #define SYS_WRITE       0x05
45 #define SYS_READ        0x06
46 #define SYS_READC       0x07
47 #define SYS_ISTTY       0x09
48 #define SYS_SEEK        0x0a
49 #define SYS_FLEN        0x0c
50 #define SYS_TMPNAM      0x0d
51 #define SYS_REMOVE      0x0e
52 #define SYS_RENAME      0x0f
53 #define SYS_CLOCK       0x10
54 #define SYS_TIME        0x11
55 #define SYS_SYSTEM      0x12
56 #define SYS_ERRNO       0x13
57 #define SYS_GET_CMDLINE 0x15
58 #define SYS_HEAPINFO    0x16
59 #define SYS_EXIT        0x18
60 
61 #ifndef O_BINARY
62 #define O_BINARY 0
63 #endif
64 
65 #define GDB_O_RDONLY  0x000
66 #define GDB_O_WRONLY  0x001
67 #define GDB_O_RDWR    0x002
68 #define GDB_O_APPEND  0x008
69 #define GDB_O_CREAT   0x200
70 #define GDB_O_TRUNC   0x400
71 #define GDB_O_BINARY  0
72 
73 static int gdb_open_modeflags[12] = {
74     GDB_O_RDONLY,
75     GDB_O_RDONLY | GDB_O_BINARY,
76     GDB_O_RDWR,
77     GDB_O_RDWR | GDB_O_BINARY,
78     GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC,
79     GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY,
80     GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC,
81     GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY,
82     GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND,
83     GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY,
84     GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND,
85     GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY
86 };
87 
88 static int open_modeflags[12] = {
89     O_RDONLY,
90     O_RDONLY | O_BINARY,
91     O_RDWR,
92     O_RDWR | O_BINARY,
93     O_WRONLY | O_CREAT | O_TRUNC,
94     O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
95     O_RDWR | O_CREAT | O_TRUNC,
96     O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
97     O_WRONLY | O_CREAT | O_APPEND,
98     O_WRONLY | O_CREAT | O_APPEND | O_BINARY,
99     O_RDWR | O_CREAT | O_APPEND,
100     O_RDWR | O_CREAT | O_APPEND | O_BINARY
101 };
102 
103 #ifdef CONFIG_USER_ONLY
set_swi_errno(TaskState * ts,uint32_t code)104 static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code)
105 {
106     if (code == (uint32_t)-1)
107         ts->swi_errno = errno;
108     return code;
109 }
110 #else
set_swi_errno(CPUState * env,uint32_t code)111 static inline uint32_t set_swi_errno(CPUState *env, uint32_t code)
112 {
113     return code;
114 }
115 
116 #include "softmmu-semi.h"
117 #endif
118 
119 static target_ulong arm_semi_syscall_len;
120 
121 #if !defined(CONFIG_USER_ONLY)
122 static target_ulong syscall_err;
123 #endif
124 
arm_semi_cb(CPUState * env,target_ulong ret,target_ulong err)125 static void arm_semi_cb(CPUState *env, target_ulong ret, target_ulong err)
126 {
127 #ifdef CONFIG_USER_ONLY
128     TaskState *ts = env->opaque;
129 #endif
130 
131     if (ret == (target_ulong)-1) {
132 #ifdef CONFIG_USER_ONLY
133         ts->swi_errno = err;
134 #else
135 	syscall_err = err;
136 #endif
137         env->regs[0] = ret;
138     } else {
139         /* Fixup syscalls that use nonstardard return conventions.  */
140         switch (env->regs[0]) {
141         case SYS_WRITE:
142         case SYS_READ:
143             env->regs[0] = arm_semi_syscall_len - ret;
144             break;
145         case SYS_SEEK:
146             env->regs[0] = 0;
147             break;
148         default:
149             env->regs[0] = ret;
150             break;
151         }
152     }
153 }
154 
arm_semi_flen_cb(CPUState * env,target_ulong ret,target_ulong err)155 static void arm_semi_flen_cb(CPUState *env, target_ulong ret, target_ulong err)
156 {
157     /* The size is always stored in big-endian order, extract
158        the value. We assume the size always fit in 32 bits.  */
159     uint32_t size;
160     cpu_memory_rw_debug(env, env->regs[13]-64+32, (uint8_t *)&size, 4, 0);
161     env->regs[0] = be32_to_cpu(size);
162 #ifdef CONFIG_USER_ONLY
163     ((TaskState *)env->opaque)->swi_errno = err;
164 #else
165     syscall_err = err;
166 #endif
167 }
168 
169 #define ARG(n)					\
170 ({						\
171     target_ulong __arg;				\
172     /* FIXME - handle get_user() failure */	\
173     get_user_ual(__arg, args + (n) * 4);	\
174     __arg;					\
175 })
176 #define SET_ARG(n, val) put_user_ual(val, args + (n) * 4)
do_arm_semihosting(CPUState * env)177 uint32_t do_arm_semihosting(CPUState *env)
178 {
179     target_ulong args;
180     char * s;
181     int nr;
182     uint32_t ret;
183     uint32_t len;
184 #ifdef CONFIG_USER_ONLY
185     TaskState *ts = env->opaque;
186 #else
187     CPUState *ts = env;
188 #endif
189 
190     nr = env->regs[0];
191     args = env->regs[1];
192     switch (nr) {
193     case SYS_OPEN:
194         if (!(s = lock_user_string(ARG(0))))
195             /* FIXME - should this error code be -TARGET_EFAULT ? */
196             return (uint32_t)-1;
197         if (ARG(1) >= 12)
198             return (uint32_t)-1;
199         if (strcmp(s, ":tt") == 0) {
200             if (ARG(1) < 4)
201                 return STDIN_FILENO;
202             else
203                 return STDOUT_FILENO;
204         }
205         if (use_gdb_syscalls()) {
206             gdb_do_syscall(arm_semi_cb, "open,%s,%x,1a4", ARG(0),
207 			   (int)ARG(2)+1, gdb_open_modeflags[ARG(1)]);
208             return env->regs[0];
209         } else {
210             ret = set_swi_errno(ts, open(s, open_modeflags[ARG(1)], 0644));
211         }
212         unlock_user(s, ARG(0), 0);
213         return ret;
214     case SYS_CLOSE:
215         if (use_gdb_syscalls()) {
216             gdb_do_syscall(arm_semi_cb, "close,%x", ARG(0));
217             return env->regs[0];
218         } else {
219             return set_swi_errno(ts, close(ARG(0)));
220         }
221     case SYS_WRITEC:
222         {
223           char c;
224 
225           if (get_user_u8(c, args))
226               /* FIXME - should this error code be -TARGET_EFAULT ? */
227               return (uint32_t)-1;
228           /* Write to debug console.  stderr is near enough.  */
229           if (use_gdb_syscalls()) {
230                 gdb_do_syscall(arm_semi_cb, "write,2,%x,1", args);
231                 return env->regs[0];
232           } else {
233                 return write(STDERR_FILENO, &c, 1);
234           }
235         }
236     case SYS_WRITE0:
237         if (!(s = lock_user_string(args)))
238             /* FIXME - should this error code be -TARGET_EFAULT ? */
239             return (uint32_t)-1;
240         len = strlen(s);
241         if (use_gdb_syscalls()) {
242             gdb_do_syscall(arm_semi_cb, "write,2,%x,%x\n", args, len);
243             ret = env->regs[0];
244         } else {
245             ret = write(STDERR_FILENO, s, len);
246         }
247         unlock_user(s, args, 0);
248         return ret;
249     case SYS_WRITE:
250         len = ARG(2);
251         if (use_gdb_syscalls()) {
252             arm_semi_syscall_len = len;
253             gdb_do_syscall(arm_semi_cb, "write,%x,%x,%x", ARG(0), ARG(1), len);
254             return env->regs[0];
255         } else {
256             if (!(s = lock_user(VERIFY_READ, ARG(1), len, 1)))
257                 /* FIXME - should this error code be -TARGET_EFAULT ? */
258                 return (uint32_t)-1;
259             ret = set_swi_errno(ts, write(ARG(0), s, len));
260             unlock_user(s, ARG(1), 0);
261             if (ret == (uint32_t)-1)
262                 return -1;
263             return len - ret;
264         }
265     case SYS_READ:
266         len = ARG(2);
267         if (use_gdb_syscalls()) {
268             arm_semi_syscall_len = len;
269             gdb_do_syscall(arm_semi_cb, "read,%x,%x,%x", ARG(0), ARG(1), len);
270             return env->regs[0];
271         } else {
272             if (!(s = lock_user(VERIFY_WRITE, ARG(1), len, 0)))
273                 /* FIXME - should this error code be -TARGET_EFAULT ? */
274                 return (uint32_t)-1;
275             do
276               ret = set_swi_errno(ts, read(ARG(0), s, len));
277             while (ret == -1 && errno == EINTR);
278             unlock_user(s, ARG(1), len);
279             if (ret == (uint32_t)-1)
280                 return -1;
281             return len - ret;
282         }
283     case SYS_READC:
284        /* XXX: Read from debug cosole. Not implemented.  */
285         return 0;
286     case SYS_ISTTY:
287         if (use_gdb_syscalls()) {
288             gdb_do_syscall(arm_semi_cb, "isatty,%x", ARG(0));
289             return env->regs[0];
290         } else {
291             return isatty(ARG(0));
292         }
293     case SYS_SEEK:
294         if (use_gdb_syscalls()) {
295             gdb_do_syscall(arm_semi_cb, "lseek,%x,%x,0", ARG(0), ARG(1));
296             return env->regs[0];
297         } else {
298             ret = set_swi_errno(ts, lseek(ARG(0), ARG(1), SEEK_SET));
299             if (ret == (uint32_t)-1)
300               return -1;
301             return 0;
302         }
303     case SYS_FLEN:
304         if (use_gdb_syscalls()) {
305             gdb_do_syscall(arm_semi_flen_cb, "fstat,%x,%x",
306 			   ARG(0), env->regs[13]-64);
307             return env->regs[0];
308         } else {
309             struct stat buf;
310             ret = set_swi_errno(ts, fstat(ARG(0), &buf));
311             if (ret == (uint32_t)-1)
312                 return -1;
313             return buf.st_size;
314         }
315     case SYS_TMPNAM:
316         /* XXX: Not implemented.  */
317         return -1;
318     case SYS_REMOVE:
319         if (use_gdb_syscalls()) {
320             gdb_do_syscall(arm_semi_cb, "unlink,%s", ARG(0), (int)ARG(1)+1);
321             ret = env->regs[0];
322         } else {
323             if (!(s = lock_user_string(ARG(0))))
324                 /* FIXME - should this error code be -TARGET_EFAULT ? */
325                 return (uint32_t)-1;
326             ret =  set_swi_errno(ts, remove(s));
327             unlock_user(s, ARG(0), 0);
328         }
329         return ret;
330     case SYS_RENAME:
331         if (use_gdb_syscalls()) {
332             gdb_do_syscall(arm_semi_cb, "rename,%s,%s",
333                            ARG(0), (int)ARG(1)+1, ARG(2), (int)ARG(3)+1);
334             return env->regs[0];
335         } else {
336             char *s2;
337             s = lock_user_string(ARG(0));
338             s2 = lock_user_string(ARG(2));
339             if (!s || !s2)
340                 /* FIXME - should this error code be -TARGET_EFAULT ? */
341                 ret = (uint32_t)-1;
342             else
343                 ret = set_swi_errno(ts, rename(s, s2));
344             if (s2)
345                 unlock_user(s2, ARG(2), 0);
346             if (s)
347                 unlock_user(s, ARG(0), 0);
348             return ret;
349         }
350     case SYS_CLOCK:
351         return clock() / (CLOCKS_PER_SEC / 100);
352     case SYS_TIME:
353         return set_swi_errno(ts, time(NULL));
354     case SYS_SYSTEM:
355         if (use_gdb_syscalls()) {
356             gdb_do_syscall(arm_semi_cb, "system,%s", ARG(0), (int)ARG(1)+1);
357             return env->regs[0];
358         } else {
359             if (!(s = lock_user_string(ARG(0))))
360                 /* FIXME - should this error code be -TARGET_EFAULT ? */
361                 return (uint32_t)-1;
362             ret = set_swi_errno(ts, system(s));
363             unlock_user(s, ARG(0), 0);
364             return ret;
365         }
366     case SYS_ERRNO:
367 #ifdef CONFIG_USER_ONLY
368         return ts->swi_errno;
369 #else
370         return syscall_err;
371 #endif
372     case SYS_GET_CMDLINE:
373 #ifdef CONFIG_USER_ONLY
374         /* Build a commandline from the original argv.  */
375         {
376             char *arm_cmdline_buffer;
377             const char *host_cmdline_buffer;
378 
379             unsigned int i;
380             unsigned int arm_cmdline_len = ARG(1);
381             unsigned int host_cmdline_len =
382                 ts->info->arg_end-ts->info->arg_start;
383 
384             if (!arm_cmdline_len || host_cmdline_len > arm_cmdline_len) {
385                 return -1; /* not enough space to store command line */
386             }
387 
388             if (!host_cmdline_len) {
389                 /* We special-case the "empty command line" case (argc==0).
390                    Just provide the terminating 0. */
391                 arm_cmdline_buffer = lock_user(VERIFY_WRITE, ARG(0), 1, 0);
392                 arm_cmdline_buffer[0] = 0;
393                 unlock_user(arm_cmdline_buffer, ARG(0), 1);
394 
395                 /* Adjust the commandline length argument. */
396                 SET_ARG(1, 0);
397                 return 0;
398             }
399 
400             /* lock the buffers on the ARM side */
401             arm_cmdline_buffer =
402                 lock_user(VERIFY_WRITE, ARG(0), host_cmdline_len, 0);
403             host_cmdline_buffer =
404                 lock_user(VERIFY_READ, ts->info->arg_start,
405                                        host_cmdline_len, 1);
406 
407             if (arm_cmdline_buffer && host_cmdline_buffer)
408             {
409                 /* the last argument is zero-terminated;
410                    no need for additional termination */
411                 memcpy(arm_cmdline_buffer, host_cmdline_buffer,
412                        host_cmdline_len);
413 
414                 /* separate arguments by white spaces */
415                 for (i = 0; i < host_cmdline_len-1; i++) {
416                     if (arm_cmdline_buffer[i] == 0) {
417                         arm_cmdline_buffer[i] = ' ';
418                     }
419                 }
420 
421                 /* Adjust the commandline length argument. */
422                 SET_ARG(1, host_cmdline_len-1);
423             }
424 
425             /* Unlock the buffers on the ARM side.  */
426             unlock_user(arm_cmdline_buffer, ARG(0), host_cmdline_len);
427             unlock_user((void*)host_cmdline_buffer, ts->info->arg_start, 0);
428 
429             /* Return success if we could return a commandline.  */
430             return (arm_cmdline_buffer && host_cmdline_buffer) ? 0 : -1;
431         }
432 #else
433         return -1;
434 #endif
435     case SYS_HEAPINFO:
436         {
437             uint32_t *ptr;
438             uint32_t limit;
439 
440 #ifdef CONFIG_USER_ONLY
441             /* Some C libraries assume the heap immediately follows .bss, so
442                allocate it using sbrk.  */
443             if (!ts->heap_limit) {
444                 long ret;
445 
446                 ts->heap_base = do_brk(0);
447                 limit = ts->heap_base + ARM_ANGEL_HEAP_SIZE;
448                 /* Try a big heap, and reduce the size if that fails.  */
449                 for (;;) {
450                     ret = do_brk(limit);
451                     if (ret != -1)
452                         break;
453                     limit = (ts->heap_base >> 1) + (limit >> 1);
454                 }
455                 ts->heap_limit = limit;
456             }
457 
458             if (!(ptr = lock_user(VERIFY_WRITE, ARG(0), 16, 0)))
459                 /* FIXME - should this error code be -TARGET_EFAULT ? */
460                 return (uint32_t)-1;
461             ptr[0] = tswap32(ts->heap_base);
462             ptr[1] = tswap32(ts->heap_limit);
463             ptr[2] = tswap32(ts->stack_base);
464             ptr[3] = tswap32(0); /* Stack limit.  */
465             unlock_user(ptr, ARG(0), 16);
466 #else
467             limit = ram_size;
468             if (!(ptr = lock_user(VERIFY_WRITE, ARG(0), 16, 0)))
469                 /* FIXME - should this error code be -TARGET_EFAULT ? */
470                 return (uint32_t)-1;
471             /* TODO: Make this use the limit of the loaded application.  */
472             ptr[0] = tswap32(limit / 2);
473             ptr[1] = tswap32(limit);
474             ptr[2] = tswap32(limit); /* Stack base */
475             ptr[3] = tswap32(0); /* Stack limit.  */
476             unlock_user(ptr, ARG(0), 16);
477 #endif
478             return 0;
479         }
480     case SYS_EXIT:
481         gdb_exit(env, 0);
482         exit(0);
483     default:
484         fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr);
485         cpu_dump_state(env, stderr, fprintf, 0);
486         abort();
487     }
488 }
489