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 #include #include +#include +#include +#include +#include +#include #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);