1 | /* |
2 | * Unified Hosting Interface syscalls. |
3 | * |
4 | * Copyright (c) 2015 Imagination Technologies |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library 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 GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "qemu/osdep.h" |
21 | #include "cpu.h" |
22 | #include "qemu/log.h" |
23 | #include "exec/helper-proto.h" |
24 | #include "exec/softmmu-semi.h" |
25 | #include "hw/semihosting/semihost.h" |
26 | #include "hw/semihosting/console.h" |
27 | |
28 | typedef enum UHIOp { |
29 | UHI_exit = 1, |
30 | UHI_open = 2, |
31 | UHI_close = 3, |
32 | UHI_read = 4, |
33 | UHI_write = 5, |
34 | UHI_lseek = 6, |
35 | UHI_unlink = 7, |
36 | UHI_fstat = 8, |
37 | UHI_argc = 9, |
38 | UHI_argnlen = 10, |
39 | UHI_argn = 11, |
40 | UHI_plog = 13, |
41 | UHI_assert = 14, |
42 | UHI_pread = 19, |
43 | UHI_pwrite = 20, |
44 | UHI_link = 22 |
45 | } UHIOp; |
46 | |
47 | typedef struct UHIStat { |
48 | int16_t uhi_st_dev; |
49 | uint16_t uhi_st_ino; |
50 | uint32_t uhi_st_mode; |
51 | uint16_t uhi_st_nlink; |
52 | uint16_t uhi_st_uid; |
53 | uint16_t uhi_st_gid; |
54 | int16_t uhi_st_rdev; |
55 | uint64_t uhi_st_size; |
56 | uint64_t uhi_st_atime; |
57 | uint64_t uhi_st_spare1; |
58 | uint64_t uhi_st_mtime; |
59 | uint64_t uhi_st_spare2; |
60 | uint64_t uhi_st_ctime; |
61 | uint64_t uhi_st_spare3; |
62 | uint64_t uhi_st_blksize; |
63 | uint64_t uhi_st_blocks; |
64 | uint64_t uhi_st_spare4[2]; |
65 | } UHIStat; |
66 | |
67 | enum UHIOpenFlags { |
68 | UHIOpen_RDONLY = 0x0, |
69 | UHIOpen_WRONLY = 0x1, |
70 | UHIOpen_RDWR = 0x2, |
71 | UHIOpen_APPEND = 0x8, |
72 | UHIOpen_CREAT = 0x200, |
73 | UHIOpen_TRUNC = 0x400, |
74 | UHIOpen_EXCL = 0x800 |
75 | }; |
76 | |
77 | /* Errno values taken from asm-mips/errno.h */ |
78 | static uint16_t host_to_mips_errno[] = { |
79 | [ENAMETOOLONG] = 78, |
80 | #ifdef EOVERFLOW |
81 | [EOVERFLOW] = 79, |
82 | #endif |
83 | #ifdef ELOOP |
84 | [ELOOP] = 90, |
85 | #endif |
86 | }; |
87 | |
88 | static int errno_mips(int err) |
89 | { |
90 | if (err < 0 || err >= ARRAY_SIZE(host_to_mips_errno)) { |
91 | return EINVAL; |
92 | } else if (host_to_mips_errno[err]) { |
93 | return host_to_mips_errno[err]; |
94 | } else { |
95 | return err; |
96 | } |
97 | } |
98 | |
99 | static int copy_stat_to_target(CPUMIPSState *env, const struct stat *src, |
100 | target_ulong vaddr) |
101 | { |
102 | hwaddr len = sizeof(struct UHIStat); |
103 | UHIStat *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); |
104 | if (!dst) { |
105 | errno = EFAULT; |
106 | return -1; |
107 | } |
108 | |
109 | dst->uhi_st_dev = tswap16(src->st_dev); |
110 | dst->uhi_st_ino = tswap16(src->st_ino); |
111 | dst->uhi_st_mode = tswap32(src->st_mode); |
112 | dst->uhi_st_nlink = tswap16(src->st_nlink); |
113 | dst->uhi_st_uid = tswap16(src->st_uid); |
114 | dst->uhi_st_gid = tswap16(src->st_gid); |
115 | dst->uhi_st_rdev = tswap16(src->st_rdev); |
116 | dst->uhi_st_size = tswap64(src->st_size); |
117 | dst->uhi_st_atime = tswap64(src->st_atime); |
118 | dst->uhi_st_mtime = tswap64(src->st_mtime); |
119 | dst->uhi_st_ctime = tswap64(src->st_ctime); |
120 | #ifdef _WIN32 |
121 | dst->uhi_st_blksize = 0; |
122 | dst->uhi_st_blocks = 0; |
123 | #else |
124 | dst->uhi_st_blksize = tswap64(src->st_blksize); |
125 | dst->uhi_st_blocks = tswap64(src->st_blocks); |
126 | #endif |
127 | unlock_user(dst, vaddr, len); |
128 | return 0; |
129 | } |
130 | |
131 | static int get_open_flags(target_ulong target_flags) |
132 | { |
133 | int open_flags = 0; |
134 | |
135 | if (target_flags & UHIOpen_RDWR) { |
136 | open_flags |= O_RDWR; |
137 | } else if (target_flags & UHIOpen_WRONLY) { |
138 | open_flags |= O_WRONLY; |
139 | } else { |
140 | open_flags |= O_RDONLY; |
141 | } |
142 | |
143 | open_flags |= (target_flags & UHIOpen_APPEND) ? O_APPEND : 0; |
144 | open_flags |= (target_flags & UHIOpen_CREAT) ? O_CREAT : 0; |
145 | open_flags |= (target_flags & UHIOpen_TRUNC) ? O_TRUNC : 0; |
146 | open_flags |= (target_flags & UHIOpen_EXCL) ? O_EXCL : 0; |
147 | |
148 | return open_flags; |
149 | } |
150 | |
151 | static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr, |
152 | target_ulong len, target_ulong offset) |
153 | { |
154 | int num_of_bytes; |
155 | void *dst = lock_user(VERIFY_READ, vaddr, len, 1); |
156 | if (!dst) { |
157 | errno = EFAULT; |
158 | return -1; |
159 | } |
160 | |
161 | if (offset) { |
162 | #ifdef _WIN32 |
163 | num_of_bytes = 0; |
164 | #else |
165 | num_of_bytes = pwrite(fd, dst, len, offset); |
166 | #endif |
167 | } else { |
168 | num_of_bytes = write(fd, dst, len); |
169 | } |
170 | |
171 | unlock_user(dst, vaddr, 0); |
172 | return num_of_bytes; |
173 | } |
174 | |
175 | static int read_from_file(CPUMIPSState *env, target_ulong fd, |
176 | target_ulong vaddr, target_ulong len, |
177 | target_ulong offset) |
178 | { |
179 | int num_of_bytes; |
180 | void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0); |
181 | if (!dst) { |
182 | errno = EFAULT; |
183 | return -1; |
184 | } |
185 | |
186 | if (offset) { |
187 | #ifdef _WIN32 |
188 | num_of_bytes = 0; |
189 | #else |
190 | num_of_bytes = pread(fd, dst, len, offset); |
191 | #endif |
192 | } else { |
193 | num_of_bytes = read(fd, dst, len); |
194 | } |
195 | |
196 | unlock_user(dst, vaddr, len); |
197 | return num_of_bytes; |
198 | } |
199 | |
200 | static int copy_argn_to_target(CPUMIPSState *env, int arg_num, |
201 | target_ulong vaddr) |
202 | { |
203 | int strsize = strlen(semihosting_get_arg(arg_num)) + 1; |
204 | char *dst = lock_user(VERIFY_WRITE, vaddr, strsize, 0); |
205 | if (!dst) { |
206 | return -1; |
207 | } |
208 | |
209 | strcpy(dst, semihosting_get_arg(arg_num)); |
210 | |
211 | unlock_user(dst, vaddr, strsize); |
212 | return 0; |
213 | } |
214 | |
215 | #define GET_TARGET_STRING(p, addr) \ |
216 | do { \ |
217 | p = lock_user_string(addr); \ |
218 | if (!p) { \ |
219 | gpr[2] = -1; \ |
220 | gpr[3] = EFAULT; \ |
221 | goto uhi_done; \ |
222 | } \ |
223 | } while (0) |
224 | |
225 | #define GET_TARGET_STRINGS_2(p, addr, p2, addr2) \ |
226 | do { \ |
227 | p = lock_user_string(addr); \ |
228 | if (!p) { \ |
229 | gpr[2] = -1; \ |
230 | gpr[3] = EFAULT; \ |
231 | goto uhi_done; \ |
232 | } \ |
233 | p2 = lock_user_string(addr2); \ |
234 | if (!p2) { \ |
235 | unlock_user(p, addr, 0); \ |
236 | gpr[2] = -1; \ |
237 | gpr[3] = EFAULT; \ |
238 | goto uhi_done; \ |
239 | } \ |
240 | } while (0) |
241 | |
242 | #define FREE_TARGET_STRING(p, gpr) \ |
243 | do { \ |
244 | unlock_user(p, gpr, 0); \ |
245 | } while (0) |
246 | |
247 | void helper_do_semihosting(CPUMIPSState *env) |
248 | { |
249 | target_ulong *gpr = env->active_tc.gpr; |
250 | const UHIOp op = gpr[25]; |
251 | char *p, *p2; |
252 | |
253 | switch (op) { |
254 | case UHI_exit: |
255 | qemu_log("UHI(%d): exit(%d)\n" , op, (int)gpr[4]); |
256 | exit(gpr[4]); |
257 | case UHI_open: |
258 | GET_TARGET_STRING(p, gpr[4]); |
259 | if (!strcmp("/dev/stdin" , p)) { |
260 | gpr[2] = 0; |
261 | } else if (!strcmp("/dev/stdout" , p)) { |
262 | gpr[2] = 1; |
263 | } else if (!strcmp("/dev/stderr" , p)) { |
264 | gpr[2] = 2; |
265 | } else { |
266 | gpr[2] = open(p, get_open_flags(gpr[5]), gpr[6]); |
267 | gpr[3] = errno_mips(errno); |
268 | } |
269 | FREE_TARGET_STRING(p, gpr[4]); |
270 | break; |
271 | case UHI_close: |
272 | if (gpr[4] < 3) { |
273 | /* ignore closing stdin/stdout/stderr */ |
274 | gpr[2] = 0; |
275 | goto uhi_done; |
276 | } |
277 | gpr[2] = close(gpr[4]); |
278 | gpr[3] = errno_mips(errno); |
279 | break; |
280 | case UHI_read: |
281 | gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], 0); |
282 | gpr[3] = errno_mips(errno); |
283 | break; |
284 | case UHI_write: |
285 | gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], 0); |
286 | gpr[3] = errno_mips(errno); |
287 | break; |
288 | case UHI_lseek: |
289 | gpr[2] = lseek(gpr[4], gpr[5], gpr[6]); |
290 | gpr[3] = errno_mips(errno); |
291 | break; |
292 | case UHI_unlink: |
293 | GET_TARGET_STRING(p, gpr[4]); |
294 | gpr[2] = remove(p); |
295 | gpr[3] = errno_mips(errno); |
296 | FREE_TARGET_STRING(p, gpr[4]); |
297 | break; |
298 | case UHI_fstat: |
299 | { |
300 | struct stat sbuf; |
301 | memset(&sbuf, 0, sizeof(sbuf)); |
302 | gpr[2] = fstat(gpr[4], &sbuf); |
303 | gpr[3] = errno_mips(errno); |
304 | if (gpr[2]) { |
305 | goto uhi_done; |
306 | } |
307 | gpr[2] = copy_stat_to_target(env, &sbuf, gpr[5]); |
308 | gpr[3] = errno_mips(errno); |
309 | } |
310 | break; |
311 | case UHI_argc: |
312 | gpr[2] = semihosting_get_argc(); |
313 | break; |
314 | case UHI_argnlen: |
315 | if (gpr[4] >= semihosting_get_argc()) { |
316 | gpr[2] = -1; |
317 | goto uhi_done; |
318 | } |
319 | gpr[2] = strlen(semihosting_get_arg(gpr[4])); |
320 | break; |
321 | case UHI_argn: |
322 | if (gpr[4] >= semihosting_get_argc()) { |
323 | gpr[2] = -1; |
324 | goto uhi_done; |
325 | } |
326 | gpr[2] = copy_argn_to_target(env, gpr[4], gpr[5]); |
327 | break; |
328 | case UHI_plog: |
329 | GET_TARGET_STRING(p, gpr[4]); |
330 | p2 = strstr(p, "%d" ); |
331 | if (p2) { |
332 | int char_num = p2 - p; |
333 | GString *s = g_string_new_len(p, char_num); |
334 | g_string_append_printf(s, "%d%s" , (int)gpr[5], p2 + 2); |
335 | gpr[2] = qemu_semihosting_log_out(s->str, s->len); |
336 | g_string_free(s, true); |
337 | } else { |
338 | gpr[2] = qemu_semihosting_log_out(p, strlen(p)); |
339 | } |
340 | FREE_TARGET_STRING(p, gpr[4]); |
341 | break; |
342 | case UHI_assert: |
343 | GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]); |
344 | printf("assertion '" ); |
345 | printf("\"%s\"" , p); |
346 | printf("': file \"%s\", line %d\n" , p2, (int)gpr[6]); |
347 | FREE_TARGET_STRING(p2, gpr[5]); |
348 | FREE_TARGET_STRING(p, gpr[4]); |
349 | abort(); |
350 | break; |
351 | case UHI_pread: |
352 | gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], gpr[7]); |
353 | gpr[3] = errno_mips(errno); |
354 | break; |
355 | case UHI_pwrite: |
356 | gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], gpr[7]); |
357 | gpr[3] = errno_mips(errno); |
358 | break; |
359 | #ifndef _WIN32 |
360 | case UHI_link: |
361 | GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]); |
362 | gpr[2] = link(p, p2); |
363 | gpr[3] = errno_mips(errno); |
364 | FREE_TARGET_STRING(p2, gpr[5]); |
365 | FREE_TARGET_STRING(p, gpr[4]); |
366 | break; |
367 | #endif |
368 | default: |
369 | fprintf(stderr, "Unknown UHI operation %d\n" , op); |
370 | abort(); |
371 | } |
372 | uhi_done: |
373 | return; |
374 | } |
375 | |