Files
linux/drivers/firmware/smccc/soc_id.c
Andre Przywara 70492cfce2 firmware: smccc: Fix Arm SMCCC SOC_ID name call
Commit 5f9c23abc4 ("firmware: smccc: Support optional Arm SMCCC SOC_ID
name") introduced the SOC_ID name string call, which reports a human
readable string describing the SoC, as returned by firmware.

The SMCCC spec v1.6 describes this feature as AArch64 only, since we rely
on 8 characters to be transmitted per register. Consequently the SMCCC
call must use the AArch64 calling convention, which requires bit 30 of
the FID to be set. The spec is a bit confusing here, since it mentions
that in the parameter description ("2: SoC name (optionally implemented for
SMC64 calls, ..."), but still prints the FID explicitly as 0x80000002.

But as this FID is using the SMC32 calling convention (correct for the
other two calls), it will not match what any SMCCC conformant firmware is
expecting, so any call would return NOT_SUPPORTED.

Add a 64-bit version of the ARCH_SOC_ID FID macro, and use that for the
SoC name version of the call to fix the issue.

Fixes: 5f9c23abc4 ("firmware: smccc: Support optional Arm SMCCC SOC_ID name")
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
Link: https://patch.msgid.link/20250902172053.304911-1-andre.przywara@arm.com
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
2026-05-20 09:41:44 +01:00

176 lines
5.0 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020 Arm Limited
*/
#define pr_fmt(fmt) "SMCCC: SOC_ID: " fmt
#include <linux/arm-smccc.h>
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#define SMCCC_SOC_ID_JEP106_BANK_IDX_MASK GENMASK(30, 24)
/*
* As per the SMC Calling Convention specification v1.2 (ARM DEN 0028C)
* Section 7.4 SMCCC_ARCH_SOC_ID bits[23:16] are JEP-106 identification
* code with parity bit for the SiP. We can drop the parity bit.
*/
#define SMCCC_SOC_ID_JEP106_ID_CODE_MASK GENMASK(22, 16)
#define SMCCC_SOC_ID_IMP_DEF_SOC_ID_MASK GENMASK(15, 0)
#define JEP106_BANK_CONT_CODE(x) \
(u8)(FIELD_GET(SMCCC_SOC_ID_JEP106_BANK_IDX_MASK, (x)))
#define JEP106_ID_CODE(x) \
(u8)(FIELD_GET(SMCCC_SOC_ID_JEP106_ID_CODE_MASK, (x)))
#define IMP_DEF_SOC_ID(x) \
(u16)(FIELD_GET(SMCCC_SOC_ID_IMP_DEF_SOC_ID_MASK, (x)))
static struct soc_device *soc_dev;
static struct soc_device_attribute *soc_dev_attr;
#ifdef CONFIG_ARM64
static char __ro_after_init smccc_soc_id_name[136] = "";
static inline void str_fragment_from_reg(char *dst, unsigned long reg)
{
dst[0] = (reg >> 0) & 0xff;
dst[1] = (reg >> 8) & 0xff;
dst[2] = (reg >> 16) & 0xff;
dst[3] = (reg >> 24) & 0xff;
dst[4] = (reg >> 32) & 0xff;
dst[5] = (reg >> 40) & 0xff;
dst[6] = (reg >> 48) & 0xff;
dst[7] = (reg >> 56) & 0xff;
}
static char __init *smccc_soc_name_init(void)
{
struct arm_smccc_1_2_regs args;
struct arm_smccc_1_2_regs res;
size_t len;
/*
* Issue Number 1.6 of the Arm SMC Calling Convention
* specification introduces an optional "name" string
* to the ARM_SMCCC_ARCH_SOC_ID function. Fetch it if
* available.
*/
args.a0 = ARM_SMCCC_ARCH_SOC_ID64;
args.a1 = 2; /* SOC_ID name */
arm_smccc_1_2_invoke(&args, &res);
if ((u32)res.a0 == 0) {
/*
* Copy res.a1..res.a17 to the smccc_soc_id_name string
* 8 bytes at a time. As per Issue 1.6 of the Arm SMC
* Calling Convention, the string will be NUL terminated
* and padded, from the end of the string to the end of the
* 136 byte buffer, with NULs.
*/
str_fragment_from_reg(smccc_soc_id_name + 8 * 0, res.a1);
str_fragment_from_reg(smccc_soc_id_name + 8 * 1, res.a2);
str_fragment_from_reg(smccc_soc_id_name + 8 * 2, res.a3);
str_fragment_from_reg(smccc_soc_id_name + 8 * 3, res.a4);
str_fragment_from_reg(smccc_soc_id_name + 8 * 4, res.a5);
str_fragment_from_reg(smccc_soc_id_name + 8 * 5, res.a6);
str_fragment_from_reg(smccc_soc_id_name + 8 * 6, res.a7);
str_fragment_from_reg(smccc_soc_id_name + 8 * 7, res.a8);
str_fragment_from_reg(smccc_soc_id_name + 8 * 8, res.a9);
str_fragment_from_reg(smccc_soc_id_name + 8 * 9, res.a10);
str_fragment_from_reg(smccc_soc_id_name + 8 * 10, res.a11);
str_fragment_from_reg(smccc_soc_id_name + 8 * 11, res.a12);
str_fragment_from_reg(smccc_soc_id_name + 8 * 12, res.a13);
str_fragment_from_reg(smccc_soc_id_name + 8 * 13, res.a14);
str_fragment_from_reg(smccc_soc_id_name + 8 * 14, res.a15);
str_fragment_from_reg(smccc_soc_id_name + 8 * 15, res.a16);
str_fragment_from_reg(smccc_soc_id_name + 8 * 16, res.a17);
len = strnlen(smccc_soc_id_name, sizeof(smccc_soc_id_name));
if (len) {
if (len == sizeof(smccc_soc_id_name))
pr_warn(FW_BUG "Ignoring improperly formatted name\n");
else
return smccc_soc_id_name;
}
}
return NULL;
}
#else
static char __init *smccc_soc_name_init(void)
{
return NULL;
}
#endif
static int __init smccc_soc_init(void)
{
int soc_id_rev, soc_id_version;
static char soc_id_str[20], soc_id_rev_str[12];
static char soc_id_jep106_id_str[12];
if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2)
return 0;
soc_id_version = arm_smccc_get_soc_id_version();
if (soc_id_version == SMCCC_RET_NOT_SUPPORTED) {
pr_info("ARCH_SOC_ID not implemented, skipping ....\n");
return 0;
}
if (soc_id_version < 0) {
pr_err("Invalid SoC Version: %x\n", soc_id_version);
return -EINVAL;
}
soc_id_rev = arm_smccc_get_soc_id_revision();
if (soc_id_rev < 0) {
pr_err("Invalid SoC Revision: %x\n", soc_id_rev);
return -EINVAL;
}
soc_dev_attr = kzalloc_obj(*soc_dev_attr);
if (!soc_dev_attr)
return -ENOMEM;
sprintf(soc_id_rev_str, "0x%08x", soc_id_rev);
sprintf(soc_id_jep106_id_str, "jep106:%02x%02x",
JEP106_BANK_CONT_CODE(soc_id_version),
JEP106_ID_CODE(soc_id_version));
sprintf(soc_id_str, "%s:%04x", soc_id_jep106_id_str,
IMP_DEF_SOC_ID(soc_id_version));
soc_dev_attr->soc_id = soc_id_str;
soc_dev_attr->revision = soc_id_rev_str;
soc_dev_attr->family = soc_id_jep106_id_str;
soc_dev_attr->machine = smccc_soc_name_init();
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
kfree(soc_dev_attr);
return PTR_ERR(soc_dev);
}
pr_info("ID = %s Revision = %s\n", soc_dev_attr->soc_id,
soc_dev_attr->revision);
return 0;
}
module_init(smccc_soc_init);
static void __exit smccc_soc_exit(void)
{
if (soc_dev)
soc_device_unregister(soc_dev);
kfree(soc_dev_attr);
}
module_exit(smccc_soc_exit);