arm-semi.c revision 85125480c07e11d5dd98f69b71bded86ee903075
1/* 2 * Arm "Angel" semihosting syscalls 3 * 4 * Copyright (c) 2005, 2007 CodeSourcery. 5 * Written by Paul Brook. 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, see <http://www.gnu.org/licenses/>. 19 */ 20 21#include <sys/types.h> 22#include <sys/stat.h> 23#include <fcntl.h> 24#include <unistd.h> 25#include <stdlib.h> 26#include <stdio.h> 27#include <time.h> 28 29#include "cpu.h" 30#ifdef CONFIG_USER_ONLY 31#include "qemu.h" 32 33#define ARM_ANGEL_HEAP_SIZE (128 * 1024 * 1024) 34#else 35#include "qemu-common.h" 36#include "exec/gdbstub.h" 37#include "hw/arm/arm.h" 38#endif 39 40#define TARGET_SYS_OPEN 0x01 41#define TARGET_SYS_CLOSE 0x02 42#define TARGET_SYS_WRITEC 0x03 43#define TARGET_SYS_WRITE0 0x04 44#define TARGET_SYS_WRITE 0x05 45#define TARGET_SYS_READ 0x06 46#define TARGET_SYS_READC 0x07 47#define TARGET_SYS_ISTTY 0x09 48#define TARGET_SYS_SEEK 0x0a 49#define TARGET_SYS_FLEN 0x0c 50#define TARGET_SYS_TMPNAM 0x0d 51#define TARGET_SYS_REMOVE 0x0e 52#define TARGET_SYS_RENAME 0x0f 53#define TARGET_SYS_CLOCK 0x10 54#define TARGET_SYS_TIME 0x11 55#define TARGET_SYS_SYSTEM 0x12 56#define TARGET_SYS_ERRNO 0x13 57#define TARGET_SYS_GET_CMDLINE 0x15 58#define TARGET_SYS_HEAPINFO 0x16 59#define TARGET_SYS_EXIT 0x18 60 61#ifndef O_BINARY 62#define O_BINARY 0 63#endif 64 65#define GDB_O_RDONLY 0x000 66#define GDB_O_WRONLY 0x001 67#define GDB_O_RDWR 0x002 68#define GDB_O_APPEND 0x008 69#define GDB_O_CREAT 0x200 70#define GDB_O_TRUNC 0x400 71#define GDB_O_BINARY 0 72 73static int gdb_open_modeflags[12] = { 74 GDB_O_RDONLY, 75 GDB_O_RDONLY | GDB_O_BINARY, 76 GDB_O_RDWR, 77 GDB_O_RDWR | GDB_O_BINARY, 78 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, 79 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, 80 GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, 81 GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, 82 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, 83 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, 84 GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, 85 GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY 86}; 87 88static int open_modeflags[12] = { 89 O_RDONLY, 90 O_RDONLY | O_BINARY, 91 O_RDWR, 92 O_RDWR | O_BINARY, 93 O_WRONLY | O_CREAT | O_TRUNC, 94 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 95 O_RDWR | O_CREAT | O_TRUNC, 96 O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 97 O_WRONLY | O_CREAT | O_APPEND, 98 O_WRONLY | O_CREAT | O_APPEND | O_BINARY, 99 O_RDWR | O_CREAT | O_APPEND, 100 O_RDWR | O_CREAT | O_APPEND | O_BINARY 101}; 102 103#ifdef CONFIG_USER_ONLY 104static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code) 105{ 106 if (code == (uint32_t)-1) 107 ts->swi_errno = errno; 108 return code; 109} 110#else 111static inline uint32_t set_swi_errno(CPUARMState *env, uint32_t code) 112{ 113 return code; 114} 115 116#include "exec/softmmu-semi.h" 117#endif 118 119static target_ulong arm_semi_syscall_len; 120 121#if !defined(CONFIG_USER_ONLY) 122static target_ulong syscall_err; 123#endif 124 125static void arm_semi_cb(CPUARMState *env, target_ulong ret, target_ulong err) 126{ 127#ifdef CONFIG_USER_ONLY 128 TaskState *ts = env->opaque; 129#endif 130 131 if (ret == (target_ulong)-1) { 132#ifdef CONFIG_USER_ONLY 133 ts->swi_errno = err; 134#else 135 syscall_err = err; 136#endif 137 env->regs[0] = ret; 138 } else { 139 /* Fixup syscalls that use nonstardard return conventions. */ 140 switch (env->regs[0]) { 141 case TARGET_SYS_WRITE: 142 case TARGET_SYS_READ: 143 env->regs[0] = arm_semi_syscall_len - ret; 144 break; 145 case TARGET_SYS_SEEK: 146 env->regs[0] = 0; 147 break; 148 default: 149 env->regs[0] = ret; 150 break; 151 } 152 } 153} 154 155static void arm_semi_flen_cb(CPUARMState *env, target_ulong ret, target_ulong err) 156{ 157 /* The size is always stored in big-endian order, extract 158 the value. We assume the size always fit in 32 bits. */ 159 uint32_t size; 160 cpu_memory_rw_debug(env, env->regs[13]-64+32, (uint8_t *)&size, 4, 0); 161 env->regs[0] = be32_to_cpu(size); 162#ifdef CONFIG_USER_ONLY 163 ((TaskState *)env->opaque)->swi_errno = err; 164#else 165 syscall_err = err; 166#endif 167} 168 169/* Read the input value from the argument block; fail the semihosting 170 * call if the memory read fails. 171 */ 172#define GET_ARG(n) do { \ 173 if (get_user_ual(arg ## n, args + (n) * 4)) { \ 174 return (uint32_t)-1; \ 175 } \ 176} while (0) 177 178#define SET_ARG(n, val) put_user_ual(val, args + (n) * 4) 179uint32_t do_arm_semihosting(CPUARMState *env) 180{ 181 target_ulong args; 182 target_ulong arg0, arg1, arg2, arg3; 183 char * s; 184 int nr; 185 uint32_t ret; 186 uint32_t len; 187#ifdef CONFIG_USER_ONLY 188 TaskState *ts = env->opaque; 189#else 190 CPUARMState *ts = env; 191#endif 192 193 nr = env->regs[0]; 194 args = env->regs[1]; 195 switch (nr) { 196 case TARGET_SYS_OPEN: 197 GET_ARG(0); 198 GET_ARG(1); 199 GET_ARG(2); 200 s = lock_user_string(arg0); 201 if (!s) { 202 /* FIXME - should this error code be -TARGET_EFAULT ? */ 203 return (uint32_t)-1; 204 } 205 if (arg1 >= 12) { 206 unlock_user(s, arg0, 0); 207 return (uint32_t)-1; 208 } 209 if (strcmp(s, ":tt") == 0) { 210 int result_fileno = arg1 < 4 ? STDIN_FILENO : STDOUT_FILENO; 211 unlock_user(s, arg0, 0); 212 return result_fileno; 213 } 214 if (use_gdb_syscalls()) { 215 gdb_do_syscall(arm_semi_cb, "open,%s,%x,1a4", arg0, 216 (int)arg2+1, gdb_open_modeflags[arg1]); 217 ret = env->regs[0]; 218 } else { 219 ret = set_swi_errno(ts, open(s, open_modeflags[arg1], 0644)); 220 } 221 unlock_user(s, arg0, 0); 222 return ret; 223 case TARGET_SYS_CLOSE: 224 GET_ARG(0); 225 if (use_gdb_syscalls()) { 226 gdb_do_syscall(arm_semi_cb, "close,%x", arg0); 227 return env->regs[0]; 228 } else { 229 return set_swi_errno(ts, close(arg0)); 230 } 231 case TARGET_SYS_WRITEC: 232 { 233 char c; 234 235 if (get_user_u8(c, args)) 236 /* FIXME - should this error code be -TARGET_EFAULT ? */ 237 return (uint32_t)-1; 238 /* Write to debug console. stderr is near enough. */ 239 if (use_gdb_syscalls()) { 240 gdb_do_syscall(arm_semi_cb, "write,2,%x,1", args); 241 return env->regs[0]; 242 } else { 243 return write(STDERR_FILENO, &c, 1); 244 } 245 } 246 case TARGET_SYS_WRITE0: 247 if (!(s = lock_user_string(args))) 248 /* FIXME - should this error code be -TARGET_EFAULT ? */ 249 return (uint32_t)-1; 250 len = strlen(s); 251 if (use_gdb_syscalls()) { 252 gdb_do_syscall(arm_semi_cb, "write,2,%x,%x\n", args, len); 253 ret = env->regs[0]; 254 } else { 255 ret = write(STDERR_FILENO, s, len); 256 } 257 unlock_user(s, args, 0); 258 return ret; 259 case TARGET_SYS_WRITE: 260 GET_ARG(0); 261 GET_ARG(1); 262 GET_ARG(2); 263 len = arg2; 264 if (use_gdb_syscalls()) { 265 arm_semi_syscall_len = len; 266 gdb_do_syscall(arm_semi_cb, "write,%x,%x,%x", arg0, arg1, len); 267 return env->regs[0]; 268 } else { 269 s = lock_user(VERIFY_READ, arg1, len, 1); 270 if (!s) { 271 /* FIXME - should this error code be -TARGET_EFAULT ? */ 272 return (uint32_t)-1; 273 } 274 ret = set_swi_errno(ts, write(arg0, s, len)); 275 unlock_user(s, arg1, 0); 276 if (ret == (uint32_t)-1) 277 return -1; 278 return len - ret; 279 } 280 case TARGET_SYS_READ: 281 GET_ARG(0); 282 GET_ARG(1); 283 GET_ARG(2); 284 len = arg2; 285 if (use_gdb_syscalls()) { 286 arm_semi_syscall_len = len; 287 gdb_do_syscall(arm_semi_cb, "read,%x,%x,%x", arg0, arg1, len); 288 return env->regs[0]; 289 } else { 290 s = lock_user(VERIFY_WRITE, arg1, len, 0); 291 if (!s) { 292 /* FIXME - should this error code be -TARGET_EFAULT ? */ 293 return (uint32_t)-1; 294 } 295 do { 296 ret = set_swi_errno(ts, read(arg0, s, len)); 297 } while (ret == -1 && errno == EINTR); 298 unlock_user(s, arg1, len); 299 if (ret == (uint32_t)-1) 300 return -1; 301 return len - ret; 302 } 303 case TARGET_SYS_READC: 304 /* XXX: Read from debug console. Not implemented. */ 305 return 0; 306 case TARGET_SYS_ISTTY: 307 GET_ARG(0); 308 if (use_gdb_syscalls()) { 309 gdb_do_syscall(arm_semi_cb, "isatty,%x", arg0); 310 return env->regs[0]; 311 } else { 312 return isatty(arg0); 313 } 314 case TARGET_SYS_SEEK: 315 GET_ARG(0); 316 GET_ARG(1); 317 if (use_gdb_syscalls()) { 318 gdb_do_syscall(arm_semi_cb, "lseek,%x,%x,0", arg0, arg1); 319 return env->regs[0]; 320 } else { 321 ret = set_swi_errno(ts, lseek(arg0, arg1, SEEK_SET)); 322 if (ret == (uint32_t)-1) 323 return -1; 324 return 0; 325 } 326 case TARGET_SYS_FLEN: 327 GET_ARG(0); 328 if (use_gdb_syscalls()) { 329 gdb_do_syscall(arm_semi_flen_cb, "fstat,%x,%x", 330 arg0, env->regs[13]-64); 331 return env->regs[0]; 332 } else { 333 struct stat buf; 334 ret = set_swi_errno(ts, fstat(arg0, &buf)); 335 if (ret == (uint32_t)-1) 336 return -1; 337 return buf.st_size; 338 } 339 case TARGET_SYS_TMPNAM: 340 /* XXX: Not implemented. */ 341 return -1; 342 case TARGET_SYS_REMOVE: 343 GET_ARG(0); 344 GET_ARG(1); 345 if (use_gdb_syscalls()) { 346 gdb_do_syscall(arm_semi_cb, "unlink,%s", arg0, (int)arg1+1); 347 ret = env->regs[0]; 348 } else { 349 s = lock_user_string(arg0); 350 if (!s) { 351 /* FIXME - should this error code be -TARGET_EFAULT ? */ 352 return (uint32_t)-1; 353 } 354 ret = set_swi_errno(ts, remove(s)); 355 unlock_user(s, arg0, 0); 356 } 357 return ret; 358 case TARGET_SYS_RENAME: 359 GET_ARG(0); 360 GET_ARG(1); 361 GET_ARG(2); 362 GET_ARG(3); 363 if (use_gdb_syscalls()) { 364 gdb_do_syscall(arm_semi_cb, "rename,%s,%s", 365 arg0, (int)arg1+1, arg2, (int)arg3+1); 366 return env->regs[0]; 367 } else { 368 char *s2; 369 s = lock_user_string(arg0); 370 s2 = lock_user_string(arg2); 371 if (!s || !s2) 372 /* FIXME - should this error code be -TARGET_EFAULT ? */ 373 ret = (uint32_t)-1; 374 else 375 ret = set_swi_errno(ts, rename(s, s2)); 376 if (s2) 377 unlock_user(s2, arg2, 0); 378 if (s) 379 unlock_user(s, arg0, 0); 380 return ret; 381 } 382 case TARGET_SYS_CLOCK: 383 return clock() / (CLOCKS_PER_SEC / 100); 384 case TARGET_SYS_TIME: 385 return set_swi_errno(ts, time(NULL)); 386 case TARGET_SYS_SYSTEM: 387 GET_ARG(0); 388 GET_ARG(1); 389 if (use_gdb_syscalls()) { 390 gdb_do_syscall(arm_semi_cb, "system,%s", arg0, (int)arg1+1); 391 return env->regs[0]; 392 } else { 393 s = lock_user_string(arg0); 394 if (!s) { 395 /* FIXME - should this error code be -TARGET_EFAULT ? */ 396 return (uint32_t)-1; 397 } 398 ret = set_swi_errno(ts, system(s)); 399 unlock_user(s, arg0, 0); 400 return ret; 401 } 402 case TARGET_SYS_ERRNO: 403#ifdef CONFIG_USER_ONLY 404 return ts->swi_errno; 405#else 406 return syscall_err; 407#endif 408 case TARGET_SYS_GET_CMDLINE: 409 { 410 /* Build a command-line from the original argv. 411 * 412 * The inputs are: 413 * * arg0, pointer to a buffer of at least the size 414 * specified in arg1. 415 * * arg1, size of the buffer pointed to by arg0 in 416 * bytes. 417 * 418 * The outputs are: 419 * * arg0, pointer to null-terminated string of the 420 * command line. 421 * * arg1, length of the string pointed to by arg0. 422 */ 423 424 char *output_buffer; 425 size_t input_size; 426 size_t output_size; 427 int status = 0; 428 GET_ARG(0); 429 GET_ARG(1); 430 input_size = arg1; 431 /* Compute the size of the output string. */ 432#if !defined(CONFIG_USER_ONLY) 433 output_size = strlen(ts->boot_info->kernel_filename) 434 + 1 /* Separating space. */ 435 + strlen(ts->boot_info->kernel_cmdline) 436 + 1; /* Terminating null byte. */ 437#else 438 unsigned int i; 439 440 output_size = ts->info->arg_end - ts->info->arg_start; 441 if (!output_size) { 442 /* We special-case the "empty command line" case (argc==0). 443 Just provide the terminating 0. */ 444 output_size = 1; 445 } 446#endif 447 448 if (output_size > input_size) { 449 /* Not enough space to store command-line arguments. */ 450 return -1; 451 } 452 453 /* Adjust the command-line length. */ 454 if (SET_ARG(1, output_size - 1)) { 455 /* Couldn't write back to argument block */ 456 return -1; 457 } 458 459 /* Lock the buffer on the ARM side. */ 460 output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0); 461 if (!output_buffer) { 462 return -1; 463 } 464 465 /* Copy the command-line arguments. */ 466#if !defined(CONFIG_USER_ONLY) 467 pstrcpy(output_buffer, output_size, ts->boot_info->kernel_filename); 468 pstrcat(output_buffer, output_size, " "); 469 pstrcat(output_buffer, output_size, ts->boot_info->kernel_cmdline); 470#else 471 if (output_size == 1) { 472 /* Empty command-line. */ 473 output_buffer[0] = '\0'; 474 goto out; 475 } 476 477 if (copy_from_user(output_buffer, ts->info->arg_start, 478 output_size)) { 479 status = -1; 480 goto out; 481 } 482 483 /* Separate arguments by white spaces. */ 484 for (i = 0; i < output_size - 1; i++) { 485 if (output_buffer[i] == 0) { 486 output_buffer[i] = ' '; 487 } 488 } 489 out: 490#endif 491 /* Unlock the buffer on the ARM side. */ 492 unlock_user(output_buffer, arg0, output_size); 493 494 return status; 495 } 496 case TARGET_SYS_HEAPINFO: 497 { 498 uint32_t *ptr; 499 uint32_t limit; 500 GET_ARG(0); 501 502#ifdef CONFIG_USER_ONLY 503 /* Some C libraries assume the heap immediately follows .bss, so 504 allocate it using sbrk. */ 505 if (!ts->heap_limit) { 506 abi_ulong ret; 507 508 ts->heap_base = do_brk(0); 509 limit = ts->heap_base + ARM_ANGEL_HEAP_SIZE; 510 /* Try a big heap, and reduce the size if that fails. */ 511 for (;;) { 512 ret = do_brk(limit); 513 if (ret >= limit) { 514 break; 515 } 516 limit = (ts->heap_base >> 1) + (limit >> 1); 517 } 518 ts->heap_limit = limit; 519 } 520 521 ptr = lock_user(VERIFY_WRITE, arg0, 16, 0); 522 if (!ptr) { 523 /* FIXME - should this error code be -TARGET_EFAULT ? */ 524 return (uint32_t)-1; 525 } 526 ptr[0] = tswap32(ts->heap_base); 527 ptr[1] = tswap32(ts->heap_limit); 528 ptr[2] = tswap32(ts->stack_base); 529 ptr[3] = tswap32(0); /* Stack limit. */ 530 unlock_user(ptr, arg0, 16); 531#else 532 limit = ram_size; 533 ptr = lock_user(VERIFY_WRITE, arg0, 16, 0); 534 if (!ptr) { 535 /* FIXME - should this error code be -TARGET_EFAULT ? */ 536 return (uint32_t)-1; 537 } 538 /* TODO: Make this use the limit of the loaded application. */ 539 ptr[0] = tswap32(limit / 2); 540 ptr[1] = tswap32(limit); 541 ptr[2] = tswap32(limit); /* Stack base */ 542 ptr[3] = tswap32(0); /* Stack limit. */ 543 unlock_user(ptr, arg0, 16); 544#endif 545 return 0; 546 } 547 case TARGET_SYS_EXIT: 548 //gdb_exit(env, 0); 549 exit(0); 550 default: 551 fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); 552 cpu_dump_state(env, stderr, fprintf, 0); 553 abort(); 554 } 555} 556