Merge tag 'topic/drm-ci-2023-08-31-1' of git://anongit.freedesktop.org/drm/drm
[platform/kernel/linux-starfive.git] / drivers / firmware / efi / libstub / printk.c
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
14 int 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  */
20 void 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
26 static
27 u32 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  */
75 void 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  */
115 int 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 }