drm/i915: GuC-specific firmware loader
authorAlex Dai <yu.dai@intel.com>
Wed, 12 Aug 2015 14:43:36 +0000 (15:43 +0100)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Fri, 14 Aug 2015 16:16:39 +0000 (18:16 +0200)
This fetches the required firmware image from the filesystem,
then loads it into the GuC's memory via a dedicated DMA engine.

This patch is derived from GuC loading work originally done by
Vinit Azad and Ben Widawsky.

v2:
    Various improvements per review comments by Chris Wilson

v3:
    Removed 'wait' parameter to intel_guc_ucode_load() as firmware
        prefetch is no longer supported in the common firmware loader,
per Daniel Vetter's request.
    Firmware checker callback fn now returns errno rather than bool.

v4:
    Squash uC-independent code into GuC-specifc loader [Daniel Vetter]
    Don't keep the driver working (by falling back to execlist mode)
        if GuC firmware loading fails [Daniel Vetter]

v5:
    Clarify WOPCM-related #defines [Tom O'Rourke]
    Delete obsolete code no longer required with current h/w & f/w
        [Tom O'Rourke]
    Move the call to intel_guc_ucode_init() later, so that it can
        allocate GEM objects, and have it fetch the firmware; then
intel_guc_ucode_load() doesn't need to fetch it later.
        [Daniel Vetter].

v6:
    Update comment describing intel_guc_ucode_load() [Tom O'Rourke]

Issue: VIZ-4884
Signed-off-by: Alex Dai <yu.dai@intel.com>
Signed-off-by: Dave Gordon <david.s.gordon@intel.com>
Reviewed-by: Tom O'Rourke <Tom.O'Rourke@intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/i915/Makefile
drivers/gpu/drm/i915/i915_dma.c
drivers/gpu/drm/i915/i915_drv.h
drivers/gpu/drm/i915/i915_gem.c
drivers/gpu/drm/i915/i915_guc_reg.h
drivers/gpu/drm/i915/i915_reg.h
drivers/gpu/drm/i915/intel_guc.h [new file with mode: 0644]
drivers/gpu/drm/i915/intel_guc_fwif.h
drivers/gpu/drm/i915/intel_guc_loader.c [new file with mode: 0644]

index 41fb8a9c5bef4e1b6f9c2b248860e0408133cc87..cc359e0e66789731b127aef430dfce5be6deec02 100644 (file)
@@ -40,6 +40,9 @@ i915-y += i915_cmd_parser.o \
          intel_ringbuffer.o \
          intel_uncore.o
 
+# general-purpose microcontroller (GuC) support
+i915-y += intel_guc_loader.o
+
 # autogenerated null render state
 i915-y += intel_renderstate_gen6.o \
          intel_renderstate_gen7.o \
index ab37d1121be8277728bff5d25a0cb4a4599de0aa..2193cc28f70c382addf5c10eafa9c66785d19869 100644 (file)
@@ -435,6 +435,11 @@ static int i915_load_modeset_init(struct drm_device *dev)
         * working irqs for e.g. gmbus and dp aux transfers. */
        intel_modeset_init(dev);
 
+       /* intel_guc_ucode_init() needs the mutex to allocate GEM objects */
+       mutex_lock(&dev->struct_mutex);
+       intel_guc_ucode_init(dev);
+       mutex_unlock(&dev->struct_mutex);
+
        ret = i915_gem_init(dev);
        if (ret)
                goto cleanup_irq;
@@ -476,6 +481,9 @@ cleanup_gem:
        i915_gem_context_fini(dev);
        mutex_unlock(&dev->struct_mutex);
 cleanup_irq:
+       mutex_lock(&dev->struct_mutex);
+       intel_guc_ucode_fini(dev);
+       mutex_unlock(&dev->struct_mutex);
        drm_irq_uninstall(dev);
 cleanup_gem_stolen:
        i915_gem_cleanup_stolen(dev);
@@ -1128,6 +1136,7 @@ int i915_driver_unload(struct drm_device *dev)
        flush_workqueue(dev_priv->wq);
 
        mutex_lock(&dev->struct_mutex);
+       intel_guc_ucode_fini(dev);
        i915_gem_cleanup_ringbuffer(dev);
        i915_gem_context_fini(dev);
        mutex_unlock(&dev->struct_mutex);
index 6b85338c4b895fc7eca749afa79c8b40f58bd9b0..61244455347f706723f96b0fb5b72184172b83f1 100644 (file)
@@ -50,6 +50,7 @@
 #include <linux/intel-iommu.h>
 #include <linux/kref.h>
 #include <linux/pm_qos.h>
+#include "intel_guc.h"
 
 /* General customization:
  */
@@ -1709,6 +1710,8 @@ struct drm_i915_private {
 
        struct i915_virtual_gpu vgpu;
 
+       struct intel_guc guc;
+
        struct intel_csr csr;
 
        /* Display CSR-related protection */
@@ -1953,6 +1956,11 @@ static inline struct drm_i915_private *dev_to_i915(struct device *dev)
        return to_i915(dev_get_drvdata(dev));
 }
 
+static inline struct drm_i915_private *guc_to_i915(struct intel_guc *guc)
+{
+       return container_of(guc, struct drm_i915_private, guc);
+}
+
 /* Iterate over initialised rings */
 #define for_each_ring(ring__, dev_priv__, i__) \
        for ((i__) = 0; (i__) < I915_NUM_RINGS; (i__)++) \
@@ -2557,6 +2565,9 @@ struct drm_i915_cmd_table {
 
 #define HAS_CSR(dev)   (IS_SKYLAKE(dev))
 
+#define HAS_GUC_UCODE(dev)     (IS_GEN9(dev))
+#define HAS_GUC_SCHED(dev)     (IS_GEN9(dev))
+
 #define HAS_RESOURCE_STREAMER(dev) (IS_HASWELL(dev) || \
                                    INTEL_INFO(dev)->gen >= 8)
 
index 407b6b3576ae40c5d5213ca110586c408911d226..1ac57ec803bdc0657f0a0a845121e83f35608165 100644 (file)
@@ -4681,6 +4681,22 @@ i915_gem_init_hw(struct drm_device *dev)
                        goto out;
        }
 
+       /* We can't enable contexts until all firmware is loaded */
+       ret = intel_guc_ucode_load(dev);
+       if (ret) {
+               /*
+                * If we got an error and GuC submission is enabled, map
+                * the error to -EIO so the GPU will be declared wedged.
+                * OTOH, if we didn't intend to use the GuC anyway, just
+                * discard the error and carry on.
+                */
+               DRM_ERROR("Failed to initialize GuC, error %d%s\n", ret,
+                       i915.enable_guc_submission ? "" : " (ignored)");
+               ret = i915.enable_guc_submission ? -EIO : 0;
+               if (ret)
+                       goto out;
+       }
+
        /* Now it is safe to go back round and do everything else: */
        for_each_ring(ring, dev_priv, i) {
                struct drm_i915_gem_request *req;
index ccdc6c8ac20b0c64e33cab97869bd5d74edb4d72..8c8e574e63bab76959bb9704fd49e77e51a295e1 100644 (file)
 #define   GS_MIA_SHIFT                 16
 #define   GS_MIA_MASK                    (0x07 << GS_MIA_SHIFT)
 
-#define GUC_WOPCM_SIZE                 0xc050
-#define   GUC_WOPCM_SIZE_VALUE           (0x80 << 12)  /* 512KB */
-#define GUC_WOPCM_OFFSET               0x80000         /* 512KB */
-
 #define SOFT_SCRATCH(n)                        (0xc180 + ((n) * 4))
 
 #define UOS_RSA_SCRATCH_0              0xc200
 #define   UOS_MOVE                       (1<<4)
 #define   START_DMA                      (1<<0)
 #define DMA_GUC_WOPCM_OFFSET           0xc340
+#define   GUC_WOPCM_OFFSET_VALUE         0x80000       /* 512KB */
+
+#define GUC_WOPCM_SIZE                 0xc050
+#define   GUC_WOPCM_SIZE_VALUE           (0x80 << 12)  /* 512KB */
+
+/* GuC addresses below GUC_WOPCM_TOP don't map through the GTT */
+#define        GUC_WOPCM_TOP                   (GUC_WOPCM_SIZE_VALUE)
 
 #define GEN8_GT_PM_CONFIG              0x138140
+#define GEN9LP_GT_PM_CONFIG            0x138140
 #define GEN9_GT_PM_CONFIG              0x13816c
-#define   GEN8_GT_DOORBELL_ENABLE        (1<<0)
+#define   GT_DOORBELL_ENABLE             (1<<0)
 
 #define GEN8_GTCR                      0x4274
 #define   GEN8_GTCR_INVALIDATE           (1<<0)
@@ -80,7 +84,8 @@
                                 GUC_ENABLE_READ_CACHE_LOGIC            | \
                                 GUC_ENABLE_MIA_CACHING                 | \
                                 GUC_ENABLE_READ_CACHE_FOR_SRAM_DATA    | \
-                                GUC_ENABLE_READ_CACHE_FOR_WOPCM_DATA)
+                                GUC_ENABLE_READ_CACHE_FOR_WOPCM_DATA   | \
+                                GUC_ENABLE_MIA_CLOCK_GATING)
 
 #define HOST2GUC_INTERRUPT             0xc4c8
 #define   HOST2GUC_TRIGGER               (1<<0)
