Files
linux/drivers/gpu/drm/i915/gt/uc/intel_huc.c
Linus Torvalds 0c86b42439 Merge tag 'drm-next-2025-03-28' of https://gitlab.freedesktop.org/drm/kernel
Pull drm updates from Dave Airlie:
 "Outside of drm there are some rust patches from Danilo who maintains
  that area in here, and some pieces for drm header check tests.

  The major things in here are a new driver supporting the touchbar
  displays on M1/M2, the nova-core stub driver which is just the vehicle
  for adding rust abstractions and start developing a real driver inside
  of.

  xe adds support for SVM with a non-driver specific SVM core
  abstraction that will hopefully be useful for other drivers, along
  with support for shrinking for TTM devices. I'm sure xe and AMD
  support new devices, but the pipeline depth on these things is hard to
  know what they end up being in the marketplace!

  uapi:
   - add mediatek tiled fourcc
   - add support for notifying userspace on device wedged

  new driver:
   - appletbdrm: support for Apple Touchbar displays on m1/m2
   - nova-core: skeleton rust driver to develop nova inside off

  firmware:
   - add some rust firmware pieces

  rust:
   - add 'LocalModule' type alias

  component:
   - add helper to query bound status

  fbdev:
   - fbtft: remove access to page->index

  media:
   - cec: tda998x: import driver from drm

  dma-buf:
   - add fast path for single fence merging

  tests:
   - fix lockdep warnings

  atomic:
   - allow full modeset on connector changes
   - clarify semantics of allow_modeset and drm_atomic_helper_check
   - async-flip: support on arbitary planes
   - writeback: fix UAF
   - Document atomic-state history

  format-helper:
   - support ARGB8888 to ARGB4444 conversions

  buddy:
   - fix multi-root cleanup

  ci:
   - update IGT

  dp:
   - support extended wake timeout
   - mst: fix RAD to string conversion
   - increase DPCD eDP control CAP size to 5 bytes
   - add DPCD eDP v1.5 definition
   - add helpers for LTTPR transparent mode

  panic:
   - encode QR code according to Fido 2.2

  scheduler:
   - add parameter struct for init
   - improve job peek/pop operations
   - optimise drm_sched_job struct layout

  ttm:
   - refactor pool allocation
   - add helpers for TTM shrinker

  panel-orientation:
   - add a bunch of new quirks

  panel:
   - convert panels to multi-style functions
   - edp: Add support for B140UAN04.4, BOE NV140FHM-NZ, CSW MNB601LS1-3,
     LG LP079QX1-SP0V, MNE007QS3-7, STA 116QHD024002, Starry
     116KHD024006, Lenovo T14s Gen6 Snapdragon
   - himax-hx83102: Add support for CSOT PNA957QT1-1, Kingdisplay
     kd110n11-51ie, Starry 2082109qfh040022-50e
   - visionox-r66451: use multi-style MIPI-DSI functions
   - raydium-rm67200: Add driver for Raydium RM67200
   - simple: Add support for BOE AV123Z7M-N17, BOE AV123Z7M-N17
   - sony-td4353-jdi: Use MIPI-DSI multi-func interface
   - summit: Add driver for Apple Summit display panel
   - visionox-rm692e5: Add driver for Visionox RM692E5

  bridge:
   - pass full atomic state to various callbacks
   - adv7511: Report correct capabilities
   - it6505: Fix HDCP V compare
   - snd65dsi86: fix device IDs
   - nwl-dsi: set bridge type
   - ti-sn65si83: add error recovery and set bridge type
   - synopsys: add HDMI audio support

  xe:
   - support device-wedged event
   - add mmap support for PCI memory barrier
   - perf pmu integration and expose per-engien activity
   - add EU stall sampling support
   - GPU SVM and Xe SVM implementation
   - use TTM shrinker
   - add survivability mode to allow the driver to do firmware updates
     in critical failure states
   - PXP HWDRM support for MTL and LNL
   - expose package/vram temps over hwmon
   - enable DP tunneling
   - drop mmio_ext abstraction
   - Reject BO evcition if BO is bound to current VM
   - Xe suballocator improvements
   - re-use display vmas when possible
   - add GuC Buffer Cache abstraction
   - PCI ID update for Panther Lake and Battlemage
   - Enable SRIOV for Panther Lake
   - Refactor VRAM manager location

  i915:
   - enable extends wake timeout
   - support device-wedged event
   - Enable DP 128b/132b SST DSC
   - FBC dirty rectangle support for display version 30+
   - convert i915/xe to drm client setup
   - Compute HDMI PLLS for rates not in fixed tables
   - Allow DSB usage when PSR is enabled on LNL+
   - Enable panel replay without full modeset
   - Enable async flips with compressed buffers on ICL+
   - support luminance based brightness via DPCD for eDP
   - enable VRR enable/disable without full modeset
   - allow GuC SLPC default strategies on MTL+ for performance
   - lots of display refactoring in move to struct intel_display

  amdgpu:
   - add device wedged event
   - support async page flips on overlay planes
   - enable broadcast RGB drm property
   - add info ioctl for virt mode
   - OEM i2c support for RGB lights
   - GC 11.5.2 + 11.5.3 support
   - SDMA 6.1.3 support
   - NBIO 7.9.1 + 7.11.2 support
   - MMHUB 1.8.1 + 3.3.2 support
   - DCN 3.6.0 support
   - Add dynamic workload profile switching for GC 10-12
   - support larger VBIOS sizes
   - Mark gttsize parameters as deprecated
   - Initial JPEG queue resset support

  amdkfd:
   - add KFD per process flags for setting precision
   - sync pasid values between KGD and KFD
   - improve GTT/VRAM handling for APUs
   - fix user queue validation on GC7/8
   - SDMA queue reset support

  raedeon:
   - rs400 hyperz fix

  i2c:
   - td998x: drop platform_data, split driver into media and bridge

  ast:
   - transmitter chip detection refactoring
   - vbios display mode refactoring
   - astdp: fix connection status and filter unsupported modes
   - cursor handling refactoring

  imagination:
   - check job dependencies with sched helper

  ivpu:
   - improve command queue handling
   - use workqueue for IRQ handling
   - add support HW fault injection
   - locking fixes

  mgag200:
   - add support for G200eH5

  msm:
   - dpu: add concurrent writeback support for DPU 10.x+
   - use LTTPR helpers
   - GPU:
     - Fix obscure GMU suspend failure
     - Expose syncobj timeline support
     - Extend GPU devcoredump with pagetable info
     - a623 support
     - Fix a6xx gen1/gen2 indexed-register blocks in gpu snapshot /
       devcoredump
   - Display:
     - Add cpu-cfg interconnect paths on SM8560 and SM8650
     - Introduce KMS OMMU fault handler, causing devcoredump snapshot
     - Fixed error pointer dereference in msm_kms_init_aspace()
   - DPU:
     - Fix mode_changing handling
     - Add writeback support on SM6150 (QCS615)
     - Fix DSC programming in 1:1:1 topology
     - Reworked hardware resource allocation, moving it to the CRTC code
     - Enabled support for Concurrent WriteBack (CWB) on SM8650
     - Enabled CDM blocks on all relevant platforms
     - Reworked debugfs interface for BW/clocks debugging
     - Clear perf params before calculating bw
     - Support YUV formats on writeback
     - Fixed double inclusion
     - Fixed writeback in YUV formats when using cloned output, Dropped
       wb2_formats_rgb
     - Corrected dpu_crtc_check_mode_changed and struct dpu_encoder_virt
       kerneldocs
     - Fixed uninitialized variable in dpu_crtc_kickoff_clone_mode()
   - DSI:
     - DSC-related fixes
     - Rework clock programming
   - DSI PHY:
     - Fix 7nm (and lower) PHY programming
     - Add proper DT schema definitions for DSI PHY clocks
   - HDMI:
     - Rework the driver, enabling the use of the HDMI Connector
       framework
   - Bindings:
     - Added eDP PHY on SA8775P

  nouveau:
   - move drm_slave_encoder interface into driver
   - nvkm: refactor GSP RPC
   - use LTTPR helpers

  mediatek:
   - HDMI fixup and refinement
   - add MT8188 dsc compatible
   - MT8365 SoC support

  panthor:
   - Expose sizes of intenral BOs via fdinfo
   - Fix race between reset and suspend
   - Improve locking

  qaic:
   - Add support for AIC200

  renesas:
   - Fix limits in DT bindings

  rockchip:
   - support rk3562-mali
   - rk3576: Add HDMI support
   - vop2: Add new display modes on RK3588 HDMI0 up to 4K
   - Don't change HDMI reference clock rate
   - Fix DT bindings
   - analogix_dp: add eDP support
   - fix shutodnw

  solomon:
   - Set SPI device table to silence warnings
   - Fix pixel and scanline encoding

  v3d:
   - handle clock

  vc4:
   - Use drm_exec
   - Use dma-resv for wait-BO ioctl
   - Remove seqno infrastructure

  virtgpu:
   - Support partial mappings of GEM objects
   - Reserve VGA resources during initialization
   - Fix UAF in virtgpu_dma_buf_free_obj()
   - Add panic support

  vkms:
   - Switch to a managed modesetting pipeline
   - Add support for ARGB8888
   - fix UAf

  xlnx:
   - Set correct DMA segment size
   - use mutex guards
   - Fix error handling
   - Fix docs"

