On Sat, Aug 06, 2022 at 07:32:24PM +0300, Markuss Broks wrote: > Add early console support for generic linear framebuffer devices. > This driver supports probing from cmdline early parameters > or from the device-tree using information in simple-framebuffer node. > The EFI functionality should be retained in whole. > The driver was disabled on ARM because of a bug in early_ioremap > implementation on ARM and on IA64 because of lack of early_memremap_prot. > > Signed-off-by: Markuss Broks <markuss.broks@xxxxxxxxx> > --- > .../admin-guide/kernel-parameters.txt | 12 +- > MAINTAINERS | 5 + > drivers/firmware/efi/Kconfig | 7 +- > drivers/firmware/efi/Makefile | 1 - > drivers/firmware/efi/earlycon.c | 246 -------------- > drivers/video/console/Kconfig | 11 + > drivers/video/console/Makefile | 1 + > drivers/video/console/earlycon.c | 305 ++++++++++++++++++ Ok I have a more fundamental issue with this than the lack of proper patch splitting I mentioned in the other thread. This is the wrong place. drivers/video/console is about the various vt console implementations, which supply a struct consw to con_register_driver. This otoh is an (early) kernel/printk console implemented using struct console. Totally different thing, and really shouldn't end up in drivers/video/console imo. Somewhere in drivers/firmware might still be the best place, the sysfb stuff is also there. Maybe drivers/firmware/sysfb_earlycon.c? Also patch split is still an issue here, like I and Greg already said. -Daniel > 8 files changed, 332 insertions(+), 256 deletions(-) > delete mode 100644 drivers/firmware/efi/earlycon.c > create mode 100644 drivers/video/console/earlycon.c > > diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt > index 8090130b544b0701237a7b657a29c83c000a60f4..bccb1ac8978eb5cf7e2bb20834b1881b27040666 100644 > --- a/Documentation/admin-guide/kernel-parameters.txt > +++ b/Documentation/admin-guide/kernel-parameters.txt > @@ -1281,12 +1281,9 @@ > specified address. The serial port must already be > setup and configured. Options are not yet supported. > > - efifb,[options] > + efifb > Start an early, unaccelerated console on the EFI > - memory mapped framebuffer (if available). On cache > - coherent non-x86 systems that use system memory for > - the framebuffer, pass the 'ram' option so that it is > - mapped with the correct attributes. > + memory mapped framebuffer (if available). > > linflex,<addr> > Use early console provided by Freescale LINFlexD UART > @@ -1294,6 +1291,11 @@ > address must be provided, and the serial port must > already be setup and configured. > > + simplefb,<addr>,<width>x<height>x<bpp> > + Use early console with simple framebuffer that is > + pre-initialized by firmware. A valid base address, > + width, height and pixel size must be provided. > + > earlyprintk= [X86,SH,ARM,M68k,S390] > earlyprintk=vga > earlyprintk=sclp > diff --git a/MAINTAINERS b/MAINTAINERS > index 1fc9ead83d2aa3e60ccc4cfa8ee16df09ef579bf..af8b8e289483b6a264d477145061bd0e0ba34a25 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7033,6 +7033,11 @@ Q: http://patchwork.linuxtv.org/project/linux-media/list/ > T: git git://linuxtv.org/anttip/media_tree.git > F: drivers/media/tuners/e4000* > > +EARLY CONSOLE FRAMEBUFFER DRIVER > +M: Markuss Broks <markuss.broks@xxxxxxxxx> > +S: Maintained > +F: drivers/video/console/earlycon.c > + > EARTH_PT1 MEDIA DRIVER > M: Akihiro Tsukada <tskd08@xxxxxxxxx> > L: linux-media@xxxxxxxxxxxxxxx > diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig > index 7aa4717cdcac46f91dd202f868c463388eb02735..ea76ccfb9bcd8ba44ddca06052eaa442ed6c30f7 100644 > --- a/drivers/firmware/efi/Kconfig > +++ b/drivers/firmware/efi/Kconfig > @@ -259,10 +259,9 @@ config EFI_DISABLE_PCI_DMA > may be used to override this option. > > config EFI_EARLYCON > - def_bool y > - depends on SERIAL_EARLYCON && !ARM && !IA64 > - select FONT_SUPPORT > - select ARCH_USE_MEMREMAP_PROT > + bool "EFI early console support" > + select FB_EARLYCON > + default y > > config EFI_CUSTOM_SSDT_OVERLAYS > bool "Load custom ACPI SSDT overlay from an EFI variable" > diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile > index c02ff25dd47707090a2ab86ee4f330e467f878f5..64eea61fbb43d76ec2d5416d467dfbb9aa21bda0 100644 > --- a/drivers/firmware/efi/Makefile > +++ b/drivers/firmware/efi/Makefile > @@ -44,6 +44,5 @@ obj-$(CONFIG_ARM64) += $(arm-obj-y) > riscv-obj-$(CONFIG_EFI) := efi-init.o riscv-runtime.o > obj-$(CONFIG_RISCV) += $(riscv-obj-y) > obj-$(CONFIG_EFI_CAPSULE_LOADER) += capsule-loader.o > -obj-$(CONFIG_EFI_EARLYCON) += earlycon.o > obj-$(CONFIG_UEFI_CPER_ARM) += cper-arm.o > obj-$(CONFIG_UEFI_CPER_X86) += cper-x86.o > diff --git a/drivers/firmware/efi/earlycon.c b/drivers/firmware/efi/earlycon.c > deleted file mode 100644 > index a52236e11e5f73ddea5bb1f42ca2ca7c42425dab..0000000000000000000000000000000000000000 > --- a/drivers/firmware/efi/earlycon.c > +++ /dev/null > @@ -1,246 +0,0 @@ > -// SPDX-License-Identifier: GPL-2.0 > -/* > - * Copyright (C) 2013 Intel Corporation; author Matt Fleming > - */ > - > -#include <linux/console.h> > -#include <linux/efi.h> > -#include <linux/font.h> > -#include <linux/io.h> > -#include <linux/kernel.h> > -#include <linux/serial_core.h> > -#include <linux/screen_info.h> > - > -#include <asm/early_ioremap.h> > - > -static const struct console *earlycon_console __initdata; > -static const struct font_desc *font; > -static u32 efi_x, efi_y; > -static u64 fb_base; > -static bool fb_wb; > -static void *efi_fb; > - > -/* > - * EFI earlycon needs to use early_memremap() to map the framebuffer. > - * But early_memremap() is not usable for 'earlycon=efifb keep_bootcon', > - * memremap() should be used instead. memremap() will be available after > - * paging_init() which is earlier than initcall callbacks. Thus adding this > - * early initcall function early_efi_map_fb() to map the whole EFI framebuffer. > - */ > -static int __init efi_earlycon_remap_fb(void) > -{ > - /* bail if there is no bootconsole or it has been disabled already */ > - if (!earlycon_console || !(earlycon_console->flags & CON_ENABLED)) > - return 0; > - > - efi_fb = memremap(fb_base, screen_info.lfb_size, > - fb_wb ? MEMREMAP_WB : MEMREMAP_WC); > - > - return efi_fb ? 0 : -ENOMEM; > -} > -early_initcall(efi_earlycon_remap_fb); > - > -static int __init efi_earlycon_unmap_fb(void) > -{ > - /* unmap the bootconsole fb unless keep_bootcon has left it enabled */ > - if (efi_fb && !(earlycon_console->flags & CON_ENABLED)) > - memunmap(efi_fb); > - return 0; > -} > -late_initcall(efi_earlycon_unmap_fb); > - > -static __ref void *efi_earlycon_map(unsigned long start, unsigned long len) > -{ > - pgprot_t fb_prot; > - > - if (efi_fb) > - return efi_fb + start; > - > - fb_prot = fb_wb ? PAGE_KERNEL : pgprot_writecombine(PAGE_KERNEL); > - return early_memremap_prot(fb_base + start, len, pgprot_val(fb_prot)); > -} > - > -static __ref void efi_earlycon_unmap(void *addr, unsigned long len) > -{ > - if (efi_fb) > - return; > - > - early_memunmap(addr, len); > -} > - > -static void efi_earlycon_clear_scanline(unsigned int y) > -{ > - unsigned long *dst; > - u16 len; > - > - len = screen_info.lfb_linelength; > - dst = efi_earlycon_map(y*len, len); > - if (!dst) > - return; > - > - memset(dst, 0, len); > - efi_earlycon_unmap(dst, len); > -} > - > -static void efi_earlycon_scroll_up(void) > -{ > - unsigned long *dst, *src; > - u16 len; > - u32 i, height; > - > - len = screen_info.lfb_linelength; > - height = screen_info.lfb_height; > - > - for (i = 0; i < height - font->height; i++) { > - dst = efi_earlycon_map(i*len, len); > - if (!dst) > - return; > - > - src = efi_earlycon_map((i + font->height) * len, len); > - if (!src) { > - efi_earlycon_unmap(dst, len); > - return; > - } > - > - memmove(dst, src, len); > - > - efi_earlycon_unmap(src, len); > - efi_earlycon_unmap(dst, len); > - } > -} > - > -static void efi_earlycon_write_char(u32 *dst, unsigned char c, unsigned int h) > -{ > - const u32 color_black = 0x00000000; > - const u32 color_white = 0x00ffffff; > - const u8 *src; > - int m, n, bytes; > - u8 x; > - > - bytes = BITS_TO_BYTES(font->width); > - src = font->data + c * font->height * bytes + h * bytes; > - > - for (m = 0; m < font->width; m++) { > - n = m % 8; > - x = *(src + m / 8); > - if ((x >> (7 - n)) & 1) > - *dst = color_white; > - else > - *dst = color_black; > - dst++; > - } > -} > - > -static void > -efi_earlycon_write(struct console *con, const char *str, unsigned int num) > -{ > - struct screen_info *si; > - unsigned int len; > - const char *s; > - void *dst; > - > - si = &screen_info; > - len = si->lfb_linelength; > - > - while (num) { > - unsigned int linemax; > - unsigned int h, count = 0; > - > - for (s = str; *s && *s != '\n'; s++) { > - if (count == num) > - break; > - count++; > - } > - > - linemax = (si->lfb_width - efi_x) / font->width; > - if (count > linemax) > - count = linemax; > - > - for (h = 0; h < font->height; h++) { > - unsigned int n, x; > - > - dst = efi_earlycon_map((efi_y + h) * len, len); > - if (!dst) > - return; > - > - s = str; > - n = count; > - x = efi_x; > - > - while (n-- > 0) { > - efi_earlycon_write_char(dst + x*4, *s, h); > - x += font->width; > - s++; > - } > - > - efi_earlycon_unmap(dst, len); > - } > - > - num -= count; > - efi_x += count * font->width; > - str += count; > - > - if (num > 0 && *s == '\n') { > - efi_x = 0; > - efi_y += font->height; > - str++; > - num--; > - } > - > - if (efi_x + font->width > si->lfb_width) { > - efi_x = 0; > - efi_y += font->height; > - } > - > - if (efi_y + font->height > si->lfb_height) { > - u32 i; > - > - efi_y -= font->height; > - efi_earlycon_scroll_up(); > - > - for (i = 0; i < font->height; i++) > - efi_earlycon_clear_scanline(efi_y + i); > - } > - } > -} > - > -static int __init efi_earlycon_setup(struct earlycon_device *device, > - const char *opt) > -{ > - struct screen_info *si; > - u16 xres, yres; > - u32 i; > - > - if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI) > - return -ENODEV; > - > - fb_base = screen_info.lfb_base; > - if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE) > - fb_base |= (u64)screen_info.ext_lfb_base << 32; > - > - fb_wb = opt && !strcmp(opt, "ram"); > - > - si = &screen_info; > - xres = si->lfb_width; > - yres = si->lfb_height; > - > - /* > - * efi_earlycon_write_char() implicitly assumes a framebuffer with > - * 32 bits per pixel. > - */ > - if (si->lfb_depth != 32) > - return -ENODEV; > - > - font = get_default_font(xres, yres, -1, -1); > - if (!font) > - return -ENODEV; > - > - efi_y = rounddown(yres, font->height) - font->height; > - for (i = 0; i < (yres - efi_y) / font->height; i++) > - efi_earlycon_scroll_up(); > - > - device->con->write = efi_earlycon_write; > - earlycon_console = device->con; > - return 0; > -} > -EARLYCON_DECLARE(efifb, efi_earlycon_setup); > diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig > index 40c50fa2dd70c33a1549141b15e6cba721352d2d..8052507e058fce37f5a51058e58ae2eb10d9669a 100644 > --- a/drivers/video/console/Kconfig > +++ b/drivers/video/console/Kconfig > @@ -69,6 +69,17 @@ config DUMMY_CONSOLE_ROWS > monitor. > Select 25 if you use a 640x480 resolution by default. > > +config FB_EARLYCON > + bool "Generic framebuffer early console" > + depends on SERIAL_EARLYCON && !ARM && !IA64 > + select FONT_SUPPORT > + select ARCH_USE_MEMREMAP_PROT > + help > + Say Y here if you want early console support for firmware established > + linear framebuffer. Unless you are using EFI framebuffer, you need to > + specify framebuffer geometry and address in device-tree or in kernel > + command line. > + > config FRAMEBUFFER_CONSOLE > bool "Framebuffer Console support" > depends on FB && !UML > diff --git a/drivers/video/console/Makefile b/drivers/video/console/Makefile > index db07b784bd2ccdcbffde933926ed5cee2bbbc7d4..7818faee587fc9c40b429617cfa224c0ccbc557c 100644 > --- a/drivers/video/console/Makefile > +++ b/drivers/video/console/Makefile > @@ -9,4 +9,5 @@ obj-$(CONFIG_STI_CONSOLE) += sticon.o sticore.o > obj-$(CONFIG_VGA_CONSOLE) += vgacon.o > obj-$(CONFIG_MDA_CONSOLE) += mdacon.o > > +obj-$(CONFIG_FB_EARLYCON) += earlycon.o > obj-$(CONFIG_FB_STI) += sticore.o > diff --git a/drivers/video/console/earlycon.c b/drivers/video/console/earlycon.c > new file mode 100644 > index 0000000000000000000000000000000000000000..54436587e3db90034652dcc144669dca91b863d5 > --- /dev/null > +++ b/drivers/video/console/earlycon.c > @@ -0,0 +1,305 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2013 Intel Corporation; author Matt Fleming > + * Copyright (C) 2022 Markuss Broks <markuss.broks@xxxxxxxxx> > + */ > + > +#include <asm/early_ioremap.h> > +#include <linux/console.h> > +#include <linux/efi.h> > +#include <linux/font.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/mm.h> > +#include <linux/of.h> > +#include <linux/of_fdt.h> > +#include <linux/serial_core.h> > +#include <linux/screen_info.h> > + > +struct fb_earlycon { > + u32 x, y, curr_x, curr_y, depth, stride; > + size_t size; > + phys_addr_t phys_base; > + void __iomem *virt_base; > +}; > + > +static const struct console *earlycon_console __initconst; > +static struct fb_earlycon info; > +static const struct font_desc *font; > + > +static int __init simplefb_earlycon_remap_fb(void) > +{ > + unsigned long mapping; > + /* bail if there is no bootconsole or it has been disabled already */ > + if (!earlycon_console || !(earlycon_console->flags & CON_ENABLED)) > + return 0; > + > + if (region_intersects(info.phys_base, info.size, > + IORESOURCE_SYSTEM_RAM, IORES_DESC_NONE) == REGION_INTERSECTS) > + mapping = MEMREMAP_WB; > + else > + mapping = MEMREMAP_WC; > + > + info.virt_base = memremap(info.phys_base, info.size, mapping); > + > + return info.virt_base ? 0 : -ENOMEM; > +} > +early_initcall(simplefb_earlycon_remap_fb); > + > +static int __init simplefb_earlycon_unmap_fb(void) > +{ > + /* unmap the bootconsole fb unless keep_bootcon has left it enabled */ > + if (info.virt_base && !(earlycon_console->flags & CON_ENABLED)) > + memunmap(info.virt_base); > + return 0; > +} > +late_initcall(simplefb_earlycon_unmap_fb); > + > +static __ref void *simplefb_earlycon_map(unsigned long start, unsigned long len) > +{ > + pgprot_t fb_prot; > + > + if (info.virt_base) > + return info.virt_base + start; > + > + fb_prot = PAGE_KERNEL; > + return early_memremap_prot(info.phys_base + start, len, pgprot_val(fb_prot)); > +} > + > +static __ref void simplefb_earlycon_unmap(void *addr, unsigned long len) > +{ > + if (info.virt_base) > + return; > + > + early_memunmap(addr, len); > +} > + > +static void simplefb_earlycon_clear_scanline(unsigned int y) > +{ > + unsigned long *dst; > + u16 len; > + > + len = info.stride; > + dst = simplefb_earlycon_map(y * len, len); > + if (!dst) > + return; > + > + memset(dst, 0, len); > + simplefb_earlycon_unmap(dst, len); > +} > + > +static void simplefb_earlycon_scroll_up(void) > +{ > + unsigned long *dst, *src; > + u16 len; > + u32 i, height; > + > + len = info.stride; > + height = info.y; > + > + for (i = 0; i < height - font->height; i++) { > + dst = simplefb_earlycon_map(i * len, len); > + if (!dst) > + return; > + > + src = simplefb_earlycon_map((i + font->height) * len, len); > + if (!src) { > + simplefb_earlycon_unmap(dst, len); > + return; > + } > + > + memmove(dst, src, len); > + > + simplefb_earlycon_unmap(src, len); > + simplefb_earlycon_unmap(dst, len); > + } > +} > + > +static void simplefb_earlycon_write_char(u8 *dst, unsigned char c, unsigned int h) > +{ > + const u8 *src; > + int m, n, bytes; > + u8 x; > + > + bytes = BITS_TO_BYTES(font->width); > + src = font->data + c * font->height * bytes + h * bytes; > + > + for (m = 0; m < font->width; m++) { > + n = m % 8; > + x = *(src + m / 8); > + if ((x >> (7 - n)) & 1) > + memset(dst, 0xff, (info.depth / 8)); > + else > + memset(dst, 0, (info.depth / 8)); > + dst += (info.depth / 8); > + } > +} > + > +static void > +simplefb_earlycon_write(struct console *con, const char *str, unsigned int num) > +{ > + unsigned int len; > + const char *s; > + void *dst; > + > + len = info.stride; > + > + while (num) { > + unsigned int linemax; > + unsigned int h, count = 0; > + > + for (s = str; *s && *s != '\n'; s++) { > + if (count == num) > + break; > + count++; > + } > + > + linemax = (info.x - info.curr_x) / font->width; > + if (count > linemax) > + count = linemax; > + > + for (h = 0; h < font->height; h++) { > + unsigned int n, x; > + > + dst = simplefb_earlycon_map((info.curr_y + h) * len, len); > + if (!dst) > + return; > + > + s = str; > + n = count; > + x = info.curr_x; > + > + while (n-- > 0) { > + simplefb_earlycon_write_char(dst + (x * 4), *s, h); > + x += font->width; > + s++; > + } > + > + simplefb_earlycon_unmap(dst, len); > + } > + > + num -= count; > + info.curr_x += count * font->width; > + str += count; > + > + if (num > 0 && *s == '\n') { > + info.curr_x = 0; > + info.curr_y += font->height; > + str++; > + num--; > + } > + > + if (info.curr_x + font->width > info.x) { > + info.curr_x = 0; > + info.curr_y += font->height; > + } > + > + if (info.curr_y + font->height > info.y) { > + u32 i; > + > + info.curr_y -= font->height; > + simplefb_earlycon_scroll_up(); > + > + for (i = 0; i < font->height; i++) > + simplefb_earlycon_clear_scanline(info.curr_y + i); > + } > + } > +} > + > +static int __init simplefb_earlycon_setup_common(struct earlycon_device *device, > + const char *opt) > +{ > + int i; > + > + info.size = info.x * info.y * (info.depth / 8); > + > + font = get_default_font(info.x, info.y, -1, -1); > + if (!font) > + return -ENODEV; > + > + info.curr_y = rounddown(info.y, font->height) - font->height; > + for (i = 0; i < (info.y - info.curr_y) / font->height; i++) > + simplefb_earlycon_scroll_up(); > + > + device->con->write = simplefb_earlycon_write; > + earlycon_console = device->con; > + return 0; > +} > + > +static int __init simplefb_earlycon_setup(struct earlycon_device *device, > + const char *opt) > +{ > + struct uart_port *port = &device->port; > + int ret; > + > + if (!port->mapbase) > + return -ENODEV; > + > + info.phys_base = port->mapbase; > + > + ret = sscanf(device->options, "%ux%ux%u", &info.x, &info.y, &info.depth); > + if (ret != 3) > + return -ENODEV; > + > + info.stride = info.x * (info.depth / 8); > + > + return simplefb_earlycon_setup_common(device, opt); > +} > + > +EARLYCON_DECLARE(simplefb, simplefb_earlycon_setup); > + > +#ifdef CONFIG_EFI_EARLYCON > +static int __init simplefb_earlycon_setup_efi(struct earlycon_device *device, > + const char *opt) > +{ > + if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI) > + return -ENODEV; > + > + info.phys_base = screen_info.lfb_base; > + if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE) > + info.phys_base |= (u64)screen_info.ext_lfb_base << 32; > + > + info.x = screen_info.lfb_width; > + info.y = screen_info.lfb_height; > + info.depth = screen_info.lfb_depth; > + info.stride = screen_info.lfb_linelength; > + > + return simplefb_earlycon_setup_common(device, opt); > +} > + > +EARLYCON_DECLARE(efifb, simplefb_earlycon_setup_efi); > +#endif > + > +#ifdef CONFIG_OF_EARLY_FLATTREE > +static int __init simplefb_earlycon_setup_of(struct earlycon_device *device, > + const char *opt) > +{ > + struct uart_port *port = &device->port; > + const __be32 *val; > + > + if (!port->mapbase) > + return -ENODEV; > + > + info.phys_base = port->mapbase; > + > + val = of_get_flat_dt_prop(device->offset, "width", NULL); > + if (!val) > + return -ENODEV; > + info.x = be32_to_cpu(*val); > + > + val = of_get_flat_dt_prop(device->offset, "height", NULL); > + if (!val) > + return -ENODEV; > + info.y = be32_to_cpu(*val); > + > + val = of_get_flat_dt_prop(device->offset, "stride", NULL); > + if (!val) > + return -ENODEV; > + info.stride = be32_to_cpu(*val); > + info.depth = (info.stride / info.x) * 8; > + > + return simplefb_earlycon_setup_common(device, opt); > +} > + > +OF_EARLYCON_DECLARE(simplefb, "simple-framebuffer", simplefb_earlycon_setup_of); > +#endif > -- > 2.37.0 > -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch