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