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;
}