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