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