* [PATCH v3 04/15] firmware: qcom: Add a PAS TEE service
From: Sumit Garg @ 2026-03-27 13:10 UTC (permalink / raw)
To: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, robin.clark,
sean, akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
harshal.dev, linux-kernel, Sumit Garg
In-Reply-To: <20260327131043.627120-1-sumit.garg@kernel.org>
From: Sumit Garg <sumit.garg@oss.qualcomm.com>
Add support for Peripheral Authentication Service (PAS) driver based
on TEE bus with OP-TEE providing the backend PAS service implementation.
The TEE PAS service ABI is designed to be extensible with additional API
as PTA_QCOM_PAS_CAPABILITIES. This allows to accommodate any future
extensions of the PAS service needed while still maintaining backwards
compatibility.
Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
---
drivers/firmware/qcom/Kconfig | 10 +
drivers/firmware/qcom/Makefile | 1 +
drivers/firmware/qcom/qcom_pas_tee.c | 478 +++++++++++++++++++++++++++
3 files changed, 489 insertions(+)
create mode 100644 drivers/firmware/qcom/qcom_pas_tee.c
diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig
index 9a12ae2b639d..300b3a1bd178 100644
--- a/drivers/firmware/qcom/Kconfig
+++ b/drivers/firmware/qcom/Kconfig
@@ -14,6 +14,16 @@ config QCOM_PAS
backends plugged in whether it's an SCM implementation or a proper
TEE bus based PAS service implementation.
+config QCOM_PAS_TEE
+ tristate
+ select QCOM_PAS
+ depends on TEE
+ depends on !CPU_BIG_ENDIAN
+ default m if ARCH_QCOM
+ help
+ Enable the generic Peripheral Authentication Service (PAS) provided
+ by the firmware TEE implementation as the backend.
+
config QCOM_SCM
select QCOM_PAS
select QCOM_TZMEM
diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile
index dc5ab45f906a..48801d18f37b 100644
--- a/drivers/firmware/qcom/Makefile
+++ b/drivers/firmware/qcom/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o
obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
obj-$(CONFIG_QCOM_PAS) += qcom_pas.o
+obj-$(CONFIG_QCOM_PAS_TEE) += qcom_pas_tee.o
diff --git a/drivers/firmware/qcom/qcom_pas_tee.c b/drivers/firmware/qcom/qcom_pas_tee.c
new file mode 100644
index 000000000000..d63122c34f04
--- /dev/null
+++ b/drivers/firmware/qcom/qcom_pas_tee.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/firmware/qcom/qcom_pas.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tee_drv.h>
+#include <linux/uuid.h>
+
+#include "qcom_pas.h"
+
+/*
+ * Peripheral Authentication Service (PAS) supported.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ */
+#define TA_QCOM_PAS_IS_SUPPORTED 1
+
+/*
+ * PAS capabilities.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ * [out] params[1].value.a: PAS capability flags
+ */
+#define TA_QCOM_PAS_CAPABILITIES 2
+
+/*
+ * PAS image initialization.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ * [in] params[1].memref: Loadable firmware metadata
+ */
+#define TA_QCOM_PAS_INIT_IMAGE 3
+
+/*
+ * PAS memory setup.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ * [in] params[0].value.b: Relocatable firmware size
+ * [in] params[1].value.a: 32bit LSB relocatable firmware memory address
+ * [in] params[1].value.b: 32bit MSB relocatable firmware memory address
+ */
+#define TA_QCOM_PAS_MEM_SETUP 4
+
+/*
+ * PAS get resource table.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ * [inout] params[1].memref: Resource table config
+ */
+#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5
+
+/*
+ * PAS image authentication and co-processor reset.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ * [in] params[0].value.b: Firmware size
+ * [in] params[1].value.a: 32bit LSB firmware memory address
+ * [in] params[1].value.b: 32bit MSB firmware memory address
+ * [in] params[2].memref: Optional fw memory space shared/lent
+ */
+#define TA_QCOM_PAS_AUTH_AND_RESET 6
+
+/*
+ * PAS co-processor set suspend/resume state.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ * [in] params[0].value.b: Co-processor state identifier
+ */
+#define TA_QCOM_PAS_SET_REMOTE_STATE 7
+
+/*
+ * PAS co-processor shutdown.
+ *
+ * [in] params[0].value.a: Unique 32bit remote processor identifier
+ */
+#define TA_QCOM_PAS_SHUTDOWN 8
+
+#define TEE_NUM_PARAMS 4
+
+/**
+ * struct qcom_pas_tee_private - PAS service private data
+ * @dev: PAS service device.
+ * @ctx: TEE context handler.
+ * @session_id: PAS TA session identifier.
+ */
+struct qcom_pas_tee_private {
+ struct device *dev;
+ struct tee_context *ctx;
+ u32 session_id;
+};
+
+static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_IS_SUPPORTED,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = pas_id
+ }
+ };
+ int ret;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS not supported, pas_id: %d, err: %x\n",
+ pas_id, inv_arg.ret);
+ return false;
+ }
+
+ return true;
+}
+
+static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id,
+ const void *metadata, size_t size,
+ struct qcom_pas_context *ctx)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_INIT_IMAGE,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = pas_id
+ },
+ [1] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
+ }
+ };
+ struct tee_shm *mdata_shm;
+ u8 *mdata_buf = NULL;
+ int ret;
+
+ if (!ctx)
+ return -EINVAL;
+
+ mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size);
+ if (IS_ERR(mdata_shm)) {
+ dev_err(dev, "mdata_shm allocation failed\n");
+ return PTR_ERR(mdata_shm);
+ }
+
+ mdata_buf = tee_shm_get_va(mdata_shm, 0);
+ if (IS_ERR(mdata_buf)) {
+ dev_err(dev, "mdata_buf get VA failed\n");
+ tee_shm_free(mdata_shm);
+ return PTR_ERR(mdata_buf);
+ }
+ memcpy(mdata_buf, metadata, size);
+
+ param[1].u.memref.shm = mdata_shm;
+ param[1].u.memref.size = size;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS init image failed, pas_id: %d, err: %x\n",
+ pas_id, inv_arg.ret);
+ tee_shm_free(mdata_shm);
+ return -EINVAL;
+ }
+ ctx->ptr = (void *)mdata_shm;
+
+ return 0;
+}
+
+static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id,
+ phys_addr_t addr, phys_addr_t size)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_MEM_SETUP,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = pas_id,
+ .u.value.b = size,
+ },
+ [1] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = lower_32_bits(addr),
+ .u.value.b = upper_32_bits(addr),
+ }
+ };
+ int ret;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS mem setup failed, pas_id: %d, err: %x\n",
+ pas_id, inv_arg.ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T))
+
+static void *qcom_pas_tee_get_rsc_table(struct device *dev,
+ struct qcom_pas_context *ctx,
+ void *input_rt, size_t input_rt_size,
+ size_t *output_rt_size)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_GET_RESOURCE_TABLE,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = ctx->pas_id,
+ },
+ [1] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
+ .u.memref.size = input_rt_size,
+ }
+ };
+ void *rt_buf = NULL;
+ int ret;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS get RT failed, pas_id: %d, err: %x\n",
+ ctx->pas_id, inv_arg.ret);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (param[1].u.memref.size) {
+ struct tee_shm *rt_shm __free(shm_free) =
+ tee_shm_alloc_kernel_buf(data->ctx,
+ param[1].u.memref.size);
+ void *rt_shm_va;
+
+ if (IS_ERR(rt_shm)) {
+ dev_err(dev, "rt_shm allocation failed\n");
+ return rt_shm;
+ }
+
+ rt_shm_va = tee_shm_get_va(rt_shm, 0);
+ if (IS_ERR_OR_NULL(rt_shm_va)) {
+ dev_err(dev, "rt_shm get VA failed\n");
+ return ERR_PTR(-EINVAL);
+ }
+ memcpy(rt_shm_va, input_rt, input_rt_size);
+
+ param[1].u.memref.shm = rt_shm;
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS get RT failed, pas_id: %d, err: %x\n",
+ ctx->pas_id, inv_arg.ret);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (param[1].u.memref.size) {
+ *output_rt_size = param[1].u.memref.size;
+ rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL);
+ if (!rt_buf)
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ return rt_buf;
+}
+
+static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id,
+ phys_addr_t mem_phys, size_t mem_size)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_AUTH_AND_RESET,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = pas_id,
+ .u.value.b = mem_size,
+ },
+ [1] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = lower_32_bits(mem_phys),
+ .u.value.b = upper_32_bits(mem_phys),
+ },
+ /* Reserved for fw memory space to be shared or lent */
+ [2] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
+ }
+ };
+ int ret;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS auth reset failed, pas_id: %d, err: %x\n",
+ pas_id, inv_arg.ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id)
+{
+ return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0);
+}
+
+static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev,
+ struct qcom_pas_context *ctx)
+{
+ return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys,
+ ctx->mem_size);
+}
+
+static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state,
+ u32 pas_id)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_SET_REMOTE_STATE,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = pas_id,
+ .u.value.b = state,
+ }
+ };
+ int ret;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS shutdown failed, pas_id: %d, err: %x\n",
+ pas_id, inv_arg.ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id)
+{
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+ struct tee_ioctl_invoke_arg inv_arg = {
+ .func = TA_QCOM_PAS_SHUTDOWN,
+ .session = data->session_id,
+ .num_params = TEE_NUM_PARAMS
+ };
+ struct tee_param param[4] = {
+ [0] = {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = pas_id
+ }
+ };
+ int ret = 0;
+
+ ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
+ if (ret < 0 || inv_arg.ret != 0) {
+ dev_err(dev, "PAS shutdown failed, pas_id: %d, err: %x\n",
+ pas_id, inv_arg.ret);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void qcom_pas_tee_metadata_release(struct device *dev,
+ struct qcom_pas_context *ctx)
+{
+ struct tee_shm *mdata_shm = ctx->ptr;
+
+ tee_shm_free(mdata_shm);
+}
+
+static struct qcom_pas_ops qcom_pas_ops_tee = {
+ .drv_name = "qcom-pas-tee",
+ .supported = qcom_pas_tee_supported,
+ .init_image = qcom_pas_tee_init_image,
+ .mem_setup = qcom_pas_tee_mem_setup,
+ .get_rsc_table = qcom_pas_tee_get_rsc_table,
+ .auth_and_reset = qcom_pas_tee_auth_and_reset,
+ .prepare_and_auth_reset = qcom_pas_tee_prepare_and_auth_reset,
+ .set_remote_state = qcom_pas_tee_set_remote_state,
+ .shutdown = qcom_pas_tee_shutdown,
+ .metadata_release = qcom_pas_tee_metadata_release,
+};
+
+static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
+{
+ return ver->impl_id == TEE_IMPL_ID_OPTEE;
+}
+
+static int qcom_pas_tee_probe(struct tee_client_device *pas_dev)
+{
+ struct device *dev = &pas_dev->dev;
+ struct qcom_pas_tee_private *data;
+ struct tee_ioctl_open_session_arg sess_arg = {
+ .clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL
+ };
+ int ret, err = -ENODEV;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
+ if (IS_ERR(data->ctx))
+ return -ENODEV;
+
+ export_uuid(sess_arg.uuid, &pas_dev->id.uuid);
+ ret = tee_client_open_session(data->ctx, &sess_arg, NULL);
+ if (ret < 0 || sess_arg.ret != 0) {
+ dev_err(dev, "tee_client_open_session failed, err: %x\n",
+ sess_arg.ret);
+ err = -EINVAL;
+ goto out_ctx;
+ }
+
+ data->session_id = sess_arg.session;
+ dev_set_drvdata(dev, data);
+ qcom_pas_ops_tee.dev = dev;
+ qcom_pas_ops_register(&qcom_pas_ops_tee);
+
+ return 0;
+out_ctx:
+ tee_client_close_context(data->ctx);
+
+ return err;
+}
+
+static void qcom_pas_tee_remove(struct tee_client_device *pas_dev)
+{
+ struct device *dev = &pas_dev->dev;
+ struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
+
+ qcom_pas_ops_unregister();
+ tee_client_close_session(data->ctx, data->session_id);
+ tee_client_close_context(data->ctx);
+}
+
+static const struct tee_client_device_id qcom_pas_tee_id_table[] = {
+ {UUID_INIT(0xcff7d191, 0x7ca0, 0x4784,
+ 0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe)},
+ {}
+};
+MODULE_DEVICE_TABLE(tee, qcom_pas_tee_id_table);
+
+static struct tee_client_driver optee_pas_tee_driver = {
+ .probe = qcom_pas_tee_probe,
+ .remove = qcom_pas_tee_remove,
+ .id_table = qcom_pas_tee_id_table,
+ .driver = {
+ .name = "qcom-pas-tee",
+ },
+};
+
+module_tee_client_driver(optee_pas_tee_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TEE bus based Qualcomm PAS driver");
--
2.51.0
^ permalink raw reply related
* [PATCH v3 03/15] firmware: qcom_scm: Migrate to generic PAS service
From: Sumit Garg @ 2026-03-27 13:10 UTC (permalink / raw)
To: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, robin.clark,
sean, akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
harshal.dev, linux-kernel, Sumit Garg
In-Reply-To: <20260327131043.627120-1-sumit.garg@kernel.org>
From: Sumit Garg <sumit.garg@oss.qualcomm.com>
With the availability of generic PAS service, let's add SCM calls as
a backend to keep supporting legacy QTEE interfaces. The exported
qcom_scm* wrappers will get dropped once all the client drivers get
migrated as part of future patches.
Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
---
drivers/firmware/qcom/Kconfig | 1 +
drivers/firmware/qcom/qcom_scm.c | 335 ++++++++++++++-----------------
2 files changed, 155 insertions(+), 181 deletions(-)
diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig
index 8653639d06db..9a12ae2b639d 100644
--- a/drivers/firmware/qcom/Kconfig
+++ b/drivers/firmware/qcom/Kconfig
@@ -15,6 +15,7 @@ config QCOM_PAS
TEE bus based PAS service implementation.
config QCOM_SCM
+ select QCOM_PAS
select QCOM_TZMEM
tristate
diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index 8fbc96693a55..ab802048a719 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -13,6 +13,7 @@
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/export.h>
+#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/firmware/qcom/qcom_tzmem.h>
#include <linux/init.h>
@@ -33,6 +34,7 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include "qcom_pas.h"
#include "qcom_scm.h"
#include "qcom_tzmem.h"
@@ -480,25 +482,6 @@ void qcom_scm_cpu_power_down(u32 flags)
}
EXPORT_SYMBOL_GPL(qcom_scm_cpu_power_down);
-int qcom_scm_set_remote_state(u32 state, u32 id)
-{
- struct qcom_scm_desc desc = {
- .svc = QCOM_SCM_SVC_BOOT,
- .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE,
- .arginfo = QCOM_SCM_ARGS(2),
- .args[0] = state,
- .args[1] = id,
- .owner = ARM_SMCCC_OWNER_SIP,
- };
- struct qcom_scm_res res;
- int ret;
-
- ret = qcom_scm_call(__scm->dev, &desc, &res);
-
- return ret ? : res.result[0];
-}
-EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state);
-
static int qcom_scm_disable_sdi(void)
{
int ret;
@@ -571,26 +554,12 @@ static void qcom_scm_set_download_mode(u32 dload_mode)
dev_err(__scm->dev, "failed to set download mode: %d\n", ret);
}
-/**
- * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service
- * context for a given peripheral
- *
- * PAS context is device-resource managed, so the caller does not need
- * to worry about freeing the context memory.
- *
- * @dev: PAS firmware device
- * @pas_id: peripheral authentication service id
- * @mem_phys: Subsystem reserve memory start address
- * @mem_size: Subsystem reserve memory size
- *
- * Returns: The new PAS context, or ERR_PTR() on failure.
- */
struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
u32 pas_id,
phys_addr_t mem_phys,
size_t mem_size)
{
- struct qcom_scm_pas_context *ctx;
+ struct qcom_pas_context *ctx;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
@@ -601,11 +570,12 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
ctx->mem_phys = mem_phys;
ctx->mem_size = mem_size;
- return ctx;
+ return (struct qcom_scm_pas_context *)ctx;
}
EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc);
-static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
+static int __qcom_scm_pas_init_image(struct device *dev, u32 pas_id,
+ dma_addr_t mdata_phys,
struct qcom_scm_res *res)
{
struct qcom_scm_desc desc = {
@@ -627,7 +597,7 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
desc.args[1] = mdata_phys;
- ret = qcom_scm_call(__scm->dev, &desc, res);
+ ret = qcom_scm_call(dev, &desc, res);
qcom_scm_bw_disable();
disable_clk:
@@ -636,7 +606,8 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
return ret;
}
-static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
+static int qcom_scm_pas_prep_and_init_image(struct device *dev,
+ struct qcom_pas_context *ctx,
const void *metadata, size_t size)
{
struct qcom_scm_res res;
@@ -651,7 +622,7 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
memcpy(mdata_buf, metadata, size);
mdata_phys = qcom_tzmem_to_phys(mdata_buf);
- ret = __qcom_scm_pas_init_image(ctx->pas_id, mdata_phys, &res);
+ ret = __qcom_scm_pas_init_image(dev, ctx->pas_id, mdata_phys, &res);
if (ret < 0)
qcom_tzmem_free(mdata_buf);
else
@@ -660,25 +631,9 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
return ret ? : res.result[0];
}
-/**
- * qcom_scm_pas_init_image() - Initialize peripheral authentication service
- * state machine for a given peripheral, using the
- * metadata
- * @pas_id: peripheral authentication service id
- * @metadata: pointer to memory containing ELF header, program header table
- * and optional blob of data used for authenticating the metadata
- * and the rest of the firmware
- * @size: size of the metadata
- * @ctx: optional pas context
- *
- * Return: 0 on success.
- *
- * Upon successful return, the PAS metadata context (@ctx) will be used to
- * track the metadata allocation, this needs to be released by invoking
- * qcom_scm_pas_metadata_release() by the caller.
- */
-int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
- struct qcom_scm_pas_context *ctx)
+static int __qcom_scm_pas_init_image2(struct device *dev, u32 pas_id,
+ const void *metadata, size_t size,
+ struct qcom_pas_context *ctx)
{
struct qcom_scm_res res;
dma_addr_t mdata_phys;
@@ -686,7 +641,7 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
int ret;
if (ctx && ctx->use_tzmem)
- return qcom_scm_pas_prep_and_init_image(ctx, metadata, size);
+ return qcom_scm_pas_prep_and_init_image(dev, ctx, metadata, size);
/*
* During the scm call memory protection will be enabled for the meta
@@ -700,16 +655,15 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
* If we pass a buffer that is already part of an SHM Bridge to this
* call, it will fail.
*/
- mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys,
- GFP_KERNEL);
+ mdata_buf = dma_alloc_coherent(dev, size, &mdata_phys, GFP_KERNEL);
if (!mdata_buf)
return -ENOMEM;
memcpy(mdata_buf, metadata, size);
- ret = __qcom_scm_pas_init_image(pas_id, mdata_phys, &res);
+ ret = __qcom_scm_pas_init_image(dev, pas_id, mdata_phys, &res);
if (ret < 0 || !ctx) {
- dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys);
+ dma_free_coherent(dev, size, mdata_buf, mdata_phys);
} else if (ctx) {
ctx->ptr = mdata_buf;
ctx->phys = mdata_phys;
@@ -718,36 +672,35 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
return ret ? : res.result[0];
}
-EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image);
-/**
- * qcom_scm_pas_metadata_release() - release metadata context
- * @ctx: pas context
- */
-void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx)
+int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
+ struct qcom_scm_pas_context *ctx)
{
- if (!ctx->ptr)
- return;
+ return __qcom_scm_pas_init_image2(__scm->dev, pas_id, metadata, size,
+ (struct qcom_pas_context *)ctx);
+}
+EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image);
+static void __qcom_scm_pas_metadata_release(struct device *dev,
+ struct qcom_pas_context *ctx)
+{
if (ctx->use_tzmem)
qcom_tzmem_free(ctx->ptr);
else
- dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys);
+ dma_free_coherent(dev, ctx->size, ctx->ptr, ctx->phys);
ctx->ptr = NULL;
}
+
+void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx)
+{
+ __qcom_scm_pas_metadata_release(__scm->dev,
+ (struct qcom_pas_context *)ctx);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release);
-/**
- * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral
- * for firmware loading
- * @pas_id: peripheral authentication service id
- * @addr: start address of memory area to prepare
- * @size: size of the memory area to prepare
- *
- * Returns 0 on success.
- */
-int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
+static int __qcom_scm_pas_mem_setup(struct device *dev, u32 pas_id,
+ phys_addr_t addr, phys_addr_t size)
{
int ret;
struct qcom_scm_desc desc = {
@@ -769,7 +722,7 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
if (ret)
goto disable_clk;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(dev, &desc, &res);
qcom_scm_bw_disable();
disable_clk:
@@ -777,9 +730,15 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
return ret ? : res.result[0];
}
+
+int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
+{
+ return __qcom_scm_pas_mem_setup(__scm->dev, pas_id, addr, size);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup);
-static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
+static void *__qcom_scm_pas_get_rsc_table(struct device *dev, u32 pas_id,
+ void *input_rt_tzm,
size_t input_rt_size,
size_t *output_rt_size)
{
@@ -814,7 +773,7 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
* with output_rt_tzm buffer with res.result[2] size however, It should not
* be of unresonable size.
*/
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(dev, &desc, &res);
if (!ret && res.result[2] > SZ_1G) {
ret = -E2BIG;
goto free_output_rt;
@@ -831,51 +790,11 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
return ret ? ERR_PTR(ret) : output_rt_tzm;
}
-/**
- * qcom_scm_pas_get_rsc_table() - Retrieve the resource table in passed output buffer
- * for a given peripheral.
- *
- * Qualcomm remote processor may rely on both static and dynamic resources for
- * its functionality. Static resources typically refer to memory-mapped addresses
- * required by the subsystem and are often embedded within the firmware binary
- * and dynamic resources, such as shared memory in DDR etc., are determined at
- * runtime during the boot process.
- *
- * On Qualcomm Technologies devices, it's possible that static resources are not
- * embedded in the firmware binary and instead are provided by TrustZone However,
- * dynamic resources are always expected to come from TrustZone. This indicates
- * that for Qualcomm devices, all resources (static and dynamic) will be provided
- * by TrustZone via the SMC call.
- *
- * If the remote processor firmware binary does contain static resources, they
- * should be passed in input_rt. These will be forwarded to TrustZone for
- * authentication. TrustZone will then append the dynamic resources and return
- * the complete resource table in output_rt_tzm.
- *
- * If the remote processor firmware binary does not include a resource table,
- * the caller of this function should set input_rt as NULL and input_rt_size
- * as zero respectively.
- *
- * More about documentation on resource table data structures can be found in
- * include/linux/remoteproc.h
- *
- * @ctx: PAS context
- * @pas_id: peripheral authentication service id
- * @input_rt: resource table buffer which is present in firmware binary
- * @input_rt_size: size of the resource table present in firmware binary
- * @output_rt_size: TrustZone expects caller should pass worst case size for
- * the output_rt_tzm.
- *
- * Return:
- * On success, returns a pointer to the allocated buffer containing the final
- * resource table and output_rt_size will have actual resource table size from
- * TrustZone. The caller is responsible for freeing the buffer. On failure,
- * returns ERR_PTR(-errno).
- */
-struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx,
- void *input_rt,
- size_t input_rt_size,
- size_t *output_rt_size)
+static void *__qcom_scm_pas_get_rsc_table2(struct device *dev,
+ struct qcom_pas_context *ctx,
+ void *input_rt,
+ size_t input_rt_size,
+ size_t *output_rt_size)
{
struct resource_table empty_rsc = {};
size_t size = SZ_16K;
@@ -910,11 +829,12 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
memcpy(input_rt_tzm, input_rt, input_rt_size);
- output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, input_rt_tzm,
+ output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id,
+ input_rt_tzm,
input_rt_size, &size);
if (PTR_ERR(output_rt_tzm) == -EOVERFLOW)
/* Try again with the size requested by the TZ */
- output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id,
+ output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id,
input_rt_tzm,
input_rt_size,
&size);
@@ -945,16 +865,20 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
return ret ? ERR_PTR(ret) : tbl_ptr;
}
+
+struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx,
+ void *input_rt,
+ size_t input_rt_size,
+ size_t *output_rt_size)
+{
+ return __qcom_scm_pas_get_rsc_table2(__scm->dev,
+ (struct qcom_pas_context *)ctx,
+ input_rt, input_rt_size,
+ output_rt_size);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_get_rsc_table);
-/**
- * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware
- * and reset the remote processor
- * @pas_id: peripheral authentication service id
- *
- * Return 0 on success.
- */
-int qcom_scm_pas_auth_and_reset(u32 pas_id)
+static int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 pas_id)
{
int ret;
struct qcom_scm_desc desc = {
@@ -974,7 +898,7 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id)
if (ret)
goto disable_clk;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(dev, &desc, &res);
qcom_scm_bw_disable();
disable_clk:
@@ -982,28 +906,15 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id)
return ret ? : res.result[0];
}
+
+int qcom_scm_pas_auth_and_reset(u32 pas_id)
+{
+ return __qcom_scm_pas_auth_and_reset(__scm->dev, pas_id);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset);
-/**
- * qcom_scm_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the
- * remote processor
- *
- * @ctx: Context saved during call to qcom_scm_pas_context_init()
- *
- * This function performs the necessary steps to prepare a PAS subsystem,
- * authenticate it using the provided metadata, and initiate a reset sequence.
- *
- * It should be used when Linux is in control setting up the IOMMU hardware
- * for remote subsystem during secure firmware loading processes. The preparation
- * step sets up a shmbridge over the firmware memory before TrustZone accesses the
- * firmware memory region for authentication. The authentication step verifies
- * the integrity and authenticity of the firmware or configuration using secure
- * metadata. Finally, the reset step ensures the subsystem starts in a clean and
- * sane state.
- *
- * Return: 0 on success, negative errno on failure.
- */
-int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
+static int __qcom_scm_pas_prepare_and_auth_reset(struct device *dev,
+ struct qcom_pas_context *ctx)
{
u64 handle;
int ret;
@@ -1014,7 +925,7 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
* memory region and then invokes a call to TrustZone to authenticate.
*/
if (!ctx->use_tzmem)
- return qcom_scm_pas_auth_and_reset(ctx->pas_id);
+ return __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id);
/*
* When Linux runs @ EL2 Linux must create the shmbridge itself and then
@@ -1024,20 +935,45 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
if (ret)
return ret;
- ret = qcom_scm_pas_auth_and_reset(ctx->pas_id);
+ ret = __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id);
qcom_tzmem_shm_bridge_delete(handle);
return ret;
}
+
+int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
+{
+ return __qcom_scm_pas_prepare_and_auth_reset(__scm->dev,
+ (struct qcom_pas_context *)ctx);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_prepare_and_auth_reset);
-/**
- * qcom_scm_pas_shutdown() - Shut down the remote processor
- * @pas_id: peripheral authentication service id
- *
- * Returns 0 on success.
- */
-int qcom_scm_pas_shutdown(u32 pas_id)
+static int __qcom_scm_pas_set_remote_state(struct device *dev, u32 state,
+ u32 pas_id)
+{
+ struct qcom_scm_desc desc = {
+ .svc = QCOM_SCM_SVC_BOOT,
+ .cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE,
+ .arginfo = QCOM_SCM_ARGS(2),
+ .args[0] = state,
+ .args[1] = pas_id,
+ .owner = ARM_SMCCC_OWNER_SIP,
+ };
+ struct qcom_scm_res res;
+ int ret;
+
+ ret = qcom_scm_call(dev, &desc, &res);
+
+ return ret ? : res.result[0];
+}
+
+int qcom_scm_set_remote_state(u32 state, u32 id)
+{
+ return __qcom_scm_pas_set_remote_state(__scm->dev, state, id);
+}
+EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state);
+
+static int __qcom_scm_pas_shutdown(struct device *dev, u32 pas_id)
{
int ret;
struct qcom_scm_desc desc = {
@@ -1057,7 +993,7 @@ int qcom_scm_pas_shutdown(u32 pas_id)
if (ret)
goto disable_clk;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(dev, &desc, &res);
qcom_scm_bw_disable();
disable_clk:
@@ -1065,16 +1001,14 @@ int qcom_scm_pas_shutdown(u32 pas_id)
return ret ? : res.result[0];
}
+
+int qcom_scm_pas_shutdown(u32 pas_id)
+{
+ return __qcom_scm_pas_shutdown(__scm->dev, pas_id);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_shutdown);
-/**
- * qcom_scm_pas_supported() - Check if the peripheral authentication service is
- * available for the given peripherial
- * @pas_id: peripheral authentication service id
- *
- * Returns true if PAS is supported for this peripheral, otherwise false.
- */
-bool qcom_scm_pas_supported(u32 pas_id)
+static bool __qcom_scm_pas_supported(struct device *dev, u32 pas_id)
{
int ret;
struct qcom_scm_desc desc = {
@@ -1086,16 +1020,49 @@ bool qcom_scm_pas_supported(u32 pas_id)
};
struct qcom_scm_res res;
- if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
+ if (!__qcom_scm_is_call_available(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PIL_PAS_IS_SUPPORTED))
return false;
- ret = qcom_scm_call(__scm->dev, &desc, &res);
+ ret = qcom_scm_call(dev, &desc, &res);
return ret ? false : !!res.result[0];
}
+
+bool qcom_scm_pas_supported(u32 pas_id)
+{
+ return __qcom_scm_pas_supported(__scm->dev, pas_id);
+}
EXPORT_SYMBOL_GPL(qcom_scm_pas_supported);
+static struct qcom_pas_ops qcom_pas_ops_scm = {
+ .drv_name = "qcom_scm",
+ .supported = __qcom_scm_pas_supported,
+ .init_image = __qcom_scm_pas_init_image2,
+ .mem_setup = __qcom_scm_pas_mem_setup,
+ .get_rsc_table = __qcom_scm_pas_get_rsc_table2,
+ .auth_and_reset = __qcom_scm_pas_auth_and_reset,
+ .prepare_and_auth_reset = __qcom_scm_pas_prepare_and_auth_reset,
+ .set_remote_state = __qcom_scm_pas_set_remote_state,
+ .shutdown = __qcom_scm_pas_shutdown,
+ .metadata_release = __qcom_scm_pas_metadata_release,
+};
+
+/**
+ * qcom_scm_is_pas_available() - Check if the peripheral authentication service
+ * is available via SCM or not
+ *
+ * Returns true if PAS is available, otherwise false.
+ */
+static bool qcom_scm_is_pas_available(void)
+{
+ if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
+ QCOM_SCM_PIL_PAS_AUTH_AND_RESET))
+ return false;
+
+ return true;
+}
+
static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
{
struct qcom_scm_desc desc = {
@@ -2782,6 +2749,11 @@ static int qcom_scm_probe(struct platform_device *pdev)
__get_convention();
+ if (qcom_scm_is_pas_available()) {
+ qcom_pas_ops_scm.dev = scm->dev;
+ qcom_pas_ops_register(&qcom_pas_ops_scm);
+ }
+
/*
* If "download mode" is requested, from this point on warmboot
* will cause the boot stages to enter download mode, unless
@@ -2818,6 +2790,7 @@ static void qcom_scm_shutdown(struct platform_device *pdev)
{
/* Clean shutdown, disable download mode to allow normal restart */
qcom_scm_set_download_mode(QCOM_DLOAD_NODUMP);
+ qcom_pas_ops_unregister();
}
static const struct of_device_id qcom_scm_dt_match[] = {
--
2.51.0
^ permalink raw reply related
* [PATCH v3 02/15] firmware: qcom: Add a generic PAS service
From: Sumit Garg @ 2026-03-27 13:10 UTC (permalink / raw)
To: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, robin.clark,
sean, akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
harshal.dev, linux-kernel, Sumit Garg
In-Reply-To: <20260327131043.627120-1-sumit.garg@kernel.org>
From: Sumit Garg <sumit.garg@oss.qualcomm.com>
Qcom platforms has the legacy of using non-standard SCM calls
splintered over the various kernel drivers. These SCM calls aren't
compliant with the standard SMC calling conventions which is a
prerequisite to enable migration to the FF-A specifications from Arm.
OP-TEE as an alternative trusted OS to Qualcomm TEE (QTEE) can't
support these non-standard SCM calls. And even for newer architectures
using S-EL2 with Hafnium support, QTEE won't be able to support SCM
calls either with FF-A requirements coming in. And with both OP-TEE
and QTEE drivers well integrated in the TEE subsystem, it makes further
sense to reuse the TEE bus client drivers infrastructure.
The added benefit of TEE bus infrastructure is that there is support
for discoverable/enumerable services. With that client drivers don't
have to manually invoke a special SCM call to know the service status.
So enable the generic Peripheral Authentication Service (PAS) provided
by the firmware. It acts as the common layer with different TZ
backends plugged in whether it's an SCM implementation or a proper
TEE bus based PAS service implementation.
Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
---
drivers/firmware/qcom/Kconfig | 8 +
drivers/firmware/qcom/Makefile | 1 +
drivers/firmware/qcom/qcom_pas.c | 288 +++++++++++++++++++++++++
drivers/firmware/qcom/qcom_pas.h | 50 +++++
include/linux/firmware/qcom/qcom_pas.h | 43 ++++
5 files changed, 390 insertions(+)
create mode 100644 drivers/firmware/qcom/qcom_pas.c
create mode 100644 drivers/firmware/qcom/qcom_pas.h
create mode 100644 include/linux/firmware/qcom/qcom_pas.h
diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig
index b477d54b495a..8653639d06db 100644
--- a/drivers/firmware/qcom/Kconfig
+++ b/drivers/firmware/qcom/Kconfig
@@ -6,6 +6,14 @@
menu "Qualcomm firmware drivers"
+config QCOM_PAS
+ tristate
+ help
+ Enable the generic Peripheral Authentication Service (PAS) provided
+ by the firmware. It acts as the common layer with different TZ
+ backends plugged in whether it's an SCM implementation or a proper
+ TEE bus based PAS service implementation.
+
config QCOM_SCM
select QCOM_TZMEM
tristate
diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile
index 0be40a1abc13..dc5ab45f906a 100644
--- a/drivers/firmware/qcom/Makefile
+++ b/drivers/firmware/qcom/Makefile
@@ -8,3 +8,4 @@ qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o
obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
+obj-$(CONFIG_QCOM_PAS) += qcom_pas.o
diff --git a/drivers/firmware/qcom/qcom_pas.c b/drivers/firmware/qcom/qcom_pas.c
new file mode 100644
index 000000000000..caf7fff33e5c
--- /dev/null
+++ b/drivers/firmware/qcom/qcom_pas.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/device/devres.h>
+#include <linux/firmware/qcom/qcom_pas.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "qcom_pas.h"
+
+static struct qcom_pas_ops *ops_ptr;
+
+/**
+ * devm_qcom_pas_context_alloc() - Allocate peripheral authentication service
+ * context for a given peripheral
+ *
+ * PAS context is device-resource managed, so the caller does not need
+ * to worry about freeing the context memory.
+ *
+ * @dev: PAS firmware device
+ * @pas_id: peripheral authentication service id
+ * @mem_phys: Subsystem reserve memory start address
+ * @mem_size: Subsystem reserve memory size
+ *
+ * Return: The new PAS context, or ERR_PTR() on failure.
+ */
+struct qcom_pas_context *devm_qcom_pas_context_alloc(struct device *dev,
+ u32 pas_id,
+ phys_addr_t mem_phys,
+ size_t mem_size)
+{
+ struct qcom_pas_context *ctx;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->dev = dev;
+ ctx->pas_id = pas_id;
+ ctx->mem_phys = mem_phys;
+ ctx->mem_size = mem_size;
+
+ return ctx;
+}
+EXPORT_SYMBOL_GPL(devm_qcom_pas_context_alloc);
+
+/**
+ * qcom_pas_init_image() - Initialize peripheral authentication service state
+ * machine for a given peripheral, using the metadata
+ * @pas_id: peripheral authentication service id
+ * @metadata: pointer to memory containing ELF header, program header table
+ * and optional blob of data used for authenticating the metadata
+ * and the rest of the firmware
+ * @size: size of the metadata
+ * @ctx: optional pas context
+ *
+ * Return: 0 on success.
+ *
+ * Upon successful return, the PAS metadata context (@ctx) will be used to
+ * track the metadata allocation, this needs to be released by invoking
+ * qcom_pas_metadata_release() by the caller.
+ */
+int qcom_pas_init_image(u32 pas_id, const void *metadata, size_t size,
+ struct qcom_pas_context *ctx)
+{
+ if (!ops_ptr)
+ return -ENODEV;
+
+ return ops_ptr->init_image(ops_ptr->dev, pas_id, metadata, size, ctx);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_init_image);
+
+/**
+ * qcom_pas_metadata_release() - release metadata context
+ * @ctx: pas context
+ */
+void qcom_pas_metadata_release(struct qcom_pas_context *ctx)
+{
+ if (!ctx || !ctx->ptr || !ops_ptr)
+ return;
+
+ ops_ptr->metadata_release(ops_ptr->dev, ctx);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_metadata_release);
+
+/**
+ * qcom_pas_mem_setup() - Prepare the memory related to a given peripheral
+ * for firmware loading
+ * @pas_id: peripheral authentication service id
+ * @addr: start address of memory area to prepare
+ * @size: size of the memory area to prepare
+ *
+ * Return: 0 on success.
+ */
+int qcom_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
+{
+ if (!ops_ptr)
+ return -ENODEV;
+
+ return ops_ptr->mem_setup(ops_ptr->dev, pas_id, addr, size);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_mem_setup);
+
+/**
+ * qcom_pas_get_rsc_table() - Retrieve the resource table in passed output buffer
+ * for a given peripheral.
+ *
+ * Qualcomm remote processor may rely on both static and dynamic resources for
+ * its functionality. Static resources typically refer to memory-mapped
+ * addresses required by the subsystem and are often embedded within the
+ * firmware binary and dynamic resources, such as shared memory in DDR etc.,
+ * are determined at runtime during the boot process.
+ *
+ * On Qualcomm Technologies devices, it's possible that static resources are
+ * not embedded in the firmware binary and instead are provided by TrustZone.
+ * However, dynamic resources are always expected to come from TrustZone. This
+ * indicates that for Qualcomm devices, all resources (static and dynamic) will
+ * be provided by TrustZone PAS service.
+ *
+ * If the remote processor firmware binary does contain static resources, they
+ * should be passed in input_rt. These will be forwarded to TrustZone for
+ * authentication. TrustZone will then append the dynamic resources and return
+ * the complete resource table in output_rt_tzm.
+ *
+ * If the remote processor firmware binary does not include a resource table,
+ * the caller of this function should set input_rt as NULL and input_rt_size
+ * as zero respectively.
+ *
+ * More about documentation on resource table data structures can be found in
+ * include/linux/remoteproc.h
+ *
+ * @ctx: PAS context
+ * @pas_id: peripheral authentication service id
+ * @input_rt: resource table buffer which is present in firmware binary
+ * @input_rt_size: size of the resource table present in firmware binary
+ * @output_rt_size: TrustZone expects caller should pass worst case size for
+ * the output_rt_tzm.
+ *
+ * Return:
+ * On success, returns a pointer to the allocated buffer containing the final
+ * resource table and output_rt_size will have actual resource table size from
+ * TrustZone. The caller is responsible for freeing the buffer. On failure,
+ * returns ERR_PTR(-errno).
+ */
+struct resource_table *qcom_pas_get_rsc_table(struct qcom_pas_context *ctx,
+ void *input_rt,
+ size_t input_rt_size,
+ size_t *output_rt_size)
+{
+ if (!ctx)
+ return ERR_PTR(-EINVAL);
+ if (!ops_ptr)
+ return ERR_PTR(-ENODEV);
+
+ return ops_ptr->get_rsc_table(ops_ptr->dev, ctx, input_rt,
+ input_rt_size, output_rt_size);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_get_rsc_table);
+
+/**
+ * qcom_pas_auth_and_reset() - Authenticate the given peripheral firmware
+ * and reset the remote processor
+ * @pas_id: peripheral authentication service id
+ *
+ * Return: 0 on success.
+ */
+int qcom_pas_auth_and_reset(u32 pas_id)
+{
+ if (!ops_ptr)
+ return -ENODEV;
+
+ return ops_ptr->auth_and_reset(ops_ptr->dev, pas_id);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_auth_and_reset);
+
+/**
+ * qcom_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the
+ * remote processor
+ *
+ * @ctx: Context saved during call to qcom_scm_pas_context_init()
+ *
+ * This function performs the necessary steps to prepare a PAS subsystem,
+ * authenticate it using the provided metadata, and initiate a reset sequence.
+ *
+ * It should be used when Linux is in control setting up the IOMMU hardware
+ * for remote subsystem during secure firmware loading processes. The
+ * preparation step sets up a shmbridge over the firmware memory before
+ * TrustZone accesses the firmware memory region for authentication. The
+ * authentication step verifies the integrity and authenticity of the firmware
+ * or configuration using secure metadata. Finally, the reset step ensures the
+ * subsystem starts in a clean and sane state.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int qcom_pas_prepare_and_auth_reset(struct qcom_pas_context *ctx)
+{
+ if (!ctx)
+ return -EINVAL;
+ if (!ops_ptr)
+ return -ENODEV;
+
+ return ops_ptr->prepare_and_auth_reset(ops_ptr->dev, ctx);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_prepare_and_auth_reset);
+
+/**
+ * qcom_pas_set_remote_state() - Set the remote processor state
+ * @state: peripheral state
+ * @pas_id: peripheral authentication service id
+ *
+ * Return: 0 on success.
+ */
+int qcom_pas_set_remote_state(u32 state, u32 pas_id)
+{
+ if (!ops_ptr)
+ return -ENODEV;
+
+ return ops_ptr->set_remote_state(ops_ptr->dev, state, pas_id);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_set_remote_state);
+
+/**
+ * qcom_pas_shutdown() - Shut down the remote processor
+ * @pas_id: peripheral authentication service id
+ *
+ * Return: 0 on success.
+ */
+int qcom_pas_shutdown(u32 pas_id)
+{
+ if (!ops_ptr)
+ return -ENODEV;
+
+ return ops_ptr->shutdown(ops_ptr->dev, pas_id);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_shutdown);
+
+/**
+ * qcom_pas_supported() - Check if the peripheral authentication service is
+ * available for the given peripheral
+ * @pas_id: peripheral authentication service id
+ *
+ * Return: true if PAS is supported for this peripheral, otherwise false.
+ */
+bool qcom_pas_supported(u32 pas_id)
+{
+ if (!ops_ptr)
+ return false;
+
+ return ops_ptr->supported(ops_ptr->dev, pas_id);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_supported);
+
+bool qcom_pas_is_available(void)
+{
+ /*
+ * The barrier for ops_ptr is intended to synchronize the data stores
+ * for the ops data structure when client drivers are in parallel
+ * checking for PAS service availability.
+ *
+ * Once the PAS backend becomes available, it is allowed for multiple
+ * threads to enter TZ for parallel bringup of co-processors during
+ * boot.
+ */
+ return !!smp_load_acquire(&ops_ptr);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_is_available);
+
+void qcom_pas_ops_register(struct qcom_pas_ops *ops)
+{
+ if (!qcom_pas_is_available())
+ /* Paired with smp_load_acquire() in qcom_pas_is_available() */
+ smp_store_release(&ops_ptr, ops);
+ else
+ pr_err("qcom_pas: ops already registered\n");
+}
+EXPORT_SYMBOL_GPL(qcom_pas_ops_register);
+
+void qcom_pas_ops_unregister(void)
+{
+ /* Paired with smp_load_acquire() in qcom_pas_is_available() */
+ smp_store_release(&ops_ptr, NULL);
+}
+EXPORT_SYMBOL_GPL(qcom_pas_ops_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Qualcomm common TZ PAS driver");
diff --git a/drivers/firmware/qcom/qcom_pas.h b/drivers/firmware/qcom/qcom_pas.h
new file mode 100644
index 000000000000..8643e2760602
--- /dev/null
+++ b/drivers/firmware/qcom/qcom_pas.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QCOM_PAS_INT_H
+#define __QCOM_PAS_INT_H
+
+struct device;
+
+/**
+ * struct qcom_pas_ops - Qcom Peripheral Authentication Service (PAS) ops
+ * @drv_name: PAS driver name.
+ * @dev: PAS device pointer.
+ * @supported: Peripheral supported callback.
+ * @init_image: Peripheral image initialization callback.
+ * @mem_setup: Peripheral memory setup callback.
+ * @get_rsc_table: Peripheral get resource table callback.
+ * @prepare_and_auth_reset: Peripheral prepare firmware authentication and
+ * reset callback.
+ * @auth_and_reset: Peripheral firmware authentication and reset
+ * callback.
+ * @set_remote_state: Peripheral set remote state callback.
+ * @shutdown: Peripheral shutdown callback.
+ * @metadata_release: Image metadata release callback.
+ */
+struct qcom_pas_ops {
+ const char *drv_name;
+ struct device *dev;
+ bool (*supported)(struct device *dev, u32 pas_id);
+ int (*init_image)(struct device *dev, u32 pas_id, const void *metadata,
+ size_t size, struct qcom_pas_context *ctx);
+ int (*mem_setup)(struct device *dev, u32 pas_id, phys_addr_t addr,
+ phys_addr_t size);
+ void *(*get_rsc_table)(struct device *dev, struct qcom_pas_context *ctx,
+ void *input_rt, size_t input_rt_size,
+ size_t *output_rt_size);
+ int (*prepare_and_auth_reset)(struct device *dev,
+ struct qcom_pas_context *ctx);
+ int (*auth_and_reset)(struct device *dev, u32 pas_id);
+ int (*set_remote_state)(struct device *dev, u32 state, u32 pas_id);
+ int (*shutdown)(struct device *dev, u32 pas_id);
+ void (*metadata_release)(struct device *dev,
+ struct qcom_pas_context *ctx);
+};
+
+void qcom_pas_ops_register(struct qcom_pas_ops *ops);
+void qcom_pas_ops_unregister(void);
+
+#endif /* __QCOM_PAS_INT_H */
diff --git a/include/linux/firmware/qcom/qcom_pas.h b/include/linux/firmware/qcom/qcom_pas.h
new file mode 100644
index 000000000000..65b1c9564458
--- /dev/null
+++ b/include/linux/firmware/qcom/qcom_pas.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2010-2015, 2018-2019 The Linux Foundation. All rights reserved.
+ * Copyright (C) 2015 Linaro Ltd.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __QCOM_PAS_H
+#define __QCOM_PAS_H
+
+#include <linux/err.h>
+#include <linux/types.h>
+
+struct qcom_pas_context {
+ struct device *dev;
+ u32 pas_id;
+ phys_addr_t mem_phys;
+ size_t mem_size;
+ void *ptr;
+ dma_addr_t phys;
+ ssize_t size;
+ bool use_tzmem;
+};
+
+bool qcom_pas_is_available(void);
+struct qcom_pas_context *devm_qcom_pas_context_alloc(struct device *dev,
+ u32 pas_id,
+ phys_addr_t mem_phys,
+ size_t mem_size);
+int qcom_pas_init_image(u32 pas_id, const void *metadata, size_t size,
+ struct qcom_pas_context *ctx);
+struct resource_table *qcom_pas_get_rsc_table(struct qcom_pas_context *ctx,
+ void *input_rt, size_t input_rt_size,
+ size_t *output_rt_size);
+int qcom_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size);
+int qcom_pas_auth_and_reset(u32 pas_id);
+int qcom_pas_prepare_and_auth_reset(struct qcom_pas_context *ctx);
+int qcom_pas_set_remote_state(u32 state, u32 pas_id);
+int qcom_pas_shutdown(u32 pas_id);
+bool qcom_pas_supported(u32 pas_id);
+void qcom_pas_metadata_release(struct qcom_pas_context *ctx);
+
+#endif /* __QCOM_PAS_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v3 01/15] arm64: dts: qcom: kodiak: Add EL2 overlay
From: Sumit Garg @ 2026-03-27 13:10 UTC (permalink / raw)
To: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, robin.clark,
sean, akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
harshal.dev, linux-kernel, Sumit Garg
In-Reply-To: <20260327131043.627120-1-sumit.garg@kernel.org>
From: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
All the existing variants Kodiak boards are using Gunyah hypervisor
which means that, so far, Linux-based OS could only boot in EL1 on those
devices. However, it is possible for us to boot Linux at EL2 on these
devices [1].
When running under Gunyah, the remote processor firmware IOMMU
streams are controlled by Gunyah. However, without Gunyah, the IOMMU is
managed by the consumer of this DeviceTree. Therefore, describe the
firmware streams for each remote processor.
Add a EL2-specific DT overlay and apply it to Kodiak IOT variant
devices to create -el2.dtb for each of them alongside "normal" dtb.
[1]
https://docs.qualcomm.com/bundle/publicresource/topics/80-70020-4/boot-developer-touchpoints.html#uefi
Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
[SG: watchdog fixup]
Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/Makefile | 2 ++
arch/arm64/boot/dts/qcom/kodiak-el2.dtso | 35 ++++++++++++++++++++++++
2 files changed, 37 insertions(+)
create mode 100644 arch/arm64/boot/dts/qcom/kodiak-el2.dtso
diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
index f80b5d9cf1e8..09a7f943190e 100644
--- a/arch/arm64/boot/dts/qcom/Makefile
+++ b/arch/arm64/boot/dts/qcom/Makefile
@@ -139,6 +139,8 @@ dtb-$(CONFIG_ARCH_QCOM) += qcs404-evb-4000.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs615-ride.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs6490-radxa-dragon-q6a.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2.dtb
+qcs6490-rb3gen2-el2-dtbs := qcs6490-rb3gen2.dtb kodiak-el2.dtbo
+dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-el2.dtb
qcs6490-rb3gen2-vision-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-vision-mezzanine.dtbo
qcs6490-rb3gen2-industrial-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo
diff --git a/arch/arm64/boot/dts/qcom/kodiak-el2.dtso b/arch/arm64/boot/dts/qcom/kodiak-el2.dtso
new file mode 100644
index 000000000000..0b3a69a0d765
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/kodiak-el2.dtso
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Kodiak specific modifications required to boot in EL2.
+ */
+
+
+/dts-v1/;
+/plugin/;
+
+&gpu_zap_shader {
+ status = "disabled";
+};
+
+&remoteproc_adsp {
+ iommus = <&apps_smmu 0x1800 0x0>;
+};
+
+&remoteproc_cdsp {
+ iommus = <&apps_smmu 0x11a0 0x0400>;
+};
+
+&remoteproc_wpss {
+ iommus = <&apps_smmu 0x1c03 0x1>,
+ <&apps_smmu 0x1c83 0x1>;
+};
+
+&venus {
+ status = "disabled";
+};
+
+&watchdog {
+ status = "okay";
+};
--
2.51.0
^ permalink raw reply related
* [PATCH v3 00/15] firmware: qcom: Add OP-TEE PAS service support
From: Sumit Garg @ 2026-03-27 13:10 UTC (permalink / raw)
To: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, robin.clark,
sean, akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
harshal.dev, linux-kernel, Sumit Garg
From: Sumit Garg <sumit.garg@oss.qualcomm.com>
Qcom platforms has the legacy of using non-standard SCM calls
splintered over the various kernel drivers. These SCM calls aren't
compliant with the standard SMC calling conventions which is a
prerequisite to enable migration to the FF-A specifications from Arm.
OP-TEE as an alternative trusted OS to Qualcomm TEE (QTEE) can't
support these non-standard SCM calls. And even for newer architectures
using S-EL2 with Hafnium support, QTEE won't be able to support SCM
calls either with FF-A requirements coming in. And with both OP-TEE
and QTEE drivers well integrated in the TEE subsystem, it makes further
sense to reuse the TEE bus client drivers infrastructure.
The added benefit of TEE bus infrastructure is that there is support
for discoverable/enumerable services. With that client drivers don't
have to manually invoke a special SCM call to know the service status.
So enable the generic Peripheral Authentication Service (PAS) provided
by the firmware. It acts as the common layer with different TZ
backends plugged in whether it's an SCM implementation or a proper
TEE bus based PAS service implementation.
The TEE PAS service ABI is designed to be extensible with additional API
as PTA_QCOM_PAS_CAPABILITIES. This allows to accommodate any future
extensions of the PAS service needed while still maintaining backwards
compatibility.
Currently OP-TEE support is being added to provide the backend PAS
service implementation which can be found as part of this PR [1].
This implementation has been tested on Kodiak/RB3Gen2 board with lemans
EVK board being the next target. In addition to that WIN/IPQ targets
planning to use OP-TEE will use this service too. Surely the backwards
compatibility is maintained and tested for SCM backend.
Patch summary:
- Patch #1: adds Kodiak EL2 overlay since boot stack with TF-A/OP-TEE
only allow UEFI and Linux to boot in EL2.
- Patch #2: adds generic PAS service.
- Patch #3: migrates SCM backend to generic PAS service.
- Patch #4: adds TEE/OP-TEE backend for generic PAS service.
- Patch #5-#13: migrates all client drivers to generic PAS service.
- Patch #14: drops legacy PAS SCM exported APIs.
The patch-set is based on v7.0-rc5 tag and can be found in git tree here
[2].
Merge strategy:
It is expected due to APIs dependency, the entire patch-set to go via
the Qcom tree. All other subsystem maintainers, it will be great if I
can get acks for the corresponding subsystem patches.
[1] https://github.com/OP-TEE/optee_os/pull/7721
[2] https://git.kernel.org/pub/scm/linux/kernel/git/sumit.garg/linux.git/log/?h=qcom-pas-v3
---
Changes in v3:
- Incorporated some style and misc. comments for patch #2, #3 and #4.
- Add QCOM_PAS Kconfig dependency for various subsystems.
- Switch from pseudo TA to proper TA invoke commands.
Changes in v2:
- Fixed kernel doc warnings.
- Polish commit message and comments for patch #2.
- Pass proper PAS ID in set_remote_state API for media firmware drivers.
- Added Maintainer entry and dropped MODULE_AUTHOR.
Mukesh Ojha (1):
arm64: dts: qcom: kodiak: Add EL2 overlay
Sumit Garg (14):
firmware: qcom: Add a generic PAS service
firmware: qcom_scm: Migrate to generic PAS service
firmware: qcom: Add a PAS TEE service
remoteproc: qcom_q6v5_pas: Switch over to generic PAS TZ APIs
remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs
soc: qcom: mdtloader: Switch to generic PAS TZ APIs
remoteproc: qcom_wcnss: Switch to generic PAS TZ APIs
remoteproc: qcom: Select QCOM_PAS generic service
drm/msm: Switch to generic PAS TZ APIs
media: qcom: Switch to generic PAS TZ APIs
net: ipa: Switch to generic PAS TZ APIs
wifi: ath12k: Switch to generic PAS TZ APIs
firmware: qcom_scm: Remove SCM PAS wrappers
MAINTAINERS: Add maintainer entry for Qualcomm PAS TZ service
MAINTAINERS | 9 +
arch/arm64/boot/dts/qcom/Makefile | 2 +
arch/arm64/boot/dts/qcom/kodiak-el2.dtso | 35 ++
drivers/firmware/qcom/Kconfig | 19 +
drivers/firmware/qcom/Makefile | 2 +
drivers/firmware/qcom/qcom_pas.c | 288 +++++++++++
drivers/firmware/qcom/qcom_pas.h | 50 ++
drivers/firmware/qcom/qcom_pas_tee.c | 478 ++++++++++++++++++
drivers/firmware/qcom/qcom_scm.c | 302 ++++-------
drivers/gpu/drm/msm/Kconfig | 1 +
drivers/gpu/drm/msm/adreno/a5xx_gpu.c | 4 +-
drivers/gpu/drm/msm/adreno/adreno_gpu.c | 11 +-
drivers/media/platform/qcom/iris/Kconfig | 25 +-
.../media/platform/qcom/iris/iris_firmware.c | 9 +-
drivers/media/platform/qcom/venus/Kconfig | 1 +
drivers/media/platform/qcom/venus/firmware.c | 11 +-
drivers/net/ipa/Kconfig | 2 +-
drivers/net/ipa/ipa_main.c | 13 +-
drivers/net/wireless/ath/ath12k/Kconfig | 2 +-
drivers/net/wireless/ath/ath12k/ahb.c | 8 +-
drivers/remoteproc/Kconfig | 1 +
drivers/remoteproc/qcom_q6v5_mss.c | 5 +-
drivers/remoteproc/qcom_q6v5_pas.c | 51 +-
drivers/remoteproc/qcom_wcnss.c | 12 +-
drivers/soc/qcom/mdt_loader.c | 12 +-
include/linux/firmware/qcom/qcom_pas.h | 43 ++
include/linux/firmware/qcom/qcom_scm.h | 29 --
include/linux/soc/qcom/mdt_loader.h | 6 +-
28 files changed, 1114 insertions(+), 317 deletions(-)
create mode 100644 arch/arm64/boot/dts/qcom/kodiak-el2.dtso
create mode 100644 drivers/firmware/qcom/qcom_pas.c
create mode 100644 drivers/firmware/qcom/qcom_pas.h
create mode 100644 drivers/firmware/qcom/qcom_pas_tee.c
create mode 100644 include/linux/firmware/qcom/qcom_pas.h
--
2.51.0
^ permalink raw reply
* Re: [PATCH] iwlegacy: 3945-mac: Fix GFP flags in allocation loop
From: Stanislaw Gruszka @ 2026-03-27 12:52 UTC (permalink / raw)
To: Brendan Jackman; +Cc: linux-wireless, linux-kernel
In-Reply-To: <20260327-iwlegacy-gfp-fix-v1-1-b83e4db0bd66@google.com>
On Fri, Mar 27, 2026 at 12:30:07PM +0000, Brendan Jackman wrote:
> Do not latch these flags, they should be re-evaluated for each
> iteration of the loop.
>
> Concretely, rxq->free_count is incremented during the loop so the
> __GFP_NOWARN decision may be stale. There may be other reasons to
> require the re-evaluation too.
>
> Suggested-by: Stanislaw Gruszka <stf_xl@wp.pl>
> Link: https://lore.kernel.org/all/20260327115739.GB16800@wp.pl/
> Signed-off-by: Brendan Jackman <jackmanb@google.com>
Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
> ---
> Transparency note: I am 100% taking Stanislaw's word for this. The bug
> being fixed here hasn't been reproduced and I don't have a way to test
> this patch at all. It does seem to make sense to me though and, well,
> Stanislaw is the mantainer :)
> ---
> drivers/net/wireless/intel/iwlegacy/3945-mac.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
> index c148654aa9533..e8ae6687c08ec 100644
> --- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c
> +++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
> @@ -979,9 +979,10 @@ il3945_rx_allocate(struct il_priv *il, gfp_t priority)
> struct page *page;
> dma_addr_t page_dma;
> unsigned long flags;
> - gfp_t gfp_mask = priority;
>
> while (1) {
> + gfp_t gfp_mask = priority;
> +
> spin_lock_irqsave(&rxq->lock, flags);
> if (list_empty(&rxq->rx_used)) {
> spin_unlock_irqrestore(&rxq->lock, flags);
>
> ---
> base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
> change-id: 20260327-iwlegacy-gfp-fix-d553e3340b4f
>
> Best regards,
> --
> Brendan Jackman <jackmanb@google.com>
>
^ permalink raw reply
* Re: [PATCH v2] wifi: rt2x00usb: fix devres lifetime
From: Greg KH @ 2026-03-27 12:45 UTC (permalink / raw)
To: Johan Hovold
Cc: Stanislaw Gruszka, linux-wireless, linux-kernel, stable,
Vishal Thanki
In-Reply-To: <20260327113219.1313748-1-johan@kernel.org>
On Fri, Mar 27, 2026 at 12:32:19PM +0100, Johan Hovold wrote:
> USB drivers bind to USB interfaces and any device managed resources
> should have their lifetime tied to the interface rather than parent USB
> device. This avoids issues like memory leaks when drivers are unbound
> without their devices being physically disconnected (e.g. on probe
> deferral or configuration changes).
>
> Fix the USB anchor lifetime so that it is released on driver unbind.
>
> Fixes: 8b4c0009313f ("rt2x00usb: Use usb anchor to manage URB")
> Cc: stable@vger.kernel.org # 4.7
> Cc: Vishal Thanki <vishalthanki@gmail.com>
> Signed-off-by: Johan Hovold <johan@kernel.org>
> ---
> drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> index 83d00b6baf64..174d89b0b1d7 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> @@ -826,7 +826,7 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
> if (retval)
> goto exit_free_device;
>
> - rt2x00dev->anchor = devm_kmalloc(&usb_dev->dev,
> + rt2x00dev->anchor = devm_kmalloc(&usb_intf->dev,
> sizeof(struct usb_anchor),
> GFP_KERNEL);
> if (!rt2x00dev->anchor) {
> --
> 2.52.0
>
>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
^ permalink raw reply
* [PATCH] iwlegacy: 3945-mac: Fix GFP flags in allocation loop
From: Brendan Jackman @ 2026-03-27 12:30 UTC (permalink / raw)
To: Stanislaw Gruszka; +Cc: linux-wireless, linux-kernel, Brendan Jackman
Do not latch these flags, they should be re-evaluated for each
iteration of the loop.
Concretely, rxq->free_count is incremented during the loop so the
__GFP_NOWARN decision may be stale. There may be other reasons to
require the re-evaluation too.
Suggested-by: Stanislaw Gruszka <stf_xl@wp.pl>
Link: https://lore.kernel.org/all/20260327115739.GB16800@wp.pl/
Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
Transparency note: I am 100% taking Stanislaw's word for this. The bug
being fixed here hasn't been reproduced and I don't have a way to test
this patch at all. It does seem to make sense to me though and, well,
Stanislaw is the mantainer :)
---
drivers/net/wireless/intel/iwlegacy/3945-mac.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
index c148654aa9533..e8ae6687c08ec 100644
--- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c
+++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
@@ -979,9 +979,10 @@ il3945_rx_allocate(struct il_priv *il, gfp_t priority)
struct page *page;
dma_addr_t page_dma;
unsigned long flags;
- gfp_t gfp_mask = priority;
while (1) {
+ gfp_t gfp_mask = priority;
+
spin_lock_irqsave(&rxq->lock, flags);
if (list_empty(&rxq->rx_used)) {
spin_unlock_irqrestore(&rxq->lock, flags);
---
base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
change-id: 20260327-iwlegacy-gfp-fix-d553e3340b4f
Best regards,
--
Brendan Jackman <jackmanb@google.com>
^ permalink raw reply related
* Re: [RFC PATCH 08/19] wifi: mac80211: clean up STA NSS handling
From: Pablo MG @ 2026-03-27 12:24 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327103659.5296c2f3a949.I97fb93ccc1b366110ab23de58fcd73676cdd85d6@changeid>
Hello,
If we are doing some cleanup, let's go full on it.
Le 27/03/2026 à 10:31, Johannes Berg a écrit :
> From: Johannes Berg <johannes.berg@intel.com>
>
> Move ieee80211_sta_init_nss() from VHT code to station code,
> and disentangle it from rate control. This way, it becomes
> clearer when 'rx_nss' is set up.
>
> While doing this, fix the client side code to set up
> link_sta->op_mode_nss instead of link_sta->pub->rx_nss for
> the opmode element in association response, and remove the
> (now wrong) comment about handling that in the function.
>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
> net/mac80211/ibss.c | 2 +
> net/mac80211/ieee80211_i.h | 2 +-
> net/mac80211/mesh_plink.c | 2 +
> net/mac80211/mlme.c | 11 +++--
> net/mac80211/ocb.c | 4 +-
> net/mac80211/rate.c | 4 +-
> net/mac80211/sta_info.c | 92 +++++++++++++++++++++++++++++++++++++
> net/mac80211/sta_info.h | 1 +
> net/mac80211/vht.c | 93 --------------------------------------
> 9 files changed, 108 insertions(+), 103 deletions(-)
>
[...]
> diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
> index b3a016f3736b..31cf45095c60 100644
> --- a/net/mac80211/sta_info.c
> +++ b/net/mac80211/sta_info.c
> @@ -3401,6 +3401,98 @@ void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
> sta_remove_link(sta, link_id, true);
> }
>
> +void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
> +{
> + u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
> + bool support_160;
`support_160` is only used by the `has_he` block, better define it only
there.
> +
> + if (link_sta->pub->eht_cap.has_eht) {
> + int i;
> + const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
> +
> + /* get the max nss for EHT over all possible bandwidths and mcs */
> + for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
> + eht_rx_nss = max_t(u8, eht_rx_nss,
> + u8_get_bits(rx_nss_mcs[i],
> + IEEE80211_EHT_MCS_NSS_RX));
> + }
> +
> + if (link_sta->pub->he_cap.has_he) {
> + int i;
> + u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
> + const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
> + u16 mcs_160_map =
> + le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
> + u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
> +
> + for (i = 7; i >= 0; i--) {
> + u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
> +
> + if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
> + rx_mcs_160 = i + 1;
> + break;
> + }
> + }
Why go throught the trouble of parsing `mcs_160_map` if `support_160` is
false ?
> + for (i = 7; i >= 0; i--) {
> + u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
> +
> + if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
> + rx_mcs_80 = i + 1;
> + break;
> + }
> + }
> +
> + support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
> + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
> +
> + if (support_160)
> + he_rx_nss = min(rx_mcs_80, rx_mcs_160);
Pretty sure this is an error, everywhere else we are taking the max NSS
but here we are taking the min ?!
> + else
> + he_rx_nss = rx_mcs_80;
> + }
> +
> + if (link_sta->pub->ht_cap.ht_supported) {
> + if (link_sta->pub->ht_cap.mcs.rx_mask[0])
> + ht_rx_nss++;
> + if (link_sta->pub->ht_cap.mcs.rx_mask[1])
> + ht_rx_nss++;
> + if (link_sta->pub->ht_cap.mcs.rx_mask[2])
> + ht_rx_nss++;
> + if (link_sta->pub->ht_cap.mcs.rx_mask[3])
> + ht_rx_nss++;
> + /* FIXME: consider rx_highest? */
> + }
> +
> + if (link_sta->pub->vht_cap.vht_supported) {
> + int i;
> + u16 rx_mcs_map;
> +
> + rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
> +
> + for (i = 7; i >= 0; i--) {
> + u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
> +
> + if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
> + vht_rx_nss = i + 1;
> + break;
> + }
> + }
> + /* FIXME: consider rx_highest? */
> + }
> +
> + rx_nss = max(vht_rx_nss, ht_rx_nss);
> + rx_nss = max(he_rx_nss, rx_nss);
> + rx_nss = max(eht_rx_nss, rx_nss);
> + rx_nss = max_t(u8, 1, rx_nss);
> + link_sta->capa_nss = rx_nss;
> +
> + if (link_sta->op_mode_nss)
> + link_sta->pub->rx_nss =
> + min_t(u8, rx_nss, link_sta->op_mode_nss);
> + else
> + link_sta->pub->rx_nss = rx_nss;
> +}
> +
> void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
> const u8 *ext_capab,
> unsigned int ext_capab_len)
[...]
Pablo MG
^ permalink raw reply
* Re: [PATCH v2] wifi: rt2x00usb: fix devres lifetime
From: Stanislaw Gruszka @ 2026-03-27 12:19 UTC (permalink / raw)
To: Johan Hovold; +Cc: linux-wireless, linux-kernel, stable, Vishal Thanki
In-Reply-To: <20260327113219.1313748-1-johan@kernel.org>
On Fri, Mar 27, 2026 at 12:32:19PM +0100, Johan Hovold wrote:
> USB drivers bind to USB interfaces and any device managed resources
> should have their lifetime tied to the interface rather than parent USB
> device. This avoids issues like memory leaks when drivers are unbound
> without their devices being physically disconnected (e.g. on probe
> deferral or configuration changes).
>
> Fix the USB anchor lifetime so that it is released on driver unbind.
>
> Fixes: 8b4c0009313f ("rt2x00usb: Use usb anchor to manage URB")
> Cc: stable@vger.kernel.org # 4.7
> Cc: Vishal Thanki <vishalthanki@gmail.com>
> Signed-off-by: Johan Hovold <johan@kernel.org>
Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
> ---
> drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> index 83d00b6baf64..174d89b0b1d7 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> @@ -826,7 +826,7 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
> if (retval)
> goto exit_free_device;
>
> - rt2x00dev->anchor = devm_kmalloc(&usb_dev->dev,
> + rt2x00dev->anchor = devm_kmalloc(&usb_intf->dev,
> sizeof(struct usb_anchor),
> GFP_KERNEL);
> if (!rt2x00dev->anchor) {
> --
> 2.52.0
>
^ permalink raw reply
* Re: [PATCH v2 03/15] firmware: qcom_scm: Migrate to generic PAS service
From: Sumit Garg @ 2026-03-27 12:18 UTC (permalink / raw)
To: Harshal Dev
Cc: linux-arm-msm, devicetree, dri-devel, freedreno, linux-media,
netdev, linux-wireless, ath12k, linux-remoteproc, andersson,
konradybcio, robh, krzk+dt, conor+dt, robin.clark, sean, akhilpo,
lumag, abhinav.kumar, jesszhan0024, marijn.suijten, airlied,
simona, vikash.garodia, dikshita.agarwal, bod, mchehab, elder,
andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
linux-kernel, Sumit Garg
In-Reply-To: <bc7b116d-de41-4b9a-9c84-1010e226bac8@oss.qualcomm.com>
On Fri, Mar 27, 2026 at 05:40:16PM +0530, Harshal Dev wrote:
>
>
> On 3/12/2026 11:57 AM, Sumit Garg wrote:
> > From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >
> > With the availability of generic PAS service, let's add SCM calls as
> > a backend to keep supporting legacy QTEE interfaces. The exported
> > qcom_scm* wrappers will get dropped once all the client drivers get
> > migrated as part of future patches.
> >
> > Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> > ---
> > drivers/firmware/qcom/Kconfig | 1 +
> > drivers/firmware/qcom/qcom_scm.c | 336 ++++++++++++++-----------------
> > 2 files changed, 156 insertions(+), 181 deletions(-)
> >
>
> [..]
>
> > diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
> > index 8fbc96693a55..2d7937ae7c8f 100644
> > --- a/drivers/firmware/qcom/qcom_scm.c
> > +++ b/drivers/firmware/qcom/qcom_scm.c
> > @@ -13,6 +13,7 @@
> > #include <linux/dma-mapping.h>
>
> [..]
>
> >
> > -/**
> > - * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service
> > - * context for a given peripheral
> > - *
> > - * PAS context is device-resource managed, so the caller does not need
> > - * to worry about freeing the context memory.
> > - *
> > - * @dev: PAS firmware device
> > - * @pas_id: peripheral authentication service id
> > - * @mem_phys: Subsystem reserve memory start address
> > - * @mem_size: Subsystem reserve memory size
> > - *
> > - * Returns: The new PAS context, or ERR_PTR() on failure.
> > - */
>
> Shouldn't we drop the documentation for the exported functions in this file as part of
> patch 14/15? After this patch is applied, the devm_qcom_scm_pas_context_alloc() function
> still remains exported and available.
I don't see value in maintaining redundant documentation during the
course of the patch-set. The wrappers are only maintained to keep the
individual commits compilable such that we don't break kernel git
bisection scripts.
-Sumit
^ permalink raw reply
* Re: [PATCH v2 03/15] firmware: qcom_scm: Migrate to generic PAS service
From: Harshal Dev @ 2026-03-27 12:10 UTC (permalink / raw)
To: Sumit Garg, linux-arm-msm, devicetree, dri-devel, freedreno,
linux-media, netdev, linux-wireless, ath12k, linux-remoteproc
Cc: andersson, konradybcio, robh, krzk+dt, conor+dt, robin.clark,
sean, akhilpo, lumag, abhinav.kumar, jesszhan0024, marijn.suijten,
airlied, simona, vikash.garodia, dikshita.agarwal, bod, mchehab,
elder, andrew+netdev, davem, edumazet, kuba, pabeni, jjohnson,
mathieu.poirier, trilokkumar.soni, mukesh.ojha, pavan.kondeti,
jorge.ramirez, tonyh, vignesh.viswanathan, srinivas.kandagatla,
amirreza.zarrabi, jens.wiklander, op-tee, apurupa, skare,
linux-kernel, Sumit Garg
In-Reply-To: <20260312062756.694390-4-sumit.garg@kernel.org>
On 3/12/2026 11:57 AM, Sumit Garg wrote:
> From: Sumit Garg <sumit.garg@oss.qualcomm.com>
>
> With the availability of generic PAS service, let's add SCM calls as
> a backend to keep supporting legacy QTEE interfaces. The exported
> qcom_scm* wrappers will get dropped once all the client drivers get
> migrated as part of future patches.
>
> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> ---
> drivers/firmware/qcom/Kconfig | 1 +
> drivers/firmware/qcom/qcom_scm.c | 336 ++++++++++++++-----------------
> 2 files changed, 156 insertions(+), 181 deletions(-)
>
[..]
> diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
> index 8fbc96693a55..2d7937ae7c8f 100644
> --- a/drivers/firmware/qcom/qcom_scm.c
> +++ b/drivers/firmware/qcom/qcom_scm.c
> @@ -13,6 +13,7 @@
> #include <linux/dma-mapping.h>
[..]
>
> -/**
> - * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service
> - * context for a given peripheral
> - *
> - * PAS context is device-resource managed, so the caller does not need
> - * to worry about freeing the context memory.
> - *
> - * @dev: PAS firmware device
> - * @pas_id: peripheral authentication service id
> - * @mem_phys: Subsystem reserve memory start address
> - * @mem_size: Subsystem reserve memory size
> - *
> - * Returns: The new PAS context, or ERR_PTR() on failure.
> - */
Shouldn't we drop the documentation for the exported functions in this file as part of
patch 14/15? After this patch is applied, the devm_qcom_scm_pas_context_alloc() function
still remains exported and available.
Regards,
Harshal
^ permalink raw reply
* Re: [PATCH v2 0/4] treewide: fixup gfp_t printks
From: Stanislaw Gruszka @ 2026-03-27 11:57 UTC (permalink / raw)
To: Brendan Jackman
Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Andrew Morton, Allison Henderson, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, dri-devel,
linux-kernel, linux-wireless, kasan-dev, linux-mm, netdev,
linux-rdma, rds-devel
In-Reply-To: <20260326-gfp64-v2-0-d916021cecdf@google.com>
On Thu, Mar 26, 2026 at 12:31:56PM +0000, Brendan Jackman wrote:
> This patchset used to be about switching gfp_t to unsigned long. That is
> probably not gonna happen any more but while writing it I found these
> cleanups that seem worthwhile regardless.
For the record, these patches are independent and could be sent separately
to the appropriate maintainers. Doing so would avoid unnecessarily large CC
lists for people not interested about the changes in other subsystems.
Regards
Stanislaw
> Signed-off-by: Brendan Jackman <jackmanb@google.com>
> ---
> Changes in v2:
> - Drop gfp_t changes
> - Add correct CCs
> - Add minor fixups to preexisting code spotted by AI review
> - Link to v1: https://lore.kernel.org/r/20260319-gfp64-v1-0-2c73b8d42b7f@google.com
>
> ---
> Brendan Jackman (4):
> drm/managed: Use special gfp_t format specifier
> iwlegacy: 3945-mac: Fixup allocation failure log
> mm/kfence: Use special gfp_t format specifier
> net/rds: Use special gfp_t format specifier
>
> drivers/gpu/drm/drm_managed.c | 4 ++--
> drivers/net/wireless/intel/iwlegacy/3945-mac.c | 4 ++--
> mm/kfence/kfence_test.c | 2 +-
> net/rds/tcp_recv.c | 2 +-
> 4 files changed, 6 insertions(+), 6 deletions(-)
> ---
> base-commit: c369299895a591d96745d6492d4888259b004a9e
> change-id: 20260319-gfp64-7a970a80ba4e
>
> Best regards,
> --
> Brendan Jackman <jackmanb@google.com>
>
^ permalink raw reply
* Re: [PATCH v2 2/4] iwlegacy: 3945-mac: Fixup allocation failure log
From: Stanislaw Gruszka @ 2026-03-27 11:45 UTC (permalink / raw)
To: Brendan Jackman
Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Andrew Morton, Allison Henderson, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, dri-devel,
linux-kernel, linux-wireless, kasan-dev, linux-mm, netdev,
linux-rdma, rds-devel
In-Reply-To: <20260326-gfp64-v2-2-d916021cecdf@google.com>
Hi,
On Thu, Mar 26, 2026 at 12:31:58PM +0000, Brendan Jackman wrote:
> Fix 2 issues spotted by AI[0]:
>
> 1. Missing space after the full stop.
>
> 2. Wrong GFP flags are printed.
We should also initialize gfp_mask = priority inside the loop.
But this can be done as separate patch.
> And also switch to %pGg for the GFP flags. This produces nice readable
> output and decouples the format string from the size of gfp_t.
>
> [0] https://sashiko.dev/#/patchset/20260319-gfp64-v1-0-2c73b8d42b7f%40google.com
>
> Signed-off-by: Brendan Jackman <jackmanb@google.com>
Acked-by: Stanislaw Gruszka <stf_xl@wp.pl>
> ---
> drivers/net/wireless/intel/iwlegacy/3945-mac.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
> index c148654aa9533..88b31e0b9568c 100644
> --- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c
> +++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
> @@ -1002,9 +1002,9 @@ il3945_rx_allocate(struct il_priv *il, gfp_t priority)
> D_INFO("Failed to allocate SKB buffer.\n");
> if (rxq->free_count <= RX_LOW_WATERMARK &&
> net_ratelimit())
> - IL_ERR("Failed to allocate SKB buffer with %0x."
> + IL_ERR("Failed to allocate SKB buffer with %pGg. "
> "Only %u free buffers remaining.\n",
> - priority, rxq->free_count);
> + &gfp_mask, rxq->free_count);
> /* We don't reschedule replenish work here -- we will
> * call the restock method and if it still needs
> * more buffers it will schedule replenish */
>
> --
> 2.51.2
>
^ permalink raw reply
* [PATCH v2] wifi: rt2x00usb: fix devres lifetime
From: Johan Hovold @ 2026-03-27 11:32 UTC (permalink / raw)
To: Stanislaw Gruszka
Cc: linux-wireless, linux-kernel, Johan Hovold, stable, Vishal Thanki
USB drivers bind to USB interfaces and any device managed resources
should have their lifetime tied to the interface rather than parent USB
device. This avoids issues like memory leaks when drivers are unbound
without their devices being physically disconnected (e.g. on probe
deferral or configuration changes).
Fix the USB anchor lifetime so that it is released on driver unbind.
Fixes: 8b4c0009313f ("rt2x00usb: Use usb anchor to manage URB")
Cc: stable@vger.kernel.org # 4.7
Cc: Vishal Thanki <vishalthanki@gmail.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
index 83d00b6baf64..174d89b0b1d7 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
@@ -826,7 +826,7 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
if (retval)
goto exit_free_device;
- rt2x00dev->anchor = devm_kmalloc(&usb_dev->dev,
+ rt2x00dev->anchor = devm_kmalloc(&usb_intf->dev,
sizeof(struct usb_anchor),
GFP_KERNEL);
if (!rt2x00dev->anchor) {
--
2.52.0
^ permalink raw reply related
* Re: [PATCH] wifi: rt2x00usb: fix devres lifetime
From: Johan Hovold @ 2026-03-27 11:28 UTC (permalink / raw)
To: Stanislaw Gruszka
Cc: linux-wireless, linux-kernel, stable, Jakob Unterwurzacher
In-Reply-To: <20260327110730.GA16592@wp.pl>
On Fri, Mar 27, 2026 at 12:07:30PM +0100, Stanislaw Gruszka wrote:
> Hi
>
> On Fri, Mar 27, 2026 at 11:47:26AM +0100, Johan Hovold wrote:
> > USB drivers bind to USB interfaces and any device managed resources
> > should have their lifetime tied to the interface rather than parent USB
> > device. This avoids issues like memory leaks when drivers are unbound
> > without their devices being physically disconnected (e.g. on probe
> > deferral or configuration changes).
> >
> > Fix the USB anchor lifetime so that it is released on driver unbind.
> >
> > Fixes: 9f2d3eae88d2 ("can: ucan: add driver for Theobroma Systems UCAN devices")
>
> Fix tag is wrong (and also cc based on it). Tag should be:
> 8b4c0009313f ("rt2x00usb: Use usb anchor to manage URB").
Of course, thanks for catching that. I'll send a v2.
Johan
^ permalink raw reply
* Re: [PATCH] wifi: rt2x00usb: fix devres lifetime
From: Stanislaw Gruszka @ 2026-03-27 11:07 UTC (permalink / raw)
To: Johan Hovold; +Cc: linux-wireless, linux-kernel, stable, Jakob Unterwurzacher
In-Reply-To: <20260327104726.1310327-1-johan@kernel.org>
Hi
On Fri, Mar 27, 2026 at 11:47:26AM +0100, Johan Hovold wrote:
> USB drivers bind to USB interfaces and any device managed resources
> should have their lifetime tied to the interface rather than parent USB
> device. This avoids issues like memory leaks when drivers are unbound
> without their devices being physically disconnected (e.g. on probe
> deferral or configuration changes).
>
> Fix the USB anchor lifetime so that it is released on driver unbind.
>
> Fixes: 9f2d3eae88d2 ("can: ucan: add driver for Theobroma Systems UCAN devices")
Fix tag is wrong (and also cc based on it). Tag should be:
8b4c0009313f ("rt2x00usb: Use usb anchor to manage URB").
Regards
Stanislaw
> Cc: stable@vger.kernel.org # 4.19
> Cc: Jakob Unterwurzacher <jakob.unterwurzacher@theobroma-systems.com>
> Signed-off-by: Johan Hovold <johan@kernel.org>
> ---
> drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> index 83d00b6baf64..174d89b0b1d7 100644
> --- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
> @@ -826,7 +826,7 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
> if (retval)
> goto exit_free_device;
>
> - rt2x00dev->anchor = devm_kmalloc(&usb_dev->dev,
> + rt2x00dev->anchor = devm_kmalloc(&usb_intf->dev,
> sizeof(struct usb_anchor),
> GFP_KERNEL);
> if (!rt2x00dev->anchor) {
> --
> 2.52.0
>
^ permalink raw reply
* Re: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: Bitterblue Smith @ 2026-03-27 10:52 UTC (permalink / raw)
To: LB F, Ping-Ke Shih
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CALdGYqQ_RCOwa2J-GsEyCxCQ4bztyxSzbc+6eYNesBSaY3Nt-w@mail.gmail.com>
On 27/03/2026 01:52, LB F wrote:
> Hi Ping-Ke,
>
> This is Oleksandr Havrylov again. Thank you for the ASPM/LPS Deep
> quirk and the rate validation patches — they are both working correctly
> (zero h2c timeouts, zero lps failures, zero mac80211 warnings).
>
> However, I'm experiencing a different, separate bug that causes kernel
> oops and makes the system completely unresponsive, requiring a hard
> power-off. After disassembling the crash site, I believe I've found
> the root cause.
>
> == Summary ==
>
> When firmware sends a C2H_ADAPTIVITY (0x37) command to an RTL8821CE
> adapter, rtw_fw_adaptivity_result() dereferences rtwdev->chip->edcca_th
> without a NULL check. The RTL8821C chip_info (rtw8821c_hw_spec) does
> not define edcca_th, so the pointer is NULL, causing a kernel oops.
>
> The crash occurs on the phy0 workqueue while holding rtwdev->mutex,
> which never gets released. This causes all subsequent processes that
> touch the network stack to hang in uninterruptible D-state, making
> the system completely unresponsive and requiring a hard power-off.
>
> == Root cause analysis ==
>
> rtw_fw_adaptivity_result() in fw.c (line ~282):
>
> static void rtw_fw_adaptivity_result(struct rtw_dev *rtwdev, u8 *payload,
> u8 length)
> {
> const struct rtw_hw_reg_offset *edcca_th = rtwdev->chip->edcca_th;
> ...
> rtw_dbg(rtwdev, RTW_DBG_ADAPTIVITY, "Reg Setting: L2H %x H2L %x\n",
> rtw_read32_mask(rtwdev, edcca_th[EDCCA_TH_L2H_IDX].hw_reg.addr,
> ^^^^^^^^^ NULL dereference here
> edcca_th[EDCCA_TH_L2H_IDX].hw_reg.mask),
> ...
>
> The RTL8822C defines .edcca_th = rtw8822c_edcca_th in its chip_info,
> but RTL8821C does not set this field at all — it remains NULL.
>
> I verified this by disassembling the compiled rtw_core.ko module:
>
> Crash RIP: rtw_fw_c2h_cmd_handle+0x127
> Address: 0x1d527 = movl (%r12), %esi
>
> R12 is loaded at +0xe5 (0x1d4e5):
> movq 0x140(%rax), %r12 ; rax = rtwdev->chip
> ; 0x140 = offset of edcca_th in rtw_chip_info
> ; R12 = chip->edcca_th = NULL for 8821c
>
> The function is entered via:
> +0xd8 (0x1d4d8): cmpl $0x37, %ecx ; c2h->id == C2H_ADAPTIVITY (0x37)
>
> With R12 = 0, the instruction at +0x127:
> movl (%r12), %esi ; reads from address 0x0 → NULL pointer dereference
>
> I also confirmed that rtw8821c_hw_spec in the mainline kernel
> (torvalds/linux master, rtw8821c.c) does NOT set .edcca_th.
>
> == Reproduction ==
>
> The crash is highly reproducible: it occurred in 4 out of 7 recent
> boots. It happens during normal active usage with no specific trigger.
>
> boot date/time of crash uptime at crash
> -5 2026-03-25 00:58:06 ~2 min
> -4 2026-03-25 21:32:00 ~6h
> -3 2026-03-26 00:28:14 ~2.5h
> -1 2026-03-27 00:56:58 ~23.5h
>
> Both ASPM and LPS Deep are disabled via the DMI quirk. The crash
> occurs every time with the same pattern and same RIP offset (+0x127).
>
> == Crash pattern ==
>
> Every crash follows the same sequence:
>
> 1) Burst of 50-60 "unused phy status page" messages in ~1 second:
>
> rtw_8821ce 0000:13:00.0: unused phy status page (8)
> rtw_8821ce 0000:13:00.0: unused phy status page (2)
> ... (50+ more within same second)
>
It looks like the firmware is not sending C2H_ADAPTIVITY (unexpected
for RTL8821CE), but rather you are getting garbage RX data. I am
curious what kind of garbage it is. Can you try this?
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c
index da67a6845fd5..aae246c2bc8e 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c
@@ -665,6 +665,7 @@ static void query_phy_status(struct rtw_dev *rtwdev, u8 *phy_status,
struct rtw_rx_pkt_stat *pkt_stat)
{
u8 page;
+ u8 *rxdesc = phy_status - rtwdev->chip->rx_pkt_desc_sz - pkt_stat->shift;
page = *phy_status & 0xf;
@@ -677,6 +678,10 @@ static void query_phy_status(struct rtw_dev *rtwdev, u8 *phy_status,
break;
default:
rtw_warn(rtwdev, "unused phy status page (%d)\n", page);
+ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 4, 4,
+ rxdesc, 56, true);
+ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
+ rxdesc, 40, true);
return;
}
}
^ permalink raw reply related
* [PATCH] wifi: rt2x00usb: fix devres lifetime
From: Johan Hovold @ 2026-03-27 10:47 UTC (permalink / raw)
To: Stanislaw Gruszka
Cc: linux-wireless, linux-kernel, Johan Hovold, stable,
Jakob Unterwurzacher
USB drivers bind to USB interfaces and any device managed resources
should have their lifetime tied to the interface rather than parent USB
device. This avoids issues like memory leaks when drivers are unbound
without their devices being physically disconnected (e.g. on probe
deferral or configuration changes).
Fix the USB anchor lifetime so that it is released on driver unbind.
Fixes: 9f2d3eae88d2 ("can: ucan: add driver for Theobroma Systems UCAN devices")
Cc: stable@vger.kernel.org # 4.19
Cc: Jakob Unterwurzacher <jakob.unterwurzacher@theobroma-systems.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
index 83d00b6baf64..174d89b0b1d7 100644
--- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
+++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c
@@ -826,7 +826,7 @@ int rt2x00usb_probe(struct usb_interface *usb_intf,
if (retval)
goto exit_free_device;
- rt2x00dev->anchor = devm_kmalloc(&usb_dev->dev,
+ rt2x00dev->anchor = devm_kmalloc(&usb_intf->dev,
sizeof(struct usb_anchor),
GFP_KERNEL);
if (!rt2x00dev->anchor) {
--
2.52.0
^ permalink raw reply related
* [PATCH] wifi: mac80211: handle VHT EXT NSS in ieee80211_determine_our_sta_mode()
From: Nicolas Escande @ 2026-03-27 10:02 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes.berg
A station which has a NSS ratio on the number of streams it is capable of
in 160MHz VHT operation is supposed to use the 'Extended NSS BW Support'
as defined by section '9.4.2.156.2 VHT Capabilities Information field'.
This was missing in ieee80211_determine_our_sta_mode() and so we would
wrongfully downgrade our bandwidth when connecting to an AP that supported
160MHz with messages such as:
[ 37.638346] wlan1: AP XX:XX:XX:XX:XX:XX changed bandwidth in assoc response, new used config is 5280.000 MHz, width 3 (5290.000/0 MHz)
Fixes: 310c8387c638 ("wifi: mac80211: clean up connection process")
Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
---
net/mac80211/mlme.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 810bea1aacc5..1f3f7fa79084 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -6009,7 +6009,8 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
if (is_5ghz &&
!(vht_cap.cap & (IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ |
- IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ))) {
+ IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ |
+ IEEE80211_VHT_CAP_EXT_NSS_BW_MASK))) {
conn->bw_limit = IEEE80211_CONN_BW_LIMIT_80;
mlme_link_id_dbg(sdata, link_id,
"no VHT 160 MHz capability on 5 GHz, limiting to 80 MHz");
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2] dt-bindings: net: wireless: brcm: Add compatible for bcm43752
From: Ronald Claveau @ 2026-03-27 9:36 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
van Spriel
Cc: linux-wireless, devicetree, linux-kernel, netdev, Conor Dooley,
Ronald Claveau
Add bcm43752 compatible with its bcm4329 compatible fallback.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
---
The Khadas VIM4 board based on Amlogic A311D2 aka T7 features an AP6275s Wi-Fi/Bluetooth module with a BCM43752 chipset.
This patch aims to add this chipset with its fallback to bcm4329 compatible.
The original patch series is here:
https://lore.kernel.org/r/20260326-add-emmc-t7-vim4-v5-0-d3f182b48e9d@aliel.fr
---
Changes in v2:
- Add netdev in CC.
- Link to v1: https://lore.kernel.org/r/20260326-add-bcm43752-compatible-v1-1-b3b9a58ab38b@aliel.fr
---
Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml
index 3be7576787644..81fd3e37452a6 100644
--- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml
+++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml
@@ -42,6 +42,7 @@ properties:
- brcm,bcm4356-fmac
- brcm,bcm4359-fmac
- brcm,bcm4366-fmac
+ - brcm,bcm43752-fmac
- cypress,cyw4373-fmac
- cypress,cyw43012-fmac
- infineon,cyw43439-fmac
---
base-commit: 45b2b84ac6fde39c427018d6cdf7d44258938faa
change-id: 20260326-add-bcm43752-compatible-e264a4f7973a
Best regards,
--
Ronald Claveau <linux-kernel-dev@aliel.fr>
^ permalink raw reply related
* [RFC PATCH 19/19] wifi: mac80211: fix per-station PHY capability bandwidth
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
When a (link) station connected to an AP interface is not
capable of EHT, it's possible that the AP interface is in
160 MHz but the HE channel is narrower, e.g. when EHT has
puncturing. In this case, the code doesn't correctly set
the STAs bandwidth, the station might be capable of using
160 MHz, but it can't use EHT 160 MHz with puncturing, so
it must be set to narrower.
Track the AP's 'he_and_lower_bw' bandwidth, use that when
calculating the maximum bandwidth to transmit to/from any
station not capable of EHT, and update all stations and
the chanctx min_def when it changes.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 116 +++++++++++++++++++++++++++++++++++++
net/mac80211/chan.c | 9 +--
net/mac80211/ieee80211_i.h | 2 +
net/mac80211/sta_info.c | 47 ++++++++++++++-
4 files changed, 168 insertions(+), 6 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 88057efae8e3..bf63039f1ade 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1310,6 +1310,120 @@ ieee80211_copy_rnr_beacon(u8 *pos, struct cfg80211_rnr_elems *dst,
return offset;
}
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params)
+{
+ const struct ieee80211_vht_operation *vht_oper = params->vht_oper;
+ int ccfs0, ccfs1;
+
+ if (params->he_oper) {
+ const struct ieee80211_he_6ghz_oper *he_6ghz_oper;
+
+ if (params->he_oper->he_oper_params &
+ cpu_to_le32(IEEE80211_HE_OPERATION_VHT_OPER_INFO))
+ vht_oper = (void *)params->he_oper->optional;
+
+ he_6ghz_oper = ieee80211_he_6ghz_oper(params->he_oper);
+ if (he_6ghz_oper) {
+ switch (u8_get_bits(he_6ghz_oper->control,
+ IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH)) {
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_20MHZ:
+ return IEEE80211_STA_RX_BW_20;
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_40MHZ:
+ return IEEE80211_STA_RX_BW_40;
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_80MHZ:
+ return IEEE80211_STA_RX_BW_80;
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ:
+ return IEEE80211_STA_RX_BW_160;
+ }
+ }
+ }
+
+ if (vht_oper) {
+ switch (vht_oper->chan_width) {
+ case IEEE80211_VHT_CHANWIDTH_USE_HT:
+ /* check for HT (or fall down to 20) below */
+ break;
+ case IEEE80211_VHT_CHANWIDTH_160MHZ:
+ case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+ /* deprecated encodings */
+ return IEEE80211_STA_RX_BW_160;
+ case IEEE80211_VHT_CHANWIDTH_80MHZ:
+ /*
+ * See IEEE 802.11-2020 Table 9-352-BSS bandwidth
+ * when the VHT Operation Information field Channel
+ * Width subfield is 1
+ *
+ * (IEEE80211_VHT_CHANWIDTH_80MHZ == 1)
+ */
+ ccfs0 = vht_oper->center_freq_seg0_idx;
+ ccfs1 = vht_oper->center_freq_seg1_idx;
+ if (!ccfs0)
+ return IEEE80211_STA_RX_BW_80;
+ if (ccfs1 && abs(ccfs1 - ccfs0) == 8)
+ return IEEE80211_STA_RX_BW_160;
+ /* 80+80 - RX BW doesn't cover that / uses 160 */
+ if (ccfs1 && abs(ccfs1 - ccfs0) > 16)
+ return IEEE80211_STA_RX_BW_160;
+ fallthrough;
+ default:
+ /* reserved encoding - assume 80 */
+ return IEEE80211_STA_RX_BW_80;
+ }
+ }
+
+ if (params->ht_oper) {
+ switch (u8_get_bits(params->ht_oper->ht_param,
+ IEEE80211_HT_PARAM_CHA_SEC_OFFSET)) {
+ case IEEE80211_HT_PARAM_CHA_SEC_NONE:
+ default: /* invalid values */
+ return IEEE80211_STA_RX_BW_20;
+ case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+ case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+ return IEEE80211_STA_RX_BW_40;
+ }
+ }
+
+ /* nothing found, must be 20 MHz */
+ return IEEE80211_STA_RX_BW_20;
+}
+
+static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
+ struct cfg80211_beacon_data *params)
+{
+ struct ieee80211_local *local = link->sdata->local;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+
+ /*
+ * Updating the beacon might, without even changing the channel, cause
+ * the usable bandwidth for some stations to be changed, for example
+ * if the beacon configuration is EHT with 160 MHz, HE could change
+ * between 20, 40, 80 and 160 MHz, and HE (or lower) clients need to
+ * be handled accordingly.
+ * Calculate the HE and lower bandwidth and apply that to all stations.
+ *
+ * In the future, this also needs to calculate EHT bandwidth and apply
+ * it to all stations not using UHR DBE, since the chandef would then
+ * include DBE.
+ */
+
+ if (link->conf->chanreq.oper.chan->band == NL80211_BAND_S1GHZ)
+ return;
+
+ link->he_and_lower_bw = ieee80211_calc_ap_he_and_lower(params);
+
+ chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata);
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+
+ /*
+ * Note: this relies on ieee80211_recalc_chanctx_min_def() having
+ * the side effect of updating all stations, if they changed; that
+ * was normally for when the chandef changed but is used here too.
+ */
+ ieee80211_recalc_chanctx_min_def(local, chanctx);
+}
+
static int
ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
@@ -1448,6 +1562,8 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
if (old)
kfree_rcu(old, rcu_head);
+ ieee80211_update_ap_bandwidth(link, params);
+
*changed |= _changed;
return 0;
}
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 130d272b048e..18d4c120b0c7 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -651,6 +651,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
* recalc the min required chan width of the channel context, which is
* the max of min required widths of all the interfaces bound to this
* channel context.
+ *
+ * Note: ieee80211_update_ap_bandwidth() relies on this iterating all
+ * affected stations, even if min_def didn't change.
*/
static void
_ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
@@ -661,13 +664,11 @@ _ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
u32 changed = __ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for,
check_reserved);
- if (!changed)
- return;
-
/* check is BW narrowed */
ieee80211_chan_bw_change(local, ctx, false, true);
- drv_change_chanctx(local, ctx, changed);
+ if (changed)
+ drv_change_chanctx(local, ctx, changed);
/* check is BW wider */
ieee80211_chan_bw_change(local, ctx, false, false);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 1d9a8ae7390e..d747fc4c7a85 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1116,6 +1116,8 @@ struct ieee80211_link_data {
struct ieee80211_bss_conf *conf;
+ enum ieee80211_sta_rx_bandwidth he_and_lower_bw;
+
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
#endif
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 76a2536d2f23..4d6d7ef868fb 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3598,6 +3598,49 @@ ieee80211_sta_bw_capability(struct link_sta_info *link_sta,
return IEEE80211_STA_RX_BW_80;
}
+/**
+ * ieee80211_sta_usable_bw - get STA's usable bandwidth capability
+ * @link_sta: the (link) STA to get the capability for
+ * @band: the band to get the capability on
+ *
+ * If the STA is on an AP interface, take into account the AP's
+ * bandwidth corresponding to this station's PHY capability
+ *
+ * Return: the maximum bandwidth supported by the STA on the
+ * connection to the interface it's connected to
+ */
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_usable_bw(struct link_sta_info *link_sta,
+ enum nl80211_band band)
+{
+ struct ieee80211_sub_if_data *sdata = link_sta->sta->sdata;
+ enum ieee80211_sta_rx_bandwidth bw;
+ struct ieee80211_link_data *link;
+
+ bw = ieee80211_sta_bw_capability(link_sta, band);
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
+ sdata = get_bss_sdata(sdata);
+
+ /* for a STA to exist on VLAN, it must have AP */
+ if (WARN_ON(!sdata))
+ return IEEE80211_STA_RX_BW_20;
+ }
+
+ if (sdata->vif.type != NL80211_IFTYPE_AP)
+ return bw;
+
+ /* for a link STA to exist, vif must have the link */
+ link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
+ if (WARN_ON(!link))
+ return IEEE80211_STA_RX_BW_20;
+
+ if (link_sta->pub->eht_cap.has_eht)
+ return bw;
+
+ return min(bw, link->he_and_lower_bw);
+}
+
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
@@ -3612,7 +3655,7 @@ ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
* at a higher bandwidth first while reducing bandwidth, and
* change the chanctx only after the peer accepts, etc.)
*/
- return min(ieee80211_sta_bw_capability(link_sta, chandef->chan->band),
+ return min(ieee80211_sta_usable_bw(link_sta, chandef->chan->band),
link_sta->rx_omi_bw_rx);
}
@@ -3628,7 +3671,7 @@ ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
bss_width = chandef->width;
band = chandef->chan->band;
- bw = ieee80211_sta_bw_capability(link_sta, band);
+ bw = ieee80211_sta_usable_bw(link_sta, band);
bw = min(bw, link_sta->op_mode_bw);
/* also limit to RX OMI bandwidth we TX to the STA */
bw = min(bw, link_sta->rx_omi_bw_tx);
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 18/19] wifi: mac80211: clarify per-STA bandwidth handling
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
The per-STA bandwidth handling is completely confusing, given
the function names etc.
Move everything to sta_info.c and rename the functions to
accurately reflect what they return:
- ieee80211_sta_bw_capability()
- ieee80211_sta_current_bw() can return the appropriate
bandwidth in the desired direction (a new enum)
At any given time, the bandwidth with which we expect to receive
frames from a station may differ from the bandwidth with which
we may transmit frames to the station; this will happen either
during RX OMI negotiation, or for a long time if the station
used an HT Notify Channel Width frame. We also implement the
(VHT) Operating Mode Notification as an asymmetric setting, even
if the spec would seem to imply it could be symmetric.
Also rename the 'cur_max_bandwidth' value to 'op_mode_bw' which
matches the 'op_mode_nss', indicating more clearly what it _is_
rather than how it's _used_. It's not quite precise (NSS is)
since it's also HT chanwidth notification, but that seems OK.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/chan.c | 11 +--
net/mac80211/he.c | 3 +-
net/mac80211/ht.c | 7 +-
net/mac80211/ieee80211_i.h | 7 --
net/mac80211/mlme.c | 5 +-
net/mac80211/sta_info.c | 157 ++++++++++++++++++++++++++++++++++++-
net/mac80211/sta_info.h | 14 +++-
net/mac80211/tdls.c | 8 +-
net/mac80211/vht.c | 136 ++------------------------------
9 files changed, 193 insertions(+), 155 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 62f6d7f4be0d..130d272b048e 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -400,10 +400,10 @@ ieee80211_get_sta_bw(struct sta_info *sta, struct ieee80211_link_data *link)
* We assume that TX/RX might be asymmetric (so e.g. VHT operating
* mode notification changes what a STA wants to receive, but not
* necessarily what it will transmit to us), and therefore use the
- * capabilities here. Calling it RX bandwidth capability is a bit
- * wrong though, since capabilities are in fact symmetric.
+ * "from station" bandwidth here.
*/
- width = ieee80211_sta_cap_rx_bw(link_sta, &link->conf->chanreq.oper);
+ width = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_RX_FROM_STA);
if (width == IEEE80211_STA_RX_BW_20 &&
!link_sta->pub->ht_cap.ht_supported &&
@@ -626,8 +626,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
else
new_chandef = &link_conf->chanreq.oper;
- new_sta_bw = ieee80211_sta_cur_vht_bw(link_sta,
- new_chandef);
+ new_sta_bw = ieee80211_sta_current_bw(link_sta,
+ new_chandef,
+ IEEE80211_STA_BW_TX_TO_STA);
/* nothing change */
if (new_sta_bw == link_sta->pub->bandwidth)
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 2f7f7a834da6..8a9029ab9a8b 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -269,7 +269,8 @@ static void ieee80211_link_sta_rc_update_omi(struct ieee80211_link_data *link,
band = link->conf->chanreq.oper.chan->band;
sband = sdata->local->hw.wiphy->bands[band];
- new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
if (link_sta->pub->bandwidth == new_bw)
return;
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index f0d5c9363a5f..8c98f259380f 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -594,9 +594,10 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
else
max_bw = IEEE80211_STA_RX_BW_MAX;
- /* set cur_max_bandwidth and recalc sta bw */
- link_sta->cur_max_bandwidth = max_bw;
- new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
+ /* set op_mode_bw and recalc sta bw */
+ link_sta->op_mode_bw = max_bw;
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
if (link_sta->pub->bandwidth == new_bw)
return;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 29f51ec142ed..1d9a8ae7390e 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2277,13 +2277,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_vht_cap *vht_cap_ie,
const struct ieee80211_vht_cap *vht_cap_ie2,
struct link_sta_info *link_sta);
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef);
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef);
-
void ieee80211_process_mu_groups(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct ieee80211_mgmt *mgmt);
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 3cd5fe11756f..db55d42a4522 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -2571,8 +2571,9 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
return;
link_sta->pub->bandwidth =
- ieee80211_sta_cur_vht_bw(link_sta,
- &link->csa.chanreq.oper);
+ ieee80211_sta_current_bw(link_sta,
+ &link->csa.chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
return;
}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 9ae32d601b61..76a2536d2f23 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -572,13 +572,13 @@ static int sta_info_alloc_link(struct ieee80211_local *local,
link_info->rx_omi_bw_tx = IEEE80211_STA_RX_BW_MAX;
link_info->rx_omi_bw_staging = IEEE80211_STA_RX_BW_MAX;
- link_info->cur_max_bandwidth = IEEE80211_STA_RX_BW_MAX;
+ link_info->op_mode_bw = IEEE80211_STA_RX_BW_MAX;
/*
* Cause (a) warning(s) if IEEE80211_STA_RX_BW_MAX != 320
* or if new values are added to the enum.
*/
- switch (link_info->cur_max_bandwidth) {
+ switch (link_info->op_mode_bw) {
case IEEE80211_STA_RX_BW_20:
case IEEE80211_STA_RX_BW_40:
case IEEE80211_STA_RX_BW_80:
@@ -3494,7 +3494,9 @@ void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
link_sta->capa_nss = ieee80211_sta_nss_capability(link_sta);
link_sta->pub->rx_nss = link_sta->capa_nss;
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta, chandef);
+ link_sta->pub->bandwidth =
+ ieee80211_sta_current_bw(link_sta, chandef,
+ IEEE80211_STA_BW_TX_TO_STA);
}
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
@@ -3529,3 +3531,152 @@ bool lockdep_sta_mutex_held(struct ieee80211_sta *pubsta)
}
EXPORT_SYMBOL(lockdep_sta_mutex_held);
#endif
+
+/**
+ * ieee80211_sta_bw_capability - get STA's bandwidth capability
+ * @link_sta: the (link) STA to get the capability for
+ * @band: the band to get the capability on
+ *
+ * Return: the maximum bandwidth supported by the STA
+ */
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_bw_capability(struct link_sta_info *link_sta,
+ enum nl80211_band band)
+{
+ struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
+ struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
+ struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
+ u32 cap_width;
+
+ if (he_cap->has_he) {
+ u8 info;
+
+ if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
+ info = eht_cap->eht_cap_elem.phy_cap_info[0];
+
+ if (info & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ)
+ return IEEE80211_STA_RX_BW_320;
+ }
+
+ info = he_cap->he_cap_elem.phy_cap_info[0];
+
+ if (band == NL80211_BAND_2GHZ) {
+ if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)
+ return IEEE80211_STA_RX_BW_40;
+ return IEEE80211_STA_RX_BW_20;
+ }
+
+ if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G ||
+ info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
+ return IEEE80211_STA_RX_BW_160;
+
+ if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)
+ return IEEE80211_STA_RX_BW_80;
+
+ return IEEE80211_STA_RX_BW_20;
+ }
+
+ if (!vht_cap->vht_supported)
+ return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
+ IEEE80211_STA_RX_BW_40 :
+ IEEE80211_STA_RX_BW_20;
+
+ cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
+
+ if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
+ cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
+ return IEEE80211_STA_RX_BW_160;
+
+ /*
+ * If this is non-zero, then it does support 160 MHz after all,
+ * in one form or the other. We don't distinguish here (or even
+ * above) between 160 and 80+80 yet.
+ */
+ if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
+ return IEEE80211_STA_RX_BW_160;
+
+ return IEEE80211_STA_RX_BW_80;
+}
+
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
+{
+ /*
+ * Take RX OMI into account. The value "rx_omi_bw_rx" is what
+ * we've indicated to the STA we can currently receive.
+ *
+ * This is needed since the RX OMI is done by us to save power,
+ * requiring changing both our TX (rate control) and RX (chanctx),
+ * which in turn needs to be done in the right order (stop TX
+ * at a higher bandwidth first while reducing bandwidth, and
+ * change the chanctx only after the peer accepts, etc.)
+ */
+ return min(ieee80211_sta_bw_capability(link_sta, chandef->chan->band),
+ link_sta->rx_omi_bw_rx);
+}
+
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
+{
+ struct sta_info *sta = link_sta->sta;
+ enum nl80211_chan_width bss_width;
+ enum ieee80211_sta_rx_bandwidth bw;
+ enum nl80211_band band;
+
+ bss_width = chandef->width;
+ band = chandef->chan->band;
+
+ bw = ieee80211_sta_bw_capability(link_sta, band);
+ bw = min(bw, link_sta->op_mode_bw);
+ /* also limit to RX OMI bandwidth we TX to the STA */
+ bw = min(bw, link_sta->rx_omi_bw_tx);
+
+ /* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
+ * IEEE80211-2016 specification makes higher bandwidth operation
+ * possible on the TDLS link if the peers have wider bandwidth
+ * capability.
+ *
+ * However, in this case, and only if the TDLS peer is authorized,
+ * limit to the tdls_chandef so that the configuration here isn't
+ * wider than what's actually requested on the channel context.
+ */
+ if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
+ test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) &&
+ test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
+ sta->tdls_chandef.chan)
+ bw = min(bw, ieee80211_chan_width_to_rx_bw(sta->tdls_chandef.width));
+ else
+ bw = min(bw, ieee80211_chan_width_to_rx_bw(bss_width));
+
+ return bw;
+}
+
+/**
+ * ieee80211_sta_current_bw - get STA's current usable bandwidth
+ * @link_sta: the (link) STA to get the bandwidth for
+ * @chandef: the chandef for the channel the STA is on
+ * @direction: the direction (to or from STA)
+ *
+ * Return: the maximum bandwidth that the station can/may
+ * (currently) use in the given direction
+ */
+enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef,
+ enum ieee80211_sta_bw_direction direction)
+{
+ if (WARN_ON(!chandef))
+ return IEEE80211_STA_RX_BW_20;
+
+ switch (direction) {
+ case IEEE80211_STA_BW_RX_FROM_STA:
+ return ieee80211_sta_current_bw_rx_from_sta(link_sta, chandef);
+ case IEEE80211_STA_BW_TX_TO_STA:
+ return ieee80211_sta_current_bw_tx_to_sta(link_sta, chandef);
+ }
+
+ /* unreachable */
+ return IEEE80211_STA_RX_BW_20;
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 46e133dafc61..c1a37a09e8cc 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -504,7 +504,7 @@ struct ieee80211_fragment_cache {
* @status_stats.last_ack_signal: last ACK signal
* @status_stats.ack_signal_filled: last ACK signal validity
* @status_stats.avg_ack_signal: average ACK signal
- * @cur_max_bandwidth: dynamic bandwidth limit for the station,
+ * @op_mode_bw: dynamic bandwidth limit for the station,
* taken from HT/VHT capabilities or VHT operating mode notification
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
* @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
@@ -557,7 +557,7 @@ struct link_sta_info {
u64 msdu[IEEE80211_NUM_TIDS + 1];
} tx_stats;
- enum ieee80211_sta_rx_bandwidth cur_max_bandwidth;
+ enum ieee80211_sta_rx_bandwidth op_mode_bw;
enum ieee80211_sta_rx_bandwidth rx_omi_bw_rx,
rx_omi_bw_tx,
rx_omi_bw_staging;
@@ -1004,6 +1004,16 @@ void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
void __ieee80211_sta_recalc_aggregates(struct sta_info *sta, u16 active_links);
+enum ieee80211_sta_bw_direction {
+ IEEE80211_STA_BW_RX_FROM_STA,
+ IEEE80211_STA_BW_TX_TO_STA,
+};
+
+enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef,
+ enum ieee80211_sta_bw_direction direction);
+
enum sta_stats_type {
STA_STATS_RATE_TYPE_INVALID = 0,
STA_STATS_RATE_TYPE_LEGACY,
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index dcb5fe98ec55..ffd575a8d188 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -314,7 +314,8 @@ ieee80211_tdls_chandef_vht_upgrade(struct ieee80211_sub_if_data *sdata,
enum nl80211_chan_width max_width;
int i;
- switch (ieee80211_sta_cap_rx_bw(&sta->deflink, &uc)) {
+ switch (ieee80211_sta_current_bw(&sta->deflink, &uc,
+ IEEE80211_STA_BW_RX_FROM_STA)) {
case IEEE80211_STA_RX_BW_20:
max_width = NL80211_CHAN_WIDTH_20;
break;
@@ -1337,8 +1338,9 @@ static void iee80211_tdls_recalc_chanctx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_sta_rx_bandwidth bw;
bw = ieee80211_chan_width_to_rx_bw(conf->def.width);
- bw = min(bw, ieee80211_sta_cap_rx_bw(&sta->deflink,
- &conf->def));
+ bw = min(bw, ieee80211_sta_current_bw(&sta->deflink,
+ &conf->def,
+ IEEE80211_STA_BW_RX_FROM_STA));
if (bw != sta->sta.deflink.bandwidth) {
sta->sta.deflink.bandwidth = bw;
rate_control_rate_update(local, sband,
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 3e3efabe9149..34d9ec6cb50c 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -327,129 +327,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
}
-/* FIXME: move this to some better location - parses HE/EHT now */
-static enum ieee80211_sta_rx_bandwidth
-_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta, enum nl80211_band band)
-{
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
- struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
- u32 cap_width;
-
- if (he_cap->has_he) {
- u8 info;
-
- if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
- info = eht_cap->eht_cap_elem.phy_cap_info[0];
-
- if (info & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ)
- return IEEE80211_STA_RX_BW_320;
- }
-
- info = he_cap->he_cap_elem.phy_cap_info[0];
-
- if (band == NL80211_BAND_2GHZ) {
- if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)
- return IEEE80211_STA_RX_BW_40;
- return IEEE80211_STA_RX_BW_20;
- }
-
- if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G ||
- info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
- return IEEE80211_STA_RX_BW_160;
-
- if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)
- return IEEE80211_STA_RX_BW_80;
-
- return IEEE80211_STA_RX_BW_20;
- }
-
- if (!vht_cap->vht_supported)
- return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
- IEEE80211_STA_RX_BW_40 :
- IEEE80211_STA_RX_BW_20;
-
- cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
-
- if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
- cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
- return IEEE80211_STA_RX_BW_160;
-
- /*
- * If this is non-zero, then it does support 160 MHz after all,
- * in one form or the other. We don't distinguish here (or even
- * above) between 160 and 80+80 yet.
- */
- if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
- return IEEE80211_STA_RX_BW_160;
-
- return IEEE80211_STA_RX_BW_80;
-}
-
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
-{
- /*
- * With RX OMI, also pretend that the STA's capability changed.
- * Of course this isn't really true, it didn't change, only our
- * RX capability was changed by notifying RX OMI to the STA.
- * The purpose, however, is to save power, and that requires
- * changing also transmissions to the AP and the chanctx. The
- * transmissions depend on link_sta->bandwidth which is set in
- * ieee80211_sta_cur_vht_bw() below, but the chanctx depends
- * on the result of this function which is also called by
- * ieee80211_sta_cur_vht_bw(), so we need to do that here as
- * well. This is sufficient for the steady state, but during
- * the transition we already need to change TX/RX separately,
- * so ieee80211_sta_cur_vht_bw() below applies the _tx one.
- */
- return min(_ieee80211_sta_cap_rx_bw(link_sta, chandef->chan->band),
- link_sta->rx_omi_bw_rx);
-}
-
-/* FIXME: rename/move - this deals with everything not just VHT */
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
-{
- struct sta_info *sta = link_sta->sta;
- enum nl80211_chan_width bss_width;
- enum ieee80211_sta_rx_bandwidth bw;
- enum nl80211_band band;
-
- if (WARN_ON(!chandef))
- return IEEE80211_STA_RX_BW_20;
-
- bss_width = chandef->width;
- band = chandef->chan->band;
-
- /* intentionally do not take rx_bw_omi_rx into account */
- bw = _ieee80211_sta_cap_rx_bw(link_sta, band);
- bw = min(bw, link_sta->cur_max_bandwidth);
- /* but do apply rx_omi_bw_tx */
- bw = min(bw, link_sta->rx_omi_bw_tx);
-
- /* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
- * IEEE80211-2016 specification makes higher bandwidth operation
- * possible on the TDLS link if the peers have wider bandwidth
- * capability.
- *
- * However, in this case, and only if the TDLS peer is authorized,
- * limit to the tdls_chandef so that the configuration here isn't
- * wider than what's actually requested on the channel context.
- */
- if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
- test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) &&
- test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
- sta->tdls_chandef.chan)
- bw = min(bw, ieee80211_chan_width_to_rx_bw(sta->tdls_chandef.width));
- else
- bw = min(bw, ieee80211_chan_width_to_rx_bw(bss_width));
-
- return bw;
-}
-
u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
struct link_sta_info *link_sta,
u8 opmode, enum nl80211_band band)
@@ -492,25 +369,26 @@ u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
switch (opmode & IEEE80211_OPMODE_NOTIF_CHANWIDTH_MASK) {
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_20MHZ:
/* ignore IEEE80211_OPMODE_NOTIF_BW_160_80P80 must not be set */
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_20;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_20;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_40MHZ:
/* ignore IEEE80211_OPMODE_NOTIF_BW_160_80P80 must not be set */
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_40;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_40;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_80MHZ:
if (opmode & IEEE80211_OPMODE_NOTIF_BW_160_80P80)
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_160;
else
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_80;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_160MHZ:
/* legacy only, no longer used by newer spec */
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_160;
break;
}
- new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
if (new_bw != link_sta->pub->bandwidth) {
link_sta->pub->bandwidth = new_bw;
sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 17/19] wifi: nl80211: always validate AP operation/PHY regulatory
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Instead of validating the AP operation elements and PHY regulatory
individually in each caller, which missed CSA and color change,
pass the channel to the beacon parsing function and validate the
parameters there. This adds it to the missing places.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/nl80211.c | 260 +++++++++++++++++++----------------------
1 file changed, 123 insertions(+), 137 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f65f3887ea62..b9c7dc099b33 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6394,9 +6394,104 @@ static int nl80211_parse_he_bss_color(struct nlattr *attrs,
return 0;
}
+static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
+ const struct element *rates)
+{
+ int i;
+
+ if (!rates)
+ return;
+
+ for (i = 0; i < rates->datalen; i++) {
+ if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
+ bcn->ht_required = true;
+ if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
+ bcn->vht_required = true;
+ }
+}
+
+/*
+ * Since the nl80211 API didn't include, from the beginning, attributes about
+ * HT/VHT/... operation, we parse them out of the elements and check for
+ * validity for use by drivers/mac80211.
+ */
+static int nl80211_calculate_ap_operation(struct nlattr *attrs[],
+ struct cfg80211_beacon_data *bcn,
+ struct netlink_ext_ack *extack)
+{
+ size_t ies_len = bcn->tail_len;
+ const u8 *ies = bcn->tail;
+ const struct element *rates;
+ const struct element *op;
+
+ rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
+ nl80211_check_ap_rate_selectors(bcn, rates);
+
+ rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
+ nl80211_check_ap_rate_selectors(bcn, rates);
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->he_oper) + 1) {
+ NL_SET_ERR_MSG(extack, "bad HE operation in beacon");
+ return -EINVAL;
+ }
+ bcn->he_oper = (void *)(op->data + 1);
+ /* takes extension ID into account */
+ if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) {
+ NL_SET_ERR_MSG(extack, "bad HE operation in beacon");
+ return -EINVAL;
+ }
+ }
+
+ op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->ht_oper)) {
+ NL_SET_ERR_MSG(extack, "bad HT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->ht_oper = (void *)op->data;
+ }
+
+ op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->vht_oper)) {
+ NL_SET_ERR_MSG(extack, "bad VHT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->vht_oper = (void *)op->data;
+ }
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
+ if (op) {
+ if (!ieee80211_eht_oper_size_ok(op->data + 1,
+ op->datalen - 1)) {
+ NL_SET_ERR_MSG(extack, "bad EHT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->eht_oper = (void *)(op->data + 1);
+ }
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
+ if (op) {
+ /* need full UHR operation separately */
+ if (!attrs[NL80211_ATTR_UHR_OPERATION]) {
+ NL_SET_ERR_MSG(extack, "missing UHR operation");
+ return -EINVAL;
+ }
+ bcn->uhr_oper = nla_data(attrs[NL80211_ATTR_UHR_OPERATION]);
+ } else if (attrs[NL80211_ATTR_UHR_OPERATION]) {
+ NL_SET_ERR_MSG(extack, "unexpected UHR operation");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs[],
struct cfg80211_beacon_data *bcn,
+ struct ieee80211_channel *chan,
struct netlink_ext_ack *extack)
{
bool haveinfo = false;
@@ -6511,6 +6606,19 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
}
}
+ err = nl80211_calculate_ap_operation(attrs, bcn, extack);
+ if (err)
+ return err;
+
+ if (bcn->he_oper && (chan->flags & IEEE80211_CHAN_NO_HE))
+ return -EOPNOTSUPP;
+
+ if (bcn->eht_oper && (chan->flags & IEEE80211_CHAN_NO_EHT))
+ return -EOPNOTSUPP;
+
+ if (bcn->uhr_oper && (chan->flags & IEEE80211_CHAN_NO_UHR))
+ return -EOPNOTSUPP;
+
return 0;
}
@@ -6628,22 +6736,6 @@ nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev,
return 0;
}
-static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
- const struct element *rates)
-{
- int i;
-
- if (!rates)
- return;
-
- for (i = 0; i < rates->datalen; i++) {
- if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
- bcn->ht_required = true;
- if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
- bcn->vht_required = true;
- }
-}
-
/*
* Since the nl80211 API didn't include, from the beginning, attributes about
* HT/VHT/... capabilities, we parse them out of the elements and check for
@@ -6697,83 +6789,6 @@ static int nl80211_calculate_ap_capabilities(struct genl_info *info,
return 0;
}
-/*
- * Since the nl80211 API didn't include, from the beginning, attributes about
- * HT/VHT/... operation, we parse them out of the elements and check for
- * validity for use by drivers/mac80211.
- */
-static int nl80211_calculate_ap_operation(struct genl_info *info,
- struct cfg80211_beacon_data *bcn)
-{
- size_t ies_len = bcn->tail_len;
- const u8 *ies = bcn->tail;
- const struct element *rates;
- const struct element *op;
-
- rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
- nl80211_check_ap_rate_selectors(bcn, rates);
-
- rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
- nl80211_check_ap_rate_selectors(bcn, rates);
-
- op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
- if (op) {
- if (op->datalen < sizeof(*bcn->he_oper) + 1) {
- GENL_SET_ERR_MSG(info, "bad HE operation in beacon");
- return -EINVAL;
- }
- bcn->he_oper = (void *)(op->data + 1);
- /* takes extension ID into account */
- if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) {
- GENL_SET_ERR_MSG(info, "bad HE operation in beacon");
- return -EINVAL;
- }
- }
-
- op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len);
- if (op) {
- if (op->datalen < sizeof(*bcn->ht_oper)) {
- GENL_SET_ERR_MSG(info, "bad HT operation in beacon");
- return -EINVAL;
- }
- bcn->ht_oper = (void *)op->data;
- }
-
- op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len);
- if (op) {
- if (op->datalen < sizeof(*bcn->vht_oper)) {
- GENL_SET_ERR_MSG(info, "bad VHT operation in beacon");
- return -EINVAL;
- }
- bcn->vht_oper = (void *)op->data;
- }
-
- op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
- if (op) {
- if (!ieee80211_eht_oper_size_ok(op->data + 1,
- op->datalen - 1)) {
- GENL_SET_ERR_MSG(info, "bad EHT operation in beacon");
- return -EINVAL;
- }
- bcn->eht_oper = (void *)(op->data + 1);
- }
-
- op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
- if (op) {
- /* need full UHR operation separately */
- if (!info->attrs[NL80211_ATTR_UHR_OPERATION]) {
- GENL_SET_ERR_MSG(info, "missing UHR operation");
- return -EINVAL;
- }
- bcn->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]);
- } else if (info->attrs[NL80211_ATTR_UHR_OPERATION]) {
- GENL_SET_ERR_MSG(info, "unexpected UHR operation");
- return -EINVAL;
- }
-
- return 0;
-}
-
static bool nl80211_get_ap_channel(struct cfg80211_registered_device *rdev,
struct cfg80211_ap_settings *params)
{
@@ -6898,21 +6913,6 @@ static void nl80211_send_ap_started(struct wireless_dev *wdev,
nlmsg_free(msg);
}
-static int nl80211_validate_ap_phy_operation(struct ieee80211_channel *channel,
- struct cfg80211_beacon_data *bcn)
-{
- if (bcn->he_oper && (channel->flags & IEEE80211_CHAN_NO_HE))
- return -EOPNOTSUPP;
-
- if (bcn->eht_oper && (channel->flags & IEEE80211_CHAN_NO_EHT))
- return -EOPNOTSUPP;
-
- if (bcn->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
- return -EOPNOTSUPP;
-
- return 0;
-}
-
static int
nl80211_parse_s1g_short_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs,
@@ -6985,11 +6985,6 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (!params)
return -ENOMEM;
- err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon,
- info->extack);
- if (err)
- goto out;
-
params->beacon_interval =
nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
params->dtim_period =
@@ -7106,6 +7101,11 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
goto out;
}
+ err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon,
+ params->chandef.chan, info->extack);
+ if (err)
+ goto out;
+
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
beacon_check.reg_power =
@@ -7209,15 +7209,6 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (err)
goto out;
- err = nl80211_calculate_ap_operation(info, ¶ms->beacon);
- if (err)
- goto out;
-
- err = nl80211_validate_ap_phy_operation(params->chandef.chan,
- ¶ms->beacon);
- if (err)
- goto out;
-
if (info->attrs[NL80211_ATTR_AP_SETTINGS_FLAGS])
params->flags = nla_get_u32(
info->attrs[NL80211_ATTR_AP_SETTINGS_FLAGS]);
@@ -7285,19 +7276,11 @@ static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info)
return -ENOMEM;
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon,
+ wdev->links[link_id].ap.chandef.chan,
info->extack);
if (err)
goto out;
- err = nl80211_calculate_ap_operation(info, ¶ms->beacon);
- if (err)
- goto out;
-
- err = nl80211_validate_ap_phy_operation(wdev->links[link_id].ap.chandef.chan,
- ¶ms->beacon);
- if (err)
- goto out;
-
/* recheck beaconing is permitted with possibly changed power type */
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
@@ -11898,11 +11881,16 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
params.count = cs_count;
+ err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
+ ¶ms.chandef);
+ if (err)
+ goto free;
+
if (!need_new_beacon)
goto skip_beacons;
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms.beacon_after,
- info->extack);
+ params.chandef.chan, info->extack);
if (err)
goto free;
@@ -11919,6 +11907,7 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
goto free;
err = nl80211_parse_beacon(rdev, csa_attrs, ¶ms.beacon_csa,
+ wdev->links[link_id].ap.chandef.chan,
info->extack);
if (err)
goto free;
@@ -11947,11 +11936,6 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
goto free;
skip_beacons:
- err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- ¶ms.chandef);
- if (err)
- goto free;
-
if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, ¶ms.chandef,
wdev->iftype)) {
err = -EINVAL;
@@ -18405,6 +18389,7 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
params.color = nla_get_u8(info->attrs[NL80211_ATTR_COLOR_CHANGE_COLOR]);
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms.beacon_next,
+ wdev->links[params.link_id].ap.chandef.chan,
info->extack);
if (err)
return err;
@@ -18420,6 +18405,7 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
goto out;
err = nl80211_parse_beacon(rdev, tb, ¶ms.beacon_color_change,
+ wdev->links[params.link_id].ap.chandef.chan,
info->extack);
if (err)
goto out;
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 08/19] wifi: mac80211: clean up STA NSS handling
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Move ieee80211_sta_init_nss() from VHT code to station code,
and disentangle it from rate control. This way, it becomes
clearer when 'rx_nss' is set up.
While doing this, fix the client side code to set up
link_sta->op_mode_nss instead of link_sta->pub->rx_nss for
the opmode element in association response, and remove the
(now wrong) comment about handling that in the function.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ibss.c | 2 +
net/mac80211/ieee80211_i.h | 2 +-
net/mac80211/mesh_plink.c | 2 +
net/mac80211/mlme.c | 11 +++--
net/mac80211/ocb.c | 4 +-
net/mac80211/rate.c | 4 +-
net/mac80211/sta_info.c | 92 +++++++++++++++++++++++++++++++++++++
net/mac80211/sta_info.h | 1 +
net/mac80211/vht.c | 93 --------------------------------------
9 files changed, 108 insertions(+), 103 deletions(-)
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 1e1ab25d9d8d..b65f090a35dc 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -553,6 +553,8 @@ static struct sta_info *ieee80211_ibss_finish_sta(struct sta_info *sta)
memcpy(addr, sta->sta.addr, ETH_ALEN);
+ ieee80211_sta_init_nss(&sta->deflink);
+
ibss_dbg(sdata, "Adding new IBSS station %pM\n", addr);
sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index f8f728619249..a683a79b7dcc 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2293,7 +2293,7 @@ ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta)
{
return _ieee80211_sta_cur_vht_bw(link_sta, NULL);
}
-void ieee80211_sta_init_nss(struct link_sta_info *link_sta);
+
void ieee80211_process_mu_groups(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct ieee80211_mgmt *mgmt);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 803106fc3134..99c666fb2d17 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -470,6 +470,8 @@ static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
elems->eht_cap, elems->eht_cap_len,
&sta->deflink);
+ ieee80211_sta_init_nss(&sta->deflink);
+
if (bw != sta->sta.deflink.bandwidth)
changed |= IEEE80211_RC_BW_CHANGED;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 7fc5616cb244..3a0fd3dc976c 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -5749,9 +5749,9 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
*/
/*
- * If an operating mode notification IE is present, override the
- * NSS calculation (that would be done in rate_control_rate_init())
- * and use the # of streams from that element.
+ * If an operating mode notification element is present, set the opmode
+ * NSS override to correct for the current number of spatial streams,
+ * overriding the capabilities. ieee80211_sta_init_nss() uses this.
*/
if (elems->opmode_notif &&
!(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
@@ -5760,9 +5760,11 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
nss += 1;
- link_sta->pub->rx_nss = nss;
+ link_sta->op_mode_nss = nss;
}
+ ieee80211_sta_init_nss(link_sta);
+
/*
* Always handle WMM once after association regardless
* of the first value the AP uses. Setting -1 here has
@@ -10620,7 +10622,6 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
if (add_links_data->link[link_id].status != WLAN_STATUS_SUCCESS)
goto disconnect;
- ieee80211_sta_init_nss(link_sta);
if (ieee80211_sta_activate_link(sta, link_id))
goto disconnect;
diff --git a/net/mac80211/ocb.c b/net/mac80211/ocb.c
index ebb4f4d88c23..447c84235c1c 100644
--- a/net/mac80211/ocb.c
+++ b/net/mac80211/ocb.c
@@ -4,7 +4,7 @@
*
* Copyright: (c) 2014 Czech Technical University in Prague
* (c) 2014 Volkswagen Group Research
- * Copyright (C) 2022 - 2024 Intel Corporation
+ * Copyright (C) 2022 - 2024, 2026 Intel Corporation
* Author: Rostislav Lisovy <rostislav.lisovy@fel.cvut.cz>
* Funded by: Volkswagen Group Research
*/
@@ -92,6 +92,8 @@ static struct sta_info *ieee80211_ocb_finish_sta(struct sta_info *sta)
memcpy(addr, sta->sta.addr, ETH_ALEN);
+ ieee80211_sta_init_nss(&sta->deflink);
+
ocb_dbg(sdata, "Adding new IBSS station %pM (dev=%s)\n",
addr, sdata->name);
diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c
index 31af7dd6aedc..ba1a3aa3f5d4 100644
--- a/net/mac80211/rate.c
+++ b/net/mac80211/rate.c
@@ -4,7 +4,7 @@
* Copyright 2005-2006, Devicescape Software, Inc.
* Copyright (c) 2006 Jiri Benc <jbenc@suse.cz>
* Copyright 2017 Intel Deutschland GmbH
- * Copyright (C) 2019, 2022-2025 Intel Corporation
+ * Copyright (C) 2019, 2022-2026 Intel Corporation
*/
#include <linux/kernel.h>
@@ -38,8 +38,6 @@ void rate_control_rate_init(struct link_sta_info *link_sta)
struct ieee80211_supported_band *sband;
struct ieee80211_chanctx_conf *chanctx_conf;
- ieee80211_sta_init_nss(link_sta);
-
if (!ref)
return;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index b3a016f3736b..31cf45095c60 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3401,6 +3401,98 @@ void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
sta_remove_link(sta, link_id, true);
}
+void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
+{
+ u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
+ bool support_160;
+
+ if (link_sta->pub->eht_cap.has_eht) {
+ int i;
+ const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
+
+ /* get the max nss for EHT over all possible bandwidths and mcs */
+ for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
+ eht_rx_nss = max_t(u8, eht_rx_nss,
+ u8_get_bits(rx_nss_mcs[i],
+ IEEE80211_EHT_MCS_NSS_RX));
+ }
+
+ if (link_sta->pub->he_cap.has_he) {
+ int i;
+ u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
+ const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
+ u16 mcs_160_map =
+ le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
+ u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
+
+ for (i = 7; i >= 0; i--) {
+ u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
+
+ if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
+ rx_mcs_160 = i + 1;
+ break;
+ }
+ }
+ for (i = 7; i >= 0; i--) {
+ u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
+
+ if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
+ rx_mcs_80 = i + 1;
+ break;
+ }
+ }
+
+ support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+
+ if (support_160)
+ he_rx_nss = min(rx_mcs_80, rx_mcs_160);
+ else
+ he_rx_nss = rx_mcs_80;
+ }
+
+ if (link_sta->pub->ht_cap.ht_supported) {
+ if (link_sta->pub->ht_cap.mcs.rx_mask[0])
+ ht_rx_nss++;
+ if (link_sta->pub->ht_cap.mcs.rx_mask[1])
+ ht_rx_nss++;
+ if (link_sta->pub->ht_cap.mcs.rx_mask[2])
+ ht_rx_nss++;
+ if (link_sta->pub->ht_cap.mcs.rx_mask[3])
+ ht_rx_nss++;
+ /* FIXME: consider rx_highest? */
+ }
+
+ if (link_sta->pub->vht_cap.vht_supported) {
+ int i;
+ u16 rx_mcs_map;
+
+ rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
+
+ for (i = 7; i >= 0; i--) {
+ u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
+
+ if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
+ vht_rx_nss = i + 1;
+ break;
+ }
+ }
+ /* FIXME: consider rx_highest? */
+ }
+
+ rx_nss = max(vht_rx_nss, ht_rx_nss);
+ rx_nss = max(he_rx_nss, rx_nss);
+ rx_nss = max(eht_rx_nss, rx_nss);
+ rx_nss = max_t(u8, 1, rx_nss);
+ link_sta->capa_nss = rx_nss;
+
+ if (link_sta->op_mode_nss)
+ link_sta->pub->rx_nss =
+ min_t(u8, rx_nss, link_sta->op_mode_nss);
+ else
+ link_sta->pub->rx_nss = rx_nss;
+}
+
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
const u8 *ext_capab,
unsigned int ext_capab_len)
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 92ba477e7c37..9c827199f949 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -996,6 +996,7 @@ void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);
unsigned long ieee80211_sta_last_active(struct sta_info *sta, int link_id);
+void ieee80211_sta_init_nss(struct link_sta_info *link_sta);
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
const u8 *ext_capab,
unsigned int ext_capab_len);
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index a72cf532bec7..63aa64b77ca0 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -475,99 +475,6 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
return bw;
}
-void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
-{
- u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
- bool support_160;
-
- if (link_sta->pub->eht_cap.has_eht) {
- int i;
- const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
-
- /* get the max nss for EHT over all possible bandwidths and mcs */
- for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
- eht_rx_nss = max_t(u8, eht_rx_nss,
- u8_get_bits(rx_nss_mcs[i],
- IEEE80211_EHT_MCS_NSS_RX));
- }
-
- if (link_sta->pub->he_cap.has_he) {
- int i;
- u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
- u16 mcs_160_map =
- le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
- u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
-
- for (i = 7; i >= 0; i--) {
- u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
-
- if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
- rx_mcs_160 = i + 1;
- break;
- }
- }
- for (i = 7; i >= 0; i--) {
- u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
-
- if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
- rx_mcs_80 = i + 1;
- break;
- }
- }
-
- support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
- IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
-
- if (support_160)
- he_rx_nss = min(rx_mcs_80, rx_mcs_160);
- else
- he_rx_nss = rx_mcs_80;
- }
-
- if (link_sta->pub->ht_cap.ht_supported) {
- if (link_sta->pub->ht_cap.mcs.rx_mask[0])
- ht_rx_nss++;
- if (link_sta->pub->ht_cap.mcs.rx_mask[1])
- ht_rx_nss++;
- if (link_sta->pub->ht_cap.mcs.rx_mask[2])
- ht_rx_nss++;
- if (link_sta->pub->ht_cap.mcs.rx_mask[3])
- ht_rx_nss++;
- /* FIXME: consider rx_highest? */
- }
-
- if (link_sta->pub->vht_cap.vht_supported) {
- int i;
- u16 rx_mcs_map;
-
- rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
-
- for (i = 7; i >= 0; i--) {
- u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
-
- if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
- vht_rx_nss = i + 1;
- break;
- }
- }
- /* FIXME: consider rx_highest? */
- }
-
- rx_nss = max(vht_rx_nss, ht_rx_nss);
- rx_nss = max(he_rx_nss, rx_nss);
- rx_nss = max(eht_rx_nss, rx_nss);
- rx_nss = max_t(u8, 1, rx_nss);
- link_sta->capa_nss = rx_nss;
-
- /* that shouldn't be set yet, but we can handle it anyway */
- if (link_sta->op_mode_nss)
- link_sta->pub->rx_nss =
- min_t(u8, rx_nss, link_sta->op_mode_nss);
- else
- link_sta->pub->rx_nss = rx_nss;
-}
-
u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
struct link_sta_info *link_sta,
u8 opmode, enum nl80211_band band)
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox