1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2013 Intel Corporation; author Matt Fleming 4 */ 5 6 #include <linux/console.h> 7 #include <linux/efi.h> 8 #include <linux/font.h> 9 #include <linux/io.h> 10 #include <linux/kernel.h> 11 #include <linux/serial_core.h> 12 #include <linux/screen_info.h> 13 14 #include <asm/early_ioremap.h> 15 16 static const struct console *earlycon_console __initdata; 17 static const struct font_desc *font; 18 static u32 efi_x, efi_y; 19 static u64 fb_base; 20 static pgprot_t fb_prot; 21 static void *efi_fb; 22 23 /* 24 * EFI earlycon needs to use early_memremap() to map the framebuffer. 25 * But early_memremap() is not usable for 'earlycon=efifb keep_bootcon', 26 * memremap() should be used instead. memremap() will be available after 27 * paging_init() which is earlier than initcall callbacks. Thus adding this 28 * early initcall function early_efi_map_fb() to map the whole EFI framebuffer. 29 */ 30 static int __init efi_earlycon_remap_fb(void) 31 { 32 /* bail if there is no bootconsole or it has been disabled already */ 33 if (!earlycon_console || !(earlycon_console->flags & CON_ENABLED)) 34 return 0; 35 36 if (pgprot_val(fb_prot) == pgprot_val(PAGE_KERNEL)) 37 efi_fb = memremap(fb_base, screen_info.lfb_size, MEMREMAP_WB); 38 else 39 efi_fb = memremap(fb_base, screen_info.lfb_size, MEMREMAP_WC); 40 41 return efi_fb ? 0 : -ENOMEM; 42 } 43 early_initcall(efi_earlycon_remap_fb); 44 45 static int __init efi_earlycon_unmap_fb(void) 46 { 47 /* unmap the bootconsole fb unless keep_bootcon has left it enabled */ 48 if (efi_fb && !(earlycon_console->flags & CON_ENABLED)) 49 memunmap(efi_fb); 50 return 0; 51 } 52 late_initcall(efi_earlycon_unmap_fb); 53 54 static __ref void *efi_earlycon_map(unsigned long start, unsigned long len) 55 { 56 if (efi_fb) 57 return efi_fb + start; 58 59 return early_memremap_prot(fb_base + start, len, pgprot_val(fb_prot)); 60 } 61 62 static __ref void efi_earlycon_unmap(void *addr, unsigned long len) 63 { 64 if (efi_fb) 65 return; 66 67 early_memunmap(addr, len); 68 } 69 70 static void efi_earlycon_clear_scanline(unsigned int y) 71 { 72 unsigned long *dst; 73 u16 len; 74 75 len = screen_info.lfb_linelength; 76 dst = efi_earlycon_map(y*len, len); 77 if (!dst) 78 return; 79 80 memset(dst, 0, len); 81 efi_earlycon_unmap(dst, len); 82 } 83 84 static void efi_earlycon_scroll_up(void) 85 { 86 unsigned long *dst, *src; 87 u16 len; 88 u32 i, height; 89 90 len = screen_info.lfb_linelength; 91 height = screen_info.lfb_height; 92 93 for (i = 0; i < height - font->height; i++) { 94 dst = efi_earlycon_map(i*len, len); 95 if (!dst) 96 return; 97 98 src = efi_earlycon_map((i + font->height) * len, len); 99 if (!src) { 100 efi_earlycon_unmap(dst, len); 101 return; 102 } 103 104 memmove(dst, src, len); 105 106 efi_earlycon_unmap(src, len); 107 efi_earlycon_unmap(dst, len); 108 } 109 } 110 111 static void efi_earlycon_write_char(u32 *dst, unsigned char c, unsigned int h) 112 { 113 const u32 color_black = 0x00000000; 114 const u32 color_white = 0x00ffffff; 115 const u8 *src; 116 u8 s8; 117 int m; 118 119 src = font->data + c * font->height; 120 s8 = *(src + h); 121 122 for (m = 0; m < 8; m++) { 123 if ((s8 >> (7 - m)) & 1) 124 *dst = color_white; 125 else 126 *dst = color_black; 127 dst++; 128 } 129 } 130 131 static void 132 efi_earlycon_write(struct console *con, const char *str, unsigned int num) 133 { 134 struct screen_info *si; 135 unsigned int len; 136 const char *s; 137 void *dst; 138 139 si = &screen_info; 140 len = si->lfb_linelength; 141 142 while (num) { 143 unsigned int linemax; 144 unsigned int h, count = 0; 145 146 for (s = str; *s && *s != '\n'; s++) { 147 if (count == num) 148 break; 149 count++; 150 } 151 152 linemax = (si->lfb_width - efi_x) / font->width; 153 if (count > linemax) 154 count = linemax; 155 156 for (h = 0; h < font->height; h++) { 157 unsigned int n, x; 158 159 dst = efi_earlycon_map((efi_y + h) * len, len); 160 if (!dst) 161 return; 162 163 s = str; 164 n = count; 165 x = efi_x; 166 167 while (n-- > 0) { 168 efi_earlycon_write_char(dst + x*4, *s, h); 169 x += font->width; 170 s++; 171 } 172 173 efi_earlycon_unmap(dst, len); 174 } 175 176 num -= count; 177 efi_x += count * font->width; 178 str += count; 179 180 if (num > 0 && *s == '\n') { 181 efi_x = 0; 182 efi_y += font->height; 183 str++; 184 num--; 185 } 186 187 if (efi_x + font->width > si->lfb_width) { 188 efi_x = 0; 189 efi_y += font->height; 190 } 191 192 if (efi_y + font->height > si->lfb_height) { 193 u32 i; 194 195 efi_y -= font->height; 196 efi_earlycon_scroll_up(); 197 198 for (i = 0; i < font->height; i++) 199 efi_earlycon_clear_scanline(efi_y + i); 200 } 201 } 202 } 203 204 static int __init efi_earlycon_setup(struct earlycon_device *device, 205 const char *opt) 206 { 207 struct screen_info *si; 208 u16 xres, yres; 209 u32 i; 210 211 if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI) 212 return -ENODEV; 213 214 fb_base = screen_info.lfb_base; 215 if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE) 216 fb_base |= (u64)screen_info.ext_lfb_base << 32; 217 218 if (opt && !strcmp(opt, "ram")) 219 fb_prot = PAGE_KERNEL; 220 else 221 fb_prot = pgprot_writecombine(PAGE_KERNEL); 222 223 si = &screen_info; 224 xres = si->lfb_width; 225 yres = si->lfb_height; 226 227 /* 228 * efi_earlycon_write_char() implicitly assumes a framebuffer with 229 * 32 bits per pixel. 230 */ 231 if (si->lfb_depth != 32) 232 return -ENODEV; 233 234 font = get_default_font(xres, yres, -1, -1); 235 if (!font) 236 return -ENODEV; 237 238 efi_y = rounddown(yres, font->height) - font->height; 239 for (i = 0; i < (yres - efi_y) / font->height; i++) 240 efi_earlycon_scroll_up(); 241 242 device->con->write = efi_earlycon_write; 243 earlycon_console = device->con; 244 return 0; 245 } 246 EARLYCON_DECLARE(efifb, efi_earlycon_setup); 247