With CONFIG_LOGO_DT_CLUT224 enabled, look for a node compatible with
"linux,boot-logo-clut224" and, when one is present and enabled, use the
image it carries in preference to the logos built into the kernel image.

This lets a single kernel image serve several products, or several
revisions of one product, that differ only in branding, at the cost of a
larger device tree blob.

The image is validated while it is parsed: the palette must be at most 224
entries, the pixel data length must match the declared geometry, and every
pixel must reference an existing palette entry. A malformed node is
reported and ignored, falling back to the built-in logo.

The two allocations live for as long as the built-in logos do; they are
released from fb_logo_late_init(), next to where the __initdata logos are
marked as freed.

If the node is absent or disabled, behaviour is unchanged.

Signed-off-by: Max Pedraza <[email protected]>
---
 drivers/video/logo/Kconfig |  13 +++
 drivers/video/logo/logo.c  | 157 +++++++++++++++++++++++++++++++++++++
 2 files changed, 170 insertions(+)

diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig
index ce6bb7535..4af349b20 100644
--- a/drivers/video/logo/Kconfig
+++ b/drivers/video/logo/Kconfig
@@ -70,4 +70,17 @@ config LOGO_SUPERH_CLUT224
        depends on SUPERH
        default y
 
+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
+         "linux,boot-logo-clut224", 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, at the cost of
+         making the device tree blob larger.
+
+         If no such node is present, or it is disabled, the built-in logo
+         selected above is used, so saying Y here is safe. If unsure, say N.
+
 endif # LOGO
diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c
index 141f15a9a..b4f533df0 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,155 @@ static bool nologo;
 module_param(nologo, bool, 0);
 MODULE_PARM_DESC(nologo, "Disables startup logo");
 
+#ifdef CONFIG_LOGO_DT_CLUT224
+
+#define LOGO_DT_COMPATIBLE     "linux,boot-logo-clut224"
+#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 
*/
+#define LOGO_DT_MAX_PIXELS     SZ_32M
+
+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;
+
+       data = kmalloc(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:
+       kfree(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 (probed)
+               return logo_dt_data ? &logo_dt_clut224 : NULL;
+
+       probed = true;
+
+       np = of_find_compatible_node(NULL, NULL, 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;
+}
+
+static void logo_dt_free(void)
+{
+       logo_dt_clut224.clut = NULL;
+       logo_dt_clut224.data = NULL;
+
+       kfree(logo_dt_clut);
+       logo_dt_clut = NULL;
+
+       kfree(logo_dt_data);
+       logo_dt_data = NULL;
+}
+
+#else /* !CONFIG_LOGO_DT_CLUT224 */
+
+static inline const struct linux_logo *logo_dt_find(void)
+{
+       return NULL;
+}
+
+static inline void logo_dt_free(void) { }
+
+#endif /* CONFIG_LOGO_DT_CLUT224 */
+
 /*
  * 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.
@@ -32,6 +184,7 @@ static bool logos_freed;
 static int __init fb_logo_late_init(void)
 {
        logos_freed = true;
+       logo_dt_free();
        return 0;
 }
 
@@ -71,6 +224,10 @@ const struct linux_logo * __ref fb_find_logo(int depth)
        }
        
        if (depth >= 8) {
+               /* A logo supplied by the device tree wins over the built-in 
ones */
+               logo = logo_dt_find();
+               if (logo)
+                       return logo;
 #ifdef CONFIG_LOGO_LINUX_CLUT224
                /* Generic Linux logo */
                logo = &logo_linux_clut224;
-- 
2.39.5

Reply via email to