All of lore.kernel.org
 help / color / mirror / Atom feed
* EDID Override
@ 2011-12-13 10:10 Thorsten Schoel
  0 siblings, 0 replies; only message in thread
From: Thorsten Schoel @ 2011-12-13 10:10 UTC (permalink / raw)
  To: dri-devel

[-- Attachment #1: Type: text/plain, Size: 2230 bytes --]

Dear all,

please find below a patch that will allow overriding a monitor's EDID 
with something provided by the user. This can be helpful in a number of 
situations as a quick google for "edid override" or similar suggests; I 
wrote it because my monitor is broken and doesn't provide any EDID at 
all. This is done through a module parameter named edid_override which 
is made up of three parts, each separated by a colon from the next. 
First is the name of a connector, second is the type of source for the 
information, third is the source of the information itself. If the 
second part is an 'f', the third will be the name of a file containing 
the EDID, if it is a 'w', the third will be the name of a firmware blob 
(i.e. the kernel firmware loading mechanism will be used to get the 
data), and if it is an 'r' th third part is raw EDID encoded as a stream 
of hexadecimal characters. 'd' as the second part will simply remove any 
previous edid override for the connector.

Since this is my first attempt at getting a patch into the kernel the 
patch might well be in need of some additional work which I will be 
pleased to provide if someone provides me with a description and 
explanation of what needs to be done.

The following points are on my mind. These are not things that I think 
have to be fixed but rather that I am unsure of:

- Is drm_edid.c, the file where all the logic and currently everything 
else is placed, really the right place for the definition of the module 
parameter or should this go elsewhere?

- Is it really desirable to have three different ways of fetching the 
EDID-data? Yet, having the raw EDID in hex (256 characters) on the 
kernel command line might not always be desirable. Loading the EDID as 
firmware may sometimes be more convenient than reading it from an 
arbitrary file. But if you look at it closely EDID data is not exactly 
what you think of when you think of firmware.

- Adding an override through sysfs doesn't work yet and I can't figure 
out why.

The patch was written and tested on Fedora 16, Linux 3.1. I have adopted 
it to 3.2-rc5 though. If anyone desires the 3.1 patch I can provide that 
as well. It is only marginally different though.

Best,
Thorsten


[-- Attachment #2: linux-3.2-edid-override.patch --]
[-- Type: text/plain, Size: 8722 bytes --]

diff -uNpr orig/drivers/gpu/drm/drm_edid.c new/drivers/gpu/drm/drm_edid.c
--- orig/drivers/gpu/drm/drm_edid.c	2011-12-12 12:36:10.409354108 +0100
+++ new/drivers/gpu/drm/drm_edid.c	2011-12-12 12:38:02.717817296 +0100
@@ -31,6 +31,11 @@
 #include <linux/slab.h>
 #include <linux/i2c.h>
 #include <linux/export.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/firmware.h>
 #include "drmP.h"
 #include "drm_edid.h"
 #include "drm_edid_modes.h"
@@ -226,6 +231,267 @@ bool drm_edid_is_valid(struct edid *edid
 }
 EXPORT_SYMBOL(drm_edid_is_valid);
 
+enum drm_edid_override_entry_type {
+	DRM_EDID_OVERRIDE_ENTRY_TYPE_DEL,
+	DRM_EDID_OVERRIDE_ENTRY_TYPE_FILE,
+	DRM_EDID_OVERRIDE_ENTRY_TYPE_FIRMWARE,
+	DRM_EDID_OVERRIDE_ENTRY_TYPE_RAW
+};
+
+struct drm_edid_override_entry {
+	struct list_head list;
+	
+	char *connector;
+	enum drm_edid_override_entry_type type;
+	char *param;
+	char edid[EDID_LENGTH];
+};
+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 following format:\n"
+	"\"[connector name]:[type]:[data]\", where type is 'f' if data is a file "
+	"name, 'w' if it is a name of a firmware image, 'r' if it is raw edid data "
+	"encoded as a hexadecimal string or 'd' (or anything else actually, but "
+	"stick to 'd' since other options might be added in future versions) if a "
+	"previous edid override for the connector is to be deleted.\n"
+	"The parameter can appear more than once. An appearance of a connector will "
+	"override the previous override for that connector. ");
+module_param_cb(edid_override, &drm_edid_override_ops, &__drm_edid_override_list, 0600);
+
+
+/*
+ * Delete an entry from the edid overrides list.
+ */
+static void
+drm_delete_edid_override_entry(struct drm_edid_override_entry *entry)
+{
+	/* everything else is generated from the same piece of memory through strsep */
+	kfree(entry->connector);
+	kfree(entry);
+}
+
+/*
+ * Convert a string representation of hexadecimal data to binary. Returns buffer
+ * if length bytes could indeed be extracted from stream, NULL otherwise.
+ */
+static char *
+convert_hex_stream(const char *stream, char *buffer, size_t length)
+{
+	bool is_upper = true;
+	char *dest = buffer;
+	
+	while(length) {
+		char c = *stream++;
+		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 NULL;
+		
+		if(is_upper) {
+			*dest = c << 4;
+		} else {
+			*dest++ |= c;
+			length--;
+		}
+		
+		is_upper = !is_upper;
+	}
+	
+	return buffer;
+}
+
+/*
+ * Helper function for setter of module parameter.
+ */
+static struct drm_edid_override_entry *
+__drm_edid_override_ops__set__gen_entry(char *val)
+{
+	char *type = NULL;
+	struct drm_edid_override_entry *entry = NULL;
+	
+	entry = (struct drm_edid_override_entry *)kzalloc(sizeof(struct drm_edid_override_entry), GFP_KERNEL);
+	if (!entry)
+		return NULL;
+	INIT_LIST_HEAD(&entry->list);
+	
+	entry->connector = strsep(&val, ":");
+	if(!val) {
+		entry->type = DRM_EDID_OVERRIDE_ENTRY_TYPE_DEL;
+		entry->param = NULL;
+		return entry;
+	}
+	type = strsep(&val, ":");
+	entry->param = val;
+	if(!val) {
+		entry->type = DRM_EDID_OVERRIDE_ENTRY_TYPE_DEL;
+		return entry;
+	}
+
+	if (type[0] == 'f')
+		entry->type = DRM_EDID_OVERRIDE_ENTRY_TYPE_FILE;
+	else if (type[0] == 'r')
+		entry->type = DRM_EDID_OVERRIDE_ENTRY_TYPE_RAW;
+	else if (type[0] == 'w')
+		entry->type = DRM_EDID_OVERRIDE_ENTRY_TYPE_FIRMWARE;
+	else
+		entry->type = DRM_EDID_OVERRIDE_ENTRY_TYPE_DEL;
+	
+	return entry;
+}
+
+/*
+ * 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;
+	
+	do {
+		struct drm_edid_override_entry *entry, *new_entry = NULL;
+		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;
+		
+		new_entry = __drm_edid_override_ops__set__gen_entry(substr);
+		if (!new_entry)
+			return -ENOMEM;
+
+		/* replace previous entry if present */
+		list_for_each_entry (entry, (struct list_head *)kp->arg, list) {
+			if (strcmp(new_entry->connector, entry->connector))
+				continue;
+			list_del(&entry->list);
+			drm_delete_edid_override_entry(entry);
+			break;
+		}
+
+		if (new_entry->type == DRM_EDID_OVERRIDE_ENTRY_TYPE_DEL)
+			drm_delete_edid_override_entry(new_entry);
+		else
+			list_add(&new_entry->list, (struct list_head *)kp->arg);
+		
+		master = new_master;
+	} while (master);
+	
+	return 0;
+}
+
+/* moduleparam.h claims this is "4k" in a comment */
+#define OPT_GET__BUFFER_LENGTH 4096
+/*
+ * Getter for module parameter. Will produce a comma separated list of
+ * all connectors an override has been set up for.
+ */
+static int
+__drm_edid_override_ops__get(char *buffer, const struct kernel_param *kp)
+{
+	struct drm_edid_override_entry *entry = NULL;
+	int remaining_buf = OPT_GET__BUFFER_LENGTH - 1;
+	
+	/* set up 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 *entry, *tmp = NULL;
+	
+	list_for_each_entry_safe(entry, tmp, (struct list_head *)arg, list) {
+		list_del(&entry->list);
+		drm_delete_edid_override_entry(entry);
+	}
+}
+
+static int
+drm_edid_override_entry__gen_edid(struct drm_edid_override_entry *entry, struct drm_connector *connector)
+{
+	struct file *filp = NULL;
+	const struct firmware *fw = NULL;
+	mm_segment_t old_fs;
+	
+	switch (entry->type) {
+	case DRM_EDID_OVERRIDE_ENTRY_TYPE_FILE:
+		old_fs = get_fs();
+		set_fs(get_ds());
+		filp = filp_open(entry->param, O_RDONLY, 0);
+		if (IS_ERR(filp)) {
+			set_fs(old_fs);
+			return -1;
+		} else {
+			loff_t off = 0;
+			vfs_read (filp, entry->edid, EDID_LENGTH, &off);
+			filp_close (filp, NULL);
+			set_fs(old_fs);
+		}
+		break;
+		
+	case DRM_EDID_OVERRIDE_ENTRY_TYPE_FIRMWARE:
+		if (request_firmware(&fw, entry->param, connector->dev->dev))
+			return -1;
+		if (fw->size != EDID_LENGTH)
+			return -1;
+		memcpy(entry->edid, fw->data, EDID_LENGTH);
+		release_firmware(fw);
+		break;
+		
+	case DRM_EDID_OVERRIDE_ENTRY_TYPE_RAW:
+		if (!convert_hex_stream(entry->param, entry->edid, EDID_LENGTH))
+			return -1;
+		break;
+		
+	default:
+		return -1;
+	}
+	
+	entry->param = NULL;
+	return 0;
+}
+
 #define DDC_ADDR 0x50
 #define DDC_SEGMENT_ADDR 0x30
 /**
@@ -379,14 +645,36 @@ struct edid *drm_get_edid(struct drm_con
 			  struct i2c_adapter *adapter)
 {
 	struct edid *edid = NULL;
-
-	if (drm_probe_ddc(adapter))
-		edid = (struct edid *)drm_do_get_edid(connector, adapter);
-
+	char *connector_name = NULL;
+	struct list_head *pos = NULL;
+	struct drm_edid_override_entry *entry = NULL;
+	
+	connector_name = drm_get_connector_name(connector);
+	list_for_each(pos, &__drm_edid_override_list) {
+		entry = (struct drm_edid_override_entry *)list_entry(pos, struct drm_edid_override_entry, list);
+		if(!strcmp(entry->connector, connector_name))
+			break;
+		else
+			entry = NULL;
+	}
+	
+	if (entry) {
+		/* entry->param will be NULL once the EDID data is cached in entry->edid */
+		if (entry->param && (drm_edid_override_entry__gen_edid(entry, connector) == -1))
+			goto probe;
+		
+		edid = (struct edid *)kmalloc(EDID_LENGTH, GFP_KERNEL);
+		if (edid)
+			memcpy(edid, entry->edid, EDID_LENGTH);
+	} else {
+probe:
+		if (drm_probe_ddc(adapter))
+			edid = (struct edid *)drm_do_get_edid(connector, adapter);
+	}
+	
 	connector->display_info.raw_edid = (char *)edid;
 
 	return edid;
-
 }
 EXPORT_SYMBOL(drm_get_edid);
 

[-- Attachment #3: Type: text/plain, Size: 159 bytes --]

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2011-12-13 10:18 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-13 10:10 EDID Override Thorsten Schoel

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.