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