[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