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