]>
Commit | Line | Data |
---|---|---|
7d9cde10 SR |
1 | /* |
2 | * Tiny printf version for SPL | |
3 | * | |
4 | * Copied from: | |
5 | * http://www.sparetimelabs.com/printfrevisited/printfrevisited.php | |
6 | * | |
7 | * Copyright (C) 2004,2008 Kustaa Nyholm | |
8 | * | |
9 | * SPDX-License-Identifier: LGPL-2.1+ | |
10 | */ | |
11 | ||
12 | #include <common.h> | |
13 | #include <stdarg.h> | |
14 | #include <serial.h> | |
cdce1f76 | 15 | #include <linux/ctype.h> |
7d9cde10 | 16 | |
45313e83 SG |
17 | struct printf_info { |
18 | char *bf; /* Digit buffer */ | |
19 | char zs; /* non-zero if a digit has been written */ | |
20 | char *outstr; /* Next output position for sprintf() */ | |
7d9cde10 | 21 | |
45313e83 SG |
22 | /* Output a character */ |
23 | void (*putc)(struct printf_info *info, char ch); | |
24 | }; | |
5c411d88 | 25 | |
433cbfb3 | 26 | static void putc_normal(struct printf_info *info, char ch) |
7d9cde10 | 27 | { |
45313e83 | 28 | putc(ch); |
7d9cde10 SR |
29 | } |
30 | ||
45313e83 | 31 | static void out(struct printf_info *info, char c) |
7d9cde10 | 32 | { |
45313e83 | 33 | *info->bf++ = c; |
7d9cde10 SR |
34 | } |
35 | ||
45313e83 SG |
36 | static void out_dgt(struct printf_info *info, char dgt) |
37 | { | |
38 | out(info, dgt + (dgt < 10 ? '0' : 'a' - 10)); | |
39 | info->zs = 1; | |
40 | } | |
41 | ||
a28e1d98 AP |
42 | static void div_out(struct printf_info *info, unsigned long *num, |
43 | unsigned long div) | |
7d9cde10 SR |
44 | { |
45 | unsigned char dgt = 0; | |
46 | ||
a5ecdd08 SR |
47 | while (*num >= div) { |
48 | *num -= div; | |
7d9cde10 SR |
49 | dgt++; |
50 | } | |
51 | ||
45313e83 SG |
52 | if (info->zs || dgt > 0) |
53 | out_dgt(info, dgt); | |
7d9cde10 SR |
54 | } |
55 | ||
cdce1f76 V |
56 | #ifdef CONFIG_SPL_NET_SUPPORT |
57 | static void string(struct printf_info *info, char *s) | |
58 | { | |
59 | char ch; | |
60 | ||
61 | while ((ch = *s++)) | |
62 | out(info, ch); | |
63 | } | |
64 | ||
65 | static const char hex_asc[] = "0123456789abcdef"; | |
66 | #define hex_asc_lo(x) hex_asc[((x) & 0x0f)] | |
67 | #define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] | |
68 | ||
69 | static inline char *pack_hex_byte(char *buf, u8 byte) | |
70 | { | |
71 | *buf++ = hex_asc_hi(byte); | |
72 | *buf++ = hex_asc_lo(byte); | |
73 | return buf; | |
74 | } | |
75 | ||
76 | static void mac_address_string(struct printf_info *info, u8 *addr, | |
77 | bool separator) | |
78 | { | |
79 | /* (6 * 2 hex digits), 5 colons and trailing zero */ | |
80 | char mac_addr[6 * 3]; | |
81 | char *p = mac_addr; | |
82 | int i; | |
83 | ||
84 | for (i = 0; i < 6; i++) { | |
85 | p = pack_hex_byte(p, addr[i]); | |
86 | if (separator && i != 5) | |
87 | *p++ = ':'; | |
88 | } | |
89 | *p = '\0'; | |
90 | ||
91 | string(info, mac_addr); | |
92 | } | |
93 | ||
94 | static char *put_dec_trunc(char *buf, unsigned int q) | |
95 | { | |
96 | unsigned int d3, d2, d1, d0; | |
97 | d1 = (q >> 4) & 0xf; | |
98 | d2 = (q >> 8) & 0xf; | |
99 | d3 = (q >> 12); | |
100 | ||
101 | d0 = 6 * (d3 + d2 + d1) + (q & 0xf); | |
102 | q = (d0 * 0xcd) >> 11; | |
103 | d0 = d0 - 10 * q; | |
104 | *buf++ = d0 + '0'; /* least significant digit */ | |
105 | d1 = q + 9 * d3 + 5 * d2 + d1; | |
106 | if (d1 != 0) { | |
107 | q = (d1 * 0xcd) >> 11; | |
108 | d1 = d1 - 10 * q; | |
109 | *buf++ = d1 + '0'; /* next digit */ | |
110 | ||
111 | d2 = q + 2 * d2; | |
112 | if ((d2 != 0) || (d3 != 0)) { | |
113 | q = (d2 * 0xd) >> 7; | |
114 | d2 = d2 - 10 * q; | |
115 | *buf++ = d2 + '0'; /* next digit */ | |
116 | ||
117 | d3 = q + 4 * d3; | |
118 | if (d3 != 0) { | |
119 | q = (d3 * 0xcd) >> 11; | |
120 | d3 = d3 - 10 * q; | |
121 | *buf++ = d3 + '0'; /* next digit */ | |
122 | if (q != 0) | |
123 | *buf++ = q + '0'; /* most sign. digit */ | |
124 | } | |
125 | } | |
126 | } | |
127 | return buf; | |
128 | } | |
129 | ||
130 | static void ip4_addr_string(struct printf_info *info, u8 *addr) | |
131 | { | |
132 | /* (4 * 3 decimal digits), 3 dots and trailing zero */ | |
133 | char ip4_addr[4 * 4]; | |
134 | char temp[3]; /* hold each IP quad in reverse order */ | |
135 | char *p = ip4_addr; | |
136 | int i, digits; | |
137 | ||
138 | for (i = 0; i < 4; i++) { | |
139 | digits = put_dec_trunc(temp, addr[i]) - temp; | |
140 | /* reverse the digits in the quad */ | |
141 | while (digits--) | |
142 | *p++ = temp[digits]; | |
143 | if (i != 3) | |
144 | *p++ = '.'; | |
145 | } | |
146 | *p = '\0'; | |
147 | ||
148 | string(info, ip4_addr); | |
149 | } | |
150 | #endif | |
151 | ||
152 | /* | |
153 | * Show a '%p' thing. A kernel extension is that the '%p' is followed | |
154 | * by an extra set of characters that are extended format | |
155 | * specifiers. | |
156 | * | |
157 | * Right now we handle: | |
158 | * | |
159 | * - 'M' For a 6-byte MAC address, it prints the address in the | |
160 | * usual colon-separated hex notation. | |
161 | * - 'm' Same as above except there is no colon-separator. | |
162 | * - 'I4'for IPv4 addresses printed in the usual way (dot-separated | |
163 | * decimal). | |
164 | */ | |
165 | ||
166 | static void pointer(struct printf_info *info, const char *fmt, void *ptr) | |
167 | { | |
168 | #ifdef DEBUG | |
169 | unsigned long num = (uintptr_t)ptr; | |
170 | unsigned long div; | |
171 | #endif | |
172 | ||
173 | switch (*fmt) { | |
174 | #ifdef DEBUG | |
175 | case 'a': | |
176 | ||
177 | switch (fmt[1]) { | |
178 | case 'p': | |
179 | default: | |
180 | num = *(phys_addr_t *)ptr; | |
181 | break; | |
182 | } | |
183 | break; | |
184 | #endif | |
185 | #ifdef CONFIG_SPL_NET_SUPPORT | |
186 | case 'm': | |
187 | return mac_address_string(info, ptr, false); | |
188 | case 'M': | |
189 | return mac_address_string(info, ptr, true); | |
190 | case 'I': | |
191 | if (fmt[1] == '4') | |
192 | return ip4_addr_string(info, ptr); | |
193 | #endif | |
194 | default: | |
195 | break; | |
196 | } | |
197 | #ifdef DEBUG | |
198 | div = 1UL << (sizeof(long) * 8 - 4); | |
199 | for (; div; div /= 0x10) | |
200 | div_out(info, &num, div); | |
201 | #endif | |
202 | } | |
203 | ||
433cbfb3 | 204 | static int _vprintf(struct printf_info *info, const char *fmt, va_list va) |
7d9cde10 | 205 | { |
7d9cde10 SR |
206 | char ch; |
207 | char *p; | |
a28e1d98 | 208 | unsigned long num; |
a5ecdd08 | 209 | char buf[12]; |
a28e1d98 | 210 | unsigned long div; |
7d9cde10 | 211 | |
7d9cde10 SR |
212 | while ((ch = *(fmt++))) { |
213 | if (ch != '%') { | |
45313e83 | 214 | info->putc(info, ch); |
7d9cde10 | 215 | } else { |
1fb67608 SG |
216 | bool lz = false; |
217 | int width = 0; | |
a28e1d98 | 218 | bool islong = false; |
7d9cde10 SR |
219 | |
220 | ch = *(fmt++); | |
1c853629 AP |
221 | if (ch == '-') |
222 | ch = *(fmt++); | |
223 | ||
7d9cde10 SR |
224 | if (ch == '0') { |
225 | ch = *(fmt++); | |
226 | lz = 1; | |
227 | } | |
228 | ||
229 | if (ch >= '0' && ch <= '9') { | |
1fb67608 | 230 | width = 0; |
7d9cde10 | 231 | while (ch >= '0' && ch <= '9') { |
1fb67608 | 232 | width = (width * 10) + ch - '0'; |
7d9cde10 SR |
233 | ch = *fmt++; |
234 | } | |
235 | } | |
a28e1d98 AP |
236 | if (ch == 'l') { |
237 | ch = *(fmt++); | |
238 | islong = true; | |
239 | } | |
240 | ||
45313e83 SG |
241 | info->bf = buf; |
242 | p = info->bf; | |
243 | info->zs = 0; | |
7d9cde10 SR |
244 | |
245 | switch (ch) { | |
1fb67608 | 246 | case '\0': |
7d9cde10 SR |
247 | goto abort; |
248 | case 'u': | |
249 | case 'd': | |
a28e1d98 AP |
250 | div = 1000000000; |
251 | if (islong) { | |
252 | num = va_arg(va, unsigned long); | |
253 | if (sizeof(long) > 4) | |
254 | div *= div * 10; | |
255 | } else { | |
256 | num = va_arg(va, unsigned int); | |
257 | } | |
258 | ||
259 | if (ch == 'd') { | |
260 | if (islong && (long)num < 0) { | |
261 | num = -(long)num; | |
262 | out(info, '-'); | |
263 | } else if (!islong && (int)num < 0) { | |
264 | num = -(int)num; | |
265 | out(info, '-'); | |
266 | } | |
7d9cde10 | 267 | } |
74b1320a | 268 | if (!num) { |
45313e83 | 269 | out_dgt(info, 0); |
74b1320a | 270 | } else { |
a28e1d98 | 271 | for (; div; div /= 10) |
45313e83 | 272 | div_out(info, &num, div); |
74b1320a | 273 | } |
7d9cde10 SR |
274 | break; |
275 | case 'x': | |
a28e1d98 AP |
276 | if (islong) { |
277 | num = va_arg(va, unsigned long); | |
278 | div = 1UL << (sizeof(long) * 8 - 4); | |
279 | } else { | |
280 | num = va_arg(va, unsigned int); | |
281 | div = 0x10000000; | |
282 | } | |
74b1320a | 283 | if (!num) { |
45313e83 | 284 | out_dgt(info, 0); |
74b1320a | 285 | } else { |
a28e1d98 | 286 | for (; div; div /= 0x10) |
45313e83 | 287 | div_out(info, &num, div); |
74b1320a | 288 | } |
7d9cde10 SR |
289 | break; |
290 | case 'c': | |
45313e83 | 291 | out(info, (char)(va_arg(va, int))); |
7d9cde10 SR |
292 | break; |
293 | case 's': | |
294 | p = va_arg(va, char*); | |
295 | break; | |
cdce1f76 V |
296 | case 'p': |
297 | pointer(info, fmt, va_arg(va, void *)); | |
298 | while (isalnum(fmt[0])) | |
299 | fmt++; | |
300 | break; | |
7d9cde10 | 301 | case '%': |
45313e83 | 302 | out(info, '%'); |
7d9cde10 SR |
303 | default: |
304 | break; | |
305 | } | |
306 | ||
45313e83 SG |
307 | *info->bf = 0; |
308 | info->bf = p; | |
309 | while (*info->bf++ && width > 0) | |
1fb67608 SG |
310 | width--; |
311 | while (width-- > 0) | |
45313e83 | 312 | info->putc(info, lz ? '0' : ' '); |
8e31681c SG |
313 | if (p) { |
314 | while ((ch = *p++)) | |
45313e83 | 315 | info->putc(info, ch); |
8e31681c | 316 | } |
7d9cde10 SR |
317 | } |
318 | } | |
319 | ||
320 | abort: | |
7d9cde10 SR |
321 | return 0; |
322 | } | |
962a43cc | 323 | |
da70b4d1 HG |
324 | int vprintf(const char *fmt, va_list va) |
325 | { | |
45313e83 SG |
326 | struct printf_info info; |
327 | ||
328 | info.putc = putc_normal; | |
329 | return _vprintf(&info, fmt, va); | |
da70b4d1 HG |
330 | } |
331 | ||
962a43cc SS |
332 | int printf(const char *fmt, ...) |
333 | { | |
45313e83 SG |
334 | struct printf_info info; |
335 | ||
962a43cc SS |
336 | va_list va; |
337 | int ret; | |
338 | ||
45313e83 | 339 | info.putc = putc_normal; |
962a43cc | 340 | va_start(va, fmt); |
45313e83 | 341 | ret = _vprintf(&info, fmt, va); |
5c411d88 SG |
342 | va_end(va); |
343 | ||
344 | return ret; | |
345 | } | |
346 | ||
45313e83 | 347 | static void putc_outstr(struct printf_info *info, char ch) |
5c411d88 | 348 | { |
45313e83 | 349 | *info->outstr++ = ch; |
5c411d88 SG |
350 | } |
351 | ||
abeb272d | 352 | int sprintf(char *buf, const char *fmt, ...) |
5c411d88 | 353 | { |
45313e83 | 354 | struct printf_info info; |
5c411d88 SG |
355 | va_list va; |
356 | int ret; | |
357 | ||
358 | va_start(va, fmt); | |
45313e83 SG |
359 | info.outstr = buf; |
360 | info.putc = putc_outstr; | |
361 | ret = _vprintf(&info, fmt, va); | |
962a43cc | 362 | va_end(va); |
45313e83 | 363 | *info.outstr = '\0'; |
962a43cc SS |
364 | |
365 | return ret; | |
366 | } | |
abeb272d MV |
367 | |
368 | /* Note that size is ignored */ | |
369 | int snprintf(char *buf, size_t size, const char *fmt, ...) | |
370 | { | |
45313e83 | 371 | struct printf_info info; |
abeb272d MV |
372 | va_list va; |
373 | int ret; | |
374 | ||
375 | va_start(va, fmt); | |
45313e83 SG |
376 | info.outstr = buf; |
377 | info.putc = putc_outstr; | |
378 | ret = _vprintf(&info, fmt, va); | |
abeb272d | 379 | va_end(va); |
45313e83 | 380 | *info.outstr = '\0'; |
abeb272d MV |
381 | |
382 | return ret; | |
383 | } | |
e2409139 SG |
384 | |
385 | void __assert_fail(const char *assertion, const char *file, unsigned line, | |
386 | const char *function) | |
387 | { | |
388 | /* This will not return */ | |
389 | printf("%s:%u: %s: Assertion `%s' failed.", file, line, function, | |
390 | assertion); | |
391 | hang(); | |
392 | } |