arm: smh: Make semihosting trap calls more robust
[platform/kernel/u-boot.git] / arch / arm / lib / semihosting.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
4  * Copyright 2014 Broadcom Corporation
5  */
6
7 #include <common.h>
8 #include <log.h>
9 #include <semihosting.h>
10
11 #define SYSOPEN         0x01
12 #define SYSCLOSE        0x02
13 #define SYSWRITEC       0x03
14 #define SYSWRITE0       0x04
15 #define SYSWRITE        0x05
16 #define SYSREAD         0x06
17 #define SYSREADC        0x07
18 #define SYSISERROR      0x08
19 #define SYSSEEK         0x0A
20 #define SYSFLEN         0x0C
21 #define SYSERRNO        0x13
22
23 /*
24  * Macro to force the compiler to *populate* memory (for an array or struct)
25  * before passing the pointer to an inline assembly call.
26  */
27 #define USE_PTR(ptr) *(const char (*)[]) (ptr)
28
29 #if defined(CONFIG_ARM64)
30         #define SMH_TRAP "hlt #0xf000"
31 #elif defined(CONFIG_CPU_V7M)
32         #define SMH_TRAP "bkpt #0xAB"
33 #elif defined(CONFIG_SYS_THUMB_BUILD)
34         #define SMH_TRAP "svc #0xab"
35 #else
36         #define SMH_TRAP "svc #0x123456"
37 #endif
38
39 /*
40  * Call the handler
41  */
42 static noinline long smh_trap(unsigned int sysnum, void *addr)
43 {
44         register long result asm("r0");
45
46         /*
47          * We need a memory clobber (aka compiler barrier) for two reasons:
48          * - The compiler needs to populate any data structures pointed to
49          *   by "addr" *before* the trap instruction is called.
50          * - At least the SYSREAD function puts the result into memory pointed
51          *   to by "addr", so the compiler must not use a cached version of
52          *   the previous content, after the call has finished.
53          */
54         asm volatile (SMH_TRAP
55                       : "=r" (result)
56                       : "0"(sysnum), "r"(USE_PTR(addr))
57                       : "memory");
58
59         return result;
60 }
61
62 #if CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)
63 static bool _semihosting_enabled = true;
64 static bool try_semihosting = true;
65
66 bool semihosting_enabled(void)
67 {
68         if (try_semihosting) {
69                 smh_trap(SYSERRNO, NULL);
70                 try_semihosting = false;
71         }
72
73         return _semihosting_enabled;
74 }
75
76 void disable_semihosting(void)
77 {
78         _semihosting_enabled = false;
79 }
80 #endif
81
82 /**
83  * smh_errno() - Read the host's errno
84  *
85  * This gets the value of the host's errno and negates it. The host's errno may
86  * or may not be set, so only call this function if a previous semihosting call
87  * has failed.
88  *
89  * Return: a negative error value
90  */
91 static int smh_errno(void)
92 {
93         long ret = smh_trap(SYSERRNO, NULL);
94
95         if (ret > 0 && ret < INT_MAX)
96                 return -ret;
97         return -EIO;
98 }
99
100 long smh_open(const char *fname, enum smh_open_mode mode)
101 {
102         long fd;
103         struct smh_open_s {
104                 const char *fname;
105                 unsigned long mode;
106                 size_t len;
107         } open;
108
109         debug("%s: file \'%s\', mode \'%u\'\n", __func__, fname, mode);
110
111         open.fname = fname;
112         open.len = strlen(fname);
113         open.mode = mode;
114
115         /* Open the file on the host */
116         fd = smh_trap(SYSOPEN, &open);
117         if (fd == -1)
118                 return smh_errno();
119         return fd;
120 }
121
122 /**
123  * struct smg_rdwr_s - Arguments for read and write
124  * @fd: A file descriptor returned from smh_open()
125  * @memp: Pointer to a buffer of memory of at least @len bytes
126  * @len: The number of bytes to read or write
127  */
128 struct smh_rdwr_s {
129         long fd;
130         void *memp;
131         size_t len;
132 };
133
134 long smh_read(long fd, void *memp, size_t len)
135 {
136         long ret;
137         struct smh_rdwr_s read;
138
139         debug("%s: fd %ld, memp %p, len %zu\n", __func__, fd, memp, len);
140
141         read.fd = fd;
142         read.memp = memp;
143         read.len = len;
144
145         ret = smh_trap(SYSREAD, &read);
146         if (ret < 0)
147                 return smh_errno();
148         return len - ret;
149 }
150
151 long smh_write(long fd, const void *memp, size_t len, ulong *written)
152 {
153         long ret;
154         struct smh_rdwr_s write;
155
156         debug("%s: fd %ld, memp %p, len %zu\n", __func__, fd, memp, len);
157
158         write.fd = fd;
159         write.memp = (void *)memp;
160         write.len = len;
161
162         ret = smh_trap(SYSWRITE, &write);
163         *written = len - ret;
164         if (ret)
165                 return smh_errno();
166         return 0;
167 }
168
169 long smh_close(long fd)
170 {
171         long ret;
172
173         debug("%s: fd %ld\n", __func__, fd);
174
175         ret = smh_trap(SYSCLOSE, &fd);
176         if (ret == -1)
177                 return smh_errno();
178         return 0;
179 }
180
181 long smh_flen(long fd)
182 {
183         long ret;
184
185         debug("%s: fd %ld\n", __func__, fd);
186
187         ret = smh_trap(SYSFLEN, &fd);
188         if (ret == -1)
189                 return smh_errno();
190         return ret;
191 }
192
193 long smh_seek(long fd, long pos)
194 {
195         long ret;
196         struct smh_seek_s {
197                 long fd;
198                 long pos;
199         } seek;
200
201         debug("%s: fd %ld pos %ld\n", __func__, fd, pos);
202
203         seek.fd = fd;
204         seek.pos = pos;
205
206         ret = smh_trap(SYSSEEK, &seek);
207         if (ret)
208                 return smh_errno();
209         return 0;
210 }
211
212 int smh_getc(void)
213 {
214         return smh_trap(SYSREADC, NULL);
215 }
216
217 void smh_putc(char ch)
218 {
219         smh_trap(SYSWRITEC, &ch);
220 }
221
222 void smh_puts(const char *s)
223 {
224         smh_trap(SYSWRITE0, (char *)s);
225 }