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);
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, "[\"", 2);
486 for (size_t i = 0; i < 3; i++) {
487 if (mod[i] == '\\') {
488 len += out.sink(out.data, "\\\\", 2);
489 } else {
490 len += out.sink(out.data, &mod[i], 1);
491 }
492 }
493 len += out.sink(out.data, "\",", 2);
494 len += write_digits(out, (uint32_t)arg, 0, 0, 10, kDigitsLow);
495 len += out.sink(out.data, "]", 1);
496 } else {
497 // Ok codes include only the arg
498 len += write_digits(out, (uint32_t)arg, 0, 0, 10, kDigitsLow);
499 }
500 len += out.sink(out.data, "}", as_json ? 1 : 0);
501 return len;
502}
503
504/**
505 * Hexdumps `bytes` onto `out`.
506 *
507 * @param out the sink to write bytes to.
508 * @param bytes the bytes to dump.
509 * @param len the number of bytes to dump.
510 * @param width the minimum width to print; going below will result in writing
511 * out zeroes.
512 * @param padding the character to use for padding.
513 * @param big_endian whether to print in big-endian order (i.e. as %x would).
514 * @param glyphs an array of characters to use as the digits of a number, which
515 * should be at least ast long as `base`.
516 * @return the number of bytes written.
517 */
518static size_t hex_dump(buffer_sink_t out, const char *bytes, size_t len,
519 uint32_t width, char padding, bool big_endian,
520 const char *glyphs) {
521 size_t bytes_written = 0;
522 char buf[32];
523 size_t buffered = 0;
524 if (len < width) {
525 width -= len;
526 memset(buf, padding, sizeof(buf));
527 while (width > 0) {
528 size_t to_write = width > ARRAYSIZE(buf) ? 32 : width;
529 bytes_written += out.sink(out.data, buf, to_write);
530 width -= to_write;
531 }
532 }
533
534 for (size_t i = 0; i < len; ++i) {
535 size_t idx = big_endian ? len - i - 1 : i;
536 buf[buffered] = glyphs[(bytes[idx] >> 4) & 0xf];
537 buf[buffered + 1] = glyphs[bytes[idx] & 0xf];
538 buffered += 2;
539
540 if (buffered == ARRAYSIZE(buf)) {
541 bytes_written += out.sink(out.data, buf, buffered);
542 buffered = 0;
543 }
544 }
545
546 if (buffered != 0) {
547 bytes_written += out.sink(out.data, buf, buffered);
548 }
549 return bytes_written;
550}
551
552/**
553 * Prints out the next entry in `args` according to `spec`.
554 *
555 * This function assumes that `spec` accurately describes the next entry in
556 * `args`.
557 *
558 * @param out the sink to write bytes to.
559 * @param spec the specifier to use for stringifying.
560 * @param[out] bytes_written out param for the number of bytes writen to `out`.
561 * @param args the list to pull an entry from.
562 */
563static void process_specifier(buffer_sink_t out, format_specifier_t spec,
564 size_t *bytes_written, va_list *args) {
565 // Switch on the specifier. At this point, we assert that there is
566 // an initialized value of correct type in the VA list; if it is
567 // missing, the caller has caused UB.
568 switch (spec.type) {
569 case kPercent: {
570 if (spec.is_nonstd) {
571 goto bad_spec;
572 }
573 *bytes_written += out.sink(out.data, "%", 1);
574 break;
575 }
576 case kCharacter: {
577 if (spec.is_nonstd) {
578 goto bad_spec;
579 }
580 char value = (char)va_arg(*args, uint32_t);
581 *bytes_written += out.sink(out.data, &value, 1);
582 break;
583 }
584 case kFourCC: {
585 uint32_t value = va_arg(*args, uint32_t);
586 for (size_t i = 0; i < sizeof(uint32_t); ++i, value >>= 8) {
587 uint8_t ch = (uint8_t)value;
588 if (ch >= 32 && ch < 127) {
589 *bytes_written += out.sink(out.data, (const char *)&ch, 1);
590 } else {
591 *bytes_written += out.sink(out.data, "\\x", 2);
592 *bytes_written += out.sink(out.data, &kDigitsLow[ch >> 4], 1);
593 *bytes_written += out.sink(out.data, &kDigitsLow[ch & 15], 1);
594 }
595 }
596 break;
597 }
598 case kString: {
599 size_t len = 0;
600 if (spec.is_nonstd) {
601 // This implements %!s.
602 len = va_arg(*args, size_t);
603 }
604
605 char *value = va_arg(*args, char *);
606 while (!spec.is_nonstd && value[len] != '\0') {
607 // This implements %s.
608 ++len;
609 }
610
611 *bytes_written += out.sink(out.data, value, len);
612 break;
613 }
614 case kSignedDec1:
615 case kSignedDec2: {
616 if (spec.is_nonstd) {
617 goto bad_spec;
618 }
619 uint32_t value = va_arg(*args, uint32_t);
620 if (((int32_t)value) < 0) {
621 *bytes_written += out.sink(out.data, "-", 1);
622 value = -value;
623 }
624 *bytes_written +=
625 write_digits(out, value, spec.width, spec.padding, 10, kDigitsLow);
626 break;
627 }
628 case kUnsignedOct: {
629 if (spec.is_nonstd) {
630 goto bad_spec;
631 }
632 uint32_t value = va_arg(*args, uint32_t);
633 *bytes_written +=
634 write_digits(out, value, spec.width, spec.padding, 8, kDigitsLow);
635 break;
636 }
637 case kPointer: {
638 if (spec.is_nonstd) {
639 goto bad_spec;
640 }
641 // Pointers are formatted as 0x<hex digits>, where the width is always
642 // set to the number necessary to represent a pointer on the current
643 // platform, that is, the size of uintptr_t in nybbles. For example, on
644 // different architecutres the null pointer prints as
645 // - rv32imc: 0x00000000 (four bytes, eight nybbles).
646 // - amd64: 0x0000000000000000 (eight bytes, sixteen nybbles).
647 *bytes_written += out.sink(out.data, "0x", 2);
648 uintptr_t value = va_arg(*args, uintptr_t);
649 *bytes_written +=
650 write_digits(out, value, sizeof(uintptr_t) * 2, '0', 16, kDigitsLow);
651 break;
652 }
653 case kUnsignedHexLow:
654 if (spec.is_nonstd) {
655 size_t len = va_arg(*args, size_t);
656 char *value = va_arg(*args, char *);
657 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
658 /*big_endian=*/true, kDigitsLow);
659 break;
660 }
662 case kSvHexLow: {
663 uint32_t value = va_arg(*args, uint32_t);
664 *bytes_written +=
665 write_digits(out, value, spec.width, spec.padding, 16, kDigitsLow);
666 break;
667 }
668 case kUnsignedHexHigh:
669 if (spec.is_nonstd) {
670 size_t len = va_arg(*args, size_t);
671 char *value = va_arg(*args, char *);
672 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
673 /*big_endian=*/true, kDigitsHigh);
674 break;
675 }
677 case kSvHexHigh: {
678 uint32_t value = va_arg(*args, uint32_t);
679 *bytes_written +=
680 write_digits(out, value, spec.width, spec.padding, 16, kDigitsHigh);
681 break;
682 }
683 case kHexLeLow: {
684 if (!spec.is_nonstd) {
685 goto bad_spec;
686 }
687 size_t len = va_arg(*args, size_t);
688 char *value = va_arg(*args, char *);
689 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
690 /*big_endian=*/false, kDigitsLow);
691 break;
692 }
693 case kHexLeHigh: {
694 if (!spec.is_nonstd) {
695 goto bad_spec;
696 }
697 size_t len = va_arg(*args, size_t);
698 char *value = va_arg(*args, char *);
699 *bytes_written += hex_dump(out, value, len, spec.width, spec.padding,
700 /*big_endian=*/false, kDigitsHigh);
701 break;
702 }
703 case kUnsignedDec: {
704 if (spec.is_nonstd) {
705 goto bad_spec;
706 }
707 uint32_t value = va_arg(*args, uint32_t);
708 *bytes_written +=
709 write_digits(out, value, spec.width, spec.padding, 10, kDigitsLow);
710 break;
711 }
712 case kSvBinary: {
713 if (spec.is_nonstd) {
714 // Bools passed into a ... function will be automatically promoted
715 // to int; va_arg(..., bool) is actually UB!
716 if (va_arg(*args, int) != 0) {
717 *bytes_written += out.sink(out.data, "true", 4);
718 } else {
719 *bytes_written += out.sink(out.data, "false", 5);
720 }
721 break;
722 }
723 uint32_t value = va_arg(*args, uint32_t);
724 *bytes_written +=
725 write_digits(out, value, spec.width, spec.padding, 2, kDigitsLow);
726 break;
727 }
728 case kStatusResult: {
729 status_t value = va_arg(*args, status_t);
730 *bytes_written += write_status(out, value, spec.is_nonstd);
731 break;
732 }
733 bad_spec: // Used with `goto` to bail out early.
734 default: {
735 *bytes_written += out.sink(out.data, kUnknownSpec, sizeof(kUnknownSpec));
736 }
737 }
738}
739
740size_t base_vfprintf(buffer_sink_t out, const char *format, va_list args) {
741 if (out.sink == NULL) {
742 out.sink = &base_dev_null;
743 }
744
745 // NOTE: This copy is necessary on amd64 and other platforms, where
746 // `va_list` is a fixed array type (and, as such, decays to a pointer in
747 // an argument list). On PSABI RV32IMC, however, `va_list` is a `void*`, so
748 // this is a copy of the pointer, not the array.
749 va_list args_copy;
750 va_copy(args_copy, args);
751
752 size_t bytes_written = 0;
753 while (format[0] != '\0') {
754 if (!consume_until_percent(out, &format, &bytes_written)) {
755 break;
756 }
757 format_specifier_t spec;
758 if (!consume_format_specifier(out, &format, &bytes_written, &spec)) {
759 break;
760 }
761
762 process_specifier(out, spec, &bytes_written, &args_copy);
763 }
764
765 va_end(args_copy);
766 return bytes_written;
767}
768
769const char kBaseHexdumpDefaultFmtAlphabet[256] =
770 // clang-format off
771 // First 32 characters are not printable.
772 "................................"
773 // Printable ASCII.
774 " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
775 // The rest of the range is also not printable (129 characters).
776 "................................................................"
777 "................................................................"
778 ".";
779// clang-format on
780
781static const base_hexdump_fmt_t kBaseHexdumpDefaultFmt = {
782 .bytes_per_word = 2,
783 .words_per_line = 8,
784 .alphabet = &kBaseHexdumpDefaultFmtAlphabet,
785};
786
787size_t base_hexdump(const char *buf, size_t len) {
788 return base_hexdump_with(kBaseHexdumpDefaultFmt, buf, len);
789}
790
791size_t base_snhexdump(char *out, size_t out_len, const char *buf, size_t len) {
792 return base_snhexdump_with(out, out_len, kBaseHexdumpDefaultFmt, buf, len);
793}
794
795size_t base_fhexdump(buffer_sink_t out, const char *buf, size_t len) {
796 return base_fhexdump_with(out, kBaseHexdumpDefaultFmt, buf, len);
797}
798
799size_t base_hexdump_with(base_hexdump_fmt_t fmt, const char *buf, size_t len) {
800 return base_fhexdump_with(base_stdout, fmt, buf, len);
801}
802
803size_t base_snhexdump_with(char *out, size_t out_len, base_hexdump_fmt_t fmt,
804 const char *buf, size_t len) {
805 snprintf_captures_t data = {
806 .buf = out,
807 .bytes_left = out_len,
808 };
809 buffer_sink_t sink = {
810 .data = &data,
811 .sink = &snprintf_sink,
812 };
813 return base_fhexdump_with(sink, fmt, buf, len);
814}
815
816size_t base_fhexdump_with(buffer_sink_t out, base_hexdump_fmt_t fmt,
817 const char *buf, size_t len) {
818 if (out.sink == NULL) {
819 out.sink = &base_dev_null;
820 }
821
822 size_t bytes_written = 0;
823 size_t bytes_per_line = fmt.bytes_per_word * fmt.words_per_line;
824
825 for (size_t line = 0; line < len; line += bytes_per_line) {
826 bytes_written += base_fprintf(out, "%08x:", line);
827
828 size_t chars_per_line = bytes_per_line * 2 + fmt.words_per_line;
829 size_t line_bytes_written = 0;
830 for (size_t word = 0; word < bytes_per_line; word += fmt.bytes_per_word) {
831 if (len < line + word) {
832 char spaces[16] = " ";
833 while (line_bytes_written < chars_per_line) {
834 size_t to_print = chars_per_line - line_bytes_written;
835 if (to_print > sizeof(spaces)) {
836 to_print = sizeof(spaces);
837 }
838 line_bytes_written += base_fprintf(out, "%!s", to_print, spaces);
839 }
840 break;
841 }
842
843 size_t bytes_left = len - line - word;
844 if (bytes_left > fmt.bytes_per_word) {
845 bytes_left = fmt.bytes_per_word;
846 }
847 line_bytes_written += base_fprintf(out, " ");
848 line_bytes_written +=
849 hex_dump(out, buf + line + word, bytes_left, bytes_left, '0',
850 /*big_endian=*/false, kDigitsLow);
851 }
852 bytes_written += line_bytes_written;
853
854 bytes_written += base_fprintf(out, " ");
855 size_t buffered = 0;
856 char glyph_buffer[16];
857 for (size_t byte = 0; byte < bytes_per_line; ++byte) {
858 if (buffered == sizeof(glyph_buffer)) {
859 bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer);
860 buffered = 0;
861 }
862 if (line + byte >= len) {
863 break;
864 }
865 glyph_buffer[buffered++] =
866 (*fmt.alphabet)[(size_t)(uint8_t)buf[line + byte]];
867 }
868 if (buffered > 0) {
869 bytes_written += base_fprintf(out, "%!s", buffered, glyph_buffer);
870 }
871 bytes_written += base_fprintf(out, "\n");
872 }
873
874 return bytes_written;
875}