Software APIs
print.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
7#include <stdarg.h>
8#include <stdbool.h>
9#include <stddef.h>
10#include <stdint.h>
11
14#include "sw/device/lib/base/status.h"
17
18#include "spi_device_regs.h" // Generated.
19
20// This is declared as an enum to force the values to be
21// compile-time constants, but the type is otherwise not
22// used for anything.
23enum {
24 // Standard format specifiers.
25 kPercent = '%',
26 kCharacter = 'c',
27 kString = 's',
28 kSignedDec1 = 'd',
29 kSignedDec2 = 'i',
30 kUnsignedOct = 'o',
31 kUnsignedHexLow = 'x',
32 kUnsignedHexHigh = 'X',
33 kUnsignedDec = 'u',
34 kPointer = 'p',
35
36 // Verilog-style format specifiers.
37 kSvBinary = 'b',
38 kSvHexLow = 'h',
39 kSvHexHigh = 'H',
40
41 // Other non-standard specifiers.
42 kHexLeLow = 'y',
43 kHexLeHigh = 'Y',
44 kStatusResult = 'r',
45 kFourCC = 'C',
46};
47
48// NOTE: all of the lengths of the strings below are given so that the NUL
49// terminator is left off; that way, `sizeof(kConst)` does not include it.
50static const char kDigitsLow[16] = "0123456789abcdef";
51static const char kDigitsHigh[16] = "0123456789ABCDEF";
52
53static const char kErrorNul[17] = "%<unexpected nul>";
54static const char kUnknownSpec[15] = "%<unknown spec>";
55static const char kErrorTooWide[12] = "%<bad width>";
56
57static size_t base_dev_null(void *data, const char *buf, size_t len) {
58 return len;
59}
60static buffer_sink_t base_stdout = {
61 .data = NULL,
62 // Note: Using `&base_dev_null` causes this variable to be placed in the
63 // .data section and triggers the assertion in rom.ld.
64 .sink = NULL,
65};
66
67void base_set_stdout(buffer_sink_t out) {
68 if (out.sink == NULL) {
69 out.sink = &base_dev_null;
70 }
71 base_stdout = out;
72}
73
74static const size_t kSpiDeviceReadBufferSizeBytes =
75 SPI_DEVICE_PARAM_SRAM_READ_BUFFER_DEPTH * sizeof(uint32_t);
76static const size_t kSpiDeviceFrameHeaderSizeBytes = 12;
77static const size_t kSpiDeviceBufferPreservedSizeBytes =
78 kSpiDeviceFrameHeaderSizeBytes;
79static const size_t kSpiDeviceMaxFramePayloadSizeBytes =
80 kSpiDeviceReadBufferSizeBytes - kSpiDeviceFrameHeaderSizeBytes -
81 kSpiDeviceBufferPreservedSizeBytes - 4;
82static const uint32_t kSpiDeviceFrameMagicNumber = 0xa5a5beef;
83static uint32_t spi_device_frame_num = 0;
84
85static status_t spi_device_send_data(dif_spi_device_handle_t *spi_device,
86 const uint8_t *buf, size_t len,
87 size_t address) {
88 if (len == 0) {
89 return OK_STATUS();
90 }
91
92 size_t space_to_end_of_buffer = kSpiDeviceReadBufferSizeBytes - address;
93 size_t first_part_size =
94 space_to_end_of_buffer < len ? space_to_end_of_buffer : len;
95
96 TRY(dif_spi_device_write_flash_buffer(spi_device,
98 address, first_part_size, buf));
99
100 // Handle wrap-around.
101 if (first_part_size < len) {
102 size_t second_part_size = len - first_part_size;
103 TRY(dif_spi_device_write_flash_buffer(
104 spi_device, kDifSpiDeviceFlashBufferTypeEFlash, 0, second_part_size,
105 (uint8_t *)(buf + first_part_size)));
106 }
107
108 return OK_STATUS();
109}
110
111/**
112 * Sends data out of the SPI device.
113 *
114 * Data is packaged into a frame that is described below.
115 * The host side reads the header first, then decides how many words
116 * to read from the data section.
117 *
118 * -----------------------------------------------
119 * | Magic Number | 4-bytes | |
120 * -----------------------------------| |
121 * | Frame Number | 4-bytes | Header |
122 * -----------------------------------| |
123 * | Data Length (bytes) | 4-bytes | |
124 * -----------------------------------|----------|
125 * | Data (word aligned) | |
126 * -----------------------------------| Data |
127 * | 0xFF Pad Bytes | <4-bytes | |
128 * -----------------------------------|----------|
129 */
130static size_t spi_device_send_frame(void *data, const char *buf, size_t len) {
132 const size_t data_packet_size_bytes = ((len + 3u) & ~3u);
133 const size_t frame_size_bytes =
134 kSpiDeviceFrameHeaderSizeBytes + data_packet_size_bytes;
135 uint8_t frame_header_bytes[kSpiDeviceFrameHeaderSizeBytes];
136
137 static uint32_t next_write_address = 0;
138
139 if (frame_size_bytes >= kSpiDeviceReadBufferSizeBytes) {
140 return 0;
141 }
142
143 // Add the magic bytes.
144 for (size_t i = 0; i < 4; ++i) {
145 frame_header_bytes[i] = (kSpiDeviceFrameMagicNumber >> (i * 8)) & 0xff;
146 }
147 // Add the frame number.
148 for (size_t i = 0; i < 4; ++i) {
149 frame_header_bytes[i + 4] = (spi_device_frame_num >> (i * 8)) & 0xff;
150 }
151 // Add the data length.
152 for (size_t i = 0; i < 4; ++i) {
153 frame_header_bytes[i + 8] = (len >> (i * 8)) & 0xff;
154 }
155
156 uint32_t available_buffer_size = 0;
157 do {
158 uint32_t last_read_address = 0;
159 if (dif_spi_device_get_last_read_address(spi_device, &last_read_address) !=
160 kDifOk) {
161 return 0;
162 }
163
164 // Adjust the last read address. The host continuously reads from the read
165 // buffer, unaware of whether a new frame has arrived. This could result in
166 // the reported last_read_address being the header size ahead of the actual
167 // address of the last valid frame if all the frames in the read buffer has
168 // been consumed by the host. While it's harmless to adjust the last read
169 // address even if the reported value is correct, doing so might temporarily
170 // underestimate the available buffer size by the size of a header.
171 uint32_t adjusted_last_read_address =
172 (kSpiDeviceReadBufferSizeBytes + last_read_address -
173 kSpiDeviceFrameHeaderSizeBytes) %
174 kSpiDeviceReadBufferSizeBytes;
175 uint32_t next_read_address = ((adjusted_last_read_address + 1) & ~3u) %
176 kSpiDeviceReadBufferSizeBytes;
177
178 if (next_read_address > next_write_address) {
179 available_buffer_size = next_read_address - next_write_address - 1;
180 } else {
181 available_buffer_size =
182 next_read_address +
183 (kSpiDeviceReadBufferSizeBytes - next_write_address) - 1;
184 }
185 } while ((frame_size_bytes + kSpiDeviceBufferPreservedSizeBytes) >
186 available_buffer_size);
187
188 // Send aligned data.
189 size_t data_write_address =
190 (next_write_address + kSpiDeviceFrameHeaderSizeBytes) %
191 kSpiDeviceReadBufferSizeBytes;
192 size_t aligned_data_len = len & (~3u);
193 if (!status_ok(spi_device_send_data(spi_device, (uint8_t *)buf,
194 aligned_data_len, data_write_address))) {
195 return 0;
196 }
197
198 // Send unaligned data.
199 if (len != aligned_data_len) {
200 uint8_t pad_bytes[4] = {0xff, 0xff, 0xff, 0xff};
201 size_t pad_write_address =
202 (data_write_address + aligned_data_len) % kSpiDeviceReadBufferSizeBytes;
203
204 for (size_t i = 0; i + aligned_data_len < len; i++) {
205 pad_bytes[i] = buf[aligned_data_len + i];
206 }
207 if (!status_ok(spi_device_send_data(spi_device, pad_bytes, 4,
208 pad_write_address))) {
209 return 0;
210 }
211 }
212
213 // Send frame header.
214 if (!status_ok(spi_device_send_data(spi_device, frame_header_bytes,
215 kSpiDeviceFrameHeaderSizeBytes,
216 next_write_address))) {
217 return 0;
218 }
219
220 next_write_address =
221 (next_write_address + frame_size_bytes) % kSpiDeviceReadBufferSizeBytes;
222 spi_device_frame_num++;
223
224 return len;
225}
226
227static size_t base_dev_spi_device(void *data, const char *buf, size_t len) {
228 size_t write_data_len = 0;
229
230 while (write_data_len < len) {
231 size_t payload_len = len - write_data_len;
232 if (payload_len > kSpiDeviceMaxFramePayloadSizeBytes) {
233 payload_len = kSpiDeviceMaxFramePayloadSizeBytes;
234 }
235
236 if (spi_device_send_frame(data, buf + write_data_len, payload_len) ==
237 payload_len) {
238 write_data_len += payload_len;
239 }
240 }
241
242 return write_data_len;
243}
244
245sink_func_ptr get_spi_device_sink(void) { return &base_dev_spi_device; }
246
247static size_t base_dev_uart(void *data, const char *buf, size_t len) {
248 const dif_uart_t *uart = (const dif_uart_t *)data;
249 for (size_t i = 0; i < len; ++i) {
250 if (dif_uart_byte_send_polled(uart, (uint8_t)buf[i]) != kDifOk) {
251 return i;
252 }
253 }
254 return len;
255}
256
257sink_func_ptr get_uart_sink(void) { return &base_dev_uart; }
258
259void base_spi_device_stdout(const dif_spi_device_handle_t *spi_device) {
260 // Reset the frame counter.
261 spi_device_frame_num = 0;
262 base_set_stdout((buffer_sink_t){.data = (void *)spi_device,
263 .sink = &base_dev_spi_device});
264}
265
266void base_uart_stdout(const dif_uart_t *uart) {
267 base_set_stdout(
268 (buffer_sink_t){.data = (void *)uart, .sink = &base_dev_uart});
269}
270
271size_t base_printf(const char *format, ...) {
272 va_list args;
273 va_start(args, format);
274 size_t bytes_left = base_vprintf(format, args);
275 va_end(args);
276 return bytes_left;
277}
278
279size_t base_vprintf(const char *format, va_list args) {
280 return base_vfprintf(base_stdout, format, args);
281}
282
283typedef struct snprintf_captures_t {
284 char *buf;
285 size_t bytes_left;
287
288static size_t snprintf_sink(void *data, const char *buf, size_t len) {
289 snprintf_captures_t *captures = (snprintf_captures_t *)data;
290 if (captures->bytes_left == 0) {
291 return 0;
292 }
293
294 if (len > captures->bytes_left) {
295 len = captures->bytes_left;
296 }
297 memcpy(captures->buf, buf, len);
298 captures->buf += len;
299 captures->bytes_left -= len;
300 return len;
301}
302
303size_t base_snprintf(char *buf, size_t len, const char *format, ...) {
304 va_list args;
305 va_start(args, format);
306
307 snprintf_captures_t data = {
308 .buf = buf,
309 .bytes_left = len,
310 };
311 buffer_sink_t out = {
312 .data = &data,
313 .sink = &snprintf_sink,
314 };
315 size_t bytes_left = base_vfprintf(out, format, args);
316 va_end(args);
317 return bytes_left;
318}
319
320size_t base_fprintf(buffer_sink_t out, const char *format, ...) {
321 va_list args;
322 va_start(args, format);
323 size_t bytes_left = base_vfprintf(out, format, args);
324 va_end(args);
325 return bytes_left;
326}
327
328/**
329 * Consumes characters from `format` until a '%' or NUL is reached. All
330 * characters seen before that are then sinked into `out`.
331 *
332 * @param out the sink to write bytes to.
333 * @param format a pointer to the format string to consume a prefix of.
334 * @param[out] bytes_written out param for the number of bytes writen to `out`.
335 * @return true if an unprocessed '%' was found.
336 */
337static bool consume_until_percent(buffer_sink_t out, const char **format,
338 size_t *bytes_written) {
339 size_t text_len = 0;
340 while (true) {
341 char c = (*format)[text_len];
342 if (c == '\0' || c == kPercent) {
343 if (text_len > 0) {
344 *bytes_written += out.sink(out.data, *format, text_len);
345 }
346 *format += text_len;
347 return c != '\0';
348 }
349 ++text_len;
350 }
351}
352
353/**
354 * Represents a parsed format specifier.
355 */
356typedef struct format_specifier {
357 char type;
358 size_t width;
359 char padding;
360 bool is_nonstd;
361} format_specifier_t;
362
363/**
364 * Consumes characters from `format` until a complete format specifier is
365 * parsed. See the documentation in `print.h` for full syntax.
366 *
367 * @param out the sink to write bytes to.
368 * @param format a pointer to the format string to consume a prefix of.
369 * @param[out] spec out param for the specifier.
370 * @return whether the parse succeeded.
371 */
372static bool consume_format_specifier(buffer_sink_t out, const char **format,
373 size_t *bytes_written,
374 format_specifier_t *spec) {
375 *spec = (format_specifier_t){0};
376
377 // Consume the percent sign.
378 ++(*format);
379
380 // Parse a ! to detect an OpenTitan-specific extension (that isn't one
381 // of the Verilog ones...).
382 if ((*format)[0] == '!') {
383 spec->is_nonstd = true;
384 ++(*format);
385 }
386
387 // Attempt to parse out an unsigned, decimal number, a "width",
388 // after the percent sign; the format specifier is the character
389 // immediately after this width.
390 //
391 // `spec->padding` is used to indicate whether we've seen a width;
392 // if nonzero, we have an actual width.
393 size_t spec_len = 0;
394 while (true) {
395 char c = (*format)[spec_len];
396 if (c == '\0') {
397 *bytes_written += out.sink(out.data, kErrorNul, sizeof(kErrorNul));
398 return false;
399 }
400 if (c < '0' || c > '9') {
401 break;
402 }
403 if (spec->padding == 0) {
404 if (c == '0') {
405 spec->padding = '0';
406 ++spec_len;
407 continue;
408 }
409 spec->padding = ' ';
410 }
411 spec->width *= 10;
412 spec->width += (c - '0');
413 ++spec_len;
414 }
415
416 if ((spec->width == 0 && spec->padding != 0) || spec->width > 32) {
417 *bytes_written += out.sink(out.data, kErrorTooWide, sizeof(kErrorTooWide));
418 return false;
419 }
420
421 spec->type = (*format)[spec_len];
422 *format += spec_len + 1;
423 return true;
424}
425
426/**
427 * Write the digits of `value` onto `out`.
428 *
429 * @param out the sink to write bytes to.
430 * @param value the value to "stringify".
431 * @param width the minimum width to print; going below will result in writing
432 * out zeroes.
433 * @param base the base to express `value` in.
434 * @param glyphs an array of characters to use as the digits of a number, which
435 * should be at least ast long as `base`.
436 * @return the number of bytes written.
437 */
438static size_t write_digits(buffer_sink_t out, uint32_t value, uint32_t width,
439 char padding, uint32_t base, const char *glyphs) {
440 // All allocations are done relative to a buffer that could hold the longest
441 // textual representation of a number: ~0x0 in base 2, i.e., 32 ones.
442 static const int kWordBits = sizeof(uint32_t) * 8;
443 char buffer[kWordBits];
444
445 size_t len = 0;
446 if (value == 0) {
447 buffer[kWordBits - 1] = glyphs[0];
448 ++len;
449 }
450 while (value > 0) {
451 uint32_t digit = value % base;
452 value /= base;
453 buffer[kWordBits - 1 - len] = glyphs[digit];
454 ++len;
455 }
456 width = width == 0 ? 1 : width;
457 width = width > kWordBits ? kWordBits : width;
458 while (len < width) {
459 buffer[kWordBits - len - 1] = padding;
460 ++len;
461 }
462 return out.sink(out.data, buffer + (kWordBits - len), len);
463}
464
465static size_t write_status(buffer_sink_t out, status_t value, bool as_json) {
466 // The module id is defined to be 3 chars long.
467 char mod[] = {'"', 0, 0, 0, '"', ','};
468 int32_t arg;
469 const char *start;
470 bool err = status_extract(value, &start, &arg, &mod[1]);
471
472 // strlen of the status code.
473 const char *end = start;
474 while (*end)
475 end++;
476 size_t len = 0;
477
478 len += out.sink(out.data, "{\"", as_json ? 2 : 0);
479 len += out.sink(out.data, start, (size_t)(end - start));
480 len += out.sink(out.data, "\"", as_json ? 1 : 0);
481
482 len += out.sink(out.data, ":", 1);
483 if (err) {
484 // All error codes include the module identifier.
485 len += out.sink(out.data, "[", 1);
486 len += out.sink(out.data, mod, sizeof(mod));
487 len += write_digits(out, (uint32_t)arg, 0, 0, 10, kDigitsLow);
488 len += out.sink(out.data, "]", 1);
489 } else {
490 // Ok codes include only the arg
491 len += write_digits(out, (uint32_t)arg, 0, 0, 10, kDigitsLow);
492 }
493 len += out.sink(out.data, "}", as_json ? 1 : 0);
494 return len;
495}
496
497/**
498 * Hexdumps `bytes` onto `out`.
499 *
500 * @param out the sink to write bytes to.
501 * @param bytes the bytes to dump.
502 * @param len the number of bytes to dump.
503 * @param width the minimum width to print; going below will result in writing
504 * out zeroes.
505 * @param padding the character to use for padding.
506 * @param big_endian whether to print in big-endian order (i.e. as %x would).
507 * @param glyphs an array of characters to use as the digits of a number, which
508 * should be at least ast long as `base`.
509 * @return the number of bytes written.
510 */
511static size_t hex_dump(buffer_sink_t out, const char *bytes, size_t len,
512 uint32_t width, char padding, bool big_endian,
513 const char *glyphs) {
514 size_t bytes_written = 0;
515 char buf[32];
516 size_t buffered = 0;
517 if (len < width) {
518 width -= len;
519 memset(buf, padding, sizeof(buf));
520 while (width > 0) {
521 size_t to_write = width > ARRAYSIZE(buf) ? 32 : width;
522 bytes_written += out.sink(out.data, buf, to_write);
523 width -= to_write;
524 }
525 }
526
527 for (size_t i = 0; i < len; ++i) {
528 size_t idx = big_endian ? len - i - 1 : i;
529 buf[buffered] = glyphs[(bytes[idx] >> 4) & 0xf];
530 buf[buffered + 1] = glyphs[bytes[idx] & 0xf];
531 buffered += 2;
532
533 if (buffered == ARRAYSIZE(buf)) {
534 bytes_written += out.sink(out.data, buf, buffered);
535 buffered = 0;
536 }
537 }
538
539 if (buffered != 0) {
540 bytes_written += out.sink(out.data, buf, buffered);
541 }
542 return bytes_written;
543}
544
545/**
546 * Prints out the next entry in `args` according to `spec`.
547 *
548 * This function assumes that `spec` accurately describes the next entry in
549 * `args`.
550 *
551 * @param out the sink to write bytes to.
552 * @param spec the specifier to use for stringifying.
553 * @param[out] bytes_written out param for the number of bytes writen to `out`.
554 * @param args the list to pull an entry from.
555 */
556static void process_specifier(buffer_sink_t out, format_specifier_t spec,
557 size_t *bytes_written, va_list *args) {
558 // Switch on the specifier. At this point, we assert that there is
559 // an initialized value of correct type in the VA list; if it is
560 // missing, the caller has caused UB.
561 switch (spec.type) {
562 case kPercent: {
563 if (spec.is_nonstd) {
564 goto bad_spec;
565 }
566 *bytes_written += out.sink(out.data, "%", 1);
567 break;
568 }
569 case kCharacter: {
570 if (spec.is_nonstd) {
571 goto bad_spec;
572 }
573 char value = (char)va_arg(*args, uint32_t);
574 *bytes_written += out.sink(out.data, &value, 1);
575 break;
576 }
577 case kFourCC: {
578 uint32_t value = va_arg(*args, uint32_t);
579 for (size_t i = 0; i < sizeof(uint32_t); ++i, value >>= 8) {
580 uint8_t ch = (uint8_t)value;
581 if (ch >= 32 && ch < 127) {
582 *bytes_written += out.sink(out.data, (const char *)&ch, 1);
583 } else {
584 *bytes_written += out.sink(out.data, "\\x", 2);
585 *bytes_written += out.sink(out.data, &kDigitsLow[ch >> 4], 1);
586 *bytes_written += out.sink(out.data, &kDigitsLow[ch & 15], 1);
587 }
588 }
589 break;
590 }
591 case kString: {
592 size_t len = 0;
593 if (spec.is_nonstd) {
594 // This implements %!s.
595 len = va_arg(*args, size_t);
596 }
597
598 char *value = va_arg(*args, char *);
599 while (!spec.is_nonstd && value[len] != '\0') {
600 // This implements %s.
601 ++len;
602 }
603
604 *bytes_written += out.sink(out.data, value, len);
605 break;
606 }
607 case kSignedDec1:
608 case kSignedDec2: {
609 if (spec.is_nonstd) {
610 goto bad_spec;
611 }
612 uint32_t value = va_arg(*args, uint32_t);
613 if (((int32_t)value) < 0) {
614 *bytes_written += out.sink(out.data, "-", 1);
615 value = -value;
616 }
617 *bytes_written +=
618 write_digits(out, value, spec.width, spec.padding, 10, kDigitsLow);
619 break;
620 }
621 case kUnsignedOct: {
622 if (spec.is_nonstd) {
623 goto bad_spec;
624 }
625 uint32_t value = va_arg(*args, uint32_t);
626 *bytes_written +=
627 write_digits(out, value, spec.width, spec.padding, 8, kDigitsLow);
628 break;
629 }
630 case kPointer: {
631 if (spec.is_nonstd) {
632 goto bad_spec;
633 }
634 // Pointers are formatted as 0x<hex digits>, where the width is always
635 // set to the number necessary to represent a pointer on the current
636 // platform, that is, the size of uintptr_t in nybbles. For example, on
637 // different architecutres the null pointer prints as
638 // - rv32imc: 0x00000000 (four bytes, eight nybbles).
639 // - amd64: 0x0000000000000000 (eight bytes, sixteen nybbles).
640 *bytes_written += out.sink(out.data, "0x", 2);
641 uintptr_t value = va_arg(*args, uintptr_t);
642 *bytes_written +=
643 write_digits(out, value, sizeof(uintptr_t) * 2, '0', 16, kDigitsLow);
644 break;
645 }
646 case kUnsignedHexLow:
647 if (spec.is_nonstd) {
648 size_t len = va_arg(*args, size_t);
649 char *value = va_arg(*args, char *);
650 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
651 /*big_endian=*/true, kDigitsLow);
652 break;
653 }
655 case kSvHexLow: {
656 uint32_t value = va_arg(*args, uint32_t);
657 *bytes_written +=
658 write_digits(out, value, spec.width, spec.padding, 16, kDigitsLow);
659 break;
660 }
661 case kUnsignedHexHigh:
662 if (spec.is_nonstd) {
663 size_t len = va_arg(*args, size_t);
664 char *value = va_arg(*args, char *);
665 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
666 /*big_endian=*/true, kDigitsHigh);
667 break;
668 }
670 case kSvHexHigh: {
671 uint32_t value = va_arg(*args, uint32_t);
672 *bytes_written +=
673 write_digits(out, value, spec.width, spec.padding, 16, kDigitsHigh);
674 break;
675 }
676 case kHexLeLow: {
677 if (!spec.is_nonstd) {
678 goto bad_spec;
679 }
680 size_t len = va_arg(*args, size_t);
681 char *value = va_arg(*args, char *);
682 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
683 /*big_endian=*/false, kDigitsLow);
684 break;
685 }
686 case kHexLeHigh: {
687 if (!spec.is_nonstd) {
688 goto bad_spec;
689 }
690 size_t len = va_arg(*args, size_t);
691 char *value = va_arg(*args, char *);
692 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
693 /*big_endian=*/false, kDigitsHigh);
694 break;
695 }
696 case kUnsignedDec: {
697 if (spec.is_nonstd) {
698 goto bad_spec;
699 }
700 uint32_t value = va_arg(*args, uint32_t);
701 *bytes_written +=
702 write_digits(out, value, spec.width, spec.padding, 10, kDigitsLow);
703 break;
704 }
705 case kSvBinary: {
706 if (spec.is_nonstd) {
707 // Bools passed into a ... function will be automatically promoted
708 // to int; va_arg(..., bool) is actually UB!
709 if (va_arg(*args, int) != 0) {
710 *bytes_written += out.sink(out.data, "true", 4);
711 } else {
712 *bytes_written += out.sink(out.data, "false", 5);
713 }
714 break;
715 }
716 uint32_t value = va_arg(*args, uint32_t);
717 *bytes_written +=
718 write_digits(out, value, spec.width, spec.padding, 2, kDigitsLow);
719 break;
720 }
721 case kStatusResult: {
722 status_t value = va_arg(*args, status_t);
723 *bytes_written += write_status(out, value, spec.is_nonstd);
724 break;
725 }
726 bad_spec: // Used with `goto` to bail out early.
727 default: {
728 *bytes_written += out.sink(out.data, kUnknownSpec, sizeof(kUnknownSpec));
729 }
730 }
731}
732
733size_t base_vfprintf(buffer_sink_t out, const char *format, va_list args) {
734 if (out.sink == NULL) {
735 out.sink = &base_dev_null;
736 }
737
738 // NOTE: This copy is necessary on amd64 and other platforms, where
739 // `va_list` is a fixed array type (and, as such, decays to a pointer in
740 // an argument list). On PSABI RV32IMC, however, `va_list` is a `void*`, so
741 // this is a copy of the pointer, not the array.
742 va_list args_copy;
743 va_copy(args_copy, args);
744
745 size_t bytes_written = 0;
746 while (format[0] != '\0') {
747 if (!consume_until_percent(out, &format, &bytes_written)) {
748 break;
749 }
750 format_specifier_t spec;
751 if (!consume_format_specifier(out, &format, &bytes_written, &spec)) {
752 break;
753 }
754
755 process_specifier(out, spec, &bytes_written, &args_copy);
756 }
757
758 va_end(args_copy);
759 return bytes_written;
760}
761
762const char kBaseHexdumpDefaultFmtAlphabet[256] =
763 // clang-format off
764 // First 32 characters are not printable.
765 "................................"
766 // Printable ASCII.
767 " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
768 // The rest of the range is also not printable (129 characters).
769 "................................................................"
770 "................................................................"
771 ".";
772// clang-format on
773
774static const base_hexdump_fmt_t kBaseHexdumpDefaultFmt = {
775 .bytes_per_word = 2,
776 .words_per_line = 8,
777 .alphabet = &kBaseHexdumpDefaultFmtAlphabet,
778};
779
780size_t base_hexdump(const char *buf, size_t len) {
781 return base_hexdump_with(kBaseHexdumpDefaultFmt, buf, len);
782}
783
784size_t base_snhexdump(char *out, size_t out_len, const char *buf, size_t len) {
785 return base_snhexdump_with(out, out_len, kBaseHexdumpDefaultFmt, buf, len);
786}
787
788size_t base_fhexdump(buffer_sink_t out, const char *buf, size_t len) {
789 return base_fhexdump_with(out, kBaseHexdumpDefaultFmt, buf, len);
790}
791
792size_t base_hexdump_with(base_hexdump_fmt_t fmt, const char *buf, size_t len) {
793 return base_fhexdump_with(base_stdout, fmt, buf, len);
794}
795
796size_t base_snhexdump_with(char *out, size_t out_len, base_hexdump_fmt_t fmt,
797 const char *buf, size_t len) {
798 snprintf_captures_t data = {
799 .buf = out,
800 .bytes_left = out_len,
801 };
802 buffer_sink_t sink = {
803 .data = &data,
804 .sink = &snprintf_sink,
805 };
806 return base_fhexdump_with(sink, fmt, buf, len);
807}
808
809size_t base_fhexdump_with(buffer_sink_t out, base_hexdump_fmt_t fmt,
810 const char *buf, size_t len) {
811 if (out.sink == NULL) {
812 out.sink = &base_dev_null;
813 }
814
815 size_t bytes_written = 0;
816 size_t bytes_per_line = fmt.bytes_per_word * fmt.words_per_line;
817
818 for (size_t line = 0; line < len; line += bytes_per_line) {
819 bytes_written += base_fprintf(out, "%08x:", line);
820
821 size_t chars_per_line = bytes_per_line * 2 + fmt.words_per_line;
822 size_t line_bytes_written = 0;
823 for (size_t word = 0; word < bytes_per_line; word += fmt.bytes_per_word) {
824 if (len < line + word) {
825 char spaces[16] = " ";
826 while (line_bytes_written < chars_per_line) {
827 size_t to_print = chars_per_line - line_bytes_written;
828 if (to_print > sizeof(spaces)) {
829 to_print = sizeof(spaces);
830 }
831 line_bytes_written += base_fprintf(out, "%!s", to_print, spaces);
832 }
833 break;
834 }
835
836 size_t bytes_left = len - line - word;
837 if (bytes_left > fmt.bytes_per_word) {
838 bytes_left = fmt.bytes_per_word;
839 }
840 line_bytes_written += base_fprintf(out, " ");
841 line_bytes_written +=
842 hex_dump(out, buf + line + word, bytes_left, bytes_left, '0',
843 /*big_endian=*/false, kDigitsLow);
844 }
845 bytes_written += line_bytes_written;
846
847 bytes_written += base_fprintf(out, " ");
848 size_t buffered = 0;
849 char glyph_buffer[16];
850 for (size_t byte = 0; byte < bytes_per_line; ++byte) {
851 if (buffered == sizeof(glyph_buffer)) {
852 bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer);
853 buffered = 0;
854 }
855 if (line + byte >= len) {
856 break;
857 }
858 glyph_buffer[buffered++] =
859 (*fmt.alphabet)[(size_t)(uint8_t)buf[line + byte]];
860 }
861 if (buffered > 0) {
862 bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer);
863 }
864 bytes_written += base_fprintf(out, "\n");
865 }
866
867 return bytes_written;
868}