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