xref: /openbmc/u-boot/lib/tiny-printf.c (revision 83d290c56fab2d38cd1ab4c4cc7099559c1d5046)
1*83d290c5STom Rini // SPDX-License-Identifier: LGPL-2.1+
27d9cde10SStefan Roese /*
37d9cde10SStefan Roese  * Tiny printf version for SPL
47d9cde10SStefan Roese  *
57d9cde10SStefan Roese  * Copied from:
67d9cde10SStefan Roese  * http://www.sparetimelabs.com/printfrevisited/printfrevisited.php
77d9cde10SStefan Roese  *
87d9cde10SStefan Roese  * Copyright (C) 2004,2008  Kustaa Nyholm
97d9cde10SStefan Roese  */
107d9cde10SStefan Roese 
117d9cde10SStefan Roese #include <common.h>
127d9cde10SStefan Roese #include <stdarg.h>
137d9cde10SStefan Roese #include <serial.h>
14cdce1f76SVignesh R #include <linux/ctype.h>
157d9cde10SStefan Roese 
1645313e83SSimon Glass struct printf_info {
1745313e83SSimon Glass 	char *bf;	/* Digit buffer */
1845313e83SSimon Glass 	char zs;	/* non-zero if a digit has been written */
1945313e83SSimon Glass 	char *outstr;	/* Next output position for sprintf() */
207d9cde10SStefan Roese 
2145313e83SSimon Glass 	/* Output a character */
2245313e83SSimon Glass 	void (*putc)(struct printf_info *info, char ch);
2345313e83SSimon Glass };
245c411d88SSimon Glass 
out(struct printf_info * info,char c)2545313e83SSimon Glass static void out(struct printf_info *info, char c)
267d9cde10SStefan Roese {
2745313e83SSimon Glass 	*info->bf++ = c;
287d9cde10SStefan Roese }
297d9cde10SStefan Roese 
out_dgt(struct printf_info * info,char dgt)3045313e83SSimon Glass static void out_dgt(struct printf_info *info, char dgt)
3145313e83SSimon Glass {
3245313e83SSimon Glass 	out(info, dgt + (dgt < 10 ? '0' : 'a' - 10));
3345313e83SSimon Glass 	info->zs = 1;
3445313e83SSimon Glass }
3545313e83SSimon Glass 
div_out(struct printf_info * info,unsigned long * num,unsigned long div)36a28e1d98SAndre Przywara static void div_out(struct printf_info *info, unsigned long *num,
37a28e1d98SAndre Przywara 		    unsigned long div)
387d9cde10SStefan Roese {
397d9cde10SStefan Roese 	unsigned char dgt = 0;
407d9cde10SStefan Roese 
41a5ecdd08SStefan Roese 	while (*num >= div) {
42a5ecdd08SStefan Roese 		*num -= div;
437d9cde10SStefan Roese 		dgt++;
447d9cde10SStefan Roese 	}
457d9cde10SStefan Roese 
4645313e83SSimon Glass 	if (info->zs || dgt > 0)
4745313e83SSimon Glass 		out_dgt(info, dgt);
487d9cde10SStefan Roese }
497d9cde10SStefan Roese 
50cdce1f76SVignesh R #ifdef CONFIG_SPL_NET_SUPPORT
string(struct printf_info * info,char * s)51cdce1f76SVignesh R static void string(struct printf_info *info, char *s)
52cdce1f76SVignesh R {
53cdce1f76SVignesh R 	char ch;
54cdce1f76SVignesh R 
55cdce1f76SVignesh R 	while ((ch = *s++))
56cdce1f76SVignesh R 		out(info, ch);
57cdce1f76SVignesh R }
58cdce1f76SVignesh R 
59cdce1f76SVignesh R static const char hex_asc[] = "0123456789abcdef";
60cdce1f76SVignesh R #define hex_asc_lo(x)	hex_asc[((x) & 0x0f)]
61cdce1f76SVignesh R #define hex_asc_hi(x)	hex_asc[((x) & 0xf0) >> 4]
62cdce1f76SVignesh R 
pack_hex_byte(char * buf,u8 byte)63cdce1f76SVignesh R static inline char *pack_hex_byte(char *buf, u8 byte)
64cdce1f76SVignesh R {
65cdce1f76SVignesh R 	*buf++ = hex_asc_hi(byte);
66cdce1f76SVignesh R 	*buf++ = hex_asc_lo(byte);
67cdce1f76SVignesh R 	return buf;
68cdce1f76SVignesh R }
69cdce1f76SVignesh R 
mac_address_string(struct printf_info * info,u8 * addr,bool separator)70cdce1f76SVignesh R static void mac_address_string(struct printf_info *info, u8 *addr,
71cdce1f76SVignesh R 				bool separator)
72cdce1f76SVignesh R {
73cdce1f76SVignesh R 	/* (6 * 2 hex digits), 5 colons and trailing zero */
74cdce1f76SVignesh R 	char mac_addr[6 * 3];
75cdce1f76SVignesh R 	char *p = mac_addr;
76cdce1f76SVignesh R 	int i;
77cdce1f76SVignesh R 
78cdce1f76SVignesh R 	for (i = 0; i < 6; i++) {
79cdce1f76SVignesh R 		p = pack_hex_byte(p, addr[i]);
80cdce1f76SVignesh R 		if (separator && i != 5)
81cdce1f76SVignesh R 			*p++ = ':';
82cdce1f76SVignesh R 	}
83cdce1f76SVignesh R 	*p = '\0';
84cdce1f76SVignesh R 
85cdce1f76SVignesh R 	string(info, mac_addr);
86cdce1f76SVignesh R }
87cdce1f76SVignesh R 
put_dec_trunc(char * buf,unsigned int q)88cdce1f76SVignesh R static char *put_dec_trunc(char *buf, unsigned int q)
89cdce1f76SVignesh R {
90cdce1f76SVignesh R 	unsigned int d3, d2, d1, d0;
91cdce1f76SVignesh R 	d1 = (q >> 4) & 0xf;
92cdce1f76SVignesh R 	d2 = (q >> 8) & 0xf;
93cdce1f76SVignesh R 	d3 = (q >> 12);
94cdce1f76SVignesh R 
95cdce1f76SVignesh R 	d0 = 6 * (d3 + d2 + d1) + (q & 0xf);
96cdce1f76SVignesh R 	q = (d0 * 0xcd) >> 11;
97cdce1f76SVignesh R 	d0 = d0 - 10 * q;
98cdce1f76SVignesh R 	*buf++ = d0 + '0'; /* least significant digit */
99cdce1f76SVignesh R 	d1 = q + 9 * d3 + 5 * d2 + d1;
100cdce1f76SVignesh R 	if (d1 != 0) {
101cdce1f76SVignesh R 		q = (d1 * 0xcd) >> 11;
102cdce1f76SVignesh R 		d1 = d1 - 10 * q;
103cdce1f76SVignesh R 		*buf++ = d1 + '0'; /* next digit */
104cdce1f76SVignesh R 
105cdce1f76SVignesh R 		d2 = q + 2 * d2;
106cdce1f76SVignesh R 		if ((d2 != 0) || (d3 != 0)) {
107cdce1f76SVignesh R 			q = (d2 * 0xd) >> 7;
108cdce1f76SVignesh R 			d2 = d2 - 10 * q;
109cdce1f76SVignesh R 			*buf++ = d2 + '0'; /* next digit */
110cdce1f76SVignesh R 
111cdce1f76SVignesh R 			d3 = q + 4 * d3;
112cdce1f76SVignesh R 			if (d3 != 0) {
113cdce1f76SVignesh R 				q = (d3 * 0xcd) >> 11;
114cdce1f76SVignesh R 				d3 = d3 - 10 * q;
115cdce1f76SVignesh R 				*buf++ = d3 + '0';  /* next digit */
116cdce1f76SVignesh R 				if (q != 0)
117cdce1f76SVignesh R 					*buf++ = q + '0'; /* most sign. digit */
118cdce1f76SVignesh R 			}
119cdce1f76SVignesh R 		}
120cdce1f76SVignesh R 	}
121cdce1f76SVignesh R 	return buf;
122cdce1f76SVignesh R }
123cdce1f76SVignesh R 
ip4_addr_string(struct printf_info * info,u8 * addr)124cdce1f76SVignesh R static void ip4_addr_string(struct printf_info *info, u8 *addr)
125cdce1f76SVignesh R {
126cdce1f76SVignesh R 	/* (4 * 3 decimal digits), 3 dots and trailing zero */
127cdce1f76SVignesh R 	char ip4_addr[4 * 4];
128cdce1f76SVignesh R 	char temp[3];	/* hold each IP quad in reverse order */
129cdce1f76SVignesh R 	char *p = ip4_addr;
130cdce1f76SVignesh R 	int i, digits;
131cdce1f76SVignesh R 
132cdce1f76SVignesh R 	for (i = 0; i < 4; i++) {
133cdce1f76SVignesh R 		digits = put_dec_trunc(temp, addr[i]) - temp;
134cdce1f76SVignesh R 		/* reverse the digits in the quad */
135cdce1f76SVignesh R 		while (digits--)
136cdce1f76SVignesh R 			*p++ = temp[digits];
137cdce1f76SVignesh R 		if (i != 3)
138cdce1f76SVignesh R 			*p++ = '.';
139cdce1f76SVignesh R 	}
140cdce1f76SVignesh R 	*p = '\0';
141cdce1f76SVignesh R 
142cdce1f76SVignesh R 	string(info, ip4_addr);
143cdce1f76SVignesh R }
144cdce1f76SVignesh R #endif
145cdce1f76SVignesh R 
146cdce1f76SVignesh R /*
147cdce1f76SVignesh R  * Show a '%p' thing.  A kernel extension is that the '%p' is followed
148cdce1f76SVignesh R  * by an extra set of characters that are extended format
149cdce1f76SVignesh R  * specifiers.
150cdce1f76SVignesh R  *
151cdce1f76SVignesh R  * Right now we handle:
152cdce1f76SVignesh R  *
153cdce1f76SVignesh R  * - 'M' For a 6-byte MAC address, it prints the address in the
154cdce1f76SVignesh R  *       usual colon-separated hex notation.
155cdce1f76SVignesh R  * - 'm' Same as above except there is no colon-separator.
156cdce1f76SVignesh R  * - 'I4'for IPv4 addresses printed in the usual way (dot-separated
157cdce1f76SVignesh R  *       decimal).
158cdce1f76SVignesh R  */
159cdce1f76SVignesh R 
pointer(struct printf_info * info,const char * fmt,void * ptr)160cdce1f76SVignesh R static void pointer(struct printf_info *info, const char *fmt, void *ptr)
161cdce1f76SVignesh R {
162cdce1f76SVignesh R #ifdef DEBUG
163cdce1f76SVignesh R 	unsigned long num = (uintptr_t)ptr;
164cdce1f76SVignesh R 	unsigned long div;
165cdce1f76SVignesh R #endif
166cdce1f76SVignesh R 
167cdce1f76SVignesh R 	switch (*fmt) {
168cdce1f76SVignesh R #ifdef DEBUG
169cdce1f76SVignesh R 	case 'a':
170cdce1f76SVignesh R 
171cdce1f76SVignesh R 		switch (fmt[1]) {
172cdce1f76SVignesh R 		case 'p':
173cdce1f76SVignesh R 		default:
174cdce1f76SVignesh R 			num = *(phys_addr_t *)ptr;
175cdce1f76SVignesh R 			break;
176cdce1f76SVignesh R 		}
177cdce1f76SVignesh R 		break;
178cdce1f76SVignesh R #endif
179cdce1f76SVignesh R #ifdef CONFIG_SPL_NET_SUPPORT
180cdce1f76SVignesh R 	case 'm':
181cdce1f76SVignesh R 		return mac_address_string(info, ptr, false);
182cdce1f76SVignesh R 	case 'M':
183cdce1f76SVignesh R 		return mac_address_string(info, ptr, true);
184cdce1f76SVignesh R 	case 'I':
185cdce1f76SVignesh R 		if (fmt[1] == '4')
186cdce1f76SVignesh R 			return ip4_addr_string(info, ptr);
187cdce1f76SVignesh R #endif
188cdce1f76SVignesh R 	default:
189cdce1f76SVignesh R 		break;
190cdce1f76SVignesh R 	}
191cdce1f76SVignesh R #ifdef DEBUG
192cdce1f76SVignesh R 	div = 1UL << (sizeof(long) * 8 - 4);
193cdce1f76SVignesh R 	for (; div; div /= 0x10)
194cdce1f76SVignesh R 		div_out(info, &num, div);
195cdce1f76SVignesh R #endif
196cdce1f76SVignesh R }
197cdce1f76SVignesh R 
_vprintf(struct printf_info * info,const char * fmt,va_list va)198433cbfb3SMasahiro Yamada static int _vprintf(struct printf_info *info, const char *fmt, va_list va)
1997d9cde10SStefan Roese {
2007d9cde10SStefan Roese 	char ch;
2017d9cde10SStefan Roese 	char *p;
202a28e1d98SAndre Przywara 	unsigned long num;
203a5ecdd08SStefan Roese 	char buf[12];
204a28e1d98SAndre Przywara 	unsigned long div;
2057d9cde10SStefan Roese 
2067d9cde10SStefan Roese 	while ((ch = *(fmt++))) {
2077d9cde10SStefan Roese 		if (ch != '%') {
20845313e83SSimon Glass 			info->putc(info, ch);
2097d9cde10SStefan Roese 		} else {
2101fb67608SSimon Glass 			bool lz = false;
2111fb67608SSimon Glass 			int width = 0;
212a28e1d98SAndre Przywara 			bool islong = false;
2137d9cde10SStefan Roese 
2147d9cde10SStefan Roese 			ch = *(fmt++);
2151c853629SAndre Przywara 			if (ch == '-')
2161c853629SAndre Przywara 				ch = *(fmt++);
2171c853629SAndre Przywara 
2187d9cde10SStefan Roese 			if (ch == '0') {
2197d9cde10SStefan Roese 				ch = *(fmt++);
2207d9cde10SStefan Roese 				lz = 1;
2217d9cde10SStefan Roese 			}
2227d9cde10SStefan Roese 
2237d9cde10SStefan Roese 			if (ch >= '0' && ch <= '9') {
2241fb67608SSimon Glass 				width = 0;
2257d9cde10SStefan Roese 				while (ch >= '0' && ch <= '9') {
2261fb67608SSimon Glass 					width = (width * 10) + ch - '0';
2277d9cde10SStefan Roese 					ch = *fmt++;
2287d9cde10SStefan Roese 				}
2297d9cde10SStefan Roese 			}
230a28e1d98SAndre Przywara 			if (ch == 'l') {
231a28e1d98SAndre Przywara 				ch = *(fmt++);
232a28e1d98SAndre Przywara 				islong = true;
233a28e1d98SAndre Przywara 			}
234a28e1d98SAndre Przywara 
23545313e83SSimon Glass 			info->bf = buf;
23645313e83SSimon Glass 			p = info->bf;
23745313e83SSimon Glass 			info->zs = 0;
2387d9cde10SStefan Roese 
2397d9cde10SStefan Roese 			switch (ch) {
2401fb67608SSimon Glass 			case '\0':
2417d9cde10SStefan Roese 				goto abort;
2427d9cde10SStefan Roese 			case 'u':
2437d9cde10SStefan Roese 			case 'd':
244a28e1d98SAndre Przywara 				div = 1000000000;
245a28e1d98SAndre Przywara 				if (islong) {
246a28e1d98SAndre Przywara 					num = va_arg(va, unsigned long);
247a28e1d98SAndre Przywara 					if (sizeof(long) > 4)
248a28e1d98SAndre Przywara 						div *= div * 10;
249a28e1d98SAndre Przywara 				} else {
2507d9cde10SStefan Roese 					num = va_arg(va, unsigned int);
251a28e1d98SAndre Przywara 				}
252a28e1d98SAndre Przywara 
253a28e1d98SAndre Przywara 				if (ch == 'd') {
254a28e1d98SAndre Przywara 					if (islong && (long)num < 0) {
255a28e1d98SAndre Przywara 						num = -(long)num;
256a28e1d98SAndre Przywara 						out(info, '-');
257a28e1d98SAndre Przywara 					} else if (!islong && (int)num < 0) {
2587d9cde10SStefan Roese 						num = -(int)num;
25945313e83SSimon Glass 						out(info, '-');
2607d9cde10SStefan Roese 					}
261a28e1d98SAndre Przywara 				}
26274b1320aSSimon Glass 				if (!num) {
26345313e83SSimon Glass 					out_dgt(info, 0);
26474b1320aSSimon Glass 				} else {
265a28e1d98SAndre Przywara 					for (; div; div /= 10)
26645313e83SSimon Glass 						div_out(info, &num, div);
26774b1320aSSimon Glass 				}
2687d9cde10SStefan Roese 				break;
2697d9cde10SStefan Roese 			case 'x':
270a28e1d98SAndre Przywara 				if (islong) {
271a28e1d98SAndre Przywara 					num = va_arg(va, unsigned long);
272a28e1d98SAndre Przywara 					div = 1UL << (sizeof(long) * 8 - 4);
273a28e1d98SAndre Przywara 				} else {
2747d9cde10SStefan Roese 					num = va_arg(va, unsigned int);
275a28e1d98SAndre Przywara 					div = 0x10000000;
276a28e1d98SAndre Przywara 				}
27774b1320aSSimon Glass 				if (!num) {
27845313e83SSimon Glass 					out_dgt(info, 0);
27974b1320aSSimon Glass 				} else {
280a28e1d98SAndre Przywara 					for (; div; div /= 0x10)
28145313e83SSimon Glass 						div_out(info, &num, div);
28274b1320aSSimon Glass 				}
2837d9cde10SStefan Roese 				break;
2847d9cde10SStefan Roese 			case 'c':
28545313e83SSimon Glass 				out(info, (char)(va_arg(va, int)));
2867d9cde10SStefan Roese 				break;
2877d9cde10SStefan Roese 			case 's':
2887d9cde10SStefan Roese 				p = va_arg(va, char*);
2897d9cde10SStefan Roese 				break;
290cdce1f76SVignesh R 			case 'p':
291cdce1f76SVignesh R 				pointer(info, fmt, va_arg(va, void *));
292cdce1f76SVignesh R 				while (isalnum(fmt[0]))
293cdce1f76SVignesh R 					fmt++;
294cdce1f76SVignesh R 				break;
2957d9cde10SStefan Roese 			case '%':
29645313e83SSimon Glass 				out(info, '%');
2977d9cde10SStefan Roese 			default:
2987d9cde10SStefan Roese 				break;
2997d9cde10SStefan Roese 			}
3007d9cde10SStefan Roese 
30145313e83SSimon Glass 			*info->bf = 0;
30245313e83SSimon Glass 			info->bf = p;
30345313e83SSimon Glass 			while (*info->bf++ && width > 0)
3041fb67608SSimon Glass 				width--;
3051fb67608SSimon Glass 			while (width-- > 0)
30645313e83SSimon Glass 				info->putc(info, lz ? '0' : ' ');
3078e31681cSSimon Glass 			if (p) {
3087d9cde10SStefan Roese 				while ((ch = *p++))
30945313e83SSimon Glass 					info->putc(info, ch);
3107d9cde10SStefan Roese 			}
3117d9cde10SStefan Roese 		}
3128e31681cSSimon Glass 	}
3137d9cde10SStefan Roese 
3147d9cde10SStefan Roese abort:
3157d9cde10SStefan Roese 	return 0;
3167d9cde10SStefan Roese }
317962a43ccSSjoerd Simons 
3184f1eed75SAlex Kiernan #if CONFIG_IS_ENABLED(PRINTF)
putc_normal(struct printf_info * info,char ch)3194f1eed75SAlex Kiernan static void putc_normal(struct printf_info *info, char ch)
3204f1eed75SAlex Kiernan {
3214f1eed75SAlex Kiernan 	putc(ch);
3224f1eed75SAlex Kiernan }
3234f1eed75SAlex Kiernan 
vprintf(const char * fmt,va_list va)324da70b4d1SHans de Goede int vprintf(const char *fmt, va_list va)
325da70b4d1SHans de Goede {
32645313e83SSimon Glass 	struct printf_info info;
32745313e83SSimon Glass 
32845313e83SSimon Glass 	info.putc = putc_normal;
32945313e83SSimon Glass 	return _vprintf(&info, fmt, va);
330da70b4d1SHans de Goede }
331da70b4d1SHans de Goede 
printf(const char * fmt,...)332962a43ccSSjoerd Simons int printf(const char *fmt, ...)
333962a43ccSSjoerd Simons {
33445313e83SSimon Glass 	struct printf_info info;
33545313e83SSimon Glass 
336962a43ccSSjoerd Simons 	va_list va;
337962a43ccSSjoerd Simons 	int ret;
338962a43ccSSjoerd Simons 
33945313e83SSimon Glass 	info.putc = putc_normal;
340962a43ccSSjoerd Simons 	va_start(va, fmt);
34145313e83SSimon Glass 	ret = _vprintf(&info, fmt, va);
342962a43ccSSjoerd Simons 	va_end(va);
343962a43ccSSjoerd Simons 
344962a43ccSSjoerd Simons 	return ret;
345962a43ccSSjoerd Simons }
3464f1eed75SAlex Kiernan #endif
3475c411d88SSimon Glass 
putc_outstr(struct printf_info * info,char ch)34845313e83SSimon Glass static void putc_outstr(struct printf_info *info, char ch)
3495c411d88SSimon Glass {
35045313e83SSimon Glass 	*info->outstr++ = ch;
3515c411d88SSimon Glass }
3525c411d88SSimon Glass 
sprintf(char * buf,const char * fmt,...)353abeb272dSMarek Vasut int sprintf(char *buf, const char *fmt, ...)
3545c411d88SSimon Glass {
35545313e83SSimon Glass 	struct printf_info info;
3565c411d88SSimon Glass 	va_list va;
3575c411d88SSimon Glass 	int ret;
3585c411d88SSimon Glass 
3595c411d88SSimon Glass 	va_start(va, fmt);
36045313e83SSimon Glass 	info.outstr = buf;
36145313e83SSimon Glass 	info.putc = putc_outstr;
36245313e83SSimon Glass 	ret = _vprintf(&info, fmt, va);
3635c411d88SSimon Glass 	va_end(va);
36445313e83SSimon Glass 	*info.outstr = '\0';
3655c411d88SSimon Glass 
3665c411d88SSimon Glass 	return ret;
3675c411d88SSimon Glass }
368abeb272dSMarek Vasut 
369abeb272dSMarek Vasut /* Note that size is ignored */
snprintf(char * buf,size_t size,const char * fmt,...)370abeb272dSMarek Vasut int snprintf(char *buf, size_t size, const char *fmt, ...)
371abeb272dSMarek Vasut {
37245313e83SSimon Glass 	struct printf_info info;
373abeb272dSMarek Vasut 	va_list va;
374abeb272dSMarek Vasut 	int ret;
375abeb272dSMarek Vasut 
376abeb272dSMarek Vasut 	va_start(va, fmt);
37745313e83SSimon Glass 	info.outstr = buf;
37845313e83SSimon Glass 	info.putc = putc_outstr;
37945313e83SSimon Glass 	ret = _vprintf(&info, fmt, va);
380abeb272dSMarek Vasut 	va_end(va);
38145313e83SSimon Glass 	*info.outstr = '\0';
382abeb272dSMarek Vasut 
383abeb272dSMarek Vasut 	return ret;
384abeb272dSMarek Vasut }
385