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