summaryrefslogtreecommitdiff
path: root/drivers/media/platform/qcom/iris/iris_firmware.c
blob: 21f57d37da18791e0e8dc61140c5e5eded907005 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include <linux/firmware.h>
#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/soc/qcom/mdt_loader.h>

#include "iris_core.h"
#include "iris_firmware.h"

#define IRIS_PAS_ID				9

/* Detect Gen2 firmware by scanning the blob for:
 *   QC_IMAGE_VERSION_STRING=<version>
 * and then checking:
 *   - version starts with "vfw", OR
 *   - version matches "video-firmware.N.M" with N >= 2
 */

static bool iris_detect_gen2_from_fwdata(const u8 *data, size_t size)
{
	static const char *marker = "QC_IMAGE_VERSION_STRING=";
	const size_t mlen = strlen(marker);
	static const char *vfw = "vfw";
	const size_t vfwlen = strlen(vfw);
	static const char *vf = "video-firmware.";
	const size_t vflen = strlen(vf);

	for (size_t i = 0; i + mlen < size; i++) {
		const char *found;

		if (memcmp(data + i, marker, mlen))
			continue;

		found = data + i + mlen;
		size -= i + mlen;

		/* vfw => Gen2 */
		if (size > vfwlen && !memcmp(found, vfw, vfwlen))
			return true;

		if (size < vflen ||
		    memcmp(found, vf, vflen))
			return false;

		found += vflen;
		size -= vflen;

		/*
		 * video-firmware.1.x is Gen1.
		 * video-firmware.2.x and video-firmware.10.x are Gen2.
		 */
		return size >= 2 &&
			(*found >= '2' || (*found == '1' && found[1] != '.'));
	}

	return false;
}

/*
 * Load the firmware image and return the descriptor that was used to pick the
 * file. On a platform that provides only one generation, or when no DT
 * firmware-name override is present, the returned descriptor is final. With a
 * DT override on a dual-generation platform the returned descriptor is only a
 * default; iris_detect_firmware() inspects the loaded image to confirm it.
 */
static const struct firmware *iris_load_firmware(struct iris_core *core,
						 const char **fw_name,
						 const struct iris_firmware_desc **fw_desc)
{
	const struct iris_firmware_desc *desc;
	const struct firmware *firmware;
	bool has_both_gens;
	int ret;

	*fw_name = NULL;
	ret = of_property_read_string_index(dev_of_node(core->dev), "firmware-name", 0, fw_name);

	/*
	 * A platform may support both Gen1 and Gen2 firmware; which one is used
	 * depends on the firmware image installed on the system, not on the
	 * hardware. That installed image does not change while the device is
	 * bound, so the generation is detected only once and the chosen
	 * descriptor is reused on later core bring-ups (e.g. after a system
	 * error recovery).
	 */
	if (core->iris_firmware_desc) {
		if (ret)
			*fw_name = core->iris_firmware_desc->fwname;
		ret = request_firmware(&firmware, *fw_name, core->dev);
		if (ret)
			return ERR_PTR(ret);
		*fw_desc = core->iris_firmware_desc;
		return firmware;
	}

	has_both_gens = core->iris_platform_data->firmware_desc_gen2 &&
		core->iris_platform_data->firmware_desc_gen1;

	if (core->iris_platform_data->firmware_desc_gen2)
		desc = core->iris_platform_data->firmware_desc_gen2;
	else if (core->iris_platform_data->firmware_desc_gen1)
		desc = core->iris_platform_data->firmware_desc_gen1;
	else
		return ERR_PTR(-EINVAL);

	if (ret) {
		/* No firmware-name in DT: select by probing Gen2 then Gen1. */
		*fw_name = desc->fwname;
		if (has_both_gens)
			ret = firmware_request_nowarn(&firmware, *fw_name, core->dev);
		else
			ret = request_firmware(&firmware, *fw_name, core->dev);
		if (ret && has_both_gens) {
			desc = core->iris_platform_data->firmware_desc_gen1;
			*fw_name = desc->fwname;
			ret = request_firmware(&firmware, *fw_name, core->dev);
		}
	} else {
		/* firmware-name given: iris_detect_firmware() picks the gen. */
		ret = request_firmware(&firmware, *fw_name, core->dev);
	}
	if (ret)
		return ERR_PTR(ret);

	*fw_desc = desc;
	return firmware;
}

