From nobody Mon Feb 9 16:51:48 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