xref: /qemu/target/m68k/m68k-semi.c (revision 0b8fa32f551e863bb548a11394239239270dd3dc)
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 = env_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(env_cpu(env), "Unsupported semihosting syscall %d\n", nr);
458          result = 0;
459      }
460  failed:
461      m68k_semi_return_u32(env, result, errno);
462  }
463