Add qemu 2.4.0
[kvmfornfv.git] / qemu / target-m68k / m68k-semi.c
1 /*
2  *  m68k/ColdFire Semihosting syscall interface
3  *
4  *  Copyright (c) 2005-2007 CodeSourcery.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <sys/time.h>
28 #include <time.h>
29
30 #include "cpu.h"
31 #if defined(CONFIG_USER_ONLY)
32 #include "qemu.h"
33 #define SEMIHOSTING_HEAP_SIZE (128 * 1024 * 1024)
34 #else
35 #include "qemu-common.h"
36 #include "exec/gdbstub.h"
37 #include "exec/softmmu-semi.h"
38 #endif
39 #include "sysemu/sysemu.h"
40
41 #define HOSTED_EXIT  0
42 #define HOSTED_INIT_SIM 1
43 #define HOSTED_OPEN 2
44 #define HOSTED_CLOSE 3
45 #define HOSTED_READ 4
46 #define HOSTED_WRITE 5
47 #define HOSTED_LSEEK 6
48 #define HOSTED_RENAME 7
49 #define HOSTED_UNLINK 8
50 #define HOSTED_STAT 9
51 #define HOSTED_FSTAT 10
52 #define HOSTED_GETTIMEOFDAY 11
53 #define HOSTED_ISATTY 12
54 #define HOSTED_SYSTEM 13
55
56 typedef uint32_t gdb_mode_t;
57 typedef uint32_t gdb_time_t;
58
59 struct m68k_gdb_stat {
60   uint32_t    gdb_st_dev;     /* device */
61   uint32_t    gdb_st_ino;     /* inode */
62   gdb_mode_t  gdb_st_mode;    /* protection */
63   uint32_t    gdb_st_nlink;   /* number of hard links */
64   uint32_t    gdb_st_uid;     /* user ID of owner */
65   uint32_t    gdb_st_gid;     /* group ID of owner */
66   uint32_t    gdb_st_rdev;    /* device type (if inode device) */
67   uint64_t    gdb_st_size;    /* total size, in bytes */
68   uint64_t    gdb_st_blksize; /* blocksize for filesystem I/O */
69   uint64_t    gdb_st_blocks;  /* number of blocks allocated */
70   gdb_time_t  gdb_st_atime;   /* time of last access */
71   gdb_time_t  gdb_st_mtime;   /* time of last modification */
72   gdb_time_t  gdb_st_ctime;   /* time of last change */
73 } QEMU_PACKED;
74
75 struct gdb_timeval {
76   gdb_time_t tv_sec;  /* second */
77   uint64_t tv_usec;   /* microsecond */
78 } QEMU_PACKED;
79
80 #define GDB_O_RDONLY   0x0
81 #define GDB_O_WRONLY   0x1
82 #define GDB_O_RDWR     0x2
83 #define GDB_O_APPEND   0x8
84 #define GDB_O_CREAT  0x200
85 #define GDB_O_TRUNC  0x400
86 #define GDB_O_EXCL   0x800
87
88 static int translate_openflags(int flags)
89 {
90     int hf;
91
92     if (flags & GDB_O_WRONLY)
93         hf = O_WRONLY;
94     else if (flags & GDB_O_RDWR)
95         hf = O_RDWR;
96     else
97         hf = O_RDONLY;
98
99     if (flags & GDB_O_APPEND) hf |= O_APPEND;
100     if (flags & GDB_O_CREAT) hf |= O_CREAT;
101     if (flags & GDB_O_TRUNC) hf |= O_TRUNC;
102     if (flags & GDB_O_EXCL) hf |= O_EXCL;
103
104     return hf;
105 }
106
107 static void translate_stat(CPUM68KState *env, target_ulong addr, struct stat *s)
108 {
109     struct m68k_gdb_stat *p;
110
111     if (!(p = lock_user(VERIFY_WRITE, addr, sizeof(struct m68k_gdb_stat), 0)))
112         /* FIXME - should this return an error code? */
113         return;
114     p->gdb_st_dev = cpu_to_be32(s->st_dev);
115     p->gdb_st_ino = cpu_to_be32(s->st_ino);
116     p->gdb_st_mode = cpu_to_be32(s->st_mode);
117     p->gdb_st_nlink = cpu_to_be32(s->st_nlink);
118     p->gdb_st_uid = cpu_to_be32(s->st_uid);
119     p->gdb_st_gid = cpu_to_be32(s->st_gid);
120     p->gdb_st_rdev = cpu_to_be32(s->st_rdev);
121     p->gdb_st_size = cpu_to_be64(s->st_size);
122 #ifdef _WIN32
123     /* Windows stat is missing some fields.  */
124     p->gdb_st_blksize = 0;
125     p->gdb_st_blocks = 0;
126 #else
127     p->gdb_st_blksize = cpu_to_be64(s->st_blksize);
128     p->gdb_st_blocks = cpu_to_be64(s->st_blocks);
129 #endif
130     p->gdb_st_atime = cpu_to_be32(s->st_atime);
131     p->gdb_st_mtime = cpu_to_be32(s->st_mtime);
132     p->gdb_st_ctime = cpu_to_be32(s->st_ctime);
133     unlock_user(p, addr, sizeof(struct m68k_gdb_stat));
134 }
135
136 static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, uint32_t err)
137 {
138     target_ulong args = env->dregs[1];
139     if (put_user_u32(ret, args) ||
140         put_user_u32(err, args + 4)) {
141         /* The m68k semihosting ABI does not provide any way to report this
142          * error to the guest, so the best we can do is log it in qemu.
143          * It is always a guest error not to pass us a valid argument block.
144          */
145         qemu_log_mask(LOG_GUEST_ERROR, "m68k-semihosting: return value "
146                       "discarded because argument block not writable\n");
147     }
148 }
149
150 static void m68k_semi_return_u64(CPUM68KState *env, uint64_t ret, uint32_t err)
151 {
152     target_ulong args = env->dregs[1];
153     if (put_user_u32(ret >> 32, args) ||
154         put_user_u32(ret, args + 4) ||
155         put_user_u32(err, args + 8)) {
156         /* No way to report this via m68k semihosting ABI; just log it */
157         qemu_log_mask(LOG_GUEST_ERROR, "m68k-semihosting: return value "
158                       "discarded because argument block not writable\n");
159     }
160 }
161
162 static int m68k_semi_is_fseek;
163
164 static void m68k_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
165 {
166     M68kCPU *cpu = M68K_CPU(cs);
167     CPUM68KState *env = &cpu->env;
168
169     if (m68k_semi_is_fseek) {
170         /* FIXME: We've already lost the high bits of the fseek
171            return value.  */
172         m68k_semi_return_u64(env, ret, err);
173         m68k_semi_is_fseek = 0;
174     } else {
175         m68k_semi_return_u32(env, ret, err);
176     }
177 }
178
179 /* Read the input value from the argument block; fail the semihosting
180  * call if the memory read fails.
181  */
182 #define GET_ARG(n) do {                                 \
183     if (get_user_ual(arg ## n, args + (n) * 4)) {       \
184         result = -1;                                    \
185         errno = EFAULT;                                 \
186         goto failed;                                    \
187     }                                                   \
188 } while (0)
189
190 void do_m68k_semihosting(CPUM68KState *env, int nr)
191 {
192     uint32_t args;
193     target_ulong arg0, arg1, arg2, arg3;
194     void *p;
195     void *q;
196     uint32_t len;
197     uint32_t result;
198
199     args = env->dregs[1];
200     switch (nr) {
201     case HOSTED_EXIT:
202         gdb_exit(env, env->dregs[0]);
203         exit(env->dregs[0]);
204     case HOSTED_OPEN:
205         GET_ARG(0);
206         GET_ARG(1);
207         GET_ARG(2);
208         GET_ARG(3);
209         if (use_gdb_syscalls()) {
210             gdb_do_syscall(m68k_semi_cb, "open,%s,%x,%x", arg0, (int)arg1,
211                            arg2, arg3);
212             return;
213         } else {
214             p = lock_user_string(arg0);
215             if (!p) {
216                 /* FIXME - check error code? */
217                 result = -1;
218             } else {
219                 result = open(p, translate_openflags(arg2), arg3);
220                 unlock_user(p, arg0, 0);
221             }
222         }
223         break;
224     case HOSTED_CLOSE:
225         {
226             /* Ignore attempts to close stdin/out/err.  */
227             GET_ARG(0);
228             int fd = arg0;
229             if (fd > 2) {
230                 if (use_gdb_syscalls()) {
231                     gdb_do_syscall(m68k_semi_cb, "close,%x", arg0);
232                     return;
233                 } else {
234                     result = close(fd);
235                 }
236             } else {
237                 result = 0;
238             }
239             break;
240         }
241     case HOSTED_READ:
242         GET_ARG(0);
243         GET_ARG(1);
244         GET_ARG(2);
245         len = arg2;
246         if (use_gdb_syscalls()) {
247             gdb_do_syscall(m68k_semi_cb, "read,%x,%x,%x",
248                            arg0, arg1, len);
249             return;
250         } else {
251             p = lock_user(VERIFY_WRITE, arg1, len, 0);
252             if (!p) {
253                 /* FIXME - check error code? */
254                 result = -1;
255             } else {
256                 result = read(arg0, p, len);
257                 unlock_user(p, arg1, len);
258             }
259         }
260         break;
261     case HOSTED_WRITE:
262         GET_ARG(0);
263         GET_ARG(1);
264         GET_ARG(2);
265         len = arg2;
266         if (use_gdb_syscalls()) {
267             gdb_do_syscall(m68k_semi_cb, "write,%x,%x,%x",
268                            arg0, arg1, len);
269             return;
270         } else {
271             p = lock_user(VERIFY_READ, arg1, len, 1);
272             if (!p) {
273                 /* FIXME - check error code? */
274                 result = -1;
275             } else {
276                 result = write(arg0, p, len);
277                 unlock_user(p, arg0, 0);
278             }
279         }
280         break;
281     case HOSTED_LSEEK:
282         {
283             uint64_t off;
284             GET_ARG(0);
285             GET_ARG(1);
286             GET_ARG(2);
287             GET_ARG(3);
288             off = (uint32_t)arg2 | ((uint64_t)arg1 << 32);
289             if (use_gdb_syscalls()) {
290                 m68k_semi_is_fseek = 1;
291                 gdb_do_syscall(m68k_semi_cb, "fseek,%x,%lx,%x",
292                                arg0, off, arg3);
293             } else {
294                 off = lseek(arg0, off, arg3);
295                 m68k_semi_return_u64(env, off, errno);
296             }
297             return;
298         }
299     case HOSTED_RENAME:
300         GET_ARG(0);
301         GET_ARG(1);
302         GET_ARG(2);
303         GET_ARG(3);
304         if (use_gdb_syscalls()) {
305             gdb_do_syscall(m68k_semi_cb, "rename,%s,%s",
306                            arg0, (int)arg1, arg2, (int)arg3);
307             return;
308         } else {
309             p = lock_user_string(arg0);
310             q = lock_user_string(arg2);
311             if (!p || !q) {
312                 /* FIXME - check error code? */
313                 result = -1;
314             } else {
315                 result = rename(p, q);
316             }
317             unlock_user(p, arg0, 0);
318             unlock_user(q, arg2, 0);
319         }
320         break;
321     case HOSTED_UNLINK:
322         GET_ARG(0);
323         GET_ARG(1);
324         if (use_gdb_syscalls()) {
325             gdb_do_syscall(m68k_semi_cb, "unlink,%s",
326                            arg0, (int)arg1);
327             return;
328         } else {
329             p = lock_user_string(arg0);
330             if (!p) {
331                 /* FIXME - check error code? */
332                 result = -1;
333             } else {
334                 result = unlink(p);
335                 unlock_user(p, arg0, 0);
336             }
337         }
338         break;
339     case HOSTED_STAT:
340         GET_ARG(0);
341         GET_ARG(1);
342         GET_ARG(2);
343         if (use_gdb_syscalls()) {
344             gdb_do_syscall(m68k_semi_cb, "stat,%s,%x",
345                            arg0, (int)arg1, arg2);
346             return;
347         } else {
348             struct stat s;
349             p = lock_user_string(arg0);
350             if (!p) {
351                 /* FIXME - check error code? */
352                 result = -1;
353             } else {
354                 result = stat(p, &s);
355                 unlock_user(p, arg0, 0);
356             }
357             if (result == 0) {
358                 translate_stat(env, arg2, &s);
359             }
360         }
361         break;
362     case HOSTED_FSTAT:
363         GET_ARG(0);
364         GET_ARG(1);
365         if (use_gdb_syscalls()) {
366             gdb_do_syscall(m68k_semi_cb, "fstat,%x,%x",
367                            arg0, arg1);
368             return;
369         } else {
370             struct stat s;
371             result = fstat(arg0, &s);
372             if (result == 0) {
373                 translate_stat(env, arg1, &s);
374             }
375         }
376         break;
377     case HOSTED_GETTIMEOFDAY:
378         GET_ARG(0);
379         GET_ARG(1);
380         if (use_gdb_syscalls()) {
381             gdb_do_syscall(m68k_semi_cb, "gettimeofday,%x,%x",
382                            arg0, arg1);
383             return;
384         } else {
385             qemu_timeval tv;
386             struct gdb_timeval *p;
387             result = qemu_gettimeofday(&tv);
388             if (result != 0) {
389                 if (!(p = lock_user(VERIFY_WRITE,
390                                     arg0, sizeof(struct gdb_timeval), 0))) {
391                     /* FIXME - check error code? */
392                     result = -1;
393                 } else {
394                     p->tv_sec = cpu_to_be32(tv.tv_sec);
395                     p->tv_usec = cpu_to_be64(tv.tv_usec);
396                     unlock_user(p, arg0, sizeof(struct gdb_timeval));
397                 }
398             }
399         }
400         break;
401     case HOSTED_ISATTY:
402         GET_ARG(0);
403         if (use_gdb_syscalls()) {
404             gdb_do_syscall(m68k_semi_cb, "isatty,%x", arg0);
405             return;
406         } else {
407             result = isatty(arg0);
408         }
409         break;
410     case HOSTED_SYSTEM:
411         GET_ARG(0);
412         GET_ARG(1);
413         if (use_gdb_syscalls()) {
414             gdb_do_syscall(m68k_semi_cb, "system,%s",
415                            arg0, (int)arg1);
416             return;
417         } else {
418             p = lock_user_string(arg0);
419             if (!p) {
420                 /* FIXME - check error code? */
421                 result = -1;
422             } else {
423                 result = system(p);
424                 unlock_user(p, arg0, 0);
425             }
426         }
427         break;
428     case HOSTED_INIT_SIM:
429 #if defined(CONFIG_USER_ONLY)
430         {
431         CPUState *cs = CPU(m68k_env_get_cpu(env));
432         TaskState *ts = cs->opaque;
433         /* Allocate the heap using sbrk.  */
434         if (!ts->heap_limit) {
435             abi_ulong ret;
436             uint32_t size;
437             uint32_t base;
438
439             base = do_brk(0);
440             size = SEMIHOSTING_HEAP_SIZE;
441             /* Try a big heap, and reduce the size if that fails.  */
442             for (;;) {
443                 ret = do_brk(base + size);
444                 if (ret >= (base + size)) {
445                     break;
446                 }
447                 size >>= 1;
448             }
449             ts->heap_limit = base + size;
450         }
451         /* This call may happen before we have writable memory, so return
452            values directly in registers.  */
453         env->dregs[1] = ts->heap_limit;
454         env->aregs[7] = ts->stack_base;
455         }
456 #else
457         /* FIXME: This is wrong for boards where RAM does not start at
458            address zero.  */
459         env->dregs[1] = ram_size;
460         env->aregs[7] = ram_size;
461 #endif
462         return;
463     default:
464         cpu_abort(CPU(m68k_env_get_cpu(env)), "Unsupported semihosting syscall %d\n", nr);
465         result = 0;
466     }
467 failed:
468     m68k_semi_return_u32(env, result, errno);
469 }