1/*
2 * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 */
6
7#include <printf.h>
8#include <types.h>
9#include <vargs.h>
10#include <elfloader_common.h>
11
12/*
13 * Maximum space needed to print an integer in any base.
14 *
15 * We set this to log_2(2**64) + 1 + safety margin ~= 80.
16 */
17#define MAX_INT_BUFF_SIZE  80
18
19/*
20 * Function to process a simple character. "payload" may point
21 * to arbitrary state needed by the "write_char" function.
22 */
23typedef void write_char_fn(void *payload, int c);
24
25/* Write a NUL-terminated string to the given 'write_char' function. */
26static void write_string(write_char_fn write_char, void *payload, const char *str)
27{
28    int i;
29    for (i = 0; str[i] != 0; i++) {
30        write_char(payload, str[i]);
31    }
32}
33
34/*
35 * Write the given unsigned number "n" to the given write_char function.
36 *
37 * We only support bases up to 16.
38 */
39static void write_num(write_char_fn write_char, void *payload,
40                      int base, unsigned long n)
41{
42    static const char hex[] = "0123456789abcdef";
43    char buff[MAX_INT_BUFF_SIZE];
44    int k = MAX_INT_BUFF_SIZE - 1;
45
46    /* Special case for "0". */
47    if (n == 0) {
48        write_string(write_char, payload, "0");
49        return;
50    }
51
52    /* NUL-terminate. */
53    buff[k--] = 0;
54
55    /* Generate the number. */
56    while (n > 0) {
57        buff[k] = hex[n % base];
58        n /= base;
59        k--;
60    }
61
62    /* Print the number. */
63    write_string(write_char, payload, &buff[k + 1]);
64}
65
66/*
67 * Print a printf-style string to the given write_char function.
68 */
69static void vxprintf(write_char_fn write_char, void *payload,
70                     const char *format, va_list args)
71{
72    int d, i;
73    char c, *s;
74    unsigned long p, ul;
75    int escape_mode = 0;
76
77    /* Iterate over the format list. */
78    for (i = 0; format[i] != 0; i++) {
79        /* Handle simple characters. */
80        if (!escape_mode && format[i] != '%') {
81            write_char(payload, format[i]);
82            continue;
83        }
84
85        /* Handle the percent escape character. */
86        if (format[i] == '%') {
87            if (!escape_mode) {
88                /* Entering escape mode. */
89                escape_mode = 1;
90            } else {
91                /* Already in escape mode; print a percent. */
92                write_char(payload, format[i]);
93                escape_mode = 0;
94            }
95            continue;
96        }
97
98        /* Handle the modifier. */
99        switch (format[i]) {
100        /* Ignore printf modifiers we don't support. */
101        case '0':
102        case '1':
103        case '2':
104        case '3':
105        case '4':
106        case '5':
107        case '6':
108        case '7':
109        case '8':
110        case '9':
111        case '-':
112        case '.':
113            break;
114
115        /* String. */
116        case 's':
117            s = va_arg(args, char *);
118            write_string(write_char, payload, s);
119            escape_mode = 0;
120            break;
121
122        /* Pointers. */
123        case 'p':
124            p = va_arg(args, unsigned long);
125            write_num(write_char, payload, 16, p);
126            escape_mode = 0;
127            break;
128
129        /* Hex number. */
130        case 'x':
131            d = va_arg(args, int);
132            write_num(write_char, payload, 16, d);
133            escape_mode = 0;
134            break;
135
136        /* Decimal number. */
137        case 'd':
138        case 'u':
139            d = va_arg(args, int);
140            write_num(write_char, payload, 10, d);
141            escape_mode = 0;
142            break;
143
144        /* Character. */
145        case 'c':
146            c = va_arg(args, int);
147            write_char(payload, c);
148            escape_mode = 0;
149            break;
150
151        /* Long number. */
152        case 'l':
153            switch (format[++i]) {
154            case 'u':
155                ul = va_arg(args, unsigned long);
156                write_num(write_char, payload, 10, ul);
157                break;
158
159            case 'x':
160                ul = va_arg(args, unsigned long);
161                write_num(write_char, payload, 16, ul);
162                break;
163
164            default:
165                write_char(payload, '?');
166            }
167            escape_mode = 0;
168            break;
169
170        /* Unknown. */
171        default:
172            write_char(payload, '?');
173            escape_mode = 0;
174            break;
175        }
176    }
177}
178
179/*
180 * Simple printf/puts implementation.
181 */
182
183static void arch_write_char(void *num_chars_printed_ptr, int c)
184{
185    int *num_chars_printed = (int *)num_chars_printed_ptr;
186
187    /* For now, console output goes into a UART on every platform eventually
188     * and we write a '\r' (CR) before every '\n' (LF) unconditinally. If there
189     * will even be a console that works differently, we can still add a
190     * configuration flag that allows disabling this feature.
191     */
192    if (c == '\n') {
193        /* TODO: There is no "(*num_chars_printed)++;" here, as the CR char has
194         *       never been counted by any platform specific implementations in
195         *       the past. For now the behavior is kept, but it seem quite
196         *       wrong to hide this. Check if any code depends really on this
197         *       and consider counting the CR char also.
198         */
199        plat_console_putchar('\r');
200    }
201
202    (*num_chars_printed)++;
203    plat_console_putchar(c);
204}
205
206int printf(const char *format, ...)
207{
208    int n = 0;
209    va_list args;
210    va_start(args, format);
211    vxprintf(arch_write_char, &n, format, args);
212    va_end(args);
213    return n;
214}
215
216int puts(const char *str)
217{
218    int n = 0;
219    write_string(arch_write_char, &n, str);
220    arch_write_char(&n, '\n');
221    return n;
222}
223
224/*
225 * Simple sprintf implementation.
226 */
227
228struct sprintf_payload {
229    char *buff;
230    int n;
231};
232
233static void sprintf_write_char(void *payload, int c)
234{
235    struct sprintf_payload *p = (struct sprintf_payload *)payload;
236    p->buff[p->n] = c;
237    p->n++;
238}
239
240int sprintf(char *buff, const char *format, ...)
241{
242    struct sprintf_payload p = {buff, 0};
243    va_list args;
244    va_start(args, format);
245    vxprintf(sprintf_write_char, &p, format, args);
246    va_end(args);
247    return p.n;
248}
249