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. v2: Local functions should pass dev_priv rather than dev [Chris Wilson] Various other improvements per Chris Wilson's review comments Signed-off-by: Dave Gordon <david.s.gordon@xxxxxxxxx> Signed-off-by: Alex Dai <yu.dai@xxxxxxxxx> --- drivers/gpu/drm/i915/Makefile | 3 + drivers/gpu/drm/i915/intel_uc_loader.c | 332 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/i915/intel_uc_loader.h | 86 +++++++++ 3 files changed, 421 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 de21965..f1f80fc 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -39,6 +39,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..8c01e47 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_uc_loader.c @@ -0,0 +1,332 @@ +/* + * 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@xxxxxxxxx> + */ +#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. + */ + +/* User-friendly representation of an enum */ +const char *intel_uc_fw_status_repr(enum intel_uc_fw_status status) +{ + switch (status) { + case INTEL_UC_FIRMWARE_FAIL: + return "FAIL"; + case INTEL_UC_FIRMWARE_NONE: + return "NONE"; + case INTEL_UC_FIRMWARE_PENDING: + return "PENDING"; + case INTEL_UC_FIRMWARE_SUCCESS: + return "SUCCESS"; + default: + return "UNKNOWN!"; + } +}; + +/* + * 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 %s, fw %p\n", + uc_fw->uc_name, + intel_uc_fw_status_repr(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 %s, fw %p\n", + uc_fw->uc_name, + intel_uc_fw_status_repr(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) { + obj = i915_gem_object_create_from_data(dev, fw->data, fw->size); + if (!obj) + goto fail; + + uc_fw->uc_fw_obj = obj; + uc_fw->uc_fw_size = fw->size; + } + + 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 %s\n", uc_fw->uc_name, + intel_uc_fw_status_repr(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 %s [%d]\n", + uc_fw->uc_name, uc_fw->uc_fw_path, + intel_uc_fw_status_repr(uc_fw->uc_fw_fetch_status), + 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 %s, fw %p\n", + uc_fw->uc_name, + uc_fw->uc_fw_path, + intel_uc_fw_status_repr(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..301033c --- /dev/null +++ b/drivers/gpu/drm/i915/intel_uc_loader.h @@ -0,0 +1,86 @@ +/* + * 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@xxxxxxxxx> + */ +#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 + * + * The function intel_uc_fw_status_repr() will convert this enum to a + * string representation suitable for sending to a logfile. + */ +enum intel_uc_fw_status { + INTEL_UC_FIRMWARE_FAIL = -1, + INTEL_UC_FIRMWARE_NONE = 0, + INTEL_UC_FIRMWARE_PENDING, + INTEL_UC_FIRMWARE_SUCCESS +}; +const char *intel_uc_fw_status_repr(enum intel_uc_fw_status status); + +/* + * 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@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/intel-gfx