EDID Override

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

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);
 
_______________________________________________
dri-devel mailing list
dri-devel@xxxxxxxxxxxxxxxxxxxxx
http://lists.freedesktop.org/mailman/listinfo/dri-devel

[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux