Add support for hexadecimal and pointer format specifiers to snprintf()

The current implementation of snprintf() does not support pointer and
hexadecimal format specifiers, which can be needed, for instance, for
DTB manipulations.

This patch adds that functionality by borrowing some code from the
printf() implementation.

Signed-off-by: Javier Almansa Sobrino <javier.almansasobrino@arm.com>
Change-Id: I2076ea46693a73a04890982bf20e3c633c2767fb
diff --git a/lib/libc/snprintf.c b/lib/libc/snprintf.c
index 38ad1c7..2686327 100644
--- a/lib/libc/snprintf.c
+++ b/lib/libc/snprintf.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2017-2020, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -24,27 +24,55 @@
 	}
 }
 
-static void unsigned_dec_print(char **s, size_t n, size_t *chars_printed,
-			       unsigned int unum)
+static void unsigned_num_print(char **s, size_t n, size_t *chars_printed,
+			      unsigned long long int unum,
+			      unsigned int radix, char padc, int padn,
+			      bool capitalise)
 {
-	/* Enough for a 32-bit unsigned decimal integer (4294967295). */
-	char num_buf[10];
+	/* Just need enough space to store 64 bit decimal integer */
+	char num_buf[20];
 	int i = 0;
+	int width;
 	unsigned int rem;
+	char ascii_a = capitalise ? 'A' : 'a';
 
 	do {
-		rem = unum % 10U;
-		num_buf[i++] = '0' + rem;
-		unum /= 10U;
+		rem = unum % radix;
+		if (rem < 10U) {
+			num_buf[i] = '0' + rem;
+		} else {
+			num_buf[i] = ascii_a + (rem - 10U);
+		}
+		i++;
+		unum /= radix;
 	} while (unum > 0U);
 
-	while (--i >= 0) {
-		if (*chars_printed < n) {
-			*(*s) = num_buf[i];
-			(*s)++;
+	width = i;
+	if (padn > width) {
+		(*chars_printed) += (size_t)padn;
+	} else {
+		(*chars_printed) += (size_t)width;
+	}
+
+	if (*chars_printed < n) {
+
+		if (padn > 0) {
+			while (width < padn) {
+				*(*s)++ = padc;
+				padn--;
+			}
 		}
 
-		(*chars_printed)++;
+		while (--i >= 0) {
+			*(*s)++ = num_buf[i];
+		}
+
+		if (padn < 0) {
+			while (width < -padn) {
+				*(*s)++ = padc;
+				padn++;
+			}
+		}
 	}
 }
 
@@ -52,9 +80,16 @@
  * Reduced snprintf to be used for Trusted firmware.
  * The following type specifiers are supported:
  *
+ * %x (or %X) - hexadecimal format
  * %d or %i - signed decimal format
  * %s - string format
  * %u - unsigned decimal format
+ * %p - pointer format
+ *
+ * The following padding specifiers are supported by this print
+ * %0NN - Left-pad the number with 0s (NN is a decimal number)
+ * %NN - Left-pad the number or string with spaces (NN is a decimal number)
+ * %-NN - Right-pad the number or string with spaces (NN is a decimal number)
  *
  * The function panics on all other formats specifiers.
  *
@@ -66,8 +101,12 @@
 {
 	va_list args;
 	int num;
-	unsigned int unum;
+	unsigned long long int unum;
 	char *str;
+	char padc;		/* Padding character */
+	int padn;		/* Number of characters to pad */
+	bool left;
+	bool capitalise;
 	size_t chars_printed = 0U;
 
 	if (n == 0U) {
@@ -83,11 +122,39 @@
 
 	va_start(args, fmt);
 	while (*fmt != '\0') {
+		left = false;
+		padc ='\0';
+		padn = 0;
+		capitalise = false;
 
 		if (*fmt == '%') {
 			fmt++;
 			/* Check the format specifier. */
+loop:
 			switch (*fmt) {
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				padc = (*fmt == '0') ? '0' : ' ';
+				for (padn = 0; *fmt >= '0' && *fmt <= '9'; fmt++) {
+					padn = (padn * 10) + (*fmt - '0');
+				}
+				if (left) {
+					padn = -padn;
+				}
+				goto loop;
+			case '-':
+				left = true;
+				fmt++;
+				goto loop;
+
 			case 'i':
 			case 'd':
 				num = va_arg(args, int);
@@ -104,7 +171,8 @@
 					unum = (unsigned int)num;
 				}
 
-				unsigned_dec_print(&s, n, &chars_printed, unum);
+				unsigned_num_print(&s, n, &chars_printed,
+						   unum, 10, padc, padn, false);
 				break;
 			case 's':
 				str = va_arg(args, char *);
@@ -112,8 +180,27 @@
 				break;
 			case 'u':
 				unum = va_arg(args, unsigned int);
-				unsigned_dec_print(&s, n, &chars_printed, unum);
+				unsigned_num_print(&s, n, &chars_printed,
+						   unum, 10, padc, padn, false);
 				break;
+			case 'p':
+				unum = (uintptr_t)va_arg(args, void *);
+				if (unum > 0U) {
+					string_print(&s, n, &chars_printed, "0x");
+					padn -= 2;
+				}
+				unsigned_num_print(&s, n, &chars_printed,
+						   unum, 16, padc, padn, false);
+				break;
+			case 'X':
+				capitalise = true;
+			case 'x':
+				unum = va_arg(args, unsigned int);
+				unsigned_num_print(&s, n, &chars_printed,
+						   unum, 16, padc, padn,
+						   capitalise);
+				break;
+
 			default:
 				/* Panic on any other format specifier. */
 				ERROR("snprintf: specifier with ASCII code '%d' not supported.",