| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2022 Intel Corporation |
| */ |
| |
| #include "xe_huc.h" |
| |
| #include <drm/drm_managed.h> |
| |
| #include "abi/gsc_pxp_commands_abi.h" |
| #include "regs/xe_gsc_regs.h" |
| #include "regs/xe_guc_regs.h" |
| #include "xe_assert.h" |
| #include "xe_bo.h" |
| #include "xe_device.h" |
| #include "xe_force_wake.h" |
| #include "xe_gsc_submit.h" |
| #include "xe_gt.h" |
| #include "xe_guc.h" |
| #include "xe_map.h" |
| #include "xe_mmio.h" |
| #include "xe_uc_fw.h" |
| |
| static struct xe_gt * |
| huc_to_gt(struct xe_huc *huc) |
| { |
| return container_of(huc, struct xe_gt, uc.huc); |
| } |
| |
| static struct xe_device * |
| huc_to_xe(struct xe_huc *huc) |
| { |
| return gt_to_xe(huc_to_gt(huc)); |
| } |
| |
| static struct xe_guc * |
| huc_to_guc(struct xe_huc *huc) |
| { |
| return &container_of(huc, struct xe_uc, huc)->guc; |
| } |
| |
| static void free_gsc_pkt(struct drm_device *drm, void *arg) |
| { |
| struct xe_huc *huc = arg; |
| |
| xe_bo_unpin_map_no_vm(huc->gsc_pkt); |
| huc->gsc_pkt = NULL; |
| } |
| |
| #define PXP43_HUC_AUTH_INOUT_SIZE SZ_4K |
| static int huc_alloc_gsc_pkt(struct xe_huc *huc) |
| { |
| struct xe_gt *gt = huc_to_gt(huc); |
| struct xe_device *xe = gt_to_xe(gt); |
| struct xe_bo *bo; |
| int err; |
| |
| /* we use a single object for both input and output */ |
| bo = xe_bo_create_pin_map(xe, gt_to_tile(gt), NULL, |
| PXP43_HUC_AUTH_INOUT_SIZE * 2, |
| ttm_bo_type_kernel, |
| XE_BO_CREATE_SYSTEM_BIT | |
| XE_BO_CREATE_GGTT_BIT); |
| if (IS_ERR(bo)) |
| return PTR_ERR(bo); |
| |
| huc->gsc_pkt = bo; |
| |
| err = drmm_add_action_or_reset(&xe->drm, free_gsc_pkt, huc); |
| if (err) { |
| free_gsc_pkt(&xe->drm, huc); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int xe_huc_init(struct xe_huc *huc) |
| { |
| struct xe_gt *gt = huc_to_gt(huc); |
| struct xe_tile *tile = gt_to_tile(gt); |
| struct xe_device *xe = gt_to_xe(gt); |
| int ret; |
| |
| huc->fw.type = XE_UC_FW_TYPE_HUC; |
| |
| /* On platforms with a media GT the HuC is only available there */ |
| if (tile->media_gt && (gt != tile->media_gt)) { |
| xe_uc_fw_change_status(&huc->fw, XE_UC_FIRMWARE_NOT_SUPPORTED); |
| return 0; |
| } |
| |
| ret = xe_uc_fw_init(&huc->fw); |
| if (ret) |
| goto out; |
| |
| if (!xe_uc_fw_is_enabled(&huc->fw)) |
| return 0; |
| |
| if (huc->fw.has_gsc_headers) { |
| ret = huc_alloc_gsc_pkt(huc); |
| if (ret) |
| goto out; |
| } |
| |
| xe_uc_fw_change_status(&huc->fw, XE_UC_FIRMWARE_LOADABLE); |
| |
| return 0; |
| |
| out: |
| drm_err(&xe->drm, "HuC init failed with %d", ret); |
| return ret; |
| } |
| |
| int xe_huc_init_post_hwconfig(struct xe_huc *huc) |
| { |
| struct xe_tile *tile = gt_to_tile(huc_to_gt(huc)); |
| struct xe_device *xe = huc_to_xe(huc); |
| int ret; |
| |
| if (!IS_DGFX(huc_to_xe(huc))) |
| return 0; |
| |
| if (!xe_uc_fw_is_loadable(&huc->fw)) |
| return 0; |
| |
| ret = xe_managed_bo_reinit_in_vram(xe, tile, &huc->fw.bo); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| int xe_huc_upload(struct xe_huc *huc) |
| { |
| if (!xe_uc_fw_is_loadable(&huc->fw)) |
| return 0; |
| return xe_uc_fw_upload(&huc->fw, 0, HUC_UKERNEL); |
| } |
| |
| #define huc_auth_msg_wr(xe_, map_, offset_, field_, val_) \ |
| xe_map_wr_field(xe_, map_, offset_, struct pxp43_new_huc_auth_in, field_, val_) |
| #define huc_auth_msg_rd(xe_, map_, offset_, field_) \ |
| xe_map_rd_field(xe_, map_, offset_, struct pxp43_huc_auth_out, field_) |
| |
| static u32 huc_emit_pxp_auth_msg(struct xe_device *xe, struct iosys_map *map, |
| u32 wr_offset, u32 huc_offset, u32 huc_size) |
| { |
| xe_map_memset(xe, map, wr_offset, 0, sizeof(struct pxp43_new_huc_auth_in)); |
| |
| huc_auth_msg_wr(xe, map, wr_offset, header.api_version, PXP_APIVER(4, 3)); |
| huc_auth_msg_wr(xe, map, wr_offset, header.command_id, PXP43_CMDID_NEW_HUC_AUTH); |
| huc_auth_msg_wr(xe, map, wr_offset, header.status, 0); |
| huc_auth_msg_wr(xe, map, wr_offset, header.buffer_len, |
| sizeof(struct pxp43_new_huc_auth_in) - sizeof(struct pxp_cmd_header)); |
| huc_auth_msg_wr(xe, map, wr_offset, huc_base_address, huc_offset); |
| huc_auth_msg_wr(xe, map, wr_offset, huc_size, huc_size); |
| |
| return wr_offset + sizeof(struct pxp43_new_huc_auth_in); |
| } |
| |
| static int huc_auth_via_gsccs(struct xe_huc *huc) |
| { |
| struct xe_gt *gt = huc_to_gt(huc); |
| struct xe_device *xe = gt_to_xe(gt); |
| struct xe_bo *pkt = huc->gsc_pkt; |
| u32 wr_offset; |
| u32 rd_offset; |
| u64 ggtt_offset; |
| u32 out_status; |
| int retry = 5; |
| int err = 0; |
| |
| if (!pkt) |
| return -ENODEV; |
| |
| ggtt_offset = xe_bo_ggtt_addr(pkt); |
| |
| wr_offset = xe_gsc_emit_header(xe, &pkt->vmap, 0, HECI_MEADDRESS_PXP, 0, |
| sizeof(struct pxp43_new_huc_auth_in)); |
| wr_offset = huc_emit_pxp_auth_msg(xe, &pkt->vmap, wr_offset, |
| xe_bo_ggtt_addr(huc->fw.bo), |
| huc->fw.bo->size); |
| do { |
| err = xe_gsc_pkt_submit_kernel(>->uc.gsc, ggtt_offset, wr_offset, |
| ggtt_offset + PXP43_HUC_AUTH_INOUT_SIZE, |
| PXP43_HUC_AUTH_INOUT_SIZE); |
| if (err) |
| break; |
| |
| if (xe_gsc_check_and_update_pending(xe, &pkt->vmap, 0, &pkt->vmap, |
| PXP43_HUC_AUTH_INOUT_SIZE)) { |
| err = -EBUSY; |
| msleep(50); |
| } |
| } while (--retry && err == -EBUSY); |
| |
| if (err) { |
| drm_err(&xe->drm, "failed to submit GSC request to auth: %d\n", err); |
| return err; |
| } |
| |
| err = xe_gsc_read_out_header(xe, &pkt->vmap, PXP43_HUC_AUTH_INOUT_SIZE, |
| sizeof(struct pxp43_huc_auth_out), &rd_offset); |
| if (err) { |
| drm_err(&xe->drm, "HuC: invalid GSC reply for auth (err=%d)\n", err); |
| return err; |
| } |
| |
| /* |
| * The GSC will return PXP_STATUS_OP_NOT_PERMITTED if the HuC is already |
| * authenticated. If the same error is ever returned with HuC not loaded |
| * we'll still catch it when we check the authentication bit later. |
| */ |
| out_status = huc_auth_msg_rd(xe, &pkt->vmap, rd_offset, header.status); |
| if (out_status != PXP_STATUS_SUCCESS && out_status != PXP_STATUS_OP_NOT_PERMITTED) { |
| drm_err(&xe->drm, "auth failed with GSC error = 0x%x\n", out_status); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static const struct { |
| const char *name; |
| struct xe_reg reg; |
| u32 val; |
| } huc_auth_modes[XE_HUC_AUTH_TYPES_COUNT] = { |
| [XE_HUC_AUTH_VIA_GUC] = { "GuC", |
| HUC_KERNEL_LOAD_INFO, |
| HUC_LOAD_SUCCESSFUL }, |
| [XE_HUC_AUTH_VIA_GSC] = { "GSC", |
| HECI_FWSTS5(MTL_GSC_HECI1_BASE), |
| HECI1_FWSTS5_HUC_AUTH_DONE }, |
| }; |
| |
| bool xe_huc_is_authenticated(struct xe_huc *huc, enum xe_huc_auth_types type) |
| { |
| struct xe_gt *gt = huc_to_gt(huc); |
| |
| return xe_mmio_read32(gt, huc_auth_modes[type].reg) & huc_auth_modes[type].val; |
| } |
| |
| int xe_huc_auth(struct xe_huc *huc, enum xe_huc_auth_types type) |
| { |
| struct xe_device *xe = huc_to_xe(huc); |
| struct xe_gt *gt = huc_to_gt(huc); |
| struct xe_guc *guc = huc_to_guc(huc); |
| int ret; |
| |
| if (!xe_uc_fw_is_loadable(&huc->fw)) |
| return 0; |
| |
| /* On newer platforms the HuC survives reset, so no need to re-auth */ |
| if (xe_huc_is_authenticated(huc, type)) { |
| xe_uc_fw_change_status(&huc->fw, XE_UC_FIRMWARE_RUNNING); |
| return 0; |
| } |
| |
| if (!xe_uc_fw_is_loaded(&huc->fw)) |
| return -ENOEXEC; |
| |
| switch (type) { |
| case XE_HUC_AUTH_VIA_GUC: |
| ret = xe_guc_auth_huc(guc, xe_bo_ggtt_addr(huc->fw.bo) + |
| xe_uc_fw_rsa_offset(&huc->fw)); |
| break; |
| case XE_HUC_AUTH_VIA_GSC: |
| ret = huc_auth_via_gsccs(huc); |
| break; |
| default: |
| XE_WARN_ON(type); |
| return -EINVAL; |
| } |
| if (ret) { |
| drm_err(&xe->drm, "Failed to trigger HuC auth via %s: %d\n", |
| huc_auth_modes[type].name, ret); |
| goto fail; |
| } |
| |
| ret = xe_mmio_wait32(gt, huc_auth_modes[type].reg, huc_auth_modes[type].val, |
| huc_auth_modes[type].val, 100000, NULL, false); |
| if (ret) { |
| drm_err(&xe->drm, "HuC: Firmware not verified %d\n", ret); |
| goto fail; |
| } |
| |
| xe_uc_fw_change_status(&huc->fw, XE_UC_FIRMWARE_RUNNING); |
| drm_dbg(&xe->drm, "HuC authenticated via %s\n", huc_auth_modes[type].name); |
| |
| return 0; |
| |
| fail: |
| drm_err(&xe->drm, "HuC: Auth via %s failed: %d\n", |
| huc_auth_modes[type].name, ret); |
| xe_uc_fw_change_status(&huc->fw, XE_UC_FIRMWARE_LOAD_FAIL); |
| |
| return ret; |
| } |
| |
| void xe_huc_sanitize(struct xe_huc *huc) |
| { |
| if (!xe_uc_fw_is_loadable(&huc->fw)) |
| return; |
| xe_uc_fw_change_status(&huc->fw, XE_UC_FIRMWARE_LOADABLE); |
| } |
| |
| void xe_huc_print_info(struct xe_huc *huc, struct drm_printer *p) |
| { |
| struct xe_gt *gt = huc_to_gt(huc); |
| int err; |
| |
| xe_uc_fw_print(&huc->fw, p); |
| |
| if (!xe_uc_fw_is_enabled(&huc->fw)) |
| return; |
| |
| err = xe_force_wake_get(gt_to_fw(gt), XE_FW_GT); |
| if (err) |
| return; |
| |
| drm_printf(p, "\nHuC status: 0x%08x\n", |
| xe_mmio_read32(gt, HUC_KERNEL_LOAD_INFO)); |
| |
| xe_force_wake_put(gt_to_fw(gt), XE_FW_GT); |
| } |