Convert the socinfo driver to Rust for a number of improvements:
* Accessing IO mapped regions through the IO subsystem, rather than
through regular memory accesses.
* Binds the device as an auxiliary device rather than a platform device,
ensuring the mapped IO regions cannot be accessed after the smem
device is removed.
* Adds bounds-checking to all accesses, hardening against a repeat of
CVE-2024-58007
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
drivers/soc/qcom/Kconfig | 1 +
drivers/soc/qcom/Makefile | 2 +-
drivers/soc/qcom/smem.c | 42 +-
drivers/soc/qcom/socinfo.c | 931 -----------------------------------
drivers/soc/qcom/socinfo/Makefile | 2 +
drivers/soc/qcom/socinfo/bindings.rs | 123 +++++
drivers/soc/qcom/socinfo/data.rs | 438 ++++++++++++++++
drivers/soc/qcom/socinfo/socinfo.rs | 446 +++++++++++++++++
include/linux/soc/qcom/smem.h | 4 +
rust/bindgen_parameters | 1 +
rust/bindings/bindings_helper.h | 6 +
11 files changed, 1056 insertions(+), 940 deletions(-)
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 2caadbbcf8307ff94f5afbdd1481e5e5e291749f..16d553f66f0cf1101d3a8c7d401d7c4d6a65dac7 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -222,6 +222,7 @@ config QCOM_SMSM
config QCOM_SOCINFO
tristate "Qualcomm socinfo driver"
depends on QCOM_SMEM
+ depends on RUST
select SOC_BUS
help
Say yes here to support the Qualcomm socinfo driver, providing
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index b7f1d2a5736748b8772c090fd24462fa91f321c6..6f6688c76a00a91ce99600f298a8e6e0fefed806 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -28,7 +28,7 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
CFLAGS_smp2p.o := -I$(src)
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
-obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
+obj-$(CONFIG_QCOM_SOCINFO) += socinfo/
obj-$(CONFIG_QCOM_SPM) += spm.o
obj-$(CONFIG_QCOM_STATS) += qcom_stats.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
index fef840b5457407a85051ded0e835430dbebfe8bb..dcea2d7f37067b0b6f801b3d2b457422ad9f342c 100644
--- a/drivers/soc/qcom/smem.c
+++ b/drivers/soc/qcom/smem.c
@@ -4,6 +4,7 @@
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*/
+#include <linux/auxiliary_bus.h>
#include <linux/hwspinlock.h>
#include <linux/io.h>
#include <linux/module.h>
@@ -279,7 +280,6 @@ struct qcom_smem {
struct hwspinlock *hwlock;
u32 item_count;
- struct platform_device *socinfo;
struct smem_ptable *ptable;
struct smem_partition global_partition;
struct smem_partition partitions[SMEM_HOST_COUNT];
@@ -675,6 +675,32 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
return ERR_PTR(-EINVAL);
}
+/**
+ * qcom_smem_get_aux() - resolve ptr of size of a smem item
+ * @aux: an aux device that should be our child
+ * @host: the remote processor, or -1
+ * @item: smem item handle
+ * @size: pointer to be filled out with size of the item
+ *
+ * Looks up smem item and returns pointer to it. Size of smem
+ * item is returned in @size.
+ *
+ * The caller may take the loaded state of the provided aux device as
+ * an acceptable proxy for this memory being valid.
+ *
+ * Return: a pointer to an SMEM item on success, ERR_PTR() on failure.
+ */
+void *qcom_smem_get_aux(struct auxiliary_device *aux, unsigned int host,
+ unsigned int item, size_t *size)
+{
+ if (IS_ERR(__smem))
+ return __smem;
+ if (aux->dev.parent != __smem->dev)
+ return ERR_PTR(-EINVAL);
+ return qcom_smem_get(host, item, size);
+}
+EXPORT_SYMBOL_GPL(qcom_smem_get_aux);
+
/**
* qcom_smem_get() - resolve ptr of size of a smem item
* @host: the remote processor, or -1
@@ -684,6 +710,9 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
* Looks up smem item and returns pointer to it. Size of smem
* item is returned in @size.
*
+ * It is up to the caller to ensure that the qcom_smem device remains
+ * loaded by some mechanism when accessing returned memory.
+ *
* Return: a pointer to an SMEM item on success, ERR_PTR() on failure.
*/
void *qcom_smem_get(unsigned host, unsigned item, size_t *size)
@@ -1127,6 +1156,7 @@ static int qcom_smem_probe(struct platform_device *pdev)
struct smem_header *header;
struct reserved_mem *rmem;
struct qcom_smem *smem;
+ struct auxiliary_device *socinfo;
unsigned long flags;
int num_regions;
int hwlock_id;
@@ -1234,19 +1264,15 @@ static int qcom_smem_probe(struct platform_device *pdev)
__smem = smem;
- smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo",
- PLATFORM_DEVID_NONE, NULL,
- 0);
- if (IS_ERR(smem->socinfo))
- dev_dbg(&pdev->dev, "failed to register socinfo device\n");
+ socinfo = devm_auxiliary_device_create(&pdev->dev, "qcom-socinfo", NULL);
+ if (IS_ERR(socinfo))
+ dev_dbg(&pdev->dev, "failed to create socinfo device\n");
return 0;
}
static void qcom_smem_remove(struct platform_device *pdev)
{
- platform_device_unregister(__smem->socinfo);
-
__smem = NULL;
}
diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c
deleted file mode 100644
index 003a2304d535c2655db566c644342dbc387e24a9..0000000000000000000000000000000000000000
--- a/drivers/soc/qcom/socinfo.c
+++ /dev/null
@@ -1,931 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (c) 2009-2017, The Linux Foundation. All rights reserved.
- * Copyright (c) 2017-2019, Linaro Ltd.
- */
-
-#include <linux/debugfs.h>
-#include <linux/err.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/random.h>
-#include <linux/slab.h>
-#include <linux/soc/qcom/smem.h>
-#include <linux/soc/qcom/socinfo.h>
-#include <linux/string.h>
-#include <linux/stringify.h>
-#include <linux/sys_soc.h>
-#include <linux/types.h>
-
-#include <linux/unaligned.h>
-
-#include <dt-bindings/arm/qcom,ids.h>
-
-/* Helper macros to create soc_id table */
-#define qcom_board_id(id) QCOM_ID_ ## id, __stringify(id)
-#define qcom_board_id_named(id, name) QCOM_ID_ ## id, (name)
-
-#ifdef CONFIG_DEBUG_FS
-#define SMEM_IMAGE_VERSION_BLOCKS_COUNT 32
-#define SMEM_IMAGE_VERSION_SIZE 4096
-#define SMEM_IMAGE_VERSION_NAME_SIZE 75
-#define SMEM_IMAGE_VERSION_VARIANT_SIZE 20
-#define SMEM_IMAGE_VERSION_OEM_SIZE 32
-
-/*
- * SMEM Image table indices
- */
-#define SMEM_IMAGE_TABLE_BOOT_INDEX 0
-#define SMEM_IMAGE_TABLE_TZ_INDEX 1
-#define SMEM_IMAGE_TABLE_TZSECAPP_INDEX 2
-#define SMEM_IMAGE_TABLE_RPM_INDEX 3
-#define SMEM_IMAGE_TABLE_SDI_INDEX 4
-#define SMEM_IMAGE_TABLE_HYP_INDEX 5
-#define SMEM_IMAGE_TABLE_ADSP1_INDEX 6
-#define SMEM_IMAGE_TABLE_ADSP2_INDEX 7
-#define SMEM_IMAGE_TABLE_CDSP2_INDEX 8
-#define SMEM_IMAGE_TABLE_APPSBL_INDEX 9
-#define SMEM_IMAGE_TABLE_APPS_INDEX 10
-#define SMEM_IMAGE_TABLE_MPSS_INDEX 11
-#define SMEM_IMAGE_TABLE_ADSP_INDEX 12
-#define SMEM_IMAGE_TABLE_CNSS_INDEX 13
-#define SMEM_IMAGE_TABLE_VIDEO_INDEX 14
-#define SMEM_IMAGE_TABLE_DSPS_INDEX 15
-#define SMEM_IMAGE_TABLE_CDSP_INDEX 16
-#define SMEM_IMAGE_TABLE_NPU_INDEX 17
-#define SMEM_IMAGE_TABLE_WPSS_INDEX 18
-#define SMEM_IMAGE_TABLE_CDSP1_INDEX 19
-#define SMEM_IMAGE_TABLE_GPDSP_INDEX 20
-#define SMEM_IMAGE_TABLE_GPDSP1_INDEX 21
-#define SMEM_IMAGE_TABLE_SENSORPD_INDEX 22
-#define SMEM_IMAGE_TABLE_AUDIOPD_INDEX 23
-#define SMEM_IMAGE_TABLE_OEMPD_INDEX 24
-#define SMEM_IMAGE_TABLE_CHARGERPD_INDEX 25
-#define SMEM_IMAGE_TABLE_OISPD_INDEX 26
-#define SMEM_IMAGE_TABLE_SOCCP_INDEX 27
-#define SMEM_IMAGE_TABLE_TME_INDEX 28
-#define SMEM_IMAGE_TABLE_GEARVM_INDEX 29
-#define SMEM_IMAGE_TABLE_UEFI_INDEX 30
-#define SMEM_IMAGE_TABLE_CDSP3_INDEX 31
-#define SMEM_IMAGE_TABLE_AUDIOPD_ADSP1_INDEX 32
-#define SMEM_IMAGE_TABLE_AUDIOPD_ADSP2_INDEX 33
-#define SMEM_IMAGE_TABLE_DCP_INDEX 34
-#define SMEM_IMAGE_TABLE_OOBS_INDEX 35
-#define SMEM_IMAGE_TABLE_OOBNS_INDEX 36
-#define SMEM_IMAGE_TABLE_DEVCFG_INDEX 37
-#define SMEM_IMAGE_TABLE_BTPD_INDEX 38
-#define SMEM_IMAGE_TABLE_QECP_INDEX 39
-
-#define SMEM_IMAGE_VERSION_TABLE 469
-#define SMEM_IMAGE_VERSION_TABLE_2 667
-
-/*
- * SMEM Image table names
- */
-static const char *const socinfo_image_names[] = {
- [SMEM_IMAGE_TABLE_ADSP1_INDEX] = "adsp1",
- [SMEM_IMAGE_TABLE_ADSP2_INDEX] = "adsp2",
- [SMEM_IMAGE_TABLE_ADSP_INDEX] = "adsp",
- [SMEM_IMAGE_TABLE_APPSBL_INDEX] = "appsbl",
- [SMEM_IMAGE_TABLE_APPS_INDEX] = "apps",
- [SMEM_IMAGE_TABLE_AUDIOPD_INDEX] = "audiopd",
- [SMEM_IMAGE_TABLE_AUDIOPD_ADSP1_INDEX] = "audiopd_adsp1",
- [SMEM_IMAGE_TABLE_AUDIOPD_ADSP2_INDEX] = "audiopd_adsp2",
- [SMEM_IMAGE_TABLE_BOOT_INDEX] = "boot",
- [SMEM_IMAGE_TABLE_BTPD_INDEX] = "btpd",
- [SMEM_IMAGE_TABLE_CDSP1_INDEX] = "cdsp1",
- [SMEM_IMAGE_TABLE_CDSP2_INDEX] = "cdsp2",
- [SMEM_IMAGE_TABLE_CDSP3_INDEX] = "cdsp3",
- [SMEM_IMAGE_TABLE_CDSP_INDEX] = "cdsp",
- [SMEM_IMAGE_TABLE_CHARGERPD_INDEX] = "chargerpd",
- [SMEM_IMAGE_TABLE_CNSS_INDEX] = "cnss",
- [SMEM_IMAGE_TABLE_DCP_INDEX] = "dcp",
- [SMEM_IMAGE_TABLE_DEVCFG_INDEX] = "devcfg",
- [SMEM_IMAGE_TABLE_DSPS_INDEX] = "dsps",
- [SMEM_IMAGE_TABLE_GEARVM_INDEX] = "gearvm",
- [SMEM_IMAGE_TABLE_GPDSP1_INDEX] = "gpdsp1",
- [SMEM_IMAGE_TABLE_GPDSP_INDEX] = "gpdsp",
- [SMEM_IMAGE_TABLE_HYP_INDEX] = "hyp",
- [SMEM_IMAGE_TABLE_MPSS_INDEX] = "mpss",
- [SMEM_IMAGE_TABLE_NPU_INDEX] = "npu",
- [SMEM_IMAGE_TABLE_OEMPD_INDEX] = "oempd",
- [SMEM_IMAGE_TABLE_OISPD_INDEX] = "oispd",
- [SMEM_IMAGE_TABLE_OOBNS_INDEX] = "oobns",
- [SMEM_IMAGE_TABLE_OOBS_INDEX] = "oobs",
- [SMEM_IMAGE_TABLE_QECP_INDEX] = "qecp",
- [SMEM_IMAGE_TABLE_RPM_INDEX] = "rpm",
- [SMEM_IMAGE_TABLE_SDI_INDEX] = "sdi",
- [SMEM_IMAGE_TABLE_SENSORPD_INDEX] = "sensorpd",
- [SMEM_IMAGE_TABLE_SOCCP_INDEX] = "soccp",
- [SMEM_IMAGE_TABLE_TME_INDEX] = "tme",
- [SMEM_IMAGE_TABLE_TZ_INDEX] = "tz",
- [SMEM_IMAGE_TABLE_TZSECAPP_INDEX] = "tzsecapp",
- [SMEM_IMAGE_TABLE_UEFI_INDEX] = "uefi",
- [SMEM_IMAGE_TABLE_VIDEO_INDEX] = "video",
- [SMEM_IMAGE_TABLE_WPSS_INDEX] = "wpss",
-};
-
-static const char *const pmic_models[] = {
- [0] = "Unknown PMIC model",
- [1] = "PM8941",
- [2] = "PM8841",
- [3] = "PM8019",
- [4] = "PM8226",
- [5] = "PM8110",
- [6] = "PMA8084",
- [7] = "PMI8962",
- [8] = "PMD9635",
- [9] = "PM8994",
- [10] = "PMI8994",
- [11] = "PM8916",
- [12] = "PM8004",
- [13] = "PM8909/PM8058",
- [14] = "PM8028",
- [15] = "PM8901",
- [16] = "PM8950/PM8027",
- [17] = "PMI8950/ISL9519",
- [18] = "PMK8001/PM8921",
- [19] = "PMI8996/PM8018",
- [20] = "PM8998/PM8015",
- [21] = "PMI8998/PM8014",
- [22] = "PM8821",
- [23] = "PM8038",
- [24] = "PM8005/PM8922",
- [25] = "PM8917/PM8937",
- [26] = "PM660L",
- [27] = "PM660",
- [30] = "PM8150",
- [31] = "PM8150L",
- [32] = "PM8150B",
- [33] = "PMK8002",
- [36] = "PM8009",
- [37] = "PMI632",
- [38] = "PM8150C",
- [40] = "PM6150",
- [41] = "SMB2351",
- [44] = "PM8008",
- [45] = "PM6125",
- [46] = "PM7250B",
- [47] = "PMK8350",
- [48] = "PM8350",
- [49] = "PM8350C",
- [50] = "PM8350B",
- [51] = "PMR735A",
- [52] = "PMR735B",
- [54] = "PM6350",
- [55] = "PM4125",
- [58] = "PM8450",
- [65] = "PM8010",
- [69] = "PM8550VS",
- [70] = "PM8550VE",
- [71] = "PM8550B",
- [72] = "PMR735D",
- [73] = "PM8550",
- [74] = "PMK8550",
- [78] = "PMM8650AU",
- [79] = "PMM8650AU_PSAIL",
- [80] = "PM7550",
- [82] = "PMC8380",
- [83] = "SMB2360",
- [91] = "PMIV0108",
-};
-
-struct socinfo_params {
- u32 raw_device_family;
- u32 hw_plat_subtype;
- u32 accessory_chip;
- u32 raw_device_num;
- u32 chip_family;
- u32 foundry_id;
- u32 plat_ver;
- u32 raw_ver;
- u32 hw_plat;
- u32 fmt;
- u32 nproduct_id;
- u32 num_clusters;
- u32 ncluster_array_offset;
- u32 num_subset_parts;
- u32 nsubset_parts_array_offset;
- u32 nmodem_supported;
- u32 feature_code;
- u32 pcode;
- u32 oem_variant;
- u32 num_func_clusters;
- u32 boot_cluster;
- u32 boot_core;
- u32 raw_package_type;
-};
-
-struct smem_image_version {
- char name[SMEM_IMAGE_VERSION_NAME_SIZE];
- char variant[SMEM_IMAGE_VERSION_VARIANT_SIZE];
- char pad;
- char oem[SMEM_IMAGE_VERSION_OEM_SIZE];
-};
-#endif /* CONFIG_DEBUG_FS */
-
-struct qcom_socinfo {
- struct soc_device *soc_dev;
- struct soc_device_attribute attr;
-#ifdef CONFIG_DEBUG_FS
- struct dentry *dbg_root;
- struct socinfo_params info;
-#endif /* CONFIG_DEBUG_FS */
-};
-
-struct soc_id {
- unsigned int id;
- const char *name;
-};
-
-static const struct soc_id soc_id[] = {
- { qcom_board_id(MSM8260) },
- { qcom_board_id(MSM8660) },
- { qcom_board_id(APQ8060) },
- { qcom_board_id(MSM8960) },
- { qcom_board_id(APQ8064) },
- { qcom_board_id(MSM8930) },
- { qcom_board_id(MSM8630) },
- { qcom_board_id(MSM8230) },
- { qcom_board_id(APQ8030) },
- { qcom_board_id(MSM8627) },
- { qcom_board_id(MSM8227) },
- { qcom_board_id(MSM8660A) },
- { qcom_board_id(MSM8260A) },
- { qcom_board_id(APQ8060A) },
- { qcom_board_id(MSM8974) },
- { qcom_board_id(MSM8225) },
- { qcom_board_id(MSM8625) },
- { qcom_board_id(MPQ8064) },
- { qcom_board_id(MSM8960AB) },
- { qcom_board_id(APQ8060AB) },
- { qcom_board_id(MSM8260AB) },
- { qcom_board_id(MSM8660AB) },
- { qcom_board_id(MSM8930AA) },
- { qcom_board_id(MSM8630AA) },
- { qcom_board_id(MSM8230AA) },
- { qcom_board_id(MSM8626) },
- { qcom_board_id(MSM8610) },
- { qcom_board_id(APQ8064AB) },
- { qcom_board_id(MSM8930AB) },
- { qcom_board_id(MSM8630AB) },
- { qcom_board_id(MSM8230AB) },
- { qcom_board_id(APQ8030AB) },
- { qcom_board_id(MSM8226) },
- { qcom_board_id(MSM8526) },
- { qcom_board_id(APQ8030AA) },
- { qcom_board_id(MSM8110) },
- { qcom_board_id(MSM8210) },
- { qcom_board_id(MSM8810) },
- { qcom_board_id(MSM8212) },
- { qcom_board_id(MSM8612) },
- { qcom_board_id(MSM8112) },
- { qcom_board_id(MSM8125) },
- { qcom_board_id(MSM8225Q) },
- { qcom_board_id(MSM8625Q) },
- { qcom_board_id(MSM8125Q) },
- { qcom_board_id(APQ8064AA) },
- { qcom_board_id(APQ8084) },
- { qcom_board_id(MSM8130) },
- { qcom_board_id(MSM8130AA) },
- { qcom_board_id(MSM8130AB) },
- { qcom_board_id(MSM8627AA) },
- { qcom_board_id(MSM8227AA) },
- { qcom_board_id(APQ8074) },
- { qcom_board_id(MSM8274) },
- { qcom_board_id(MSM8674) },
- { qcom_board_id(MDM9635) },
- { qcom_board_id_named(MSM8974PRO_AC, "MSM8974PRO-AC") },
- { qcom_board_id(MSM8126) },
- { qcom_board_id(APQ8026) },
- { qcom_board_id(MSM8926) },
- { qcom_board_id(IPQ8062) },
- { qcom_board_id(IPQ8064) },
- { qcom_board_id(IPQ8066) },
- { qcom_board_id(IPQ8068) },
- { qcom_board_id(MSM8326) },
- { qcom_board_id(MSM8916) },
- { qcom_board_id(MSM8994) },
- { qcom_board_id_named(APQ8074PRO_AA, "APQ8074PRO-AA") },
- { qcom_board_id_named(APQ8074PRO_AB, "APQ8074PRO-AB") },
- { qcom_board_id_named(APQ8074PRO_AC, "APQ8074PRO-AC") },
- { qcom_board_id_named(MSM8274PRO_AA, "MSM8274PRO-AA") },
- { qcom_board_id_named(MSM8274PRO_AB, "MSM8274PRO-AB") },
- { qcom_board_id_named(MSM8274PRO_AC, "MSM8274PRO-AC") },
- { qcom_board_id_named(MSM8674PRO_AA, "MSM8674PRO-AA") },
- { qcom_board_id_named(MSM8674PRO_AB, "MSM8674PRO-AB") },
- { qcom_board_id_named(MSM8674PRO_AC, "MSM8674PRO-AC") },
- { qcom_board_id_named(MSM8974PRO_AA, "MSM8974PRO-AA") },
- { qcom_board_id_named(MSM8974PRO_AB, "MSM8974PRO-AB") },
- { qcom_board_id(APQ8028) },
- { qcom_board_id(MSM8128) },
- { qcom_board_id(MSM8228) },
- { qcom_board_id(MSM8528) },
- { qcom_board_id(MSM8628) },
- { qcom_board_id(MSM8928) },
- { qcom_board_id(MSM8510) },
- { qcom_board_id(MSM8512) },
- { qcom_board_id(MSM8936) },
- { qcom_board_id(MDM9640) },
- { qcom_board_id(MSM8939) },
- { qcom_board_id(APQ8036) },
- { qcom_board_id(APQ8039) },
- { qcom_board_id(MSM8236) },
- { qcom_board_id(MSM8636) },
- { qcom_board_id(MSM8909) },
- { qcom_board_id(MSM8996) },
- { qcom_board_id(APQ8016) },
- { qcom_board_id(MSM8216) },
- { qcom_board_id(MSM8116) },
- { qcom_board_id(MSM8616) },
- { qcom_board_id(MSM8992) },
- { qcom_board_id(APQ8092) },
- { qcom_board_id(APQ8094) },
- { qcom_board_id(MSM8209) },
- { qcom_board_id(MSM8208) },
- { qcom_board_id(MDM9209) },
- { qcom_board_id(MDM9309) },
- { qcom_board_id(MDM9609) },
- { qcom_board_id(MSM8239) },
- { qcom_board_id(MSM8952) },
- { qcom_board_id(APQ8009) },
- { qcom_board_id(MSM8956) },
- { qcom_board_id(MSM8929) },
- { qcom_board_id(MSM8629) },
- { qcom_board_id(MSM8229) },
- { qcom_board_id(APQ8029) },
- { qcom_board_id(APQ8056) },
- { qcom_board_id(MSM8609) },
- { qcom_board_id(APQ8076) },
- { qcom_board_id(MSM8976) },
- { qcom_board_id(IPQ8065) },
- { qcom_board_id(IPQ8069) },
- { qcom_board_id(MDM9650) },
- { qcom_board_id(MDM9655) },
- { qcom_board_id(MDM9250) },
- { qcom_board_id(MDM9255) },
- { qcom_board_id(MDM9350) },
- { qcom_board_id(APQ8052) },
- { qcom_board_id(MDM9607) },
- { qcom_board_id(APQ8096) },
- { qcom_board_id(MSM8998) },
- { qcom_board_id(MSM8953) },
- { qcom_board_id(MSM8937) },
- { qcom_board_id(APQ8037) },
- { qcom_board_id(MDM8207) },
- { qcom_board_id(MDM9207) },
- { qcom_board_id(MDM9307) },
- { qcom_board_id(MDM9628) },
- { qcom_board_id(MSM8909W) },
- { qcom_board_id(APQ8009W) },
- { qcom_board_id(MSM8996L) },
- { qcom_board_id(MSM8917) },
- { qcom_board_id(APQ8053) },
- { qcom_board_id(MSM8996SG) },
- { qcom_board_id(APQ8017) },
- { qcom_board_id(MSM8217) },
- { qcom_board_id(MSM8617) },
- { qcom_board_id(MSM8996AU) },
- { qcom_board_id(APQ8096AU) },
- { qcom_board_id(APQ8096SG) },
- { qcom_board_id(MSM8940) },
- { qcom_board_id(SDX201) },
- { qcom_board_id(SDM660) },
- { qcom_board_id(SDM630) },
- { qcom_board_id(APQ8098) },
- { qcom_board_id(MSM8920) },
- { qcom_board_id(SDM845) },
- { qcom_board_id(MDM9206) },
- { qcom_board_id(IPQ8074) },
- { qcom_board_id(SDA660) },
- { qcom_board_id(SDM658) },
- { qcom_board_id(SDA658) },
- { qcom_board_id(SDA630) },
- { qcom_board_id(MSM8905) },
- { qcom_board_id(SDX202) },
- { qcom_board_id(SDM670) },
- { qcom_board_id(SDM450) },
- { qcom_board_id(SM8150) },
- { qcom_board_id(SDA845) },
- { qcom_board_id(IPQ8072) },
- { qcom_board_id(IPQ8076) },
- { qcom_board_id(IPQ8078) },
- { qcom_board_id(SDM636) },
- { qcom_board_id(SDA636) },
- { qcom_board_id(SDM632) },
- { qcom_board_id(SDA632) },
- { qcom_board_id(SDA450) },
- { qcom_board_id(SDM439) },
- { qcom_board_id(SDM429) },
- { qcom_board_id(SM8250) },
- { qcom_board_id(SA8155) },
- { qcom_board_id(SDA439) },
- { qcom_board_id(SDA429) },
- { qcom_board_id(SM7150) },
- { qcom_board_id(SM7150P) },
- { qcom_board_id(IPQ8070) },
- { qcom_board_id(IPQ8071) },
- { qcom_board_id(QM215) },
- { qcom_board_id(IPQ8072A) },
- { qcom_board_id(IPQ8074A) },
- { qcom_board_id(IPQ8076A) },
- { qcom_board_id(IPQ8078A) },
- { qcom_board_id(SM6125) },
- { qcom_board_id(IPQ8070A) },
- { qcom_board_id(IPQ8071A) },
- { qcom_board_id(IPQ8172) },
- { qcom_board_id(IPQ8173) },
- { qcom_board_id(IPQ8174) },
- { qcom_board_id(IPQ6018) },
- { qcom_board_id(IPQ6028) },
- { qcom_board_id(SDM429W) },
- { qcom_board_id(SM4250) },
- { qcom_board_id(IPQ6000) },
- { qcom_board_id(IPQ6010) },
- { qcom_board_id(SC7180) },
- { qcom_board_id(SM6350) },
- { qcom_board_id(QCM2150) },
- { qcom_board_id(SDA429W) },
- { qcom_board_id(SM8350) },
- { qcom_board_id(QCM2290) },
- { qcom_board_id(SM7125) },
- { qcom_board_id(SM6115) },
- { qcom_board_id(IPQ5010) },
- { qcom_board_id(IPQ5018) },
- { qcom_board_id(IPQ5028) },
- { qcom_board_id(SC8280XP) },
- { qcom_board_id(IPQ6005) },
- { qcom_board_id(QRB5165) },
- { qcom_board_id(SM8450) },
- { qcom_board_id(SM7225) },
- { qcom_board_id(SA8295P) },
- { qcom_board_id(SA8540P) },
- { qcom_board_id(QCM4290) },
- { qcom_board_id(QCS4290) },
- { qcom_board_id(SM7325) },
- { qcom_board_id_named(SM8450_2, "SM8450") },
- { qcom_board_id_named(SM8450_3, "SM8450") },
- { qcom_board_id(SC7280) },
- { qcom_board_id(SC7180P) },
- { qcom_board_id(QCM6490) },
- { qcom_board_id(QCS6490) },
- { qcom_board_id(SM7325P) },
- { qcom_board_id(IPQ5000) },
- { qcom_board_id(IPQ0509) },
- { qcom_board_id(IPQ0518) },
- { qcom_board_id(SM6375) },
- { qcom_board_id(IPQ9514) },
- { qcom_board_id(IPQ9550) },
- { qcom_board_id(IPQ9554) },
- { qcom_board_id(IPQ9570) },
- { qcom_board_id(IPQ9574) },
- { qcom_board_id(SM8550) },
- { qcom_board_id(IPQ5016) },
- { qcom_board_id(IPQ9510) },
- { qcom_board_id(QRB4210) },
- { qcom_board_id(QRB2210) },
- { qcom_board_id(SAR2130P) },
- { qcom_board_id(SM8475) },
- { qcom_board_id(SM8475P) },
- { qcom_board_id(SA8255P) },
- { qcom_board_id(SA8775P) },
- { qcom_board_id(QRU1000) },
- { qcom_board_id(SM8475_2) },
- { qcom_board_id(QDU1000) },
- { qcom_board_id(X1E80100) },
- { qcom_board_id(SM8650) },
- { qcom_board_id(SM4450) },
- { qcom_board_id(SAR1130P) },
- { qcom_board_id(QDU1010) },
- { qcom_board_id(QRU1032) },
- { qcom_board_id(QRU1052) },
- { qcom_board_id(QRU1062) },
- { qcom_board_id(IPQ5332) },
- { qcom_board_id(IPQ5322) },
- { qcom_board_id(IPQ5312) },
- { qcom_board_id(IPQ5302) },
- { qcom_board_id(QCS8550) },
- { qcom_board_id(QCM8550) },
- { qcom_board_id(SM8750) },
- { qcom_board_id(IPQ5300) },
- { qcom_board_id(SM7635) },
- { qcom_board_id(SM6650) },
- { qcom_board_id(SM6650P) },
- { qcom_board_id(IPQ5321) },
- { qcom_board_id(IPQ5424) },
- { qcom_board_id(QCM6690) },
- { qcom_board_id(QCS6690) },
- { qcom_board_id(SM8850) },
- { qcom_board_id(IPQ5404) },
- { qcom_board_id(QCS9100) },
- { qcom_board_id(QCS8300) },
- { qcom_board_id(QCS8275) },
- { qcom_board_id(QCS9075) },
- { qcom_board_id(QCS615) },
-};
-
-static const char *socinfo_machine(struct device *dev, unsigned int id)
-{
- int idx;
-
- for (idx = 0; idx < ARRAY_SIZE(soc_id); idx++) {
- if (soc_id[idx].id == id)
- return soc_id[idx].name;
- }
-
- return NULL;
-}
-
-#ifdef CONFIG_DEBUG_FS
-
-#define QCOM_OPEN(name, _func) \
-static int qcom_open_##name(struct inode *inode, struct file *file) \
-{ \
- return single_open(file, _func, inode->i_private); \
-} \
- \
-static const struct file_operations qcom_ ##name## _ops = { \
- .open = qcom_open_##name, \
- .read = seq_read, \
- .llseek = seq_lseek, \
- .release = single_release, \
-}
-
-#define DEBUGFS_ADD(info, name) \
- debugfs_create_file(__stringify(name), 0444, \
- qcom_socinfo->dbg_root, \
- info, &qcom_ ##name## _ops)
-
-
-static int qcom_show_build_id(struct seq_file *seq, void *p)
-{
- struct socinfo *socinfo = seq->private;
-
- seq_printf(seq, "%s\n", socinfo->build_id);
-
- return 0;
-}
-
-static int qcom_show_pmic_model(struct seq_file *seq, void *p)
-{
- struct socinfo *socinfo = seq->private;
- int model = SOCINFO_MINOR(le32_to_cpu(socinfo->pmic_model));
-
- if (model < 0)
- return -EINVAL;
-
- if (model < ARRAY_SIZE(pmic_models) && pmic_models[model])
- seq_printf(seq, "%s\n", pmic_models[model]);
- else
- seq_printf(seq, "unknown (%d)\n", model);
-
- return 0;
-}
-
-static int qcom_show_pmic_model_array(struct seq_file *seq, void *p)
-{
- struct socinfo *socinfo = seq->private;
- unsigned int num_pmics = le32_to_cpu(socinfo->num_pmics);
- unsigned int pmic_array_offset = le32_to_cpu(socinfo->pmic_array_offset);
- int i;
- void *ptr = socinfo;
-
- ptr += pmic_array_offset;
-
- /* No need for bounds checking, it happened at socinfo_debugfs_init */
- for (i = 0; i < num_pmics; i++) {
- unsigned int model = SOCINFO_MINOR(get_unaligned_le32(ptr + 2 * i * sizeof(u32)));
- unsigned int die_rev = get_unaligned_le32(ptr + (2 * i + 1) * sizeof(u32));
-
- if (model < ARRAY_SIZE(pmic_models) && pmic_models[model])
- seq_printf(seq, "%s %u.%u\n", pmic_models[model],
- SOCINFO_MAJOR(die_rev),
- SOCINFO_MINOR(die_rev));
- else
- seq_printf(seq, "unknown (%d)\n", model);
- }
-
- return 0;
-}
-
-static int qcom_show_pmic_die_revision(struct seq_file *seq, void *p)
-{
- struct socinfo *socinfo = seq->private;
-
- seq_printf(seq, "%u.%u\n",
- SOCINFO_MAJOR(le32_to_cpu(socinfo->pmic_die_rev)),
- SOCINFO_MINOR(le32_to_cpu(socinfo->pmic_die_rev)));
-
- return 0;
-}
-
-static int qcom_show_chip_id(struct seq_file *seq, void *p)
-{
- struct socinfo *socinfo = seq->private;
-
- seq_printf(seq, "%s\n", socinfo->chip_id);
-
- return 0;
-}
-
-QCOM_OPEN(build_id, qcom_show_build_id);
-QCOM_OPEN(pmic_model, qcom_show_pmic_model);
-QCOM_OPEN(pmic_model_array, qcom_show_pmic_model_array);
-QCOM_OPEN(pmic_die_rev, qcom_show_pmic_die_revision);
-QCOM_OPEN(chip_id, qcom_show_chip_id);
-
-#define DEFINE_IMAGE_OPS(type) \
-static int show_image_##type(struct seq_file *seq, void *p) \
-{ \
- struct smem_image_version *image_version = seq->private; \
- if (image_version->type[0] != '\0') \
- seq_printf(seq, "%s\n", image_version->type); \
- return 0; \
-} \
-static int open_image_##type(struct inode *inode, struct file *file) \
-{ \
- return single_open(file, show_image_##type, inode->i_private); \
-} \
- \
-static const struct file_operations qcom_image_##type##_ops = { \
- .open = open_image_##type, \
- .read = seq_read, \
- .llseek = seq_lseek, \
- .release = single_release, \
-}
-
-DEFINE_IMAGE_OPS(name);
-DEFINE_IMAGE_OPS(variant);
-DEFINE_IMAGE_OPS(oem);
-
-static void socinfo_debugfs_init(struct qcom_socinfo *qcom_socinfo,
- struct socinfo *info, size_t info_size)
-{
- struct smem_image_version *versions;
- struct dentry *dentry;
- size_t size;
- int i, j;
- unsigned int num_pmics;
- unsigned int pmic_array_offset;
-
- qcom_socinfo->dbg_root = debugfs_create_dir("qcom_socinfo", NULL);
-
- qcom_socinfo->info.fmt = __le32_to_cpu(info->fmt);
-
- debugfs_create_x32("info_fmt", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.fmt);
-
- switch (qcom_socinfo->info.fmt) {
- case SOCINFO_VERSION(0, 23):
- case SOCINFO_VERSION(0, 22):
- case SOCINFO_VERSION(0, 21):
- case SOCINFO_VERSION(0, 20):
- qcom_socinfo->info.raw_package_type = __le32_to_cpu(info->raw_package_type);
- debugfs_create_u32("raw_package_type", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.raw_package_type);
- fallthrough;
- case SOCINFO_VERSION(0, 19):
- qcom_socinfo->info.num_func_clusters = __le32_to_cpu(info->num_func_clusters);
- qcom_socinfo->info.boot_cluster = __le32_to_cpu(info->boot_cluster);
- qcom_socinfo->info.boot_core = __le32_to_cpu(info->boot_core);
-
- debugfs_create_u32("num_func_clusters", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.num_func_clusters);
- debugfs_create_u32("boot_cluster", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.boot_cluster);
- debugfs_create_u32("boot_core", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.boot_core);
- fallthrough;
- case SOCINFO_VERSION(0, 18):
- case SOCINFO_VERSION(0, 17):
- qcom_socinfo->info.oem_variant = __le32_to_cpu(info->oem_variant);
- debugfs_create_u32("oem_variant", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.oem_variant);
- fallthrough;
- case SOCINFO_VERSION(0, 16):
- qcom_socinfo->info.feature_code = __le32_to_cpu(info->feature_code);
- qcom_socinfo->info.pcode = __le32_to_cpu(info->pcode);
-
- debugfs_create_u32("feature_code", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.feature_code);
- debugfs_create_u32("pcode", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.pcode);
- fallthrough;
- case SOCINFO_VERSION(0, 15):
- qcom_socinfo->info.nmodem_supported = __le32_to_cpu(info->nmodem_supported);
-
- debugfs_create_u32("nmodem_supported", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.nmodem_supported);
- fallthrough;
- case SOCINFO_VERSION(0, 14):
- qcom_socinfo->info.num_clusters = __le32_to_cpu(info->num_clusters);
- qcom_socinfo->info.ncluster_array_offset = __le32_to_cpu(info->ncluster_array_offset);
- qcom_socinfo->info.num_subset_parts = __le32_to_cpu(info->num_subset_parts);
- qcom_socinfo->info.nsubset_parts_array_offset =
- __le32_to_cpu(info->nsubset_parts_array_offset);
-
- debugfs_create_u32("num_clusters", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.num_clusters);
- debugfs_create_u32("ncluster_array_offset", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.ncluster_array_offset);
- debugfs_create_u32("num_subset_parts", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.num_subset_parts);
- debugfs_create_u32("nsubset_parts_array_offset", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.nsubset_parts_array_offset);
- fallthrough;
- case SOCINFO_VERSION(0, 13):
- qcom_socinfo->info.nproduct_id = __le32_to_cpu(info->nproduct_id);
-
- debugfs_create_u32("nproduct_id", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.nproduct_id);
- DEBUGFS_ADD(info, chip_id);
- fallthrough;
- case SOCINFO_VERSION(0, 12):
- qcom_socinfo->info.chip_family =
- __le32_to_cpu(info->chip_family);
- qcom_socinfo->info.raw_device_family =
- __le32_to_cpu(info->raw_device_family);
- qcom_socinfo->info.raw_device_num =
- __le32_to_cpu(info->raw_device_num);
-
- debugfs_create_x32("chip_family", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.chip_family);
- debugfs_create_x32("raw_device_family", 0444,
- qcom_socinfo->dbg_root,
- &qcom_socinfo->info.raw_device_family);
- debugfs_create_x32("raw_device_number", 0444,
- qcom_socinfo->dbg_root,
- &qcom_socinfo->info.raw_device_num);
- fallthrough;
- case SOCINFO_VERSION(0, 11):
- num_pmics = le32_to_cpu(info->num_pmics);
- pmic_array_offset = le32_to_cpu(info->pmic_array_offset);
- if (pmic_array_offset + 2 * num_pmics * sizeof(u32) <= info_size)
- DEBUGFS_ADD(info, pmic_model_array);
- fallthrough;
- case SOCINFO_VERSION(0, 10):
- case SOCINFO_VERSION(0, 9):
- qcom_socinfo->info.foundry_id = __le32_to_cpu(info->foundry_id);
-
- debugfs_create_u32("foundry_id", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.foundry_id);
- fallthrough;
- case SOCINFO_VERSION(0, 8):
- case SOCINFO_VERSION(0, 7):
- DEBUGFS_ADD(info, pmic_model);
- DEBUGFS_ADD(info, pmic_die_rev);
- fallthrough;
- case SOCINFO_VERSION(0, 6):
- qcom_socinfo->info.hw_plat_subtype =
- __le32_to_cpu(info->hw_plat_subtype);
-
- debugfs_create_u32("hardware_platform_subtype", 0444,
- qcom_socinfo->dbg_root,
- &qcom_socinfo->info.hw_plat_subtype);
- fallthrough;
- case SOCINFO_VERSION(0, 5):
- qcom_socinfo->info.accessory_chip =
- __le32_to_cpu(info->accessory_chip);
-
- debugfs_create_u32("accessory_chip", 0444,
- qcom_socinfo->dbg_root,
- &qcom_socinfo->info.accessory_chip);
- fallthrough;
- case SOCINFO_VERSION(0, 4):
- qcom_socinfo->info.plat_ver = __le32_to_cpu(info->plat_ver);
-
- debugfs_create_u32("platform_version", 0444,
- qcom_socinfo->dbg_root,
- &qcom_socinfo->info.plat_ver);
- fallthrough;
- case SOCINFO_VERSION(0, 3):
- qcom_socinfo->info.hw_plat = __le32_to_cpu(info->hw_plat);
-
- debugfs_create_u32("hardware_platform", 0444,
- qcom_socinfo->dbg_root,
- &qcom_socinfo->info.hw_plat);
- fallthrough;
- case SOCINFO_VERSION(0, 2):
- qcom_socinfo->info.raw_ver = __le32_to_cpu(info->raw_ver);
-
- debugfs_create_u32("raw_version", 0444, qcom_socinfo->dbg_root,
- &qcom_socinfo->info.raw_ver);
- fallthrough;
- case SOCINFO_VERSION(0, 1):
- DEBUGFS_ADD(info, build_id);
- break;
- }
-
- for (i = 0, j = 0; i < ARRAY_SIZE(socinfo_image_names); i++, j++) {
- if (!socinfo_image_names[i])
- continue;
-
- if (i == 0) {
- versions = qcom_smem_get(QCOM_SMEM_HOST_ANY,
- SMEM_IMAGE_VERSION_TABLE,
- &size);
- } else if (i == 32) {
- versions = qcom_smem_get(QCOM_SMEM_HOST_ANY,
- SMEM_IMAGE_VERSION_TABLE_2,
- &size);
- if (IS_ERR(versions))
- break;
-
- j = 0;
- }
-
- dentry = debugfs_create_dir(socinfo_image_names[i],
- qcom_socinfo->dbg_root);
- debugfs_create_file("name", 0444, dentry, &versions[j],
- &qcom_image_name_ops);
- debugfs_create_file("variant", 0444, dentry, &versions[j],
- &qcom_image_variant_ops);
- debugfs_create_file("oem", 0444, dentry, &versions[j],
- &qcom_image_oem_ops);
- }
-}
-
-static void socinfo_debugfs_exit(struct qcom_socinfo *qcom_socinfo)
-{
- debugfs_remove_recursive(qcom_socinfo->dbg_root);
-}
-#else
-static void socinfo_debugfs_init(struct qcom_socinfo *qcom_socinfo,
- struct socinfo *info, size_t info_size)
-{
-}
-static void socinfo_debugfs_exit(struct qcom_socinfo *qcom_socinfo) { }
-#endif /* CONFIG_DEBUG_FS */
-
-static int qcom_socinfo_probe(struct platform_device *pdev)
-{
- struct qcom_socinfo *qs;
- struct socinfo *info;
- size_t item_size;
-
- info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID,
- &item_size);
- if (IS_ERR(info)) {
- dev_err(&pdev->dev, "Couldn't find socinfo\n");
- return PTR_ERR(info);
- }
-
- qs = devm_kzalloc(&pdev->dev, sizeof(*qs), GFP_KERNEL);
- if (!qs)
- return -ENOMEM;
-
- qs->attr.family = "Snapdragon";
- qs->attr.machine = socinfo_machine(&pdev->dev,
- le32_to_cpu(info->id));
- qs->attr.soc_id = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%u",
- le32_to_cpu(info->id));
- qs->attr.revision = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%u.%u",
- SOCINFO_MAJOR(le32_to_cpu(info->ver)),
- SOCINFO_MINOR(le32_to_cpu(info->ver)));
- if (!qs->attr.soc_id || !qs->attr.revision)
- return -ENOMEM;
-
- if (offsetofend(struct socinfo, serial_num) <= item_size) {
- qs->attr.serial_number = devm_kasprintf(&pdev->dev, GFP_KERNEL,
- "%u",
- le32_to_cpu(info->serial_num));
- if (!qs->attr.serial_number)
- return -ENOMEM;
- }
-
- qs->soc_dev = soc_device_register(&qs->attr);
- if (IS_ERR(qs->soc_dev))
- return PTR_ERR(qs->soc_dev);
-
- socinfo_debugfs_init(qs, info, item_size);
-
- /* Feed the soc specific unique data into entropy pool */
- add_device_randomness(info, item_size);
-
- platform_set_drvdata(pdev, qs);
-
- return 0;
-}
-
-static void qcom_socinfo_remove(struct platform_device *pdev)
-{
- struct qcom_socinfo *qs = platform_get_drvdata(pdev);
-
- soc_device_unregister(qs->soc_dev);
-
- socinfo_debugfs_exit(qs);
-}
-
-static struct platform_driver qcom_socinfo_driver = {
- .probe = qcom_socinfo_probe,
- .remove = qcom_socinfo_remove,
- .driver = {
- .name = "qcom-socinfo",
- },
-};
-
-module_platform_driver(qcom_socinfo_driver);
-
-MODULE_DESCRIPTION("Qualcomm SoCinfo driver");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:qcom-socinfo");
diff --git a/drivers/soc/qcom/socinfo/Makefile b/drivers/soc/qcom/socinfo/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..8cdd77f46a22aeb97bcf1eeb90418db8f6352d3b
--- /dev/null
+++ b/drivers/soc/qcom/socinfo/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
diff --git a/drivers/soc/qcom/socinfo/bindings.rs b/drivers/soc/qcom/socinfo/bindings.rs
new file mode 100644
index 0000000000000000000000000000000000000000..243fd2adf26ccf417166a08465b2b791030a2364
--- /dev/null
+++ b/drivers/soc/qcom/socinfo/bindings.rs
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use kernel::{
+ auxiliary,
+ bindings,
+ device::{
+ Bound,
+ Device, //
+ },
+ error::from_err_ptr,
+ ffi::c_char,
+ io::{
+ Io,
+ Mmio,
+ MmioRaw, //
+ },
+ macros::{
+ AsBytes,
+ FromBytes, //
+ },
+ prelude::*,
+ transmute::AsBytes,
+ types::ARef, //
+};
+
+// INVARIANT: raw is a region or subregion returned by get_smem_aux when presented with
+// the auxdev in dev.
+pub(crate) struct Smem {
+ dev: ARef<Device>,
+ raw: MmioRaw,
+}
+
+impl Smem {
+ pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
+ if *dev != *self.dev {
+ return None;
+ }
+
+ // SAFETY: By our invariant, this was a subrange of what was returned by smem_aux_get, for
+ // self.dev, and by our above check, that auxdev is still available.
+ Some(unsafe { Mmio::from_raw(&self.raw) })
+ }
+
+ pub(crate) fn subrange(&self, offset: usize, len: usize) -> Option<Self> {
+ let remaining_len: usize = self.raw.maxsize().checked_sub(offset)?;
+ remaining_len.checked_sub(len)?;
+ // INVARIANT: Maintained because offset is positive, and the length fits.
+ Some(Self {
+ dev: self.dev.clone(),
+ raw: MmioRaw::new(self.raw.addr() + offset, len).ok()?,
+ })
+ }
+
+ pub(crate) fn maxsize(&self) -> usize {
+ self.raw.maxsize()
+ }
+
+ pub(crate) fn get(dev: &auxiliary::Device<Bound>, host: i32, item: u32) -> Result<Self> {
+ let mut size = 0;
+ // SAFETY: qcom_smem_get_aux only requires that the size pointer be a writable size_t,
+ // and that aux be an aux device.
+ let err_ptr =
+ unsafe { bindings::qcom_smem_get_aux(dev.as_raw(), host as u32, item, &mut size) };
+ let ptr = from_err_ptr(err_ptr)?;
+ let base_dev: &Device<Bound> = dev.as_ref();
+ // INVARIANT: If qcom_smem_get_aux does not return an error, the returned pointer points to
+ // a readable IO region that qcom-smem will keep mapped while our provided aux device is
+ // loaded.
+ Ok(Self {
+ dev: base_dev.into(),
+ raw: MmioRaw::new(ptr as usize, size)?,
+ })
+ }
+}
+
+pub(crate) const SMEM_IMAGE_VERSION_TABLE: u32 = 469;
+pub(crate) const SMEM_IMAGE_VERSION_TABLE_2: u32 = 667;
+
+pub(crate) use bindings::socinfo;
+
+/// Produce `socinfo` by initializing from a byte buffer that may be too small.
+/// Code using the resulting value is expected to use the info_fmt field to determine
+/// which fields have meaningful values.
+pub(crate) fn socinfo_from_partial_bytes(soc_info_mem: &Mmio) -> Result<socinfo> {
+ let mut soc_info = socinfo::default();
+ let byte_view: &mut [u8] = soc_info.as_bytes_mut();
+ let len = core::cmp::min(soc_info_mem.maxsize(), byte_view.len());
+ soc_info_mem.try_copy_to(&mut byte_view[..len], 0)?;
+ Ok(soc_info)
+}
+
+#[derive(AsBytes, FromBytes, Default, Copy, Clone)]
+#[repr(C)]
+pub(crate) struct PmicEntry {
+ pub(crate) model: u32,
+ pub(crate) die_rev: u32,
+}
+
+const SMEM_IMAGE_VERSION_NAME_SIZE: usize = 75;
+const SMEM_IMAGE_VERSION_VARIANT_SIZE: usize = 20;
+const SMEM_IMAGE_VERSION_OEM_SIZE: usize = 32;
+
+#[derive(AsBytes, FromBytes)]
+#[repr(C)]
+pub(crate) struct ImageVersion {
+ pub(crate) name: [c_char; SMEM_IMAGE_VERSION_NAME_SIZE],
+ pub(crate) variant: [c_char; SMEM_IMAGE_VERSION_VARIANT_SIZE],
+ pub(crate) pad: c_char,
+ pub(crate) oem: [c_char; SMEM_IMAGE_VERSION_OEM_SIZE],
+}
+
+impl Default for ImageVersion {
+ fn default() -> Self {
+ ImageVersion {
+ name: [0; SMEM_IMAGE_VERSION_NAME_SIZE],
+ variant: [0; SMEM_IMAGE_VERSION_VARIANT_SIZE],
+ pad: 0,
+ oem: [0; SMEM_IMAGE_VERSION_OEM_SIZE],
+ }
+ }
+}
diff --git a/drivers/soc/qcom/socinfo/data.rs b/drivers/soc/qcom/socinfo/data.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e33e040ae34ddfd2acd1e1e64f5e35cc78e85483
--- /dev/null
+++ b/drivers/soc/qcom/socinfo/data.rs
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Data tables for QCom SocInfo driver
+use kernel::{
+ c_str,
+ prelude::*,
+ sparse_array, //
+};
+
+macro_rules! soc_ids {
+ { $( $entry:tt ),* $(,)? } => {
+ pub(crate) static SOC_IDS: &[SocId] = &[
+ $(
+ soc_id_entry!($entry)
+ ),*
+ ];
+ }
+}
+
+macro_rules! soc_id_entry {
+ ($id:ident) => {
+ kernel::macros::paste! {
+ SocId {
+ id: kernel::bindings::[<QCOM_ID_ $id>],
+ name: c_str!(stringify!($id)),
+ }
+ }
+ };
+ (($id:ident, $name:literal)) => {
+ kernel::macros::paste! {
+ SocId {
+ id: kernel::bindings::[<QCOM_ID_ $id>],
+ name: c_str!($name),
+ }
+ }
+ };
+}
+
+pub(crate) struct SocId {
+ pub(crate) id: u32,
+ pub(crate) name: &'static CStr,
+}
+
+soc_ids! {
+ MSM8260,
+ MSM8660,
+ APQ8060,
+ MSM8960,
+ APQ8064,
+ MSM8930,
+ MSM8630,
+ MSM8230,
+ APQ8030,
+ MSM8627,
+ MSM8227,
+ MSM8660A,
+ MSM8260A,
+ APQ8060A,
+ MSM8974,
+ MSM8225,
+ MSM8625,
+ MPQ8064,
+ MSM8960AB,
+ APQ8060AB,
+ MSM8260AB,
+ MSM8660AB,
+ MSM8930AA,
+ MSM8630AA,
+ MSM8230AA,
+ MSM8626,
+ MSM8610,
+ APQ8064AB,
+ MSM8930AB,
+ MSM8630AB,
+ MSM8230AB,
+ APQ8030AB,
+ MSM8226,
+ MSM8526,
+ APQ8030AA,
+ MSM8110,
+ MSM8210,
+ MSM8810,
+ MSM8212,
+ MSM8612,
+ MSM8112,
+ MSM8125,
+ MSM8225Q,
+ MSM8625Q,
+ MSM8125Q,
+ APQ8064AA,
+ APQ8084,
+ MSM8130,
+ MSM8130AA,
+ MSM8130AB,
+ MSM8627AA,
+ MSM8227AA,
+ APQ8074,
+ MSM8274,
+ MSM8674,
+ MDM9635,
+ (MSM8974PRO_AC, "MSM8974PRO-AC"),
+ MSM8126,
+ APQ8026,
+ MSM8926,
+ IPQ8062,
+ IPQ8064,
+ IPQ8066,
+ IPQ8068,
+ MSM8326,
+ MSM8916,
+ MSM8994,
+ (APQ8074PRO_AA, "APQ8074PRO-AA"),
+ (APQ8074PRO_AB, "APQ8074PRO-AB"),
+ (APQ8074PRO_AC, "APQ8074PRO-AC"),
+ (MSM8274PRO_AA, "MSM8274PRO-AA"),
+ (MSM8274PRO_AB, "MSM8274PRO-AB"),
+ (MSM8274PRO_AC, "MSM8274PRO-AC"),
+ (MSM8674PRO_AA, "MSM8674PRO-AA"),
+ (MSM8674PRO_AB, "MSM8674PRO-AB"),
+ (MSM8674PRO_AC, "MSM8674PRO-AC"),
+ (MSM8974PRO_AA, "MSM8974PRO-AA"),
+ (MSM8974PRO_AB, "MSM8974PRO-AB"),
+ APQ8028,
+ MSM8128,
+ MSM8228,
+ MSM8528,
+ MSM8628,
+ MSM8928,
+ MSM8510,
+ MSM8512,
+ MSM8936,
+ MDM9640,
+ MSM8939,
+ APQ8036,
+ APQ8039,
+ MSM8236,
+ MSM8636,
+ MSM8909,
+ MSM8996,
+ APQ8016,
+ MSM8216,
+ MSM8116,
+ MSM8616,
+ MSM8992,
+ APQ8092,
+ APQ8094,
+ MSM8209,
+ MSM8208,
+ MDM9209,
+ MDM9309,
+ MDM9609,
+ MSM8239,
+ MSM8952,
+ APQ8009,
+ MSM8956,
+ MSM8929,
+ MSM8629,
+ MSM8229,
+ APQ8029,
+ APQ8056,
+ MSM8609,
+ APQ8076,
+ MSM8976,
+ IPQ8065,
+ IPQ8069,
+ MDM9650,
+ MDM9655,
+ MDM9250,
+ MDM9255,
+ MDM9350,
+ APQ8052,
+ MDM9607,
+ APQ8096,
+ MSM8998,
+ MSM8953,
+ MSM8937,
+ APQ8037,
+ MDM8207,
+ MDM9207,
+ MDM9307,
+ MDM9628,
+ MSM8909W,
+ APQ8009W,
+ MSM8996L,
+ MSM8917,
+ APQ8053,
+ MSM8996SG,
+ APQ8017,
+ MSM8217,
+ MSM8617,
+ MSM8996AU,
+ APQ8096AU,
+ APQ8096SG,
+ MSM8940,
+ SDX201,
+ SDM660,
+ SDM630,
+ APQ8098,
+ MSM8920,
+ SDM845,
+ MDM9206,
+ IPQ8074,
+ SDA660,
+ SDM658,
+ SDA658,
+ SDA630,
+ MSM8905,
+ SDX202,
+ SDM670,
+ SDM450,
+ SM8150,
+ SDA845,
+ IPQ8072,
+ IPQ8076,
+ IPQ8078,
+ SDM636,
+ SDA636,
+ SDM632,
+ SDA632,
+ SDA450,
+ SDM439,
+ SDM429,
+ SM8250,
+ SA8155,
+ SDA439,
+ SDA429,
+ SM7150,
+ SM7150P,
+ IPQ8070,
+ IPQ8071,
+ QM215,
+ IPQ8072A,
+ IPQ8074A,
+ IPQ8076A,
+ IPQ8078A,
+ SM6125,
+ IPQ8070A,
+ IPQ8071A,
+ IPQ8172,
+ IPQ8173,
+ IPQ8174,
+ IPQ6018,
+ IPQ6028,
+ SDM429W,
+ SM4250,
+ IPQ6000,
+ IPQ6010,
+ SC7180,
+ SM6350,
+ QCM2150,
+ SDA429W,
+ SM8350,
+ QCM2290,
+ SM7125,
+ SM6115,
+ IPQ5010,
+ IPQ5018,
+ IPQ5028,
+ SC8280XP,
+ IPQ6005,
+ QRB5165,
+ SM8450,
+ SM7225,
+ SA8295P,
+ SA8540P,
+ QCM4290,
+ QCS4290,
+ SM7325,
+ (SM8450_2, "SM8450"),
+ (SM8450_3, "SM8450"),
+ SC7280,
+ SC7180P,
+ QCM6490,
+ QCS6490,
+ SM7325P,
+ IPQ5000,
+ IPQ0509,
+ IPQ0518,
+ SM6375,
+ IPQ9514,
+ IPQ9550,
+ IPQ9554,
+ IPQ9570,
+ IPQ9574,
+ SM8550,
+ IPQ5016,
+ IPQ9510,
+ QRB4210,
+ QRB2210,
+ SAR2130P,
+ SM8475,
+ SM8475P,
+ SA8255P,
+ SA8775P,
+ QRU1000,
+ SM8475_2,
+ QDU1000,
+ X1E80100,
+ SM8650,
+ SM4450,
+ SAR1130P,
+ QDU1010,
+ QRU1032,
+ QRU1052,
+ QRU1062,
+ IPQ5332,
+ IPQ5322,
+ IPQ5312,
+ IPQ5302,
+ QCS8550,
+ QCM8550,
+ SM8750,
+ IPQ5300,
+ SM7635,
+ SM6650,
+ SM6650P,
+ IPQ5321,
+ IPQ5424,
+ QCM6690,
+ QCS6690,
+ SM8850,
+ IPQ5404,
+ QCS9100,
+ QCS8300,
+ QCS8275,
+ QCS9075,
+ QCS615,
+}
+
+pub(crate) const PMIC_MODELS: &[Option<&str>] = &sparse_array! {
+ 0: "Unknown PMIC model",
+ 1: "PM8941",
+ 2: "PM8841",
+ 3: "PM8019",
+ 4: "PM8226",
+ 5: "PM8110",
+ 6: "PMA8084",
+ 7: "PMI8962",
+ 8: "PMD9635",
+ 9: "PM8994",
+ 10: "PMI8994",
+ 11: "PM8916",
+ 12: "PM8004",
+ 13: "PM8909/PM8058",
+ 14: "PM8028",
+ 15: "PM8901",
+ 16: "PM8950/PM8027",
+ 17: "PMI8950/ISL9519",
+ 18: "PMK8001/PM8921",
+ 19: "PMI8996/PM8018",
+ 20: "PM8998/PM8015",
+ 21: "PMI8998/PM8014",
+ 22: "PM8821",
+ 23: "PM8038",
+ 24: "PM8005/PM8922",
+ 25: "PM8917/PM8937",
+ 26: "PM660L",
+ 27: "PM660",
+ 30: "PM8150",
+ 31: "PM8150L",
+ 32: "PM8150B",
+ 33: "PMK8002",
+ 36: "PM8009",
+ 37: "PMI632",
+ 38: "PM8150C",
+ 40: "PM6150",
+ 41: "SMB2351",
+ 44: "PM8008",
+ 45: "PM6125",
+ 46: "PM7250B",
+ 47: "PMK8350",
+ 48: "PM8350",
+ 49: "PM8350C",
+ 50: "PM8350B",
+ 51: "PMR735A",
+ 52: "PMR735B",
+ 54: "PM6350",
+ 55: "PM4125",
+ 58: "PM8450",
+ 65: "PM8010",
+ 69: "PM8550VS",
+ 70: "PM8550VE",
+ 71: "PM8550B",
+ 72: "PMR735D",
+ 73: "PM8550",
+ 74: "PMK8550",
+ 78: "PMM8650AU",
+ 79: "PMM8650AU_PSAIL",
+ 80: "PM7550",
+ 82: "PMC8380",
+ 83: "SMB2360",
+ 91: "PMIV0108"
+};
+
+pub(crate) const IMAGE_NAMES: &[&CStr] = &[
+ c"boot",
+ c"tz",
+ c"tzsecapp",
+ c"rpm",
+ c"sdi",
+ c"hyp",
+ c"adsp1",
+ c"adsp2",
+ c"cdsp2",
+ c"appsbl",
+ c"apps",
+ c"mpss",
+ c"adsp",
+ c"cnss",
+ c"video",
+ c"dsps",
+ c"cdsp",
+ c"npu",
+ c"wpss",
+ c"cdsp1",
+ c"gpdsp",
+ c"gpdsp1",
+ c"sensorpd",
+ c"audiopd",
+ c"oempd",
+ c"chargerpd",
+ c"oispd",
+ c"soccp",
+ c"tme",
+ c"gearvm",
+ c"uefi",
+ c"cdsp3",
+ c"audiopd",
+ c"audiopd",
+ c"dcp",
+ c"oobs",
+ c"oobns",
+ c"devcfg",
+ c"btpd",
+ c"qecp",
+];
diff --git a/drivers/soc/qcom/socinfo/socinfo.rs b/drivers/soc/qcom/socinfo/socinfo.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c0791b33232eed9aef01c4db9e2031961fca783f
--- /dev/null
+++ b/drivers/soc/qcom/socinfo/socinfo.rs
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! socinfo driver for Qualcomm SoCs.
+
+use core::fmt::{
+ self,
+ Formatter, //
+};
+
+use kernel::{
+ auxiliary,
+ c_str,
+ debugfs::{
+ Scope,
+ ScopedDir, //
+ },
+ device::{
+ Bound,
+ Core,
+ Device, //
+ },
+ devres::Devres,
+ error,
+ io::{
+ Io,
+ Mmio, //
+ },
+ module_auxiliary_driver,
+ pr_warn,
+ prelude::*,
+ soc,
+ str::{
+ CStr,
+ CStrExt,
+ CString, //
+ },
+ transmute::AsBytes,
+ try_pin_init, //
+};
+use pin_init::pin_init_scope;
+
+mod bindings;
+mod data;
+
+use bindings::*;
+use data::{
+ IMAGE_NAMES,
+ PMIC_MODELS,
+ SOC_IDS, //
+};
+
+module_auxiliary_driver! {
+ type: QcomSocInfo,
+ name: "qcom-socinfo",
+ authors: ["Matthew Maurer"],
+ description: "Qualcomm SoCinfo driver",
+ license: "GPL",
+ alias: ["platform:qcom-socinfo"],
+}
+
+#[pin_data]
+struct QcomSocInfo {
+ #[pin]
+ registration: soc::Registration,
+ #[pin]
+ params: Devres<Scope<Params>>,
+}
+
+fn pmic_name(model: u32) -> Option<&'static str> {
+ let idx = SocInfo::version_split(model).1 as usize;
+ PMIC_MODELS.get(idx).copied().flatten()
+}
+
+fn fmt_pmic_model(model: &u32, f: &mut Formatter<'_>) -> fmt::Result {
+ if let Some(model) = pmic_name(*model) {
+ writeln!(f, "{model}")
+ } else {
+ writeln!(f, "unknown ({})", SocInfo::version_split(*model).1)
+ }
+}
+
+fn fmt_pmic_die_rev(rev: &u32, f: &mut Formatter<'_>) -> fmt::Result {
+ let (major, minor) = SocInfo::version_split(*rev);
+ writeln!(f, "{major}.{minor}")
+}
+
+fn fmt_pmic_model_array(array: &KVec<PmicEntry>, f: &mut Formatter<'_>) -> fmt::Result {
+ for pmic_entry in array {
+ let (die_rev_major, die_rev_minor) =
+ SocInfo::version_split(u32::from_le(pmic_entry.die_rev));
+ if let Some(model) = pmic_name(pmic_entry.model) {
+ writeln!(f, "{model} {die_rev_major}.{die_rev_minor}")?
+ } else {
+ writeln!(f, "unknown ({})", pmic_entry.model)?
+ }
+ }
+ Ok(())
+}
+
+#[derive(Default)]
+struct Params {
+ info_fmt: u32,
+ build_id: [u8; 32],
+ raw_version: Option<u32>,
+ hardware_platform: Option<u32>,
+ platform_version: Option<u32>,
+ accessory_chip: Option<u32>,
+ hardware_platform_subtype: Option<u32>,
+ pmic_model: Option<u32>,
+ pmic_die_rev: Option<u32>,
+ foundry_id: Option<u32>,
+ pmic_model_array: Option<KVec<PmicEntry>>,
+ chip_family: Option<u32>,
+ raw_device_family: Option<u32>,
+ raw_device_number: Option<u32>,
+ nproduct_id: Option<u32>,
+ chip_id: Option<[u8; 32]>,
+ num_clusters: Option<u32>,
+ ncluster_array_offset: Option<u32>,
+ num_subset_parts: Option<u32>,
+ nsubset_parts_array_offset: Option<u32>,
+ nmodem_supported: Option<u32>,
+ feature_code: Option<u32>,
+ pcode: Option<u32>,
+ oem_variant: Option<u32>,
+ boot_core: Option<u32>,
+ boot_cluster: Option<u32>,
+ num_func_clusters: Option<u32>,
+ raw_package_type: Option<u32>,
+ versions: KVec<Smem>,
+}
+
+struct SocInfo {
+ soc_info: socinfo,
+ soc_info_mem: Smem,
+ version_mem: Smem,
+ version_mem2: Option<Smem>,
+}
+
+impl SocInfo {
+ fn from_mem(
+ soc_info_mem: Smem,
+ version_mem: Smem,
+ version_mem2: Option<Smem>,
+ dev: &Device<Bound>,
+ ) -> Result<Self> {
+ Ok(Self {
+ soc_info: socinfo_from_partial_bytes(
+ soc_info_mem.access(dev).ok_or(error::code::EINVAL)?,
+ )?,
+ soc_info_mem,
+ version_mem,
+ version_mem2,
+ })
+ }
+ fn id(&self) -> u32 {
+ u32::from_le(self.soc_info.id)
+ }
+ fn version_split(ver: u32) -> (u16, u16) {
+ let major = (ver >> 16) as u16;
+ let minor = (ver & 0xFFFF) as u16;
+ (major, minor)
+ }
+ fn version_fuse(major: u16, minor: u16) -> u32 {
+ (u32::from(major) << 16) | u32::from(minor)
+ }
+ fn version(&self) -> (u16, u16) {
+ Self::version_split(self.soc_info.ver)
+ }
+ fn serial(&self) -> u32 {
+ u32::from_le(self.soc_info.serial_num)
+ }
+ fn machine(&self) -> Result<Option<CString>> {
+ for soc in SOC_IDS {
+ if soc.id == self.id() {
+ return Ok(Some(soc.name.to_cstring()?));
+ }
+ }
+ Ok(None)
+ }
+ fn soc_attributes(&self) -> Result<soc::Attributes> {
+ Ok(soc::Attributes {
+ family: Some(c_str!("Snapdragon").to_cstring()?),
+ machine: self.machine()?,
+ revision: Some(CString::try_from_fmt(fmt!(
+ "{}.{}",
+ self.version().0,
+ self.version().1
+ ))?),
+ serial_number: Some(CString::try_from_fmt(fmt!("{}", self.serial()))?),
+ soc_id: Some(CString::try_from_fmt(fmt!("{}", self.id()))?),
+ })
+ }
+}
+
+macro_rules! u32_le_versioned {
+ { $params:expr, $self:ident,
+ [ $( { $major:expr, $minor:expr, { $( $dst:ident: $src:ident ),* } } ),* ] } => {$(
+ if $params.info_fmt >= SocInfo::version_fuse($major, $minor) {
+ $( $params.$dst = Some(u32::from_le($self.soc_info.$src).into()) );*
+ }
+ )*}
+}
+
+impl SocInfo {
+ fn build_params(self, dev: &Device<Bound>) -> Result<Params> {
+ let mut params = Params {
+ build_id: self.soc_info.build_id,
+ info_fmt: u32::from_le(self.soc_info.fmt),
+ ..Default::default()
+ };
+ u32_le_versioned! { params, self, [
+ {0, 2, { raw_version: raw_ver }},
+ {0, 3, { hardware_platform: hw_plat }},
+ {0, 4, { platform_version: plat_ver }},
+ {0, 5, { accessory_chip: accessory_chip }},
+ {0, 6, { hardware_platform_subtype: hw_plat_subtype }},
+ {0, 7, { pmic_model: pmic_model, pmic_die_rev: pmic_die_rev }},
+ {0, 9, { foundry_id: foundry_id }},
+ {0, 12, {
+ chip_family: chip_family,
+ raw_device_family: raw_device_family,
+ raw_device_number: raw_device_num
+ }},
+ {0, 13, { nproduct_id: nproduct_id }},
+ {0, 14, {
+ num_clusters: num_clusters,
+ ncluster_array_offset: ncluster_array_offset,
+ num_subset_parts: num_subset_parts,
+ nsubset_parts_array_offset: nsubset_parts_array_offset
+ }},
+ {0, 15, { nmodem_supported: nmodem_supported }},
+ {0, 16, { feature_code: feature_code, pcode: pcode }},
+ {0, 17, { oem_variant: oem_variant }},
+ {0, 19, {
+ boot_core: boot_core,
+ boot_cluster: boot_cluster,
+ num_func_clusters: num_func_clusters
+ }},
+ {0, 20, { raw_package_type: raw_package_type }}
+ ]};
+ if params.info_fmt >= SocInfo::version_fuse(0, 11) {
+ let offset = u32::from_le(self.soc_info.pmic_array_offset) as usize;
+ let num_pmics = u32::from_le(self.soc_info.num_pmics) as usize;
+ let mut pmic_model_array =
+ KVec::from_elem(PmicEntry::default(), num_pmics, GFP_KERNEL)?;
+ self.soc_info_mem
+ .access(dev)
+ .ok_or(error::code::EINVAL)?
+ .try_copy_to(pmic_model_array.as_bytes_mut(), offset)?;
+ params.pmic_model_array = Some(pmic_model_array);
+ }
+ if params.info_fmt >= SocInfo::version_fuse(0, 13) {
+ params.chip_id = Some(self.soc_info.chip_id);
+ }
+ load_versions(&mut params.versions, &self.version_mem)?;
+ if let Some(versions) = self.version_mem2.as_ref() {
+ load_versions(&mut params.versions, versions)?;
+ }
+ Ok(params)
+ }
+}
+
+fn load_versions(versions: &mut KVec<Smem>, mem: &Smem) -> Result<()> {
+ const IMAGE_SIZE: usize = core::mem::size_of::<ImageVersion>();
+ for offset in (0..mem.maxsize()).step_by(IMAGE_SIZE) {
+ if let Some(subrange) = mem.subrange(offset, IMAGE_SIZE) {
+ versions.push(subrange, GFP_KERNEL)?;
+ }
+ }
+ Ok(())
+}
+
+macro_rules! value_attrs {
+ ($builder:ident, $params:ident, @) => {};
+ ($builder:ident, $params:ident, @ $s:ident $($rest:tt)*) => {
+ if let Some(v) = $params.$s.as_ref() {
+ $builder.read_only_file(c_str!(stringify!($s)), v);
+ }
+ value_attrs!($builder, $params, @ $($rest)*)
+ };
+ ($builder:ident, $params:ident, @ {$s:ident, $cb:expr} $($rest:tt)*) => {
+ if let Some(v) = $params.$s.as_ref() {
+ $builder.read_callback_file(c_str!(stringify!($s)), v, $cb);
+ }
+ value_attrs!($builder, $params, @ $($rest)*)
+ };
+ ($builder:ident, $params:ident, {$($items:tt),*}) => {
+ value_attrs!($builder, $params, @ $($items)*)
+ };
+}
+
+fn no_quirk<const SIZE: usize>(buf: &[u8; SIZE], f: &mut Formatter<'_>) -> fmt::Result {
+ if buf[0] == 0 {
+ writeln!(f)
+ } else {
+ nul_array(buf, f)
+ }
+}
+
+fn nul_array<const SIZE: usize>(buf: &[u8; SIZE], f: &mut Formatter<'_>) -> fmt::Result {
+ if let Some(end) = buf.iter().position(|x| *x == 0) {
+ if end == 0 {
+ // Match original driver quirk - empty strings don't have a trailing newline
+ return Ok(());
+ }
+ let Ok(c_str) = CStr::from_bytes_with_nul(&buf[0..=end]) else {
+ pr_warn!("Creating CStr from bytes with known first NUL failed?");
+ return Ok(());
+ };
+ let Ok(str) = c_str.to_str() else {
+ pr_warn!("Non-unicode-compatible character in string");
+ return Ok(());
+ };
+ writeln!(f, "{str}")
+ } else {
+ writeln!(f, "Missing NUL: {buf:?}")
+ }
+}
+
+fn build_image_debugfs<'a>(dir: &ScopedDir<'a, '_>, image_name: &CStr, mem: &'a Mmio) {
+ let subdir = dir.dir(image_name);
+ subdir.read_callback_file(c_str!("name"), mem, &debug_name);
+ subdir.read_callback_file(c_str!("variant"), mem, &debug_variant);
+ subdir.read_callback_file(c_str!("oem"), mem, &debug_oem);
+}
+
+fn debug_name(mem: &Mmio, f: &mut Formatter<'_>) -> fmt::Result {
+ let mut iv = ImageVersion::default();
+ mem.try_copy_to(iv.as_bytes_mut(), 0)
+ .map_err(|_| fmt::Error)?;
+ nul_array(&iv.name, f)
+}
+
+fn debug_variant(mem: &Mmio, f: &mut Formatter<'_>) -> fmt::Result {
+ let mut iv = ImageVersion::default();
+ mem.try_copy_to(iv.as_bytes_mut(), 0)
+ .map_err(|_| fmt::Error)?;
+ nul_array(&iv.variant, f)
+}
+
+fn debug_oem(mem: &Mmio, f: &mut Formatter<'_>) -> fmt::Result {
+ let mut iv = ImageVersion::default();
+ mem.try_copy_to(iv.as_bytes_mut(), 0)
+ .map_err(|_| fmt::Error)?;
+ nul_array(&iv.oem, f)
+}
+
+fn hex(x: &u32, f: &mut Formatter<'_>) -> fmt::Result {
+ writeln!(f, "{x:#010x}")
+}
+
+impl Params {
+ fn build_debugfs<'data>(&'data self, dev: &'data Device<Bound>, dir: &ScopedDir<'data, '_>) {
+ dir.read_callback_file(c_str!("info_fmt"), &self.info_fmt, &hex);
+ dir.read_callback_file(c_str!("build_id"), &self.build_id, &no_quirk);
+ value_attrs!(dir, self, {
+ raw_version,
+ hardware_platform,
+ platform_version,
+ accessory_chip,
+ hardware_platform_subtype,
+ {raw_device_number, &hex},
+ {raw_device_family, &hex},
+ {chip_family, &hex},
+ {chip_id, &nul_array},
+ nproduct_id,
+ nsubset_parts_array_offset,
+ num_subset_parts,
+ ncluster_array_offset,
+ num_clusters,
+ nmodem_supported,
+ pcode,
+ feature_code,
+ oem_variant,
+ boot_core,
+ boot_cluster,
+ num_func_clusters,
+ foundry_id,
+ raw_package_type,
+ {pmic_model, &fmt_pmic_model},
+ {pmic_die_rev, &fmt_pmic_die_rev},
+ {pmic_model_array, &fmt_pmic_model_array}
+ });
+ for (version, name) in self.versions.iter().zip(IMAGE_NAMES.iter()) {
+ if let Some(version_mem) = version.access(dev) {
+ build_image_debugfs(dir, name, version_mem);
+ }
+ }
+ }
+}
+
+kernel::auxiliary_device_table! {
+ AUX_TABLE,
+ MODULE_AUX_TABLE,
+ <QcomSocInfo as auxiliary::Driver>::IdInfo,
+ [(auxiliary::DeviceId::new(c_str!("smem"), c_str!("qcom-socinfo")), ())]
+}
+
+impl auxiliary::Driver for QcomSocInfo {
+ type IdInfo = ();
+ const ID_TABLE: auxiliary::IdTable<Self::IdInfo> = &AUX_TABLE;
+ fn probe(dev: &auxiliary::Device<Core>, _id_info: &Self::IdInfo) -> impl PinInit<Self, Error> {
+ pin_init_scope(move || {
+ let soc_info_mem = Smem::get(
+ dev,
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ kernel::bindings::SMEM_HW_SW_BUILD_ID,
+ )?;
+ let version_mem = Smem::get(
+ dev,
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ bindings::SMEM_IMAGE_VERSION_TABLE,
+ )?;
+ let version_mem2 = Smem::get(
+ dev,
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ bindings::SMEM_IMAGE_VERSION_TABLE_2,
+ )
+ .ok();
+ let base_dev = dev.as_ref();
+ let mem = soc_info_mem.access(base_dev).ok_or(error::code::EINVAL)?;
+
+ rand_from_mmio(mem)?;
+
+ let info = SocInfo::from_mem(soc_info_mem, version_mem, version_mem2, base_dev)?;
+ let attributes = info.soc_attributes()?;
+ let backing = info.build_params(base_dev)?;
+
+ Ok(try_pin_init!(Self {
+ registration <- soc::Registration::new(attributes),
+ params <- Devres::dir(base_dev, backing, c_str!("qcom_socinfo"),
+ Params::build_debugfs),
+ }? Error))
+ })
+ }
+}
+
+fn rand_from_mmio(mmio: &Mmio) -> Result<()> {
+ let mut buf: KVec<u8> = KVec::from_elem(0, mmio.maxsize(), GFP_KERNEL)?;
+ mmio.try_copy_to(&mut buf, 0)?;
+ kernel::random::add_device_randomness(&buf);
+ Ok(())
+}
diff --git a/include/linux/soc/qcom/smem.h b/include/linux/soc/qcom/smem.h
index f946e3beca215548ac56dbf779138d05479712f5..1c26f258be1fc1bf34bf3b0112c4a8472e676ed8 100644
--- a/include/linux/soc/qcom/smem.h
+++ b/include/linux/soc/qcom/smem.h
@@ -4,8 +4,12 @@
#define QCOM_SMEM_HOST_ANY -1
+struct auxiliary_device;
+
bool qcom_smem_is_available(void);
int qcom_smem_alloc(unsigned host, unsigned item, size_t size);
+void *qcom_smem_get_aux(struct auxiliary_device *aux, unsigned int host,
+ unsigned int item, size_t *size);
void *qcom_smem_get(unsigned host, unsigned item, size_t *size);
int qcom_smem_get_free_space(unsigned host);
diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters
index d56343ca03979e345f8adb7eb8fd7f2b9d4be6ee..3398a8eff517b71c062fd3e52a4a7895526f64a1 100644
--- a/rust/bindgen_parameters
+++ b/rust/bindgen_parameters
@@ -72,3 +72,4 @@
# We can't auto-derive AsBytes, as we need a const-time check to see if there
# is padding involved. Add it explicitly when you expect no padding.
--with-derive-custom-struct cpumask=AsBytesFfi
+--with-derive-custom-struct socinfo=AsBytesFfi
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 9fdf76ca630e00715503e2a3a809bedc895697fd..b076b847731d97cf345d8ef80df9c315a79c8fab 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -95,6 +95,12 @@
*/
#include <../../drivers/base/base.h>
+#if IS_ENABLED(CONFIG_QCOM_SOCINFO)
+#include <dt-bindings/arm/qcom,ids.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/socinfo.h>
+#endif
+
#if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
// Used by `#[export]` in `drivers/gpu/drm/drm_panic_qr.rs`.
#include <drm/drm_panic.h>
--
2.53.0.rc2.204.g2597b5adb4-goog
On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
> Convert the socinfo driver to Rust for a number of improvements:
> * Accessing IO mapped regions through the IO subsystem, rather than
> through regular memory accesses.
> * Binds the device as an auxiliary device rather than a platform device,
> ensuring the mapped IO regions cannot be accessed after the smem
> device is removed.
> * Adds bounds-checking to all accesses, hardening against a repeat of
> CVE-2024-58007
>
These three bullet represents things that would be good to fix (in the
C-implementation), but the commit message does not describe the problem
that a complete Rust-rewrite solves.
I expressed my scepticism in v1 about changing this driver, for the sake
of supporting this experiment in your downstream kernel(s). The people
who suggested this driver to be a good candidate choose not to engage in
either that discussion nor in the review of the solution itself.
[..]
> diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
> index fef840b5457407a85051ded0e835430dbebfe8bb..dcea2d7f37067b0b6f801b3d2b457422ad9f342c 100644
> --- a/drivers/soc/qcom/smem.c
> +++ b/drivers/soc/qcom/smem.c
> @@ -4,6 +4,7 @@
> * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
> */
>
> +#include <linux/auxiliary_bus.h>
> #include <linux/hwspinlock.h>
> #include <linux/io.h>
> #include <linux/module.h>
> @@ -279,7 +280,6 @@ struct qcom_smem {
> struct hwspinlock *hwlock;
>
> u32 item_count;
> - struct platform_device *socinfo;
> struct smem_ptable *ptable;
> struct smem_partition global_partition;
> struct smem_partition partitions[SMEM_HOST_COUNT];
> @@ -675,6 +675,32 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> return ERR_PTR(-EINVAL);
> }
>
> +/**
> + * qcom_smem_get_aux() - resolve ptr of size of a smem item
> + * @aux: an aux device that should be our child
> + * @host: the remote processor, or -1
> + * @item: smem item handle
> + * @size: pointer to be filled out with size of the item
> + *
> + * Looks up smem item and returns pointer to it. Size of smem
> + * item is returned in @size.
> + *
> + * The caller may take the loaded state of the provided aux device as
> + * an acceptable proxy for this memory being valid.
Is this sentence trying to say "The purpose of this function is to
implicitly bind the life cycle of the returned pointer to the life of
the child auxiliary_device, which has due to devres will not outlive the
mapping of this pointer"?
IMHO this represents a very specific corner case and any other - real -
use-case of accessing SMEM would have to solve this problem properly.
> + *
> + * Return: a pointer to an SMEM item on success, ERR_PTR() on failure.
> + */
> +void *qcom_smem_get_aux(struct auxiliary_device *aux, unsigned int host,
> + unsigned int item, size_t *size)
> +{
> + if (IS_ERR(__smem))
> + return __smem;
> + if (aux->dev.parent != __smem->dev)
> + return ERR_PTR(-EINVAL);
> + return qcom_smem_get(host, item, size);
> +}
> +EXPORT_SYMBOL_GPL(qcom_smem_get_aux);
> +
> /**
> * qcom_smem_get() - resolve ptr of size of a smem item
> * @host: the remote processor, or -1
> @@ -684,6 +710,9 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> * Looks up smem item and returns pointer to it. Size of smem
> * item is returned in @size.
> *
> + * It is up to the caller to ensure that the qcom_smem device remains
> + * loaded by some mechanism when accessing returned memory.
> + *
> * Return: a pointer to an SMEM item on success, ERR_PTR() on failure.
> */
> void *qcom_smem_get(unsigned host, unsigned item, size_t *size)
> @@ -1127,6 +1156,7 @@ static int qcom_smem_probe(struct platform_device *pdev)
> struct smem_header *header;
> struct reserved_mem *rmem;
> struct qcom_smem *smem;
> + struct auxiliary_device *socinfo;
> unsigned long flags;
> int num_regions;
> int hwlock_id;
> @@ -1234,19 +1264,15 @@ static int qcom_smem_probe(struct platform_device *pdev)
>
> __smem = smem;
>
> - smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo",
> - PLATFORM_DEVID_NONE, NULL,
> - 0);
> - if (IS_ERR(smem->socinfo))
> - dev_dbg(&pdev->dev, "failed to register socinfo device\n");
> + socinfo = devm_auxiliary_device_create(&pdev->dev, "qcom-socinfo", NULL);
> + if (IS_ERR(socinfo))
> + dev_dbg(&pdev->dev, "failed to create socinfo device\n");
I wouldn't mind transitioning this to auxiliary_device independent of
this effort.
Regards,
Bjorn
>
> return 0;
> }
>
> static void qcom_smem_remove(struct platform_device *pdev)
> {
> - platform_device_unregister(__smem->socinfo);
> -
> __smem = NULL;
> }
>
On Tue, Feb 03, 2026 at 02:27:21PM -0600, Bjorn Andersson wrote: > I expressed my scepticism in v1 about changing this driver, for the sake > of supporting this experiment in your downstream kernel(s). The people > who suggested this driver to be a good candidate choose not to engage in > either that discussion nor in the review of the solution itself. I was told that QCOM wanted this driver to be redone in rust as that is what they wanted to do for further socinfo drivers going forward. Hopefully the people that said that actually talked to the kernel developers/maintainers involved in this driver first before doing so :) thanks, greg k-h
On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
> Convert the socinfo driver to Rust for a number of improvements:
> * Accessing IO mapped regions through the IO subsystem, rather than
> through regular memory accesses.
That's good, but the C code could also be "fixed" to do this, right?
> * Binds the device as an auxiliary device rather than a platform device,
> ensuring the mapped IO regions cannot be accessed after the smem
> device is removed.
I'm all for this, but is this really an aux device? What is the
"parent" device of this aux device? Where are the "siblings"? What
does sysfs look like before/after this?
> * Adds bounds-checking to all accesses, hardening against a repeat of
> CVE-2024-58007
How do you now "know" that the bounds checking is correct? The C
version also had this, it was just "not correct" :)
And which accesses are you referring to? From userspace? From the
kernel? That CVE looks very odd, it's probably not even a real one and
should be revoked, right?
> diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
> index fef840b5457407a85051ded0e835430dbebfe8bb..dcea2d7f37067b0b6f801b3d2b457422ad9f342c 100644
> --- a/drivers/soc/qcom/smem.c
> +++ b/drivers/soc/qcom/smem.c
> @@ -4,6 +4,7 @@
> * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
> */
>
> +#include <linux/auxiliary_bus.h>
> #include <linux/hwspinlock.h>
> #include <linux/io.h>
> #include <linux/module.h>
> @@ -279,7 +280,6 @@ struct qcom_smem {
> struct hwspinlock *hwlock;
>
> u32 item_count;
> - struct platform_device *socinfo;
> struct smem_ptable *ptable;
> struct smem_partition global_partition;
> struct smem_partition partitions[SMEM_HOST_COUNT];
> @@ -675,6 +675,32 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> return ERR_PTR(-EINVAL);
> }
>
> +/**
> + * qcom_smem_get_aux() - resolve ptr of size of a smem item
> + * @aux: an aux device that should be our child
> + * @host: the remote processor, or -1
> + * @item: smem item handle
> + * @size: pointer to be filled out with size of the item
> + *
> + * Looks up smem item and returns pointer to it. Size of smem
> + * item is returned in @size.
> + *
> + * The caller may take the loaded state of the provided aux device as
> + * an acceptable proxy for this memory being valid.
> + *
> + * Return: a pointer to an SMEM item on success, ERR_PTR() on failure.
> + */
> +void *qcom_smem_get_aux(struct auxiliary_device *aux, unsigned int host,
> + unsigned int item, size_t *size)
> +{
> + if (IS_ERR(__smem))
> + return __smem;
> + if (aux->dev.parent != __smem->dev)
> + return ERR_PTR(-EINVAL);
> + return qcom_smem_get(host, item, size);
So you are returning a void pointer? But don't you really know the
"type" of what is being asked here? It's a memory chunk, so u8? Or
something else? void * feels "rough" here.
> +}
> +EXPORT_SYMBOL_GPL(qcom_smem_get_aux);
> +
> /**
> * qcom_smem_get() - resolve ptr of size of a smem item
> * @host: the remote processor, or -1
> @@ -684,6 +710,9 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> * Looks up smem item and returns pointer to it. Size of smem
> * item is returned in @size.
> *
> + * It is up to the caller to ensure that the qcom_smem device remains
> + * loaded by some mechanism when accessing returned memory.
What do you mean by "loaded"? You are saying that the caller needs to
rely on this driver remaining in memory for that memory to be "valid"?
If this is the case, why not take a reference count?
> +impl Smem {
> + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
> + if *dev != *self.dev {
How can this ever happen?
thanks,
greg k-h
On Tue, Feb 3, 2026 at 8:28 AM Greg Kroah-Hartman
<gregkh@linuxfoundation.org> wrote:
>
> On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
> > Convert the socinfo driver to Rust for a number of improvements:
> > * Accessing IO mapped regions through the IO subsystem, rather than
> > through regular memory accesses.
>
> That's good, but the C code could also be "fixed" to do this, right?
Yes, nothing stops the C code from being fixed to do all of this - all
of this is representable in C code.
>
> > * Binds the device as an auxiliary device rather than a platform device,
> > ensuring the mapped IO regions cannot be accessed after the smem
> > device is removed.
>
> I'm all for this, but is this really an aux device? What is the
> "parent" device of this aux device? Where are the "siblings"? What
> does sysfs look like before/after this?
The parent of this aux device is qcom-smem. In the prior
implementation, qcom-smem loads this device through
`platform_device_register_data` and `platform_device_unregister`,
holding a reference in its global struct to release it when being
released. The probe table for the qcom-socinfo driver was empty, so it
would not probe without an explicit registration.
>
> > * Adds bounds-checking to all accesses, hardening against a repeat of
> > CVE-2024-58007
>
> How do you now "know" that the bounds checking is correct? The C
> version also had this, it was just "not correct" :)
While it's technically possible for the Rust code to have an error
here, the error would not be in the driver, but in the kernel crate.
The advantage here is that the bounds checking is all centralized, so
we get it right once, for the entire kernel, instead of needing to get
it right every time.
>
> And which accesses are you referring to? From userspace? From the
> kernel? That CVE looks very odd, it's probably not even a real one and
> should be revoked, right?
>
That CVE looks like this:
1. qcom_smem_get returns an object of size N
2. When initializing the `serial_number` field of
soc_device_attributes, the offset of the serial number field was
checked as <= N, rather than the *end* of the serial number field.
3. This resulted in the driver exposing through sysfs whatever data
was mapped afterwards, interpreted as a number.
I agree that the severity seems oddly high, given that in practice,
this will expose the remainder of the IO mapped page - I don't believe
it crosses a page boundary, so it can't even *really* leak anything.
>
> > diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
> > index fef840b5457407a85051ded0e835430dbebfe8bb..dcea2d7f37067b0b6f801b3d2b457422ad9f342c 100644
> > --- a/drivers/soc/qcom/smem.c
> > +++ b/drivers/soc/qcom/smem.c
> > @@ -4,6 +4,7 @@
> > * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
> > */
> >
> > +#include <linux/auxiliary_bus.h>
> > #include <linux/hwspinlock.h>
> > #include <linux/io.h>
> > #include <linux/module.h>
> > @@ -279,7 +280,6 @@ struct qcom_smem {
> > struct hwspinlock *hwlock;
> >
> > u32 item_count;
> > - struct platform_device *socinfo;
> > struct smem_ptable *ptable;
> > struct smem_partition global_partition;
> > struct smem_partition partitions[SMEM_HOST_COUNT];
> > @@ -675,6 +675,32 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> > return ERR_PTR(-EINVAL);
> > }
> >
> > +/**
> > + * qcom_smem_get_aux() - resolve ptr of size of a smem item
> > + * @aux: an aux device that should be our child
> > + * @host: the remote processor, or -1
> > + * @item: smem item handle
> > + * @size: pointer to be filled out with size of the item
> > + *
> > + * Looks up smem item and returns pointer to it. Size of smem
> > + * item is returned in @size.
> > + *
> > + * The caller may take the loaded state of the provided aux device as
> > + * an acceptable proxy for this memory being valid.
> > + *
> > + * Return: a pointer to an SMEM item on success, ERR_PTR() on failure.
> > + */
> > +void *qcom_smem_get_aux(struct auxiliary_device *aux, unsigned int host,
> > + unsigned int item, size_t *size)
> > +{
> > + if (IS_ERR(__smem))
> > + return __smem;
> > + if (aux->dev.parent != __smem->dev)
> > + return ERR_PTR(-EINVAL);
> > + return qcom_smem_get(host, item, size);
>
> So you are returning a void pointer? But don't you really know the
> "type" of what is being asked here? It's a memory chunk, so u8? Or
> something else? void * feels "rough" here.
>
> > +}
> > +EXPORT_SYMBOL_GPL(qcom_smem_get_aux);
> > +
> > /**
> > * qcom_smem_get() - resolve ptr of size of a smem item
> > * @host: the remote processor, or -1
> > @@ -684,6 +710,9 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> > * Looks up smem item and returns pointer to it. Size of smem
> > * item is returned in @size.
> > *
> > + * It is up to the caller to ensure that the qcom_smem device remains
> > + * loaded by some mechanism when accessing returned memory.
>
> What do you mean by "loaded"? You are saying that the caller needs to
> rely on this driver remaining in memory for that memory to be "valid"?
>
> If this is the case, why not take a reference count?
>
> > +impl Smem {
> > + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
> > + if *dev != *self.dev {
>
> How can this ever happen?
>
> thanks,
>
> greg k-h
On Tue, Feb 03, 2026 at 09:37:50AM -0800, Matthew Maurer wrote:
> On Tue, Feb 3, 2026 at 8:28 AM Greg Kroah-Hartman
> <gregkh@linuxfoundation.org> wrote:
> >
> > On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
> > > Convert the socinfo driver to Rust for a number of improvements:
> > > * Accessing IO mapped regions through the IO subsystem, rather than
> > > through regular memory accesses.
> >
> > That's good, but the C code could also be "fixed" to do this, right?
>
> Yes, nothing stops the C code from being fixed to do all of this - all
> of this is representable in C code.
Great! How about sending fixes for that first so that older kernels can
benifit from those fixes :)
> > > * Binds the device as an auxiliary device rather than a platform device,
> > > ensuring the mapped IO regions cannot be accessed after the smem
> > > device is removed.
> >
> > I'm all for this, but is this really an aux device? What is the
> > "parent" device of this aux device? Where are the "siblings"? What
> > does sysfs look like before/after this?
>
> The parent of this aux device is qcom-smem. In the prior
> implementation, qcom-smem loads this device through
> `platform_device_register_data` and `platform_device_unregister`,
> holding a reference in its global struct to release it when being
> released. The probe table for the qcom-socinfo driver was empty, so it
> would not probe without an explicit registration.
So it's a "fake" platform device being created? As Bjorn said, moving
this to aux today would probably be good first.
> > > * Adds bounds-checking to all accesses, hardening against a repeat of
> > > CVE-2024-58007
> >
> > How do you now "know" that the bounds checking is correct? The C
> > version also had this, it was just "not correct" :)
>
> While it's technically possible for the Rust code to have an error
> here, the error would not be in the driver, but in the kernel crate.
> The advantage here is that the bounds checking is all centralized, so
> we get it right once, for the entire kernel, instead of needing to get
> it right every time.
I missed where the bounds checking is happening at all here. I see
fields and sizes of fields, but what is verifying that those sizes are
actually correct?
> > And which accesses are you referring to? From userspace? From the
> > kernel? That CVE looks very odd, it's probably not even a real one and
> > should be revoked, right?
> >
>
> That CVE looks like this:
> 1. qcom_smem_get returns an object of size N
> 2. When initializing the `serial_number` field of
> soc_device_attributes, the offset of the serial number field was
> checked as <= N, rather than the *end* of the serial number field.
> 3. This resulted in the driver exposing through sysfs whatever data
> was mapped afterwards, interpreted as a number.
>
> I agree that the severity seems oddly high, given that in practice,
> this will expose the remainder of the IO mapped page - I don't believe
> it crosses a page boundary, so it can't even *really* leak anything.
Where do you see this as "high"? The kernel CNA does NOT give any
severity info for any CVEs that are issued.
If you are looking at the crap that NVD adds, well, it's just that,
crap, that has no actual relevance to anyone and is flat out wrong most
of the time. We have asked them to stop doing this, but so far we have
not gotten a response:
https://github.com/cisagov/vulnrichment/issues/262
In short, for kernel CVEs only look at the data provided by cve.org, or
the raw data provided by the kernel CNA team.
That being said, this seems like a basic "we are just displaying wrong
data in a sysfs file", so it probably should be rejected unless someone
can tell me how it fits the definition of a vulnerability?
As for why it's a CVE at all, it came in as part of the required GSD
import into CVE.org.
> > > +void *qcom_smem_get_aux(struct auxiliary_device *aux, unsigned int host,
> > > + unsigned int item, size_t *size)
> > > +{
> > > + if (IS_ERR(__smem))
> > > + return __smem;
> > > + if (aux->dev.parent != __smem->dev)
> > > + return ERR_PTR(-EINVAL);
> > > + return qcom_smem_get(host, item, size);
> >
> > So you are returning a void pointer? But don't you really know the
> > "type" of what is being asked here? It's a memory chunk, so u8? Or
> > something else? void * feels "rough" here.
> >
> > > +}
> > > +EXPORT_SYMBOL_GPL(qcom_smem_get_aux);
> > > +
> > > /**
> > > * qcom_smem_get() - resolve ptr of size of a smem item
> > > * @host: the remote processor, or -1
> > > @@ -684,6 +710,9 @@ static void *qcom_smem_get_private(struct qcom_smem *smem,
> > > * Looks up smem item and returns pointer to it. Size of smem
> > > * item is returned in @size.
> > > *
> > > + * It is up to the caller to ensure that the qcom_smem device remains
> > > + * loaded by some mechanism when accessing returned memory.
> >
> > What do you mean by "loaded"? You are saying that the caller needs to
> > rely on this driver remaining in memory for that memory to be "valid"?
> >
> > If this is the case, why not take a reference count?
> >
> > > +impl Smem {
> > > + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
> > > + if *dev != *self.dev {
> >
> > How can this ever happen?
I think you missed these review comments?
thanks,
greg k-h
On Tue Feb 3, 2026 at 5:28 PM CET, Greg Kroah-Hartman wrote:
> On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
>> +impl Smem {
>> + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
>> + if *dev != *self.dev {
>
> How can this ever happen?
You are right, since this resource is local to the driver, it should not be
possible to have another devce from somewhere else (especially not a
&Device<Bound>).
However, from a Rust perspective I think not having this check would be unsound,
as the method by itself would not be able guarantee correct behavor anymore.
On Tue, Feb 03, 2026 at 05:35:24PM +0100, Danilo Krummrich wrote:
> On Tue Feb 3, 2026 at 5:28 PM CET, Greg Kroah-Hartman wrote:
> > On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
> >> +impl Smem {
> >> + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
> >> + if *dev != *self.dev {
> >
> > How can this ever happen?
>
> You are right, since this resource is local to the driver, it should not be
> possible to have another devce from somewhere else (especially not a
> &Device<Bound>).
>
> However, from a Rust perspective I think not having this check would be unsound,
> as the method by itself would not be able guarantee correct behavor anymore.
>
I don't really understand this last sentence, sorry. If this is "not
possible" why would that at the same time be "unsound"?
thanks,
greg k-h
On Tue Feb 3, 2026 at 5:48 PM CET, Greg Kroah-Hartman wrote:
> On Tue, Feb 03, 2026 at 05:35:24PM +0100, Danilo Krummrich wrote:
>> On Tue Feb 3, 2026 at 5:28 PM CET, Greg Kroah-Hartman wrote:
>> > On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
>> >> +impl Smem {
>> >> + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
>> >> + if *dev != *self.dev {
>> >
>> > How can this ever happen?
>>
>> You are right, since this resource is local to the driver, it should not be
>> possible to have another devce from somewhere else (especially not a
>> &Device<Bound>).
>>
>> However, from a Rust perspective I think not having this check would be unsound,
>> as the method by itself would not be able guarantee correct behavor anymore.
>>
>
> I don't really understand this last sentence, sorry. If this is "not
> possible" why would that at the same time be "unsound"?
It would be considered unsound because the function itself can not guarantee
that it does not produce undefined behavior, i.e. it would otherwise becomes
unsafe.
But I'm not an expert on this, I'll let Benno and Gary jump in.
On Tue Feb 3, 2026 at 4:56 PM GMT, Danilo Krummrich wrote:
> On Tue Feb 3, 2026 at 5:48 PM CET, Greg Kroah-Hartman wrote:
>> On Tue, Feb 03, 2026 at 05:35:24PM +0100, Danilo Krummrich wrote:
>>> On Tue Feb 3, 2026 at 5:28 PM CET, Greg Kroah-Hartman wrote:
>>> > On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
>>> >> +impl Smem {
>>> >> + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
>>> >> + if *dev != *self.dev {
>>> >
>>> > How can this ever happen?
>>>
>>> You are right, since this resource is local to the driver, it should not be
>>> possible to have another devce from somewhere else (especially not a
>>> &Device<Bound>).
>>>
>>> However, from a Rust perspective I think not having this check would be unsound,
>>> as the method by itself would not be able guarantee correct behavor anymore.
>>>
>>
>> I don't really understand this last sentence, sorry. If this is "not
>> possible" why would that at the same time be "unsound"?
>
> It would be considered unsound because the function itself can not guarantee
> that it does not produce undefined behavior, i.e. it would otherwise becomes
> unsafe.
>
> But I'm not an expert on this, I'll let Benno and Gary jump in.
Yes, in this code, `Smem::access` is using the passed-in `&'a Device<Bound>` as
a proof that the device is still bound.
If the check is not present, the user of this API can give in a reference of a
different device, and now you're pontentially providing access to the MMIO
region to a device that is not bound anymore. A important property we want to
enforce is that UB cannot happen no matter how an API is misused (it may still
oops, which is bad, but better than UB).
This particular code, though, probably should use `Devres` as Danilo suggested.
PS. Personally I think this check (and the one in Devres::access) should use
assertion rather than `EINVAL`, because it is a bad driver bug when this is hit,
and adding failing code path for something that is a bug is something I really
dislike... It should at least be a `WARN`.
Best,
Gary
On Tue Feb 3, 2026 at 6:17 PM CET, Gary Guo wrote:
> PS. Personally I think this check (and the one in Devres::access) should use
> assertion rather than `EINVAL`, because it is a bad driver bug when this is hit,
> and adding failing code path for something that is a bug is something I really
> dislike... It should at least be a `WARN`.
I don't think we should panic in this case, as it can happen too easily.
// Let's assume the resource is owned by the parent.
fn foo(adev: &auxiliary::Device<Bound>, res: &Devres<Resource>) {
let res = res.access(dev.parent()); // OK
let res = res.access(dev.as_ref()); // panic()
}
Printing a warning is probably a good idea though.
On Tue, Feb 3, 2026 at 9:17 AM Gary Guo <gary@garyguo.net> wrote:
>
> On Tue Feb 3, 2026 at 4:56 PM GMT, Danilo Krummrich wrote:
> > On Tue Feb 3, 2026 at 5:48 PM CET, Greg Kroah-Hartman wrote:
> >> On Tue, Feb 03, 2026 at 05:35:24PM +0100, Danilo Krummrich wrote:
> >>> On Tue Feb 3, 2026 at 5:28 PM CET, Greg Kroah-Hartman wrote:
> >>> > On Tue, Feb 03, 2026 at 03:46:35PM +0000, Matthew Maurer wrote:
> >>> >> +impl Smem {
> >>> >> + pub(crate) fn access<'a>(&'a self, dev: &'a Device<Bound>) -> Option<&'a Mmio> {
> >>> >> + if *dev != *self.dev {
> >>> >
> >>> > How can this ever happen?
> >>>
> >>> You are right, since this resource is local to the driver, it should not be
> >>> possible to have another devce from somewhere else (especially not a
> >>> &Device<Bound>).
> >>>
> >>> However, from a Rust perspective I think not having this check would be unsound,
> >>> as the method by itself would not be able guarantee correct behavor anymore.
> >>>
> >>
> >> I don't really understand this last sentence, sorry. If this is "not
> >> possible" why would that at the same time be "unsound"?
The code as it stands doesn't do anything that would grant `Smem`
objects to something that exceeds the lifetime of the device. The goal
of bindings APIs like these are that it should be impossible for
someone to write Rust code without the word `unsafe` in it that breaks
its invariants. Without that check, someone could do:
let smem = Smem::get(dev)?;
let f = move |offset| smem.try_copy(offset);
some_other_driver_or_subsystem(f);
That other region of the code would now have ownership over that
function, and expect to be able to call it safely. We're not doing
that, but we're trying to design the unsafe API in bindings so that
you can't mess it up even if you wanted to.
> >
> > It would be considered unsound because the function itself can not guarantee
> > that it does not produce undefined behavior, i.e. it would otherwise becomes
> > unsafe.
> >
> > But I'm not an expert on this, I'll let Benno and Gary jump in.
>
> Yes, in this code, `Smem::access` is using the passed-in `&'a Device<Bound>` as
> a proof that the device is still bound.
>
> If the check is not present, the user of this API can give in a reference of a
> different device, and now you're pontentially providing access to the MMIO
> region to a device that is not bound anymore. A important property we want to
> enforce is that UB cannot happen no matter how an API is misused (it may still
> oops, which is bad, but better than UB).
>
> This particular code, though, probably should use `Devres` as Danilo suggested.
Mentioned on the other thread, but I didn't use `Devres` because it
being PinInit is problematic and I would need to encode an additional
invariant anyways.
>
> PS. Personally I think this check (and the one in Devres::access) should use
> assertion rather than `EINVAL`, because it is a bad driver bug when this is hit,
> and adding failing code path for something that is a bug is something I really
> dislike... It should at least be a `WARN`.
>
> Best,
> Gary
© 2016 - 2026 Red Hat, Inc.