* tag 'drm-next-2025-03-28' of https://gitlab.freedesktop.org/drm/kernel: (1762 commits)
  drm/amd/pm: Update feature list for smu_v13_0_6
  drm/amdgpu: Add parameter documentation for amdgpu_sync_fence
  drm/amdgpu/discovery: optionally use fw based ip discovery
  drm/amdgpu/discovery: use specific ip_discovery.bin for legacy asics
  drm/amdgpu/discovery: check ip_discovery fw file available
  drm/amd/pm: Remove unnecessay UQ10 to UINT conversion
  drm/amd/pm: Remove unnecessay UQ10 to UINT conversion
  drm/amdgpu/sdma_v4_4_2: update VM flush implementation for SDMA
  drm/amdgpu: Optimize VM invalidation engine allocation and synchronize GPU TLB flush
  drm/amd/amdgpu: Increase max rings to enable SDMA page ring
  drm/amdgpu: Decode deferred error type in gfx aca bank parser
  drm/amdgpu/gfx11: Add Cleaner Shader Support for GFX11.5 GPUs
  drm/amdgpu/mes: clean up SDMA HQD loop
  drm/amdgpu/mes: enable compute pipes across all MEC
  drm/amdgpu/mes: drop MES 10.x leftovers
  drm/amdgpu/mes: optimize compute loop handling
  drm/amdgpu/sdma: guilty tracking is per instance
  drm/amdgpu/sdma: fix engine reset handling
  drm/amdgpu: remove invalid usage of sched.ready
  drm/amdgpu: add cleaner shader trace point
  ...
