162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2013 Intel Corporation; author Matt Fleming 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/console.h> 762306a36Sopenharmony_ci#include <linux/efi.h> 862306a36Sopenharmony_ci#include <linux/font.h> 962306a36Sopenharmony_ci#include <linux/io.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/serial_core.h> 1262306a36Sopenharmony_ci#include <linux/screen_info.h> 1362306a36Sopenharmony_ci#include <linux/string.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <asm/early_ioremap.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic const struct console *earlycon_console __initdata; 1862306a36Sopenharmony_cistatic const struct font_desc *font; 1962306a36Sopenharmony_cistatic u16 cur_line_y, max_line_y; 2062306a36Sopenharmony_cistatic u32 efi_x_array[1024]; 2162306a36Sopenharmony_cistatic u32 efi_x, efi_y; 2262306a36Sopenharmony_cistatic u64 fb_base; 2362306a36Sopenharmony_cistatic bool fb_wb; 2462306a36Sopenharmony_cistatic void *efi_fb; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* 2762306a36Sopenharmony_ci * EFI earlycon needs to use early_memremap() to map the framebuffer. 2862306a36Sopenharmony_ci * But early_memremap() is not usable for 'earlycon=efifb keep_bootcon', 2962306a36Sopenharmony_ci * memremap() should be used instead. memremap() will be available after 3062306a36Sopenharmony_ci * paging_init() which is earlier than initcall callbacks. Thus adding this 3162306a36Sopenharmony_ci * early initcall function early_efi_map_fb() to map the whole EFI framebuffer. 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_cistatic int __init efi_earlycon_remap_fb(void) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci /* bail if there is no bootconsole or it was unregistered already */ 3662306a36Sopenharmony_ci if (!earlycon_console || !console_is_registered(earlycon_console)) 3762306a36Sopenharmony_ci return 0; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci efi_fb = memremap(fb_base, screen_info.lfb_size, 4062306a36Sopenharmony_ci fb_wb ? MEMREMAP_WB : MEMREMAP_WC); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci return efi_fb ? 0 : -ENOMEM; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ciearly_initcall(efi_earlycon_remap_fb); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int __init efi_earlycon_unmap_fb(void) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci /* unmap the bootconsole fb unless keep_bootcon left it registered */ 4962306a36Sopenharmony_ci if (efi_fb && !console_is_registered(earlycon_console)) 5062306a36Sopenharmony_ci memunmap(efi_fb); 5162306a36Sopenharmony_ci return 0; 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_cilate_initcall(efi_earlycon_unmap_fb); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic __ref void *efi_earlycon_map(unsigned long start, unsigned long len) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci pgprot_t fb_prot; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (efi_fb) 6062306a36Sopenharmony_ci return efi_fb + start; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci fb_prot = fb_wb ? PAGE_KERNEL : pgprot_writecombine(PAGE_KERNEL); 6362306a36Sopenharmony_ci return early_memremap_prot(fb_base + start, len, pgprot_val(fb_prot)); 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic __ref void efi_earlycon_unmap(void *addr, unsigned long len) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci if (efi_fb) 6962306a36Sopenharmony_ci return; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci early_memunmap(addr, len); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic void efi_earlycon_clear_scanline(unsigned int y) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci unsigned long *dst; 7762306a36Sopenharmony_ci u16 len; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci len = screen_info.lfb_linelength; 8062306a36Sopenharmony_ci dst = efi_earlycon_map(y*len, len); 8162306a36Sopenharmony_ci if (!dst) 8262306a36Sopenharmony_ci return; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci memset(dst, 0, len); 8562306a36Sopenharmony_ci efi_earlycon_unmap(dst, len); 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic void efi_earlycon_scroll_up(void) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci unsigned long *dst, *src; 9162306a36Sopenharmony_ci u16 maxlen = 0; 9262306a36Sopenharmony_ci u16 len; 9362306a36Sopenharmony_ci u32 i, height; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci /* Find the cached maximum x coordinate */ 9662306a36Sopenharmony_ci for (i = 0; i < max_line_y; i++) { 9762306a36Sopenharmony_ci if (efi_x_array[i] > maxlen) 9862306a36Sopenharmony_ci maxlen = efi_x_array[i]; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci maxlen *= 4; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci len = screen_info.lfb_linelength; 10362306a36Sopenharmony_ci height = screen_info.lfb_height; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci for (i = 0; i < height - font->height; i++) { 10662306a36Sopenharmony_ci dst = efi_earlycon_map(i*len, len); 10762306a36Sopenharmony_ci if (!dst) 10862306a36Sopenharmony_ci return; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci src = efi_earlycon_map((i + font->height) * len, len); 11162306a36Sopenharmony_ci if (!src) { 11262306a36Sopenharmony_ci efi_earlycon_unmap(dst, len); 11362306a36Sopenharmony_ci return; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci memmove(dst, src, maxlen); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci efi_earlycon_unmap(src, len); 11962306a36Sopenharmony_ci efi_earlycon_unmap(dst, len); 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic void efi_earlycon_write_char(u32 *dst, unsigned char c, unsigned int h) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci const u32 color_black = 0x00000000; 12662306a36Sopenharmony_ci const u32 color_white = 0x00ffffff; 12762306a36Sopenharmony_ci const u8 *src; 12862306a36Sopenharmony_ci int m, n, bytes; 12962306a36Sopenharmony_ci u8 x; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci bytes = BITS_TO_BYTES(font->width); 13262306a36Sopenharmony_ci src = font->data + c * font->height * bytes + h * bytes; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci for (m = 0; m < font->width; m++) { 13562306a36Sopenharmony_ci n = m % 8; 13662306a36Sopenharmony_ci x = *(src + m / 8); 13762306a36Sopenharmony_ci if ((x >> (7 - n)) & 1) 13862306a36Sopenharmony_ci *dst = color_white; 13962306a36Sopenharmony_ci else 14062306a36Sopenharmony_ci *dst = color_black; 14162306a36Sopenharmony_ci dst++; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic void 14662306a36Sopenharmony_ciefi_earlycon_write(struct console *con, const char *str, unsigned int num) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct screen_info *si; 14962306a36Sopenharmony_ci u32 cur_efi_x = efi_x; 15062306a36Sopenharmony_ci unsigned int len; 15162306a36Sopenharmony_ci const char *s; 15262306a36Sopenharmony_ci void *dst; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci si = &screen_info; 15562306a36Sopenharmony_ci len = si->lfb_linelength; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci while (num) { 15862306a36Sopenharmony_ci unsigned int linemax = (si->lfb_width - efi_x) / font->width; 15962306a36Sopenharmony_ci unsigned int h, count; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci count = strnchrnul(str, num, '\n') - str; 16262306a36Sopenharmony_ci if (count > linemax) 16362306a36Sopenharmony_ci count = linemax; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci for (h = 0; h < font->height; h++) { 16662306a36Sopenharmony_ci unsigned int n, x; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci dst = efi_earlycon_map((efi_y + h) * len, len); 16962306a36Sopenharmony_ci if (!dst) 17062306a36Sopenharmony_ci return; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci s = str; 17362306a36Sopenharmony_ci n = count; 17462306a36Sopenharmony_ci x = efi_x; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci while (n-- > 0) { 17762306a36Sopenharmony_ci efi_earlycon_write_char(dst + x*4, *s, h); 17862306a36Sopenharmony_ci x += font->width; 17962306a36Sopenharmony_ci s++; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci efi_earlycon_unmap(dst, len); 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci num -= count; 18662306a36Sopenharmony_ci efi_x += count * font->width; 18762306a36Sopenharmony_ci str += count; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (num > 0 && *s == '\n') { 19062306a36Sopenharmony_ci cur_efi_x = efi_x; 19162306a36Sopenharmony_ci efi_x = 0; 19262306a36Sopenharmony_ci efi_y += font->height; 19362306a36Sopenharmony_ci str++; 19462306a36Sopenharmony_ci num--; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (efi_x + font->width > si->lfb_width) { 19862306a36Sopenharmony_ci cur_efi_x = efi_x; 19962306a36Sopenharmony_ci efi_x = 0; 20062306a36Sopenharmony_ci efi_y += font->height; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci if (efi_y + font->height > si->lfb_height) { 20462306a36Sopenharmony_ci u32 i; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci efi_x_array[cur_line_y] = cur_efi_x; 20762306a36Sopenharmony_ci cur_line_y = (cur_line_y + 1) % max_line_y; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci efi_y -= font->height; 21062306a36Sopenharmony_ci efi_earlycon_scroll_up(); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci for (i = 0; i < font->height; i++) 21362306a36Sopenharmony_ci efi_earlycon_clear_scanline(efi_y + i); 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_cistatic bool __initdata fb_probed; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_civoid __init efi_earlycon_reprobe(void) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci if (fb_probed) 22362306a36Sopenharmony_ci setup_earlycon("efifb"); 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic int __init efi_earlycon_setup(struct earlycon_device *device, 22762306a36Sopenharmony_ci const char *opt) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci struct screen_info *si; 23062306a36Sopenharmony_ci u16 xres, yres; 23162306a36Sopenharmony_ci u32 i; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci fb_wb = opt && !strcmp(opt, "ram"); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI) { 23662306a36Sopenharmony_ci fb_probed = true; 23762306a36Sopenharmony_ci return -ENODEV; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci fb_base = screen_info.lfb_base; 24162306a36Sopenharmony_ci if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE) 24262306a36Sopenharmony_ci fb_base |= (u64)screen_info.ext_lfb_base << 32; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci si = &screen_info; 24562306a36Sopenharmony_ci xres = si->lfb_width; 24662306a36Sopenharmony_ci yres = si->lfb_height; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* 24962306a36Sopenharmony_ci * efi_earlycon_write_char() implicitly assumes a framebuffer with 25062306a36Sopenharmony_ci * 32 bits per pixel. 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_ci if (si->lfb_depth != 32) 25362306a36Sopenharmony_ci return -ENODEV; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci font = get_default_font(xres, yres, -1, -1); 25662306a36Sopenharmony_ci if (!font) 25762306a36Sopenharmony_ci return -ENODEV; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* Fill the cache with maximum possible value of x coordinate */ 26062306a36Sopenharmony_ci memset32(efi_x_array, rounddown(xres, font->width), ARRAY_SIZE(efi_x_array)); 26162306a36Sopenharmony_ci efi_y = rounddown(yres, font->height); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* Make sure we have cache for the x coordinate for the full screen */ 26462306a36Sopenharmony_ci max_line_y = efi_y / font->height + 1; 26562306a36Sopenharmony_ci cur_line_y = 0; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci efi_y -= font->height; 26862306a36Sopenharmony_ci for (i = 0; i < (yres - efi_y) / font->height; i++) 26962306a36Sopenharmony_ci efi_earlycon_scroll_up(); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci device->con->write = efi_earlycon_write; 27262306a36Sopenharmony_ci earlycon_console = device->con; 27362306a36Sopenharmony_ci return 0; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ciEARLYCON_DECLARE(efifb, efi_earlycon_setup); 276