From nobody Sun Feb 8 07:21:40 2026 Received: from bg5.exmail.qq.com (bg5.exmail.qq.com [43.154.197.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 564EB3314C3; Mon, 26 Jan 2026 11:47:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=43.154.197.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769428046; cv=none; b=LNrSi4vyBvJF7kqQ3wXRcbpMSHrnCY4+CEsh0tLXmCIRKDYDkxBNfG9ktTbZe9QQUmsDJJJDwGOXnthBlSEfBXoVTzf+P7P9Bjytfef2jL7TTTjM9k2YXJrJtSm4ayKjZ3J+EDgtqAWyPR+rh0nJHc5Of+dkMacmkcFgiBuPOn8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769428046; c=relaxed/simple; bh=Q21Fx2Hu+D4JMyClzSR8puOfoq/ZqCVjMicA/UeaWhI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Z53wa+KDXfAKsv3ZVSLOUIbA0MRk5F0Z3qOi/p+ZYJLVe3ujRFFhf53srzIsQqb1tdXLUQ07c7DDTOCj40NBR20AiHxEPGwOjAIqs+rEFf8hWF1dnzcSEtTlJZHEBoIHNQjKOxhMRqVggvTiIBV3yvxQEx30KQo/SHO60Wlbx4U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com; spf=pass smtp.mailfrom=radxa.com; arc=none smtp.client-ip=43.154.197.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=radxa.com X-QQ-mid: zesmtpgz1t1769427932tbb02073a X-QQ-Originating-IP: wCXjarD80Q/CugS2IJGugSN3M4crF9NufmwewRf1LH8= Received: from [127.0.1.1] ( [183.250.239.212]) by bizesmtp.qq.com (ESMTP) with id ; Mon, 26 Jan 2026 19:45:31 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 7763489091627574520 EX-QQ-RecipientCnt: 10 From: Junhao Xie Date: Mon, 26 Jan 2026 19:44:51 +0800 Subject: [PATCH v2 1/2] firmware: qcom: scm: Add SCM storage interface support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260126-scm-storage-v2-v2-1-fa045c7e7699@radxa.com> References: <20260126-scm-storage-v2-v2-0-fa045c7e7699@radxa.com> In-Reply-To: <20260126-scm-storage-v2-v2-0-fa045c7e7699@radxa.com> To: Bjorn Andersson , Konrad Dybcio , Miquel Raynal , Richard Weinberger , Vignesh Raghavendra Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org, Xilin Wu , Junhao Xie X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1769427927; l=8225; i=bigfoot@radxa.com; s=20251219; h=from:subject:message-id; bh=Q21Fx2Hu+D4JMyClzSR8puOfoq/ZqCVjMicA/UeaWhI=; b=LLjAFG49tx2yhebjw5CB8Jkoftv/XfVb2Vds1+ntvYKrFuMfwFFyVHXYgbl2qVMCsq96mu507 pB33+dbgI2NDdxEhRV4jEbFyZRrcxhg1/mnh98TOt5Su3ao8eD35Aok X-Developer-Key: i=bigfoot@radxa.com; a=ed25519; pk=aP5LX0jneuAa4pTVEww/6IbMlyp5VzzCwzcbMt1cpeI= X-QQ-SENDSIZE: 520 Feedback-ID: zesmtpgz:radxa.com:qybglogicsvrsz:qybglogicsvrsz4b-0 X-QQ-XMAILINFO: OOpR7IuvaX669XBnab+uQ46wiCETEozsJGyVA2nr0p2tZIq75OpenzHQ VdaunM2sMfRK8qoPu3NloYMzIBb3iyr+1Zxtg+Zpwn1PuFWnN0VqBoYkC78aemX2mLv/5VS F2W8/DO99SNQvOkkTTBV/FeTgShxS7STXPLArvQ2DlY05VV1PSWopSI0FRzi2mxu8gOf6qD lRkXcglRmt3r1JQ96gRRc4YwJieLrhWOS67ZoxqMH/NLBsrqQnfPOLgmJptochDwwE4UVeH WALQ8Acm6QEZFTtWEQBTuFgmgyceXCuEVo13GczOrv6WVbmW9ILsTz8gNzK+WwXJYkntDcm i4TPfaDERlHr3idABIMMVIaQ3YNHwttDpdGdT/nhP0BOPehFDXyL4lHE2GjWMJA7RTuqjUk wHDC4+nGl7qtyfBkUwwH+c21IVnIECkgDPEQ1CZotNkk0EeVJAr0N2RI2HR2tuNeXewcXvF /pcA1cSDFMH8tE4h1Kc0uIjAPUXKlBKYJQCVch0nPfsNQq9yu1OBebdd/2pyLnyu2fdvKcE 6YkjB4SVZgCjM2t3BFww9ja5H3EmWHIn9eosbmM9Kp1spMNvUzooTKBMQcvDpF7/d+Gk+AO 9CGPm7cnlgTaVBLO6YDq5hU/IyTf6sKdzq3W57tOr9tdFkcCQi1hSxgf5I1sJ7+WvYNYW6m +bsEE4smQr2PiabD5SIpbpGww2jD4xJ8o6ZFsNP5Z9JSMhTi3eWEXa3hP1Bka+Te23ixeCg IfG9qkVb8oa7GpA7OnLSd5mD9DVKIAuz6um9qRwf8rPAe2KbEB24kJQMt7X97IsbTeFuxoZ 8POL7tD/gB5b8zqiSxqywoQ9HG2k1aAcCwkqhhl3iIs8jnVhg523pjuk67qYuFAlUHXnUuu qcRfUy580uS3pshD/DfCa7GcHXL5SLRVSF12Ou3n5JTd/0dxRmA8v+iigYiWFLFSxuV7p/n taTF2qgoCQrYrOm+XBrpTzjA2vk+lrK52HQ1isLia4eSfd+Dv61Q5xxVQwd6noPjb0IQ5GY BTdFBLIp0AiRhQWOSIuZmrUS10s+fojcL+0sBi25aGrsBddqYIq2TdCSW2OQhp06WibegEI Wyp/nr0wsEP/HsyTgeX3GA= X-QQ-XMRINFO: MPJ6Tf5t3I/ylTmHUqvI8+Wpn+Gzalws3A== X-QQ-RECHKSPAM: 0 Add infrastructure to support accessing TrustZone-protected storage devices through SCM (Secure Channel Manager) calls. Some Qualcomm platforms protect their firmware storage (typically SPI NOR flash) via TrustZone, making it inaccessible from the non-secure world. Signed-off-by: Junhao Xie Tested-by: Xilin Wu --- drivers/firmware/qcom/qcom_scm.c | 161 +++++++++++++++++++++++++++++= ++++ drivers/firmware/qcom/qcom_scm.h | 3 + include/linux/firmware/qcom/qcom_scm.h | 34 +++++++ 3 files changed, 198 insertions(+) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_= scm.c index 1a6f85e46..f6e643cb1 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -66,6 +66,27 @@ struct qcom_scm_mem_map_info { __le64 mem_size; }; =20 +struct qcom_scm_storage_cmd { + __le64 storage_type; + __le64 slot_num; + __le64 lun; + __le64 guid_ptr; + __le64 storage_cmd; +} __packed; + +struct qcom_scm_storage_cmd_details { + __le64 lba; + __le64 length; + __le64 data_ptr; + __le64 data_size; +} __packed; + +struct qcom_scm_storage_payload { + struct qcom_scm_storage_cmd cmd; + struct qcom_scm_storage_cmd_details details; + u8 data[]; +}; + /** * struct qcom_scm_qseecom_resp - QSEECOM SCM call response. * @result: Result or status of the SCM call. See &enum qcom_scm_qseeco= m_result. @@ -111,6 +132,17 @@ enum qcom_scm_qseecom_tz_cmd_info { QSEECOM_TZ_CMD_INFO_VERSION =3D 3, }; =20 +#define STORAGE_RESULT_SUCCESS 0 +#define STORAGE_RESULT_NO_MEMORY 1 +#define STORAGE_RESULT_INVALID_PARAMETER 2 +#define STORAGE_RESULT_STORAGE_ERROR 3 +#define STORAGE_RESULT_ACCESS_DENIED 4 +#define STORAGE_RESULT_NOT_SUPPORTED 5 +#define STORAGE_RESULT_MAC_MISMATCH 6 +#define STORAGE_RESULT_ALREADY_RUNNING 7 +#define STORAGE_RESULT_PARTITION_NOT_FOUND 8 +#define STORAGE_RESULT_READONLY 9 + #define QSEECOM_MAX_APP_NAME_SIZE 64 #define SHMBRIDGE_RESULT_NOTSUPP 4 =20 @@ -2198,6 +2230,132 @@ static void qcom_scm_qtee_init(struct qcom_scm *scm) devm_add_action_or_reset(scm->dev, qcom_scm_qtee_free, qtee_dev); } =20 +int qcom_scm_storage_send_cmd(enum qcom_scm_storage_type storage_type, + enum qcom_scm_storage_cmd_id cmd_id, + u64 lba, void *data, size_t size) +{ + struct qcom_scm_storage_payload *payload __free(qcom_tzmem) =3D NULL; + struct qcom_scm_res scm_res =3D {}; + struct qcom_scm_desc desc =3D {}; + phys_addr_t payload_addr; + size_t buf_size; + int ret; + + buf_size =3D sizeof(*payload); + if (data) + buf_size +=3D size; + + payload =3D qcom_tzmem_alloc(__scm->mempool, buf_size, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + memset(payload, 0, buf_size); + if (data) + memcpy(payload->data, data, size); + + payload->cmd.storage_type =3D cpu_to_le64(storage_type); + payload->cmd.storage_cmd =3D cpu_to_le64(cmd_id); + + payload->details.lba =3D cpu_to_le64(lba); + if (payload) { + payload_addr =3D qcom_tzmem_to_phys(payload->data); + payload->details.data_ptr =3D cpu_to_le64(payload_addr); + } + payload->details.length =3D cpu_to_le64(size); + + desc.svc =3D QCOM_SCM_SVC_STORAGE; + desc.cmd =3D QCOM_SCM_STORAGE_CMD; + desc.arginfo =3D QCOM_SCM_ARGS(4, QCOM_SCM_RO, QCOM_SCM_VAL, + QCOM_SCM_RW, QCOM_SCM_VAL); + desc.args[0] =3D qcom_tzmem_to_phys(&payload->cmd); + desc.args[1] =3D sizeof(payload->cmd); + desc.args[2] =3D qcom_tzmem_to_phys(&payload->details); + desc.args[3] =3D sizeof(payload->details); + desc.owner =3D ARM_SMCCC_OWNER_SIP; + + ret =3D qcom_scm_call(__scm->dev, &desc, &scm_res); + if (ret) + return ret; + + if (data) + memcpy(data, payload->data, size); + + switch (scm_res.result[0]) { + case STORAGE_RESULT_SUCCESS: + return 0; + case STORAGE_RESULT_NO_MEMORY: + return -ENOMEM; + case STORAGE_RESULT_INVALID_PARAMETER: + return -EINVAL; + case STORAGE_RESULT_STORAGE_ERROR: + return -EIO; + case STORAGE_RESULT_ACCESS_DENIED: + return -EACCES; + case STORAGE_RESULT_NOT_SUPPORTED: + return -EOPNOTSUPP; + case STORAGE_RESULT_MAC_MISMATCH: + return -EBADMSG; + case STORAGE_RESULT_ALREADY_RUNNING: + return -EALREADY; + case STORAGE_RESULT_PARTITION_NOT_FOUND: + return -ENOENT; + case STORAGE_RESULT_READONLY: + return -EROFS; + default: + return -EIO; + } +} +EXPORT_SYMBOL_GPL(qcom_scm_storage_send_cmd); + +static void qcom_scm_storage_free(void *data) +{ + struct platform_device *storage_dev =3D data; + + platform_device_unregister(storage_dev); +} + +static void qcom_scm_storage_init(struct qcom_scm *scm) +{ + struct qcom_scm_storage_info info; + struct platform_device *storage_dev; + u64 total_blocks; + u32 block_size; + int ret; + + if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_STORAGE, + QCOM_SCM_STORAGE_CMD)) + return; + + ret =3D qcom_scm_storage_send_cmd(QCOM_SCM_STORAGE_SPINOR, + QCOM_SCM_STORAGE_GET_INFO, + 0, &info, sizeof(info)); + if (ret < 0) { + dev_warn(scm->dev, "scm storage get info failed: %d\n", ret); + return; + } + + total_blocks =3D le64_to_cpu(info.total_blocks); + block_size =3D le32_to_cpu(info.block_size); + + dev_dbg(scm->dev, "scm storage size %llu bytes\n", + total_blocks * block_size); + + storage_dev =3D platform_device_alloc("qcom_scm_storage", -1); + if (!storage_dev) + return; + + storage_dev->dev.parent =3D scm->dev; + + ret =3D platform_device_add(storage_dev); + if (ret) { + platform_device_put(storage_dev); + return; + } + + devm_add_action_or_reset(scm->dev, qcom_scm_storage_free, + storage_dev); +} + /** * qcom_scm_is_available() - Checks if SCM is available */ @@ -2433,6 +2591,9 @@ static int qcom_scm_probe(struct platform_device *pde= v) /* Initialize the QTEE object interface. */ qcom_scm_qtee_init(scm); =20 + /* Initialize the SCM storage interface. */ + qcom_scm_storage_init(scm); + return 0; } =20 diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_= scm.h index a56c8212c..3b68b33c5 100644 --- a/drivers/firmware/qcom/qcom_scm.h +++ b/drivers/firmware/qcom/qcom_scm.h @@ -149,6 +149,9 @@ int qcom_scm_shm_bridge_enable(struct device *scm_dev); #define QCOM_SCM_SMMU_CONFIG_ERRATA1 0x03 #define QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL 0x02 =20 +#define QCOM_SCM_SVC_STORAGE 0x1a +#define QCOM_SCM_STORAGE_CMD 0x01 + #define QCOM_SCM_SVC_WAITQ 0x24 #define QCOM_SCM_WAITQ_RESUME 0x02 #define QCOM_SCM_WAITQ_GET_WQ_CTX 0x03 diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmwar= e/qcom/qcom_scm.h index a55ca7712..644c3cf46 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -53,6 +53,36 @@ enum qcom_scm_ice_cipher { QCOM_SCM_ICE_CIPHER_AES_256_CBC =3D 4, }; =20 +enum qcom_scm_storage_type { + QCOM_SCM_STORAGE_NULL =3D 0, + QCOM_SCM_STORAGE_SPINOR =3D 1, +}; + +enum qcom_scm_storage_cmd_id { + QCOM_SCM_STORAGE_INIT =3D 0, + QCOM_SCM_STORAGE_READ =3D 1, + QCOM_SCM_STORAGE_WRITE =3D 2, + QCOM_SCM_STORAGE_ERASE =3D 3, + QCOM_SCM_STORAGE_GET_INFO =3D 4, + QCOM_SCM_STORAGE_DEINIT =3D 5, +}; + +#define QCOM_SCM_STORAGE_FW_VER_LEN 32 +#define QCOM_SCM_STORAGE_MEM_TYPE_LEN 5 +#define QCOM_SCM_STORAGE_PROD_NAME_LEN 32 + +struct qcom_scm_storage_info { + __le64 total_blocks; + __le32 block_size; + __le32 page_size; + __le32 num_physical; + __le64 manufacturer_id; + __le64 serial_num; + char fw_version[QCOM_SCM_STORAGE_FW_VER_LEN]; + char memory_type[QCOM_SCM_STORAGE_MEM_TYPE_LEN]; + char product_name[QCOM_SCM_STORAGE_PROD_NAME_LEN]; +} __packed; + #define QCOM_SCM_PERM_READ 0x4 #define QCOM_SCM_PERM_WRITE 0x2 #define QCOM_SCM_PERM_EXEC 0x1 @@ -181,4 +211,8 @@ int qcom_scm_qtee_invoke_smc(phys_addr_t inbuf, size_t = inbuf_size, int qcom_scm_qtee_callback_response(phys_addr_t buf, size_t buf_size, u64 *result, u64 *response_type); =20 +int qcom_scm_storage_send_cmd(enum qcom_scm_storage_type storage_type, + enum qcom_scm_storage_cmd_id cmd_id, + u64 lba, void *payload, size_t size); + #endif --=20 2.52.0 From nobody Sun Feb 8 07:21:40 2026 Received: from smtpbgeu1.qq.com (smtpbgeu1.qq.com [52.59.177.22]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B594E332EBF; Mon, 26 Jan 2026 11:48:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=52.59.177.22 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769428127; cv=none; b=H5Hi5Rfxz5a87OmjVcqsa7nVghuDGC1A2+33d9PbDEU9p9K61YkDcNVvPPTfu6u7vsnjZuILYhy/UzCDtWM4D2Z6Plg9uE35jF23mvUkYbUBvFyESsGqoCl8y4gcONJj2JO2SCV/nR344EF5iADsAY0IrJcRspn2N5yc2L/fLUY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769428127; c=relaxed/simple; bh=xv5hI5i8Spz479SHAGBAWIJ0ldJdMF3MSWUhT5gnxRo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=D2WgQTi9CvhwbKQWebNKmji4Pzl9bqcn4vqJTiJ02ZT7nMTZNnYNxwfmhEzUAx35cZNvoXoo+I2B2HyWC+rWtN8OZazTPnOH/Gqd5ZeSyEYOSfZHoIG7iY1Z6hnFjHdUKHjExon/49nznDrQZPJlH1aqffVjsNP+alEVuqx3Ns8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com; spf=pass smtp.mailfrom=radxa.com; arc=none smtp.client-ip=52.59.177.22 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=radxa.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=radxa.com X-QQ-mid: zesmtpgz1t1769427934t1f0ecabc X-QQ-Originating-IP: kNj2rKUaFapOMqL8WyA1fFTWQeTO1h3ALWK/ovGas3s= Received: from [127.0.1.1] ( [183.250.239.212]) by bizesmtp.qq.com (ESMTP) with id ; Mon, 26 Jan 2026 19:45:33 +0800 (CST) X-QQ-SSF: 0000000000000000000000000000000 X-QQ-GoodBg: 0 X-BIZMAIL-ID: 793247866761747800 EX-QQ-RecipientCnt: 10 From: Junhao Xie Date: Mon, 26 Jan 2026 19:44:52 +0800 Subject: [PATCH v2 2/2] mtd: devices: Add Qualcomm SCM storage driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260126-scm-storage-v2-v2-2-fa045c7e7699@radxa.com> References: <20260126-scm-storage-v2-v2-0-fa045c7e7699@radxa.com> In-Reply-To: <20260126-scm-storage-v2-v2-0-fa045c7e7699@radxa.com> To: Bjorn Andersson , Konrad Dybcio , Miquel Raynal , Richard Weinberger , Vignesh Raghavendra Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org, Xilin Wu , Junhao Xie X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1769427928; l=9323; i=bigfoot@radxa.com; s=20251219; h=from:subject:message-id; bh=xv5hI5i8Spz479SHAGBAWIJ0ldJdMF3MSWUhT5gnxRo=; b=/tqQF1n+ioszbYxAx1ecrmkqIOD/3xZXdJksmGdc2ri1tBsc6C4HFkE6+umqWlBHQDtCUioF2 ux0Wwi7BBsQCqWasB7yyDywsRWVPc3zzulDd/h4/XyhWcrxPPremi6s X-Developer-Key: i=bigfoot@radxa.com; a=ed25519; pk=aP5LX0jneuAa4pTVEww/6IbMlyp5VzzCwzcbMt1cpeI= X-QQ-SENDSIZE: 520 Feedback-ID: zesmtpgz:radxa.com:qybglogicsvrsz:qybglogicsvrsz4b-0 X-QQ-XMAILINFO: Ma7K3O/gF9R327bI1RuQaiJpJdppIeVtM3kFdJb9RskZFKZh7fCZfBSg 7IO2kYpJ9OJPO2a+dZXBJo5dfwPfABoZ6MOEeNsXs6/UfR50rBEy9A2egHx+ZN1KJMtiIBc tB+ZXWE/4Fqv00V0xIYB8uvNMw3xJbJxv99IEsGgX7jZOOvjW+6XZklQwuXmwY+jHG81nUc WFgRtK2DEHJ0YBIU+2SyT1atA4uoervbkHTn9SNd675C1lUTzbKBNAZeU4s29fs2lUDzWfb cgHw2AeaKfK/DrQ6YA4L9fYDtt6gtlgDQwAgyF9g5TCME5CIeCgx+FZ4HB/XlgHyWq8D8lh 2oANzASwt5E1oYxCSiUN/K130ewiUK0yzx4NX3Z1LzA8WiTeF20KeyQzGNRsDTT2/7zJGnN WtcWmXAC3yO6DF24riWDT1wa0ynEBprlbF7jL3b3vKamivJUwfQFylarUUIqM72Dgba+6kV xo8ek1xQOeqKGYK99aPgHzGPjhI1uAOM6uP+A2aOVCu6GNXW1MfAr9VoBmK/c7WTTcmk7Aj 9qbP+dlXzdlNGdiCdnyxS9z7/km7Oa5T/BO2wJAmb512WpIc30ZUeEpFIlVCf1UxdLL4wS+ 33lsCmwtlpqxUtziyiMSrHgsRr9jHKg1zlO5NXYlq09RoPeTHA7+lp4zH9esMsc7lP/H1R5 4BX9rYQrRNjzZl+L6B5is44Ro7NHSQjWM9JRYoQEwNcirRPuEAVqgEThl+ZIy6p53G8eI9O zTqyUBrcTwzyDnD3qwaKG+U2rbXJDwq9bshuAcZ3rlXtg3rSy/PRJKAUMQg6DGI/QXilrlu OLqnAMEi4eQOl3wi8ITQNbKgG+sKM0CxQk2yN23vsslA7wJSZpTwlF1MLYCfzrR2L1M6/br FBEwkdsWbHGjUgKTe3G4d4Q2fid07eRDgG3EWF6NRWP9qL5JT55UBCg0vU5pqbidN35Gw8I 8LHN+hrNKk0lU7P9GD0QfAwwChIcPpfr47lmJVaDjuZAngfaSAPV9OZEsodQ6pzO1bT/boS V8lN8vb5P5cP+Q4ibFWzPskNAcQ+tp876i5InrLOvpcm7OiORKAs0LdNAhnX2o/ALUUGtT4 O0mdVmT5K33Nkrj4hyE0MXs3ANG7cPsSCiIq3Di6WDZsJWWL0g4P2pbt6zhEHK5hR/dz/GL aWMaw6E7p2Ursgc= X-QQ-XMRINFO: NyFYKkN4Ny6FuXrnB5Ye7Aabb3ujjtK+gg== X-QQ-RECHKSPAM: 0 Add MTD driver for accessing storage devices managed by Qualcomm's TrustZone firmware. On some platforms, BIOS/firmware storage (typically SPI NOR flash) is not directly accessible from the non-secure world and all operations must go through SCM (Secure Channel Manager) calls. Signed-off-by: Junhao Xie Tested-by: Xilin Wu --- drivers/mtd/devices/Kconfig | 17 +++ drivers/mtd/devices/Makefile | 1 + drivers/mtd/devices/qcom_scm_storage.c | 265 +++++++++++++++++++++++++++++= ++++ 3 files changed, 283 insertions(+) diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index e518dfeee..4f73e89a1 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -194,6 +194,23 @@ config MTD_INTEL_DG To compile this driver as a module, choose M here: the module will be called mtd-intel-dg. =20 +config MTD_QCOM_SCM_STORAGE + tristate "Qualcomm TrustZone protected storage MTD driver" + depends on MTD + depends on QCOM_SCM || COMPILE_TEST + help + This provides an MTD device to access storage (typically SPI NOR + flash) that is managed by Qualcomm's TrustZone firmware. On some + platforms, the firmware storage is not directly accessible from + the non-secure world and all operations must go through secure + monitor calls. + + This driver is only functional on devices where the bootloader + has configured TrustZone to expose the storage interface. + + To compile this driver as a module, choose M here: the module + will be called qcom_scm_storage. + comment "Disk-On-Chip Device Drivers" =20 config MTD_DOCG3 diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 9fe4ce9cf..d71d07f81 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_MTD_BCM47XXSFLASH) +=3D bcm47xxsflash.o obj-$(CONFIG_MTD_ST_SPI_FSM) +=3D st_spi_fsm.o obj-$(CONFIG_MTD_POWERNV_FLASH) +=3D powernv_flash.o obj-$(CONFIG_MTD_INTEL_DG) +=3D mtd_intel_dg.o +obj-$(CONFIG_MTD_QCOM_SCM_STORAGE) +=3D qcom_scm_storage.o =20 =20 CFLAGS_docg3.o +=3D -I$(src) diff --git a/drivers/mtd/devices/qcom_scm_storage.c b/drivers/mtd/devices/q= com_scm_storage.c new file mode 100644 index 000000000..bc3f40424 --- /dev/null +++ b/drivers/mtd/devices/qcom_scm_storage.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm TrustZone SCM Storage Flash driver + * + * Copyright (c) 2025 Junhao Xie + */ + +#include +#include +#include +#include +#include + +#include + +/* + * This driver provides MTD access to storage devices managed by Qualcomm's + * TrustZone firmware. The storage (typically SPI NOR flash) is not direct= ly + * accessible from the non-secure world and all operations must go through + * SCM (Secure Channel Manager) calls. + * + * A bounce buffer is required because the interface requires + * block-aligned addresses and sizes + */ +struct qcom_scm_storage { + struct device *dev; + struct mutex lock; /* Protects SCM storage operations */ + struct mtd_info mtd; + struct qcom_scm_storage_info info; + size_t buffer_size; + u8 *buffer; +}; + +static int qcom_scm_storage_erase(struct mtd_info *mtd, + struct erase_info *instr) +{ + struct qcom_scm_storage *host =3D mtd->priv; + size_t block_size; + + if (instr->addr % host->mtd.erasesize || + instr->len % host->mtd.erasesize) + return -EINVAL; + + block_size =3D le32_to_cpu(host->info.block_size); + + guard(mutex)(&host->lock); + + return qcom_scm_storage_send_cmd(QCOM_SCM_STORAGE_SPINOR, + QCOM_SCM_STORAGE_ERASE, + instr->addr / block_size, + 0, instr->len); +} + +static int qcom_scm_storage_read(struct mtd_info *mtd, + loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct qcom_scm_storage *host =3D mtd->priv; + loff_t block_start, block_off, lba; + size_t block_size, chunk, to_read; + int ret; + + if (retlen) + *retlen =3D 0; + + if (from + len > mtd->size) + return -EINVAL; + + if (len =3D=3D 0) + return 0; + + block_size =3D le32_to_cpu(host->info.block_size); + + guard(mutex)(&host->lock); + + while (len > 0) { + block_start =3D round_down(from, block_size); + block_off =3D from - block_start; + lba =3D block_start / block_size; + + if (block_off || len < block_size) { + chunk =3D min_t(size_t, block_size - block_off, len); + to_read =3D block_size; + } else { + chunk =3D round_down(len, block_size); + chunk =3D min_t(size_t, chunk, host->buffer_size); + to_read =3D chunk; + } + + ret =3D qcom_scm_storage_send_cmd(QCOM_SCM_STORAGE_SPINOR, + QCOM_SCM_STORAGE_READ, + lba, host->buffer, + to_read); + if (ret) + return ret; + + memcpy(buf, host->buffer + block_off, chunk); + + buf +=3D chunk; + from +=3D chunk; + len -=3D chunk; + if (retlen) + *retlen +=3D chunk; + } + + return 0; +} + +static int qcom_scm_storage_write(struct mtd_info *mtd, + loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct qcom_scm_storage *host =3D mtd->priv; + loff_t block_start, block_off, lba; + size_t block_size, chunk, to_write; + int ret; + + if (retlen) + *retlen =3D 0; + + if (to + len > mtd->size) + return -EINVAL; + + if (len =3D=3D 0) + return 0; + + block_size =3D le32_to_cpu(host->info.block_size); + + guard(mutex)(&host->lock); + + while (len > 0) { + block_start =3D round_down(to, block_size); + block_off =3D to - block_start; + lba =3D block_start / block_size; + + if (block_off || len < block_size) { + chunk =3D min_t(size_t, block_size - block_off, len); + to_write =3D block_size; + + ret =3D qcom_scm_storage_send_cmd(QCOM_SCM_STORAGE_SPINOR, + QCOM_SCM_STORAGE_READ, + lba, host->buffer, + block_size); + if (ret) + return ret; + } else { + chunk =3D round_down(len, block_size); + chunk =3D min_t(size_t, chunk, host->buffer_size); + to_write =3D chunk; + } + + memcpy(host->buffer + block_off, buf, chunk); + + ret =3D qcom_scm_storage_send_cmd(QCOM_SCM_STORAGE_SPINOR, + QCOM_SCM_STORAGE_WRITE, + lba, host->buffer, + to_write); + if (ret) + return ret; + + buf +=3D chunk; + to +=3D chunk; + len -=3D chunk; + if (retlen) + *retlen +=3D chunk; + } + + return 0; +} + +static int qcom_scm_storage_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct qcom_scm_storage *host; + u64 total_blocks, serial_num; + u32 block_size; + int ret; + + host =3D devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + platform_set_drvdata(pdev, host); + host->dev =3D dev; + + ret =3D devm_mutex_init(dev, &host->lock); + if (ret) + return ret; + + host->buffer_size =3D SZ_256K; + host->buffer =3D devm_kzalloc(dev, host->buffer_size, GFP_KERNEL); + if (!host->buffer) + return -ENOMEM; + + ret =3D qcom_scm_storage_send_cmd(QCOM_SCM_STORAGE_SPINOR, + QCOM_SCM_STORAGE_GET_INFO, + 0, &host->info, + sizeof(host->info)); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get storage info\n"); + + total_blocks =3D le64_to_cpu(host->info.total_blocks); + serial_num =3D le64_to_cpu(host->info.serial_num); + block_size =3D le32_to_cpu(host->info.block_size); + + if (!block_size || !total_blocks) + return dev_err_probe(dev, -EINVAL, + "invalid storage geometry\n"); + + if (block_size > host->buffer_size) + return dev_err_probe(dev, -EINVAL, + "block size %u exceeds buffer size\n", + block_size); + + host->mtd.priv =3D host; + host->mtd.name =3D dev_name(dev); + host->mtd.owner =3D THIS_MODULE; + host->mtd.dev.parent =3D dev; + host->mtd.size =3D total_blocks * block_size; + host->mtd.erasesize =3D block_size; + host->mtd.writesize =3D block_size; + host->mtd.writebufsize =3D block_size; + host->mtd.type =3D MTD_NORFLASH; + host->mtd.flags =3D MTD_WRITEABLE; + host->mtd._erase =3D qcom_scm_storage_erase; + host->mtd._read =3D qcom_scm_storage_read; + host->mtd._write =3D qcom_scm_storage_write; + + ret =3D mtd_device_register(&host->mtd, NULL, 0); + if (ret) + return ret; + + dev_info(dev, "scm storage 0x%llx registered with size %llu bytes\n", + serial_num, host->mtd.size); + + return 0; +} + +static void qcom_scm_storage_remove(struct platform_device *pdev) +{ + struct qcom_scm_storage *host =3D platform_get_drvdata(pdev); + + WARN_ON(mtd_device_unregister(&host->mtd)); +} + +static const struct platform_device_id qcom_scm_storage_ids[] =3D { + { "qcom_scm_storage", 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, qcom_scm_storage_ids); + +static struct platform_driver qcom_scm_storage_driver =3D { + .probe =3D qcom_scm_storage_probe, + .remove =3D qcom_scm_storage_remove, + .driver =3D { + .name =3D "qcom_scm_storage", + }, + .id_table =3D qcom_scm_storage_ids, +}; +module_platform_driver(qcom_scm_storage_driver); + +MODULE_AUTHOR("Junhao Xie "); +MODULE_DESCRIPTION("Qualcomm TrustZone SCM Storage Flash driver"); +MODULE_LICENSE("GPL"); --=20 2.52.0