Re: [PATCH 02/15] drm/i915: Embedded microcontroller (uC) firmware loading support

Daniel Vetter
2015-06-17T12:02:49Z

On Mon, Jun 15, 2015 at 07:36:20PM +0100, Dave Gordon wrote:
> Current devices may contain one or more programmable microcontrollers
> that need to have a firmware image (aka "binary blob") loaded from an
> external medium and transferred to the device's memory.
>
> This file provides generic support functions for doing this; they can
> then be used by each uC-specific loader, thus reducing code duplication
> and testing effort.
>
> Signed-off-by: Dave Gordon <david.s.gordon@intel.com>
> Signed-off-by: Alex Dai <yu.dai@intel.com>

Given that I'm just shredding the synchronization used by the dmc loader
I'm not convinced this is a good idea. Abstraction has cost, and a bit of
copy-paste for similar sounding but slightly different things doesn't
sound awful to me. And the critical bit in all the firmware loading I've
seen thus far is in synchronizing the loading with other operations,
hiding that isn't a good idea. Worse if we enforce stuff like requiring
dev->struct_mutex.
-Daniel

> ---
> drivers/gpu/drm/i915/Makefile | 3 +
> drivers/gpu/drm/i915/intel_uc_loader.c | 312 ++++++++++++++++++++++++++++++++
> drivers/gpu/drm/i915/intel_uc_loader.h | 82 +++++++++
> 3 files changed, 397 insertions(+)
> create mode 100644 drivers/gpu/drm/i915/intel_uc_loader.c
> create mode 100644 drivers/gpu/drm/i915/intel_uc_loader.h
>
> diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
> index b7ddf48..607fa2a 100644
> --- a/drivers/gpu/drm/i915/Makefile
> +++ b/drivers/gpu/drm/i915/Makefile
> @@ -38,6 +38,9 @@ i915-y += i915_cmd_parser.o \
> intel_ringbuffer.o \
> intel_uncore.o
>
> +# generic ancilliary microcontroller support
> +i915-y += intel_uc_loader.o
> +
> # autogenerated null render state
> i915-y += intel_renderstate_gen6.o \
> intel_renderstate_gen7.o \
> diff --git a/drivers/gpu/drm/i915/intel_uc_loader.c b/drivers/gpu/drm/i915/intel_uc_loader.c
> new file mode 100644
> index 0000000..26f0fbe
> --- /dev/null
> +++ b/drivers/gpu/drm/i915/intel_uc_loader.c
> @@ -0,0 +1,312 @@
> +/*
> + * Copyright © 2014 Intel Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> + * IN THE SOFTWARE.
> + *
> + * Author:
> + * Dave Gordon <david.s.gordon@intel.com>
> + */
> +#include <linux/firmware.h>
> +#include "i915_drv.h"
> +#include "intel_uc_loader.h"
> +
> +/**
> + * DOC: Generic embedded microcontroller (uC) firmware loading support
> + *
> + * The functions in this file provide a generic way to load the firmware that
> + * may be required by an embedded microcontroller (uC).
> + *
> + * The function intel_uc_fw_init() should be called early, and will initiate
> + * an asynchronous request to fetch the firmware image (aka "binary blob").
> + * When the image has been fetched into memory, the kernel will call back to
> + * uc_fw_fetch_callback() whose function is simply to record the completion
> + * status, and stash the firmware blob for later.
> + *
> + * At some convenient point after GEM initialisation, the driver should call
> + * intel_uc_fw_check(); this will check whether the asynchronous thread has
> + * completed and wait for it if not, check whether the image was successfully
> + * fetched; and then allow the callback() function (if provided) to validate
> + * the image and/or save the data in a GEM object.
> + *
> + * Thereafter the uC-specific code can transfer the data in the GEM object
> + * to the uC's memory (in some uC-specific way, not handled here).
> + *
> + * During driver shutdown, or if driver load is aborted, intel_uc_fw_fini()
> + * should be called to release any remaining resources.
> + */
> +
> +
> +/*
> + * Called once per uC, late in driver initialisation. GEM is now ready, and so
> + * we can now create a GEM object to hold the uC firmware. But first, we must
> + * synchronise with the firmware-fetching thread that was initiated during
> + * early driver load, in intel_uc_fw_init(), and see whether it successfully
> + * fetched the firmware blob.
> + */
> +static void
> +uc_fw_fetch_wait(struct intel_uc_fw *uc_fw,
> + bool callback(struct intel_uc_fw *))
> +{
> + struct drm_device *dev = uc_fw->uc_dev;
> + struct drm_i915_gem_object *obj;
> + const struct firmware *fw;
> +
> + DRM_DEBUG_DRIVER("before waiting: %s fw fetch status %d, fw %p\n",
> + uc_fw->uc_name, uc_fw->uc_fw_fetch_status, uc_fw->uc_fw_blob);
> +
> + WARN_ON(!mutex_is_locked(&dev->struct_mutex));
> + WARN_ON(uc_fw->uc_fw_fetch_status != INTEL_UC_FIRMWARE_PENDING);
> +
> + wait_for_completion(&uc_fw->uc_fw_fetched);
> +
> + DRM_DEBUG_DRIVER("after waiting: %s fw fetch status %d, fw %p\n",
> + uc_fw->uc_name, uc_fw->uc_fw_fetch_status, uc_fw->uc_fw_blob);
> +
> + fw = uc_fw->uc_fw_blob;
> + if (!fw) {
> + /* no firmware found; try again in case FS was not mounted */
> + DRM_DEBUG_DRIVER("retry fetching %s fw from <%s>\n",
> + uc_fw->uc_name, uc_fw->uc_fw_path);
> + if (request_firmware(&fw, uc_fw->uc_fw_path, &dev->pdev->dev))
> + goto fail;
> + if (!fw)
> + goto fail;
> + DRM_DEBUG_DRIVER("fetch %s fw from <%s> succeeded, fw %p\n",
> + uc_fw->uc_name, uc_fw->uc_fw_path, fw);
> + uc_fw->uc_fw_blob = fw;
> + }
> +
> + /* Callback to the optional uC-specific function, if supplied */
> + if (callback && !callback(uc_fw))
> + goto fail;
> +
> + /* Callback may have done the object allocation & write itself */
> + obj = uc_fw->uc_fw_obj;
> + if (!obj) {
> + size_t pages = round_up(fw->size, PAGE_SIZE);
> + obj = i915_gem_alloc_object(dev, pages);
> + if (!obj)
> + goto fail;
> +
> + uc_fw->uc_fw_obj = obj;
> + uc_fw->uc_fw_size = fw->size;
> + if (i915_gem_object_write(obj, fw->data, fw->size))
> + goto fail;
> + }
> +
> + DRM_DEBUG_DRIVER("%s fw fetch status SUCCESS\n", uc_fw->uc_name);
> + release_firmware(fw);
> + uc_fw->uc_fw_blob = NULL;
> + uc_fw->uc_fw_fetch_status = INTEL_UC_FIRMWARE_SUCCESS;
> + return;
> +
> +fail:
> + DRM_DEBUG_DRIVER("%s fw fetch status FAIL; fw %p, obj %p\n",
> + uc_fw->uc_name, fw, uc_fw->uc_fw_obj);
> + DRM_ERROR("Failed to fetch %s firmware from <%s>\n",
> + uc_fw->uc_name, uc_fw->uc_fw_path);
> +
> + obj = uc_fw->uc_fw_obj;
> + if (obj)
> + drm_gem_object_unreference(&obj->base);
> + uc_fw->uc_fw_obj = NULL;
> +
> + release_firmware(fw); /* OK even if fw is NULL */
> + uc_fw->uc_fw_blob = NULL;
> + uc_fw->uc_fw_fetch_status = INTEL_UC_FIRMWARE_FAIL;
> +}
> +
> +/**
> + * intel_uc_fw_check() - check the status of the firmware fetching process
> + * @uc_fw: intel_uc_fw structure
> + * @callback: optional callback function to validate and/or save the image
> + *
> + * If the fetch is still PENDING, wait for completion first, then check and
> + * return the outcome. Subsequent calls will just return the same outcome
> + * based on the recorded fetch status, without triggering another fetch
> + * and without calling @callback().
> + *
> + * After this call, @uc_fw->uc_fw_fetch_status will show whether the firmware
> + * image was successfully fetched and transferred to a GEM object. If it is
> + * INTEL_UC_FIRMWARE_SUCCESS, @uc_fw->uc_fw_obj will be point to the GEM
> + * object, and the size of the image will be in @uc_fw->uc_fw_size. For any
> + * other status value, these members are undefined.
> + *
> + * The @callback() parameter allows the uC-specific code to validate the
> + * image before it is saved, and also to override the default save mechanism
> + * if required. When it is called, @uc_fw->uc_fw_blob refers to the fetched
> + * firmware image, and @uc_fw->uc_fw_obj is NULL.
> + *
> + * If @callback() returns FALSE, the fetched image is considered invalid.
> + * The fetch status will be set to FAIL, and this function will return -EIO.
> + *
> + * If @callback() returns TRUE but doesn't set @uc_fw->uc_fw_obj, the image
> + * is considered good; it will be saved in a GEM object as described above.
> + * This is the default if no @callback() is supplied.
> + *
> + * If @callback() returns TRUE after setting @uc_fw->uc_fw_obj, this means
> + * that the image has already been saved by @callback() itself. This allows
> + * @callback() to customise the format of the data in the GEM object, for
> + * example if it needs to save only a portion of the loaded image.
> + *
> + * In all cases the firmware blob is released before this function returns.
> + *
> + * Return: non-zero code on error
> + */
> +int
> +intel_uc_fw_check(struct intel_uc_fw *uc_fw,
> + bool callback(struct intel_uc_fw *))
> +{
> + WARN_ON(!mutex_is_locked(&uc_fw->uc_dev->struct_mutex));
> +
> + if (uc_fw->uc_fw_fetch_status == INTEL_UC_FIRMWARE_PENDING) {
> + /* We only come here once */
> + uc_fw_fetch_wait(uc_fw, callback);
> + /* state must now be FAIL or SUCCESS */
> + }
> +
> + DRM_DEBUG_DRIVER("%s fw fetch status %d\n",
> + uc_fw->uc_name, uc_fw->uc_fw_fetch_status);
> +
> + switch (uc_fw->uc_fw_fetch_status) {
> + case INTEL_UC_FIRMWARE_FAIL:
> + /* something went wrong :( */
> + return -EIO;
> +
> + case INTEL_UC_FIRMWARE_NONE:
> + /* no firmware, nothing to do (not an error) */
> + return 0;
> +
> + case INTEL_UC_FIRMWARE_PENDING:
> + default:
> + /* "can't happen" */
> + WARN_ONCE(1, "%s fw <%s> invalid uc_fw_fetch_status %d!\n",
> + uc_fw->uc_name, uc_fw->uc_fw_path,
> + uc_fw->uc_fw_fetch_status);
> + return -ENXIO;
> +
> + case INTEL_UC_FIRMWARE_SUCCESS:
> + return 0;
> + }
> +}
> +
> +/*
> + * Callback from the kernel's asynchronous firmware-fetching subsystem.
> + * All we have to do here is stash the blob and signal completion.
> + * Error checking (e.g. no firmware found) is left to mainline code.
> + * We don't have (and don't want or need to acquire) the struct_mutex here.
> + */
> +static void
> +uc_fw_fetch_callback(const struct firmware *fw, void *context)
> +{
> + struct intel_uc_fw *uc_fw = context;
> +
> + WARN_ON(uc_fw->uc_fw_fetch_status != INTEL_UC_FIRMWARE_PENDING);
> + DRM_DEBUG_DRIVER("%s firmware fetch from <%s> status %d, fw %p\n",
> + uc_fw->uc_name, uc_fw->uc_fw_path,
> + uc_fw->uc_fw_fetch_status, fw);
> +
> + uc_fw->uc_fw_blob = fw;
> + complete(&uc_fw->uc_fw_fetched);
> +}
> +
> +/**
> + * intel_uc_fw_init() - initiate the fetching of firmware
> + * @dev: drm device
> + * @uc_fw: intel_uc_fw structure
> + * @name: human-readable device name (e.g. "GuC") for messages
> + * @fw_path: (trailing parts of) path to firmware (e.g. "i915/guc_fw.bin")
> + * @fw_path == NULL means "no firmware expected" (not an error),
> + * @fw_path == "" (empty string) means "firmware unknown" i.e.
> + * the uC requires firmware, but the driver doesn't know where
> + * to find the proper version. This will be logged as an error.
> + *
> + * This is called just once per uC, during driver loading. It is therefore
> + * automatically single-threaded and does not need to acquire any mutexes
> + * or spinlocks. OTOH, GEM is not yet fully initialised, so we can't do
> + * very much here.
> + *
> + * The main task here is to initiate the fetching of the uC firmware into
> + * memory, using the standard kernel firmware fetching support. The actual
> + * fetching will then proceed asynchronously and in parallel with the rest
> + * of driver initialisation; later in the loading process we will synchronise
> + * with the firmware-fetching thread before transferring the firmware image
> + * firstly into a GEM object and then into the uC's memory.
> + */
> +void
> +intel_uc_fw_init(struct drm_device *dev, struct intel_uc_fw *uc_fw,
> + const char *name, const char *fw_path)
> +{
> + uc_fw->uc_dev = dev;
> + uc_fw->uc_name = name;
> + uc_fw->uc_fw_path = fw_path;
> + uc_fw->uc_fw_fetch_status = INTEL_UC_FIRMWARE_NONE;
> + uc_fw->uc_fw_load_status = INTEL_UC_FIRMWARE_NONE;
> + init_completion(&uc_fw->uc_fw_fetched);
> +
> + if (fw_path == NULL)
> + return;
> +
> + if (*fw_path == '\0') {
> + DRM_ERROR("No %s firmware known for this platform\n", name);
> + uc_fw->uc_fw_fetch_status = INTEL_UC_FIRMWARE_FAIL;
> + return;
> + }
> +
> + uc_fw->uc_fw_fetch_status = INTEL_UC_FIRMWARE_PENDING;
> +
> + if (request_firmware_nowait(THIS_MODULE, true, fw_path,
> + &dev->pdev->dev,
> + GFP_KERNEL, uc_fw,
> + uc_fw_fetch_callback)) {
> + DRM_ERROR("Failed to request %s firmware from <%s>\n",
> + name, fw_path);
> + uc_fw->uc_fw_fetch_status = INTEL_UC_FIRMWARE_FAIL;
> + return;
> + }
> +
> + /* firmware fetch initiated, callback will signal completion */
> + DRM_DEBUG_DRIVER("initiated fetching %s firmware from <%s>\n",
> + name, fw_path);
> +}
> +
> +/**
> + * intel_uc_fw_fini() - clean up all uC firmware-related data
> + * @uc_fw: intel_uc_fw structure
> + */
> +void
> +intel_uc_fw_fini(struct intel_uc_fw *uc_fw)
> +{
> + WARN_ON(!mutex_is_locked(&uc_fw->uc_dev->struct_mutex));
> +
> + /*
> + * Generally, the blob should have been released earlier, but
> + * if the driver load were aborted after the fetch had been
> + * initiated but not completed it might still be around
> + */
> + if (uc_fw->uc_fw_fetch_status == INTEL_UC_FIRMWARE_PENDING)
> + wait_for_completion(&uc_fw->uc_fw_fetched);
> + release_firmware(uc_fw->uc_fw_blob); /* OK even if NULL */
> + uc_fw->uc_fw_blob = NULL;
> +
> + if (uc_fw->uc_fw_obj)
> + drm_gem_object_unreference(&uc_fw->uc_fw_obj->base);
> + uc_fw->uc_fw_obj = NULL;
> +}
> diff --git a/drivers/gpu/drm/i915/intel_uc_loader.h b/drivers/gpu/drm/i915/intel_uc_loader.h
> new file mode 100644
> index 0000000..22502ea
> --- /dev/null
> +++ b/drivers/gpu/drm/i915/intel_uc_loader.h
> @@ -0,0 +1,82 @@
> +/*
> + * Copyright © 2014 Intel Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> + * IN THE SOFTWARE.
> + *
> + * Author:
> + * Dave Gordon <david.s.gordon@intel.com>
> + */
> +#ifndef _INTEL_UC_LOADER_H
> +#define _INTEL_UC_LOADER_H
> +
> +/*
> + * Microcontroller (uC) firmware loading support
> + */
> +
> +/*
> + * These values are used to track the stages of getting the required firmware
> + * into an onboard microcontroller. The common code tracks the phases of
> + * fetching the firmware (aka "binary blob") from an external file into a GEM
> + * object in the 'uc_fw_fetch_status' field below; the uC-specific DMA code
> + * uses the 'uc_fw_load_status' field to track the transfer from GEM object
> + * to uC memory.
> + *
> + * For the first (fetch) stage, the interpretation of the values is:
> + * NONE - no firmware is being fetched e.g. because there is no uC
> + * PENDING - firmware fetch initiated; callback will complete 'uc_fw_fetched'
> + * SUCCESS - uC firmware fetched into a GEM object and ready for use
> + * FAIL - something went wrong; uC firmware is not available
> + *
> + * The second (load) stage is simpler as there is no asynchronous handoff:
> + * NONE - no firmware is being loaded e.g. because there is no uC
> + * PENDING - firmware DMA load in progress
> + * SUCCESS - uC firmware loaded into uC memory and ready for use
> + * FAIL - something went wrong; uC firmware is not available
> + */
> +enum intel_uc_fw_status {
> + INTEL_UC_FIRMWARE_FAIL = -1,
> + INTEL_UC_FIRMWARE_NONE = 0,
> + INTEL_UC_FIRMWARE_PENDING,
> + INTEL_UC_FIRMWARE_SUCCESS
> +};
> +
> +/*
> + * This structure encapsulates all the data needed during the process of
> + * fetching, caching, and loading the firmware image into the uC.
> + */
> +struct intel_uc_fw {
> + struct drm_device * uc_dev;
> + const char * uc_name;
> + const char * uc_fw_path;
> + const struct firmware * uc_fw_blob;
> + struct completion uc_fw_fetched;
> + size_t uc_fw_size;
> + struct drm_i915_gem_object * uc_fw_obj;
> + enum intel_uc_fw_status uc_fw_fetch_status;
> + enum intel_uc_fw_status uc_fw_load_status;
> +};
> +
> +void intel_uc_fw_init(struct drm_device *dev, struct intel_uc_fw *uc_fw,
> + const char *uc_name, const char *fw_path);
> +int intel_uc_fw_check(struct intel_uc_fw *uc_fw,
> + bool callback(struct intel_uc_fw *));
> +void intel_uc_fw_fini(struct intel_uc_fw *uc_fw);
> +
> +#endif
> --
> 1.7.9.5
>
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/intel-gfx

--
Daniel Vetter
Software Engineer, Intel Corporation