/*
 * Detect the firmware generation and publish the descriptor. Run only after
 * qcom_mdt_load() has succeeded, so the driver commits to a HFI generation
 * only for a firmware image that has actually been loaded.
 *
 * The generation is detected from the loaded image (@data / @size point at the
 * reserved memory region populated by qcom_mdt_load()) rather than from the
 * request_firmware() blob: for a split .mdt the latter holds only the ELF
 * headers, while QC_IMAGE_VERSION_STRING lives in the .bNN data segments.
 *
 * The descriptor and firmware data are published exactly once, before any
 * session exists, so the lockless readers in the ioctl paths never observe a
 * reassignment. Later bring-ups reuse the already published descriptor.
 */
static void iris_detect_firmware(struct iris_core *core, const char *fw_name,
				 const u8 *data, size_t size,
				 const struct iris_firmware_desc *desc)
{
	if (core->iris_firmware_desc)
		return;

	/*
	 * With a DT firmware-name override on a dual-generation platform the
	 * image on disk decides the generation, so inspect it and switch to the
	 * Gen1 descriptor when a Gen1 image was loaded.
	 */
	if (desc == core->iris_platform_data->firmware_desc_gen2 &&
	    core->iris_platform_data->firmware_desc_gen1 &&
	    of_property_present(dev_of_node(core->dev), "firmware-name") &&
	    !iris_detect_gen2_from_fwdata(data, size)) {
		dev_info(core->dev, "Gen1 FW detected in %s\n", fw_name);
		desc = core->iris_platform_data->firmware_desc_gen1;
	}

	/* Publish iris_firmware_data first, then iris_firmware_desc (the guard). */
	core->iris_firmware_data = desc->firmware_data;
	core->iris_firmware_desc = desc;
}

static int iris_load_fw_to_memory(struct iris_core *core)
{
	const struct iris_firmware_desc *desc = NULL;
	const struct firmware *firmware = NULL;
	struct device *dev = core->dev;
	struct resource res;
	phys_addr_t mem_phys;
	const char *fw_name;
	size_t res_size;
	ssize_t fw_size;
	void *mem_virt;
	int ret;

	ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
	if (ret)
		return ret;

	mem_phys = res.start;
	res_size = resource_size(&res);

	firmware = iris_load_firmware(core, &fw_name, &desc);
	if (IS_ERR(firmware))
		return PTR_ERR(firmware);

	fw_size = qcom_mdt_get_size(firmware);
	if (fw_size < 0 || res_size < (size_t)fw_size) {
		ret = -EINVAL;
		goto err_release_fw;
	}

	mem_virt = memremap(mem_phys, res_size, MEMREMAP_WC);
	if (!mem_virt) {
		ret = -ENOMEM;
		goto err_release_fw;
	}

	memset(mem_virt, 0, res_size);

	ret = qcom_mdt_load(dev, firmware, fw_name,
			    IRIS_PAS_ID, mem_virt, mem_phys, res_size, NULL);

	if (!ret)
		iris_detect_firmware(core, fw_name, mem_virt, res_size, desc);

	memunmap(mem_virt);
err_release_fw:
	release_firmware(firmware);

	return ret;
}

int iris_fw_load(struct iris_core *core)
{
	const struct tz_cp_config *cp_config;
	int i, ret;

	ret = iris_load_fw_to_memory(core);
	if (ret) {
		dev_err(core->dev, "firmware download failed %d\n", ret);
		return ret;
	}

	ret = qcom_pas_auth_and_reset(IRIS_PAS_ID);
	if (ret)  {
		dev_err(core->dev, "auth and reset failed: %d\n", ret);
		return ret;
	}

	for (i = 0; i < core->iris_platform_data->tz_cp_config_data_size; i++) {
		cp_config = &core->iris_platform_data->tz_cp_config_data[i];
		ret = qcom_scm_mem_protect_video_var(cp_config->cp_start,
						     cp_config->cp_size,
						     cp_config->cp_nonpixel_start,
						     cp_config->cp_nonpixel_size);
		if (ret) {
			dev_err(core->dev, "qcom_scm_mem_protect_video_var failed: %d\n", ret);
			qcom_pas_shutdown(IRIS_PAS_ID);
			return ret;
		}
	}

	return 0;
}

int iris_fw_unload(struct iris_core *core)
{
	return qcom_pas_shutdown(IRIS_PAS_ID);
}

int iris_set_hw_state(struct iris_core *core, bool resume)
{
	return qcom_pas_set_remote_state(resume, 0);
}