From: Thorsten Schoel <tsch...@web.de>

Allows for setting edid overrides through new parameter edid_override
for module drm.

Signed-off-by: Thorsten Schoel <tschoel at web.de>
---
diff -Nurp infra/drivers/gpu/drm/drm_edid.c param/drivers/gpu/drm/drm_edid.c
--- infra/drivers/gpu/drm/drm_edid.c    2012-01-25 22:12:59.000000000 +0100
+++ param/drivers/gpu/drm/drm_edid.c    2012-01-25 22:14:51.000000000 +0100
@@ -32,6 +32,8 @@
 #include <linux/i2c.h>
 #include <linux/types.h>
 #include <linux/list.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
 #include <linux/export.h>
 #include "drmP.h"
 #include "drm_edid.h"
@@ -232,11 +234,37 @@ struct drm_edid_override {
        struct list_head list;

        char *connector;
+       char *param;
        struct edid *edid;
        unsigned num_blocks;
 };
 LIST_HEAD(drm_edid_override_list);

+
+static int drm_edid_override_ops__get(char *buffer, const struct kernel_param 
*kp);
+static int drm_edid_override_ops__set(const char *val, const struct 
kernel_param *kp);
+static void drm_edid_override_ops__free(void *arg);
+
+
+struct kernel_param_ops drm_edid_override_ops = {
+       .get = drm_edid_override_ops__get,
+       .set = drm_edid_override_ops__set,
+       .free = drm_edid_override_ops__free
+};
+
+MODULE_PARM_DESC(edid_override, "Override the EDID data of a monitor. "
+       "This should be a comma separated list of entries of the format "
+       "[connector name]:[data]. data is either raw EDID data encoded in "
+       "hexadecimal format, or, if it cannot be parsed as such, the name "
+       "of a file containing the EDID data to be loaded through "
+       "request_firmware. Examples:\nVGA-1:syncmaster913n.edid\n"
+       "DVI-I-1:00ffffffffffff?");
+module_param_cb(edid_override,
+               &drm_edid_override_ops,
+               &drm_edid_override_list,
+               0600);
+
+
 /*
  * Free the memory associated with an EDID override.
  */
@@ -246,6 +273,7 @@ drm_edid_override_delete(struct drm_edid
        if (entry->edid)
                kfree(entry->edid);
        kfree(entry->connector);
+       /* param is generated from the same piece of memory through strsep */
        kfree(entry);
 }

