1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright © 2014-2019 Intel Corporation
4  */
5 
6 #include "gt/intel_gsc.h"
7 #include "gt/intel_gt.h"
8 #include "intel_gsc_binary_headers.h"
9 #include "intel_gsc_uc_heci_cmd_submit.h"
10 #include "intel_huc.h"
11 #include "intel_huc_fw.h"
12 #include "intel_huc_print.h"
13 #include "i915_drv.h"
14 #include "pxp/intel_pxp_huc.h"
15 #include "pxp/intel_pxp_cmd_interface_43.h"
16 
17 struct mtl_huc_auth_msg_in {
18 	struct intel_gsc_mtl_header header;
19 	struct pxp43_new_huc_auth_in huc_in;
20 } __packed;
21 
22 struct mtl_huc_auth_msg_out {
23 	struct intel_gsc_mtl_header header;
24 	struct pxp43_huc_auth_out huc_out;
25 } __packed;
26 
27 int intel_huc_fw_auth_via_gsccs(struct intel_huc *huc)
28 {
29 	struct intel_gt *gt = huc_to_gt(huc);
30 	struct drm_i915_private *i915 = gt->i915;
31 	struct drm_i915_gem_object *obj;
32 	struct mtl_huc_auth_msg_in *msg_in;
33 	struct mtl_huc_auth_msg_out *msg_out;
34 	void *pkt_vaddr;
35 	u64 pkt_offset;
36 	int retry = 5;
37 	int err = 0;
38 
39 	if (!huc->heci_pkt)
40 		return -ENODEV;
41 
42 	obj = huc->heci_pkt->obj;
43 	pkt_offset = i915_ggtt_offset(huc->heci_pkt);
44 
45 	pkt_vaddr = i915_gem_object_pin_map_unlocked(obj,
46 						     i915_coherent_map_type(i915, obj, true));
47 	if (IS_ERR(pkt_vaddr))
48 		return PTR_ERR(pkt_vaddr);
49 
50 	msg_in = pkt_vaddr;
51 	msg_out = pkt_vaddr + PXP43_HUC_AUTH_INOUT_SIZE;
52 
53 	intel_gsc_uc_heci_cmd_emit_mtl_header(&msg_in->header,
54 					      HECI_MEADDRESS_PXP,
55 					      sizeof(*msg_in), 0);
56 
57 	msg_in->huc_in.header.api_version = PXP_APIVER(4, 3);
58 	msg_in->huc_in.header.command_id = PXP43_CMDID_NEW_HUC_AUTH;
59 	msg_in->huc_in.header.status = 0;
60 	msg_in->huc_in.header.buffer_len = sizeof(msg_in->huc_in) -
61 					   sizeof(msg_in->huc_in.header);
62 	msg_in->huc_in.huc_base_address = huc->fw.vma_res.start;
63 	msg_in->huc_in.huc_size = huc->fw.obj->base.size;
64 
65 	do {
66 		err = intel_gsc_uc_heci_cmd_submit_packet(&gt->uc.gsc,
67 							  pkt_offset, sizeof(*msg_in),
68 							  pkt_offset + PXP43_HUC_AUTH_INOUT_SIZE,
69 							  PXP43_HUC_AUTH_INOUT_SIZE);
70 		if (err) {
71 			huc_err(huc, "failed to submit GSC request to auth: %d\n", err);
72 			goto out_unpin;
73 		}
74 
75 		if (msg_out->header.flags & GSC_OUTFLAG_MSG_PENDING) {
76 			msg_in->header.gsc_message_handle = msg_out->header.gsc_message_handle;
77 			err = -EBUSY;
78 			msleep(50);
79 		}
80 	} while (--retry && err == -EBUSY);
81 
82 	if (err)
83 		goto out_unpin;
84 
85 	if (msg_out->header.message_size != sizeof(*msg_out)) {
86 		huc_err(huc, "invalid GSC reply length %u [expected %zu]\n",
87 			msg_out->header.message_size, sizeof(*msg_out));
88 		err = -EPROTO;
89 		goto out_unpin;
90 	}
91 
92 	/*
93 	 * The GSC will return PXP_STATUS_OP_NOT_PERMITTED if the HuC is already
94 	 * loaded. If the same error is ever returned with HuC not loaded we'll
95 	 * still catch it when we check the authentication bit later.
96 	 */
97 	if (msg_out->huc_out.header.status != PXP_STATUS_SUCCESS &&
98 	    msg_out->huc_out.header.status != PXP_STATUS_OP_NOT_PERMITTED) {
99 		huc_err(huc, "auth failed with GSC error = 0x%x\n",
100 			msg_out->huc_out.header.status);
101 		err = -EIO;
102 		goto out_unpin;
103 	}
104 
105 out_unpin:
106 	i915_gem_object_unpin_map(obj);
107 	return err;
108 }
109 
110 static void get_version_from_gsc_manifest(struct intel_uc_fw_ver *ver, const void *data)
111 {
112 	const struct intel_gsc_manifest_header *manifest = data;
113 
114 	ver->major = manifest->fw_version.major;
115 	ver->minor = manifest->fw_version.minor;
116 	ver->patch = manifest->fw_version.hotfix;
117 }
118 
119 static bool css_valid(const void *data, size_t size)
120 {
121 	const struct uc_css_header *css = data;
122 
123 	if (unlikely(size < sizeof(struct uc_css_header)))
124 		return false;
125 
126 	if (css->module_type != 0x6)
127 		return false;
128 
129 	if (css->module_vendor != PCI_VENDOR_ID_INTEL)
130 		return false;
131 
132 	return true;
133 }
134 
135 static inline u32 entry_offset(const struct intel_gsc_cpd_entry *entry)
136 {
137 	return entry->offset & INTEL_GSC_CPD_ENTRY_OFFSET_MASK;
138 }
139 
140 int intel_huc_fw_get_binary_info(struct intel_uc_fw *huc_fw, const void *data, size_t size)
141 {
142 	struct intel_huc *huc = container_of(huc_fw, struct intel_huc, fw);
143 	const struct intel_gsc_cpd_header_v2 *header = data;
144 	const struct intel_gsc_cpd_entry *entry;
145 	size_t min_size = sizeof(*header);
146 	int i;
147 
148 	if (!huc_fw->has_gsc_headers) {
149 		huc_err(huc, "Invalid FW type for GSC header parsing!\n");
150 		return -EINVAL;
151 	}
152 
153 	if (size < sizeof(*header)) {
154 		huc_err(huc, "FW too small! %zu < %zu\n", size, min_size);
155 		return -ENODATA;
156 	}
157 
158 	/*
159 	 * The GSC-enabled HuC binary starts with a directory header, followed
160 	 * by a series of entries. Each entry is identified by a name and
161 	 * points to a specific section of the binary containing the relevant
162 	 * data. The entries we're interested in are:
163 	 * - "HUCP.man": points to the GSC manifest header for the HuC, which
164 	 *               contains the version info.
165 	 * - "huc_fw": points to the legacy-style binary that can be used for
166 	 *             load via the DMA. This entry only contains a valid CSS
167 	 *             on binaries for platforms that support 2-step HuC load
168 	 *             via dma and auth via GSC (like MTL).
169 	 *
170 	 * --------------------------------------------------
171 	 * [  intel_gsc_cpd_header_v2                       ]
172 	 * --------------------------------------------------
173 	 * [  intel_gsc_cpd_entry[]                         ]
174 	 * [      entry1                                    ]
175 	 * [      ...                                       ]
176 	 * [      entryX                                    ]
177 	 * [          "HUCP.man"                            ]
178 	 * [           ...                                  ]
179 	 * [           offset  >----------------------------]------o
180 	 * [      ...                                       ]      |
181 	 * [      entryY                                    ]      |
182 	 * [          "huc_fw"                              ]      |
183 	 * [           ...                                  ]      |
184 	 * [           offset  >----------------------------]----------o
185 	 * --------------------------------------------------      |   |
186 	 *                                                         |   |
187 	 * --------------------------------------------------      |   |
188 	 * [ intel_gsc_manifest_header                      ]<-----o   |
189 	 * [  ...                                           ]          |
190 	 * [  intel_gsc_version fw_version                  ]          |
191 	 * [  ...                                           ]          |
192 	 * --------------------------------------------------          |
193 	 *                                                             |
194 	 * --------------------------------------------------          |
195 	 * [ data[]                                         ]<---------o
196 	 * [  ...                                           ]
197 	 * [  ...                                           ]
198 	 * --------------------------------------------------
199 	 */
200 
201 	if (header->header_marker != INTEL_GSC_CPD_HEADER_MARKER) {
202 		huc_err(huc, "invalid marker for CPD header: 0x%08x!\n",
203 			header->header_marker);
204 		return -EINVAL;
205 	}
206 
207 	/* we only have binaries with header v2 and entry v1 for now */
208 	if (header->header_version != 2 || header->entry_version != 1) {
209 		huc_err(huc, "invalid CPD header/entry version %u:%u!\n",
210 			header->header_version, header->entry_version);
211 		return -EINVAL;
212 	}
213 
214 	if (header->header_length < sizeof(struct intel_gsc_cpd_header_v2)) {
215 		huc_err(huc, "invalid CPD header length %u!\n",
216 			header->header_length);
217 		return -EINVAL;
218 	}
219 
220 	min_size = header->header_length + sizeof(*entry) * header->num_of_entries;
221 	if (size < min_size) {
222 		huc_err(huc, "FW too small! %zu < %zu\n", size, min_size);
223 		return -ENODATA;
224 	}
225 
226 	entry = data + header->header_length;
227 
228 	for (i = 0; i < header->num_of_entries; i++, entry++) {
229 		if (strcmp(entry->name, "HUCP.man") == 0)
230 			get_version_from_gsc_manifest(&huc_fw->file_selected.ver,
231 						      data + entry_offset(entry));
232 
233 		if (strcmp(entry->name, "huc_fw") == 0) {
234 			u32 offset = entry_offset(entry);
235 
236 			if (offset < size && css_valid(data + offset, size - offset))
237 				huc_fw->dma_start_offset = offset;
238 		}
239 	}
240 
241 	return 0;
242 }
243 
244 int intel_huc_fw_load_and_auth_via_gsc(struct intel_huc *huc)
245 {
246 	int ret;
247 
248 	if (!intel_huc_is_loaded_by_gsc(huc))
249 		return -ENODEV;
250 
251 	if (!intel_uc_fw_is_loadable(&huc->fw))
252 		return -ENOEXEC;
253 
254 	/*
255 	 * If we abort a suspend, HuC might still be loaded when the mei
256 	 * component gets re-bound and this function called again. If so, just
257 	 * mark the HuC as loaded.
258 	 */
259 	if (intel_huc_is_authenticated(huc, INTEL_HUC_AUTH_BY_GSC)) {
260 		intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_RUNNING);
261 		return 0;
262 	}
263 
264 	GEM_WARN_ON(intel_uc_fw_is_loaded(&huc->fw));
265 
266 	ret = intel_pxp_huc_load_and_auth(huc_to_gt(huc)->i915->pxp);
267 	if (ret)
268 		return ret;
269 
270 	intel_uc_fw_change_status(&huc->fw, INTEL_UC_FIRMWARE_TRANSFERRED);
271 
272 	return intel_huc_wait_for_auth_complete(huc, INTEL_HUC_AUTH_BY_GSC);
273 }
274 
275 /**
276  * intel_huc_fw_upload() - load HuC uCode to device via DMA transfer
277  * @huc: intel_huc structure
278  *
279  * Called from intel_uc_init_hw() during driver load, resume from sleep and
280  * after a GPU reset. Note that HuC must be loaded before GuC.
281  *
282  * The firmware image should have already been fetched into memory, so only
283  * check that fetch succeeded, and then transfer the image to the h/w.
284  *
285  * Return:	non-zero code on error
286  */
287 int intel_huc_fw_upload(struct intel_huc *huc)
288 {
289 	if (intel_huc_is_loaded_by_gsc(huc))
290 		return -ENODEV;
291 
292 	/* HW doesn't look at destination address for HuC, so set it to 0 */
293 	return intel_uc_fw_upload(&huc->fw, 0, HUC_UKERNEL);
294 }
295