index f22aca3218c2ff9fa2af1e0b4689759b23ca8813..651d0dde774e0b848c07e1e228a0d735976aa662 100644 (file)
@@ -6860,7 +6860,9 @@ enum skl_disp_power_wells {
 #define   GEN9_PGCTL_SSB_EU311_ACK     (1 << 14)
 
 #define GEN7_MISCCPCTL                 (0x9424)
-#define   GEN7_DOP_CLOCK_GATE_ENABLE   (1<<0)
+#define   GEN7_DOP_CLOCK_GATE_ENABLE           (1<<0)
+#define   GEN8_DOP_CLOCK_GATE_CFCLK_ENABLE     (1<<2)
+#define   GEN8_DOP_CLOCK_GATE_GUC_ENABLE       (1<<4)
 
 #define GEN8_GARBCNTL                   0xB004
 #define   GEN9_GAPS_TSV_CREDIT_DISABLE  (1<<7)
diff --git a/drivers/gpu/drm/i915/intel_guc.h b/drivers/gpu/drm/i915/intel_guc.h
new file mode 100644 (file)
index 0000000..2846b6d
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ *
+ */
+#ifndef _INTEL_GUC_H_
+#define _INTEL_GUC_H_
+
+#include "intel_guc_fwif.h"
+#include "i915_guc_reg.h"
+
+enum intel_guc_fw_status {
+       GUC_FIRMWARE_FAIL = -1,
+       GUC_FIRMWARE_NONE = 0,
+       GUC_FIRMWARE_PENDING,
+       GUC_FIRMWARE_SUCCESS
+};
+
+/*
+ * This structure encapsulates all the data needed during the process
+ * of fetching, caching, and loading the firmware image into the GuC.
+ */
+struct intel_guc_fw {
+       struct drm_device *             guc_dev;
+       const char *                    guc_fw_path;
+       size_t                          guc_fw_size;
+       struct drm_i915_gem_object *    guc_fw_obj;
+       enum intel_guc_fw_status        guc_fw_fetch_status;
+       enum intel_guc_fw_status        guc_fw_load_status;
+
+       uint16_t                        guc_fw_major_wanted;
+       uint16_t                        guc_fw_minor_wanted;
+       uint16_t                        guc_fw_major_found;
+       uint16_t                        guc_fw_minor_found;
+};
+
+struct intel_guc {
+       struct intel_guc_fw guc_fw;
+
+       uint32_t log_flags;
+};
+
+/* intel_guc_loader.c */
+extern void intel_guc_ucode_init(struct drm_device *dev);
+extern int intel_guc_ucode_load(struct drm_device *dev);
+extern void intel_guc_ucode_fini(struct drm_device *dev);
+extern const char *intel_guc_fw_status_repr(enum intel_guc_fw_status status);
+
+#endif
index 18d7f20936c8e28b1d057a48ea7f4eb8caab8b88..06aad6fb02ed322814406f06011a30fde02d052c 100644 (file)
@@ -32,9 +32,8 @@
  * EDITING THIS FILE IS THEREFORE NOT RECOMMENDED - YOUR CHANGES MAY BE LOST.
  */
 
-#define GFXCORE_FAMILY_GEN8            11
 #define GFXCORE_FAMILY_GEN9            12
-#define GFXCORE_FAMILY_FORCE_ULONG     0x7fffffff
+#define GFXCORE_FAMILY_UNKNOWN         0x7fffffff
 
 #define GUC_CTX_PRIORITY_CRITICAL      0
 #define GUC_CTX_PRIORITY_HIGH          1
diff --git a/drivers/gpu/drm/i915/intel_guc_loader.c b/drivers/gpu/drm/i915/intel_guc_loader.c
new file mode 100644 (file)
index 0000000..dd62c31
--- /dev/null
@@ -0,0 +1,529 @@
+/*
+ * 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.
+ *
+ * Authors:
+ *    Vinit Azad <vinit.azad@intel.com>
+ *    Ben Widawsky <ben@bwidawsk.net>
+ *    Dave Gordon <david.s.gordon@intel.com>
+ *    Alex Dai <yu.dai@intel.com>
+ */
+#include <linux/firmware.h>
+#include "i915_drv.h"
+#include "intel_guc.h"
+
+/**
+ * DOC: GuC
+ *
+ * intel_guc:
+ * Top level structure of guc. It handles firmware loading and manages client
+ * pool and doorbells. intel_guc owns a i915_guc_client to replace the legacy
+ * ExecList submission.
+ *
+ * Firmware versioning:
+ * The firmware build process will generate a version header file with major and
+ * minor version defined. The versions are built into CSS header of firmware.
+ * i915 kernel driver set the minimal firmware version required per platform.
+ * The firmware installation package will install (symbolic link) proper version
+ * of firmware.
+ *
+ * GuC address space:
+ * GuC does not allow any gfx GGTT address that falls into range [0, WOPCM_TOP),
+ * which is reserved for Boot ROM, SRAM and WOPCM. Currently this top address is
+ * 512K. In order to exclude 0-512K address space from GGTT, all gfx objects
+ * used by GuC is pinned with PIN_OFFSET_BIAS along with size of WOPCM.
+ *
+ * Firmware log:
+ * Firmware log is enabled by setting i915.guc_log_level to non-negative level.
+ * Log data is printed out via reading debugfs i915_guc_log_dump. Reading from
+ * i915_guc_load_status will print out firmware loading status and scratch
+ * registers value.
+ *
+ */
+
+#define I915_SKL_GUC_UCODE "i915/skl_guc_ver3.bin"
+MODULE_FIRMWARE(I915_SKL_GUC_UCODE);
+
+/* User-friendly representation of an enum */
+const char *intel_guc_fw_status_repr(enum intel_guc_fw_status status)
+{
+       switch (status) {
+       case GUC_FIRMWARE_FAIL:
+               return "FAIL";
+       case GUC_FIRMWARE_NONE:
+               return "NONE";
+       case GUC_FIRMWARE_PENDING:
+               return "PENDING";
+       case GUC_FIRMWARE_SUCCESS:
+               return "SUCCESS";
+       default:
+               return "UNKNOWN!";
+       }
+};
+
+static u32 get_gttype(struct drm_i915_private *dev_priv)
+{
+       /* XXX: GT type based on PCI device ID? field seems unused by fw */
+       return 0;
+}
+
+static u32 get_core_family(struct drm_i915_private *dev_priv)
+{
+       switch (INTEL_INFO(dev_priv)->gen) {
+       case 9:
+               return GFXCORE_FAMILY_GEN9;
+
+       default:
+               DRM_ERROR("GUC: unsupported core family\n");
+               return GFXCORE_FAMILY_UNKNOWN;
+       }
+}
+
+static void set_guc_init_params(struct drm_i915_private *dev_priv)
+{
+       struct intel_guc *guc = &dev_priv->guc;
+       u32 params[GUC_CTL_MAX_DWORDS];
+       int i;
+
+       memset(&params, 0, sizeof(params));
+
+       params[GUC_CTL_DEVICE_INFO] |=
+               (get_gttype(dev_priv) << GUC_CTL_GTTYPE_SHIFT) |
+               (get_core_family(dev_priv) << GUC_CTL_COREFAMILY_SHIFT);
+
+       /*
+        * GuC ARAT increment is 10 ns. GuC default scheduler quantum is one
+        * second. This ARAR is calculated by:
+        * Scheduler-Quantum-in-ns / ARAT-increment-in-ns = 1000000000 / 10
+        */
+       params[GUC_CTL_ARAT_HIGH] = 0;
+       params[GUC_CTL_ARAT_LOW] = 100000000;
+
+       params[GUC_CTL_WA] |= GUC_CTL_WA_UK_BY_DRIVER;
+
+       params[GUC_CTL_FEATURE] |= GUC_CTL_DISABLE_SCHEDULER |
+                       GUC_CTL_VCS2_ENABLED;
+
+       if (i915.guc_log_level >= 0) {
+               params[GUC_CTL_LOG_PARAMS] = guc->log_flags;
+               params[GUC_CTL_DEBUG] =
+                       i915.guc_log_level << GUC_LOG_VERBOSITY_SHIFT;
+       }
+
+       I915_WRITE(SOFT_SCRATCH(0), 0);
+
+       for (i = 0; i < GUC_CTL_MAX_DWORDS; i++)
+               I915_WRITE(SOFT_SCRATCH(1 + i), params[i]);
+}
+
+/*
+ * Read the GuC status register (GUC_STATUS) and store it in the
+ * specified location; then return a boolean indicating whether
+ * the value matches either of two values representing completion
+ * of the GuC boot process.
+ *
+ * This is used for polling the GuC status in a wait_for_atomic()
+ * loop below.
+ */
+static inline bool guc_ucode_response(struct drm_i915_private *dev_priv,
+                                     u32 *status)
+{
+       u32 val = I915_READ(GUC_STATUS);
+       *status = val;
+       return ((val & GS_UKERNEL_MASK) == GS_UKERNEL_READY ||
+               (val & GS_UKERNEL_MASK) == GS_UKERNEL_LAPIC_DONE);
+}
+
+/*
+ * Transfer the firmware image to RAM for execution by the microcontroller.
+ *
+ * GuC Firmware layout:
+ * +-------------------------------+  ----
+ * |          CSS header           |  128B
+ * | contains major/minor version  |
+ * +-------------------------------+  ----
+ * |             uCode             |
+ * +-------------------------------+  ----
+ * |         RSA signature         |  256B
+ * +-------------------------------+  ----
+ * |         RSA public Key        |  256B
+ * +-------------------------------+  ----
+ * |       Public key modulus      |    4B
+ * +-------------------------------+  ----
+ *
+ * Architecturally, the DMA engine is bidirectional, and can potentially even
+ * transfer between GTT locations. This functionality is left out of the API
+ * for now as there is no need for it.
+ *
+ * Note that GuC needs the CSS header plus uKernel code to be copied by the
+ * DMA engine in one operation, whereas the RSA signature is loaded via MMIO.
+ */
+
+#define UOS_CSS_HEADER_OFFSET          0
+#define UOS_VER_MINOR_OFFSET           0x44
+#define UOS_VER_MAJOR_OFFSET           0x46
+#define UOS_CSS_HEADER_SIZE            0x80
+#define UOS_RSA_SIG_SIZE               0x100
+#define UOS_CSS_SIGNING_SIZE           0x204
+
+static int guc_ucode_xfer_dma(struct drm_i915_private *dev_priv)
+{
+       struct intel_guc_fw *guc_fw = &dev_priv->guc.guc_fw;
+       struct drm_i915_gem_object *fw_obj = guc_fw->guc_fw_obj;
+       unsigned long offset;
+       struct sg_table *sg = fw_obj->pages;
+       u32 status, ucode_size, rsa[UOS_RSA_SIG_SIZE / sizeof(u32)];
+       int i, ret = 0;
+
+       /* uCode size, also is where RSA signature starts */
+       offset = ucode_size = guc_fw->guc_fw_size - UOS_CSS_SIGNING_SIZE;
+       I915_WRITE(DMA_COPY_SIZE, ucode_size);
+
+       /* Copy RSA signature from the fw image to HW for verification */
+       sg_pcopy_to_buffer(sg->sgl, sg->nents, rsa, UOS_RSA_SIG_SIZE, offset);
+       for (i = 0; i < UOS_RSA_SIG_SIZE / sizeof(u32); i++)
+               I915_WRITE(UOS_RSA_SCRATCH_0 + i * sizeof(u32), rsa[i]);
+
+       /* Set the source address for the new blob */
+       offset = i915_gem_obj_ggtt_offset(fw_obj);
+       I915_WRITE(DMA_ADDR_0_LOW, lower_32_bits(offset));
+       I915_WRITE(DMA_ADDR_0_HIGH, upper_32_bits(offset) & 0xFFFF);
+
+       /*
+        * Set the DMA destination. Current uCode expects the code to be
+        * loaded at 8k; locations below this are used for the stack.
+        */
+       I915_WRITE(DMA_ADDR_1_LOW, 0x2000);
+       I915_WRITE(DMA_ADDR_1_HIGH, DMA_ADDRESS_SPACE_WOPCM);
+
+       /* Finally start the DMA */
+       I915_WRITE(DMA_CTRL, _MASKED_BIT_ENABLE(UOS_MOVE | START_DMA));
+
+       /*
+        * Spin-wait for the DMA to complete & the GuC to start up.
+        * NB: Docs recommend not using the interrupt for completion.
+        * Measurements indicate this should take no more than 20ms, so a
+        * timeout here indicates that the GuC has failed and is unusable.
+        * (Higher levels of the driver will attempt to fall back to
+        * execlist mode if this happens.)
+        */
+       ret = wait_for_atomic(guc_ucode_response(dev_priv, &status), 100);
+
+       DRM_DEBUG_DRIVER("DMA status 0x%x, GuC status 0x%x\n",
+                       I915_READ(DMA_CTRL), status);
+
+       if ((status & GS_BOOTROM_MASK) == GS_BOOTROM_RSA_FAILED) {
+               DRM_ERROR("GuC firmware signature verification failed\n");
+               ret = -ENOEXEC;
+       }
+
+       DRM_DEBUG_DRIVER("returning %d\n", ret);
+
+       return ret;
+}
+
+/*
+ * Load the GuC firmware blob into the MinuteIA.
+ */
+static int guc_ucode_xfer(struct drm_i915_private *dev_priv)
+{
+       struct intel_guc_fw *guc_fw = &dev_priv->guc.guc_fw;
+       struct drm_device *dev = dev_priv->dev;
+       int ret;
+
+       ret = i915_gem_object_set_to_gtt_domain(guc_fw->guc_fw_obj, false);
+       if (ret) {
+               DRM_DEBUG_DRIVER("set-domain failed %d\n", ret);
+               return ret;
+       }
+
+       ret = i915_gem_obj_ggtt_pin(guc_fw->guc_fw_obj, 0, 0);
+       if (ret) {
+               DRM_DEBUG_DRIVER("pin failed %d\n", ret);
+               return ret;
+       }
+
+       /* Invalidate GuC TLB to let GuC take the latest updates to GTT. */
+       I915_WRITE(GEN8_GTCR, GEN8_GTCR_INVALIDATE);
+
+       intel_uncore_forcewake_get(dev_priv, FORCEWAKE_ALL);
+
+       /* init WOPCM */
+       I915_WRITE(GUC_WOPCM_SIZE, GUC_WOPCM_SIZE_VALUE);
+       I915_WRITE(DMA_GUC_WOPCM_OFFSET, GUC_WOPCM_OFFSET_VALUE);
+
+       /* Enable MIA caching. GuC clock gating is disabled. */
+       I915_WRITE(GUC_SHIM_CONTROL, GUC_SHIM_CONTROL_VALUE);
+
+       /* WaC6DisallowByGfxPause*/
+       I915_WRITE(GEN6_GFXPAUSE, 0x30FFF);
+
+       if (IS_BROXTON(dev))
+               I915_WRITE(GEN9LP_GT_PM_CONFIG, GT_DOORBELL_ENABLE);
+       else
+               I915_WRITE(GEN9_GT_PM_CONFIG, GT_DOORBELL_ENABLE);
+
+       if (IS_GEN9(dev)) {
+               /* DOP Clock Gating Enable for GuC clocks */
+               I915_WRITE(GEN7_MISCCPCTL, (GEN8_DOP_CLOCK_GATE_GUC_ENABLE |
+                                           I915_READ(GEN7_MISCCPCTL)));
+
+               /* allows for 5us before GT can go to RC6 */
+               I915_WRITE(GUC_ARAT_C6DIS, 0x1FF);
+       }
+
+       set_guc_init_params(dev_priv);
+
+       ret = guc_ucode_xfer_dma(dev_priv);
+
+       intel_uncore_forcewake_put(dev_priv, FORCEWAKE_ALL);
+
+       /*
+        * We keep the object pages for reuse during resume. But we can unpin it
+        * now that DMA has completed, so it doesn't continue to take up space.
+        */
+       i915_gem_object_ggtt_unpin(guc_fw->guc_fw_obj);
+
+       return ret;
+}
+
+/**
+ * intel_guc_ucode_load() - load GuC uCode into the device
+ * @dev:       drm device
+ *
+ * Called from gem_init_hw() during driver loading and also after a GPU reset.
+ *
+ * The firmware image should have already been fetched into memory by the
+ * earlier call to intel_guc_ucode_init(), so here we need only check that
+ * is succeeded, and then transfer the image to the h/w.
+ *
+ * Return:     non-zero code on error
+ */
+int intel_guc_ucode_load(struct drm_device *dev)
+{
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_guc_fw *guc_fw = &dev_priv->guc.guc_fw;
+       int err = 0;
+
+       DRM_DEBUG_DRIVER("GuC fw status: fetch %s, load %s\n",
+               intel_guc_fw_status_repr(guc_fw->guc_fw_fetch_status),
+               intel_guc_fw_status_repr(guc_fw->guc_fw_load_status));
+
+       if (guc_fw->guc_fw_fetch_status == GUC_FIRMWARE_NONE)
+               return 0;
+
+       if (guc_fw->guc_fw_fetch_status == GUC_FIRMWARE_SUCCESS &&
+           guc_fw->guc_fw_load_status == GUC_FIRMWARE_FAIL)
+               return -ENOEXEC;
+
+       guc_fw->guc_fw_load_status = GUC_FIRMWARE_PENDING;
+
+       DRM_DEBUG_DRIVER("GuC fw fetch status %s\n",
+               intel_guc_fw_status_repr(guc_fw->guc_fw_fetch_status));
+
+       switch (guc_fw->guc_fw_fetch_status) {
+       case GUC_FIRMWARE_FAIL:
+               /* something went wrong :( */
+               err = -EIO;
+               goto fail;
+
+       case GUC_FIRMWARE_NONE:
+       case GUC_FIRMWARE_PENDING:
+       default:
+               /* "can't happen" */
+               WARN_ONCE(1, "GuC fw %s invalid guc_fw_fetch_status %s [%d]\n",
+                       guc_fw->guc_fw_path,
+                       intel_guc_fw_status_repr(guc_fw->guc_fw_fetch_status),
+                       guc_fw->guc_fw_fetch_status);
+               err = -ENXIO;
+               goto fail;
+
+       case GUC_FIRMWARE_SUCCESS:
+               break;
+       }
+
+       err = guc_ucode_xfer(dev_priv);
+       if (err)
+               goto fail;
+
+       guc_fw->guc_fw_load_status = GUC_FIRMWARE_SUCCESS;
+
+       DRM_DEBUG_DRIVER("GuC fw status: fetch %s, load %s\n",
+               intel_guc_fw_status_repr(guc_fw->guc_fw_fetch_status),
+               intel_guc_fw_status_repr(guc_fw->guc_fw_load_status));
+
+       return 0;
+
+fail:
+       if (guc_fw->guc_fw_load_status == GUC_FIRMWARE_PENDING)
+               guc_fw->guc_fw_load_status = GUC_FIRMWARE_FAIL;
+
+       return err;
+}
+
+static void guc_fw_fetch(struct drm_device *dev, struct intel_guc_fw *guc_fw)
+{
+       struct drm_i915_gem_object *obj;
+       const struct firmware *fw;
+       const u8 *css_header;
+       const size_t minsize = UOS_CSS_HEADER_SIZE + UOS_CSS_SIGNING_SIZE;
+       const size_t maxsize = GUC_WOPCM_SIZE_VALUE + UOS_CSS_SIGNING_SIZE
+                       - 0x8000; /* 32k reserved (8K stack + 24k context) */
+       int err;
+
+       DRM_DEBUG_DRIVER("before requesting firmware: GuC fw fetch status %s\n",
+               intel_guc_fw_status_repr(guc_fw->guc_fw_fetch_status));
+
+       err = request_firmware(&fw, guc_fw->guc_fw_path, &dev->pdev->dev);
+       if (err)
+               goto fail;
+       if (!fw)
+               goto fail;
+
+       DRM_DEBUG_DRIVER("fetch GuC fw from %s succeeded, fw %p\n",
+               guc_fw->guc_fw_path, fw);
+       DRM_DEBUG_DRIVER("firmware file size %zu (minimum %zu, maximum %zu)\n",
+               fw->size, minsize, maxsize);
+
+       /* Check the size of the blob befoe examining buffer contents */
+       if (fw->size < minsize || fw->size > maxsize)
+               goto fail;
+
+       /*
+        * The GuC firmware image has the version number embedded at a well-known
+        * offset within the firmware blob; note that major / minor version are
+        * TWO bytes each (i.e. u16), although all pointers and offsets are defined
+        * in terms of bytes (u8).
+        */
+       css_header = fw->data + UOS_CSS_HEADER_OFFSET;
+       guc_fw->guc_fw_major_found = *(u16 *)(css_header + UOS_VER_MAJOR_OFFSET);
+       guc_fw->guc_fw_minor_found = *(u16 *)(css_header + UOS_VER_MINOR_OFFSET);
+
+       if (guc_fw->guc_fw_major_found != guc_fw->guc_fw_major_wanted ||
+           guc_fw->guc_fw_minor_found < guc_fw->guc_fw_minor_wanted) {
+               DRM_ERROR("GuC firmware version %d.%d, required %d.%d\n",
+                       guc_fw->guc_fw_major_found, guc_fw->guc_fw_minor_found,
+                       guc_fw->guc_fw_major_wanted, guc_fw->guc_fw_minor_wanted);
+               err = -ENOEXEC;
+               goto fail;
+       }
+
+       DRM_DEBUG_DRIVER("firmware version %d.%d OK (minimum %d.%d)\n",
+                       guc_fw->guc_fw_major_found, guc_fw->guc_fw_minor_found,
+                       guc_fw->guc_fw_major_wanted, guc_fw->guc_fw_minor_wanted);
+
+       obj = i915_gem_object_create_from_data(dev, fw->data, fw->size);
+       if (IS_ERR_OR_NULL(obj)) {
+               err = obj ? PTR_ERR(obj) : -ENOMEM;
+               goto fail;
+       }
+
+       guc_fw->guc_fw_obj = obj;
+       guc_fw->guc_fw_size = fw->size;
+
+       DRM_DEBUG_DRIVER("GuC fw fetch status SUCCESS, obj %p\n",
+                       guc_fw->guc_fw_obj);
+
+       release_firmware(fw);
+       guc_fw->guc_fw_fetch_status = GUC_FIRMWARE_SUCCESS;
+       return;
+
+fail:
+       DRM_DEBUG_DRIVER("GuC fw fetch status FAIL; err %d, fw %p, obj %p\n",
+               err, fw, guc_fw->guc_fw_obj);
+       DRM_ERROR("Failed to fetch GuC firmware from %s (error %d)\n",
+                 guc_fw->guc_fw_path, err);
+
+       obj = guc_fw->guc_fw_obj;
+       if (obj)
+               drm_gem_object_unreference(&obj->base);
+       guc_fw->guc_fw_obj = NULL;
+
+       release_firmware(fw);           /* OK even if fw is NULL */
+       guc_fw->guc_fw_fetch_status = GUC_FIRMWARE_FAIL;
+}
+
+/**
+ * intel_guc_ucode_init() - define parameters and fetch firmware
+ * @dev:       drm device
+ *
+ * Called early during driver load, but after GEM is initialised.
+ * The device struct_mutex must be held by the caller, as we're
+ * going to allocate a GEM object to hold the firmware image.
+ *
+ * The firmware will be transferred to the GuC's memory later,
+ * when intel_guc_ucode_load() is called.
+ */
+void intel_guc_ucode_init(struct drm_device *dev)
+{
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_guc_fw *guc_fw = &dev_priv->guc.guc_fw;
+       const char *fw_path;
+
+       if (!HAS_GUC_SCHED(dev))
+               i915.enable_guc_submission = false;
+
+       if (!HAS_GUC_UCODE(dev)) {
+               fw_path = NULL;
+       } else if (IS_SKYLAKE(dev)) {
+               fw_path = I915_SKL_GUC_UCODE;
+               guc_fw->guc_fw_major_wanted = 3;
+               guc_fw->guc_fw_minor_wanted = 0;
+       } else {
+               i915.enable_guc_submission = false;
+               fw_path = "";   /* unknown device */
+       }
+
+       guc_fw->guc_dev = dev;
+       guc_fw->guc_fw_path = fw_path;
+       guc_fw->guc_fw_fetch_status = GUC_FIRMWARE_NONE;
+       guc_fw->guc_fw_load_status = GUC_FIRMWARE_NONE;
+
+       if (fw_path == NULL)
+               return;
+
+       if (*fw_path == '\0') {
+               DRM_ERROR("No GuC firmware known for this platform\n");
+               guc_fw->guc_fw_fetch_status = GUC_FIRMWARE_FAIL;
+               return;
+       }
+
+       guc_fw->guc_fw_fetch_status = GUC_FIRMWARE_PENDING;
+       DRM_DEBUG_DRIVER("GuC firmware pending, path %s\n", fw_path);
+       guc_fw_fetch(dev, guc_fw);
+       /* status must now be FAIL or SUCCESS */
+}
+
+/**
+ * intel_guc_ucode_fini() - clean up all allocated resources
+ * @dev:       drm device
+ */
+void intel_guc_ucode_fini(struct drm_device *dev)
+{
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_guc_fw *guc_fw = &dev_priv->guc.guc_fw;
+
+       if (guc_fw->guc_fw_obj)
+               drm_gem_object_unreference(&guc_fw->guc_fw_obj->base);
+       guc_fw->guc_fw_obj = NULL;
+
+       guc_fw->guc_fw_fetch_status = GUC_FIRMWARE_NONE;
+}