1 /*
2 * Copyright 2018 The Hafnium Authors.
3 *
4 * Use of this source code is governed by a BSD-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/BSD-3-Clause.
7 */
8
9 #include "hf/dlog.h"
10
11 #include <stdbool.h>
12 #include <stddef.h>
13
14 #include "hf/ffa.h"
15 #include "hf/spinlock.h"
16 #include "hf/std.h"
17 #include "hf/stdout.h"
18
19 /* Keep macro alignment */
20 /* clang-format off */
21
22 #define FLAG_SPACE 0x01
23 #define FLAG_ZERO 0x02
24 #define FLAG_MINUS 0x04
25 #define FLAG_PLUS 0x08
26 #define FLAG_ALT 0x10
27 #define FLAG_UPPER 0x20
28 #define FLAG_NEG 0x40
29
30 #define DLOG_MAX_STRING_LENGTH 64
31
32 /* clang-format on */
33
34 static bool dlog_lock_enabled = false;
35 static struct spinlock sl = SPINLOCK_INIT;
36
37 /*
38 * These global variables for the log buffer are not static because a test needs
39 * to access them directly.
40 */
41 size_t dlog_buffer_offset;
42 char dlog_buffer[DLOG_BUFFER_SIZE];
43
44 /**
45 * Takes the lock, if it is enabled.
46 */
lock(void)47 static void lock(void)
48 {
49 if (dlog_lock_enabled) {
50 sl_lock(&sl);
51 }
52 }
53
54 /**
55 * Releases the lock, if it is enabled.
56 */
unlock(void)57 static void unlock(void)
58 {
59 if (dlog_lock_enabled) {
60 sl_unlock(&sl);
61 }
62 }
63
64 /**
65 * Enables the lock protecting the serial device.
66 */
dlog_enable_lock(void)67 void dlog_enable_lock(void)
68 {
69 dlog_lock_enabled = true;
70 }
71
dlog_putchar(char c)72 static void dlog_putchar(char c)
73 {
74 dlog_buffer[dlog_buffer_offset] = c;
75 dlog_buffer_offset = (dlog_buffer_offset + 1) % DLOG_BUFFER_SIZE;
76 stdout_putchar(c);
77 }
78
79 /**
80 * Prints a raw string to the debug log and returns its length.
81 */
print_raw_string(const char * str)82 static size_t print_raw_string(const char *str)
83 {
84 const char *c = str;
85
86 while (*c != '\0') {
87 dlog_putchar(*c++);
88 }
89
90 return c - str;
91 }
92
93 /**
94 * Prints a formatted string to the debug log. The format includes a minimum
95 * width, the fill character, and flags (whether to align to left or right).
96 *
97 * str is the full string, while suffix is a pointer within str that indicates
98 * where the suffix begins. This is used when printing right-aligned numbers
99 * with a zero fill; for example, -10 with width 4 should be padded to -010,
100 * so suffix would point to index one of the "-10" string .
101 */
print_string(const char * str,const char * suffix,size_t width,int flags,char fill)102 static void print_string(const char *str, const char *suffix, size_t width,
103 int flags, char fill)
104 {
105 size_t len = suffix - str;
106
107 /* Print the string up to the beginning of the suffix. */
108 while (str != suffix) {
109 dlog_putchar(*str++);
110 }
111
112 if (flags & FLAG_MINUS) {
113 /* Left-aligned. Print suffix, then print padding if needed. */
114 len += print_raw_string(suffix);
115 while (len < width) {
116 dlog_putchar(' ');
117 len++;
118 }
119 return;
120 }
121
122 /* Fill until we reach the desired length. */
123 len += strnlen_s(suffix, DLOG_MAX_STRING_LENGTH);
124 while (len < width) {
125 dlog_putchar(fill);
126 len++;
127 }
128
129 /* Now print the rest of the string. */
130 print_raw_string(suffix);
131 }
132
133 /**
134 * Prints a number to the debug log. The caller specifies the base, its minimum
135 * width and printf-style flags.
136 */
print_num(size_t v,size_t base,size_t width,int flags)137 static void print_num(size_t v, size_t base, size_t width, int flags)
138 {
139 static const char *digits_lower = "0123456789abcdefx";
140 static const char *digits_upper = "0123456789ABCDEFX";
141 const char *d = (flags & FLAG_UPPER) ? digits_upper : digits_lower;
142 char buf[DLOG_MAX_STRING_LENGTH];
143 char *ptr = &buf[sizeof(buf) - 1];
144 char *num;
145 *ptr = '\0';
146 do {
147 --ptr;
148 *ptr = d[v % base];
149 v /= base;
150 } while (v);
151
152 /* Num stores where the actual number begins. */
153 num = ptr;
154
155 /* Add prefix if requested. */
156 if (flags & FLAG_ALT) {
157 switch (base) {
158 case 16:
159 ptr -= 2;
160 ptr[0] = '0';
161 ptr[1] = d[16];
162 break;
163
164 case 8:
165 ptr--;
166 *ptr = '0';
167 break;
168 }
169 }
170
171 /* Add sign if requested. */
172 if (flags & FLAG_NEG) {
173 *--ptr = '-';
174 } else if (flags & FLAG_PLUS) {
175 *--ptr = '+';
176 } else if (flags & FLAG_SPACE) {
177 *--ptr = ' ';
178 }
179 if (flags & FLAG_ZERO) {
180 print_string(ptr, num, width, flags, '0');
181 } else {
182 print_string(ptr, ptr, width, flags, ' ');
183 }
184 }
185
186 /**
187 * Parses the optional flags field of a printf-style format. It returns the spot
188 * on the string where a non-flag character was found.
189 */
parse_flags(const char * p,int * flags)190 static const char *parse_flags(const char *p, int *flags)
191 {
192 for (;;) {
193 switch (*p) {
194 case ' ':
195 *flags |= FLAG_SPACE;
196 break;
197
198 case '0':
199 *flags |= FLAG_ZERO;
200 break;
201
202 case '-':
203 *flags |= FLAG_MINUS;
204 break;
205
206 case '+':
207 *flags |= FLAG_PLUS;
208
209 case '#':
210 *flags |= FLAG_ALT;
211 break;
212
213 default:
214 return p;
215 }
216 p++;
217 }
218 }
219
220 /**
221 * Send the contents of the given VM's log buffer to the log, preceded by the VM
222 * ID and followed by a newline.
223 */
dlog_flush_vm_buffer(ffa_vm_id_t id,char buffer[],size_t length)224 void dlog_flush_vm_buffer(ffa_vm_id_t id, char buffer[], size_t length)
225 {
226 lock();
227
228 print_raw_string("VM ");
229 print_num(id, 16, 0, 0);
230 print_raw_string(": ");
231
232 for (size_t i = 0; i < length; ++i) {
233 dlog_putchar(buffer[i]);
234 buffer[i] = '\0';
235 }
236 dlog_putchar('\n');
237
238 unlock();
239 }
240
241 /**
242 * Same as "dlog", except that arguments are passed as a va_list
243 */
vdlog(const char * fmt,va_list args)244 void vdlog(const char *fmt, va_list args)
245 {
246 const char *p;
247 size_t w;
248 int flags;
249 char buf[2];
250
251 lock();
252
253 for (p = fmt; *p; p++) {
254 switch (*p) {
255 default:
256 dlog_putchar(*p);
257 break;
258
259 case '%':
260 /* Read optional flags. */
261 flags = 0;
262 p = parse_flags(p + 1, &flags) - 1;
263
264 /* Read the minimum width, if one is specified. */
265 w = 0;
266 while (p[1] >= '0' && p[1] <= '9') {
267 w = (w * 10) + (p[1] - '0');
268 p++;
269 }
270
271 /* Read minimum width from arguments. */
272 if (w == 0 && p[1] == '*') {
273 int v = va_arg(args, int);
274
275 if (v >= 0) {
276 w = v;
277 } else {
278 w = -v;
279 flags |= FLAG_MINUS;
280 }
281 p++;
282 }
283
284 /* Handle the format specifier. */
285 switch (p[1]) {
286 case 's': {
287 char *str = va_arg(args, char *);
288
289 print_string(str, str, w, flags, ' ');
290 p++;
291 } break;
292
293 case 'd':
294 case 'i': {
295 int v = va_arg(args, int);
296
297 if (v < 0) {
298 flags |= FLAG_NEG;
299 v = -v;
300 }
301
302 print_num((size_t)v, 10, w, flags);
303 p++;
304 } break;
305
306 case 'X':
307 flags |= FLAG_UPPER;
308 print_num(va_arg(args, size_t), 16, w, flags);
309 p++;
310 break;
311
312 case 'p':
313 print_num(va_arg(args, size_t), 16,
314 sizeof(size_t) * 2, FLAG_ZERO);
315 p++;
316 break;
317
318 case 'x':
319 print_num(va_arg(args, size_t), 16, w, flags);
320 p++;
321 break;
322
323 case 'u':
324 print_num(va_arg(args, size_t), 10, w, flags);
325 p++;
326 break;
327
328 case 'o':
329 print_num(va_arg(args, size_t), 8, w, flags);
330 p++;
331 break;
332
333 case 'c':
334 buf[1] = 0;
335 buf[0] = va_arg(args, int);
336 print_string(buf, buf, w, flags, ' ');
337 p++;
338 break;
339
340 case '%':
341 break;
342
343 default:
344 dlog_putchar('%');
345 }
346
347 break;
348 }
349 }
350
351 unlock();
352 }
353
354 /**
355 * Prints the given format string to the debug log.
356 */
dlog(const char * fmt,...)357 void dlog(const char *fmt, ...)
358 {
359 va_list args;
360
361 va_start(args, fmt);
362 vdlog(fmt, args);
363 va_end(args);
364 }
365