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