static int q6v5_mpss_load()

in qcom_q6v5_mss.c [1254:1447]


static int q6v5_mpss_load(struct q6v5 *qproc)
{
	const struct elf32_phdr *phdrs;
	const struct elf32_phdr *phdr;
	const struct firmware *seg_fw;
	const struct firmware *fw;
	struct elf32_hdr *ehdr;
	phys_addr_t mpss_reloc;
	phys_addr_t boot_addr;
	phys_addr_t min_addr = PHYS_ADDR_MAX;
	phys_addr_t max_addr = 0;
	u32 code_length;
	bool relocate = false;
	char *fw_name;
	size_t fw_name_len;
	ssize_t offset;
	size_t size = 0;
	void *ptr;
	int ret;
	int i;

	fw_name_len = strlen(qproc->hexagon_mdt_image);
	if (fw_name_len <= 4)
		return -EINVAL;

	fw_name = kstrdup(qproc->hexagon_mdt_image, GFP_KERNEL);
	if (!fw_name)
		return -ENOMEM;

	ret = request_firmware(&fw, fw_name, qproc->dev);
	if (ret < 0) {
		dev_err(qproc->dev, "unable to load %s\n", fw_name);
		goto out;
	}

	/* Initialize the RMB validator */
	writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);

	ret = q6v5_mpss_init_image(qproc, fw);
	if (ret)
		goto release_firmware;

	ehdr = (struct elf32_hdr *)fw->data;
	phdrs = (struct elf32_phdr *)(ehdr + 1);

	for (i = 0; i < ehdr->e_phnum; i++) {
		phdr = &phdrs[i];

		if (!q6v5_phdr_valid(phdr))
			continue;

		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
			relocate = true;

		if (phdr->p_paddr < min_addr)
			min_addr = phdr->p_paddr;

		if (phdr->p_paddr + phdr->p_memsz > max_addr)
			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
	}

	/*
	 * In case of a modem subsystem restart on secure devices, the modem
	 * memory can be reclaimed only after MBA is loaded.
	 */
	q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, false,
				qproc->mpss_phys, qproc->mpss_size);

	/* Share ownership between Linux and MSS, during segment loading */
	ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, true, true,
				      qproc->mpss_phys, qproc->mpss_size);
	if (ret) {
		dev_err(qproc->dev,
			"assigning Q6 access to mpss memory failed: %d\n", ret);
		ret = -EAGAIN;
		goto release_firmware;
	}

	mpss_reloc = relocate ? min_addr : qproc->mpss_phys;
	qproc->mpss_reloc = mpss_reloc;
	/* Load firmware segments */
	for (i = 0; i < ehdr->e_phnum; i++) {
		phdr = &phdrs[i];

		if (!q6v5_phdr_valid(phdr))
			continue;

		offset = phdr->p_paddr - mpss_reloc;
		if (offset < 0 || offset + phdr->p_memsz > qproc->mpss_size) {
			dev_err(qproc->dev, "segment outside memory range\n");
			ret = -EINVAL;
			goto release_firmware;
		}

		if (phdr->p_filesz > phdr->p_memsz) {
			dev_err(qproc->dev,
				"refusing to load segment %d with p_filesz > p_memsz\n",
				i);
			ret = -EINVAL;
			goto release_firmware;
		}

		ptr = memremap(qproc->mpss_phys + offset, phdr->p_memsz, MEMREMAP_WC);
		if (!ptr) {
			dev_err(qproc->dev,
				"unable to map memory region: %pa+%zx-%x\n",
				&qproc->mpss_phys, offset, phdr->p_memsz);
			goto release_firmware;
		}

		if (phdr->p_filesz && phdr->p_offset < fw->size) {
			/* Firmware is large enough to be non-split */
			if (phdr->p_offset + phdr->p_filesz > fw->size) {
				dev_err(qproc->dev,
					"failed to load segment %d from truncated file %s\n",
					i, fw_name);
				ret = -EINVAL;
				memunmap(ptr);
				goto release_firmware;
			}

			memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
		} else if (phdr->p_filesz) {
			/* Replace "xxx.xxx" with "xxx.bxx" */
			sprintf(fw_name + fw_name_len - 3, "b%02d", i);
			ret = request_firmware_into_buf(&seg_fw, fw_name, qproc->dev,
							ptr, phdr->p_filesz);
			if (ret) {
				dev_err(qproc->dev, "failed to load %s\n", fw_name);
				memunmap(ptr);
				goto release_firmware;
			}

			if (seg_fw->size != phdr->p_filesz) {
				dev_err(qproc->dev,
					"failed to load segment %d from truncated file %s\n",
					i, fw_name);
				ret = -EINVAL;
				release_firmware(seg_fw);
				memunmap(ptr);
				goto release_firmware;
			}

			release_firmware(seg_fw);
		}

		if (phdr->p_memsz > phdr->p_filesz) {
			memset(ptr + phdr->p_filesz, 0,
			       phdr->p_memsz - phdr->p_filesz);
		}
		memunmap(ptr);
		size += phdr->p_memsz;

		code_length = readl(qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
		if (!code_length) {
			boot_addr = relocate ? qproc->mpss_phys : min_addr;
			writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG);
			writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG);
		}
		writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);

		ret = readl(qproc->rmb_base + RMB_MBA_STATUS_REG);
		if (ret < 0) {
			dev_err(qproc->dev, "MPSS authentication failed: %d\n",
				ret);
			goto release_firmware;
		}
	}

	/* Transfer ownership of modem ddr region to q6 */
	ret = q6v5_xfer_mem_ownership(qproc, &qproc->mpss_perm, false, true,
				      qproc->mpss_phys, qproc->mpss_size);
	if (ret) {
		dev_err(qproc->dev,
			"assigning Q6 access to mpss memory failed: %d\n", ret);
		ret = -EAGAIN;
		goto release_firmware;
	}

	ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_AUTH_COMPLETE, 10000);
	if (ret == -ETIMEDOUT)
		dev_err(qproc->dev, "MPSS authentication timed out\n");
	else if (ret < 0)
		dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret);

	qcom_pil_info_store("modem", qproc->mpss_phys, qproc->mpss_size);

release_firmware:
	release_firmware(fw);
out:
	kfree(fw_name);

	return ret < 0 ? ret : 0;
}