@@ -270,7 +299,7 @@ drm_edid_override_remove(const char *con
 EXPORT_SYMBOL(drm_edid_override_remove);

 static int
-drm_edid_override_do_set(char *connector, struct edid *edid, unsigned 
num_blocks)
+drm_edid_override_do_set(char *connector, struct edid *edid, unsigned 
num_blocks, char *param)
 {
        struct drm_edid_override *entry = NULL;
        int found = 0;
@@ -300,6 +329,7 @@ drm_edid_override_do_set(char *connector
        entry->connector = connector;
        entry->edid = edid;
        entry->num_blocks = num_blocks;
+       entry->param = param;

        if (!found)
                list_add_tail(&entry->list, &drm_edid_override_list);
@@ -308,6 +338,25 @@ drm_edid_override_do_set(char *connector
        return 0;
 }

+/*
+ * Helper function for setter of module parameter.
+ */
+static int
+drm_edid_override_set_from_param(char *val)
+{
+       char *connector = NULL, *param = NULL;
+               
+       connector = strsep(&val, ":");
+       param = strlen(val) ? val : NULL;
+       
+       if (param)
+               return drm_edid_override_do_set(connector, NULL, 0, param);
+       else
+               drm_edid_override_remove(connector);
+       
+       return 0;
+}
+
 /**
  * Add a new override for connector if none has been set yet or replace the
  * current one.
@@ -336,10 +385,179 @@ drm_edid_override_set(const char *connec
        }
        memcpy(edid_dup, edid, edid_size);

-       return drm_edid_override_do_set(connector_dup, edid_dup, num_blocks);
+       return drm_edid_override_do_set(connector_dup, edid_dup, num_blocks, 
NULL);
 }
 EXPORT_SYMBOL(drm_edid_override_set);

+/*
+ * Setter for module parameter.
+ */
+static int
+drm_edid_override_ops__set(const char *val, const struct kernel_param *kp)
+{
+       const char *master = val;
+       char *substr = NULL;
+       int result = 0;
+       
+       do {
+               const char *new_master = strchr(master, ',');
+               int substr_len = 0;
+               
+               if (new_master)
+                       substr_len = new_master - master;
+               else
+                       substr_len = strlen(master);
+
+               substr = kstrndup(master, substr_len, GFP_KERNEL);
+               if (!substr)
+                       return -ENOMEM;
+
+               result = drm_edid_override_set_from_param(substr);
+               if (result)
+                       return result;
+               
+               master = new_master;
+       } while (master);
+       
+       return 0;
+}
+
+/* moduleparam.h claims this is "4k" */
+#define OPT_GET__BUFFER_LENGTH 4096
+/*
+ * Getter for module parameter. Will produce a comma separated list of
+ * all connectors an override has been set for.
+ */
+static int
+drm_edid_override_ops__get(char *buffer, const struct kernel_param *kp)
+{
+       struct drm_edid_override *entry = NULL;
+       int remaining_buf = OPT_GET__BUFFER_LENGTH - 1;
+       
+       /* prepare with empty string for strcat */
+       buffer[0] = '\0';
+       
+       list_for_each_entry(entry, (struct list_head *)kp->arg, list)
+       {
+               if (remaining_buf < OPT_GET__BUFFER_LENGTH - 1)
+               {
+                       strcat(buffer, ",");
+                       remaining_buf--;
+               }
+               
+               strncat(buffer, entry->connector, remaining_buf);
+               remaining_buf -= strlen(entry->connector);
+               if (remaining_buf <= 0) break;
+       }
+       
+       return OPT_GET__BUFFER_LENGTH - remaining_buf;
+}
+
+/*
+ * free function for module parameter.
+ */
+static void drm_edid_override_ops__free(void *arg)
+{
+       struct drm_edid_override *entry = NULL, *tmp = NULL;
+       
+       list_for_each_entry_safe(entry, tmp, (struct list_head *)arg, list) {
+               list_del(&entry->list);
+               drm_edid_override_delete(entry);
+       }
+}
+
+/*
+ * Convert a string representation of hexadecimal data to binary.
+ *
+ * \param str    : 0-terminated string containing data encoded as hexadecimal 
bytes
+ * \param buffer : Where to put the binary data
+ * \return The number of bytes converted or -errno on error. If buffer is NULL
+ *         0 will be returned. At most buff_len bytes will be converted.
+ */
+static int
+convert_hex_str(const char *str, u8 *buffer, size_t buff_len)
+{
+       int result = 0;
+       bool is_upper_nibble = true;
+       char c = 0;
+       
+       if (!buffer)
+               return 0;
+       
+       while ((c = *str++) && buff_len) {
+               if (c >= '0' && c <= '9')
+                       c -= '0';
+               else if (c >= 'A' && c <= 'F')
+                       c -= 'A' - 10;
+               else if (c >= 'a' && c <= 'f')
+                       c -= 'a' - 10;
+               else
+                       return -EINVAL;
+               
+               if (is_upper_nibble) {
+                       *buffer = c << 4;
+               } else {
+                       *buffer++ |= c;
+                       result++;
+                       buff_len--;
+               }
+               
+               is_upper_nibble = !is_upper_nibble;
+       }
+       
+       return is_upper_nibble ? result : -EINVAL;
+}
+
+/**
+ * Helper function to generate the binary EDID date from its representation in
+ * the module parameter (i.e. a hex-string or a firmware file).
+ *
+ * \param entry     : drm_edid_override to generate binary data for
+ * \param connector : corresponding connector
+ *
+ * Function should only be called while entry->param is NULL. If run without
+ * errors entry->param will be set to NULL afterwards.
+ */
+static long
+drm_edid_override__gen_edid(struct drm_edid_override *entry,
+                           struct drm_connector *connector)
+{
+       const struct firmware *fw = NULL;
+       long result = 0;
+       unsigned param_len = strlen(entry->param) / 2;
+       
+       if (param_len && !(param_len % EDID_LENGTH)) {
+               entry->edid = kmalloc(param_len, GFP_KERNEL);
+               if (!entry->edid)
+                       return -ENOMEM;
+               result = convert_hex_str(entry->param, (u8 *)entry->edid, 
param_len);
+               if (result != param_len) {
+                       kfree(entry->edid);
+                       entry->edid = NULL;
+                       goto firmware;
+               }
+               entry->num_blocks = param_len / EDID_LENGTH;
+       } else {
+               /* if it is not a valid EDID in hex assume it is a firmware */
+firmware:
+               result = request_firmware(&fw, entry->param, 
connector->dev->dev);
+               if (result < 0)
+                       return result;
+               if (fw->size >= EDID_LENGTH && fw->size == (fw->data[0x7e] + 1) 
* EDID_LENGTH) {
+                       entry->edid = kmalloc(fw->size, GFP_KERNEL);
+                       if (entry->edid) {
+                               memcpy(entry->edid, fw->data, fw->size);
+                               entry->num_blocks = fw->size / EDID_LENGTH;
+                       } else
+                               result = -ENOMEM;
+               } else
+                       result = -EINVAL;
+               release_firmware(fw);
+       }
+       
+       return result;
+}
+
 /**
  * Get EDID information from overrides list if available.
  *
@@ -370,8 +588,12 @@ drm_edid_override_get (struct drm_connec
                return NULL;

        if (!entry->edid) {
-               DRM_ERROR("EDID override without EDID data for connector %s", 
connector_name);
-               return NULL;
+               int res = drm_edid_override__gen_edid(entry, connector);
+               if (res < 0) {
+                       DRM_DEBUG("Error generating EDID data for override of 
%s: %d", connector_name, res);
+                       return NULL;
+               } else
+                       DRM_DEBUG("Successfully generated EDID data for 
override of %s", connector_name);
        }

        if ((result = kmalloc(entry->num_blocks * EDID_LENGTH, GFP_KERNEL)))
---


Reply via email to