155 lines
3.3 KiB
C
155 lines
3.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/stdarg.h>
|
|
|
|
#include <linux/ctype.h>
|
|
#include <linux/efi.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/printk.h> /* For CONSOLE_LOGLEVEL_* */
|
|
#include <asm/efi.h>
|
|
#include <asm/setup.h>
|
|
|
|
#include "efistub.h"
|
|
|
|
int efi_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
|
|
|
|
/**
|
|
* efi_char16_puts() - Write a UCS-2 encoded string to the console
|
|
* @str: UCS-2 encoded string
|
|
*/
|
|
void efi_char16_puts(efi_char16_t *str)
|
|
{
|
|
efi_call_proto(efi_table_attr(efi_system_table, con_out),
|
|
output_string, str);
|
|
}
|
|
|
|
static
|
|
u32 utf8_to_utf32(const u8 **s8)
|
|
{
|
|
u32 c32;
|
|
u8 c0, cx;
|
|
size_t clen, i;
|
|
|
|
c0 = cx = *(*s8)++;
|
|
/*
|
|
* The position of the most-significant 0 bit gives us the length of
|
|
* a multi-octet encoding.
|
|
*/
|
|
for (clen = 0; cx & 0x80; ++clen)
|
|
cx <<= 1;
|
|
/*
|
|
* If the 0 bit is in position 8, this is a valid single-octet
|
|
* encoding. If the 0 bit is in position 7 or positions 1-3, the
|
|
* encoding is invalid.
|
|
* In either case, we just return the first octet.
|
|
*/
|
|
if (clen < 2 || clen > 4)
|
|
return c0;
|
|
/* Get the bits from the first octet. */
|
|
c32 = cx >> clen--;
|
|
for (i = 0; i < clen; ++i) {
|
|
/* Trailing octets must have 10 in most significant bits. */
|
|
cx = (*s8)[i] ^ 0x80;
|
|
if (cx & 0xc0)
|
|
return c0;
|
|
c32 = (c32 << 6) | cx;
|
|
}
|
|
/*
|
|
* Check for validity:
|
|
* - The character must be in the Unicode range.
|
|
* - It must not be a surrogate.
|
|
* - It must be encoded using the correct number of octets.
|
|
*/
|
|
if (c32 > 0x10ffff ||
|
|
(c32 & 0xf800) == 0xd800 ||
|
|
clen != (c32 >= 0x80) + (c32 >= 0x800) + (c32 >= 0x10000))
|
|
return c0;
|
|
*s8 += clen;
|
|
return c32;
|
|
}
|
|
|
|
/**
|
|
* efi_puts() - Write a UTF-8 encoded string to the console
|
|
* @str: UTF-8 encoded string
|
|
*/
|
|
void efi_puts(const char *str)
|
|
{
|
|
efi_char16_t buf[128];
|
|
size_t pos = 0, lim = ARRAY_SIZE(buf);
|
|
const u8 *s8 = (const u8 *)str;
|
|
u32 c32;
|
|
|
|
while (*s8) {
|
|
if (*s8 == '\n')
|
|
buf[pos++] = L'\r';
|
|
c32 = utf8_to_utf32(&s8);
|
|
if (c32 < 0x10000) {
|
|
/* Characters in plane 0 use a single word. */
|
|
buf[pos++] = c32;
|
|
} else {
|
|
/*
|
|
* Characters in other planes encode into a surrogate
|
|
* pair.
|
|
*/
|
|
buf[pos++] = (0xd800 - (0x10000 >> 10)) + (c32 >> 10);
|
|
buf[pos++] = 0xdc00 + (c32 & 0x3ff);
|
|
}
|
|
if (*s8 == '\0' || pos >= lim - 2) {
|
|
buf[pos] = L'\0';
|
|
efi_char16_puts(buf);
|
|
pos = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* efi_printk() - Print a kernel message
|
|
* @fmt: format string
|
|
*
|
|
* The first letter of the format string is used to determine the logging level
|
|
* of the message. If the level is less then the current EFI logging level, the
|
|
* message is suppressed. The message will be truncated to 255 bytes.
|
|
*
|
|
* Return: number of printed characters
|
|
*/
|
|
int efi_printk(const char *fmt, ...)
|
|
{
|
|
char printf_buf[256];
|
|
va_list args;
|
|
int printed;
|
|
int loglevel = printk_get_level(fmt);
|
|
|
|
switch (loglevel) {
|
|
case '0' ... '9':
|
|
loglevel -= '0';
|
|
break;
|
|
default:
|
|
/*
|
|
* Use loglevel -1 for cases where we just want to print to
|
|
* the screen.
|
|
*/
|
|
loglevel = -1;
|
|
break;
|
|
}
|
|
|
|
if (loglevel >= efi_loglevel)
|
|
return 0;
|
|
|
|
if (loglevel >= 0)
|
|
efi_puts("EFI stub: ");
|
|
|
|
fmt = printk_skip_level(fmt);
|
|
|
|
va_start(args, fmt);
|
|
printed = vsnprintf(printf_buf, sizeof(printf_buf), fmt, args);
|
|
va_end(args);
|
|
|
|
efi_puts(printf_buf);
|
|
if (printed >= sizeof(printf_buf)) {
|
|
efi_puts("[Message truncated]\n");
|
|
return -1;
|
|
}
|
|
|
|
return printed;
|
|
}
|