diff mbox series

[v2,3/3] efi: earlycon: Add support for generic framebuffers and move to console subsystem

Message ID 20220806163255.10404-4-markuss.broks@gmail.com
State New
Headers show
Series Add generic framebuffer support to EFI earlycon driver | expand

Commit Message

Markuss Broks Aug. 6, 2022, 4:32 p.m. UTC
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@gmail.com>
---
 .../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 ++++++++++++++++++
 8 files changed, 332 insertions(+), 256 deletions(-)
 delete mode 100644 drivers/firmware/efi/earlycon.c
 create mode 100644 drivers/video/console/earlycon.c

Comments

kernel test robot Aug. 14, 2022, 12:01 a.m. UTC | #1
Hi Markuss,

I love your patch! Perhaps something to improve:

[auto build test WARNING on tty/tty-testing]
[also build test WARNING on efi/next staging/staging-testing usb/usb-testing linus/master v5.19 next-20220812]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Markuss-Broks/Add-generic-framebuffer-support-to-EFI-earlycon-driver/20220807-003646
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
config: loongarch-randconfig-s031-20220807 (https://download.01.org/0day-ci/archive/20220814/202208140705.bU9i1c1t-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 12.1.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # apt-get install sparse
        # sparse version: v0.6.4-39-gce1a6720-dirty
        # https://github.com/intel-lab-lkp/linux/commit/97dfc2aa69b065de769a191352afe2099c52fedb
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Markuss-Broks/Add-generic-framebuffer-support-to-EFI-earlycon-driver/20220807-003646
        git checkout 97dfc2aa69b065de769a191352afe2099c52fedb
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=loongarch SHELL=/bin/bash drivers/video/console/

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

sparse warnings: (new ones prefixed by >>)
>> drivers/video/console/earlycon.c:43:24: sparse: sparse: incorrect type in assignment (different address spaces) @@     expected void [noderef] __iomem *static [toplevel] virt_base @@     got void * @@
   drivers/video/console/earlycon.c:43:24: sparse:     expected void [noderef] __iomem *static [toplevel] virt_base
   drivers/video/console/earlycon.c:43:24: sparse:     got void *
>> drivers/video/console/earlycon.c:53:30: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void *addr @@     got void [noderef] __iomem *static [toplevel] virt_base @@
   drivers/video/console/earlycon.c:53:30: sparse:     expected void *addr
   drivers/video/console/earlycon.c:53:30: sparse:     got void [noderef] __iomem *static [toplevel] virt_base
>> drivers/video/console/earlycon.c:63:39: sparse: sparse: incorrect type in return expression (different address spaces) @@     expected void * @@     got void [noderef] __iomem * @@
   drivers/video/console/earlycon.c:63:39: sparse:     expected void *
   drivers/video/console/earlycon.c:63:39: sparse:     got void [noderef] __iomem *
>> drivers/video/console/earlycon.c:74:24: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void [noderef] __iomem *addr @@     got void *addr @@
   drivers/video/console/earlycon.c:74:24: sparse:     expected void [noderef] __iomem *addr
   drivers/video/console/earlycon.c:74:24: sparse:     got void *addr

vim +43 drivers/video/console/earlycon.c

    29	
    30	static int __init simplefb_earlycon_remap_fb(void)
    31	{
    32		unsigned long mapping;
    33		/* bail if there is no bootconsole or it has been disabled already */
    34		if (!earlycon_console || !(earlycon_console->flags & CON_ENABLED))
    35			return 0;
    36	
    37		if (region_intersects(info.phys_base, info.size,
    38				      IORESOURCE_SYSTEM_RAM, IORES_DESC_NONE) == REGION_INTERSECTS)		
    39			mapping = MEMREMAP_WB;
    40		else
    41			mapping = MEMREMAP_WC;
    42	
  > 43		info.virt_base = memremap(info.phys_base, info.size, mapping);
    44	
    45		return info.virt_base ? 0 : -ENOMEM;
    46	}
    47	early_initcall(simplefb_earlycon_remap_fb);
    48	
    49	static int __init simplefb_earlycon_unmap_fb(void)
    50	{
    51		/* unmap the bootconsole fb unless keep_bootcon has left it enabled */
    52		if (info.virt_base && !(earlycon_console->flags & CON_ENABLED))
  > 53			memunmap(info.virt_base);
    54		return 0;
    55	}
    56	late_initcall(simplefb_earlycon_unmap_fb);
    57	
    58	static __ref void *simplefb_earlycon_map(unsigned long start, unsigned long len)
    59	{
    60		pgprot_t fb_prot;
    61	
    62		if (info.virt_base)
  > 63			return info.virt_base + start;
    64	
    65		fb_prot = PAGE_KERNEL;
    66		return early_memremap_prot(info.phys_base + start, len, pgprot_val(fb_prot));
    67	}
    68	
    69	static __ref void simplefb_earlycon_unmap(void *addr, unsigned long len)
    70	{
    71		if (info.virt_base)
    72			return;
    73	
  > 74		early_memunmap(addr, len);
    75	}
    76
kernel test robot Aug. 14, 2022, 7:04 a.m. UTC | #2
Hi Markuss,

I love your patch! Perhaps something to improve:

[auto build test WARNING on tty/tty-testing]
[also build test WARNING on efi/next staging/staging-testing usb/usb-testing linus/master v5.19 next-20220812]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Markuss-Broks/Add-generic-framebuffer-support-to-EFI-earlycon-driver/20220807-003646
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-testing
config: x86_64-kismet-CONFIG_FB_EARLYCON-CONFIG_EFI_EARLYCON-0-0 (https://download.01.org/0day-ci/archive/20220814/202208141430.Ez8EkEwO-lkp@intel.com/config)
reproduce:
        # https://github.com/intel-lab-lkp/linux/commit/97dfc2aa69b065de769a191352afe2099c52fedb
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Markuss-Broks/Add-generic-framebuffer-support-to-EFI-earlycon-driver/20220807-003646
        git checkout 97dfc2aa69b065de769a191352afe2099c52fedb
        # 1. reproduce by kismet
           # install kmax per https://github.com/paulgazz/kmax/blob/master/README.md
           kismet --linux-ksrc=linux --selectees CONFIG_FB_EARLYCON --selectors CONFIG_EFI_EARLYCON -a=x86_64
        # 2. reproduce by make
           # save the config file to linux source tree
           cd linux
           make ARCH=x86_64 olddefconfig

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for FB_EARLYCON when selected by EFI_EARLYCON
   
   WARNING: unmet direct dependencies detected for FB_EARLYCON
     Depends on [n]: VT [=y] && SERIAL_EARLYCON [=n] && !ARM && !IA64
     Selected by [y]:
     - EFI_EARLYCON [=y] && EFI [=y]
Daniel Vetter Sept. 6, 2022, 9:52 p.m. UTC | #3
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@gmail.com>
> ---
>  .../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@gmail.com>
> +S:	Maintained
> +F:	drivers/video/console/earlycon.c
> +
>  EARTH_PT1 MEDIA DRIVER
>  M:	Akihiro Tsukada <tskd08@gmail.com>
>  L:	linux-media@vger.kernel.org
> 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@gmail.com>
> + */
> +
> +#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
>
Thomas Zimmermann Sept. 7, 2022, 6:55 a.m. UTC | #4
Hi

Am 06.09.22 um 23:52 schrieb Daniel Vetter:
> 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@gmail.com>
>> ---
>>   .../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?

Then better use just earlycon.c in that directory. We use the sysfb_ 
prefix for code of the framebuffer devices that simpledrm and simplefb 
run on top. It would not fit well here.

Best regards
Thomas

> 
> 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@gmail.com>
>> +S:	Maintained
>> +F:	drivers/video/console/earlycon.c
>> +
>>   EARTH_PT1 MEDIA DRIVER
>>   M:	Akihiro Tsukada <tskd08@gmail.com>
>>   L:	linux-media@vger.kernel.org
>> 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@gmail.com>
>> + */
>> +
>> +#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
>>
>
Arnd Bergmann Sept. 7, 2022, 8:22 a.m. UTC | #5
On Tue, Sep 6, 2022, at 11:52 PM, Daniel Vetter wrote:
> On Sat, Aug 06, 2022 at 07:32:24PM +0300, Markuss Broks wrote:
>
> 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.

You are right, that was my mistake. The original patch was for
drivers/video/fbdev/, and I suggested moving it out of there because
it does not depend on the fbdev subsystem, but clearly my suggestions
of drivers/video/console was equally wrong.

> 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?

drivers/firmware/ is better but doesn't sound great to me either,
since one important thing the patch does is to not make it depend
on EFI firmware or BIOS style screen_info any more. 

Maybe drivers/tty/earlycon_simplefb.c would work better, keeping
it close to the earlycon base support in drivers/tty/serial/,
the vt console and the old simplefb driver/, without tying to too
closely to fbdev of UEFI.

     Arnd
diff mbox series

Patch

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@gmail.com>
+S:	Maintained
+F:	drivers/video/console/earlycon.c
+
 EARTH_PT1 MEDIA DRIVER
 M:	Akihiro Tsukada <tskd08@gmail.com>
 L:	linux-media@vger.kernel.org
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@gmail.com>
+ */
+
+#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