xref: /openbmc/qemu/target/mips/tcg/sysemu/mips-semi.c (revision ed3a06b1)
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.1 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 "semihosting/softmmu-uaccess.h"
25 #include "semihosting/semihost.h"
26 #include "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 static int errno_mips(int host_errno)
78 {
79     /* Errno values taken from asm-mips/errno.h */
80     switch (host_errno) {
81     case 0:             return 0;
82     case ENAMETOOLONG:  return 78;
83 #ifdef EOVERFLOW
84     case EOVERFLOW:     return 79;
85 #endif
86 #ifdef ELOOP
87     case ELOOP:         return 90;
88 #endif
89     default:            return EINVAL;
90     }
91 }
92 
93 static int copy_stat_to_target(CPUMIPSState *env, const struct stat *src,
94                                target_ulong vaddr)
95 {
96     hwaddr len = sizeof(struct UHIStat);
97     UHIStat *dst = lock_user(VERIFY_WRITE, vaddr, len, 0);
98     if (!dst) {
99         errno = EFAULT;
100         return -1;
101     }
102 
103     dst->uhi_st_dev = tswap16(src->st_dev);
104     dst->uhi_st_ino = tswap16(src->st_ino);
105     dst->uhi_st_mode = tswap32(src->st_mode);
106     dst->uhi_st_nlink = tswap16(src->st_nlink);
107     dst->uhi_st_uid = tswap16(src->st_uid);
108     dst->uhi_st_gid = tswap16(src->st_gid);
109     dst->uhi_st_rdev = tswap16(src->st_rdev);
110     dst->uhi_st_size = tswap64(src->st_size);
111     dst->uhi_st_atime = tswap64(src->st_atime);
112     dst->uhi_st_mtime = tswap64(src->st_mtime);
113     dst->uhi_st_ctime = tswap64(src->st_ctime);
114 #ifdef _WIN32
115     dst->uhi_st_blksize = 0;
116     dst->uhi_st_blocks = 0;
117 #else
118     dst->uhi_st_blksize = tswap64(src->st_blksize);
119     dst->uhi_st_blocks = tswap64(src->st_blocks);
120 #endif
121     unlock_user(dst, vaddr, len);
122     return 0;
123 }
124 
125 static int get_open_flags(target_ulong target_flags)
126 {
127     int open_flags = 0;
128 
129     if (target_flags & UHIOpen_RDWR) {
130         open_flags |= O_RDWR;
131     } else if (target_flags & UHIOpen_WRONLY) {
132         open_flags |= O_WRONLY;
133     } else {
134         open_flags |= O_RDONLY;
135     }
136 
137     open_flags |= (target_flags & UHIOpen_APPEND) ? O_APPEND : 0;
138     open_flags |= (target_flags & UHIOpen_CREAT)  ? O_CREAT  : 0;
139     open_flags |= (target_flags & UHIOpen_TRUNC)  ? O_TRUNC  : 0;
140     open_flags |= (target_flags & UHIOpen_EXCL)   ? O_EXCL   : 0;
141 
142     return open_flags;
143 }
144 
145 static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr,
146                          target_ulong len, target_ulong offset)
147 {
148     int num_of_bytes;
149     void *dst = lock_user(VERIFY_READ, vaddr, len, 1);
150     if (!dst) {
151         errno = EFAULT;
152         return -1;
153     }
154 
155     if (offset) {
156 #ifdef _WIN32
157         num_of_bytes = 0;
158 #else
159         num_of_bytes = pwrite(fd, dst, len, offset);
160 #endif
161     } else {
162         num_of_bytes = write(fd, dst, len);
163     }
164 
165     unlock_user(dst, vaddr, 0);
166     return num_of_bytes;
167 }
168 
169 static int read_from_file(CPUMIPSState *env, target_ulong fd,
170                           target_ulong vaddr, target_ulong len,
171                           target_ulong offset)
172 {
173     int num_of_bytes;
174     void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0);
175     if (!dst) {
176         errno = EFAULT;
177         return -1;
178     }
179 
180     if (offset) {
181 #ifdef _WIN32
182         num_of_bytes = 0;
183 #else
184         num_of_bytes = pread(fd, dst, len, offset);
185 #endif
186     } else {
187         num_of_bytes = read(fd, dst, len);
188     }
189 
190     unlock_user(dst, vaddr, len);
191     return num_of_bytes;
192 }
193 
194 static int copy_argn_to_target(CPUMIPSState *env, int arg_num,
195                                target_ulong vaddr)
196 {
197     int strsize = strlen(semihosting_get_arg(arg_num)) + 1;
198     char *dst = lock_user(VERIFY_WRITE, vaddr, strsize, 0);
199     if (!dst) {
200         return -1;
201     }
202 
203     strcpy(dst, semihosting_get_arg(arg_num));
204 
205     unlock_user(dst, vaddr, strsize);
206     return 0;
207 }
208 
209 #define GET_TARGET_STRING(p, addr)              \
210     do {                                        \
211         p = lock_user_string(addr);             \
212         if (!p) {                               \
213             gpr[2] = -1;                        \
214             gpr[3] = EFAULT;                    \
215             return;                             \
216         }                                       \
217     } while (0)
218 
219 #define GET_TARGET_STRINGS_2(p, addr, p2, addr2)        \
220     do {                                                \
221         p = lock_user_string(addr);                     \
222         if (!p) {                                       \
223             gpr[2] = -1;                                \
224             gpr[3] = EFAULT;                            \
225             return;                                     \
226         }                                               \
227         p2 = lock_user_string(addr2);                   \
228         if (!p2) {                                      \
229             unlock_user(p, addr, 0);                    \
230             gpr[2] = -1;                                \
231             gpr[3] = EFAULT;                            \
232             return;                                     \
233         }                                               \
234     } while (0)
235 
236 #define FREE_TARGET_STRING(p, gpr)              \
237     do {                                        \
238         unlock_user(p, gpr, 0);                 \
239     } while (0)
240 
241 void helper_do_semihosting(CPUMIPSState *env)
242 {
243     target_ulong *gpr = env->active_tc.gpr;
244     const UHIOp op = gpr[25];
245     char *p, *p2;
246 
247     switch (op) {
248     case UHI_exit:
249         qemu_log("UHI(%d): exit(%d)\n", op, (int)gpr[4]);
250         exit(gpr[4]);
251     case UHI_open:
252         GET_TARGET_STRING(p, gpr[4]);
253         if (!strcmp("/dev/stdin", p)) {
254             gpr[2] = 0;
255         } else if (!strcmp("/dev/stdout", p)) {
256             gpr[2] = 1;
257         } else if (!strcmp("/dev/stderr", p)) {
258             gpr[2] = 2;
259         } else {
260             gpr[2] = open(p, get_open_flags(gpr[5]), gpr[6]);
261             gpr[3] = errno_mips(errno);
262         }
263         FREE_TARGET_STRING(p, gpr[4]);
264         break;
265     case UHI_close:
266         if (gpr[4] < 3) {
267             /* ignore closing stdin/stdout/stderr */
268             gpr[2] = 0;
269             return;
270         }
271         gpr[2] = close(gpr[4]);
272         gpr[3] = errno_mips(errno);
273         break;
274     case UHI_read:
275         gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], 0);
276         gpr[3] = errno_mips(errno);
277         break;
278     case UHI_write:
279         gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], 0);
280         gpr[3] = errno_mips(errno);
281         break;
282     case UHI_lseek:
283         gpr[2] = lseek(gpr[4], gpr[5], gpr[6]);
284         gpr[3] = errno_mips(errno);
285         break;
286     case UHI_unlink:
287         GET_TARGET_STRING(p, gpr[4]);
288         gpr[2] = remove(p);
289         gpr[3] = errno_mips(errno);
290         FREE_TARGET_STRING(p, gpr[4]);
291         break;
292     case UHI_fstat:
293         {
294             struct stat sbuf;
295             memset(&sbuf, 0, sizeof(sbuf));
296             gpr[2] = fstat(gpr[4], &sbuf);
297             gpr[3] = errno_mips(errno);
298             if (gpr[2]) {
299                 return;
300             }
301             gpr[2] = copy_stat_to_target(env, &sbuf, gpr[5]);
302             gpr[3] = errno_mips(errno);
303         }
304         break;
305     case UHI_argc:
306         gpr[2] = semihosting_get_argc();
307         break;
308     case UHI_argnlen:
309         if (gpr[4] >= semihosting_get_argc()) {
310             gpr[2] = -1;
311             return;
312         }
313         gpr[2] = strlen(semihosting_get_arg(gpr[4]));
314         break;
315     case UHI_argn:
316         if (gpr[4] >= semihosting_get_argc()) {
317             gpr[2] = -1;
318             return;
319         }
320         gpr[2] = copy_argn_to_target(env, gpr[4], gpr[5]);
321         break;
322     case UHI_plog:
323         GET_TARGET_STRING(p, gpr[4]);
324         p2 = strstr(p, "%d");
325         if (p2) {
326             int char_num = p2 - p;
327             GString *s = g_string_new_len(p, char_num);
328             g_string_append_printf(s, "%d%s", (int)gpr[5], p2 + 2);
329             gpr[2] = qemu_semihosting_log_out(s->str, s->len);
330             g_string_free(s, true);
331         } else {
332             gpr[2] = qemu_semihosting_log_out(p, strlen(p));
333         }
334         FREE_TARGET_STRING(p, gpr[4]);
335         break;
336     case UHI_assert:
337         GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]);
338         printf("assertion '");
339         printf("\"%s\"", p);
340         printf("': file \"%s\", line %d\n", p2, (int)gpr[6]);
341         FREE_TARGET_STRING(p2, gpr[5]);
342         FREE_TARGET_STRING(p, gpr[4]);
343         abort();
344         break;
345     case UHI_pread:
346         gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
347         gpr[3] = errno_mips(errno);
348         break;
349     case UHI_pwrite:
350         gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
351         gpr[3] = errno_mips(errno);
352         break;
353 #ifndef _WIN32
354     case UHI_link:
355         GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]);
356         gpr[2] = link(p, p2);
357         gpr[3] = errno_mips(errno);
358         FREE_TARGET_STRING(p2, gpr[5]);
359         FREE_TARGET_STRING(p, gpr[4]);
360         break;
361 #endif
362     default:
363         fprintf(stderr, "Unknown UHI operation %d\n", op);
364         abort();
365     }
366     return;
367 }
368