From patchwork Wed Aug 12 14:43:36 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Dave Gordon X-Patchwork-Id: 7002171 Return-Path: X-Original-To: patchwork-intel-gfx@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 49057C05AD for ; Wed, 12 Aug 2015 14:43:58 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id F2405206D8 for ; Wed, 12 Aug 2015 14:43:55 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by mail.kernel.org (Postfix) with ESMTP id 5165B206DB for ; Wed, 12 Aug 2015 14:43:53 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id ACB186EB84; Wed, 12 Aug 2015 07:43:52 -0700 (PDT) X-Original-To: intel-gfx@lists.freedesktop.org Delivered-To: intel-gfx@lists.freedesktop.org Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by gabe.freedesktop.org (Postfix) with ESMTP id 933E36EB84 for ; Wed, 12 Aug 2015 07:43:51 -0700 (PDT) Received: from fmsmga003.fm.intel.com ([10.253.24.29]) by fmsmga102.fm.intel.com with ESMTP; 12 Aug 2015 07:43:51 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.15,661,1432623600"; d="scan'208";a="540491098" Received: from dsgordon-linux2.isw.intel.com ([10.102.226.88]) by FMSMGA003.fm.intel.com with ESMTP; 12 Aug 2015 07:43:50 -0700 From: Dave Gordon To: intel-gfx@lists.freedesktop.org Date: Wed, 12 Aug 2015 15:43:36 +0100 Message-Id: <1439390624-21724-2-git-send-email-david.s.gordon@intel.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1439390624-21724-1-git-send-email-david.s.gordon@intel.com> References: <1439390624-21724-1-git-send-email-david.s.gordon@intel.com> Organization: Intel Corporation (UK) Ltd. - Co. Reg. #1134945 - Pipers Way, Swindon SN3 1RJ MIME-Version: 1.0 Subject: [Intel-gfx] [PATCH 1/9 v6] drm/i915: GuC-specific firmware loader X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: intel-gfx-bounces@lists.freedesktop.org Sender: "Intel-gfx" X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Alex Dai 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 Signed-off-by: Dave Gordon --- drivers/gpu/drm/i915/Makefile | 3 + drivers/gpu/drm/i915/i915_dma.c | 9 + drivers/gpu/drm/i915/i915_drv.h | 11 + drivers/gpu/drm/i915/i915_gem.c | 16 + drivers/gpu/drm/i915/i915_guc_reg.h | 17 +- drivers/gpu/drm/i915/i915_reg.h | 4 +- drivers/gpu/drm/i915/intel_guc.h | 67 ++++ drivers/gpu/drm/i915/intel_guc_fwif.h | 3 +- drivers/gpu/drm/i915/intel_guc_loader.c | 529 ++++++++++++++++++++++++++++++++ 9 files changed, 650 insertions(+), 9 deletions(-) create mode 100644 drivers/gpu/drm/i915/intel_guc.h create mode 100644 drivers/gpu/drm/i915/intel_guc_loader.c diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index 998b464..65e52fa 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -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 \ diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index ab37d11..2193cc2 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -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); diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 674b223..0c0125c 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -50,6 +50,7 @@ #include #include #include +#include "intel_guc.h" /* General customization: */ @@ -1706,6 +1707,8 @@ struct drm_i915_private { struct i915_virtual_gpu vgpu; + struct intel_guc guc; + struct intel_csr csr; /* Display CSR-related protection */ @@ -1950,6 +1953,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__)++) \ @@ -2554,6 +2562,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) diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 1c54601..23263e2 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -4680,6 +4680,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; diff --git a/drivers/gpu/drm/i915/i915_guc_reg.h b/drivers/gpu/drm/i915/i915_guc_reg.h index ccdc6c8..8c8e574 100644 --- a/drivers/gpu/drm/i915/i915_guc_reg.h +++ b/drivers/gpu/drm/i915/i915_guc_reg.h @@ -38,10 +38,6 @@ #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 @@ -56,10 +52,18 @@ #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) diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 6786e94..d54dc2c 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -6871,7 +6871,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 index 0000000..2846b6d --- /dev/null +++ b/drivers/gpu/drm/i915/intel_guc.h @@ -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 diff --git a/drivers/gpu/drm/i915/intel_guc_fwif.h b/drivers/gpu/drm/i915/intel_guc_fwif.h index 18d7f20..06aad6f 100644 --- a/drivers/gpu/drm/i915/intel_guc_fwif.h +++ b/drivers/gpu/drm/i915/intel_guc_fwif.h @@ -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 index 0000000..dd62c31 --- /dev/null +++ b/drivers/gpu/drm/i915/intel_guc_loader.c @@ -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 + * Ben Widawsky + * Dave Gordon + * Alex Dai + */ +#include +#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(¶ms, 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; +}