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