root/drivers/firmware/efi/libstub/printk.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/kern_levels.h>
#include <asm/efi.h>
#include <asm/setup.h>

#include "efistub.h"

int efi_loglevel = LOGLEVEL_NOTICE;

/**
 * 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;
}