2025-03-28 17:44:52 -07:00

694 lines
20 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2016-2019 Intel Corporation
*/
#include <linux/types.h>
#include "gt/intel_gt.h"
#include "gt/intel_rps.h"
#include "intel_guc_reg.h"
#include "intel_huc.h"
#include "intel_huc_print.h"
#include "i915_drv.h"
#include "i915_reg.h"
#include "pxp/intel_pxp_cmd_interface_43.h"
#include <linux/device/bus.h>
#include <linux/mei_aux.h>
/**
* DOC: HuC
*
* The HuC is a dedicated microcontroller for usage in media HEVC (High
* Efficiency Video Coding) operations. Userspace can directly use the firmware
* capabilities by adding HuC specific commands to batch buffers.
*
* The kernel driver is only responsible for loading the HuC firmware and
* triggering its security authentication. This is done differently depending
* on the platform:
*
* - older platforms (from Gen9 to most Gen12s): the load is performed via DMA
* and the authentication via GuC
* - DG2: load and authentication are both performed via GSC.
* - MTL and newer platforms: the load is performed via DMA (same as with
* not-DG2 older platforms), while the authentication is done in 2-steps,
* a first auth for clear-media workloads via GuC and a second one for all
* workloads via GSC.
*
* On platforms where the GuC does the authentication, to correctly do so the
* HuC binary must be loaded before the GuC one.
* Loading the HuC is optional; however, not using the HuC might negatively
* impact power usage and/or performance of media workloads, depending on the
* use-cases.
* HuC must be reloaded on events that cause the WOPCM to lose its contents
* (S3/S4, FLR); on older platforms the HuC must also be reloaded on GuC/GT
* reset, while on newer ones it will survive that.
*
* See https://github.com/intel/media-driver for the latest details on HuC
* functionality.
*/
/**
* DOC: HuC Memory Management
*
* Similarly to the GuC, the HuC can't do any memory allocations on its own,
* with the difference being that the allocations for HuC usage are handled by
* the userspace driver instead of the kernel one. The HuC accesses the memory
* via the PPGTT belonging to the context loaded on the VCS executing the
* HuC-specific commands.
*/
/*
* MEI-GSC load is an async process. The probing of the exposed aux device
* (see intel_gsc.c) usually happens a few seconds after i915 probe, depending
* on when the kernel schedules it. Unless something goes terribly wrong, we're
* guaranteed for this to happen during boot, so the big timeout is a safety net
* that we never expect to need.
* MEI-PXP + HuC load usually takes ~300ms, but if the GSC needs to be resumed
* and/or reset, this can take longer. Note that the kernel might schedule
* other work between the i915 init/resume and the MEI one, which can add to
* the delay.
*/
#define GSC_INIT_TIMEOUT_MS 10000
#define PXP_INIT_TIMEOUT_MS 5000
static int sw_fence_dummy_notify(struct i915_sw_fence *sf,
enum i915_sw_fence_notify state)
{
return NOTIFY_DONE;
}
static void __delayed_huc_load_complete(struct intel_huc *huc)
{
if (!i915_sw_fence_done(&huc->delayed_load.fence))
i915_sw_fence_complete(&huc->delayed_load.fence);
}
static void delayed_huc_load_complete(struct intel_huc *huc)
{
hrtimer_cancel(&huc->delayed_load.timer);
__delayed_huc_load_complete(huc);
}
static void __gsc_init_error(struct intel_huc *huc)
{
huc->delayed_load.status = INTEL_HUC_DELAYED_LOAD_ERROR;
__delayed_huc_load_complete(huc);
}
static void gsc_init_error(struct intel_huc *huc)
{
hrtimer_cancel(&huc->delayed_load.timer);
__gsc_init_error(huc);
}
static void gsc_init_done(struct intel_huc *huc)
{
hrtimer_cancel(&huc->delayed_load.timer);
/* MEI-GSC init is done, now we wait for MEI-PXP to bind */
huc->delayed_load.status = INTEL_HUC_WAITING_ON_PXP;
if (!i915_sw_fence_done(&huc->delayed_load.fence))
hrtimer_start(&huc->delayed_load.timer,
ms_to_ktime(PXP_INIT_TIMEOUT_MS),
HRTIMER_MODE_REL);
}
static enum hrtimer_restart huc_delayed_load_timer_callback(struct hrtimer *hrtimer)
{
struct intel_huc *huc = container_of(hrtimer, struct intel_huc, delayed_load.timer);
if (!intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC)) {
if (huc->delayed_load.status == INTEL_HUC_WAITING_ON_GSC)
huc_notice(huc, "timed out waiting for MEI GSC\n");
else if (huc->delayed_load.status == INTEL_HUC_WAITING_ON_PXP)
huc_notice(huc, "timed out waiting for MEI PXP\n");
else
MISSING_CASE(huc->delayed_load.status);
__gsc_init_error(huc);
}
return HRTIMER_NORESTART;
}
static void huc_delayed_load_start(struct intel_huc *huc)
{
ktime_t delay;
GEM_BUG_ON(intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC));
/*
* On resume we don't have to wait for MEI-GSC to be re-probed, but we
* do need to wait for MEI-PXP to reset & re-bind
*/
switch (huc->delayed_load.status) {
case INTEL_HUC_WAITING_ON_GSC:
delay = ms_to_ktime(GSC_INIT_TIMEOUT_MS);
break;
case INTEL_HUC_WAITING_ON_PXP:
delay = ms_to_ktime(PXP_INIT_TIMEOUT_MS);
break;
default:
gsc_init_error(huc);
return;
}
/*
* This fence is always complete unless we're waiting for the
* GSC device to come up to load the HuC. We arm the fence here
* and complete it when we confirm that the HuC is loaded from
* the PXP bind callback.
*/
GEM_BUG_ON(!i915_sw_fence_done(&huc->delayed_load.fence));
i915_sw_fence_fini(&huc->delayed_load.fence);
i915_sw_fence_reinit(&huc->delayed_load.fence);
i915_sw_fence_await(&huc->delayed_load.fence);
i915_sw_fence_commit(&huc->delayed_load.fence);
hrtimer_start(&huc->delayed_load.timer, delay, HRTIMER_MODE_REL);
}
static int gsc_notifier(struct notifier_block *nb, unsigned long action, void *data)
{
struct device *dev = data;
struct intel_huc *huc = container_of(nb, struct intel_huc, delayed_load.nb);
struct intel_gsc_intf *intf = &huc_to_gt(huc)->gsc.intf[0];
if (!intf->adev || &intf->adev->aux_dev.dev != dev)
return 0;
switch (action) {
case BUS_NOTIFY_BOUND_DRIVER: /* mei driver bound to aux device */
gsc_init_done(huc);
break;
case BUS_NOTIFY_DRIVER_NOT_BOUND: /* mei driver fails to be bound */
case BUS_NOTIFY_UNBIND_DRIVER: /* mei driver about to be unbound */
huc_info(huc, "MEI driver not bound, disabling load\n");
gsc_init_error(huc);
break;
}
return 0;
}
void intel_huc_register_gsc_notifier(struct intel_huc *huc, const struct bus_type *bus)
{
int ret;
if (!intel_huc_is_loaded_by_gsc(huc))
return;
huc->delayed_load.nb.notifier_call = gsc_notifier;
ret = bus_register_notifier(bus, &huc->delayed_load.nb);
if (ret) {
huc_err(huc, "failed to register GSC notifier %pe\n", ERR_PTR(ret));
huc->delayed_load.nb.notifier_call = NULL;
gsc_init_error(huc);
}
}
void intel_huc_unregister_gsc_notifier(struct intel_huc *huc, const struct bus_type *bus)
{
if (!huc->delayed_load.nb.notifier_call)
return;
delayed_huc_load_complete(huc);
bus_unregister_notifier(bus, &huc->delayed_load.nb);
huc->delayed_load.nb.notifier_call = NULL;
}
static void delayed_huc_load_init(struct intel_huc *huc)
{
/*
* Initialize fence to be complete as this is expected to be complete
* unless there is a delayed HuC load in progress.
*/
i915_sw_fence_init(&huc->delayed_load.fence,
sw_fence_dummy_notify);
i915_sw_fence_commit(&huc->delayed_load.fence);
hrtimer_setup(&huc->delayed_load.timer, huc_delayed_load_timer_callback, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
}
static void delayed_huc_load_fini(struct intel_huc *huc)
{
/*
* the fence is initialized in init_early, so we need to clean it up
* even if HuC loading is off.
*/
delayed_huc_load_complete(huc);
i915_sw_fence_fini(&huc->delayed_load.fence);
}
int intel_huc_sanitize(struct intel_huc *huc)
{
delayed_huc_load_complete(huc);
intel_uc_fw_sanitize(&huc->fw);
return 0;
}
static bool vcs_supported(struct intel_gt *gt)
{
intel_engine_mask_t mask = gt->info.engine_mask;
/*
* We reach here from i915_driver_early_probe for the primary GT before
* its engine mask is set, so we use the device info engine mask for it;
* this means we're not taking VCS fusing into account, but if the
* primary GT supports VCS engines we expect at least one of them to
* remain unfused so we're fine.
* For other GTs we expect the GT-specific mask to be set before we
* call this function.
*/
GEM_BUG_ON(!gt_is_root(gt) && !gt->info.engine_mask);
if (gt_is_root(gt))
mask = INTEL_INFO(gt->i915)->platform_engine_mask;
else
mask = gt->info.engine_mask;
return __ENGINE_INSTANCES_MASK(mask, VCS0, I915_MAX_VCS);
}
void intel_huc_init_early(struct intel_huc *huc)
{
struct drm_i915_private *i915 = huc_to_gt(huc)->i915;
struct intel_gt *gt = huc_to_gt(huc);
intel_uc_fw_init_early(&huc->fw, INTEL_UC_FW_TYPE_HUC, true);
/*
* we always init the fence as already completed, even if HuC is not
* supported. This way we don't have to distinguish between HuC not
* supported/disabled or already loaded, and can focus on if the load
* is currently in progress (fence not complete) or not, which is what
* we care about for stalling userspace submissions.
*/
delayed_huc_load_init(huc);
if (!vcs_supported(gt)) {
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_NOT_SUPPORTED);
return;
}
if (GRAPHICS_VER(i915) >= 11) {
huc->status[INTEL_HUC_AUTH_BY_GUC].reg = GEN11_HUC_KERNEL_LOAD_INFO;
huc->status[INTEL_HUC_AUTH_BY_GUC].mask = HUC_LOAD_SUCCESSFUL;
huc->status[INTEL_HUC_AUTH_BY_GUC].value = HUC_LOAD_SUCCESSFUL;
} else {
huc->status[INTEL_HUC_AUTH_BY_GUC].reg = HUC_STATUS2;
huc->status[INTEL_HUC_AUTH_BY_GUC].mask = HUC_FW_VERIFIED;
huc->status[INTEL_HUC_AUTH_BY_GUC].value = HUC_FW_VERIFIED;
}
if (IS_DG2(i915)) {
huc->status[INTEL_HUC_AUTH_BY_GSC].reg = GEN11_HUC_KERNEL_LOAD_INFO;
huc->status[INTEL_HUC_AUTH_BY_GSC].mask = HUC_LOAD_SUCCESSFUL;
huc->status[INTEL_HUC_AUTH_BY_GSC].value = HUC_LOAD_SUCCESSFUL;
} else {
huc->status[INTEL_HUC_AUTH_BY_GSC].reg = HECI_FWSTS(MTL_GSC_HECI1_BASE, 5);
huc->status[INTEL_HUC_AUTH_BY_GSC].mask = HECI1_FWSTS5_HUC_AUTH_DONE;
huc->status[INTEL_HUC_AUTH_BY_GSC].value = HECI1_FWSTS5_HUC_AUTH_DONE;
}
}
#define HUC_LOAD_MODE_STRING(x) (x ? "GSC" : "legacy")
static int check_huc_loading_mode(struct intel_huc *huc)
{
struct intel_gt *gt = huc_to_gt(huc);
bool gsc_enabled = huc->fw.has_gsc_headers;
/*
* The fuse for HuC load via GSC is only valid on platforms that have
* GuC deprivilege.
*/
if (HAS_GUC_DEPRIVILEGE(gt->i915))
huc->loaded_via_gsc = intel_uncore_read(gt->uncore, GUC_SHIM_CONTROL2) &
GSC_LOADS_HUC;
if (huc->loaded_via_gsc && !gsc_enabled) {
huc_err(huc, "HW requires a GSC-enabled blob, but we found a legacy one\n");
return -ENOEXEC;
}
/*
* On newer platforms we have GSC-enabled binaries but we load the HuC
* via DMA. To do so we need to find the location of the legacy-style
* binary inside the GSC-enabled one, which we do at fetch time. Make
* sure that we were able to do so if the fuse says we need to load via
* DMA and the binary is GSC-enabled.
*/
if (!huc->loaded_via_gsc && gsc_enabled && !huc->fw.dma_start_offset) {
huc_err(huc, "HW in DMA mode, but we have an incompatible GSC-enabled blob\n");
return -ENOEXEC;
}
/*
* If the HuC is loaded via GSC, we need to be able to access the GSC.
* On DG2 this is done via the mei components, while on newer platforms
* it is done via the GSCCS,
*/
if (huc->loaded_via_gsc) {
if (IS_DG2(gt->i915)) {
if (!IS_ENABLED(CONFIG_INTEL_MEI_PXP) ||
!IS_ENABLED(CONFIG_INTEL_MEI_GSC)) {
huc_info(huc, "can't load due to missing mei modules\n");
return -EIO;
}
} else {
if (!HAS_ENGINE(gt, GSC0)) {
huc_info(huc, "can't load due to missing GSCCS\n");
return -EIO;
}
}
}
huc_dbg(huc, "loaded by GSC = %s\n", str_yes_no(huc->loaded_via_gsc));
return 0;
}
int intel_huc_init(struct intel_huc *huc)
{
struct intel_gt *gt = huc_to_gt(huc);
int err;
err = check_huc_loading_mode(huc);
if (err)
goto out;
if (HAS_ENGINE(gt, GSC0)) {
struct i915_vma *vma;
vma = intel_guc_allocate_vma(gt_to_guc(gt), PXP43_HUC_AUTH_INOUT_SIZE * 2);
if (IS_ERR(vma)) {
err = PTR_ERR(vma);
huc_info(huc, "Failed to allocate heci pkt\n");
goto out;
}
huc->heci_pkt = vma;
}
err = intel_uc_fw_init(&huc->fw);
if (err)
goto out_pkt;
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_LOADABLE);
return 0;
out_pkt:
if (huc->heci_pkt)
i915_vma_unpin_and_release(&huc->heci_pkt, 0);
out:
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_INIT_FAIL);
huc_info(huc, "initialization failed %pe\n", ERR_PTR(err));
return err;
}
void intel_huc_fini(struct intel_huc *huc)
{
/*
* the fence is initialized in init_early, so we need to clean it up
* even if HuC loading is off.
*/
delayed_huc_load_fini(huc);
if (huc->heci_pkt)
i915_vma_unpin_and_release(&huc->heci_pkt, 0);
if (intel_uc_fw_is_loadable(&huc->fw))
intel_uc_fw_fini(&huc->fw);
}
static const char *auth_mode_string(struct intel_huc *huc,
enum intel_huc_authentication_type type)
{
bool partial = huc->fw.has_gsc_headers && type == INTEL_HUC_AUTH_BY_GUC;
return partial ? "clear media" : "all workloads";
}
/*
* Use a longer timeout for debug builds so that problems can be detected
* and analysed. But a shorter timeout for releases so that user's don't
* wait forever to find out there is a problem. Note that the only reason
* an end user should hit the timeout is in case of extreme thermal throttling.
* And a system that is that hot during boot is probably dead anyway!
*/
#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)
#define HUC_LOAD_RETRY_LIMIT 20
#else
#define HUC_LOAD_RETRY_LIMIT 3
#endif
int intel_huc_wait_for_auth_complete(struct intel_huc *huc,
enum intel_huc_authentication_type type)
{
struct intel_gt *gt = huc_to_gt(huc);
struct intel_uncore *uncore = gt->uncore;
ktime_t before, after, delta;
int ret, count;
u64 delta_ms;
u32 before_freq;
/*
* The KMD requests maximum frequency during driver load, however thermal
* throttling can force the frequency down to minimum (although the board
* really should never get that hot in real life!). IFWI issues have been
* seen to cause sporadic failures to grant the higher frequency. And at
* minimum frequency, the authentication time can be in the seconds range.
* Note that there is a limit on how long an individual wait_for() can wait.
* So wrap it in a loop.
*/
before_freq = intel_rps_read_actual_frequency(&gt->rps);
before = ktime_get();
for (count = 0; count < HUC_LOAD_RETRY_LIMIT; count++) {
ret = __intel_wait_for_register(gt->uncore,
huc->status[type].reg,
huc->status[type].mask,
huc->status[type].value,
2, 1000, NULL);
if (!ret)
break;
huc_dbg(huc, "auth still in progress, count = %d, freq = %dMHz, status = 0x%08X\n",
count, intel_rps_read_actual_frequency(&gt->rps),
huc->status[type].reg.reg);
}
after = ktime_get();
delta = ktime_sub(after, before);
delta_ms = ktime_to_ms(delta);
if (delta_ms > 50) {
huc_warn(huc, "excessive auth time: %lldms! [status = 0x%08X, count = %d, ret = %d]\n",
delta_ms, huc->status[type].reg.reg, count, ret);
huc_warn(huc, "excessive auth time: [freq = %dMHz -> %dMHz vs %dMHz, perf_limit_reasons = 0x%08X]\n",
before_freq, intel_rps_read_actual_frequency(&gt->rps),
intel_rps_get_requested_frequency(&gt->rps),
intel_uncore_read(uncore, intel_gt_perf_limit_reasons_reg(gt)));
} else {
huc_dbg(huc, "auth took %lldms, freq = %dMHz -> %dMHz vs %dMHz, status = 0x%08X, count = %d, ret = %d\n",
delta_ms, before_freq, intel_rps_read_actual_frequency(&gt->rps),
intel_rps_get_requested_frequency(&gt->rps),
huc->status[type].reg.reg, count, ret);
}
/* mark the load process as complete even if the wait failed */
delayed_huc_load_complete(huc);
if (ret) {
huc_err(huc, "firmware not verified for %s: %pe\n",
auth_mode_string(huc, type), ERR_PTR(ret));
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_LOAD_FAIL);
return ret;
}
intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_RUNNING);
huc_info(huc, "authenticated for %s\n", auth_mode_string(huc, type));
return 0;
}
/**
* intel_huc_auth() - Authenticate HuC uCode
* @huc: intel_huc structure
* @type: authentication type (via GuC or via GSC)
*
* Called after HuC and GuC firmware loading during intel_uc_init_hw().
*
* This function invokes the GuC action to authenticate the HuC firmware,
* passing the offset of the RSA signature to intel_guc_auth_huc(). It then
* waits for up to 50ms for firmware verification ACK.
*/
int intel_huc_auth(struct intel_huc *huc, enum intel_huc_authentication_type type)
{
struct intel_gt *gt = huc_to_gt(huc);
struct intel_guc *guc = gt_to_guc(gt);
int ret;
if (!intel_uc_fw_is_loaded(&huc->fw))
return -ENOEXEC;
/* GSC will do the auth with the load */
if (intel_huc_is_loaded_by_gsc(huc))
return -ENODEV;
if (intel_huc_is_authenticated(huc, type))
return -EEXIST;
ret = i915_inject_probe_error(gt->i915, -ENXIO);
if (ret)
goto fail;
switch (type) {
case INTEL_HUC_AUTH_BY_GUC:
ret = intel_guc_auth_huc(guc, intel_guc_ggtt_offset(guc, huc->fw.rsa_data));
break;
case INTEL_HUC_AUTH_BY_GSC:
ret = intel_huc_fw_auth_via_gsccs(huc);
break;
default:
MISSING_CASE(type);
ret = -EINVAL;
}
if (ret)
goto fail;
/* Check authentication status, it should be done by now */
ret = intel_huc_wait_for_auth_complete(huc, type);
if (ret)
goto fail;
return 0;
fail:
huc_probe_error(huc, "%s authentication failed %pe\n",
auth_mode_string(huc, type), ERR_PTR(ret));
return ret;
}
bool intel_huc_is_authenticated(struct intel_huc *huc,
enum intel_huc_authentication_type type)
{
struct intel_gt *gt = huc_to_gt(huc);
intel_wakeref_t wakeref;
u32 status = 0;
with_intel_runtime_pm(gt->uncore->rpm, wakeref)
status = intel_uncore_read(gt->uncore, huc->status[type].reg);
return (status & huc->status[type].mask) == huc->status[type].value;
}
static bool huc_is_fully_authenticated(struct intel_huc *huc)
{
struct intel_uc_fw *huc_fw = &huc->fw;
if (!huc_fw->has_gsc_headers)
return intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GUC);
else if (intel_huc_is_loaded_by_gsc(huc) || HAS_ENGINE(huc_to_gt(huc), GSC0))
return intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC);
else
return false;
}
/**
* intel_huc_check_status() - check HuC status
* @huc: intel_huc structure
*
* This function reads status register to verify if HuC
* firmware was successfully loaded.
*
* The return values match what is expected for the I915_PARAM_HUC_STATUS
* getparam.
*/
int intel_huc_check_status(struct intel_huc *huc)
{
struct intel_uc_fw *huc_fw = &huc->fw;
switch (__intel_uc_fw_status(huc_fw)) {
case INTEL_UC_FIRMWARE_NOT_SUPPORTED:
return -ENODEV;
case INTEL_UC_FIRMWARE_DISABLED:
return -EOPNOTSUPP;
case INTEL_UC_FIRMWARE_MISSING:
return -ENOPKG;
case INTEL_UC_FIRMWARE_ERROR:
return -ENOEXEC;
case INTEL_UC_FIRMWARE_INIT_FAIL:
return -ENOMEM;
case INTEL_UC_FIRMWARE_LOAD_FAIL:
return -EIO;
default:
break;
}
/*
* GSC-enabled binaries loaded via DMA are first partially
* authenticated by GuC and then fully authenticated by GSC
*/
if (huc_is_fully_authenticated(huc))
return 1; /* full auth */
else if (huc_fw->has_gsc_headers && !intel_huc_is_loaded_by_gsc(huc) &&
intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GUC))
return 2; /* clear media only */
else
return 0;
}
static bool huc_has_delayed_load(struct intel_huc *huc)
{
return intel_huc_is_loaded_by_gsc(huc) &&
(huc->delayed_load.status != INTEL_HUC_DELAYED_LOAD_ERROR);
}
void intel_huc_update_auth_status(struct intel_huc *huc)
{
if (!intel_uc_fw_is_loadable(&huc->fw))
return;
if (!huc->fw.has_gsc_headers)
return;
if (huc_is_fully_authenticated(huc))
intel_uc_fw_change_status(&huc->fw,
INTEL_UC_FIRMWARE_RUNNING);
else if (huc_has_delayed_load(huc))
huc_delayed_load_start(huc);
}
/**
* intel_huc_load_status - dump information about HuC load status
* @huc: the HuC
* @p: the &drm_printer
*
* Pretty printer for HuC load status.
*/
void intel_huc_load_status(struct intel_huc *huc, struct drm_printer *p)
{
struct intel_gt *gt = huc_to_gt(huc);
intel_wakeref_t wakeref;
if (!intel_huc_is_supported(huc)) {
drm_printf(p, "HuC not supported\n");
return;
}
if (!intel_huc_is_wanted(huc)) {
drm_printf(p, "HuC disabled\n");
return;
}
intel_uc_fw_dump(&huc->fw, p);
with_intel_runtime_pm(gt->uncore->rpm, wakeref)
drm_printf(p, "HuC status: 0x%08x\n",
intel_uncore_read(gt->uncore, huc->status[INTEL_HUC_AUTH_BY_GUC].reg));
}