1// SPDX-License-Identifier: GPL-2.0
2
3#include <linux/stdarg.h>
4
5#include <linux/ctype.h>
6#include <linux/efi.h>
7#include <linux/kernel.h>
8#include <linux/printk.h> /* For CONSOLE_LOGLEVEL_* */
9#include <asm/efi.h>
10#include <asm/setup.h>
11
12#include "efistub.h"
13
14int efi_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
15
16/**
17 * efi_char16_puts() - Write a UCS-2 encoded string to the console
18 * @str:	UCS-2 encoded string
19 */
20void efi_char16_puts(efi_char16_t *str)
21{
22	efi_call_proto(efi_table_attr(efi_system_table, con_out),
23		       output_string, str);
24}
25
26static
27u32 utf8_to_utf32(const u8 **s8)
28{
29	u32 c32;
30	u8 c0, cx;
31	size_t clen, i;
32
33	c0 = cx = *(*s8)++;
34	/*
35	 * The position of the most-significant 0 bit gives us the length of
36	 * a multi-octet encoding.
37	 */
38	for (clen = 0; cx & 0x80; ++clen)
39		cx <<= 1;
40	/*
41	 * If the 0 bit is in position 8, this is a valid single-octet
42	 * encoding. If the 0 bit is in position 7 or positions 1-3, the
43	 * encoding is invalid.
44	 * In either case, we just return the first octet.
45	 */
46	if (clen < 2 || clen > 4)
47		return c0;
48	/* Get the bits from the first octet. */
49	c32 = cx >> clen--;
50	for (i = 0; i < clen; ++i) {
51		/* Trailing octets must have 10 in most significant bits. */
52		cx = (*s8)[i] ^ 0x80;
53		if (cx & 0xc0)
54			return c0;
55		c32 = (c32 << 6) | cx;
56	}
57	/*
58	 * Check for validity:
59	 * - The character must be in the Unicode range.
60	 * - It must not be a surrogate.
61	 * - It must be encoded using the correct number of octets.
62	 */
63	if (c32 > 0x10ffff ||
64	    (c32 & 0xf800) == 0xd800 ||
65	    clen != (c32 >= 0x80) + (c32 >= 0x800) + (c32 >= 0x10000))
66		return c0;
67	*s8 += clen;
68	return c32;
69}
70
71/**
72 * efi_puts() - Write a UTF-8 encoded string to the console
73 * @str:	UTF-8 encoded string
74 */
75void efi_puts(const char *str)
76{
77	efi_char16_t buf[128];
78	size_t pos = 0, lim = ARRAY_SIZE(buf);
79	const u8 *s8 = (const u8 *)str;
80	u32 c32;
81
82	while (*s8) {
83		if (*s8 == '\n')
84			buf[pos++] = L'\r';
85		c32 = utf8_to_utf32(&s8);
86		if (c32 < 0x10000) {
87			/* Characters in plane 0 use a single word. */
88			buf[pos++] = c32;
89		} else {
90			/*
91			 * Characters in other planes encode into a surrogate
92			 * pair.
93			 */
94			buf[pos++] = (0xd800 - (0x10000 >> 10)) + (c32 >> 10);
95			buf[pos++] = 0xdc00 + (c32 & 0x3ff);
96		}
97		if (*s8 == '\0' || pos >= lim - 2) {
98			buf[pos] = L'\0';
99			efi_char16_puts(buf);
100			pos = 0;
101		}
102	}
103}
104
105/**
106 * efi_printk() - Print a kernel message
107 * @fmt:	format string
108 *
109 * The first letter of the format string is used to determine the logging level
110 * of the message. If the level is less then the current EFI logging level, the
111 * message is suppressed. The message will be truncated to 255 bytes.
112 *
113 * Return:	number of printed characters
114 */
115int efi_printk(const char *fmt, ...)
116{
117	char printf_buf[256];
118	va_list args;
119	int printed;
120	int loglevel = printk_get_level(fmt);
121
122	switch (loglevel) {
123	case '0' ... '9':
124		loglevel -= '0';
125		break;
126	default:
127		/*
128		 * Use loglevel -1 for cases where we just want to print to
129		 * the screen.
130		 */
131		loglevel = -1;
132		break;
133	}
134
135	if (loglevel >= efi_loglevel)
136		return 0;
137
138	if (loglevel >= 0)
139		efi_puts("EFI stub: ");
140
141	fmt = printk_skip_level(fmt);
142
143	va_start(args, fmt);
144	printed = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args);
145	va_end(args);
146
147	efi_puts(printf_buf);
148	if (printed >= sizeof(printf_buf)) {
149		efi_puts("[Message truncated]\n");
150		return -1;
151	}
152
153	return printed;
154}
155