|
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, write to the Free Software |
|
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|
20 */ |
|
21 |
|
22 #include <sys/types.h> |
|
23 #include <sys/stat.h> |
|
24 #include <fcntl.h> |
|
25 #include <unistd.h> |
|
26 #include <stdlib.h> |
|
27 #include <stdio.h> |
|
28 #include <time.h> |
|
29 |
|
30 #include "cpu.h" |
|
31 #ifdef CONFIG_USER_ONLY |
|
32 #include "qemu.h" |
|
33 |
|
34 #define ARM_ANGEL_HEAP_SIZE (128 * 1024 * 1024) |
|
35 #else |
|
36 #include "qemu-common.h" |
|
37 #include "sysemu.h" |
|
38 #include "gdbstub.h" |
|
39 #endif |
|
40 |
|
41 #define SYS_OPEN 0x01 |
|
42 #define SYS_CLOSE 0x02 |
|
43 #define SYS_WRITEC 0x03 |
|
44 #define SYS_WRITE0 0x04 |
|
45 #define SYS_WRITE 0x05 |
|
46 #define SYS_READ 0x06 |
|
47 #define SYS_READC 0x07 |
|
48 #define SYS_ISTTY 0x09 |
|
49 #define SYS_SEEK 0x0a |
|
50 #define SYS_FLEN 0x0c |
|
51 #define SYS_TMPNAM 0x0d |
|
52 #define SYS_REMOVE 0x0e |
|
53 #define SYS_RENAME 0x0f |
|
54 #define SYS_CLOCK 0x10 |
|
55 #define SYS_TIME 0x11 |
|
56 #define SYS_SYSTEM 0x12 |
|
57 #define SYS_ERRNO 0x13 |
|
58 #define SYS_GET_CMDLINE 0x15 |
|
59 #define SYS_HEAPINFO 0x16 |
|
60 #define SYS_EXIT 0x18 |
|
61 |
|
62 #ifndef O_BINARY |
|
63 #define O_BINARY 0 |
|
64 #endif |
|
65 |
|
66 #define GDB_O_RDONLY 0x000 |
|
67 #define GDB_O_WRONLY 0x001 |
|
68 #define GDB_O_RDWR 0x002 |
|
69 #define GDB_O_APPEND 0x008 |
|
70 #define GDB_O_CREAT 0x200 |
|
71 #define GDB_O_TRUNC 0x400 |
|
72 #define GDB_O_BINARY 0 |
|
73 |
|
74 static int gdb_open_modeflags[12] = { |
|
75 GDB_O_RDONLY, |
|
76 GDB_O_RDONLY | GDB_O_BINARY, |
|
77 GDB_O_RDWR, |
|
78 GDB_O_RDWR | GDB_O_BINARY, |
|
79 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, |
|
80 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, |
|
81 GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, |
|
82 GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, |
|
83 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, |
|
84 GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, |
|
85 GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, |
|
86 GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY |
|
87 }; |
|
88 |
|
89 static int open_modeflags[12] = { |
|
90 O_RDONLY, |
|
91 O_RDONLY | O_BINARY, |
|
92 O_RDWR, |
|
93 O_RDWR | O_BINARY, |
|
94 O_WRONLY | O_CREAT | O_TRUNC, |
|
95 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, |
|
96 O_RDWR | O_CREAT | O_TRUNC, |
|
97 O_RDWR | O_CREAT | O_TRUNC | O_BINARY, |
|
98 O_WRONLY | O_CREAT | O_APPEND, |
|
99 O_WRONLY | O_CREAT | O_APPEND | O_BINARY, |
|
100 O_RDWR | O_CREAT | O_APPEND, |
|
101 O_RDWR | O_CREAT | O_APPEND | O_BINARY |
|
102 }; |
|
103 |
|
104 #ifdef CONFIG_USER_ONLY |
|
105 static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code) |
|
106 { |
|
107 if (code == (uint32_t)-1) |
|
108 ts->swi_errno = errno; |
|
109 return code; |
|
110 } |
|
111 #else |
|
112 static inline uint32_t set_swi_errno(CPUState *env, uint32_t code) |
|
113 { |
|
114 return code; |
|
115 } |
|
116 |
|
117 #include "softmmu-semi.h" |
|
118 #endif |
|
119 |
|
120 static target_ulong arm_semi_syscall_len; |
|
121 |
|
122 #if !defined(CONFIG_USER_ONLY) |
|
123 static target_ulong syscall_err; |
|
124 #endif |
|
125 |
|
126 static void arm_semi_cb(CPUState *env, target_ulong ret, target_ulong err) |
|
127 { |
|
128 #ifdef CONFIG_USER_ONLY |
|
129 TaskState *ts = env->opaque; |
|
130 #endif |
|
131 |
|
132 if (ret == (target_ulong)-1) { |
|
133 #ifdef CONFIG_USER_ONLY |
|
134 ts->swi_errno = err; |
|
135 #else |
|
136 syscall_err = err; |
|
137 #endif |
|
138 env->regs[0] = ret; |
|
139 } else { |
|
140 /* Fixup syscalls that use nonstardard return conventions. */ |
|
141 switch (env->regs[0]) { |
|
142 case SYS_WRITE: |
|
143 case SYS_READ: |
|
144 env->regs[0] = arm_semi_syscall_len - ret; |
|
145 break; |
|
146 case SYS_SEEK: |
|
147 env->regs[0] = 0; |
|
148 break; |
|
149 default: |
|
150 env->regs[0] = ret; |
|
151 break; |
|
152 } |
|
153 } |
|
154 } |
|
155 |
|
156 static void arm_semi_flen_cb(CPUState *env, target_ulong ret, target_ulong err) |
|
157 { |
|
158 /* The size is always stored in big-endian order, extract |
|
159 the value. We assume the size always fit in 32 bits. */ |
|
160 uint32_t size; |
|
161 cpu_memory_rw_debug(env, env->regs[13]-64+32, (uint8_t *)&size, 4, 0); |
|
162 env->regs[0] = be32_to_cpu(size); |
|
163 #ifdef CONFIG_USER_ONLY |
|
164 ((TaskState *)env->opaque)->swi_errno = err; |
|
165 #else |
|
166 syscall_err = err; |
|
167 #endif |
|
168 } |
|
169 |
|
170 #define ARG(n) \ |
|
171 ({ \ |
|
172 target_ulong __arg; \ |
|
173 /* FIXME - handle get_user() failure */ \ |
|
174 get_user_ual(__arg, args + (n) * 4); \ |
|
175 __arg; \ |
|
176 }) |
|
177 #define SET_ARG(n, val) put_user_ual(val, args + (n) * 4) |
|
178 uint32_t do_arm_semihosting(CPUState *env) |
|
179 { |
|
180 target_ulong args; |
|
181 char * s; |
|
182 int nr; |
|
183 uint32_t ret; |
|
184 uint32_t len; |
|
185 #ifdef CONFIG_USER_ONLY |
|
186 TaskState *ts = env->opaque; |
|
187 #else |
|
188 CPUState *ts = env; |
|
189 #endif |
|
190 |
|
191 nr = env->regs[0]; |
|
192 args = env->regs[1]; |
|
193 switch (nr) { |
|
194 case SYS_OPEN: |
|
195 if (!(s = lock_user_string(ARG(0)))) |
|
196 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
197 return (uint32_t)-1; |
|
198 if (ARG(1) >= 12) |
|
199 return (uint32_t)-1; |
|
200 if (strcmp(s, ":tt") == 0) { |
|
201 if (ARG(1) < 4) |
|
202 return STDIN_FILENO; |
|
203 else |
|
204 return STDOUT_FILENO; |
|
205 } |
|
206 if (use_gdb_syscalls()) { |
|
207 gdb_do_syscall(arm_semi_cb, "open,%s,%x,1a4", ARG(0), |
|
208 (int)ARG(2)+1, gdb_open_modeflags[ARG(1)]); |
|
209 return env->regs[0]; |
|
210 } else { |
|
211 ret = uninterrupted_open(s, open_modeflags[ARG(1)], 0644); |
|
212 ret = set_swi_errno(ts, ret); |
|
213 } |
|
214 unlock_user(s, ARG(0), 0); |
|
215 return ret; |
|
216 case SYS_CLOSE: |
|
217 if (use_gdb_syscalls()) { |
|
218 gdb_do_syscall(arm_semi_cb, "close,%x", ARG(0)); |
|
219 return env->regs[0]; |
|
220 } else { |
|
221 return set_swi_errno(ts, uninterrupted_close(ARG(0))); |
|
222 } |
|
223 case SYS_WRITEC: |
|
224 { |
|
225 char c; |
|
226 |
|
227 if (get_user_u8(c, args)) |
|
228 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
229 return (uint32_t)-1; |
|
230 /* Write to debug console. stderr is near enough. */ |
|
231 if (use_gdb_syscalls()) { |
|
232 gdb_do_syscall(arm_semi_cb, "write,2,%x,1", args); |
|
233 return env->regs[0]; |
|
234 } else { |
|
235 ret = uninterrupted_write(STDERR_FILENO, &c, 1); |
|
236 return set_swi_errno(ts, ret); |
|
237 } |
|
238 } |
|
239 case SYS_WRITE0: |
|
240 if (!(s = lock_user_string(args))) |
|
241 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
242 return (uint32_t)-1; |
|
243 len = strlen(s); |
|
244 if (use_gdb_syscalls()) { |
|
245 gdb_do_syscall(arm_semi_cb, "write,2,%x,%x\n", args, len); |
|
246 ret = env->regs[0]; |
|
247 } else { |
|
248 ret = uninterrupted_write(STDERR_FILENO, s, len); |
|
249 ret = set_swi_errno(ts, ret); |
|
250 } |
|
251 unlock_user(s, args, 0); |
|
252 return ret; |
|
253 case SYS_WRITE: |
|
254 len = ARG(2); |
|
255 if (use_gdb_syscalls()) { |
|
256 arm_semi_syscall_len = len; |
|
257 gdb_do_syscall(arm_semi_cb, "write,%x,%x,%x", ARG(0), ARG(1), len); |
|
258 return env->regs[0]; |
|
259 } else { |
|
260 if (!(s = lock_user(VERIFY_READ, ARG(1), len, 1))) |
|
261 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
262 return (uint32_t)-1; |
|
263 ret = set_swi_errno(ts, uninterrupted_write(ARG(0), s, len)); |
|
264 unlock_user(s, ARG(1), 0); |
|
265 if (ret == (uint32_t)-1) |
|
266 return -1; |
|
267 return len - ret; |
|
268 } |
|
269 case SYS_READ: |
|
270 len = ARG(2); |
|
271 if (use_gdb_syscalls()) { |
|
272 arm_semi_syscall_len = len; |
|
273 gdb_do_syscall(arm_semi_cb, "read,%x,%x,%x", ARG(0), ARG(1), len); |
|
274 return env->regs[0]; |
|
275 } else { |
|
276 if (!(s = lock_user(VERIFY_WRITE, ARG(1), len, 0))) |
|
277 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
278 return (uint32_t)-1; |
|
279 ret = set_swi_errno(ts, uninterrupted_read(ARG(0), s, len)); |
|
280 unlock_user(s, ARG(1), len); |
|
281 if (ret == (uint32_t)-1) |
|
282 return -1; |
|
283 return len - ret; |
|
284 } |
|
285 case SYS_READC: |
|
286 /* XXX: Read from debug cosole. Not implemented. */ |
|
287 return 0; |
|
288 case SYS_ISTTY: |
|
289 if (use_gdb_syscalls()) { |
|
290 gdb_do_syscall(arm_semi_cb, "isatty,%x", ARG(0)); |
|
291 return env->regs[0]; |
|
292 } else { |
|
293 return isatty(ARG(0)); |
|
294 } |
|
295 case SYS_SEEK: |
|
296 if (use_gdb_syscalls()) { |
|
297 gdb_do_syscall(arm_semi_cb, "lseek,%x,%x,0", ARG(0), ARG(1)); |
|
298 return env->regs[0]; |
|
299 } else { |
|
300 ret = set_swi_errno(ts, lseek(ARG(0), ARG(1), SEEK_SET)); |
|
301 if (ret == (uint32_t)-1) |
|
302 return -1; |
|
303 return 0; |
|
304 } |
|
305 case SYS_FLEN: |
|
306 if (use_gdb_syscalls()) { |
|
307 gdb_do_syscall(arm_semi_flen_cb, "fstat,%x,%x", |
|
308 ARG(0), env->regs[13]-64); |
|
309 return env->regs[0]; |
|
310 } else { |
|
311 struct stat buf; |
|
312 ret = set_swi_errno(ts, fstat(ARG(0), &buf)); |
|
313 if (ret == (uint32_t)-1) |
|
314 return -1; |
|
315 return buf.st_size; |
|
316 } |
|
317 case SYS_TMPNAM: |
|
318 /* XXX: Not implemented. */ |
|
319 return -1; |
|
320 case SYS_REMOVE: |
|
321 if (use_gdb_syscalls()) { |
|
322 gdb_do_syscall(arm_semi_cb, "unlink,%s", ARG(0), (int)ARG(1)+1); |
|
323 ret = env->regs[0]; |
|
324 } else { |
|
325 if (!(s = lock_user_string(ARG(0)))) |
|
326 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
327 return (uint32_t)-1; |
|
328 ret = set_swi_errno(ts, remove(s)); |
|
329 unlock_user(s, ARG(0), 0); |
|
330 } |
|
331 return ret; |
|
332 case SYS_RENAME: |
|
333 if (use_gdb_syscalls()) { |
|
334 gdb_do_syscall(arm_semi_cb, "rename,%s,%s", |
|
335 ARG(0), (int)ARG(1)+1, ARG(2), (int)ARG(3)+1); |
|
336 return env->regs[0]; |
|
337 } else { |
|
338 char *s2; |
|
339 s = lock_user_string(ARG(0)); |
|
340 s2 = lock_user_string(ARG(2)); |
|
341 if (!s || !s2) |
|
342 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
343 ret = (uint32_t)-1; |
|
344 else |
|
345 ret = set_swi_errno(ts, rename(s, s2)); |
|
346 if (s2) |
|
347 unlock_user(s2, ARG(2), 0); |
|
348 if (s) |
|
349 unlock_user(s, ARG(0), 0); |
|
350 return ret; |
|
351 } |
|
352 case SYS_CLOCK: |
|
353 return clock() / (CLOCKS_PER_SEC / 100); |
|
354 case SYS_TIME: |
|
355 return set_swi_errno(ts, time(NULL)); |
|
356 case SYS_SYSTEM: |
|
357 if (use_gdb_syscalls()) { |
|
358 gdb_do_syscall(arm_semi_cb, "system,%s", ARG(0), (int)ARG(1)+1); |
|
359 return env->regs[0]; |
|
360 } else { |
|
361 if (!(s = lock_user_string(ARG(0)))) |
|
362 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
363 return (uint32_t)-1; |
|
364 ret = set_swi_errno(ts, system(s)); |
|
365 unlock_user(s, ARG(0), 0); |
|
366 return ret; |
|
367 } |
|
368 case SYS_ERRNO: |
|
369 #ifdef CONFIG_USER_ONLY |
|
370 return ts->swi_errno; |
|
371 #else |
|
372 return syscall_err; |
|
373 #endif |
|
374 case SYS_GET_CMDLINE: |
|
375 #ifdef CONFIG_USER_ONLY |
|
376 /* Build a commandline from the original argv. */ |
|
377 { |
|
378 char **arg = ts->info->host_argv; |
|
379 int len = ARG(1); |
|
380 /* lock the buffer on the ARM side */ |
|
381 char *cmdline_buffer = (char*)lock_user(VERIFY_WRITE, ARG(0), len, 0); |
|
382 |
|
383 if (!cmdline_buffer) |
|
384 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
385 return (uint32_t)-1; |
|
386 |
|
387 s = cmdline_buffer; |
|
388 while (*arg && len > 2) { |
|
389 int n = strlen(*arg); |
|
390 |
|
391 if (s != cmdline_buffer) { |
|
392 *(s++) = ' '; |
|
393 len--; |
|
394 } |
|
395 if (n >= len) |
|
396 n = len - 1; |
|
397 memcpy(s, *arg, n); |
|
398 s += n; |
|
399 len -= n; |
|
400 arg++; |
|
401 } |
|
402 /* Null terminate the string. */ |
|
403 *s = 0; |
|
404 len = s - cmdline_buffer; |
|
405 |
|
406 /* Unlock the buffer on the ARM side. */ |
|
407 unlock_user(cmdline_buffer, ARG(0), len); |
|
408 |
|
409 /* Adjust the commandline length argument. */ |
|
410 SET_ARG(1, len); |
|
411 |
|
412 /* Return success if commandline fit into buffer. */ |
|
413 return *arg ? -1 : 0; |
|
414 } |
|
415 #else |
|
416 return -1; |
|
417 #endif |
|
418 case SYS_HEAPINFO: |
|
419 { |
|
420 uint32_t *ptr; |
|
421 uint32_t limit; |
|
422 |
|
423 #ifdef CONFIG_USER_ONLY |
|
424 /* Some C libraries assume the heap immediately follows .bss, so |
|
425 allocate it using sbrk. */ |
|
426 if (!ts->heap_limit) { |
|
427 long ret; |
|
428 |
|
429 ts->heap_base = do_brk(0); |
|
430 limit = ts->heap_base + ARM_ANGEL_HEAP_SIZE; |
|
431 /* Try a big heap, and reduce the size if that fails. */ |
|
432 for (;;) { |
|
433 ret = do_brk(limit); |
|
434 if (ret != -1) |
|
435 break; |
|
436 limit = (ts->heap_base >> 1) + (limit >> 1); |
|
437 } |
|
438 ts->heap_limit = limit; |
|
439 } |
|
440 |
|
441 if (!(ptr = lock_user(VERIFY_WRITE, ARG(0), 16, 0))) |
|
442 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
443 return (uint32_t)-1; |
|
444 ptr[0] = tswap32(ts->heap_base); |
|
445 ptr[1] = tswap32(ts->heap_limit); |
|
446 ptr[2] = tswap32(ts->stack_base); |
|
447 ptr[3] = tswap32(0); /* Stack limit. */ |
|
448 unlock_user(ptr, ARG(0), 16); |
|
449 #else |
|
450 limit = ram_size; |
|
451 if (!(ptr = lock_user(VERIFY_WRITE, ARG(0), 16, 0))) |
|
452 /* FIXME - should this error code be -TARGET_EFAULT ? */ |
|
453 return (uint32_t)-1; |
|
454 /* TODO: Make this use the limit of the loaded application. */ |
|
455 ptr[0] = tswap32(limit / 2); |
|
456 ptr[1] = tswap32(limit); |
|
457 ptr[2] = tswap32(limit); /* Stack base */ |
|
458 ptr[3] = tswap32(0); /* Stack limit. */ |
|
459 unlock_user(ptr, ARG(0), 16); |
|
460 #endif |
|
461 return 0; |
|
462 } |
|
463 case SYS_EXIT: |
|
464 /* FIXME: ideally we want to inform gdb about program |
|
465 exit whenever gdb is connected, even if syscalls |
|
466 are not handled by gdb. */ |
|
467 if (use_gdb_syscalls()) |
|
468 gdb_exit(env, 0); |
|
469 exit(0); |
|
470 default: |
|
471 fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); |
|
472 cpu_dump_state(env, stderr, fprintf, 0); |
|
473 abort(); |
|
474 } |
|
475 } |