[Patch 2/3] edid override: module parameter
Thorsten Schoel
tschoel at web.de
Tue Apr 24 01:49:33 PDT 2012
From: Thorsten Schoel <tschoel at 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)))
---
More information about the dri-devel
mailing list