Add CONFIG_LOGO_DT_CLUT224, which makes fb_find_logo() look for a node compatible with "boot-logo-clut224" under /chosen before falling back to the logos built into the kernel image.
The image is validated before it is used: the palette must have at most 224 entries, the pixel data length must match the geometry, and every pixel must reference an entry that exists. A malformed node is reported and ignored rather than drawn, so a bad device tree cannot take the display down with it. The image is copied out of the device tree so that the 32 entry offset the frame buffer layer reserves for the console can be applied to the pixels. The pixels are allocated with kvmalloc(), since a full screen image is larger than kmalloc() will comfortably serve. Unlike the built-in logos the copy is never freed. Those are initdata and go away in free_initmem(), which runs after async_synchronize_full(); anything this code could hook into runs before that, so releasing the image here would pull it out from under a display driver whose probe is still in flight. It is a modest allocation and it lives as long as the device tree it came from, which also means fb_find_logo() can still hand it out after the built-in logos are gone. The node lives under /chosen because a logo is configuration handed over by firmware rather than a description of the hardware, which is also where simple-framebuffer nodes live for the same reason. The lookup is guarded with IS_ENABLED() rather than wrapped in an #ifdef, so that the code is compile checked whatever the configuration and the compiler drops it when the option is off. Built for x86_64 with CONFIG_OF=n the object is left with no unresolved of_* symbol and none of the data. Signed-off-by: Max Pedraza <[email protected]> --- drivers/video/logo/Kconfig | 12 +++ drivers/video/logo/logo.c | 154 ++++++++++++++++++++++++++++++++++++- include/linux/linux_logo.h | 3 + 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig index cda15b9589..cce89948f1 100644 --- a/drivers/video/logo/Kconfig +++ b/drivers/video/logo/Kconfig @@ -76,4 +76,16 @@ config LOGO_LINUX_CLUT224_FILE magick source_image -compress none -colors 224 destination.ppm +config LOGO_DT_CLUT224 + bool "224-color logo supplied by the device tree" + depends on OF + help + Look for a boot logo in the device tree, in a node compatible with + "boot-logo-clut224" under /chosen, instead of using one of + the logos built into the kernel image. This allows a single kernel + image to be used by several products that only differ in branding. + + If no such node is present, or it is disabled, the built-in logo + selected above is used, so saying Y here is safe. + endif # LOGO diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c index 91535f8848..84afd5b337 100644 --- a/drivers/video/logo/logo.c +++ b/drivers/video/logo/logo.c @@ -11,6 +11,9 @@ */ #include <linux/linux_logo.h> +#include <linux/of.h> +#include <linux/sizes.h> +#include <linux/slab.h> #include <linux/stddef.h> #include <linux/module.h> @@ -22,6 +25,139 @@ static bool nologo; module_param(nologo, bool, 0); MODULE_PARM_DESC(nologo, "Disables startup logo"); +/* Boot logo supplied by the device tree */ + +#define LOGO_DT_MAX_CLUT 224 +/* + * The first 32 palette entries are reserved for the console, so the logo + * colours start at index 32. That is an implementation detail of the frame + * buffer layer rather than a property of the image, so the device tree stores + * plain indices and the offset is applied here. + */ +#define LOGO_DT_CLUT_OFFSET 32 +/* + * Sanity limit on the image size, a device tree is not a good place for more: + * a 4K screen is 8.3M pixels, and the copy is kept for the life of the kernel. + * One byte per pixel, so this is a byte count as well. + */ +#define LOGO_DT_MAX_PIXELS SZ_16M + +static struct linux_logo logo_dt_clut224 = { + .type = LINUX_LOGO_CLUT224, +}; + +static unsigned char *logo_dt_clut; +static unsigned char *logo_dt_data; + +static int logo_dt_parse(struct device_node *np) +{ + unsigned int clutsize, npixels, i; + unsigned char *clut, *data; + u32 width, height; + int len, ret; + + ret = of_property_read_u32(np, "width", &width); + if (ret) + return ret; + + ret = of_property_read_u32(np, "height", &height); + if (ret) + return ret; + + if (!width || !height || (u64)width * height > LOGO_DT_MAX_PIXELS) + return -EINVAL; + + npixels = width * height; + + len = of_property_count_u8_elems(np, "clut"); + if (len < 3 || len % 3) + return -EINVAL; + + clutsize = len / 3; + if (clutsize > LOGO_DT_MAX_CLUT) + return -EINVAL; + + ret = of_property_count_u8_elems(np, "data"); + if (ret < 0) + return ret; + if ((unsigned int)ret != npixels) + return -EINVAL; + + clut = kmalloc(len, GFP_KERNEL); + if (!clut) + return -ENOMEM; + + /* The palette is at most 672 bytes, the pixels can be megabytes */ + data = kvmalloc(npixels, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err_free_clut; + } + + ret = of_property_read_u8_array(np, "clut", clut, len); + if (ret) + goto err_free_data; + + ret = of_property_read_u8_array(np, "data", data, npixels); + if (ret) + goto err_free_data; + + for (i = 0; i < npixels; i++) { + if (data[i] >= clutsize) { + ret = -ERANGE; + goto err_free_data; + } + data[i] += LOGO_DT_CLUT_OFFSET; + } + + logo_dt_clut = clut; + logo_dt_data = data; + + logo_dt_clut224.width = width; + logo_dt_clut224.height = height; + logo_dt_clut224.clutsize = clutsize; + logo_dt_clut224.clut = clut; + logo_dt_clut224.data = data; + + return 0; + +err_free_data: + kvfree(data); +err_free_clut: + kfree(clut); + return ret; +} + +static const struct linux_logo *logo_dt_find(void) +{ + static bool probed; + struct device_node *np; + int ret; + + if (!IS_ENABLED(CONFIG_LOGO_DT_CLUT224)) + return NULL; + + if (probed) + return logo_dt_data ? &logo_dt_clut224 : NULL; + + probed = true; + + np = of_get_compatible_child(of_chosen, LOGO_DT_COMPATIBLE); + if (!np) + return NULL; + + if (of_device_is_available(np)) { + ret = logo_dt_parse(np); + if (ret) + pr_warn("logo: ignoring malformed %pOF node (%d)\n", + np, ret); + } + + of_node_put(np); + + return logo_dt_data ? &logo_dt_clut224 : NULL; +} + /* * Logos are located in the initdata, and will be freed in kernel_init. * Use late_init to mark the logos as freed to prevent any further use. @@ -45,7 +181,23 @@ const struct linux_logo * __ref fb_find_logo(int depth) { const struct linux_logo *logo = NULL; - if (nologo || logos_freed) + if (nologo) + return NULL; + + /* + * A logo supplied by the device tree wins over the built-in ones. It + * is an ordinary allocation rather than initdata, so unlike them it + * stays valid for the life of the kernel, and is still there for a + * display driver whose probe finishes after the built-in logos have + * gone. + */ + if (depth >= 8) { + logo = logo_dt_find(); + if (logo) + return logo; + } + + if (logos_freed) return NULL; #ifdef CONFIG_LOGO_LINUX_MONO diff --git a/include/linux/linux_logo.h b/include/linux/linux_logo.h index b3b15d3800..1e7e9db6dd 100644 --- a/include/linux/linux_logo.h +++ b/include/linux/linux_logo.h @@ -22,6 +22,9 @@ #define LINUX_LOGO_CLUT224 3 /* 224 colors */ #define LINUX_LOGO_GRAY256 4 /* 256 levels grayscale */ +/* Compatible of the /chosen child describing a device tree supplied logo */ +#define LOGO_DT_COMPATIBLE "boot-logo-clut224" + struct linux_logo { int type; /* one of LINUX_LOGO_* */ -- 2.39.5
