root/drivers/gpu/drm/drm_edid_load.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
   drm_edid_load.c: use a built-in EDID data set or load it via the firmware
                    interface

   Copyright (C) 2012 Carsten Emde <C.Emde@osadl.org>

*/

#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <drm/drm_connector.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>

#include "drm_crtc_internal.h"

static char edid_firmware[PATH_MAX];
module_param_string(edid_firmware, edid_firmware, sizeof(edid_firmware), 0644);
MODULE_PARM_DESC(edid_firmware,
                 "Do not probe monitor, use specified EDID blob from /lib/firmware instead.");

static const struct drm_edid *edid_load(struct drm_connector *connector, const char *name)
{
        const struct firmware *fw = NULL;
        const struct drm_edid *drm_edid;
        int err;

        err = request_firmware(&fw, name, connector->dev->dev);
        if (err) {
                drm_err(connector->dev,
                        "[CONNECTOR:%d:%s] Requesting EDID firmware \"%s\" failed (err=%d)\n",
                        connector->base.id, connector->name,
                        name, err);
                return ERR_PTR(err);
        }

        drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Loaded external firmware EDID \"%s\"\n",
                    connector->base.id, connector->name, name);

        drm_edid = drm_edid_alloc(fw->data, fw->size);
        if (!drm_edid_valid(drm_edid)) {
                drm_err(connector->dev, "Invalid firmware EDID \"%s\"\n", name);
                drm_edid_free(drm_edid);
                drm_edid = ERR_PTR(-EINVAL);
        }

        release_firmware(fw);

        return drm_edid;
}

const struct drm_edid *drm_edid_load_firmware(struct drm_connector *connector)
{
        char *edidname, *last, *colon, *fwstr, *edidstr, *fallback = NULL;
        const struct drm_edid *drm_edid;

        if (edid_firmware[0] == '\0')
                return ERR_PTR(-ENOENT);

        /*
         * If there are multiple edid files specified and separated
         * by commas, search through the list looking for one that
         * matches the connector.
         *
         * If there's one or more that doesn't specify a connector, keep
         * the last one found one as a fallback.
         */
        fwstr = kstrdup(edid_firmware, GFP_KERNEL);
        if (!fwstr)
                return ERR_PTR(-ENOMEM);
        edidstr = fwstr;

        while ((edidname = strsep(&edidstr, ","))) {
                colon = strchr(edidname, ':');
                if (colon != NULL) {
                        if (strncmp(connector->name, edidname, colon - edidname))
                                continue;
                        edidname = colon + 1;
                        break;
                }

                if (*edidname != '\0') /* corner case: multiple ',' */
                        fallback = edidname;
        }

        if (!edidname) {
                if (!fallback) {
                        kfree(fwstr);
                        return ERR_PTR(-ENOENT);
                }
                edidname = fallback;
        }

        last = edidname + strlen(edidname) - 1;
        if (*last == '\n')
                *last = '\0';

        drm_edid = edid_load(connector, edidname);

        kfree(fwstr);

        return drm_edid;
}