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